From 6026f4e674efa1c5dbc274e8e0e7ca42bff03616 Mon Sep 17 00:00:00 2001 From: ph1ll Date: Tue, 26 Feb 2019 16:01:21 +0000 Subject: [PATCH] Initial Commit --- .gitattributes | 63 +++ .gitignore | 261 ++++++++++ CodeMaid.config | 47 ++ Directory.Build.props | 30 ++ LICENSE | 21 + README.md | 37 ++ RuleSet.ruleset | 29 ++ ShareAudit.sln | 63 +++ .../AccessRuleEntry.cs | 79 +++ src/Dionach.ShareAudit.Model/Configuration.cs | 126 +++++ src/Dionach.ShareAudit.Model/Credentials.cs | 81 +++ .../Dionach.ShareAudit.Model.csproj | 64 +++ .../DirectoryEntry.cs | 40 ++ .../EffectiveAccess.cs | 46 ++ src/Dionach.ShareAudit.Model/FileEntry.cs | 38 ++ .../FileEntryState.cs | 11 + .../FileSystemEntry.cs | 100 ++++ .../FolderEntryState.cs | 14 + src/Dionach.ShareAudit.Model/Host.cs | 113 ++++ src/Dionach.ShareAudit.Model/HostState.cs | 13 + src/Dionach.ShareAudit.Model/IFolderEntry.cs | 17 + .../ImportComputerType.cs | 9 + src/Dionach.ShareAudit.Model/Project.cs | 63 +++ src/Dionach.ShareAudit.Model/ProjectState.cs | 11 + .../Properties/AssemblyInfo.cs | 31 ++ src/Dionach.ShareAudit.Model/Share.cs | 73 +++ src/Dionach.ShareAudit.Model/ShareType.cs | 72 +++ .../Advapi32.cs | 37 ++ ...ShareAudit.Modules.Services.Interop.csproj | 51 ++ .../Mpr.cs | 59 +++ .../Netapi32.cs | 33 ++ .../Properties/AssemblyInfo.cs | 31 ++ .../CredentialsValidationService.cs | 93 ++++ ...Dionach.ShareAudit.Modules.Services.csproj | 134 +++++ .../DnsUtilitiesService.cs | 47 ++ .../FileSystemOperationService.cs | 256 ++++++++++ .../FileSystemStoreService.cs | 95 ++++ .../ICredentialsValidationService.cs | 18 + .../IDnsUtilitiesService.cs | 12 + .../IFileSystemOperationService.cs | 18 + .../IFileSystemStoreService.cs | 24 + .../IImportScopeFromActiveDirectoryService.cs | 12 + .../IPortScanService.cs | 7 + .../IScopeExpansionService.cs | 10 + .../IScopeNormalizationService.cs | 9 + .../IScopeValidationService.cs | 9 + .../IShareAuditService.cs | 16 + .../ISidUtilitiesService.cs | 7 + .../ISmbUtilitiesService.cs | 11 + .../ImportScopeFromActiveDirectoryService.cs | 100 ++++ .../NetUseConnection.cs | 76 +++ .../PortScanService.cs | 31 ++ .../Properties/AssemblyInfo.cs | 41 ++ .../Properties/Resources.Designer.cs | 72 +++ .../Properties/Resources.resx | 117 +++++ .../Properties/Settings.Designer.cs | 30 ++ .../Properties/Settings.settings | 7 + .../ScopeExpansionService.cs | 64 +++ .../ScopeNormalizationService.cs | 23 + .../ScopeValidationService.cs | 23 + .../ServicesModule.cs | 28 + .../ShareAuditService.cs | 481 ++++++++++++++++++ .../ShareInfo1.cs | 21 + .../SidUtilitiesService.cs | 73 +++ .../SmbUtilitiesService.cs | 58 +++ .../Converters/AndTrueToTrueMultiConverter.cs | 29 ++ .../AndTrueToVisibleMultiConverter.cs | 30 ++ .../Converters/Base64Converter.cs | 33 ++ .../Converters/BoolInversionConverter.cs | 22 + .../Converters/ComparisonConverter.cs | 21 + .../Converters/IsFalseToCollapsedConverter.cs | 22 + .../IsStateInProgressToBoolConverter.cs | 38 ++ .../Converters/IsTrueToCollapsedConverter.cs | 22 + .../Converters/ItemDetailToStringConverter.cs | 61 +++ .../OrTrueToVisibleMultiConverter.cs | 31 ++ .../ShareTypeToIconSourceConverter.cs | 41 ++ ...StringIsNullOrEmptyToCollapsedConverter.cs | 22 + .../StringIsNullOrEmptyToTrueConverter.cs | 21 + ...ch.ShareAudit.Modules.UserInterface.csproj | 170 +++++++ .../Helpers/BindingProxy.cs | 21 + .../Helpers/PasswordHelper.cs | 68 +++ .../Helpers/TreeViewExtension.cs | 65 +++ .../Helpers/TreeViewHelper.cs | 62 +++ .../Images/Banner.png | Bin 0 -> 46935 bytes .../Images/Icon.png | Bin 0 -> 9755 bytes .../Images/imageres_2.ico | Bin 0 -> 48049 bytes .../Images/imageres_32.ico | Bin 0 -> 47740 bytes .../Images/imageres_4.ico | Bin 0 -> 51902 bytes .../Images/imageres_51.ico | Bin 0 -> 52614 bytes .../Images/imageres_78.ico | Bin 0 -> 50871 bytes .../Properties/AssemblyInfo.cs | 41 ++ .../Properties/Resources.Designer.cs | 72 +++ .../Properties/Resources.resx | 117 +++++ .../Properties/Settings.Designer.cs | 30 ++ .../Properties/Settings.settings | 7 + .../UserInterfaceModule.cs | 27 + .../ViewModels/AuditViewModel.cs | 231 +++++++++ .../ViewModels/ConfigurationViewModel.cs | 283 +++++++++++ .../ViewModels/HelpViewModel.cs | 24 + .../ViewModels/ImportScopeViewModel.cs | 123 +++++ .../ViewModels/WelcomeViewModel.cs | 97 ++++ .../Views/AuditView.xaml | 219 ++++++++ .../Views/AuditView.xaml.cs | 15 + .../Views/ConfigurationView.xaml | 212 ++++++++ .../Views/ConfigurationView.xaml.cs | 15 + .../Views/HelpView.xaml | 127 +++++ .../Views/HelpView.xaml.cs | 15 + .../Views/ImportScopeView.xaml | 71 +++ .../Views/ImportScopeView.xaml.cs | 15 + .../Views/WelcomeView.xaml | 56 ++ .../Views/WelcomeView.xaml.cs | 15 + src/Dionach.ShareAudit/App.xaml | 8 + src/Dionach.ShareAudit/App.xaml.cs | 90 ++++ .../Dionach.ShareAudit.csproj | 129 +++++ src/Dionach.ShareAudit/FodyWeavers.xml | 4 + src/Dionach.ShareAudit/FodyWeavers.xsd | 111 ++++ src/Dionach.ShareAudit/Icon.ico | Bin 0 -> 108971 bytes src/Dionach.ShareAudit/Images/Icon.ico | Bin 0 -> 108971 bytes .../Properties/AssemblyInfo.cs | 41 ++ .../Properties/Resources.Designer.cs | 71 +++ .../Properties/Resources.resx | 117 +++++ .../Properties/Settings.Designer.cs | 30 ++ .../Properties/Settings.settings | 7 + .../ViewModels/MainWindowViewModel.cs | 19 + src/Dionach.ShareAudit/Views/ErrorWindow.xaml | 38 ++ .../Views/ErrorWindow.xaml.cs | 21 + src/Dionach.ShareAudit/Views/MainWindow.xaml | 16 + .../Views/MainWindow.xaml.cs | 15 + 128 files changed, 7094 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 CodeMaid.config create mode 100644 Directory.Build.props create mode 100644 LICENSE create mode 100644 README.md create mode 100644 RuleSet.ruleset create mode 100644 ShareAudit.sln create mode 100644 src/Dionach.ShareAudit.Model/AccessRuleEntry.cs create mode 100644 src/Dionach.ShareAudit.Model/Configuration.cs create mode 100644 src/Dionach.ShareAudit.Model/Credentials.cs create mode 100644 src/Dionach.ShareAudit.Model/Dionach.ShareAudit.Model.csproj create mode 100644 src/Dionach.ShareAudit.Model/DirectoryEntry.cs create mode 100644 src/Dionach.ShareAudit.Model/EffectiveAccess.cs create mode 100644 src/Dionach.ShareAudit.Model/FileEntry.cs create mode 100644 src/Dionach.ShareAudit.Model/FileEntryState.cs create mode 100644 src/Dionach.ShareAudit.Model/FileSystemEntry.cs create mode 100644 src/Dionach.ShareAudit.Model/FolderEntryState.cs create mode 100644 src/Dionach.ShareAudit.Model/Host.cs create mode 100644 src/Dionach.ShareAudit.Model/HostState.cs create mode 100644 src/Dionach.ShareAudit.Model/IFolderEntry.cs create mode 100644 src/Dionach.ShareAudit.Model/ImportComputerType.cs create mode 100644 src/Dionach.ShareAudit.Model/Project.cs create mode 100644 src/Dionach.ShareAudit.Model/ProjectState.cs create mode 100644 src/Dionach.ShareAudit.Model/Properties/AssemblyInfo.cs create mode 100644 src/Dionach.ShareAudit.Model/Share.cs create mode 100644 src/Dionach.ShareAudit.Model/ShareType.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services.Interop/Advapi32.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services.Interop/Dionach.ShareAudit.Modules.Services.Interop.csproj create mode 100644 src/Dionach.ShareAudit.Modules.Services.Interop/Mpr.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services.Interop/Netapi32.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services.Interop/Properties/AssemblyInfo.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/CredentialsValidationService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/Dionach.ShareAudit.Modules.Services.csproj create mode 100644 src/Dionach.ShareAudit.Modules.Services/DnsUtilitiesService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/FileSystemOperationService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/FileSystemStoreService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/ICredentialsValidationService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/IDnsUtilitiesService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/IFileSystemOperationService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/IFileSystemStoreService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/IImportScopeFromActiveDirectoryService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/IPortScanService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/IScopeExpansionService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/IScopeNormalizationService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/IScopeValidationService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/IShareAuditService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/ISidUtilitiesService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/ISmbUtilitiesService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/ImportScopeFromActiveDirectoryService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/NetUseConnection.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/PortScanService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/Properties/AssemblyInfo.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/Properties/Resources.Designer.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/Properties/Resources.resx create mode 100644 src/Dionach.ShareAudit.Modules.Services/Properties/Settings.Designer.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/Properties/Settings.settings create mode 100644 src/Dionach.ShareAudit.Modules.Services/ScopeExpansionService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/ScopeNormalizationService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/ScopeValidationService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/ServicesModule.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/ShareAuditService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/ShareInfo1.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/SidUtilitiesService.cs create mode 100644 src/Dionach.ShareAudit.Modules.Services/SmbUtilitiesService.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToTrueMultiConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToVisibleMultiConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/Base64Converter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/BoolInversionConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/ComparisonConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsFalseToCollapsedConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsStateInProgressToBoolConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsTrueToCollapsedConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/ItemDetailToStringConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/OrTrueToVisibleMultiConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/ShareTypeToIconSourceConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToCollapsedConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToTrueConverter.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Dionach.ShareAudit.Modules.UserInterface.csproj create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Helpers/BindingProxy.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Helpers/PasswordHelper.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewExtension.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewHelper.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Images/Banner.png create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Images/Icon.png create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_2.ico create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_32.ico create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_4.ico create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_51.ico create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_78.ico create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Properties/AssemblyInfo.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.Designer.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.resx create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.Designer.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.settings create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/UserInterfaceModule.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/AuditViewModel.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ConfigurationViewModel.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/HelpViewModel.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ImportScopeViewModel.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/WelcomeViewModel.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Views/AuditView.xaml create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Views/AuditView.xaml.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Views/ConfigurationView.xaml create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Views/ConfigurationView.xaml.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Views/HelpView.xaml create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Views/HelpView.xaml.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Views/ImportScopeView.xaml create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Views/ImportScopeView.xaml.cs create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Views/WelcomeView.xaml create mode 100644 src/Dionach.ShareAudit.Modules.UserInterface/Views/WelcomeView.xaml.cs create mode 100644 src/Dionach.ShareAudit/App.xaml create mode 100644 src/Dionach.ShareAudit/App.xaml.cs create mode 100644 src/Dionach.ShareAudit/Dionach.ShareAudit.csproj create mode 100644 src/Dionach.ShareAudit/FodyWeavers.xml create mode 100644 src/Dionach.ShareAudit/FodyWeavers.xsd create mode 100644 src/Dionach.ShareAudit/Icon.ico create mode 100644 src/Dionach.ShareAudit/Images/Icon.ico create mode 100644 src/Dionach.ShareAudit/Properties/AssemblyInfo.cs create mode 100644 src/Dionach.ShareAudit/Properties/Resources.Designer.cs create mode 100644 src/Dionach.ShareAudit/Properties/Resources.resx create mode 100644 src/Dionach.ShareAudit/Properties/Settings.Designer.cs create mode 100644 src/Dionach.ShareAudit/Properties/Settings.settings create mode 100644 src/Dionach.ShareAudit/ViewModels/MainWindowViewModel.cs create mode 100644 src/Dionach.ShareAudit/Views/ErrorWindow.xaml create mode 100644 src/Dionach.ShareAudit/Views/ErrorWindow.xaml.cs create mode 100644 src/Dionach.ShareAudit/Views/MainWindow.xaml create mode 100644 src/Dionach.ShareAudit/Views/MainWindow.xaml.cs diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..1ff0c42 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,63 @@ +############################################################################### +# Set default behavior to automatically normalize line endings. +############################################################################### +* text=auto + +############################################################################### +# Set default behavior for command prompt diff. +# +# This is need for earlier builds of msysgit that does not have it on by +# default for csharp files. +# Note: This is only used by command line +############################################################################### +#*.cs diff=csharp + +############################################################################### +# Set the merge driver for project and solution files +# +# Merging from the command prompt will add diff markers to the files if there +# are conflicts (Merging from VS is not affected by the settings below, in VS +# the diff markers are never inserted). Diff markers may cause the following +# file extensions to fail to load in VS. An alternative would be to treat +# these files as binary and thus will always conflict and require user +# intervention with every merge. To do so, just uncomment the entries below +############################################################################### +#*.sln merge=binary +#*.csproj merge=binary +#*.vbproj merge=binary +#*.vcxproj merge=binary +#*.vcproj merge=binary +#*.dbproj merge=binary +#*.fsproj merge=binary +#*.lsproj merge=binary +#*.wixproj merge=binary +#*.modelproj merge=binary +#*.sqlproj merge=binary +#*.wwaproj merge=binary + +############################################################################### +# behavior for image files +# +# image files are treated as binary by default. +############################################################################### +#*.jpg binary +#*.png binary +#*.gif binary + +############################################################################### +# diff behavior for common document formats +# +# Convert binary document formats to text before diffing them. This feature +# is only available from the command line. Turn it on by uncommenting the +# entries below. +############################################################################### +#*.doc diff=astextplain +#*.DOC diff=astextplain +#*.docx diff=astextplain +#*.DOCX diff=astextplain +#*.dot diff=astextplain +#*.DOT diff=astextplain +#*.pdf diff=astextplain +#*.PDF diff=astextplain +#*.rtf diff=astextplain +#*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c4efe2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,261 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ + +# Visual Studio 2015 cache/options directory +.vs/ +# Uncomment if you have tasks that create the project's static files in wwwroot +#wwwroot/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +project.fragment.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opendb +*.opensdf +*.sdf +*.cachefile +*.VC.db +*.VC.VC.opendb + +# Visual Studio profiler +*.psess +*.vsp +*.vspx +*.sap + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml +nCrunchTemp_* + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +#*.pubxml +*.publishproj + +# Microsoft Azure Web App publish settings. Comment the next line if you want to +# checkin your Azure Web App publish settings, but sensitive information contained +# in these scripts will be unencrypted +PublishScripts/ + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config +# NuGet v3's project.json files produces more ignoreable files +*.nuget.props +*.nuget.targets + +# Microsoft Azure Build Output +csx/ +*.build.csdef + +# Microsoft Azure Emulator +ecf/ +rcf/ + +# Windows Store app package directories and files +AppPackages/ +BundleArtifacts/ +Package.StoreAssociation.xml +_pkginfo.txt + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.jfm +*.pfx +*.publishsettings +node_modules/ +orleans.codegen.cs + +# Since there are multiple workflows, uncomment next line to ignore bower_components +# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) +#bower_components/ + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# GhostDoc plugin setting file +*.GhostDoc.xml + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt + +# Visual Studio LightSwitch build output +**/*.HTMLClient/GeneratedArtifacts +**/*.DesktopClient/GeneratedArtifacts +**/*.DesktopClient/ModelManifest.xml +**/*.Server/GeneratedArtifacts +**/*.Server/ModelManifest.xml +_Pvt_Extensions + +# Paket dependency manager +.paket/paket.exe +paket-files/ + +# FAKE - F# Make +.fake/ + +# JetBrains Rider +.idea/ +*.sln.iml + +# CodeRush +.cr/ + +# Python Tools for Visual Studio (PTVS) +__pycache__/ +*.pyc \ No newline at end of file diff --git a/CodeMaid.config b/CodeMaid.config new file mode 100644 index 0000000..ae15bb6 --- /dev/null +++ b/CodeMaid.config @@ -0,0 +1,47 @@ + + + + +
+ + + + + + False + + + True + + + True + + + False + + + True + + + False + + + True + + + True + + + <?xml version="1.0" encoding="utf-16"?> +<ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> + <string>ReSharper disable </string> + <string>ReSharper enable </string> +</ArrayOfString> + + + 1 + + + + \ No newline at end of file diff --git a/Directory.Build.props b/Directory.Build.props new file mode 100644 index 0000000..2e2fca6 --- /dev/null +++ b/Directory.Build.props @@ -0,0 +1,30 @@ + + + true + $(MSBuildThisFileDirectory)\RuleSet.ruleset + latest + + + + + 2.1.2 + runtime; build; native; contentfiles; analyzers + all + + + 2.7.1 + runtime; build; native; contentfiles; analyzers + all + + + 1.0.2 + runtime; build; native; contentfiles; analyzers + all + + + 2.2.0.2 + runtime; build; native; contentfiles; analyzers + all + + + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..9b8597d --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 Dionach Limitied + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..0013f14 --- /dev/null +++ b/README.md @@ -0,0 +1,37 @@ +# What is share auditing? + +Share auditing is the process of reviewing file shares within an Active Directory environment in order to determine whether any content is inappropriately accessible. + +# What does this tool do? + +ShareAudit impersonates the provided credentials and attempts to access the given systems. It will then enumerate all shares, including hidden shares, and retrieve the first level contents of the shares. It will display whether each file or folder is readable or writable to the impersonated user, as well as display the NTFS permissions. For files, ShareAudit will also retrieve the first 1024 bytes of the file, to add in determining whether the file has sensitive content. + +Once the initial audit of the first level of all shares is complete, additional folders can also be audited using the right click menu. The reason to only audit the first level of each share automatically is that it is common to see very complex directory structures, that can take a very long time to process, and could be quickly identified as non-sensitive by the auditor. + +The right click menu on folders and files also has the option to reveal the item in explorer. This will open a new Explorer window that can be used to browse the share as the impersonated user. + +# How do I use this tool? + +When creating a new project, you will be first asked for the credentials you wish to impersonate. This would typically be a low privileged user, representative of a normal domain user. In larger organisations, you may wish to run ShareAudit several times in order to represent users from different departments. + +When entering credentials, you will need to specify the user's domain (which is not necessarily the same as the domain you wish to test), username, and password. + +Alternatively, you can select to use the current Windows credentials, if you are already logged in as the target user. This can be more reliable, as it prevents the need to impersonate the target user. + +Next, you will need to enter the scope, which is the list of systems you wish to audit. The scope can either be entered manually or imported from Active Directory. + +Once the configuration is complete, you will be presented with the audit page, press start to begin the scan. You will see systems appear and disappear as they are audited and found to be accessible or inaccessible. Whilst you can interact with the result whilst the scan is running, it is best to wait for it to complete to ensure you are working with accurate results. + +Once complete, you can browse the result tree and select a share, folder, or file to view more information, including the effective permissions, NTFS permissions, and file previews. + +You can also right click folders and files to open in Explorer as the impersonated user and to audit additional folders that may be interesting. + +Results can also be exported to a CSV file once the audit is complete for processing with other tools. + +# What am I looking for in the results? + +Whilst the nature of sensitive data will vary from organisation to organisation, common indicators include: + +* Unnecessary writable files or folders. Write access should be as restrictive as possible to limit the impact of ransomware attacks. + +* Access to administrative shares (ADMIN$, C$, etc), as this means the impersonated user has local admin rights on the system. diff --git a/RuleSet.ruleset b/RuleSet.ruleset new file mode 100644 index 0000000..d3ac2ca --- /dev/null +++ b/RuleSet.ruleset @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ShareAudit.sln b/ShareAudit.sln new file mode 100644 index 0000000..75761db --- /dev/null +++ b/ShareAudit.sln @@ -0,0 +1,63 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.28307.329 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{20E0FD49-F242-4361-809A-111F4F0CE77E}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dionach.ShareAudit", "src\Dionach.ShareAudit\Dionach.ShareAudit.csproj", "{1D1B59D9-10AF-40FE-BE99-578C09DB7A2A}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{AC9F4628-6E6A-4BD5-A684-A19CB94249FB}" + ProjectSection(SolutionItems) = preProject + RuleSet.ruleset = RuleSet.ruleset + EndProjectSection +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dionach.ShareAudit.Modules.UserInterface", "src\Dionach.ShareAudit.Modules.UserInterface\Dionach.ShareAudit.Modules.UserInterface.csproj", "{28CF3837-FF58-463B-AF81-E6B0039DE55F}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dionach.ShareAudit.Modules.Services", "src\Dionach.ShareAudit.Modules.Services\Dionach.ShareAudit.Modules.Services.csproj", "{F5BFA34B-3CDE-4C77-9162-96666303FDEA}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dionach.ShareAudit.Model", "src\Dionach.ShareAudit.Model\Dionach.ShareAudit.Model.csproj", "{1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Dionach.ShareAudit.Modules.Services.Interop", "src\Dionach.ShareAudit.Modules.Services.Interop\Dionach.ShareAudit.Modules.Services.Interop.csproj", "{8425D05F-F3F4-4132-9BE1-BED752685333}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {1D1B59D9-10AF-40FE-BE99-578C09DB7A2A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1D1B59D9-10AF-40FE-BE99-578C09DB7A2A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1D1B59D9-10AF-40FE-BE99-578C09DB7A2A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1D1B59D9-10AF-40FE-BE99-578C09DB7A2A}.Release|Any CPU.Build.0 = Release|Any CPU + {28CF3837-FF58-463B-AF81-E6B0039DE55F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {28CF3837-FF58-463B-AF81-E6B0039DE55F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {28CF3837-FF58-463B-AF81-E6B0039DE55F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {28CF3837-FF58-463B-AF81-E6B0039DE55F}.Release|Any CPU.Build.0 = Release|Any CPU + {F5BFA34B-3CDE-4C77-9162-96666303FDEA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5BFA34B-3CDE-4C77-9162-96666303FDEA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5BFA34B-3CDE-4C77-9162-96666303FDEA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5BFA34B-3CDE-4C77-9162-96666303FDEA}.Release|Any CPU.Build.0 = Release|Any CPU + {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC}.Release|Any CPU.Build.0 = Release|Any CPU + {8425D05F-F3F4-4132-9BE1-BED752685333}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8425D05F-F3F4-4132-9BE1-BED752685333}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8425D05F-F3F4-4132-9BE1-BED752685333}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8425D05F-F3F4-4132-9BE1-BED752685333}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {1D1B59D9-10AF-40FE-BE99-578C09DB7A2A} = {20E0FD49-F242-4361-809A-111F4F0CE77E} + {28CF3837-FF58-463B-AF81-E6B0039DE55F} = {20E0FD49-F242-4361-809A-111F4F0CE77E} + {F5BFA34B-3CDE-4C77-9162-96666303FDEA} = {20E0FD49-F242-4361-809A-111F4F0CE77E} + {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC} = {20E0FD49-F242-4361-809A-111F4F0CE77E} + {8425D05F-F3F4-4132-9BE1-BED752685333} = {20E0FD49-F242-4361-809A-111F4F0CE77E} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {04F6815F-15A2-4B74-A2F3-2CF8753BDD87} + EndGlobalSection +EndGlobal diff --git a/src/Dionach.ShareAudit.Model/AccessRuleEntry.cs b/src/Dionach.ShareAudit.Model/AccessRuleEntry.cs new file mode 100644 index 0000000..71953c9 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/AccessRuleEntry.cs @@ -0,0 +1,79 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Security.AccessControl; + +namespace Dionach.ShareAudit.Model +{ + public class AccessRuleEntry : INotifyPropertyChanged + { + private string _identity; + private bool _inherited; + private string _rights; + private AccessControlType _type; + + public event PropertyChangedEventHandler PropertyChanged; + + public string Identity + { + get => _identity; + set + { + if (value == _identity) + { + return; + } + + _identity = value; + OnPropertyChanged(); + } + } + + public bool Inherited + { + get => _inherited; + set + { + if (value == _inherited) + { + return; + } + + _inherited = value; + OnPropertyChanged(); + } + } + + public string Rights + { + get => _rights; + set + { + if (value == _rights) + { + return; + } + + _rights = value; + OnPropertyChanged(); + } + } + + public AccessControlType Type + { + get => _type; + set + { + if (value == _type) + { + return; + } + + _type = value; + OnPropertyChanged(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/Dionach.ShareAudit.Model/Configuration.cs b/src/Dionach.ShareAudit.Model/Configuration.cs new file mode 100644 index 0000000..51d6614 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/Configuration.cs @@ -0,0 +1,126 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Dionach.ShareAudit.Model +{ + public class Configuration : INotifyPropertyChanged + { + private Credentials _credentials = new Credentials(); + private bool _disablePortScan = false; + private bool _disableReverseDnsLookup = false; + private bool _isReadOnly = false; + private string _scope; + private bool _useAlternateAuthenticationMethod = true; + private bool _useVerbatimScope = false; + + public event PropertyChangedEventHandler PropertyChanged; + + public Credentials Credentials + { + get => _credentials; + set + { + if (ReferenceEquals(value, _credentials)) + { + return; + } + + _credentials = value; + OnPropertyChanged(); + } + } + + public bool DisablePortScan + { + get => _disablePortScan; + set + { + if (value == _disablePortScan) + { + return; + } + + _disablePortScan = value; + OnPropertyChanged(); + } + } + + public bool DisableReverseDnsLookup + { + get => _disableReverseDnsLookup; + set + { + if (value == _disableReverseDnsLookup) + { + return; + } + + _disableReverseDnsLookup = value; + OnPropertyChanged(); + } + } + + public bool IsReadOnly + { + get => _isReadOnly; + set + { + if (value == _isReadOnly) + { + return; + } + + _isReadOnly = value; + OnPropertyChanged(); + } + } + + public string Scope + { + get => _scope; + set + { + if (value == _scope) + { + return; + } + + _scope = value; + OnPropertyChanged(); + } + } + + public bool UseAlternateAuthenticationMethod + { + get => _useAlternateAuthenticationMethod; + set + { + if (value == _useAlternateAuthenticationMethod) + { + return; + } + + _useAlternateAuthenticationMethod = value; + OnPropertyChanged(); + } + } + + public bool UseVerbatimScope + { + get => _useVerbatimScope; + set + { + if (value == _useVerbatimScope) + { + return; + } + + _useVerbatimScope = value; + OnPropertyChanged(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/Dionach.ShareAudit.Model/Credentials.cs b/src/Dionach.ShareAudit.Model/Credentials.cs new file mode 100644 index 0000000..6dd0323 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/Credentials.cs @@ -0,0 +1,81 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Xml.Serialization; + +namespace Dionach.ShareAudit.Model +{ + public class Credentials : INotifyPropertyChanged + { + private string _domain = string.Empty; + private string _password = string.Empty; + private bool _useCurrentCredentials = false; + + private string _username = string.Empty; + + public event PropertyChangedEventHandler PropertyChanged; + + public string Domain + { + get => _domain; + set + { + if (value == _domain) + { + return; + } + + _domain = value; + OnPropertyChanged(); + } + } + + [XmlIgnore] + public string Password + { + get => _password; + set + { + if (value == _password) + { + return; + } + + _password = value; + OnPropertyChanged(); + } + } + + public bool UseCurrentCredentials + { + get => _useCurrentCredentials; + set + { + if (value == _useCurrentCredentials) + { + return; + } + + _useCurrentCredentials = value; + OnPropertyChanged(); + } + } + + public string Username + { + get => _username; + set + { + if (value == _username) + { + return; + } + + _username = value; + OnPropertyChanged(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/Dionach.ShareAudit.Model/Dionach.ShareAudit.Model.csproj b/src/Dionach.ShareAudit.Model/Dionach.ShareAudit.Model.csproj new file mode 100644 index 0000000..c5e9bb0 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/Dionach.ShareAudit.Model.csproj @@ -0,0 +1,64 @@ + + + + + Debug + AnyCPU + {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC} + Library + Properties + Dionach.ShareAudit.Model + Dionach.ShareAudit.Model + v4.6.1 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Dionach.ShareAudit.Model/DirectoryEntry.cs b/src/Dionach.ShareAudit.Model/DirectoryEntry.cs new file mode 100644 index 0000000..9d55ed5 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/DirectoryEntry.cs @@ -0,0 +1,40 @@ +using System.Collections.ObjectModel; + +namespace Dionach.ShareAudit.Model +{ + public class DirectoryEntry : FileSystemEntry, IFolderEntry + { + private ObservableCollection _fileSystemEntries = new ObservableCollection(); + private FolderEntryState _state = FolderEntryState.New; + + public ObservableCollection FileSystemEntries + { + get => _fileSystemEntries; + set + { + if (ReferenceEquals(value, _fileSystemEntries)) + { + return; + } + + _fileSystemEntries = value; + OnPropertyChanged(); + } + } + + public FolderEntryState State + { + get => _state; + set + { + if (value == _state) + { + return; + } + + _state = value; + OnPropertyChanged(); + } + } + } +} diff --git a/src/Dionach.ShareAudit.Model/EffectiveAccess.cs b/src/Dionach.ShareAudit.Model/EffectiveAccess.cs new file mode 100644 index 0000000..0f2f520 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/EffectiveAccess.cs @@ -0,0 +1,46 @@ +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Dionach.ShareAudit.Model +{ + public class EffectiveAccess : INotifyPropertyChanged + { + private bool _read = false; + private bool _write = false; + + public event PropertyChangedEventHandler PropertyChanged; + + public bool Read + { + get => _read; + set + { + if (value == _read) + { + return; + } + + _read = value; + OnPropertyChanged(); + } + } + + public bool Write + { + get => _write; + set + { + if (value == _write) + { + return; + } + + _write = value; + OnPropertyChanged(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/Dionach.ShareAudit.Model/FileEntry.cs b/src/Dionach.ShareAudit.Model/FileEntry.cs new file mode 100644 index 0000000..0866790 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/FileEntry.cs @@ -0,0 +1,38 @@ +namespace Dionach.ShareAudit.Model +{ + public class FileEntry : FileSystemEntry + { + private string _head; + private FileEntryState _state; + + public string Head + { + get => _head; + set + { + if (value == _head) + { + return; + } + + _head = value; + OnPropertyChanged(); + } + } + + public FileEntryState State + { + get => _state; + set + { + if (value == _state) + { + return; + } + + _state = value; + OnPropertyChanged(); + } + } + } +} diff --git a/src/Dionach.ShareAudit.Model/FileEntryState.cs b/src/Dionach.ShareAudit.Model/FileEntryState.cs new file mode 100644 index 0000000..22ac751 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/FileEntryState.cs @@ -0,0 +1,11 @@ +namespace Dionach.ShareAudit.Model +{ + public enum FileEntryState + { + New, + EnumeratingAcls, + GettingEffectiveAccess, + ReadingHead, + Complete + } +} diff --git a/src/Dionach.ShareAudit.Model/FileSystemEntry.cs b/src/Dionach.ShareAudit.Model/FileSystemEntry.cs new file mode 100644 index 0000000..4a08558 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/FileSystemEntry.cs @@ -0,0 +1,100 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; +using System.Xml.Serialization; + +namespace Dionach.ShareAudit.Model +{ + [XmlInclude(typeof(DirectoryEntry))] + [XmlInclude(typeof(FileEntry))] + public abstract class FileSystemEntry : INotifyPropertyChanged + { + private bool _accessible; + private ObservableCollection _accessRules; + private EffectiveAccess _effectiveAccess = new EffectiveAccess { Read = false, Write = false }; + private string _fullName; + + private string _name; + + public event PropertyChangedEventHandler PropertyChanged; + + public bool Accessible + { + get => _accessible; + set + { + if (value == _accessible) + { + return; + } + + _accessible = value; + OnPropertyChanged(); + } + } + + public virtual ObservableCollection AccessRules + { + get => _accessRules; + set + { + if (ReferenceEquals(value, _accessRules)) + { + return; + } + + _accessRules = value; + _accessRules.CollectionChanged += (s, e) => Accessible = _accessRules.Count > 0; + OnPropertyChanged(); + } + } + + public EffectiveAccess EffectiveAccess + { + get => _effectiveAccess; + set + { + if (ReferenceEquals(value, _effectiveAccess)) + { + return; + } + + _effectiveAccess = value; + OnPropertyChanged(); + } + } + + public string FullName + { + get => _fullName; + set + { + if (value == _fullName) + { + return; + } + + _fullName = value; + OnPropertyChanged(); + } + } + + public string Name + { + get => _name; + set + { + if (value == _name) + { + return; + } + + _name = value; + OnPropertyChanged(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/Dionach.ShareAudit.Model/FolderEntryState.cs b/src/Dionach.ShareAudit.Model/FolderEntryState.cs new file mode 100644 index 0000000..66c3b22 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/FolderEntryState.cs @@ -0,0 +1,14 @@ +namespace Dionach.ShareAudit.Model +{ + public enum FolderEntryState + { + New, + EnumeratingAcls, + GettingEffectiveAccess, + EnumerationSuspended, + EnumeratingFilesystemEntries, + AuditingFileSystemEntries, + NestedAuditingSuspended, + Complete + } +} diff --git a/src/Dionach.ShareAudit.Model/Host.cs b/src/Dionach.ShareAudit.Model/Host.cs new file mode 100644 index 0000000..84c7c1f --- /dev/null +++ b/src/Dionach.ShareAudit.Model/Host.cs @@ -0,0 +1,113 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace Dionach.ShareAudit.Model +{ + public class Host : INotifyPropertyChanged + { + private bool _accessible; + private string _ipAddress; + private string _ptrRecord; + private ObservableCollection _shares = new ObservableCollection(); + private HostState _state = HostState.New; + + public event PropertyChangedEventHandler PropertyChanged; + + public bool Accessible + { + get => _accessible; + set + { + if (value == _accessible) + { + return; + } + + _accessible = value; + OnPropertyChanged(); + } + } + + public string IPAddress + { + get => _ipAddress; + set + { + if (ReferenceEquals(value, _ipAddress)) + { + return; + } + + _ipAddress = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(Name)); + } + } + + public string Name => string.IsNullOrEmpty(PtrRecord) ? IPAddress : PtrRecord; + + public string PtrRecord + { + get => _ptrRecord; + set + { + if (value == _ptrRecord) + { + return; + } + + _ptrRecord = value; + OnPropertyChanged(); + OnPropertyChanged(nameof(Name)); + } + } + + public ObservableCollection Shares + { + get => _shares; + set + { + if (ReferenceEquals(value, _shares)) + { + return; + } + + _shares = value; + _shares.CollectionChanged += (s, e) => + { + foreach (Share share in e.NewItems) + { + share.PropertyChanged += (ss, se) => + { + if (se.PropertyName.Equals(nameof(Share.Accessible))) + { + Accessible = Shares.Any(x => x.Accessible); + } + }; + } + }; + OnPropertyChanged(); + } + } + + public HostState State + { + get => _state; + set + { + if (value == _state) + { + return; + } + + _state = value; + OnPropertyChanged(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/Dionach.ShareAudit.Model/HostState.cs b/src/Dionach.ShareAudit.Model/HostState.cs new file mode 100644 index 0000000..316158e --- /dev/null +++ b/src/Dionach.ShareAudit.Model/HostState.cs @@ -0,0 +1,13 @@ +namespace Dionach.ShareAudit.Model +{ + public enum HostState + { + New, + LookingUpPtr, + CheckingPorts, + EnumeratingShares, + AuditingShares, + NestedAuditingSuspended, + Complete + } +} diff --git a/src/Dionach.ShareAudit.Model/IFolderEntry.cs b/src/Dionach.ShareAudit.Model/IFolderEntry.cs new file mode 100644 index 0000000..fbf2183 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/IFolderEntry.cs @@ -0,0 +1,17 @@ +using System.Collections.ObjectModel; + +namespace Dionach.ShareAudit.Model +{ + public interface IFolderEntry + { + ObservableCollection AccessRules { get; set; } + + EffectiveAccess EffectiveAccess { get; set; } + + ObservableCollection FileSystemEntries { get; set; } + + string FullName { get; set; } + + FolderEntryState State { get; set; } + } +} diff --git a/src/Dionach.ShareAudit.Model/ImportComputerType.cs b/src/Dionach.ShareAudit.Model/ImportComputerType.cs new file mode 100644 index 0000000..bd57f3d --- /dev/null +++ b/src/Dionach.ShareAudit.Model/ImportComputerType.cs @@ -0,0 +1,9 @@ +namespace Dionach.ShareAudit.Model +{ + public enum ImportComputerType + { + Servers, + Workstations, + All, + } +} diff --git a/src/Dionach.ShareAudit.Model/Project.cs b/src/Dionach.ShareAudit.Model/Project.cs new file mode 100644 index 0000000..4184937 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/Project.cs @@ -0,0 +1,63 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Runtime.CompilerServices; + +namespace Dionach.ShareAudit.Model +{ + public class Project : INotifyPropertyChanged + { + private Configuration _configuration = new Configuration(); + private ObservableCollection _hosts = new ObservableCollection(); + private ProjectState _state = ProjectState.New; + + public event PropertyChangedEventHandler PropertyChanged; + + public Configuration Configuration + { + get => _configuration; + set + { + if (ReferenceEquals(value, _configuration)) + { + return; + } + + _configuration = value; + OnPropertyChanged(); + } + } + + public ObservableCollection Hosts + { + get => _hosts; + set + { + if (ReferenceEquals(value, _hosts)) + { + return; + } + + _hosts = value; + OnPropertyChanged(); + } + } + + public ProjectState State + { + get => _state; + set + { + if (value == _state) + { + return; + } + + _state = value; + OnPropertyChanged(); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1026:DefaultParametersShouldNotBeUsed")] + protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } +} diff --git a/src/Dionach.ShareAudit.Model/ProjectState.cs b/src/Dionach.ShareAudit.Model/ProjectState.cs new file mode 100644 index 0000000..195248b --- /dev/null +++ b/src/Dionach.ShareAudit.Model/ProjectState.cs @@ -0,0 +1,11 @@ +namespace Dionach.ShareAudit.Model +{ + public enum ProjectState + { + New, + Configured, + ExpandingScope, + AuditingHosts, + Complete + } +} diff --git a/src/Dionach.ShareAudit.Model/Properties/AssemblyInfo.cs b/src/Dionach.ShareAudit.Model/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..d7a9788 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/Properties/AssemblyInfo.cs @@ -0,0 +1,31 @@ +using System; +using System.Reflection; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following set of attributes. +// Change these attribute values to modify the information associated with an assembly. +[assembly: AssemblyTitle("Dionach.ShareAudit.Model")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Dionach.ShareAudit.Model")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible to COM components. If you +// need to access a type in this assembly from COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("1dfc488d-e104-4f35-98da-f23bf6d3f9dc")] + +// Version information for an assembly consists of the following four values: +// +// Major Version Minor Version Build Number Revision +// +// You can specify all the values or you can default the Build and Revision Numbers by using the '*' +// as shown below: [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: CLSCompliant(false)] diff --git a/src/Dionach.ShareAudit.Model/Share.cs b/src/Dionach.ShareAudit.Model/Share.cs new file mode 100644 index 0000000..d9321c7 --- /dev/null +++ b/src/Dionach.ShareAudit.Model/Share.cs @@ -0,0 +1,73 @@ +using System.Collections.ObjectModel; +using System.ComponentModel; + +namespace Dionach.ShareAudit.Model +{ + public class Share : FileSystemEntry, INotifyPropertyChanged, IFolderEntry + { + private ObservableCollection _fileSystemEntries = new ObservableCollection(); + private string _remark; + private FolderEntryState _state = FolderEntryState.New; + private ShareTypes _type; + + public ObservableCollection FileSystemEntries + { + get => _fileSystemEntries; + set + { + if (ReferenceEquals(value, _fileSystemEntries)) + { + return; + } + + _fileSystemEntries = value; + OnPropertyChanged(); + } + } + + public string Remark + { + get => _remark; + set + { + if (value == _remark) + { + return; + } + + _remark = value; + OnPropertyChanged(); + } + } + + public FolderEntryState State + { + get => _state; + set + { + if (value == _state) + { + return; + } + + _state = value; + OnPropertyChanged(); + } + } + + public ShareTypes Type + { + get => _type; + set + { + if (value == _type) + { + return; + } + + _type = value; + OnPropertyChanged(); + } + } + } +} diff --git a/src/Dionach.ShareAudit.Model/ShareType.cs b/src/Dionach.ShareAudit.Model/ShareType.cs new file mode 100644 index 0000000..6599e0f --- /dev/null +++ b/src/Dionach.ShareAudit.Model/ShareType.cs @@ -0,0 +1,72 @@ +using System; + +namespace Dionach.ShareAudit.Model +{ + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1028:EnumStorageShouldBeInt32")] + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue")] + [Flags] + public enum ShareTypes : uint + { + Disktree = 0x00000000, + + PrintQueue = 0x00000001, + + Device = 0x00000002, + + Ipc = 0x00000003, + + ClusterFS = 0x02000000, + + ClusterSofs = 0x04000000, + + ClusterDfs = 0x08000000, + + Temporary = 0x40000000, + + Special = 0x80000000, + + SpecialDisktree = Special | Disktree, + + SpecialPrintQueue = Special | PrintQueue, + + SpecialDevice = Special | Device, + + SpecialIpc = Special | Ipc, + + SpecialClusterFS = Special | ClusterFS, + + SpecialClusterSofs = Special | ClusterSofs, + + SpecialClusterDfs = Special | ClusterDfs, + + TemporaryDisktree = Temporary | Disktree, + + TemporaryPrintQueue = Temporary | PrintQueue, + + TemporaryDevice = Temporary | Device, + + TemporaryIpc = Temporary | Ipc, + + TemporaryClusterFS = Temporary | ClusterFS, + + TemporaryClusterSofs = Temporary | ClusterSofs, + + TemporaryClusterDfs = Temporary | ClusterDfs, + + SpecialTemporaryDisktree = Special | Temporary | Disktree, + + SpecialTemporaryPrintQueue = Special | Temporary | PrintQueue, + + SpecialTemporaryDevice = Special | Temporary | Device, + + SpecialTemporaryIpc = Special | Temporary | Ipc, + + SpecialTemporaryClusterFS = Special | Temporary | ClusterFS, + + SpecialTemporaryClusterSofs = Special | Temporary | ClusterSofs, + + SpecialTemporaryClusterDfs = Special | Temporary | ClusterDfs, + + SpecialTemporary = Special | Temporary, + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services.Interop/Advapi32.cs b/src/Dionach.ShareAudit.Modules.Services.Interop/Advapi32.cs new file mode 100644 index 0000000..6608fe6 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services.Interop/Advapi32.cs @@ -0,0 +1,37 @@ +using System; +using System.Runtime.InteropServices; + +namespace Dionach.ShareAudit.Modules.Services.Interop +{ + internal enum SID_NAME_USE + { + SidTypeUser = 1, + SidTypeGroup, + SidTypeDomain, + SidTypeAlias, + SidTypeWellKnownGroup, + SidTypeDeletedAccount, + SidTypeInvalid, + SidTypeUnknown, + SidTypeComputer + } + + internal partial class NativeMethods + { + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, ThrowOnUnmappableChar = true, SetLastError = true)] + internal static extern bool ConvertStringSidToSid(string StringSid, out IntPtr ptrSid); + + [DllImport("advapi32.dll", EntryPoint = "GetLengthSid", CharSet = CharSet.Auto)] + internal static extern int GetLengthSid(IntPtr pSID); + + [DllImport("advapi32.dll", CharSet = CharSet.Unicode, ThrowOnUnmappableChar = true, SetLastError = true)] + internal static extern bool LookupAccountSid( + string lpSystemName, + [MarshalAs(UnmanagedType.LPArray)] byte[] Sid, + System.Text.StringBuilder lpName, + ref uint cchName, + System.Text.StringBuilder ReferencedDomainName, + ref uint cchReferencedDomainName, + out SID_NAME_USE peUse); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services.Interop/Dionach.ShareAudit.Modules.Services.Interop.csproj b/src/Dionach.ShareAudit.Modules.Services.Interop/Dionach.ShareAudit.Modules.Services.Interop.csproj new file mode 100644 index 0000000..f84aa26 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services.Interop/Dionach.ShareAudit.Modules.Services.Interop.csproj @@ -0,0 +1,51 @@ + + + + + Debug + AnyCPU + {8425D05F-F3F4-4132-9BE1-BED752685333} + Library + Properties + Dionach.ShareAudit.Modules.Services.Interop + Dionach.ShareAudit.Modules.Services.Interop + v4.6.1 + 512 + true + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Dionach.ShareAudit.Modules.Services.Interop/Mpr.cs b/src/Dionach.ShareAudit.Modules.Services.Interop/Mpr.cs new file mode 100644 index 0000000..b4c7bd5 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services.Interop/Mpr.cs @@ -0,0 +1,59 @@ +using System.Runtime.InteropServices; + +namespace Dionach.ShareAudit.Modules.Services.Interop +{ + internal enum ResourceDisplaytype : int + { + Generic = 0x0, + Domain = 0x01, + Server = 0x02, + Share = 0x03, + File = 0x04, + Group = 0x05, + Network = 0x06, + Root = 0x07, + Shareadmin = 0x08, + Directory = 0x09, + Tree = 0x0a, + Ndscontainer = 0x0b + } + + internal enum ResourceScope : int + { + Connected = 1, + GlobalNetwork, + Remembered, + Recent, + Context + } + + internal enum ResourceType : int + { + Any = 0, + Disk = 1, + Print = 2, + Reserved = 8, + } + + [StructLayout(LayoutKind.Sequential)] + internal struct NetResource + { + internal ResourceScope Scope; + internal ResourceType ResourceType; + internal ResourceDisplaytype DisplayType; + internal int Usage; + internal string LocalName; + internal string RemoteName; + internal string Comment; + internal string Provider; + } + + internal partial class NativeMethods + { + [DllImport("mpr.dll")] + internal static extern int WNetAddConnection2(ref NetResource netResource, string password, string username, uint flags); + + [DllImport("mpr.dll")] + internal static extern int WNetCancelConnection2(string name, int flags, bool force); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services.Interop/Netapi32.cs b/src/Dionach.ShareAudit.Modules.Services.Interop/Netapi32.cs new file mode 100644 index 0000000..bfd4df1 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services.Interop/Netapi32.cs @@ -0,0 +1,33 @@ +using System; +using System.Runtime.InteropServices; + +namespace Dionach.ShareAudit.Modules.Services.Interop +{ + [StructLayout(LayoutKind.Sequential)] + internal struct SHARE_INFO_1 + { + [MarshalAs(UnmanagedType.LPWStr)] + internal string shi1_netname; + + internal uint shi1_type; + + [MarshalAs(UnmanagedType.LPWStr)] + internal string shi1_remark; + } + + internal partial class NativeMethods + { + [DllImport("Netapi32.dll")] + internal static extern int NetApiBufferFree(IntPtr Buffer); + + [DllImport("Netapi32.dll", CharSet = CharSet.Unicode)] + internal static extern int NetShareEnum( + string ServerName, + int level, + ref IntPtr bufPtr, + uint prefmaxlen, + ref int entriesread, + ref int totalentries, + ref int resume_handle); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services.Interop/Properties/AssemblyInfo.cs b/src/Dionach.ShareAudit.Modules.Services.Interop/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..46c5f0e --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services.Interop/Properties/AssemblyInfo.cs @@ -0,0 +1,31 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following set of attributes. +// Change these attribute values to modify the information associated with an assembly. +[assembly: AssemblyTitle("Dionach.ShareAudit.Modules.Services.Interop")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Dionach.ShareAudit.Modules.Services.Interop")] +[assembly: AssemblyCopyright("Copyright © 2019")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible to COM components. If you +// need to access a type in this assembly from COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("8425d05f-f3f4-4132-9be1-bed752685333")] + +// Version information for an assembly consists of the following four values: +// +// Major Version Minor Version Build Number Revision +// +// You can specify all the values or you can default the Build and Revision Numbers by using the '*' +// as shown below: [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: InternalsVisibleTo("Dionach.ShareAudit.Modules.Services")] diff --git a/src/Dionach.ShareAudit.Modules.Services/CredentialsValidationService.cs b/src/Dionach.ShareAudit.Modules.Services/CredentialsValidationService.cs new file mode 100644 index 0000000..6d25ef8 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/CredentialsValidationService.cs @@ -0,0 +1,93 @@ +using Dionach.ShareAudit.Model; +using System; +using System.DirectoryServices.AccountManagement; +using System.Threading.Tasks; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class CredentialsValidationService : ICredentialsValidationService + { + public async Task<(string domain, string username)> GetCurrentUserInformation() + { + return await Task.Run(() => + { + return (domain: Environment.UserDomainName, username: Environment.UserName); + }); + } + + public async Task<(bool isValid, string errorMessage)> ValidateCredentialsAsync(Credentials credentials) + { + return await Task.Run(() => + { + var result = (isValid: true, errorMessage: string.Empty); + + try + { + using (var context = new PrincipalContext(ContextType.Domain, credentials.Domain)) + { + if (!context.ValidateCredentials(credentials.Username, credentials.Password)) + { + result.isValid = false; + result.errorMessage = "Domain, user name, or password are incorrect"; + } + } + } + catch (Exception ex) + { + result.isValid = false; + result.errorMessage = $"Credentials could not be validated: {ex.Message}"; + } + + return result; + }); + } + + public async Task<(bool isValid, string errorMessage)> ValidateDomainAsync(string domain) + { + return await Task.Run(() => + { + var result = (isValid: true, errorMessage: string.Empty); + + if (string.IsNullOrWhiteSpace(domain)) + { + result.isValid = false; + result.errorMessage = "Domain cannot be empty"; + } + + return result; + }); + } + + public async Task<(bool isValid, string errorMessage)> ValidatePasswordAsync(string password) + { + return await Task.Run(() => + { + var result = (isValid: true, errorMessage: string.Empty); + + if (string.IsNullOrWhiteSpace(password)) + { + result.isValid = false; + result.errorMessage = "Password cannot be empty"; + } + + return result; + }); + } + + public async Task<(bool isValid, string errorMessage)> ValidateUsernameAsync(string username) + { + return await Task.Run(() => + { + var result = (isValid: true, errorMessage: string.Empty); + + if (string.IsNullOrWhiteSpace(username)) + { + result.isValid = false; + result.errorMessage = "User name cannot be empty"; + } + + return result; + }); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/Dionach.ShareAudit.Modules.Services.csproj b/src/Dionach.ShareAudit.Modules.Services/Dionach.ShareAudit.Modules.Services.csproj new file mode 100644 index 0000000..0523d48 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/Dionach.ShareAudit.Modules.Services.csproj @@ -0,0 +1,134 @@ + + + + + Debug + AnyCPU + {F5BFA34B-3CDE-4C77-9162-96666303FDEA} + library + Properties + Dionach.ShareAudit.Modules.Services + Dionach.ShareAudit.Modules.Services + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + PackageReference + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + 4.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC} + Dionach.ShareAudit.Model + + + {8425D05F-F3F4-4132-9BE1-BED752685333} + Dionach.ShareAudit.Modules.Services.Interop + + + + + 2.4.0.99 + + + 7.1.0.431 + + + + 3.0.0 + + + 4.5.0 + + + 4.5.0 + + + 4.5.0 + + + + \ No newline at end of file diff --git a/src/Dionach.ShareAudit.Modules.Services/DnsUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/DnsUtilitiesService.cs new file mode 100644 index 0000000..3acf447 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/DnsUtilitiesService.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Net.Sockets; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class DnsUtilitiesService : IDnsUtilitiesService + { + public string GetPtrRecord(IPAddress ipAddress) + { + try + { + var ptrRecord = Dns.GetHostEntry(ipAddress).HostName; + if (TryResolveHost(ptrRecord, out var resolvedIPAddresses)) + { + if (resolvedIPAddresses.Contains(ipAddress)) + { + return ptrRecord; + } + } + } + catch (SocketException) + { + } + + return null; + } + + public bool TryResolveHost(string host, out IEnumerable ipAddresses) + { + host = host ?? throw new ArgumentNullException(nameof(host)); + + try + { + ipAddresses = Dns.GetHostAddresses(host); + return true; + } + catch (SocketException ex) when (ex.Message == "No such host is known") + { + ipAddresses = Array.Empty(); + return false; + } + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/FileSystemOperationService.cs b/src/Dionach.ShareAudit.Modules.Services/FileSystemOperationService.cs new file mode 100644 index 0000000..e9ac5a3 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/FileSystemOperationService.cs @@ -0,0 +1,256 @@ +using Dionach.ShareAudit.Model; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Security.AccessControl; +using System.Security.Principal; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class FileSystemOperationService : IFileSystemOperationService + { + private readonly ISidUtilitiesService _sidUtilitiesService; + + public FileSystemOperationService(ISidUtilitiesService sidUtilitiesService) + { + _sidUtilitiesService = sidUtilitiesService ?? throw new ArgumentNullException(nameof(sidUtilitiesService)); + } + + public IEnumerable EnumerateAccessRules(string path) + { + FileAttributes attributes = FileAttributes.Normal; + try + { + attributes = File.GetAttributes(path); + } + catch (UnauthorizedAccessException) + { + } + catch (FileNotFoundException) + { + } + catch (IOException) + { + } + + AuthorizationRuleCollection authorizationRuleCollection = null; + + if ((attributes & FileAttributes.Directory) == FileAttributes.Directory) + { + try + { + authorizationRuleCollection = Directory.GetAccessControl(path).GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier)); + } + catch (UnauthorizedAccessException) + { + } + } + else + { + try + { + authorizationRuleCollection = File.GetAccessControl(path).GetAccessRules(true, true, typeof(System.Security.Principal.SecurityIdentifier)); + } + catch (InvalidOperationException) + { + } + catch (UnauthorizedAccessException) + { + } + } + + if (authorizationRuleCollection != null) + { + foreach (AccessRule authorizationRule in authorizationRuleCollection) + { + string identity = _sidUtilitiesService.SidStringToAccountName(path.TrimStart("\\\\".ToCharArray()).Split('\\')[0], authorizationRule.IdentityReference.Value); + string rights = (authorizationRule as FileSystemAccessRule).FileSystemRights.ToString(); + + // Handle rights that .NET decided not to include in the enum http://stackoverflow.com/questions/9694834/encountering-a-filesystemrights-value-that-isnt-defined-in-enumeration + switch (rights) + { + case "268435456": + rights = "FullControl"; + break; + + case "-536805376": + rights = "Modify, Synchronize"; + break; + + case "-1610612736": + rights = "ReadAndExecute, Synchronize"; + break; + } + + yield return new AccessRuleEntry + { + Identity = identity, + Rights = rights, + Type = authorizationRule.AccessControlType, + Inherited = authorizationRule.IsInherited, + }; + } + } + } + + public IEnumerable EnumerateDirectories(string path) + { + try + { + return Directory.EnumerateDirectories(path).Select(x => new DirectoryInfo(x)).Select(x => new DirectoryEntry + { + Name = x.Name, + FullName = x.FullName + }); + } + catch (IOException) + { + return Enumerable.Empty(); + } + catch (UnauthorizedAccessException) + { + return Enumerable.Empty(); + } + } + + public IEnumerable EnumerateFiles(string path) + { + try + { + return Directory.EnumerateFiles(path).Select(x => new FileInfo(x)).Select(x => new FileEntry + { + Name = x.Name, + FullName = x.FullName + }); + } + catch (IOException) + { + return Enumerable.Empty(); + } + catch (UnauthorizedAccessException) + { + return Enumerable.Empty(); + } + } + + public EffectiveAccess GetEffectiveAccess(string path) + { + var effectiveAccess = new EffectiveAccess { Read = true, Write = true }; + bool isDirectory; + + try + { + isDirectory = File.GetAttributes(path).HasFlag(FileAttributes.Directory); + } + catch (IOException) + { + return new EffectiveAccess { Read = false, Write = false }; + } + catch (UnauthorizedAccessException) + { + return new EffectiveAccess { Read = false, Write = false }; + } + + if (isDirectory) + { + try + { + Directory.GetFileSystemEntries(path); + } + catch (IOException) + { + effectiveAccess.Read = false; + } + catch (UnauthorizedAccessException) + { + effectiveAccess.Read = false; + } + + if (effectiveAccess.Read) + { + // create a FileSecurity object allowing full control so that we can be sure we + // have permissions to delete the file + var fileSecurity = new FileSecurity(); + fileSecurity.AddAccessRule(new FileSystemAccessRule(new SecurityIdentifier(WellKnownSidType.WorldSid, null), FileSystemRights.FullControl, AccessControlType.Allow)); + + try + { + // create the file in a using statement to allow the FileStream, which + // implements IDisposable, to be closed on completion + using (File.Create(path + "\\write-test.shareaudit", 1, FileOptions.DeleteOnClose, fileSecurity)) + { + } + } + catch (IOException) + { + effectiveAccess.Write = false; + } + catch (UnauthorizedAccessException) + { + effectiveAccess.Write = false; + } + } + } + else + { + try + { + using (File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) + { + } + } + catch (IOException) + { + effectiveAccess.Read = false; + } + catch (UnauthorizedAccessException) + { + effectiveAccess.Read = false; + } + + if (effectiveAccess.Read) + { + try + { + using (File.Open(path, FileMode.Open, FileAccess.Write, FileShare.ReadWrite)) + { + } + } + catch (IOException) + { + effectiveAccess.Write = false; + } + catch (UnauthorizedAccessException) + { + effectiveAccess.Write = false; + } + } + } + + return effectiveAccess; + } + + public string HeadFile(string path, int bytes) + { + try + { + var bufferSize = Math.Min(bytes, new FileInfo(path).Length); + var buffer = new byte[bufferSize]; + using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read)) + { + fs.Read(buffer, 0, buffer.Length); + return Convert.ToBase64String(buffer); + } + } + catch (IOException) + { + return default; + } + catch (UnauthorizedAccessException) + { + return default; + } + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/FileSystemStoreService.cs b/src/Dionach.ShareAudit.Modules.Services/FileSystemStoreService.cs new file mode 100644 index 0000000..6e8d6d5 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/FileSystemStoreService.cs @@ -0,0 +1,95 @@ +using Dionach.ShareAudit.Model; +using System; +using System.Globalization; +using System.IO; +using System.Text; +using System.Threading.Tasks; +using System.Xml; +using System.Xml.Serialization; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class FileSystemStoreService : IFileSystemStoreService + { + public string ExportDefaultFilename => DateTime.Now.ToString("yyyyMMddHHmm", CultureInfo.InvariantCulture); + + public string ExportFilter => "Share Audit Export (*.csv)|*.csv"; + + public string ShareAuditDefaultFilename => DateTime.Now.ToString("yyyyMMddHHmm", CultureInfo.InvariantCulture); + + public string ShareAuditFilter => "Share Audit File (*.shareaudit)|*.shareaudit"; + + public async Task CreateProjectAsync(string path) + { + await SaveProjectAsync(new Project(), path); + } + + public async Task ExportProjectAsync(Project project, string path) + { + await Task.Run(() => + { + var username = $"{project.Configuration.Credentials.Username}@{project.Configuration.Credentials.Domain}"; + var sb = new StringBuilder(); + sb.AppendLine("\"UNC Path\",\"Type\",\"Accessible\",\"Effective Read\",\"Effective Write\",\"Username\""); + + foreach (var host in project.Hosts) + { + sb.AppendLine($"\"\\\\{host.Name}\",\"Host\",\"{host.Accessible}\",\"N/A\",\"N/A\",\"{username}\""); + + foreach (var share in host.Shares) + { + WriteFolderEntry(sb, share, username); + } + } + + File.WriteAllText(path, sb.ToString()); + }); + } + + public async Task LoadProjectAsync(string path) + { + return await Task.Run(() => + { + using (var reader = File.OpenRead(path)) + { + var serializer = new XmlSerializer(typeof(Project)); + return serializer.Deserialize(reader) as Project; + } + }); + } + + public async Task SaveProjectAsync(Project project, string path) + { + await Task.Run(() => + { + var settings = new XmlWriterSettings + { + Indent = true + }; + + using (var writer = XmlWriter.Create(path, settings)) + { + var serializer = new XmlSerializer(typeof(Project)); + serializer.Serialize(writer, project); + } + }); + } + + private void WriteFolderEntry(StringBuilder sb, IFolderEntry entry, string username) + { + sb.AppendLine($"\"{entry.FullName}\",\"{((entry is Share) ? "Share" : "Directory")}\",\"{((entry is Share) ? (entry as Share).Accessible : entry.EffectiveAccess.Read)}\",\"{entry.EffectiveAccess.Read}\",\"{entry.EffectiveAccess.Write}\",\"{username}\""); + + foreach (var childEntry in entry.FileSystemEntries) + { + if (childEntry is IFolderEntry) + { + WriteFolderEntry(sb, childEntry as IFolderEntry, username); + } + else + { + sb.AppendLine($"\"{childEntry.FullName}\",\"File\",\"{childEntry.EffectiveAccess.Read}\",\"{childEntry.EffectiveAccess.Read}\",\"{childEntry.EffectiveAccess.Write}\",\"{username}\""); + } + } + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/ICredentialsValidationService.cs b/src/Dionach.ShareAudit.Modules.Services/ICredentialsValidationService.cs new file mode 100644 index 0000000..01ca2bd --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/ICredentialsValidationService.cs @@ -0,0 +1,18 @@ +using Dionach.ShareAudit.Model; +using System.Threading.Tasks; + +namespace Dionach.ShareAudit.Modules.Services +{ + public interface ICredentialsValidationService + { + Task<(string domain, string username)> GetCurrentUserInformation(); + + Task<(bool isValid, string errorMessage)> ValidateCredentialsAsync(Credentials credentials); + + Task<(bool isValid, string errorMessage)> ValidateDomainAsync(string domain); + + Task<(bool isValid, string errorMessage)> ValidatePasswordAsync(string password); + + Task<(bool isValid, string errorMessage)> ValidateUsernameAsync(string username); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/IDnsUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/IDnsUtilitiesService.cs new file mode 100644 index 0000000..4eae76c --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/IDnsUtilitiesService.cs @@ -0,0 +1,12 @@ +using System.Collections.Generic; +using System.Net; + +namespace Dionach.ShareAudit.Modules.Services +{ + public interface IDnsUtilitiesService + { + string GetPtrRecord(IPAddress ipAddress); + + bool TryResolveHost(string host, out IEnumerable ipAddresses); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/IFileSystemOperationService.cs b/src/Dionach.ShareAudit.Modules.Services/IFileSystemOperationService.cs new file mode 100644 index 0000000..a039ee4 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/IFileSystemOperationService.cs @@ -0,0 +1,18 @@ +using Dionach.ShareAudit.Model; +using System.Collections.Generic; + +namespace Dionach.ShareAudit.Modules.Services +{ + public interface IFileSystemOperationService + { + IEnumerable EnumerateAccessRules(string path); + + IEnumerable EnumerateDirectories(string path); + + IEnumerable EnumerateFiles(string path); + + EffectiveAccess GetEffectiveAccess(string path); + + string HeadFile(string path, int bytes); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/IFileSystemStoreService.cs b/src/Dionach.ShareAudit.Modules.Services/IFileSystemStoreService.cs new file mode 100644 index 0000000..1ba77b4 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/IFileSystemStoreService.cs @@ -0,0 +1,24 @@ +using Dionach.ShareAudit.Model; +using System.Threading.Tasks; + +namespace Dionach.ShareAudit.Modules.Services +{ + public interface IFileSystemStoreService + { + string ExportDefaultFilename { get; } + + string ExportFilter { get; } + + string ShareAuditDefaultFilename { get; } + + string ShareAuditFilter { get; } + + Task CreateProjectAsync(string path); + + Task ExportProjectAsync(Project project, string path); + + Task LoadProjectAsync(string path); + + Task SaveProjectAsync(Project project, string path); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/IImportScopeFromActiveDirectoryService.cs b/src/Dionach.ShareAudit.Modules.Services/IImportScopeFromActiveDirectoryService.cs new file mode 100644 index 0000000..f7d63ee --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/IImportScopeFromActiveDirectoryService.cs @@ -0,0 +1,12 @@ +using Dionach.ShareAudit.Model; +using System.Threading.Tasks; + +namespace Dionach.ShareAudit.Modules.Services +{ + public interface IImportScopeFromActiveDirectoryService + { + Task Import(string domain, string username, string password, ImportComputerType importComputerType); + + Task Import(string domain, ImportComputerType importComputerType); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/IPortScanService.cs b/src/Dionach.ShareAudit.Modules.Services/IPortScanService.cs new file mode 100644 index 0000000..dda296b --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/IPortScanService.cs @@ -0,0 +1,7 @@ +namespace Dionach.ShareAudit.Modules.Services +{ + public interface IPortScanService + { + bool IsTcpPortOpen(string host, ushort port, int millisecondsTimeout); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/IScopeExpansionService.cs b/src/Dionach.ShareAudit.Modules.Services/IScopeExpansionService.cs new file mode 100644 index 0000000..55e0a3f --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/IScopeExpansionService.cs @@ -0,0 +1,10 @@ +using System.Collections.Generic; +using System.Net; + +namespace Dionach.ShareAudit.Modules.Services +{ + public interface IScopeExpansionService + { + IEnumerable ExpandScopeToIPAddresses(string scope); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/IScopeNormalizationService.cs b/src/Dionach.ShareAudit.Modules.Services/IScopeNormalizationService.cs new file mode 100644 index 0000000..fc29854 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/IScopeNormalizationService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Dionach.ShareAudit.Modules.Services +{ + public interface IScopeNormalizationService + { + Task NormalizeScopeAsync(string scope); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/IScopeValidationService.cs b/src/Dionach.ShareAudit.Modules.Services/IScopeValidationService.cs new file mode 100644 index 0000000..49c8d37 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/IScopeValidationService.cs @@ -0,0 +1,9 @@ +using System.Threading.Tasks; + +namespace Dionach.ShareAudit.Modules.Services +{ + public interface IScopeValidationService + { + Task<(bool isValid, string errorMessage)> ValidateScopeAsync(string scope); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/IShareAuditService.cs b/src/Dionach.ShareAudit.Modules.Services/IShareAuditService.cs new file mode 100644 index 0000000..53d8a78 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/IShareAuditService.cs @@ -0,0 +1,16 @@ +using Dionach.ShareAudit.Model; +using System; + +namespace Dionach.ShareAudit.Modules.Services +{ + public interface IShareAuditService + { + event EventHandler Started; + + event EventHandler Stopped; + + void StartAudit(Project project); + + void StopAudit(); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/ISidUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/ISidUtilitiesService.cs new file mode 100644 index 0000000..0084273 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/ISidUtilitiesService.cs @@ -0,0 +1,7 @@ +namespace Dionach.ShareAudit.Modules.Services +{ + public interface ISidUtilitiesService + { + string SidStringToAccountName(string host, string sid); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/ISmbUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/ISmbUtilitiesService.cs new file mode 100644 index 0000000..3a3c65e --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/ISmbUtilitiesService.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace Dionach.ShareAudit.Modules.Services +{ + public interface ISmbUtilitiesService + { + NetUseConnection CreateNetUseConnection(string host, string username, string domain, string password); + + IEnumerable NetShareEnum(string host); + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/ImportScopeFromActiveDirectoryService.cs b/src/Dionach.ShareAudit.Modules.Services/ImportScopeFromActiveDirectoryService.cs new file mode 100644 index 0000000..7f3ff2e --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/ImportScopeFromActiveDirectoryService.cs @@ -0,0 +1,100 @@ +using Dionach.ShareAudit.Model; +using System.Collections.Generic; +using System.DirectoryServices; +using System.Threading.Tasks; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class ImportScopeFromActiveDirectoryService : IImportScopeFromActiveDirectoryService + { + public async Task Import(string domain, string username, string password, ImportComputerType importComputerType) + { + return await Task.Run(() => + { + var computerNames = new List(); + + using (var directoryEntry = new System.DirectoryServices.DirectoryEntry($"LDAP://{domain}", username, password)) + { + using (var directorySearcher = new DirectorySearcher(directoryEntry)) + { + switch (importComputerType) + { + case ImportComputerType.Servers: + directorySearcher.Filter = "(&(objectClass=computer)(operatingSystem=Windows Server*))"; + break; + + case ImportComputerType.Workstations: + directorySearcher.Filter = "(&(objectClass=computer)(|(operatingSystem=Windows XP*)(operatingSystem=Windows Vista*)(operatingSystem=Windows 7*)(operatingSystem=Windows 8*)(operatingSystem=Windows 10*)))"; + break; + + case ImportComputerType.All: + directorySearcher.Filter = "(objectClass=computer)"; + break; + } + + directorySearcher.SizeLimit = 0; + directorySearcher.PageSize = 250; + directorySearcher.PropertiesToLoad.Add("dNSHostName"); + + using (var searchResultCollection = directorySearcher.FindAll()) + { + foreach (SearchResult searchResult in searchResultCollection) + { + if (searchResult.Properties["dNSHostName"].Count > 0) + { + computerNames.Add(searchResult.Properties["dNSHostName"][0] as string); + } + } + } + } + } + return string.Join(", ", computerNames); + }); + } + + public async Task Import(string domain, ImportComputerType importComputerType) + { + return await Task.Run(() => + { + var computerNames = new List(); + + using (var directoryEntry = new System.DirectoryServices.DirectoryEntry($"LDAP://{domain}")) + { + using (var directorySearcher = new DirectorySearcher(directoryEntry)) + { + switch (importComputerType) + { + case ImportComputerType.Servers: + directorySearcher.Filter = "(&(objectClass=computer)(operatingSystem=Windows Server*))"; + break; + + case ImportComputerType.Workstations: + directorySearcher.Filter = "(&(objectClass=computer)(|(operatingSystem=Windows XP*)(operatingSystem=Windows Vista*)(operatingSystem=Windows 7*)(operatingSystem=Windows 8*)(operatingSystem=Windows 10*)))"; + break; + + case ImportComputerType.All: + directorySearcher.Filter = "(objectClass=computer)"; + break; + } + + directorySearcher.SizeLimit = 0; + directorySearcher.PageSize = 250; + directorySearcher.PropertiesToLoad.Add("dNSHostName"); + + using (var searchResultCollection = directorySearcher.FindAll()) + { + foreach (SearchResult searchResult in searchResultCollection) + { + if (searchResult.Properties["dNSHostName"].Count > 0) + { + computerNames.Add(searchResult.Properties["dNSHostName"][0] as string); + } + } + } + } + } + return string.Join(", ", computerNames); + }); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/NetUseConnection.cs b/src/Dionach.ShareAudit.Modules.Services/NetUseConnection.cs new file mode 100644 index 0000000..18c1d6c --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/NetUseConnection.cs @@ -0,0 +1,76 @@ +using Dionach.ShareAudit.Modules.Services.Interop; +using System; +using System.ComponentModel; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class NetUseConnection : IDisposable + { + private readonly string _networkName; + + private bool _disposedValue = false; + + public NetUseConnection(string host, string username, string domain, string password) + { + const int ERROR_SESSION_CREDENTIAL_CONFLICT = 1219; + const int ERROR_SUCCESS = 0; + const int ERROR_NETWORK_PATH_NOT_FOUND = 53; + + _networkName = "\\\\" + host; + var netResource = new NetResource() + { + Scope = ResourceScope.GlobalNetwork, + ResourceType = ResourceType.Any, + DisplayType = ResourceDisplaytype.Generic, + RemoteName = _networkName + }; + + var result = NativeMethods.WNetAddConnection2( + ref netResource, + password, + $"{domain}\\{username}", + 0); + + // If there is already a net connection, cancel it and try again + if (result == ERROR_SESSION_CREDENTIAL_CONFLICT) + { + NativeMethods.WNetCancelConnection2(_networkName, 0, true); + result = NativeMethods.WNetAddConnection2( + ref netResource, + password, + $"{domain}\\{username}", + 0); + } + + if (!(result == ERROR_SUCCESS || result == ERROR_SESSION_CREDENTIAL_CONFLICT || result == ERROR_NETWORK_PATH_NOT_FOUND)) + { + throw new Win32Exception((int)result); + } + } + + ~NetUseConnection() + { + Dispose(false); + } + + void IDisposable.Dispose() + { + Dispose(true); + + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + NativeMethods.WNetCancelConnection2(_networkName, 0, true); + } + + _disposedValue = true; + } + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/PortScanService.cs b/src/Dionach.ShareAudit.Modules.Services/PortScanService.cs new file mode 100644 index 0000000..ec419bb --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/PortScanService.cs @@ -0,0 +1,31 @@ +using System.Net.Sockets; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class PortScanService : IPortScanService + { + public bool IsTcpPortOpen(string host, ushort port, int millisecondsTimeout) + { + try + { + using (var client = new TcpClient()) + { + var result = client.BeginConnect(host, port, null, null); + var success = result.AsyncWaitHandle.WaitOne(millisecondsTimeout); + if (!success) + { + return false; + } + + client.EndConnect(result); + } + } + catch + { + return false; + } + + return true; + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/Properties/AssemblyInfo.cs b/src/Dionach.ShareAudit.Modules.Services/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..c1a0589 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/Properties/AssemblyInfo.cs @@ -0,0 +1,41 @@ +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following set of attributes. +// Change these attribute values to modify the information associated with an assembly. +[assembly: AssemblyTitle("Dionach.ShareAudit.Modules.Services")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Dionach.ShareAudit.Modules.Services")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible to COM components. If you +// need to access a type in this assembly from COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// In order to begin building localizable applications, set +// CultureYouAreCodingWith in your .csproj file inside a . For +// example, if you are using US english in your source files, set the to en-US. Then +// uncomment the NeutralResourceLanguage attribute below. Update the "en-US" in the line below to +// match the UICulture setting in the project file. +[assembly: NeutralResourcesLanguage("en-GB"/*, UltimateResourceFallbackLocation.Satellite*/)] +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located (used if a resource is not found in the page, or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly) // where the generic resource dictionary is located (used if a resource is not found in the page, app, or any theme specific resource dictionaries) +] + +// Version information for an assembly consists of the following four values: +// +// Major Version Minor Version Build Number Revision +// +// You can specify all the values or you can default the Build and Revision Numbers by using the '*' +// as shown below: [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: CLSCompliant(false)] diff --git a/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.Designer.cs b/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.Designer.cs new file mode 100644 index 0000000..a548489 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Dionach.ShareAudit.Modules.Services.Properties +{ + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Dionach.ShareAudit.Modules.Services.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.resx b/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.Designer.cs b/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.Designer.cs new file mode 100644 index 0000000..d27f270 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Dionach.ShareAudit.Modules.Services.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.settings b/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Dionach.ShareAudit.Modules.Services/ScopeExpansionService.cs b/src/Dionach.ShareAudit.Modules.Services/ScopeExpansionService.cs new file mode 100644 index 0000000..abfb091 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/ScopeExpansionService.cs @@ -0,0 +1,64 @@ +using System; +using System.Collections.Generic; +using System.Net; +using System.Net.Sockets; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class ScopeExpansionService : IScopeExpansionService + { + private readonly IDnsUtilitiesService _dnsUtilitiesService; + + public ScopeExpansionService( + IDnsUtilitiesService dnsUtilitiesService) + { + _dnsUtilitiesService = dnsUtilitiesService ?? throw new ArgumentNullException(nameof(dnsUtilitiesService)); + } + + public IEnumerable ExpandScopeToIPAddresses(string scope) + { + scope = scope ?? throw new ArgumentNullException(nameof(scope)); + scope = scope.Replace(" ", string.Empty); + scope = scope.Replace("\t", string.Empty); + + var lines = scope.Split(",;\r\n".ToCharArray()); + + var scopeIPAddresses = new HashSet(); + + foreach (var line in lines) + { + if (!string.IsNullOrWhiteSpace(line)) + { + if (line.Contains("/") && IPNetwork.TryParse(line, out var ipNetwork)) + { + using (var ipAddresses = ipNetwork.ListIPAddress()) + { + foreach (var ipAddress in ipAddresses) + { + if (ipAddress.AddressFamily == AddressFamily.InterNetwork && (ipAddress.Equals(ipNetwork.Network) || ipAddress.Equals(ipNetwork.Broadcast))) + { + continue; + } + + scopeIPAddresses.Add(ipAddress); + } + } + } + else if (IPAddress.TryParse(line, out var ipAddress)) + { + scopeIPAddresses.Add(ipAddress); + } + else if (_dnsUtilitiesService.TryResolveHost(line, out var ipAddresses)) + { + foreach (var address in ipAddresses) + { + scopeIPAddresses.Add(address); + } + } + } + } + + return scopeIPAddresses; + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/ScopeNormalizationService.cs b/src/Dionach.ShareAudit.Modules.Services/ScopeNormalizationService.cs new file mode 100644 index 0000000..220d5f9 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/ScopeNormalizationService.cs @@ -0,0 +1,23 @@ +using System; +using System.Linq; +using System.Threading.Tasks; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class ScopeNormalizationService : IScopeNormalizationService + { + public async Task NormalizeScopeAsync(string scope) + { + return await Task.Run(() => + { + scope = scope ?? throw new ArgumentNullException(nameof(scope)); + scope = scope.Replace(" ", string.Empty); + scope = scope.Replace("\t", string.Empty); + + var lines = scope.Split(",;\r\n".ToCharArray()); + + return string.Join(", ", lines.Where(x => !string.IsNullOrWhiteSpace(x))); + }); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/ScopeValidationService.cs b/src/Dionach.ShareAudit.Modules.Services/ScopeValidationService.cs new file mode 100644 index 0000000..2e9b654 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/ScopeValidationService.cs @@ -0,0 +1,23 @@ +using System.Threading.Tasks; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class ScopeValidationService : IScopeValidationService + { + public async Task<(bool isValid, string errorMessage)> ValidateScopeAsync(string scope) + { + return await Task.Run(() => + { + var result = (isValid: true, errorMessage: string.Empty); + + if (string.IsNullOrWhiteSpace(scope)) + { + result.isValid = false; + result.errorMessage = "Scope cannot be empty"; + } + + return result; + }); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/ServicesModule.cs b/src/Dionach.ShareAudit.Modules.Services/ServicesModule.cs new file mode 100644 index 0000000..54a5175 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/ServicesModule.cs @@ -0,0 +1,28 @@ +using Prism.Ioc; +using Prism.Modularity; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class ServicesModule : IModule + { + public void OnInitialized(IContainerProvider containerProvider) + { + } + + public void RegisterTypes(IContainerRegistry containerRegistry) + { + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + containerRegistry.RegisterSingleton(); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/ShareAuditService.cs b/src/Dionach.ShareAudit.Modules.Services/ShareAuditService.cs new file mode 100644 index 0000000..59eea72 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/ShareAuditService.cs @@ -0,0 +1,481 @@ +using Dionach.ShareAudit.Model; +using SimpleImpersonation; +using System; +using System.Collections.ObjectModel; +using System.ComponentModel; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using System.Threading; +using System.Threading.Tasks; + +namespace Dionach.ShareAudit.Modules.Services +{ + public sealed class ShareAuditService : IShareAuditService, IDisposable, INotifyPropertyChanged + { + private readonly BackgroundWorker _backgroundWorker; + + private readonly IDnsUtilitiesService _dnsUtilitiesService; + + private readonly IFileSystemOperationService _fileSystemOperationService; + + private readonly ParallelOptions _parallelOptions = new ParallelOptions + { +#if DEBUG + //MaxDegreeOfParallelism = 1 +#endif + }; + + private readonly IPortScanService _portScanService; + private readonly IScopeExpansionService _scopeExpansionService; + private readonly ISmbUtilitiesService _smbUtilitiesService; + private bool _disposedValue = false; + private bool _isBusy; + private Project _project; + private SynchronizationContext _uiContext; + + public ShareAuditService( + IScopeExpansionService scopeExpansionService, + IDnsUtilitiesService dnsUtilitiesService, + IPortScanService portScanService, + ISmbUtilitiesService smbUtilitiesService, + IFileSystemOperationService fileSystemOperationService) + { + _scopeExpansionService = scopeExpansionService ?? throw new ArgumentNullException(nameof(scopeExpansionService)); + _dnsUtilitiesService = dnsUtilitiesService ?? throw new ArgumentNullException(nameof(dnsUtilitiesService)); + _portScanService = portScanService ?? throw new ArgumentNullException(nameof(portScanService)); + _smbUtilitiesService = smbUtilitiesService ?? throw new ArgumentNullException(nameof(smbUtilitiesService)); + _fileSystemOperationService = fileSystemOperationService ?? throw new ArgumentNullException(nameof(fileSystemOperationService)); + + _backgroundWorker = new BackgroundWorker + { + WorkerSupportsCancellation = true + }; + + _backgroundWorker.DoWork += DoWork; + _backgroundWorker.RunWorkerCompleted += RunWorkerCompleted; + } + + ~ShareAuditService() + { + Dispose(false); + } + + public event PropertyChangedEventHandler PropertyChanged; + + public event EventHandler Started; + + public event EventHandler Stopped; + + public bool IsBusy + { + get => _isBusy; + private set + { + if (value == _isBusy) + { + return; + } + + _isBusy = value; + OnPropertyChanged(); + } + } + + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + public void StartAudit(Project project) + { + if (!_backgroundWorker.IsBusy) + { + _uiContext = SynchronizationContext.Current; + _project = project; + _backgroundWorker.RunWorkerAsync(); + } + } + + public void StopAudit() + { + if (_backgroundWorker.IsBusy) + { + _backgroundWorker.CancelAsync(); + } + } + + private void Dispose(bool disposing) + { + if (!_disposedValue) + { + if (disposing) + { + _backgroundWorker.Dispose(); + } + + _disposedValue = true; + } + } + + private void DoAuthenticatedHostWork(Host host) + { + if (host.State == HostState.EnumeratingShares && !_backgroundWorker.CancellationPending) + { + _uiContext.Send((_) => host.Shares = new ObservableCollection(), null); + + foreach (var shareInfo1 in _smbUtilitiesService.NetShareEnum(host.Name)) + { + if ((shareInfo1.Type & ShareTypes.Ipc) == ShareTypes.Ipc) + { + continue; + } + + _uiContext.Send( + (_) => + { + host.Shares.Add(new Share + { + Name = shareInfo1.NetName, + FullName = $"\\\\{host.Name}\\{shareInfo1.NetName}", + Type = shareInfo1.Type, + Remark = shareInfo1.Remark + }); + }, null); + } + + _uiContext.Send((_) => host.State = HostState.AuditingShares, null); + } + + if ((host.State == HostState.AuditingShares || host.State == HostState.NestedAuditingSuspended) && !_backgroundWorker.CancellationPending) + { + Parallel.ForEach(host.Shares, _parallelOptions, (share) => + { + if (!_backgroundWorker.CancellationPending) + { + if (share.Type.HasFlag(ShareTypes.PrintQueue)) + { + _uiContext.Send((_) => share.State = FolderEntryState.Complete, null); + } + else + { + DoFolderEntryWork(share); + } + } + }); + + if (host.Shares.All(x => x.State == FolderEntryState.Complete)) + { + _uiContext.Send((_) => host.State = HostState.Complete, null); + } + else if (host.Shares.All(x => x.State == FolderEntryState.Complete || x.State == FolderEntryState.NestedAuditingSuspended)) + { + _uiContext.Send((_) => host.State = HostState.NestedAuditingSuspended, null); + } + } + } + + private void DoFileEntryWork(FileEntry fileEntry) + { + if (fileEntry.State == FileEntryState.New && !_backgroundWorker.CancellationPending) + { + _uiContext.Send((_) => fileEntry.State = FileEntryState.EnumeratingAcls, null); + } + + if (fileEntry.State == FileEntryState.EnumeratingAcls && !_backgroundWorker.CancellationPending) + { + _uiContext.Send((_) => fileEntry.AccessRules = new ObservableCollection(), null); + + foreach (var accessRule in _fileSystemOperationService.EnumerateAccessRules(fileEntry.FullName)) + { + _uiContext.Send((_) => fileEntry.AccessRules.Add(accessRule), null); + } + + _uiContext.Send((_) => fileEntry.State = FileEntryState.GettingEffectiveAccess, null); + } + + if (fileEntry.State == FileEntryState.GettingEffectiveAccess && !_backgroundWorker.CancellationPending) + { + var effectiveAccess = _fileSystemOperationService.GetEffectiveAccess(fileEntry.FullName); + _uiContext.Send((_) => fileEntry.EffectiveAccess = effectiveAccess, null); + + _uiContext.Send((_) => fileEntry.State = FileEntryState.ReadingHead, null); + } + + if (fileEntry.State == FileEntryState.ReadingHead && !_backgroundWorker.CancellationPending) + { + if (fileEntry.EffectiveAccess.Read) + { + var head = _fileSystemOperationService.HeadFile(fileEntry.FullName, 1024); + _uiContext.Send((_) => fileEntry.Head = head, null); + } + + _uiContext.Send((_) => fileEntry.State = FileEntryState.Complete, null); + } + } + + private void DoFolderEntryWork(IFolderEntry folderEntry) + { + var level = folderEntry.FullName.Trim('\\').Split('\\').Count() - 1; + + if (folderEntry.State == FolderEntryState.New && !_backgroundWorker.CancellationPending) + { + _uiContext.Send((_) => folderEntry.State = FolderEntryState.EnumeratingAcls, null); + } + + if (folderEntry.State == FolderEntryState.EnumeratingAcls && !_backgroundWorker.CancellationPending) + { + _uiContext.Send((_) => folderEntry.AccessRules = new ObservableCollection(), null); + + foreach (var accessRule in _fileSystemOperationService.EnumerateAccessRules(folderEntry.FullName)) + { + _uiContext.Send((_) => folderEntry.AccessRules.Add(accessRule), null); + } + + _uiContext.Send((_) => folderEntry.State = FolderEntryState.GettingEffectiveAccess, null); + } + + if (folderEntry.State == FolderEntryState.GettingEffectiveAccess && !_backgroundWorker.CancellationPending) + { + var effectiveAccess = _fileSystemOperationService.GetEffectiveAccess(folderEntry.FullName); + _uiContext.Send((_) => folderEntry.EffectiveAccess = effectiveAccess, null); + + if (level > 1 && folderEntry is DirectoryEntry) + { + _uiContext.Send((_) => folderEntry.State = FolderEntryState.EnumerationSuspended, null); + } + else + { + _uiContext.Send((_) => folderEntry.State = FolderEntryState.EnumeratingFilesystemEntries, null); + } + } + + if (folderEntry.State == FolderEntryState.EnumeratingFilesystemEntries && !_backgroundWorker.CancellationPending) + { + _uiContext.Send((_) => folderEntry.FileSystemEntries = new ObservableCollection(), null); + + var directories = Enumerable.Empty(); + + foreach (var directory in _fileSystemOperationService.EnumerateDirectories(folderEntry.FullName)) + { + _uiContext.Send((_) => { folderEntry.FileSystemEntries.Add(directory); }, null); + } + + foreach (var files in _fileSystemOperationService.EnumerateFiles(folderEntry.FullName)) + { + _uiContext.Send((_) => { folderEntry.FileSystemEntries.Add(files); }, null); + } + + _uiContext.Send((_) => folderEntry.State = FolderEntryState.AuditingFileSystemEntries, null); + } + + if (folderEntry.State == FolderEntryState.AuditingFileSystemEntries && !_backgroundWorker.CancellationPending) + { + Parallel.ForEach(folderEntry.FileSystemEntries, _parallelOptions, (entry) => + { + if (!_backgroundWorker.CancellationPending) + { + if (entry is DirectoryEntry) + { + DoFolderEntryWork(entry as DirectoryEntry); + } + else if (entry is FileEntry) + { + DoFileEntryWork(entry as FileEntry); + } + } + }); + + if (folderEntry.FileSystemEntries.Where(x => x is DirectoryEntry).All(x => (x as DirectoryEntry).State == FolderEntryState.Complete) && + folderEntry.FileSystemEntries.Where(x => x is FileEntry).All(x => (x as FileEntry).State == FileEntryState.Complete)) + { + _uiContext.Send((_) => folderEntry.State = FolderEntryState.Complete, null); + } + else if (folderEntry.FileSystemEntries.Where(x => x is DirectoryEntry).All(x => (x as DirectoryEntry).State == FolderEntryState.EnumerationSuspended || (x as DirectoryEntry).State == FolderEntryState.Complete) && + folderEntry.FileSystemEntries.Where(x => x is FileEntry).All(x => (x as FileEntry).State == FileEntryState.Complete)) + { + _uiContext.Send((_) => folderEntry.State = FolderEntryState.NestedAuditingSuspended, null); + } + } + + if (folderEntry.State == FolderEntryState.NestedAuditingSuspended && !_backgroundWorker.CancellationPending) + { + Parallel.ForEach(folderEntry.FileSystemEntries, _parallelOptions, (entry) => + { + if (!_backgroundWorker.CancellationPending) + { + if (entry is DirectoryEntry) + { + DoFolderEntryWork(entry as DirectoryEntry); + } + } + }); + + if (folderEntry.FileSystemEntries.Where(x => x is DirectoryEntry).All(x => (x as DirectoryEntry).State == FolderEntryState.Complete) && + folderEntry.FileSystemEntries.Where(x => x is FileEntry).All(x => (x as FileEntry).State == FileEntryState.Complete)) + { + _uiContext.Send((_) => folderEntry.State = FolderEntryState.Complete, null); + } + } + } + + private void DoHostWork(Host host) + { + if (host.State == HostState.New && !_backgroundWorker.CancellationPending) + { + _uiContext.Send((_) => host.State = HostState.LookingUpPtr, null); + } + + if (host.State == HostState.LookingUpPtr && !_backgroundWorker.CancellationPending) + { + if (!_project.Configuration.DisableReverseDnsLookup && !string.IsNullOrEmpty(host.IPAddress)) + { + var ptrRecord = _dnsUtilitiesService.GetPtrRecord(IPAddress.Parse(host.IPAddress)); + _uiContext.Send((_) => host.PtrRecord = ptrRecord, null); + } + + _uiContext.Send((_) => host.State = HostState.CheckingPorts, null); + } + + if (host.State == HostState.CheckingPorts && !_backgroundWorker.CancellationPending) + { + if (_project.Configuration.DisablePortScan) + { + _uiContext.Send((_) => host.State = HostState.EnumeratingShares, null); + } + else + { + var arePortsAccessible = _portScanService.IsTcpPortOpen(host.Name, 445, 1500); + if (arePortsAccessible) + { + _uiContext.Send((_) => host.State = HostState.EnumeratingShares, null); + } + else + { + _uiContext.Send((_) => host.State = HostState.Complete, null); + } + } + } + + if ((host.State == HostState.EnumeratingShares || host.State == HostState.AuditingShares || host.State == HostState.NestedAuditingSuspended) && !_backgroundWorker.CancellationPending) + { + if (_project.Configuration.Credentials.UseCurrentCredentials || _project.Configuration.UseAlternateAuthenticationMethod) + { + DoAuthenticatedHostWork(host); + } + else + { + using (var netUseConnection = _smbUtilitiesService.CreateNetUseConnection(host.Name, _project.Configuration.Credentials.Username, _project.Configuration.Credentials.Domain, _project.Configuration.Credentials.Password)) + { + DoAuthenticatedHostWork(host); + } + } + } + } + + private void DoProjectWork() + { + if (_project.State == ProjectState.Configured && !_backgroundWorker.CancellationPending) + { + _uiContext.Send((_) => _project.State = ProjectState.ExpandingScope, null); + } + + if (_project.State == ProjectState.ExpandingScope && !_backgroundWorker.CancellationPending) + { + _uiContext.Send((_) => _project.Hosts = new ObservableCollection(), null); + + if (_project.Configuration.UseVerbatimScope) + { + Parallel.ForEach(_project.Configuration.Scope.Split(',').Select(x => x.Trim()), _parallelOptions, (target) => + { + if (IPAddress.TryParse(target, out var ipAddress)) + { + _uiContext.Send( + (_) => + { + _project.Hosts.Add(new Host + { + IPAddress = ipAddress.ToString() + }); + }, null); + } + else + { + _uiContext.Send( + (_) => + { + _project.Hosts.Add(new Host + { + PtrRecord = target + }); + }, null); + } + }); + } + else + { + Parallel.ForEach(_scopeExpansionService.ExpandScopeToIPAddresses(_project.Configuration.Scope), _parallelOptions, (ipAddress) => + { + _uiContext.Send( + (_) => + { + _project.Hosts.Add(new Host + { + IPAddress = ipAddress.ToString() + }); + }, null); + }); + } + + _uiContext.Send((_) => _project.State = ProjectState.AuditingHosts, null); + } + + if (_project.State == ProjectState.AuditingHosts && !_backgroundWorker.CancellationPending) + { + Parallel.ForEach(_project.Hosts, _parallelOptions, (host) => + { + if (!_backgroundWorker.CancellationPending) + { + DoHostWork(host); + } + }); + + if (_project.Hosts.All(x => x.State == HostState.Complete)) + { + _uiContext.Send((_) => _project.State = ProjectState.Complete, null); + } + } + } + + private void DoWork(object sender, DoWorkEventArgs e) + { + Started?.Invoke(this, new EventArgs()); + + if (!_project.Configuration.Credentials.UseCurrentCredentials && _project.Configuration.UseAlternateAuthenticationMethod) + { + var credentials = new UserCredentials(_project.Configuration.Credentials.Domain, _project.Configuration.Credentials.Username, _project.Configuration.Credentials.Password); + Impersonation.RunAsUser(credentials, LogonType.NewCredentials, () => + { + DoProjectWork(); + }); + } + else + { + DoProjectWork(); + } + } + + private void OnPropertyChanged([CallerMemberName] string propertyName = null) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + + private void RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) + { + if (e.Error != null) + { + throw e.Error; + } + + Stopped?.Invoke(this, new EventArgs()); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/ShareInfo1.cs b/src/Dionach.ShareAudit.Modules.Services/ShareInfo1.cs new file mode 100644 index 0000000..c314567 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/ShareInfo1.cs @@ -0,0 +1,21 @@ +using Dionach.ShareAudit.Model; +using Dionach.ShareAudit.Modules.Services.Interop; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class ShareInfo1 + { + internal ShareInfo1(SHARE_INFO_1 shi1) + { + NetName = shi1.shi1_netname; + Type = (ShareTypes)shi1.shi1_type; + Remark = shi1.shi1_remark; + } + + public string NetName { get; } + + public string Remark { get; } + + public ShareTypes Type { get; } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/SidUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/SidUtilitiesService.cs new file mode 100644 index 0000000..af69b43 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/SidUtilitiesService.cs @@ -0,0 +1,73 @@ +using Dionach.ShareAudit.Modules.Services.Interop; +using System; +using System.Collections.Concurrent; +using System.Runtime.InteropServices; +using System.Text; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class SidUtilitiesService : ISidUtilitiesService + { + private readonly ConcurrentDictionary _sidCache = new ConcurrentDictionary(); + + public string SidStringToAccountName(string host, string sid) + { + const int ERROR_SUCCESS = 0; + const int ERROR_INSUFFICIENT_BUFFER = 122; + + if (_sidCache.ContainsKey(sid)) + { + return _sidCache[sid]; + } + else + { + var name = new StringBuilder(); + uint cchName = (uint)name.Capacity; + var referencedDomainName = new StringBuilder(); + uint cchReferencedDomainName = (uint)referencedDomainName.Capacity; + + byte[] sidByteArray = null; + var sid_ptr = new IntPtr(0); + NativeMethods.ConvertStringSidToSid(sid, out sid_ptr); + var size = (int)NativeMethods.GetLengthSid(sid_ptr); + sidByteArray = new byte[size]; + Marshal.Copy(sid_ptr, sidByteArray, 0, size); + Marshal.FreeHGlobal(sid_ptr); + + var result = ERROR_SUCCESS; + if (!NativeMethods.LookupAccountSid(host, sidByteArray, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out var sidUse)) + { + result = Marshal.GetLastWin32Error(); + if (result == ERROR_INSUFFICIENT_BUFFER) + { + name.EnsureCapacity((int)cchName); + referencedDomainName.EnsureCapacity((int)cchReferencedDomainName); + result = ERROR_SUCCESS; + if (!NativeMethods.LookupAccountSid(host, sidByteArray, name, ref cchName, referencedDomainName, ref cchReferencedDomainName, out sidUse)) + { + result = Marshal.GetLastWin32Error(); + } + } + } + + if (result == ERROR_SUCCESS) + { + if (!string.IsNullOrEmpty(referencedDomainName.ToString())) + { + _sidCache.TryAdd(sid, referencedDomainName.ToString() + @"\" + name.ToString()); + } + else + { + _sidCache.TryAdd(sid, name.ToString()); + } + } + else + { + _sidCache.TryAdd(sid, name.ToString()); + } + } + + return _sidCache[sid]; + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.Services/SmbUtilitiesService.cs b/src/Dionach.ShareAudit.Modules.Services/SmbUtilitiesService.cs new file mode 100644 index 0000000..d128dee --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.Services/SmbUtilitiesService.cs @@ -0,0 +1,58 @@ +using Dionach.ShareAudit.Modules.Services.Interop; +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Runtime.InteropServices; + +namespace Dionach.ShareAudit.Modules.Services +{ + public class SmbUtilitiesService : ISmbUtilitiesService + { + public NetUseConnection CreateNetUseConnection(string host, string username, string domain, string password) + { + return new NetUseConnection(host, username, domain, password); + } + + public IEnumerable NetShareEnum(string host) + { + const int ERROR_SUCCESS = 0; + const int ERROR_MORE_DATA = 234; + const int ERROR_NETWORK_PATH_NOT_FOUND = 53; + const int ERROR_ACCESS_DENIED = 0x5; + + var maxPreferredLength = 0xFFFFFFFF; + var level = 1; + var entriesRead = 0; + var totalEntries = 0; + var resumeHandle = 0; + var offset = Marshal.SizeOf(typeof(SHARE_INFO_1)); + var bufPtr = IntPtr.Zero; + var result = ERROR_SUCCESS; + + do + { + result = NativeMethods.NetShareEnum(host, level, ref bufPtr, maxPreferredLength, ref entriesRead, ref totalEntries, ref resumeHandle); + + if (result == ERROR_SUCCESS || result == ERROR_MORE_DATA || result == ERROR_NETWORK_PATH_NOT_FOUND) + { + for (int i = 0, lpItem = bufPtr.ToInt32(); i < entriesRead; i++, lpItem += offset) + { + var pItem = new IntPtr(lpItem); + + yield return new ShareInfo1((SHARE_INFO_1)Marshal.PtrToStructure(pItem, typeof(SHARE_INFO_1))); + } + } + else if (result != ERROR_ACCESS_DENIED) + { + throw new Win32Exception(result); + } + + if (bufPtr != IntPtr.Zero) + { + NativeMethods.NetApiBufferFree(bufPtr); + } + } + while (result == ERROR_MORE_DATA); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToTrueMultiConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToTrueMultiConverter.cs new file mode 100644 index 0000000..81acac5 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToTrueMultiConverter.cs @@ -0,0 +1,29 @@ +using System; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + public sealed class AndTrueToTrueMultiConverter : IMultiValueConverter + { + public static AndTrueToTrueMultiConverter Default { get; } = new AndTrueToTrueMultiConverter(); + + public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + var andTrue = true; + foreach (object value in values) + { + if (value is bool) + { + andTrue = andTrue && (bool)value; + } + } + + return andTrue; + } + + object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException($"{nameof(AndTrueToTrueMultiConverter)} can only be used in OneWay bindings"); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToVisibleMultiConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToVisibleMultiConverter.cs new file mode 100644 index 0000000..caf668e --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/AndTrueToVisibleMultiConverter.cs @@ -0,0 +1,30 @@ +using System; +using System.Windows; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + public sealed class AndTrueToVisibleMultiConverter : IMultiValueConverter + { + public static AndTrueToVisibleMultiConverter Default { get; } = new AndTrueToVisibleMultiConverter(); + + public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + var visible = true; + foreach (object value in values) + { + if (value is bool) + { + visible = visible && (bool)value; + } + } + + return visible ? Visibility.Visible : Visibility.Collapsed; + } + + object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException($"{nameof(AndTrueToVisibleMultiConverter)} can only be used in OneWay bindings"); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/Base64Converter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/Base64Converter.cs new file mode 100644 index 0000000..b7cfce0 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/Base64Converter.cs @@ -0,0 +1,33 @@ +using System; +using System.Globalization; +using System.Text; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + [ValueConversion(typeof(string), typeof(string))] + public sealed class Base64Converter : IValueConverter + { + public static Base64Converter Default { get; } = new Base64Converter(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string) + { + return Encoding.Default.GetString(System.Convert.FromBase64String(value as string)); + } + + return string.Empty; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is string) + { + return System.Convert.ToBase64String(Encoding.Default.GetBytes(value as string)); + } + + return string.Empty; + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/BoolInversionConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/BoolInversionConverter.cs new file mode 100644 index 0000000..8e46aae --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/BoolInversionConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + [ValueConversion(typeof(bool), typeof(bool))] + public sealed class BoolInversionConverter : IValueConverter + { + public static BoolInversionConverter Default { get; } = new BoolInversionConverter(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return !(bool)value; + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + return !(bool)value; + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ComparisonConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ComparisonConverter.cs new file mode 100644 index 0000000..c67f186 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ComparisonConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + [ValueConversion(typeof(object), typeof(bool?))] + public sealed class ComparisonConverter : IValueConverter + { + public static ComparisonConverter Default { get; } = new ComparisonConverter(); + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return value?.Equals(parameter); + } + + public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return value?.Equals(true) == true ? parameter : Binding.DoNothing; + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsFalseToCollapsedConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsFalseToCollapsedConverter.cs new file mode 100644 index 0000000..6a4d3c8 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsFalseToCollapsedConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Windows; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + [ValueConversion(typeof(bool), typeof(Visibility))] + public sealed class IsFalseToCollapsedConverter : IValueConverter + { + public static IsFalseToCollapsedConverter Default { get; } = new IsFalseToCollapsedConverter(); + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return value is bool && !(bool)value ? Visibility.Collapsed : Visibility.Visible; + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException($"{nameof(IsTrueToCollapsedConverter)} can only be used in OneWay bindings"); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsStateInProgressToBoolConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsStateInProgressToBoolConverter.cs new file mode 100644 index 0000000..b889faf --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsStateInProgressToBoolConverter.cs @@ -0,0 +1,38 @@ +using Dionach.ShareAudit.Model; +using System; +using System.Globalization; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + [ValueConversion(typeof(object), typeof(bool))] + public sealed class IsStateInProgressToBoolConverter : IValueConverter + { + public static IsStateInProgressToBoolConverter Default { get; } = new IsStateInProgressToBoolConverter(); + + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value is HostState) + { + return (HostState)value != HostState.Complete && (HostState)value != HostState.NestedAuditingSuspended; + } + + if (value is FolderEntryState) + { + return (FolderEntryState)value != FolderEntryState.Complete && (FolderEntryState)value != FolderEntryState.EnumerationSuspended && (FolderEntryState)value != FolderEntryState.NestedAuditingSuspended; + } + + if (value is FileEntryState) + { + return (FileEntryState)value != FileEntryState.Complete; + } + + return Binding.DoNothing; + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException($"{nameof(IsStateInProgressToBoolConverter)} can only be used in OneWay bindings"); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsTrueToCollapsedConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsTrueToCollapsedConverter.cs new file mode 100644 index 0000000..b319fef --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/IsTrueToCollapsedConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Windows; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + [ValueConversion(typeof(bool), typeof(Visibility))] + public sealed class IsTrueToCollapsedConverter : IValueConverter + { + public static IsTrueToCollapsedConverter Default { get; } = new IsTrueToCollapsedConverter(); + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return value is bool && (bool)value ? Visibility.Collapsed : Visibility.Visible; + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException($"{nameof(IsTrueToCollapsedConverter)} can only be used in OneWay bindings"); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ItemDetailToStringConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ItemDetailToStringConverter.cs new file mode 100644 index 0000000..8fb5a96 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ItemDetailToStringConverter.cs @@ -0,0 +1,61 @@ +using Dionach.ShareAudit.Model; +using System; +using System.Linq; +using System.Text; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + [ValueConversion(typeof(FileSystemEntry), typeof(string))] + public sealed class ItemDetailToStringConverter : IValueConverter + { + public static ItemDetailToStringConverter Default { get; } = new ItemDetailToStringConverter(); + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (value is FileSystemEntry) + { + if ((value is FileEntry && (value as FileEntry).State < FileEntryState.Complete) || + (value is IFolderEntry && (value as IFolderEntry).State < FolderEntryState.GettingEffectiveAccess)) + { + return string.Empty; + } + + var fileSystemEntry = value as FileSystemEntry; + var sb = new StringBuilder(); + sb.AppendLine(fileSystemEntry.FullName); + sb.AppendLine(); + sb.AppendLine($"Effective Access: {(fileSystemEntry.EffectiveAccess.Write ? "Write" : "Read")}"); + sb.AppendLine(); + + var maxIdentity = Math.Max("Identity".Length, fileSystemEntry.AccessRules.Max(x => x.Identity.Length)); + var maxRights = Math.Max("Rights".Length, fileSystemEntry.AccessRules.Max(x => x.Rights.Length)); + var maxType = Math.Max("Type".Length, fileSystemEntry.AccessRules.Max(x => x.Type.ToString().Length)); + var maxInherited = Math.Max("Inherited".Length, fileSystemEntry.AccessRules.Max(x => x.Inherited.ToString().Length)); + sb.AppendLine($"| {"Identity".PadRight(maxIdentity)} | {"Rights".PadRight(maxRights)} | {"Type".PadRight(maxType)} | {"Inherited".PadRight(maxInherited)} |"); + sb.AppendLine($"| {string.Empty.PadRight(maxIdentity, '-')} | {string.Empty.PadRight(maxRights, '-')} | {string.Empty.PadRight(maxType, '-')} | {string.Empty.PadRight(maxInherited, '-')} |"); + foreach (var accessRule in fileSystemEntry.AccessRules) + { + sb.AppendLine($"| {accessRule.Identity.PadRight(maxIdentity)} | {accessRule.Rights.PadRight(maxRights)} | {accessRule.Type.ToString().PadRight(maxType)} | {accessRule.Inherited.ToString().PadRight(maxInherited)} |"); + } + + if (value is FileEntry && fileSystemEntry.EffectiveAccess.Read) + { + sb.AppendLine(); + sb.AppendLine("Preview:"); + sb.AppendLine("--------"); + sb.Append(Encoding.Default.GetString(System.Convert.FromBase64String((value as FileEntry).Head))); + } + + return sb.ToString(); + } + + return string.Empty; + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException($"{nameof(ItemDetailToStringConverter)} can only be used in OneWay bindings"); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/OrTrueToVisibleMultiConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/OrTrueToVisibleMultiConverter.cs new file mode 100644 index 0000000..5b3bf5f --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/OrTrueToVisibleMultiConverter.cs @@ -0,0 +1,31 @@ +using System; +using System.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + public sealed class OrTrueToVisibleMultiConverter : IMultiValueConverter + { + public static OrTrueToVisibleMultiConverter Default { get; } = new OrTrueToVisibleMultiConverter(); + + public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) + { + var visible = false; + foreach (object value in values) + { + if (value is bool) + { + visible = visible || (bool)value; + } + } + + return visible ? Visibility.Visible : Visibility.Collapsed; + } + + object[] IMultiValueConverter.ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException($"{nameof(OrTrueToVisibleMultiConverter)} can only be used in OneWay bindings"); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ShareTypeToIconSourceConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ShareTypeToIconSourceConverter.cs new file mode 100644 index 0000000..0d3a132 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/ShareTypeToIconSourceConverter.cs @@ -0,0 +1,41 @@ +using Dionach.ShareAudit.Model; +using System; +using System.Windows.Data; +using System.Windows.Media; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + [ValueConversion(typeof(ShareTypes), typeof(ImageSource))] + public sealed class ShareTypeToIconSourceConverter : IValueConverter + { + public static ShareTypeToIconSourceConverter Default { get; } = new ShareTypeToIconSourceConverter(); + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + if (!(value is ShareTypes)) + { + throw new NotSupportedException($"{nameof(ShareTypeToIconSourceConverter)} can only be used with {nameof(ShareTypes)} values"); + } + + switch ((ShareTypes)value) + { + case ShareTypes.Disktree: + return "/Dionach.ShareAudit.Modules.UserInterface;component/Images/imageres_4.ico"; + case ShareTypes.Device: + return "/Dionach.ShareAudit.Modules.UserInterface;component/Images/imageres_32.ico"; + case ShareTypes.PrintQueue: + return "/Dionach.ShareAudit.Modules.UserInterface;component/Images/imageres_51.ico"; + case ShareTypes.Special: + return "/Dionach.ShareAudit.Modules.UserInterface;component/Images/imageres_78.ico"; + + default: + return Binding.DoNothing; + } + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException($"{nameof(ShareTypeToIconSourceConverter)} can only be used in OneWay bindings"); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToCollapsedConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToCollapsedConverter.cs new file mode 100644 index 0000000..79012f5 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToCollapsedConverter.cs @@ -0,0 +1,22 @@ +using System; +using System.Windows; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + [ValueConversion(typeof(string), typeof(Visibility))] + public sealed class StringIsNullOrEmptyToCollapsedConverter : IValueConverter + { + public static StringIsNullOrEmptyToCollapsedConverter Default { get; } = new StringIsNullOrEmptyToCollapsedConverter(); + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return string.IsNullOrEmpty(value as string) ? Visibility.Collapsed : Visibility.Visible; + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException($"{nameof(StringIsNullOrEmptyToCollapsedConverter)} can only be used in OneWay bindings"); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToTrueConverter.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToTrueConverter.cs new file mode 100644 index 0000000..9b36b7a --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Converters/StringIsNullOrEmptyToTrueConverter.cs @@ -0,0 +1,21 @@ +using System; +using System.Windows.Data; + +namespace Dionach.ShareAudit.Modules.UserInterface.Converters +{ + [ValueConversion(typeof(string), typeof(bool))] + public sealed class StringIsNullOrEmptyToTrueConverter : IValueConverter + { + public static StringIsNullOrEmptyToTrueConverter Default { get; } = new StringIsNullOrEmptyToTrueConverter(); + + public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + return string.IsNullOrEmpty(value as string); + } + + object IValueConverter.ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture) + { + throw new NotSupportedException($"{nameof(StringIsNullOrEmptyToTrueConverter)} can only be used in OneWay bindings"); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Dionach.ShareAudit.Modules.UserInterface.csproj b/src/Dionach.ShareAudit.Modules.UserInterface/Dionach.ShareAudit.Modules.UserInterface.csproj new file mode 100644 index 0000000..4ee989b --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Dionach.ShareAudit.Modules.UserInterface.csproj @@ -0,0 +1,170 @@ + + + + + Debug + AnyCPU + {28CF3837-FF58-463B-AF81-E6B0039DE55F} + library + Properties + Dionach.ShareAudit.Modules.UserInterface + Dionach.ShareAudit.Modules.UserInterface + v4.6.1 + 512 + {60dc8134-eba5-43b8-bcc9-bb4bc16c2548};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 4 + PackageReference + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + 4.0 + + + + + + + + + + + + + + + + + + + + + + + + + + Code + + + True + True + Resources.resx + + + True + Settings.settings + True + + + + + + + + AuditView.xaml + + + ConfigurationView.xaml + + + HelpView.xaml + + + ImportScopeView.xaml + + + WelcomeView.xaml + + + ResXFileCodeGenerator + Resources.Designer.cs + + + SettingsSingleFileGenerator + Settings.Designer.cs + + + + + + + + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + 4.7.0.9 + + + 7.1.0.431 + + + + + + {1DFC488D-E104-4F35-98DA-F23BF6D3F9DC} + Dionach.ShareAudit.Model + + + {F5BFA34B-3CDE-4C77-9162-96666303FDEA} + Dionach.ShareAudit.Modules.Services + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/BindingProxy.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/BindingProxy.cs new file mode 100644 index 0000000..bbd482c --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/BindingProxy.cs @@ -0,0 +1,21 @@ +using System.Windows; + +namespace Dionach.ShareAudit.Modules.UserInterface.Helpers +{ + public class BindingProxy : Freezable + { + public static readonly DependencyProperty DataProperty = + DependencyProperty.Register(nameof(Data), typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null)); + + public object Data + { + get => (object)GetValue(DataProperty); + set => SetValue(DataProperty, value); + } + + protected override Freezable CreateInstanceCore() + { + return new BindingProxy(); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/PasswordHelper.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/PasswordHelper.cs new file mode 100644 index 0000000..19bc50a --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/PasswordHelper.cs @@ -0,0 +1,68 @@ +using System.Windows; +using System.Windows.Controls; + +namespace Dionach.ShareAudit.Modules.UserInterface.Helpers +{ + public static class PasswordHelper + { + public static readonly DependencyProperty AttachProperty = DependencyProperty.RegisterAttached("Attach", typeof(bool), typeof(PasswordHelper), new PropertyMetadata(false, OnAttachChanged)); + + public static readonly DependencyProperty PasswordProperty = DependencyProperty.RegisterAttached("Password", typeof(string), typeof(PasswordHelper), new FrameworkPropertyMetadata(string.Empty, OnPasswordChanged)); + + private static readonly DependencyProperty IsUpdatingProperty = DependencyProperty.RegisterAttached("IsUpdating", typeof(bool), typeof(PasswordHelper)); + + [AttachedPropertyBrowsableForType(typeof(DependencyObject))] + public static bool GetAttach(DependencyObject dp) => (bool)dp.GetValue(AttachProperty); + + [AttachedPropertyBrowsableForType(typeof(DependencyObject))] + public static string GetPassword(DependencyObject dp) => (string)dp.GetValue(PasswordProperty); + + public static void SetAttach(DependencyObject dp, bool value) => dp.SetValue(AttachProperty, value); + + public static void SetPassword(DependencyObject dp, string value) => dp.SetValue(PasswordProperty, value); + + [AttachedPropertyBrowsableForType(typeof(DependencyObject))] + private static bool GetIsUpdating(DependencyObject dp) => (bool)dp.GetValue(IsUpdatingProperty); + + private static void OnAttachChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + if (!(sender is PasswordBox passwordBox)) + { + return; + } + + if ((bool)e.OldValue) + { + passwordBox.PasswordChanged -= PasswordChanged; + } + + if ((bool)e.NewValue) + { + passwordBox.PasswordChanged += PasswordChanged; + } + } + + private static void OnPasswordChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e) + { + PasswordBox passwordBox = sender as PasswordBox; + passwordBox.PasswordChanged -= PasswordChanged; + + if (!(bool)GetIsUpdating(passwordBox)) + { + passwordBox.Password = (string)e.NewValue; + } + + passwordBox.PasswordChanged += PasswordChanged; + } + + private static void PasswordChanged(object sender, RoutedEventArgs e) + { + PasswordBox passwordBox = sender as PasswordBox; + SetIsUpdating(passwordBox, true); + SetPassword(passwordBox, passwordBox.Password); + SetIsUpdating(passwordBox, false); + } + + private static void SetIsUpdating(DependencyObject dp, bool value) => dp.SetValue(IsUpdatingProperty, value); + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewExtension.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewExtension.cs new file mode 100644 index 0000000..b4f763c --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewExtension.cs @@ -0,0 +1,65 @@ +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace Dionach.ShareAudit.Modules.UserInterface.Helpers +{ + public static class TreeViewExtension + { + public static readonly DependencyProperty SelectItemOnRightClickProperty = DependencyProperty.RegisterAttached( + "SelectItemOnRightClick", + typeof(bool), + typeof(TreeViewExtension), + new UIPropertyMetadata(false, OnSelectItemOnRightClickChanged)); + + [AttachedPropertyBrowsableForType(typeof(DependencyObject))] + public static bool GetSelectItemOnRightClick(DependencyObject d) + { + return (bool)d.GetValue(SelectItemOnRightClickProperty); + } + + public static void SetSelectItemOnRightClick(DependencyObject d, bool value) + { + d.SetValue(SelectItemOnRightClickProperty, value); + } + + public static TreeViewItem VisualUpwardSearch(DependencyObject source) + { + while (source != null && !(source is TreeViewItem)) + { + source = VisualTreeHelper.GetParent(source); + } + + return source as TreeViewItem; + } + + private static void OnPreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) + { + TreeViewItem treeViewItem = VisualUpwardSearch(e.OriginalSource as DependencyObject); + + if (treeViewItem != null) + { + treeViewItem.Focus(); + e.Handled = true; + } + } + + private static void OnSelectItemOnRightClickChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + bool selectItemOnRightClick = (bool)e.NewValue; + + if (d is TreeView treeView) + { + if (selectItemOnRightClick) + { + treeView.PreviewMouseRightButtonDown += OnPreviewMouseRightButtonDown; + } + else + { + treeView.PreviewMouseRightButtonDown -= OnPreviewMouseRightButtonDown; + } + } + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewHelper.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewHelper.cs new file mode 100644 index 0000000..c4e4308 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Helpers/TreeViewHelper.cs @@ -0,0 +1,62 @@ +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; + +namespace Dionach.ShareAudit.Modules.UserInterface.Helpers +{ + public class TreeViewHelper + { + // Using a DependencyProperty as the backing store for SelectedItem. This enables animation, + // styling, binding, etc... + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.RegisterAttached("SelectedItem", typeof(object), typeof(TreeViewHelper), new UIPropertyMetadata(null, OnSelectedItemChanged)); + + private static Dictionary behaviors = new Dictionary(); + + [AttachedPropertyBrowsableForType(typeof(DependencyObject))] + public static object GetSelectedItem(DependencyObject obj) + { + return (object)obj.GetValue(SelectedItemProperty); + } + + public static void SetSelectedItem(DependencyObject obj, object value) + { + obj.SetValue(SelectedItemProperty, value); + } + + private static void OnSelectedItemChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + if (!(obj is TreeView)) + { + return; + } + + if (!behaviors.ContainsKey(obj)) + { + behaviors.Add(obj, new TreeViewSelectedItemBehavior(obj as TreeView)); + } + + TreeViewSelectedItemBehavior view = behaviors[obj]; + view.ChangeSelectedItem(e.NewValue); + } + + private class TreeViewSelectedItemBehavior + { + private TreeView _view; + + public TreeViewSelectedItemBehavior(TreeView view) + { + _view = view; + view.SelectedItemChanged += (sender, e) => SetSelectedItem(view, e.NewValue); + } + + internal void ChangeSelectedItem(object p) + { + if (_view.ItemContainerGenerator.ContainerFromItem(p) is TreeViewItem item) + { + item.IsSelected = true; + } + } + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Images/Banner.png b/src/Dionach.ShareAudit.Modules.UserInterface/Images/Banner.png new file mode 100644 index 0000000000000000000000000000000000000000..1ae2155aa432ceb87c4c5b761fe46f0ada2be87f GIT binary patch literal 46935 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� zygui+zQwbW|6@Lm6wq{-(YUi>xsT>!e|OAnw7msU^nfRo2C<@Z44D#g@)D=-AEBr? zPu}0yFgJR%IugRW;Ue7%Dcf?JDnC`{>}v zX$SIyxyS*$!BT=1QMOrT1|6g0+uwh3@j24=P|1!W|IwZ8=`DfZ_&sNkY;pel0!sw* z`_nO*ja;(QG|ggx(y4mYQJM} zVUL3#I%eg|ufA_{Koz2SV_t&cR{ekFIW;R`S zyB7rIKG?_23EmkS^HjIt>V1u5-2=^=|Hr?I=zXAry*NI@8-w=5G|q0$p!`404cyuh-@4!XF8bn>M3BjSvh##7wv1bkn!Xgy!r)fytK={g%- zbYCgQ8ltESJI(LO)32TLk=J8$vt4fmfHmw$7b#p=7ljRXSx_9iI~ljwd*_+>AAD$l zZhqmTuSxEIS`kJh(YtljpfOT*0knE&dIu<@q_d*7(qlP^&8d&xx}r*9d%gLXtMe{I z*tMH4o(CHWZO$pTitcY0EiOThsY zs?JgNAAP!Xxx8F`+0NQ#YwY0~#y^E;t6@AazPuE~Mla=t5-QdfB&h49*}AG*d!|rR zjX0x+hok|_AFj&0x^ydZnuVqe9=++T9%y~Mz$*qe&fw`qs>kXdD^=;G?FkLNT8`8j zj$WWyo%^&aS@6dfNa>p+yTD=2UE{FvU#1Kqx{uheI_zRo5q!7(UUj>pah9epwJo3k z)Z6=GHLuWJ`nWhVdc>>T4cFK27-Fm1Ir>v9G{42JkYK8av4YQdd0m`Wh1;Y~)(hP2 zqir4`r|R%tpk3`bx6+E*0}CH1ibj*){+t?3(eg7z7BLJ{YcvhwvbJZcCD8O_{ zJd%UDN7mTe7DQNcx3|my2UXLEJy}8sG87g>Ye>*7${p7I!nkT%N zTvrTy#IZr2KOXq)Xhpq!JbVxYTK(?XQ=it>6b8}GI~34)shN3_e8+yL{zr#QAr=Qv z;g@rG?d8~pF4T|^G9+nx$!2Rvo9a6e>RtQN2q?=%3O5hG!g{+lw^3+y^1_T8vW-H$ znR(=O9c5sHaD7X0%>j}AoDc%er{cH7W%^d`Bxb(Nt%< zz~2nk4~zr7)~4AH%%9xTXC@8%ZWCG10I1!3)TQoSmAOPSJg$hn_y`x$F@MOX==ftW zgh9pfS^iYCfL>}~j=IRE4W@jzJ&80Us@Nx8pBrD+JN=OZV(s0#I z>y-1Hmrp-cpS^I4tF0HCUT{MBLSQc1#B<`Z#TG*~d~4tHc@ zDe&rnwwM?}G2F=I$`bWren{==gXx+KOME7sLU9{^iEE$YEQin63#9RHHfG9;`Xd{h zu`U2(hbv&;tIvvg`iei9+=0X&u*Z~743Haot8b}DgSPbLBAgikZ8X%<9lCv7Pu?K7 zUixWAse7bQfvXOoFt^4HPG>X7T0Wa!L z;y|eMJHkSK01TO6aye_(mGd-OvnVY>!9YoweFV1jb#O#&0xrYGpptNhZRN_4#n&uH zg%_>bp5nJ!AJ-v23m_q1(RZ7&pFoplakys#{+ZX8RmTAg2-=+2p(kK2^j$X={R&Qm z$9*N+OLrmhzZkj6=)x38ykL(;mR>_SS-o9_a`$CxS+0L~tWP^t8B3+Olcfq~rSamW zj&BcPp{)IUdzg3p#nBSj|1%2W7lh?=`E8I76g7XfQvo?<4C>D%`mO>5)a7bqr%t?$ z3;5D|D0bodxBNG9wiF=fm3QHPihhmUv2mcBo}-=_=SMBd980+OGNwC8;5Qd`WZGfY zHSJle|ApkCBFlZbfa^|hI|ZEtT7Vpd>5$64inH@aiP=u1A8d;S%g}pBMuT9ImcBwP z({d1q_kuqFKH^Ul1s64Y=n*%(qMxBQXuj((O*{yL=~ZMMSSJj%TOD^+)dERwxXGGG z;Mm`V28uCjHA{NHAZFp8L}=pJ){CNwurCji?ikg&`&SeK`&`kvQ`zc#8^m$_KiVyS z`2iQ7eDEKQSTL`j+wOg|O*{OF;+o|_OmUe!74{`RSM>|Il_&ZJV4&WqM48Av(ULoCc zIc!(stj?@$4rTO7mwqz^LmB8vhd&P79FHcRs$5|COtoII4>dfC$^GTp#|h3(n443p zx4TJ_auzak252B-wZh{BGg_UNy+V)5oz={7y!!dv^l9VZTfygdx3yFR*|T5en;2=A z;D!NqnXnx{UM8YNBeXWkSTDSeWX}M5wyjb&M27GdkF`gnaqdlJCET4obe8wQr`2e{ z_=xNVvp+`Um-{ojFFWv#nVheKQSkDwj&zO>nW;($SK+$WfhaeG>>3p!2(<09pJ_EC z{{ppmLX^Zl3NHJ6-wW?UVrQ;JPnbHr2;QHiy%>^bjUBG!v0o^jI^?`nF&uWC8ETK> zRo>o^OEay~4q#QG(|ICHV!Xg{fu(+m!=8-`?)&kP=8*&EA)rRg9oS!$J^L=cP{Kea zhxm>EMT58NRT4E$fKT|IYrI!uZfQlgyAjd<=7fW@xYC01VRhYLdTIN)_1hNaMI%hpjU2_t}2Fe z=+-t%F((vHd$WtH{19aq$!^sedO{-xy~)Cf>@xwVcWm;VG*RhpTHpKY&d+`-8-sm)5<1C zc0j1*P04=gAfu$|gI0l6-`~iZm{!k3(qcMHuzpnF7)p{tR<~00sfI=aRX0qG zI1L4CHX5Bnd9%ps*YTnW{qoW6!aJ-dV70BMel@pl<@?prdJ%bOcMiK+QbW)|Xd{l4 zej)!*d+3UXa>-k{%(s_c3tAJJfw00mCcxQVgIc_IET^dK4NSLg!yxsufw+h-0kH&n zQ47L!lY~KzIGY=o;vU_(Zh$ea1q0+;Pc83R)nbB9#|QrwvQ2-_YKT}HMsy^+e7ot3 zK}4PTpzOe;-mvKNY@1(F)@EJLf46sYaKCFkIBc&)3;dMlR!L6PI%Di|&dwmv@KcC6 z--VHZIC>+kZfBu$4T>vtEV+j;bN*Uk>`U6o_brc?1Rb+BWk(RGCp($4tRV%G0A8pX zamqd)!u<#`+^s_L>WJY~r~;^aLTJz@;#uK#no_%2*AKYycPzvoKAlV)hjAH*$@#bk z2m5&o(oz*u(Rt!@7_rZuoMO+KcdT;VG2eehB+TobPw6m30>w{f<5Ua{CmG9}oPqAb z3$WR-N4uabJ{L1Nv~yjf{-*%+(HsZ9v($?+u8jHc3n6MpiJ04+OmCv*9CHs3qzS}XNU zY}+Xvyap~HRq|1oN$oSpX!LglUm500hvrQV?}gfTJ38CQgMVx1vIK)a>qYvVF&1ma zFUW&kk+x3XuDmSPYD7lt4KH+GX_{Aoqi5~!%BH4o9z|Zi0d&60uk;v-;7-!mb9gb=;g)1#gu`zXXK@>pM3I^Tfs#zKaH;!S28L^)u1ar4 z*f2RwMNWGRmdIkgk&?&ESjK7uTr}k35c`&tVabX%GL^=bS>I^xC;kMc{gsEl@V8v| z^~Jg=w2HKoM-$Gw2$_4cKGV}zK>eL8kBT@jJE}_P$mmS$41e#GgoRiPRDXD%@LIw-w~W2a2u6E%PbtWq>66`Iy*&;ndZS@UR_Ol`p8)`nRSNc~g!$ zQJiBP+Y?A@3B>!#;JMVI)xOqCV!30{-6S!k^S7|faTPN|*l>a1fbaDZ7~H++bkKA2 zNPlsBPeb(-u#Y&swbeGWjd0p%u+M%eXlrW=iWmdUuARvlP0O0NWBY!U8pT~k?x-g+ zdJlQvnSOV#^81ErAn8=a4d?0Y#773gnZWHZ*Igy_!y>t(EFl-LybLls zR-b|yg&+Scopzns_N?vas~P$hC_gJylSs8`kvGL-&DhavenJo;nLY4^ZwQiX-afNL zF?Dy$&h(U2C)ia@wC6{=pT<9M&Bd_K2RZ*UPHYmn5EjYYF3jgfh*8l;axatzZ5o^) zFH~LHMaD6y!$*e9Tc_;d5LDoHdXU6i;Vnool7^MK9H<)gm_b$tIJgW)rQGbrXJ`yw z@de7<|4u;ebWcteZbm!YL%CRP8D%0g49b(PK11`o9QvF89@c45V`ZtW&IA|h!-zf@ zD!j8y@I^!rwpjD*QYD(%78jPd0om8iySF)x7l0D)KFx;`PR#obhtKuIn;n9JS{Hq5 zfE)U-6}Ol|oG4z`cf*S*v<^Im&eTHM>~)wYmZQ`itSi~Z_!Py3s_2Ws{%ir8H<=RVwnvz%cK%$~mktk2n9#S`Cw9@LucB*C zy6BK!1(3fdwcLzj9MXti{_G}9?!us#3Wh*{zTtjMMh!=Szvxh^2EkW9E8ZdVmmBRf zyD^ff*cc7#LcjY@%e&5L-2oR-8?HBpK(BKNgm2%bKE;M>9ze*pqDQt@IlZNV3#xEv z&^jMn!P{f0IEp?hf7H=_Hw#D04^-_g8IFEpz}Z-#6iky(*C7&|{uFYC2!hG8BwhrK zyX%mWH2Gyh`9som`lhnvDHtBmOwLY>HSYi)7~}7rmn6Tay=7jllmEEHIi_MNc|G}) zYtgdO>zhH9S;!w)#!=@i=6!Ub0|aEG`T)By3*cXIQvRXaQ<}i6AM1I>Tbxe zSVj`xM9qJ0wu@eCmpQE*q!g3nO-SLcM|2k979g5wyqsL#g_+|H=wvtK##^i&)Y#KM ztKQdY17^JrT~XQlB&!61|K%| zWo)Rnk)nbhAY5xtm63q-GV|I*^?|4)k<_}FG4iLFopsQXSKmlIh@0)}#SWsRP4JaKJ=zgILp-SdeX)-AY! zNeR^k;gD6?l#(jV6<-i_2Ej!6DqSA(#3S9p5{(YeLy#L&-Et#s7DP~TmS1^&6R06m zeE=|S8!6=TtZXjcbTIq{E_mSa1m<|)cRy|omJ&_!G`?8?0gNlJz`T7ECHTEJ6`AE< zkxjp#tXToQxHw*6Uk-5#p{oq9S$%$AU(=k~Pqt%4f)l48>B)=(Al8)#{E~qS zNzGFgZ4Sy7{dvS&_30-H!4qx{g&`D1o{>Qp%n+BH3@3ln*M4DOf}d(hHQ+3UEOKR@ z!1?@LpKSV#?SlKW>iV|jgPcqWQbOlK-B;OG(&i3h(P5$OOEs1~UO1=R#bt6qYn?BC2_B}6;{&v=FlPp>N}&mL z?}m56jF$lWJsWz`KGjXE{9p=lz@6PN5>>DM32mvVB@L9K1GBA?Lm#-9^iU z1hXRAUqZC-m-Ff}d}!Aq0|uA)CVmmR5}ATG%kk(Gt<8Ky^b*%2W5``~7s4VD*O zfgzK-dq!jn`*5sPnUNeH6{eV_`)knRRX6tnI~4j@L{(w>(FOVJAk}6D4H4A${d$2O zcj5O&W7N%CcsuHprp{MvE`|>3WO*g{oh;8@CC}N1K%TGSkju&1>^c%Q@Eti|(wPqn zHheIy{%)&Li56Gs{^ONJDe5jz8?P8SAac9>DXi?)tZ9xr_qD^C~hoNB8)(>QIKZLNw?m843K@l*a{a;h@W=mTl6A>5@-V zN-iU6oU%gx7u(v|6GcA-DZ z2Lgj2G4M?J^dj`*msFJ}WwOv8V6qVs$^0(5-*T8FqZE zs`=9iuq)}ORf11JL2)SeKIi^ROryQF@7%QMdS{8Twus{;IG$YZ5~J;`tOov;FEFhN z57OZ|+JQ)^)XZ9>YNsPN=tXFTz z_E&C^cxkb9;_nrzOs$#Ic*&KsFZ~6MkH7>rgZx0#6p!{~0liM>Q7fZ}$$78usC#zP%G^3~`qBh9 z+8Y(Qu*A;*ru*tVhC)9Hzp}-(JqKk~&0_xMFZibhnzZ;aiE-+3ziT{wjymuWmjOif zgtwYc{7VC4p6x18xZ%5sT_N(zl&cKM7YrPv*7OvV$LsjW_3qQC5PJp{&s=3S# zfRS{eYkF|Cqsj!T)4lEeXz86~rugo~6v;_;c5&w`z+L^a}jm_QaNm?GwFqgG-slFGo%4 z>{f@5XyP>aRzgjFRNbGrxjq2xjgld*=hk{2ljQu`(X#U->nmvw(sGY!n~r3|->aq1 z$EQO4z=m=mO>E=nZ88IqFY6zX!HqP#<1cr8RVK`W<)xxpRxw!-3aOo0tbU_}1j$;V zJD;rO$%pWJjV*UL=8f9Kjak~4f4xTn)3?z1K|5qOJ3J0_U7?H1No(W!{76KsVgrIV z6EPQj)RxHN@oKZG?V6Gr092S8_dxF|I!qbo5HnjcZ}imS2O?sA$~)&{vaBA@K_u^K zcP^z&ocrh*uocQoU!_o5D)0b_}szw2pXv{aK}BRY-^gV>F*ATN`g3`JJ#yruB| zw!70&NLs3wA9!0(1-MBVd28VNzKecY>+RDsMot$?Nj1z5D?-~PwbPU;`dSMFFercI z&zQC*vvxJjJN}kKka1MhcUUp)4iwp?oQJ?jJ;!0BIDD-ze3E8!L|n0yvd8z#ja8-V zMyx?_(|>eQe=~E%do{#X-ns|UUIi-i9UUou*?N-JpdFzp3U!zxPQ-P`5Gk0L_b;rw zNl9{2Y?3X$S|*@+SQ>>cEJnPm*~3OH)wo8^YSbG?0`w3{EU2JuhJW~i=bMP7pqWdr z`fDuCjRM-t+53hYuyUSw|6;Q9*5 zcKz;{tMS26n$9W1)H;dRFILN^BoPHSV_yS_n)CRvA+ z_h(rW9KYdcPXip=lq8_v6C@9apb_$>7Ul05HyLmB=SlDveZ`E1EaZTRx0ga31$o97 zKU(qnxGvZs#Ed#x-@#rly`WdG)Z^3?zXZws@=&OgxzcY`6|&TF&ZmrS!Oc41@bEN0 zM+DjYux4vp+eP3yT{C9$xIqb~_Va9<2`50Zv%#Vg*EMcV{GDQV^r;9$jcYk5toePt z8GA6UWTQw>2Q;}_f-=fq<$(pMdJE+Sa>`cYB`Z6x$~N0VLn@iVrtN5`H;>|GSFa<4 z`WkB{JBo|Q8O^~R#KJl)jn<+!5tPU%{G~1nIm^j!;{A_PPpuMGnVIHCWO?NR`Nno& z=bI_)Zn=Z>0SC%?V&^F(^bXTEpUuVyKH%EjizBQHon$JBL1TxF_YNi;iJK|c+>vwh z{S5LwRNN@N8w0RBg`+pDvtW!XoT%X~E_R9q`f~yP^Y!#9aGHGE4Dj~i;HnD_xn5s& zw%d!f*z|u=M}r!D7{9ruvt>cj-}GnaWyBZZ9iR43Mbv`}w4aXWu2}nm`HnW^7x;8S z5`%dA6}-A7x@Fbh3jh#ZA3WXOG$ej$lFcmnSLUyD@uyCI&@oySD8IQk1-Cn>LRl4m z*{3`>A3XBI3Z4@T`O|+SR_riY?01=xRLB}uk;@q_E~{^7I35_E3CW#uj5{b!o=4BF2v@$an+SD)oqg=kXk7-M^~%3p*u2 zbjWZ>4U;)rV||QnC~au5{?LGb!7DlN&c!{}_f=nh%0peHd`u&9=de7+>c(y=QNR2J zM`InZEV{faS`STM+rdYMep5N1%PkFnxjP~j370`%y*>Wkg;0S#qd4>|soCSnj2lr# zqUjmc@qN^DDfP6XEju8O1wJe(*K*L-l8(K(h34fB+%F-D?oUL6GmfC*0#6ZZ+A*$j zdB>vJfgw!8@+WJ2M0_t!!kc2nI_AKD(EDWpmwT7Fk1RaQ0N@L0}GBJ4)czjZAm^AeJCjZRR z4@a?F2%sPPGX7{jSh%ArsPhL_L-S#&}*o9OwK zfxY81iWA;vjdw6su%UGT9R&dLqwGKp++3XPjOUp;z%SQ5j)**$rb%+LA==WMM<^tB z@A}EuFtrX6htlD{qTu5tjJ`YmoXpP8-bSsWvp)^{r$NN>Jc75L*MMZ=R5f5F^08hSJRXXh#Lj{Rmj8zGdK7p1X?XUr6cl*(#!g<8vK8?VidEo8f!wy#kR-&URWH8RA9_Oy9hW&pysak1g8s!b8Ka}?{b+!VDZ(5xd?ek+-cEs zWc)~0+Nu>9P{YIyY*ida>U?znJ#0|yFZav9DW2v6=g1gqFoR^}#adEhI>3emUb}8| z3Gv_bcnaF^cy>S@L=cepc#VVG>-Zp5wB!}0r5Axp*uO$(oD)B{22~!`SoVOX{K$*6 zQL(P<&6?-L0f6SM7%v|>^g0e(bbZe|sJU$vvca>Lp)bo8JrotRrV@C-HGOvA<~`@M z?*6y|J%_N8h*XD^hJL34K`%Lu~cocEzlP9eKT=7jwPRxF> zV@J3Yee06tCFHTaw(kuCTLNr-Hw0C}_q_{l0OPo*J%SpE#V3%u=WjL5EZaVL$Uwvd zKXx3pa=@z7@c0=5Y@8D+V1ltrLpmqL~~N(F}qi2;yFUiu8)cRde6qvB}vfHuem~U1@2M;el$zr zd#MB2caMG^uzun~oc}2+!k4MHTL1RO9OZa0U7B2whUH`mA~&xB+A`@Mi*Z`2yR>l& zxRg%a&ggcaH<@tv& z+*ljRGS>zD1}cL$$C*nv$A(o=98b8BfBR*RQM4;J4iWu7bT$uh zM-2XVqtLljR4nNVna%fh$l@@?cy*)o-RG#t?{h}0>b@h6|2BvcR{FrmCY|i6q=^>T z@Z_tYpNqPC8jNA6t=xl$5e688xq$2G$K)zCVSi&hMm6*#e?*3b+~mQV59{ezQZ~C} zJ}rJB#A+99@-=5uvR}i|D?o?}L&C8>+i}Lfmp@{H)p ze@5^m=Z5Mz^|vAP7>mu!UCNvCx|@ZVN>jW|ltzGHEWz5rT{_^%0sc6+bQ?Bexd)&7 zpy}v8Y4-p{k~%U&R}swUr>p$o;l@XwOvq4=S@rX6T{tq}Hf0wzpwe;X?}ZIu+Zr+Z_eKK>qUm&aHobqU)3-3f&$+T)$(>S}%~UAHfHHGb*?qr)ijHSQ5Mb zMgDmI-yLF5>v7ujfuQB>{0q-JZ)Gd$VUj#G#(<{t%=lAW8Y_b3$7B7ck26`taz5(i zhvax=ttrfZ8%IJr8A)qo^@lz!GDbpnZ zST`IHZ)A!SU>ecZT&1meglz)criG2t)J_nm%Mk3+p^#&t&m>q1GTE z#eJecvNcEwr`xiF!798x=1X%m*8Amg{7CZjKB0fawhR8Yp@dFGS+)VVvB|mATV!;e zk_0T+Vam=2$&*Ud4g~ZMpRk#b z4hDgn?3yPe&^wgQ-kVZ$RL&1L@74iwXHBj%m&Rb-7c}UbqXfDR`*T;s0mn&bPh1goX56qG!~O zeu=%}mJII^T2Q^RR!2^@0VyA%TCrr;-Ra@Grv+~?|B~bb8Ss272UH&RAk={LxFqmr z2Ov&WhqlqyKh+d`NB#r5+rF3*t&=eF=F#D!^H&p{YwFW zA=t~s1K+|5mZup#q44rQsbsXVlzEPG@Orqf89SbtSEu3GIn1!la1_)-2V<`toh^{-B2KQF5;?{ z-3HTreIAUD!Q&z{qKw~x2IYe98-J7B920+NZh1;D1pwYZUXS%>10XIkPkOqPso_gh z{B~)^_A}t0V-xI~=PNVqMz!yHjMEfn7@gJ#umzm%F9Yr0$M~WFOws@)u^!JZuvFRw z^tY4&nWr62eS89mU)Yrce#g2$8#AMr7DTe97uQe@aNhJk(6#OQNdbIcQ^SG!M=y@_ zVYqy0^lzm39<295d}W(%s9P~nw-ae>W~RO=_YhA1q2teTdYILp6rS{4Vo4^VX+-Dk zEsZBhU1MDL|6%H_!=iw;?%^Q>kp`8PZk6r^m2N>o8l;r&8jwc1JEXfskdj7_k`Sc3 zyJO}%!@ckQegF9IJTlBawa-3luf6t+>ogI%cr&*@3WRXwSr~1o2g1MGe+421Js~9A zY|KFcB=vR~%{ZgHdzosMfF5$$7mVVo^*2iopfA-S=@1N3__I}1e!8Ct5cDcS!z?aF zSLEmKzd6z=`A%}f%FoZwe~%pQD}VJnS-d!0PqUrx8O21JnMt3bt<=X_*BkG6HXc1x*LB>I+<77dg-ZW;AHrmH6D&(iPBZbva>tn;04|l1 zBHD4;D2r;q;XSm6bV>VwT5NmdyhDERDMMsdBsF%Jh({1?QBa7$1^I0)fp0qGlpnH( zp`x79P;{h>n6T8`UG!LsMe^ReZ=d+i3c@19VBnP!wwvF&GJd@Ej3%TSU_}ZiG}QQHwod~0&Yh}H zJyNDSfKid94m3i4PiF zR}2awS^1Dtq4cy#>#!oNvw5>xtE2>?)C(Z#%w95fAw|Ccaf*tz;qr-WwZws8IoklLTXpM5&B74UX^RYcLrx%IFXYCEwPBD6>h75iWkN1cM!V)m!QS>N{+c5$YGobRx;GP&W8i@qIC5w(%h>Up}vV z93Gu~twav_;9po}m;3Y=X8AaFh$cA%Oh1!5YWov=NWg~twe0CBO3#86t3btKBBysPzZ?=;R%*dH+8QJ=~$7vE-8bo}o*#IeUg0|X(gvduXJ6+W1NLQT8=BqvV9Igg7YuNwH)6f8skVn?|L?@CLPnaZtO0d`A>nWi4j3Q%1GF(HA$iwSOmD`@Q{?s{n8mEw&$wu5Ql7Ubi;%xY!4$l2 z?pk?@ugkZNutACIC?>U`Wc53OI39wA{h|f?aSd7=U$_1*D2C@Bcg)zMZRJbgMQ>z? zO8Dr~dx8!7;AHL|EUV_k20zGq=O66tc6J*ZJ-g0r9A~WlJAQDJkKk#9FB(_#4QTJQ2{$3fujV+Zn?3CQ(iDHDvnC3c7_tl=3tK-GwTN!HgW* zv`bBn{NN@BUrLlGK!gK1?LCPrDJ8_n4?MNo$@6C2J`Ih`JukrKF(1zS_MkNF(c`ui z{>osl=AyR^liL>~_76Q4KHoxli(9Aa&aTNJr^$b%WH&9rg{k}$fqt;P^5ust=emU$_V`L z#&iJQ;}xs3x|+9ivdZkN=Kgc5n-JqspM`V*NBv*dW*HHduaN}Mvf;V*8@czc-eUOa zc$^U7b90>sS8pQqLm&ue?t!O6<&S_VYU%F@m}V5#q4a%Y={M!2dtTdW0=OOUNAJ2K=4dN zUgk2bo{7bhx!KCwcS(+%`PK%)6fA}Z)wU3s&ow?3xw$6tk>6~N zmm!N&!U@d_f=Wx*FUa(QApW=EbldzG6N=>;V$ynq;JhXQ#h;OdTk2vDxsEf#$Es7i zWxoWk>>x!ggnLQxcX%Q=-J_V;;Ih25@Ii-E8+N-)eocrPkn;_RNr1Q;mS}EY=aDv& z6afN3?+bP&e+~O2q+lrFSL;jsaIm!?+YR1&v1eq2a?|Kk?Liyx&XX5b_B#>R52Ow5 zzdl?DQoujc%2EvbywdmsLE~MqWjS7iY^rQ`$|mq(bZb0+JU{$@CSapOIQCKbl{JxH z$zCjG#knGfix}>CCh{Ydpc1Y@dqk*D0)*mjKvI^K|2i>vzb_ZdxJ`a<|I9F}f0XwX zxNO<^chzH)siIXfqjUZNK$Jw9%@?rYWlOMRWM}*Y(IcQWa5$P+i*+&I&AjS*%2l$NQSWdWi(}DqGDcOXE zqrc#xgxP{4e$S#`p>NC9DtlOU+wXeE^+Cf>C_LyYiwBZWvPaTP?_8WGok_BOKVoQ> zJt%4Y1`@16xs@2ecgQDFKoIHU@qnpQ!e9CTbEOC8d@zmAmJlc%&};152%oxMNj|8vS>PysqtrpTKu_*H z;2$~=D7z|#Ye>0nK}MsD15uD%NN~EFl*6_=eG%0#EW@6)Ou%AYK7k$5R3WX0a5DfR zVwy+_;E<3MboqPk2~rDmKrM+TRFDL38cTo%afHh^!2LUoQDqRTHN-lkpT@~s^$~`?Q%H;VS2ky_^X*MbEFWv;C z(L`xm&iWC)$3pDPxnW&h`MY9{r#$!fYz+#;`7Ajv9HV>!%0E*;544otduknf#|xw3 zJ-ELB9U)E?ezleySAQDB5;t#N0Yx*p$ZtjnnWc2pg);@?({FoXV1@=}8yyXh zrFJ9p81U(AGMIONv5W(ytY!K?I1n>7<*!+bHMZABY{0N^b4vM|C{BCk8(xoFUk$wN z`(5)M$G2iF%f#OFers!n2(OQ$P*(dfs@y)BlzO@qJO4c!fnGj_xf*+*!DGJ@ zifbY($fy=)(g~_6&nk7p3|MUw->_0eqYnqHDUTa7$jpd_=h`HLl0cQVwp7zQhEX|@ zi0iqZEOm5!7a(SERVd4HAn*O~#OnSjV1^E1VB`Z>Otr6Frp&f; zA6*_7R@ofL%_-eG<`oFl3Usv5+UOP`Gem^8RKnPeF+ybuFz@ueZOpl?TJu#(FDK(M zJLEn&-!Ohq!o%G`bZ9RLw!1z7fdlC|c6UdJA=MxDhXkHD z8C?^4cbOU%0w7F0Q5!${7ndp?oh{kqZ9btPKLw-slz^=%HYz2%=1b+0>Nvair5v& z4x_n>*YWDJ$!ST#0+10~@^~ev#=?%=10qCh0Hc6l-iTT6ZE(Tv(5MBFt~Et9>It)f zGDg1;5?aj$YLV%!=Q8QpgaA@ga3mq4IIl{Hodqch6+=L8?V}ecXyt$6ep&N9yNM!q zrdtIS&i$K7V?_%(4fAA3AI4T*MEcMa_xmenTAU7*Aa*9A;#>EDh-ioFrU{{XL%c}G zC@+t@1El@0du?vqUM0VQP2`xsg4KN-y$1mSRV}?MtP@MpmcG5?$-zb`OI#|U;U-a; z#Px2K8`z@AAmb!B&*jBwz{63@kJJM*-=kH6h@H-VX6Dd;EcRuN{8lMv;wA1K@)4S;iQ>+P;z3sA zKaK#(hwFe$066|2?h&AlFdL(@Nk301d-LZrH-m@ycvt=-vGq+_+DgRfymNlYH~Raf ztbzc)RrrL34npQol`T`jYHSgsyO7qSvI)!@c-wv`E45LIcLWVUsp*SLaq?&8}u@{KdNuHqv_B}e9QJMY!`wwLc*H`ArBSm#{OcBa9#Qg zS#eVUz}-s#D-B+d&``v zBOiUwT3Iu{SjNNo3**~FonoPW_x=*@fdB5hT8wN>Wwg#P9fdiy^KSA_>T%NO;;O~N z`J+)WhnI(Xny-t1AlE&d69G0EF#yzAZ?o&G#B!A&O}V1P@C-UmkW3!>_!lHY^e|NB z7!+4gvroD)qWN*2+i~}CyBzW^yVM2FX=Ti=JbpyUCb%7#ck)LetPX}+iq8xrUGUh6 zhZRj?rQOlAgZ<99%H-62IRzOaZt0c;YkV~2qNKp>f8w9* z0{SQ%NEJs%GjpgYmBOGR9;h90#%BOEUq>;x=;Cj|GSe&hvS=bZ#Sj`!#D!L(ZibCw z7>2`_Ho;aLl=Ia|hgg=O^2?_~%V>xQt!!E~dxq`tdID9|_pUjU5 z{vhtOYg@$<6Xyy5pD5`(-hQchiOi{{Lo-drr0kEdXw7G_Dzn`ol*RXk{zVe^?kMT{ zHQ(Rn@5y<*4amv|ieF#8CD^B=S!i#V2EXKG`VR?Am!koD50v~(!vaQ?t7Zn|L(1*2 zmVZy^KXBC3AuXMZ_GN@|zg8e77up#E8#QPzFa#$l=|-!S=xlDUSMQmr<-+XsMQ8YV zo}`2i!wAh>|1`T#GL$K=0-)0{=u_JR%MkCW13Ag7UbA|!(s(kUyv0j_rofWM>|wl} zRa5CF7*pYZfYVH;k$h{!L_-CC-_1}-|EmM=n)u@c5O+dpb1uK=Wbit!uYW4j`~z7z z{#`_wwVLJZ??Q)gniVM7wQv;n+Z8UD2!|46hS+YcQ}d$zoZQo_4@2CRK*ob!{K63M zCh`#4OqrQEe+(^@ZDyTu;XE8IAbq)CcP{Ap)Oy4rdNd-)9jZnlPQN5^*koMIe#I)N zj)pQrmgC4Woa8BKZ=w=zDt#&kM&{d|$5C%SC1StR;7t?0{X(l;!C&^L%gD|3>4{>c zw8$+}=@Syfz5lTiQgxwRrT^#0!iL?rqHHDw-UdXU;?p z{?O7hGA|^Qg0ch?aVmK!AWr7rYNsQ zLxXM4wppkmJ03{JWhrsBF=bs&!z1+SmvgkqA;9TSErH0+c#k>7r%=itz{Me z(YcGnve0CEm;I7yaN%j^B|IysuwXX+nj%XL5gA483P3h(?1%dz)^R@V_V9EZkU1Zq zxt160xt9wIAgOV{*uFNmOzUuSrqvfk&ZA~P-ohTgn_46x_muyPbKdbB>oZ+h?~4)U zNuk+#-=j-R;_(W)x!CRJtf3?-&N)kF&T*s9aUjTO{rlQQ3-MVtncHGdzH}>;3)6Hy z6x7d5@aotw(f!u&ZiBx9|Gxv;D^mZypHjuXg16Zid98+cJN-cMwX5@1eR?fIFuBtD zRc%hy#*37Zou(mZ39i0*hqpzkMx-w;QtUbW0~6Z+k{@nM?bJ-4l(sDv>Q~i{|-LK|C|u0fKZ~(H73kXdRVW zj^#;JC9*ZuEBrsivm9l!@&x-s1CC#ny3OBE=A{7aBX{?0miYzd&XI-c&VdPJvdoy} z@t5-(^77$daukdl&K;4%NK6Q5uLf*eXSh;H#g9F?xy-2_O>v`-fjGon#)w#bi}7bk z_kNSEh&pA7T=~rvWUCg6Ab1wVr!OW~iDj&j9Qla(fcfBwg1{;4-5l-s<ZQ(CWuQS)*S#iy0Olg z%aU+!ZE0y~q0$j<#K9HM;OFcYRE==+uASVL2*>X=w2}0T@4nt#F#Y?jg}FTBmAAf< zpk|u+IHh5DsdW?zVmV#NAsMulLDc7E6MULB*{@kZez6v65ZS`9&Ner{t$X!$SIIY% z`ohL}c=4co9@Ai`Iyf%zXo1M@=m6xL))PK+U^Pm4cX&P_dFtiO2D`u)Hy z^gnowxY%J1-LD06Sr^fp8;Tc^iZTGC6d1PXcfgg4eTIsbgQ3^f~URjaYHR!n%r?;aa2{A zeu8?OHZRSVy}4KQ{NX1~so_BhQTZTN#TtZ2WRBmL%=!YSoLo;r2H3wp8Om5pH&QT} zgMgkhT-1)i^yL01wWO~hybkg#KB=Qf;)VoQ19S|8XCd!!kG!!H_!vB-rCfqVgmDQ) zADoq@3a9j!tqd|q1#f$(mj1E3n$xb_$V-~s6LR1*zQ+Ka(II4>p=~IcE1u*l-DdC4=WODXP%ME+OCSH^za^Q9@f+Hc40V!$n&QU0 zaAFY;Uzb~=a4C2X`4;okrtQbw>>UmUjfT-C+&ob3#Sw#zE_aISvclld8&ajO z7^JIinMHSmz1K(XBqTTd7me6Prfr#NPAn6>W{+8ce+HO1zym&qGZ_4nQ)uQ5J=plF zypS@{|Kc~m6I5Jr6?65+X-~DX-(|^s$C@)-XLyZF7f9Xp zg82T`9%|9r5VWKR9tQYEQLoNX#Xb7)C#Io-y)&`dop%}p6cv4|#%N1|&Fc|kT{w_0 ziOGKg7_H?O83|Z0mHWPb^1 zR-xk&Bhz6+#1z8(kcZ*+rDM-lm?+9RSNa-iM~PnUZdNS_0#(%K2d}vkGQ}88`TpGvu%geFyN)iV$L-f#oKHg={!r1-k%0Vgr561fpVg*Wj%MJ7g$M=r#>Y#4hTUy%vtYRoeQptN;v}@^ls*@DJ8-{q5@BwjjvE+nr%4l`24U@Np zyZ@%C?f6e`Ial=%GEaGTm|A!IDUzRJ&+&8Iyl>XY<%a~zCn8SY^RLZ)Z{_$x!bkvM z!qdKt+W?%v6o5!QZo)i&Bt94*t?OXy>YleET%YB`|p z4wbqBYo zZB+-v-F&GJnKw=L-5xkU4po{JePs}{zWo$fDW3_tt*^v!Lmf%aF7zc=z_veg;8#MM9{W@Ujy7xH`Miw~gn@37U=&JX2!HLg{5*eH{&j zg{7iu?LP&I;J+Rq44MZ9Il;b!f0};6rIZJ}V%^K#FhR!BE}F-2NxxdXYrJ3FJ2-yG z)5@$zxyL=5d~kn!nK$-Ri>l=6->4YdeW;auXCmOx(4&KDpGU`g?{~ykPBE0FT9yT4 zmDWOZp7q*)}Xy5;pkm{1LWg5rxiL|*22 z69OTVI#9K|n0^dQ+60)ND70>~s5g!?Sw}*l(dx!P1pI6e4yHKbJ{rm$SA#~_#XN$MrltK^}gl~ z@D8lI*$pLO1Y|qBK*^=OBF_dHiqJRg7dgYdTlKN&-DU{rX$&+Xz}>aw)yCv%+J54( z-)6gp$*!}|S8bX!luzkfp{#q8bx^K#i9%VdVATWH`UZl|p(h4hrI3y>waRG6Aab5L zloYMR4EiuR3P}oSwG3&lN4QoSzdu^1{hUrE(V0eCL~8Mo4GJGWx(?X6pskB}YHrDZ zcu3#BLBDh(ceGJq_)Fz^08ff`--jS_r3k;6b~J=pm-s5Z<=#v+2MAWCN%#PJZs|+E z>eL3kfBhJC?XQ(}=cIa%#1dp}rvYK#ZMtn)w4dkkA%SWntMvzK;l1eTnoC-*!VtKG`5*WplF+_ojho79eE4%eEC& zpYd|(3MVA7hp?-6OOR(IpO>AX^V`ZJUipCH!hc?)XlOW(4^(^RN6$2e`_ussD{Mf9 zvtN|;Tgt8dgW=1z=fZocUx3Pt>(*r1E78qox5+m;{SPT$2#oMQPISPMgy=MBj9+ta zXPZ%_zn}F|Q+|?gjbm=afREmeW{5ZVS;8)H{HVV>1Fw`wwV-Yl8kXiieGiqnqWcDe z7iXwn5;mB;Z{!d(VwEgsa_>sZ9cqs+3NM8#T75Z8aEB%d5Zkd|2-0M9dX7~YcwZ+t zm%q0QFC~-Us`inya)q4$WnZT=gSD6caAgfdY8F_2CFz#8M9u}6;7N|?9QeI~MPmgl zNoJJwlSv!+DqSe1e@X#&+*trbiGK^Moe{nNG>luFnhpiYhy0WqT`i@;%bQFsTI$8H zT8CgGYqVE^E)A4dg~V_kf-@LFUBj!3MaRk8m}zo<6~D->KMf%9{%Tvrlxmi0ze`*M zOr*G}I>SL28h}0cKYjYdrIOgIK0mVVVq|H>v(!IL%YM5#@nd?wH= zkZFi+J33iH?rZAPOdfgTTM!vxw*KUkHhK?j%gYS{SE`S5 z7=-zAA)4^*O^bz~T1KJ}6hq^>NujUr-?{LY>-PT68ysabrf~(L-@S?HJSm`w%hA|J z97xnxHA)G+wyKHVdTzvabcNvU?d_#KsTUH z?hkHqi1eFn2vujjsi$lif&DARAB{MR^W)T#5Q zo)mVV#pAYb9|5OJx}$9GfhrRc!=HgSK3U-#t<;?ue+BQorIA3=?4f6G4qIPK>j4!{ z2Dz-1OTlep9h^SxXnDZuB2+gz-Hmi;>&n>WHlQeMYLBZTiWyQ@5t|>d90pw}LXtkX zf-^kQd+^!*)1Wn(?0B&zxKmd9kQOCa;bAkPJxfoJ%GIQsdHoJ{Hc8+h_xsvq{OmFp zgjTc<(A(QBFrZZfMYz?@|4TLsDX_evkJul3gy3|O}7 zdF%I=FkoTzNTflDW^NC1ro1aqRML@DL2%}<2{~+UHstMT>;#Qkx;X{SZ7=8 zcXcVG?>_dRrrl_O&o0xtql-^1HUbr{U^1QhFn3YUC!)a%2WXkP1zIAs@&NS_O5#H( zx>Rg_0df(BS8M`KITD-uIk{()qs++-XsdgX#e5@;6oo<~JpM+jz08kY4#$WK2RE52 zf+fxgBfnW@>qQu`!;u}N=fjc59N!yA>#2=5VV>zajyXh4?old~*yL8;AOZ4gwk>7m zD84OB?aI=u(w=v2eBDCb{JhRmcZ62rbvf+89Q(Jm(m8X*o&i!$AOS1@fpuwT=zc&n zAn3JsksuRLXFcPdh!VDC*r|Wb6|BCEG=4|ehoQg22@h>2zUKRr88D*;5+*_esm-|OK9QBnlp8gz3?O3 zYTaOs2uXJQM`H0m>N7lzLyW0_oR*Emweh!z>p{b0Ysnx0z3-Z~Fh+G-jj!Z0)-b;p4{WhQpj?vuKS(Rro z@i+LqCIinY+xLwu%x(5i6+K9{{_TyyNNtlR78-?m^3B>#BM-;-Zm9brheF^~n4^1} zLUcm#vgX*Sn;M`hLN3x8J@5{gh`pX@(z?s}0nrsLND<$CmLf9#fM^-DRkhdfl!Jtw z5GDU*_u>f?ws@wIO#nJmo16c;WYl4L5?oBlHV$U@ryKJDvNO~BT%LhZ!-$Be1Vhr) z$;s)X|FvQoI_#t-d!-++yR;>j=lmuQm&B;%ta%gH0k^;y??r zQo4td$b-ooCJfK~j>X#3;DaJ?6r!uOw6`5a{f z(H`j~XK*GF;3>y!Xpq5KUAUA|r7E*`P8zQC%%OFasoXfePZVoawV&nCCmlnlmJ__JPTI9XPnh3CU8#t{N8ESrVTyD|gE zczPNKg--&QQV{X2LioZi=<%e=cv0BT#b98byB~`(Sh^AwJbpxE|MKnvYgN^&4LxgW z`9!!b21-*#ONH6@mkkI)r3g*QZC6Bq_D;3<0xNx2dq4z;W6~RQ53bUuN?fY+Mpxf& z_zTs$QQ{yM<-J>cuZo@xN4!b{t%1AG2KtAQwToVkbGgyMhj%Z%=8+n4?s}kjy@AmZ zeI%PgN&z@+%vN_whoQP((gx2UbYW9;?*u0+<|PSy8|doXzd)wn4#>*3i9aw zXHnw`;PU&vVxTn>Ge>0(n7l4Mc5VF6S`5(uu%8t&D+;tfLdR{W18q4ex|P3Sh&Ps<A#jAWdbNepkPUw58Xyklyv@cYA$ z_P=4}Ff^gVo|NaHtt6a3*?fQG8c9tCG}jNjhpYkhc^qI-v9Ce-!kZW8;CTiv>{-Ne>!&N{D)z|a1hJD2hy4$i~@X;K=I;ieyF z!JFEZ(MJnBb`56{D+WCr82_`E=g$N*p&stO6!fk|vZtyMQ`m{6H&)n^1gO-E#&OXw@&yUtmyd5#kQhseyl;5Z#s36N ztf%__+2=gkVoV0UwcCOc#qcPx-~xxTzq)(i&6Y%tddhA8zT4TXUwueqYqd9ZRGL=^t4LjHj=5We{r>;H zgpNVE|d2gZR?3&i%DU7m8VdzzL<9|WzEwFY|_q!Ic8D1`4AxJ zOf&&9Axhouyx(P3HMr_lQ~o4tqNe1z+UIcd{n| z^7c;H>Fpf<=~syO`j`=s@48;{jUs-k(g#k3=Hs5yN8U~;6GF#NtlKz(>yy)8qg2*- z^ql`ZD-Eaqv@agq{luT!_|}t{vAtv?d|#ITxkBa%Es_W=o0fNOp;W7>bEb9eo48u| zuc^gUoA`b?gMsIObLE1eX)Ws7keUmLp1Hz1+&s0F88pjUT}HfiNArcLIb zM=^BA(Ao|au|l_>dg_KW+(jj%rWk$BjDY94|M_P;0;9?SUO!crYK~tz{ZubFU?#06l%}w;44p{~@u7ktm15?t9BIu zox1e*&Qj_$6$KxxcVG7R<;CW%ZA1bjo>#|cOvt0Lek#^7-Q)j8flAHuwCwK?D0Vz5 za7I>1wYYHSgb`)l2`V`^fw~9=eGKrhUf6WjoYGEJfAPPQmxpuDEVSB>ir+LZ$z0+`i{R&*zEX%#N-%Ud@W+;^8w}^OsAnnEw2Yccodhn0Ve+p{>HZ(lOo}BcEbR7-J3IVmu1d($)R9?lt#HfjR@ci>G_qXpPQyEp_d543=T)Bwcq7 zR>2f<-o#l3Z&LPFY)eI_iH={VpS-M_MmEqVdYhQgabMB^b?>JBDQZsRP3pIOU5c0W z>0jEh0k*_hz-jMP$vl6jWdxmGU+%l0HNDx7A|Sp#Wm}NOFAuo=u~|fK5&UCuu;PF7 zSDZagn>^gseq>(rjSK&~kM(;PPq?eJM56I+E8d!mfcNr+!0S(xe#f)e_Jiy4q{w&c z&%>+#Q4bCq>wiZV^=v-pe<^o1MjqR4`?$&7_3oypqP8j6W%cdqm;D&q zlosMX{OGF*NZ(1Ts=g(MOA10WiT{Ztgfw~7onph{Olx#>M^-RwC_r*!(JS`H=vF39 zr{#r?AghzjL67O7aTwvTY3K4L8GE}=n13aSlS|uaVL1wCZtnMI{=e!Y6tuGcXmY3c zMn*|xivNAL*oJ$!^ftSnV9b;Fv~k*C@>GP0qI+zGY2zQ2G%dF6;vxg;+O6e#*C+XN z@#?wDo2>X3Klm?Yb0o%{?KVqiPZi=PW1GBj8SvZkb_9wD4&lLe&&Kj)E2aC|EuCsO zh|t7-t>HdApq69pXnSzvbh5-5&*DT>--NP}xM`q0>$H3;Z9m77ci^kz)YIr_zXx)* z@~2O>!R_$P>u>7xBrghl0iBtx;o$?z@0dqEWXE!;yE#fK|8%gof~)td1hM@>hrPNS zS$o|nmoEvI|ApaBb$uju8F9qua1ZEUeR}5naA&~lJ66}-gRbEIvQST+Usm+>w4(31 zX1-lW>+CNb?n`E!V~Rdz{qTNtTW(vrcs@o&g!_(V=4sYbboCKlpKveF=&^7&+k$=p zN^9a&Vf7n{zi$toNna8fEyEv(eiXlI#oTC*b{fEQcn09-wN44%ARn>Uo*wO4%7kG8 zxGR-4l;VHyinwl+KA_SL^xv$pyr$rC7Txls< z=>2^%wV<#>rTk2Cjx0xTD7X}D>f;V;Z7(~o0k^6FaR8Rx`!@0^(RVXXc|)K>pHDJ> zdaZpNPsk7mNu`p?95|T9UUF=H5-{}h%9@&wdcQ4ND$2h!sMy9JyT$%gDWEzziU;d> z>FnXj`?1Q@y32JeWy45EW~VFn(QgX_Kl2A~^CLm`)n-uMx34#^BE@b-EtQg)bD-Ne zxlMAF-)-#@C4=iUOeZq^l(B6{{;muSuQkh#pQ!n zBdYTX`c63CXiQa1mL16S{D)?-3z2em<}UYi@x6YVDWcSPdRxvk+x7n&D4fJX8z8jk z*a4vpG(>hnRtC*u$Mn)204l;L>`& zNSCU@!(a7;QzBZ*I^J8&=qU3y{4!C+KwE$4aIAZSrUe^2eD%{sIQ^q3VmY}N8=dr^ z&yk797EO+|aK_OUD%x4r+m%%sz2+u`HKSIcA-+p?r<%5YcTG(f)zCsQ(Sz4Q*VArZ zn?DqMEN`A`7TVzTJcBjoE33bBQ6y}%$D5Z}_rMumO;c-I>!gofdbM$RGMYA`cDMMa z1dVrz_i-f=U!cE3a|&+nqx_g~rm!hC;h|>VbIxGph{wH_(BQUPCV2EZk;=QB)|Jg_ zlyK(&=fJ6R+1cZ|;U#sfx{|)z;VpXa==rGQB?9)=-K|Q>`iFWe6S!V&(67~x2rmbI zyF2M&EpI(8TNv4*&0jOxUW8uS%~6G)3>>>%pQiq^s8(?k)7#lPAUbJN^Y*g(y*MN0 zDHv-v4@kyBpcVU1uig!B{jvJDi8rMRM(!gq3;?F5^qY?-m}ftk3D|t7qic zsjw-psIy3ZBHgIWvu@cbePZzJG#Bd4g|3aQZu#1;?bZrE%u#Cgy4h+SedIuDydgd@ z#haSaCZ2gSsfR{RSurxrZHweGd*a8ju!#kw_!~>KeI>F^IE`yR^k7p!bE;i<@|(cs zv#UBFYhczhQotRwCNU-%BsPKM(e>4K_pP-Nlvlqr*?#0FRvZye+0S6vLt*7jVrRU~ z5ooVOaEE^12>bSvFf#uEDegWyZVi7W`|&*s*1*#J64xh?3W`HCB?7{zPX`0Rgb$AP z)rzrN;Id-MEEzi(xbsgA7>wC(`d&><(b~A3Lj}TAKG=xXVtc$e8B<|S&ko4gAjLhW zEqb@&nrP|%Y=Q^Qb4r0?{M+@_?MQ}>x>(Ojk86J7;SxbS%jxCtS(ieOx8 z7++6#?n{X#LH~(&t(-dt?nd51Cu{iFouv)$CTI?-ew|n5o+$F=PWlrtJv?3uISw| z86WdSwuLm$2K(baFgvG!3eK_bNr=wkeF)5{Imnb5whHX98&JX(0;LoJVewmJcLb|6 zj^?T1wmL49ADDWWTXOg3%xAI;N43OGukfs=0-k4rhw>hh$&FksjqkB=#BY+{Xi%qK z$}De_Lg2*?j2;BCv_l#|HCk077NysiT3*`dv_aH~%~z12Rfuj>$J9sf9MP_f_kN6S zfF?NE0Q#yPb(Bf}CC(51q&S@T6f@BMCpRio{gRlwwKHiPRq!@n+YZwH{U6#aMk&Dqj|x(O#b9-FzYSS z)bS%DmV>=b{yVXhfUU(qft2VNf9^T|?_$KwnVsGp_TP$j?6KlmXLf?T)~;dP%O%!V zVt$;$Gn55a`lCpO2i&Uj;%#5-bu>I5;;{!cTm(xm#JE-qK~%(`o65CB4RF2)T3w}O zrxNt>C4q-oiHniF-R^w93dgapGku^<3aRnl*f>|c6vTzwK{Z~Lix6@B4DvzNhxKM2 z!uz5DP_l*f_%42=cyC^GF`Q+Am>$_C8%~TM+2wA5l)>~-iL;wt z@)zA?prcspe3lQPO#}|4i8g$PsZze<2+rH2Z2VmVc`Bb5M*Au2sCTbhIr4*OK1s*M%xW!5uikId7v z-ZhL2+hcKM*uzH=fuKob^!F_3nXT$D&(UYLt8TUu*RbG&50Os_YA?io*v+|dDr_sF)c zd-z1{_Fh@E3d&}e966-GU&YGxLfd}js({{v`{#5>X0Uaxxi?33o_U-d3>$_sKzLm_ z7OPF_UCvSNOUYgmS1aiw^nu?aGvREZgjbhX zXpMjF%-(%@OcC!GnD6vKT`K=2MnPNv+<(-9GP3R?*{m?;S(7-S6QT`Cv*(>r@v%>*w|FWux^HQ&U?e%!_y_meH&(UkgS31ykhv=fH%}8u4uxy9fgtRiuu@kBz#dZa z+czv-DJo-ldJ}7S8(+Azk@#o=NlvUgcv$TE#4q6F(;(AZl%h%l4D5k#7c1)9$W5=7 zAyiA&&|ihh|6fsW;t16n{{0`3LALC&XDwwbJ7dkh#wSab6os;7YiwgzvPMysu@yz8 zPoU8*3S0|fZD8x;^geEA z{7!-wzC!GHWyh1`S^Imx>_f$^KHXb73(qf1z7dsb{E;;_M<3UFST%g}cF!fEqrjIx z??WCSev`g>f=?`TFH zrFjk=R%UMAkpg#bnvW2Q!)K20+n=PRe$-V1bF`<)mI**@LtFBup1EB7_#8>R^= znid#1hvu|0@1N&=S+dLE6_cqWIKW%=YqT;qj?(n#Ph7YVbn0RN)%MDVQL&=iyC+^u z!`844!rQW2l}l}*9zE5Jjcs$U*h7FFY4t1fdcnZFK>TXrgo>()EbT@Rc+{sj=hwjhi zla|Bx&r)twyLnx8Zf2lghA9|L@!i`MW!U1^rT5epnEs*M&@9N`M^}HG&%P$+-pG0( z)wA;8*4d)K*R(*9-hb6Ht3#jNQmr;2T~=LN-?|a5NyR7iT*Ad6=dzwX0Qc=~TdLS3wx$r$EpZLGjFwq2J3rAOP`1N~D-e>66FbTs0}R?a1>FT+OF!lO>GmJV8lL7{(Cg-+lCH1VGYN-RMRxd*Hc(WgKt*m`wXf{CX8zY!L#zaezLYr zj246FjZxgU|82bUo>Tw$3LRQ8LVXEV8hTtAACc)^zHjp-Q&G+8STkr2=Hc<0@Uvw( z`7d}ORr?Gr*-0gajTpC{I4Jm&T`w){z?WHp6}|riH14GpCc)0Hl5v;dd;&!K(OUwy z2*kq1FN!>oMigf3u8zWG5mjV^IBjv7*}b8hQZcOuMZ(#JbBGZy|0UDWnvc8L)3 zSm={u2%zu1NZQam{L)t*P~gCD!0hf9%BJmAJc^VYP5*3Nt(q*{y>^r(&OZO3rgq6N z{ri2XA&_BmCo@iNh~#W+K6;cJbXgrZ31@i$(DrAZXax5@l6v|lM` zFQY3h83?tWun*^rQM%FGh?}Z1=wueu;6(mJjMV z3L$AAl*CN-bdwq*kJ!aeb5Il#i(VK0sy#cb=1nT-Npsm3AJyXQ02=<3p*Gb4=Wl#!C zDT`)W^T5A4(o>Z$4}vbCzMRy4a&g5yiyKTFn4;vm))wxx3Dcm1;rIR?rnS>1zn=}N zO*tLK$L>7dzuWXg}O_kTwf^44N%4Qg}Nsw9;KZ+I8F@8;SZZCKL)Y3c88 z>&@+Wm2LDKr%`t(r`G&kUG39HJI<ljO@rtkM%(;9;!ck0b>tjG)^DZQriDb$E~LE3+9r~?5X;g*5TK-y zGxyPsSGPUR2I|IXZyZln1&wX50ghmI=hZ^Hk8SLbf& zl>zjPsL}w~(*c^;u)Wt=S{0Ie6bG*h7+%kL8r@Ght2!~QafuopgGA4B(4q z2$6F**32_2lGP{#Pv7+lv#{G4Qn+8C{vlj>y-vy3=e{ z_xY5WEcp@=SfJwJKuK*!*K;&y@g4in@UmNP`e|anx<5|ot$087?FyO|xZ^gx!WNv6 z@iM$%?STuP?cKQ+!!T)DFULmO&QpJc>5_l{z`k6TJ>Rs+(_>rDKHCk(M>={FAE?W$l4;cj331H zPTzz1nsPSi6V|?aekgd|Ug<0L@IzhjWi?~j?4;d=3h{hvXKgbqAA$oUEY}B7F5oo| zXIK_YEoP)(*Y767@;X}I&n9nXu~qSc8_3gfY*xatw(57IiiUe>Z<~$qp8&k=K~dWS zD@I#&Aa%TQR9`SIYGP9}cP7J{AF?kCqLkuSHyO%TY@Jw-%>+lfE(3b(-eWw^z^l>a zSz|t=wT`6$vsAGS=)OTRha_%dmh!Y5=(Pt`&Kg`m)b?PvvXdBfF$@Y3`AW4E{*8zd z#(Scf8`mB~VzIdkiX}~;HkohzEO+%W@JnYW<=&XRM~;kzAvl^x*LmGPFJ=~5UMY=R zUIQ;W8TcR(gPWFd_e)d*6O$68QV$1VO+>5(ddtV)(nV8{Lq$!`z-M!CBI}Snr7!wL zakKngC$Uz_c&ZIc-`cs8pzMj6l<*LX1rBmLp4;GV$WQX`D6=ekLEz~gqAQU7;4^qu zR%OubS5gYurVOQo0I`kob~x!1&7XZq)Vz@m+d6k%)Ii1J2~h~(K0xaV6$FlZt{?4p zhI;=#_^)HZKSJsWmh(K1&PwyAC_|y&S1laY3Chs7Vm88FNTZN7{Xeb0j6wWmJ7D90 zVD#_d`}$gua2f2UII~st_x$;sue!H6=)}qmmjKan)+McFyo*KeVs7~CdZ4}#uyW~- z-QBahaG9z~CPk=sua}^_lax0&t1bK}*R;QzE*6n;^;+6cfU!_I4Nf`Aw*r&+Lqr;m z;!W|Lw}f3(XsW&Pyf<3e;mY-Cap1wL?;DA|ufgspy-dL)v=Ey>w4H!Fe!s-jYsW>^ zQo!wq@2m{kNC<3uSG2=7v{bh-U$$n?2?c)jCRPLoK(#Vj8;yKA4BS?t&sg<3iG_-N%^!&fh2MR@wxpjqT^$NlA-S20#6NDvDV;r061EYHtp={CDg;W#jteXT}QFLt-0P zjIjM$D&6zOm6ZFYM1x}jK@Ntxa5@D8qT6*ZIWUUE&@dT+J>dwrAgQgOkTz-{vBc`Z z-4o+Cc_|gxj?|4#Qsznf_@H}EqxZEcH1eBTVD+I6W4D{Z=_PlkGnB@lk(~Kvi9(3i zAhpYAebb+q;y3ai?g>V_=cUsZXH9;0j2&K_$0q!M=;`qf8x( zF%cgMuLuvF*kH-$SLy)M_vPcGor)5J6Rz4wjizsm2-Ot+7wduDg244Gt;~!kmX^!V z@&kt2w+=Iw5fAu_BKlhkUA29Tf9@2O9vyulgzIQdTGrGl(c~*XoCz_jga?B2acPCH zk8eMZTMmml5QoFT3JF%F=fz2Jfwi8wQ`q7kE z{w)JA%*rT_=}D13+cLIlBbnKig~5X)SQY5UL=ZC7+0)l(ezFuvUZzsZIc<=mSD(9A zr`RCkks%NHzVW}Njm3P8HhjOmy4QR+@^}+`au5|Msx4{$a6=QfjywwFU#C4hXNt!8 z&Ofq|#gwlo@~T3|-K14{f--cdj&4_Y2KQ!+(X6qoA7wk-lo?RCza4j?NKr2YuSGdq z+6XZwPcgFsxn0$P{)?<%qm3m~RL2Bc9wgmNnj_^h>b3UuumG(MC`m<_gNK2|%6RL^ zo_g?Afn}LiB?uGY(si8%xm+D1y?e0FQHSuv(xAWnSx*j^>@L!5eSCC-Eq(1b-7TVh zAW-ve>hj*DocJczo!Z01WjW9h4}D8lh|qxmxEnSPCbG?r%NgRcB*8^m=UUHr^1bM_ zqOMGzG=@agTRYa|=D@LIj#8lF&psIl0sBnfw|b`Bc8h9-^R=jB3r?@`Ie&B0Mrw4P zut>4U9_=KG{@Iw%4-Y!LJZnUewQ`#dvzGGag&~aUfQr#C$?$DaPPvW4 z;^+HCqx+Z04rj{bvg)fnIpyRM>`1w(x`-E&{YJ(UB2brSU_6ca$ad`@wffWgXE`Re z;{IPy42Bq5mteOU*7om01(`DLF(0~Oaqg3{G;H^2ikDKW*cfi#b-DU4F1CY@q&&Rnt?;JJq>H9AGa}1AGw{;Mo6NItQF}%4*kem?#hEuQVdN8GS=p| ziylf$)z&dLylc~B_N&v;(G$TzEK}?Y5M%?F#|_FJap|$wG0`V0Kw^FRit}5$Iuq>7nBLYF7A##H!EQV<97B2ekZ3H!S7WV8b?PJ54!Nof61D{Y zKhcUV*(@xs@)LUuC@rM5O192`YB+fcAg2C!u{J*!*o}`k)+4>OhN+I9Ng3Ihw|wG<9d^_#%C!NC_4y^e0fr5|Bx+kbkDLpruGv zC&QR?C6U%d-T7qL48q8**zs2Ke++5%+jf(0q8=q`guZmwQ4RR(n^yNm zZ($FA2P7o$2r~VqdR-nt)w-xo4)#i41jlL%5lleLgyH9V8k5;2l>c4N7SxtCqW|36 zTf0jUjG;i)!9i;HbK5C7rb1N4yh)saN9ee`y-}Rk$m-tbjA5zySiXJ9Ygm+`H?8cH ziAe5^{&ePhqQ_&t9kTm&&_|uh@N>oGevvdHDvNJkJU|bFcAV#Id3x2OHt#$#&u(>9 z8phvD@<+Mliab61kuj_yduh;&HCDsZ&$jwuHqw2iL{{I21(zIAux|`dY~I!1bRc+u zJJKSbHui#|GR|ySCvVh!Fkz#j1kD%>)J!64{K6-lbql@1Q^n-(inhi){@gFxAs+a# zfK3e9Ovri5sLNte8vcAdk-O+vzhcRLof7ESJPucr4r!LRW*?!&@yWt%{M;^xULzkaJyYH)q5{N=J=Q(&KU!@!e`gFV=?X8}*M}q2Nku~){4)`yL>oMYm z9JDp(OW?VzvGPZ~x;eZo{v1ZEQi(y~HpX+p9c=7=x$8Q4N&XtM0OLo5z_*SJ@^R;v zcY;+St1|j}nxhjW>(LVdAxDwMDqNTh}*h-zhT7M zC^P;Brsu0pT@SMkjsI;%O2app$8xZ{fD3`aKoSDDYG#v0y+S(!c_Un)jrMZl8_Q%5!o`$WWyOUh$@>TIlJ;PmNKrwM(j ztg#uU#g_u;J~Bxn@9riNYj1Ve-k`X2*(SdD!l>Jh0gS@)x(PIO8E!&-l^F({M%1H| z(+F}MXfg<8}R)UVozb5Qb zPqdr^@timOw!yqTkDDPY5FYQM(SO)$mk!==ODBxc++O0CqM$@~irZlpqY7cY;m+$H zj(sW8lgQiboe4FXZP9Hni%kyWlPS)HX7~ooc$o)wevrc#u68k zCIo%4ygvISQ^Q@;>9BrT3)B1dj_EvqZ)=OWet7xmVfI?XgBv$&!loYsYDq%zKBWsX zx1AY%hX0XAQQ0O4nQBPFJr$giUyV8?j4rsNPNb8%+8huSQ|5THau|m)L!K3q&Ug0* ziwc5DvWgdyj$>a$i(35t35sU9LGi4B71nNIVfjQvHzTP79v4IXwzlh?hpIf-VEfI2 zsFf8Q?y_R&9(qX|J0mur{K=Y$=z!~uN)=^r`ls$*;aS^VvccF2u?yNXi+?0pyoG2K z8VUh?X(YKIr$Ux<;U&L=?7ckw&R}HtbTbH*z2-STlaxlG|0^|>hXM9)X0=lu!@0po zwd&^J;J|;}7cLOlSI<&TmJdQiMdWCxd1?S@mJuOe5$2HSvF?g>O&L80#`9f<5{;}N zGq&|C)*!~1d?iW}1NnvnfCfSGLYJC?qEO)M@RR)ej9Uf^r-_K>z%wPrl~fJ+*)l(PuC))tL?)6Bt%^Eg zfy97fhx5mtQpSMCrFe~NS6E)ophPNk99V(%+y0Oyj|(X;L>c%YwEDl8Vj0~lX}#|i zawNn;em%gDdfmr)=rg2oKxe02-D^(94NL0eWeV{H2F_{OqNGOS&A^2H~)y+!n@b+g1PMKs~%eWosX0$D;V>bU6EroU)e1uYryWDFfSF z5X8~JNF~*m8-BrLU8f--Re;g%cyNZ4Nxns>;;-Cr(@3T~(4i`lGafTV7hM}W7$*d8 zp@%q2b@8hM>!8fHxM) zn73C?ySuUIm=WTdg67mv&W1OGJ^mpf4dsOcss^`k^D+lw=jp^~Fo;Pk&>2cjp!8TT zhq(z9pFJn;0qHbae=_(AT3#;n4fsk|G^ta~4Mjb|ibdUuQn~(|GuEgi}S=cNoAiR=803KO4^mPy*||kavqHNPqqY@GYR};hhK`#Pzdlxdqf^q zn~l=BK27jg0pkfA5Y)R3qFA`L@R_}>!ia1nAj8EJaJbn|XhNZCZUkO!4-3<}qMG@) zUcJRcFNV_5AeTQnIV*3O1eMHW%*TIi&xs8EPhuH}jZ=cG<<;1ixK3q5XJV^M!vhVd zegD7_7Z@@Uh$14j{E0zt-F0f^;j8GF^`MV0(xqHKr##VO`?kwFeGqrKPHx9s|8)=(WhR3GA;huo6)5KO6#ed_ zqiCoxdtyxjrA7`c8uY{%IZDS`yDx>@v2+%E~Shf-QWnZ2-_taJ_LdT(SMQCeZ7t7o&2j}Yfin5aRKZyuzzw%r3 z5eElI-K{d#Ik#GHq}{*TkfRqWQ3aMUqMGj8AO{5t{foJL-TvtLRI~q-26s7AoP%Y{ z!m#WB?hMR^uuj9c5#Q7b7RgE;N(F#@rL#CFb#`mzzozZ!JJ5m!E0WejB~088%E+S_< z9n+8647gh2l6N$YuH*4?KKKQpoC^jF$vl8aNab4DCp|{QXu06}M?hI!C-ug0xkm;I zn^Jzl?vH>KJd%`a`uZP)zV{N`vgpX~H)3skpwqC_AN)|r5jRZKJ1`*7iv@9$p5Pz| zgvK`I1OsX#E-sBdBFXL+PgpcSKDp`$GKDs%#=!N7IO66tKI3_wiPw9V2*Uv5#FrOA zUBkS`8osCX9u>i%uZx{OCYSC*p?)_@ z&<_HKgT0nIKfS)zds$$-!4JVVbeq_iK4;2WBeRHaz?D7fNNCA4i<;Np634T$|&g0JxC*O>Tz903%Tsj>eiv+Z;csbr0c>=h`MaImyYl{?S*WJR)Vz+)<)gZC8qR{zm_5}X6YeTXu}qYq3I;hjZkA0{3cT`tuc*$A1`2FkLj1lNi3EQ8LvU= z!-!+8gFz9$9)Ei;gZ?NQxj5_LZ1sE-aYI92`+zuqR}lVA%X;Vbd&R^((>I1Y8^rZa6l3)gq0mw=O4FxAsGKK9@lVoljQjkjP#9{a?JkkFl$Bf8`V% z!QV8Wv-8_p)$i;S$Or@Mm_0vUeRwnQv{a2+tZ+I6&v|5KvL(Fs6C7wzg7u;h9yr~8 zUyD7-;s$W{BldJ1i}Fpfj8kqt5hNPQ0v$8+^YeOE+!5_kR&#tlQIhIgCf14j>O(0p zNqYem4J71Y;@+OLzeT+03O(@wi#niSvdA&hS#zB^M5Qrh&c`T)a}`i1GHL$@9bmaG zIm%ULfO~t7vUBO7R9#Eax$WEO83;{r zx`>4(Fw)`vTwcLnVvk6gLb1<-MkQre-ih&(Fygr!tF{Xv0DJCg$u^hHz|{~_QG)6J zoD7_xBqLK@H>rSKQvM6rtQ{YpNlhs{13qDV=*&1TCnj^}66kbF?k4ncs=9KboXXEU z10{|TRd91%fqLm!7JS(CQ3lU&KH-07xSY3K?5A3W!C(sh#(LD%WjFdGS={u3=qV|t z9A8+s8K^~kiR_!==zXfL8vkcPA?<6Tn-^ARh6uWhgLd3;Sh%FOOL^dF%TcJIlS}5f zR7gjt1vpB9fFh>zLlu|Gk!#}*(I2NyJv1Ucpp{?6xO8E_WNL3{~?D4gpwMRu9p_{7Up`wI?lC&VzxN#8;k#OUa+WbZ{Aml#U+0%O0{#E$I@ip_MMBn#!Cg zFt(0pBAD-Fyh{VM1f#@dL)T+kKClx$VCG#A_OjrFGUQHPsLysX#(C+=c(BiEK$9V8gi zBs01r9j{j9tdYhSUm+u|SDEwlP|g0qP^AWLSFG_y;Sz1ATs*8FMRobL1PeJQ`=m0k z{!EUh4&;T8*S6OZ*P1uT+TASCT9eiw88yB~8Scu@Hs}MFMkuk8<82_i+WJAFRQW$8 z(qX{PkshOFW@5(YVm5HLd1!_{dtzH0r3QQ@EKz)a%~Wn+@rPh@f48V$D;O?4;+;5K zw3J}$r<<}9elBccwb2!~HPT0S=bloNpfNSCTXc8L@~VDtTkMBNT>5m>fm6CH&P>RFk9|M(1NeBE$V znw%Ib+y7Y!-Avhk=lIEz00d3fTBFK@BPMyA*55V? zn--e8Y&uH=cn7?xJ`JkVavxo70RubR_Q3e>@>2&k8=6m}WNQU8BvT-LTySq zh?|g*a+lT%uaA`9z+2NkL)9OC)65)8%b+&-O1-QyaA!!Kt7?LkSS`v(y#))!340sL z_pBW(s_~7@&liizCxx=yOKKWd7NrvDBUXlQy6oMBxoADjpvv`+_ktiF`ke6(c$JIA zQ26O;)RB-#?S%HzVfJb4**baXsh$u1g+1VD1@F}w(BS`MB2n}q*<<1Tq1i!!GD4yiw# ztG$PZ_^k%yhT)w**m(zYLib$HooGz%_BQokF$eGtYN4bEkcBLXNOBc{F$S>77-23 zDPb=4*EqI(IuWB4yr)Kvl2*#SJ`2AWc&}fqkWa@7Hh_bt4$)l5{(_SHhc0vRy)s%A zr-~DK=Y1Qm-G90y&tOTT@iRQz_LwlLxyQK=5 zPa7pxycRJL;HVC!r(740;L#yf8XdbOK#Wo>z8vO42PD2Hzsutr<)FL z=QEs~^=SnjUmFWm9Xr+8d;jQ6(O1A`7ri?8@JvKU9CHXmyDC|U^;q~D zOb2=i9uQxciS1pQ)&BY@0KaGswru(RK{^nHe7^CyYgW~3lF%2$x%QWXs~$=gAeP#; zR12{^rUj7Qr8gyvhCYSwTsC1MLH}WHZr_Q@1^uM5PdC#kO&gj6mqR#ZP68s=VDXQu zBC+xlOHM*UZGGT zD*>P!FJ$BnV@cx*U~b`Ez)MK;bR3qWPA(Bkn!i;bJ-@*;>GvAuJ3esaUmV`+T}p*< zFG+3l8)aH#P=#NvK6+aDp=e3lMit7%453Envl%m`n@_NnLQigaU8OU%0 zZ{Y;$KC-m8QWUvj{?O~*8whFKP__XFOW>GnzoV13fG8Wjdci`&8SWiTrup;5 RuNnYd?;9(v=+aCe`R|s%8wwYIi=T%O<5j35w)!JPH8#F*`p9P;x^O6!JtLaI)k1 z3fV0nEq!$zcN75j@Ex$VIuifsciFjOAG_;ZeDdQ0VP1d1)L!W|JKKDzqMSYjMn-E6 zMM1g;A|VQofnsMA&cG3Yl5)y83Ma>8pcSe^LFT&&+qwp#tR0|=sp@d+XCrSG^A^qT zCz`gjEuI}XS37WjaCzm5<+I3RWi^ZI?>9SqQhOhqdp%_UZARV!8X$LIZLl`r3Z6RAvS(PB-L;zMc28>o-Yy;wYm}eUEv@3{*1VW9 z0HR@9%7^1+TN^f4d37#EC}eP^8z8Orlkjl~BM~FnaoF?KS3f*TAc4FtiZ-nMlVbN%3-Ww|l*r}PO5YP6>>BZcA5 z2&I#&%PxB7D&VK@`x9VAk>2^#-V4~hip{o|km&@ViW1Gi&ShVuqQytC{Plf&?u)@e=~C5p1W@6SDwQ4wSe z=1bto)gikDZsku}{2r$F5UC9d4QrfxA=cg4^ekfn?oV&NvSmawM(75kJ%0;h=q^J6<%xaVDR3PP}UJvHazk!iSgp?zU=l4t2D7x#2>eZQ3OK%w;r_1`rk(e#926Jw>xTRtK2`XnpnWv=c&G zUK0CA?$vU))TqZyf6JFiQ83(##60qi)scO?c}jO_{JdlE8!RrQ73%XPGuqjZDHaHq zlhchP^Hsc*yYOW*cvnxkYGTEe3+XU_XCCkCQ$k5supB+9^bsGz2z5<>uE8p!HlL|t zjReP8>r)e!d~>_b*u4vqCGBP^R@6>%hBrm&;j7OjTC?Kmdqa2I1b=-|EM&X%v!8#q zaS}}_+9HG^8Pe-@vo)b+^a8Yhv85yo@75?MmBcTenc-A&~$ilop^BX$qQsv;hsgzqGkG0 z$q7-}b9@YoStEvt=sn$&+43V)Y>NExw388^p*iD*C1ucs^Jx-p>=Zw|NXE?iRgD7o zIO31ebVDHSCHHW*@{JDvV5l3rBTBfW{+RuogpCYkt5XW=Ozy`2HKbxWuJQP1WbH7T zWC$M)U41#!;gC^VLS(9dpJ`L?IpHijyqtal zas9)U0*a&GX#0v|#R+y4?=iMoftv-T-V2}kXrtctsHH9(z=X+)vuRR=g+&h z3&o`7JH)9pRix1G*c~{YLcP?-LF6J!^J>1{?DP=rWVtqJq`>=*{5CDOg4i3* zqHUz;p_EcrKNoo^OO)|X=oF#MqAc}B{lmSL8BgTx`70F>CI?=@yTrMtS5CIpZw8y7 zjOY9PBBS^HF((!>=Ijte%iZX zBe`jHS5>&4mFRYF^$lf$dTgSGtlNrt)K-a!zsvY}{JI6Bn%;uMXWJti^_fw6Q%ji^ z(a(MzBwi|a*;Lt^szF6p7$3#fPUThhTE{`_oHG^7mbC1$o-1uP%w(FyTb!9K^lQUJ`m0@sG`6u{2liM-4XQgX zp(}(^EJ$eaO!qlo(Q^CFvq^oYK3^C>zD10#U+mP>hZsrGy19D)iEbeLj2{P&-$))? zbky0rk#TZk{W$)H|Eyi&>ah=nQ7&EaxweG9^?v@&y4e-z%QUZI@)hv)(=%Xm%C+Ww-D40FS$b2Yd3Kpv8el^zSzE z!^>PvzIWPF&6Y8;=SS5>m0_e@vOwd-n0>Wo=hM_%U~7V2*?o`r!mp+jsMKAPFSKKa z4jHWeq+T;$kOye+Q!{y}Fc{msv72iU&0 zQuj#yf_j}=?l6mr59K&k6GLC|@VW$(1rV1GYrU1DXT)fnQZzsHjDxb;ZOVW=l zn077`X$eoy7=L>45==QSW)RV7-OutCFCJ=YFr++xI5?YV&h%2qsU$1^)dzDzKI!{S zo}^c;dJ}HB3uTh@&hZml$%QjqUeEo8Z6Y+Ul>3YJMi~Cmy74G6%ic zAJu-}2y;@%!YA#9q!7K+V3dbq(MEKgsClJNZ$p3Re*}{xdA3#gd(?2hyV@GiTvM?@ z`7ZYI+-z^;lEY3q%w$cJ zJ)>@Acn1wdW_r8J>MjfRSi~3iF)kA(Jec_ljRIP`V+y-O}#B zJLyCwi;@dwJ2h{7CQe;y@zuTic6(oIaRudHsWV3 z_eRqkUC8+B*|>BoQSzBH;cYUWJdJ)%h_;D%vCFQ3fBbDls#tFWRRMMxf?s^Bzj@y%nH=SokFV$cq$(Dx+3iMZPhHA zY-_BAc$*wWpFSccTJ%OIWZLWD-~pnGDETAFcr(lcpHR-|tC8otf#0-?Z z%froTeP~fXMS*bWrJLo`ZZuPqfv3*b#At>l(h!ZY9b26~$OPcx?4y&18L|sqSH8P! zbaNvZ`~5^ti=xi~ic1(Z~7S=EgGt034Tc_n9Pcn_= zIWH0moVpTSR27qf<=Bf(z`c|2KqUgmka~%bWT4{ENCgclE-fL{B+0VNinVR^o@oHM zy}dIo*k%-Vt!EG2e-vT$g4+fZn^#9*A5j8BU*1Fm62Mo!VX3}795;HpTm15=?s8W% zjH{f1J;c|gS=0Gy+r8HWk5J$_)+?kT2IW>*=mG>o0xtX!_%6z&y*u&p{!B5Y&jQ)M zG5EkjCiGds%Q2VLAfTSQ2)Ws;8FY22*LYC%2nKtXMRN>(s@i1;xq42WxleR1IVYx| z=IvhN<-_4)owWu0RIj>&$6vrf9EIgL{{v=&_Q_*sX=I$CHkOmF3+S%_B-khMm5WSA zFgpl#=|X5dTr*~J#UEBo^>!~FXz{ofD?GiE9!gRJpefn%dh28olN%NYm=i@AUSBM@ zJ86ZVZ3yA^(4Z`#A;Z?>c|rR2*GanE=Aj_wkEIH>eBh5%) z9G$;xNXLUW?;wops35S^U~h<%`N16@0=x;Wh<=%*Zuy-~40)#$#^#mV4wp>yZ#{?# zb>(@OBd#aA2D*-f;+HbruwEy$L6TIHksin=oU>Zs(?;V79 ziI7J|)~5yOF@=lOk0BOUo}&s6vVG_wvnTWNW?Xu|fEsw>Ck~#TVRekK8!`(Ut=f7^ zlUdpde?0&P?+OB6B%0Erh%lAk(>zj8h5=7|h%MpUXIhl?Bl zP7t5;PYAlOWtrO1A-!=Xawx~@C*!g1f!Asql9Z|1AIi~I?3U_Y8?C107j-g}c@Gyw z46Aw-Lp(p~ME|hNLl1VdF!g)p=vJh-XEmgH(yJV*@*Z8# zfVks@&sD|FP>YiuuN3*4FgDlR=~wBQ(N$N&;zjp-c;0Z|M9?bu>RI%h2g3`yL0uGm z4G%jMeXSGjZJw-0T^lu&9y*?-w4NPlJwojnb-9E3R3@}iIsFa)^;I<8p8e-sVUP(E zDt&6OC-0Z#>6!E<*?#l4EwE!40+e&roWBnz!VERZuVEc{qBIO=YeW^13j2TXi?B3l@f~6^-n4j#*TVa_L~`45Dsz-L~05WOE#ZEfeV?T&@$mWtjXn! zQ0VW1QkG)F(YHn-y_~o$2Pu2#FK3>!Vt6^xrkE;J;JRtDYKf%*O?S^<^S8oI3fog+ z(0|@y9qG30{8eeX-%JUeBzhQ}aJN47WpSqbg8{u)1(rX{<&?m)T>(|bz7#3I7K2qAhC4C`0fiFF;RU-V!IfR1?57hbTDzPoio@dR!Df8LXT+F) zN*1MV5}|TmZCr;um&V@lC&7&5J>PNrlBiXWYNEz?Fe|&ug`FxK z^vF$yC6>4Edjh#a_r6yx%YrJ3brP+ zVF@P%o4?NJFZKUaY!k-LRigrJ*O2)){|PKN<2FEnWiv9rV@JSpjHQ}^%iSm`P&z8zWJ9R)4q@%xIXXV`&vlc-7Loyv z!jc64S5KU!#z}otOs}Tjrzbm&J%;=S>k)k&^i?`!)Fpry3?h>YZq?bS&-_Wktf-My zS=)Kg!6OU_Mk-!CEJ?Ls@QH>+`{+>(Py{w=5T;8lr7D%uf78<`2+&_zYyi~RYH1=# z0*|eGf2e}?t2{#+IgfH-1GP22{g_iZorW-9So7(h>0Y`(Oll@QjNvg~V%RDD?Z+`7 zS>tJd9%R=YmORWE_6OpbsFoH2U4&~wRGU8i=m+^eV}yETY2ST^026tUYm@LUF>rMti zm0}*egJK_6*HfN1bN7d?wKU4nPI|KWqrBut;JuWAwO2^G#~2Cz$P0xYfxANnn^!Py z?A6p@XgcgC_RDS{_HxBX{tjn^CRd00j%13ll@og|S_Blo@&j7?Cn>HAxNHSvrLrh~q zLj*BF(m!>(ES_c$)=JJz+e3ZrlS#R&Hd5z9=*Q`;l^y;zFjC}`YUB~KR^?8N_-OCe=_7vkcG2NYbNDaMRs@;gd2Zf84tA2NjVKevagr!V2nQ57 z$oT4**D?(7Itv4_&{PS|k>S7)%;i)L^ZKbjvwxZybDw9rfrZ&&Vky4NDYz=^E>Mkh zfk{izAwzxy$n)(nVaNrv4uy}@xMcGbrop*dtNXr`?%3oc8Nd<~J2vJE zr5R}UQ!5Zu4eE0N{zu3?l0Knq%P-YiPd%)$qruutYVyiXHfKs(k~R8x!G$bSgR z6rH$?QE-JMjxA15c?1tWGRNIMw4ME(&2?`=3Ww~lIHuCvmb)YLj{DH*ile=Y5+Qq% zqdsQNXd(YdeNGiZ9mhe&A3_&|a&-20D>ua%bR>bScGnvYUV8jCbm24$_(R}VDwqaW zZGSB#U4YD83EVYw9&*_(f800?q;0dGBUx>64=Tbf`AVl4^uLW*1225)_y9AS+tx7JrR7kFD8J)&c6o&ek`RGMSyrqU?#5e=gVq_)_j#e z@UG&+fzKSi(*pdeesP#S`96h|SEE?#KX)k)b02*u%Fk_V%YJ-T&ok$-5r3mGu0UrI zUTDR3{^nJQTiiP>PSy$@ZEt+h5jXJigBJhh{_%diYPGYQbKhQ$8WmH^HLvvi`8ZRV zTIZI!oBzapzZR%*K?&>IZIJaX)@)oNLTB_H_{AU8R2W5z=OES~*rQ*sS+OsH+oS6V z3@K((rfs5CZ9m;tQ2s^dxSZWUxlRUowEfmi%Muy_$ULX#iQ4x?77nRtoySpf?NhE-;&uI0HC zr{%&ln6`*`={DVs09X5ywZ^V%MoYf%8y6=l^W1w+58y?!#2CHNa zxW5I~^w`@U-LyC^!Y>mDqfwj!aFbfNAoAC93EFt;{ADo{6&WmW*3;Po8owh~&^UZ+qoNn@! z=d83Vs-yCaLJGkh+y{$L_t@2xyb5@kdo*mFdQfOnLT_(lplanJFL+{p4Q>#sBvl!G z$ws8CGa)zaN_-=M?1w%)K!5r`bxDhv5lCkWuCXv#{?LSo#}tj6H%q*pTV7x}ZRqsb zzAz&sOQq5Y0j6ICZ;b14LbZ(_Inqa#(L$8AynSan65%c*@c?=Q)ehqg*zby5GJOC7 znVYCqotu;&9Pz=rA~AY6@b;!vxI-gPW50U|PO4QPUYF5=0%O|%V^fmFkb+G4SUUgp zhV*a04^*zvr(AQm(Rovj+Hl3YkomCSjZ(^VHVbpw$?wLiZCF!dvKYrZnY%}SFBv4y z%Aa#|);8%U=rQzCfaYi!bUk%ojj%LsGO#OSHilrfyCqAmlbLV|io?Jxd;&Zw_J$eHAVaW>_ zq`W%}2&F9gOJJLJnL@^(KVetiN`I7_HWMx8Z_mSNYCmaX7)lF1cIk3i+M5HhfV1(U zzFqa?+4YD_`#s!C_!lWdxl>P%9OGi@M_Y)hJwAekQKpxhSxa2GR71>*1SbbJ=A2D{7=rint7)_tgYJPvOsRo_({yQ6h9FdD#`4>(Tb` z`md4a#T9~~k=>f zn7WiVw^9IL@Ds0o2u4!uKoq7mr1WZ8MWWPG{;#ubw!tfa0OJv{U%Bb|&S^}n8ATa= z@`tef)jj&#_V=QjxrxXC%0A*fu7T(qrPQBGQjB-@F%b(MHB3$VAn#re0-7nn#Ca;S zly%Z23bw{H!(uDNz8%1-PG2JSe_j2s8F7ggVIlkR+Z_KC+)^w1`>e>@#adq%*uf#< z^ijPd&XVZ^=faNGEp`wK&fxW|h5XlfR@U%B9^?1$0>J%vA2!>y-H>5&xwtsbT_u=nUm?y1y-z*XGcFcjXqJ={s+h@E^JA;_3+L3q}s$OwV@-7GxH@y!rUFgLg(S+ zG(D}kDu4P5;H`|$(0r->r2b4M^VrUa>c>;TN5*_j@9~~8kXAHDN{4#Qb92MNl=W{+ zadjKd2iYql>UHwu4_cCzarIiZ!S|Fu3SZix7vEl=7H#^zW$CqD{d{@xPZHg5s3st9 z^$J#DI`bcyh^}TBMa?Rx+HXntt?;+GwSJ8RIiC{O}Za~aJd~QL9bCu+MASA0 zVE76ljJx#XDg>i!B@R3oYFb^}O#q*-;>2M;D}>|k_jEdH@uqo6w(siqt$Qq(ZjI(U zyXl{`pR;MQEoLOxGv_t7_Va2sfr763z&Ub(aYufwCZ#n_o22gFJ=nr9ZpzpUzZ~sk z2S&cSBr#~2IDV#Sy!b>@Mgk;U_HRg9*vyxw_BV6Vo&>vdckhB><+jvC8_Y|Geppmn z9I{dz3%;VVnxUd@6Bk*pYW$_tO8&J0^Y+1FlDg;995s5vh;QD_S4tHu@VPfhD@$%@ zX7n`{ZS!#hM%ryMIaVIT?X~z@7y8X-_}up^t`9mN4d?xFOA=%48uIa+M%yO`nvtM$ z%3H#xj{I;wera?_lW_c!4%ztIdj5yr09WyWuVOLW47Z`Pf(`(aD^HXWMt(&t2^QAyBo<@K}QUa8GqTKi7)xb}-wFWmZs zdSRHzkRO_@$XKb?l|NQBJMk+>>$Hf>w6XFy>bAQHM9OOR;ReRNk{umIF{M0lV`
KdywS(q~u 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 GJNZA^)9HHv literal 0 HcmV?d00001 diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_4.ico b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_4.ico new file mode 100644 index 0000000000000000000000000000000000000000..bdc381b827429ca12eab1b18535123c1a83c27cd GIT binary patch literal 51902 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 literal 0 HcmV?d00001 diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_51.ico b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_51.ico new file mode 100644 index 0000000000000000000000000000000000000000..5a25434551c31274d5b78de60f382ae163b8a353 GIT binary patch literal 52614 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 Tq+rm`0QY$ZXCa-0WDNcv{$SD1 literal 0 HcmV?d00001 diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_78.ico b/src/Dionach.ShareAudit.Modules.UserInterface/Images/imageres_78.ico new file mode 100644 index 0000000000000000000000000000000000000000..437d65dbd50c830071b386a429c2c908aa3834a3 GIT binary patch literal 50871 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 literal 0 HcmV?d00001 diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Properties/AssemblyInfo.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..8706551 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/AssemblyInfo.cs @@ -0,0 +1,41 @@ +using System; +using System.Reflection; +using System.Resources; +using System.Runtime.InteropServices; +using System.Windows; + +// General Information about an assembly is controlled through the following set of attributes. +// Change these attribute values to modify the information associated with an assembly. +[assembly: AssemblyTitle("Dionach.ShareAudit.Modules.UserInterface")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Dionach.ShareAudit.Modules.UserInterface")] +[assembly: AssemblyCopyright("Copyright © 2017")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible to COM components. If you +// need to access a type in this assembly from COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// In order to begin building localizable applications, set +// CultureYouAreCodingWith in your .csproj file inside a . For +// example, if you are using US english in your source files, set the to en-US. Then +// uncomment the NeutralResourceLanguage attribute below. Update the "en-US" in the line below to +// match the UICulture setting in the project file. +[assembly: NeutralResourcesLanguage("en-GB"/*, UltimateResourceFallbackLocation.Satellite*/)] +[assembly: ThemeInfo( + ResourceDictionaryLocation.None, // where theme specific resource dictionaries are located (used if a resource is not found in the page, or application resource dictionaries) + ResourceDictionaryLocation.SourceAssembly) // where the generic resource dictionary is located (used if a resource is not found in the page, app, or any theme specific resource dictionaries) +] + +// Version information for an assembly consists of the following four values: +// +// Major Version Minor Version Build Number Revision +// +// You can specify all the values or you can default the Build and Revision Numbers by using the '*' +// as shown below: [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: CLSCompliant(false)] diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.Designer.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.Designer.cs new file mode 100644 index 0000000..d623def --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.Designer.cs @@ -0,0 +1,72 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Dionach.ShareAudit.Modules.UserInterface.Properties +{ + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources + { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() + { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager + { + get + { + if (object.ReferenceEquals(resourceMan, null)) + { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Dionach.ShareAudit.Modules.UserInterface.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture + { + get + { + return resourceCulture; + } + set + { + resourceCulture = value; + } + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.resx b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.resx new file mode 100644 index 0000000..af7dbeb --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Resources.resx @@ -0,0 +1,117 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.Designer.cs b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.Designer.cs new file mode 100644 index 0000000..8202770 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.Designer.cs @@ -0,0 +1,30 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Dionach.ShareAudit.Modules.UserInterface.Properties +{ + + + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] + internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase + { + + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + + public static Settings Default + { + get + { + return defaultInstance; + } + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.settings b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.settings new file mode 100644 index 0000000..033d7a5 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Properties/Settings.settings @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/UserInterfaceModule.cs b/src/Dionach.ShareAudit.Modules.UserInterface/UserInterfaceModule.cs new file mode 100644 index 0000000..5b6bafe --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/UserInterfaceModule.cs @@ -0,0 +1,27 @@ +using Dionach.ShareAudit.Modules.UserInterface.Views; +using Prism.Ioc; +using Prism.Modularity; +using Prism.Regions; + +namespace Dionach.ShareAudit.Modules.UserInterface +{ + public class UserInterfaceModule : IModule + { + public void OnInitialized(IContainerProvider containerProvider) + { + var regionManager = containerProvider.Resolve(); + var region = regionManager.Regions["ContentRegion"]; + + var welcomeView = containerProvider.Resolve(); + region.Add(welcomeView); + } + + public void RegisterTypes(IContainerRegistry containerRegistry) + { + containerRegistry.RegisterForNavigation(); + containerRegistry.RegisterForNavigation(); + containerRegistry.RegisterForNavigation(); + containerRegistry.RegisterForNavigation(); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/AuditViewModel.cs b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/AuditViewModel.cs new file mode 100644 index 0000000..ce86cd9 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/AuditViewModel.cs @@ -0,0 +1,231 @@ +using Dionach.ShareAudit.Model; +using Dionach.ShareAudit.Modules.Services; +using Microsoft.Win32; +using Prism.Commands; +using Prism.Mvvm; +using Prism.Regions; +using System; +using System.Diagnostics; +using System.Windows; + +namespace Dionach.ShareAudit.Modules.UserInterface.ViewModels +{ + public class AuditViewModel : BindableBase, INavigationAware + { + private readonly IFileSystemStoreService _fileSystemStoreService; + private readonly IShareAuditService _shareAuditService; + private readonly ISmbUtilitiesService _smbUtilitiesService; + private bool _isBusy; + private bool _isRunning; + private Project _project; + private string _projectPath; + private bool _runningInitialAutomaticAudit = false; + private object _selectedItem = new object(); + + public AuditViewModel( + IFileSystemStoreService fileSystemStoreService, + IShareAuditService shareAuditService, + ISmbUtilitiesService smbUtilitiesService) + { + _fileSystemStoreService = fileSystemStoreService ?? throw new ArgumentNullException(nameof(fileSystemStoreService)); + _shareAuditService = shareAuditService ?? throw new ArgumentNullException(nameof(shareAuditService)); + _smbUtilitiesService = smbUtilitiesService ?? throw new ArgumentNullException(nameof(smbUtilitiesService)); + + _shareAuditService.Started += (sender, e) => + { + IsRunning = true; + IsBusy = false; + }; + _shareAuditService.Stopped += async (sender, e) => + { + IsRunning = false; + await _fileSystemStoreService.SaveProjectAsync(Project, ProjectPath); + + if (_runningInitialAutomaticAudit) + { + MessageBox.Show("The initial audit is now complete, you may proceed to review the results", "Initial Audit Complete", MessageBoxButton.OK, MessageBoxImage.Information); + _runningInitialAutomaticAudit = false; + } + + IsBusy = false; + }; + + Export = new DelegateCommand(OnExport, CanExport).ObservesProperty(() => IsBusy).ObservesProperty(() => IsRunning); + StartAudit = new DelegateCommand(OnStartAudit, CanStartAudit).ObservesProperty(() => IsBusy).ObservesProperty(() => IsRunning); + StopAudit = new DelegateCommand(OnStopAudit, CanStopAudit).ObservesProperty(() => IsBusy).ObservesProperty(() => IsRunning); + AuditFolder = new DelegateCommand(OnAuditFolder, CanAuditFolder).ObservesProperty(() => IsBusy).ObservesProperty(() => IsRunning).ObservesProperty(() => SelectedItem); + RevealInExplorer = new DelegateCommand(OnRevealInExplorer, CanRevealInExplorer).ObservesProperty(() => IsBusy).ObservesProperty(() => SelectedItem); + } + + public DelegateCommand AuditFolder { get; } + + public DelegateCommand Export { get; } + + public bool IsBusy + { + get => _isBusy; + private set => SetProperty(ref _isBusy, value); + } + + public bool IsRunning + { + get => _isRunning; + private set => SetProperty(ref _isRunning, value); + } + + public Project Project + { + get => _project; + private set => SetProperty(ref _project, value); + } + + public string ProjectPath + { + get => _projectPath; + private set => SetProperty(ref _projectPath, value); + } + + public DelegateCommand RevealInExplorer { get; } + + public object SelectedItem + { + get => _selectedItem; + set => SetProperty(ref _selectedItem, value); + } + + public DelegateCommand StartAudit { get; } + + public DelegateCommand StopAudit { get; } + + public bool IsNavigationTarget(NavigationContext navigationContext) => false; + + public void OnNavigatedFrom(NavigationContext navigationContext) + { + } + + public void OnNavigatedTo(NavigationContext navigationContext) + { + IsBusy = true; + + ProjectPath = navigationContext.Parameters.GetValue(nameof(ProjectPath)); + Project = navigationContext.Parameters.GetValue(nameof(Project)); + + if (Project.State == ProjectState.Configured && !Project.Configuration.IsReadOnly) + { + _runningInitialAutomaticAudit = true; + OnStartAudit(); + } + + IsBusy = false; + } + + private bool CanAuditFolder() + { + return SelectedItem is IFolderEntry && + (SelectedItem as IFolderEntry).State == FolderEntryState.EnumerationSuspended && + !IsBusy && + !IsRunning && + !(Project?.Configuration?.IsReadOnly ?? true); + } + + private bool CanExport() => !IsBusy && !IsRunning; + + private bool CanRevealInExplorer() + { + return (SelectedItem is IFolderEntry || SelectedItem is FileEntry) && + !IsBusy && + !(Project?.Configuration?.IsReadOnly ?? true); + } + + private bool CanStartAudit() => !IsBusy && !IsRunning && !(Project?.Configuration?.IsReadOnly ?? true); + + private bool CanStopAudit() => !IsBusy && IsRunning; + + private void OnAuditFolder() + { + IsBusy = true; + + (SelectedItem as IFolderEntry).State = FolderEntryState.EnumeratingFilesystemEntries; + + _shareAuditService.StartAudit(Project); + } + + private async void OnExport() + { + IsBusy = true; + + var dialog = new SaveFileDialog + { + Filter = _fileSystemStoreService.ExportFilter, + FileName = _fileSystemStoreService.ExportDefaultFilename + }; + + if (dialog.ShowDialog() == true) + { + await _fileSystemStoreService.ExportProjectAsync(_project, dialog.FileName); + } + + IsBusy = false; + } + + private void OnRevealInExplorer() + { + IsBusy = true; + + if (SelectedItem is FileEntry) + { + var host = (SelectedItem as FileEntry).FullName.Trim('\\').Split('\\')[0]; + if (_project.Configuration.Credentials.UseCurrentCredentials) + { + using (Process.Start("explorer.exe", $"/select,\"{(SelectedItem as FileEntry).FullName}\"")) + { + } + } + else + { + using (var netUseConnection = _smbUtilitiesService.CreateNetUseConnection(host, Project.Configuration.Credentials.Username, Project.Configuration.Credentials.Domain, Project.Configuration.Credentials.Password)) + { + using (Process.Start("explorer.exe", $"/select,\"{(SelectedItem as FileEntry).FullName}\"")) + { + } + } + } + } + else + { + var host = (SelectedItem as IFolderEntry).FullName.Trim('\\').Split('\\')[0]; + if (_project.Configuration.Credentials.UseCurrentCredentials) + { + using (Process.Start("explorer.exe", $"\"{(SelectedItem as IFolderEntry).FullName}\"")) + { + } + } + else + { + using (var netUseConnection = _smbUtilitiesService.CreateNetUseConnection(host, Project.Configuration.Credentials.Username, Project.Configuration.Credentials.Domain, Project.Configuration.Credentials.Password)) + { + using (Process.Start("explorer.exe", $"\"{(SelectedItem as IFolderEntry).FullName}\"")) + { + } + } + } + } + + IsBusy = false; + } + + private void OnStartAudit() + { + IsBusy = true; + + _shareAuditService.StartAudit(Project); + } + + private void OnStopAudit() + { + IsBusy = true; + + _shareAuditService.StopAudit(); + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ConfigurationViewModel.cs b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ConfigurationViewModel.cs new file mode 100644 index 0000000..0d90856 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ConfigurationViewModel.cs @@ -0,0 +1,283 @@ +using Dionach.ShareAudit.Model; +using Dionach.ShareAudit.Modules.Services; +using Dionach.ShareAudit.Modules.UserInterface.Views; +using Prism.Commands; +using Prism.Mvvm; +using Prism.Regions; +using System; + +namespace Dionach.ShareAudit.Modules.UserInterface.ViewModels +{ + public class ConfigurationViewModel : BindableBase, INavigationAware + { + private readonly ICredentialsValidationService _credentialsValidationService; + private readonly IFileSystemStoreService _fileSystemStoreService; + private readonly IRegionManager _regionManager; + private readonly IScopeNormalizationService _scopeNormalizationService; + private readonly IScopeValidationService _scopeValidationService; + private string _errorMessageForCredentials; + private string _errorMessageForDomain; + private string _errorMessageForPassword; + private string _errorMessageForScope; + private string _errorMessageForUsername; + private bool _isBusy; + private bool _isProjectUnconfigured; + private Project _project; + private string _projectPath; + + public ConfigurationViewModel( + IRegionManager regionManager, + IFileSystemStoreService fileSystemStoreService, + ICredentialsValidationService credentialsValidationService, + IScopeValidationService scopeValidationService, + IScopeNormalizationService scopeNormalizationService) + { + _regionManager = regionManager ?? throw new ArgumentNullException(nameof(regionManager)); + _fileSystemStoreService = fileSystemStoreService ?? throw new ArgumentNullException(nameof(fileSystemStoreService)); + _credentialsValidationService = credentialsValidationService ?? throw new ArgumentNullException(nameof(credentialsValidationService)); + _scopeValidationService = scopeValidationService ?? throw new ArgumentNullException(nameof(scopeValidationService)); + _scopeNormalizationService = scopeNormalizationService ?? throw new ArgumentNullException(nameof(scopeNormalizationService)); + + Cancel = new DelegateCommand(OnCancel, CanCancel).ObservesProperty(() => IsBusy); + Next = new DelegateCommand(OnNext, CanNext).ObservesProperty(() => IsBusy); + Import = new DelegateCommand(OnImport, CanImport).ObservesProperty(() => IsBusy).ObservesProperty(() => Project.Configuration.Scope); + UseCurrentCredentials = new DelegateCommand(OnUseCurrentCredentials, CanUseCurrentCredentials).ObservesProperty(() => IsBusy); + } + + public DelegateCommand Cancel { get; } + + public string ErrorMessageForCredentials + { + get => _errorMessageForCredentials; + set => SetProperty(ref _errorMessageForCredentials, value); + } + + public string ErrorMessageForDomain + { + get => _errorMessageForDomain; + set => SetProperty(ref _errorMessageForDomain, value); + } + + public string ErrorMessageForPassword + { + get => _errorMessageForPassword; + set => SetProperty(ref _errorMessageForPassword, value); + } + + public string ErrorMessageForScope + { + get => _errorMessageForScope; + set => SetProperty(ref _errorMessageForScope, value); + } + + public string ErrorMessageForUsername + { + get => _errorMessageForUsername; + set => SetProperty(ref _errorMessageForUsername, value); + } + + public DelegateCommand Import { get; } + + public bool IsBusy + { + get => _isBusy; + private set => SetProperty(ref _isBusy, value); + } + + public bool IsProjectUnconfigured + { + get => _isProjectUnconfigured; + private set => SetProperty(ref _isProjectUnconfigured, value); + } + + public DelegateCommand Next { get; } + + public Project Project + { + get => _project; + private set => SetProperty(ref _project, value); + } + + public string ProjectPath + { + get => _projectPath; + private set => SetProperty(ref _projectPath, value); + } + + public DelegateCommand UseCurrentCredentials { get; } + + public bool IsNavigationTarget(NavigationContext navigationContext) => false; + + public void OnNavigatedFrom(NavigationContext navigationContext) + { + } + + public async void OnNavigatedTo(NavigationContext navigationContext) + { + ProjectPath = navigationContext.Parameters.GetValue(nameof(ProjectPath)); + if (navigationContext.Parameters.ContainsKey(nameof(Project))) + { + Project = navigationContext.Parameters.GetValue(nameof(Project)); + } + else + { + Project = await _fileSystemStoreService.LoadProjectAsync(ProjectPath); + } + + IsProjectUnconfigured = Project.State == ProjectState.New; + } + + private bool CanCancel() + { + return !IsBusy; + } + + private bool CanImport() + { + return !IsBusy && string.IsNullOrEmpty(Project?.Configuration?.Scope); + } + + private bool CanNext() + { + return !IsBusy; + } + + private bool CanUseCurrentCredentials() => !IsBusy; + + private void OnCancel() => _regionManager.RequestNavigate("ContentRegion", nameof(WelcomeView)); + + private async void OnImport() + { + IsBusy = true; + + ErrorMessageForDomain = string.Empty; + ErrorMessageForUsername = string.Empty; + ErrorMessageForPassword = string.Empty; + ErrorMessageForCredentials = string.Empty; + +#pragma warning disable IDE0042 // Deconstruct variable declaration + var domainValidationResult = await _credentialsValidationService.ValidateDomainAsync(Project.Configuration.Credentials.Domain); + ErrorMessageForDomain = domainValidationResult.errorMessage; + + var usernameValidationResult = await _credentialsValidationService.ValidateUsernameAsync(Project.Configuration.Credentials.Username); + ErrorMessageForUsername = usernameValidationResult.errorMessage; + + var passwordValidationResult = (isValid: true, errorMessage: string.Empty); + if (!Project.Configuration.Credentials.UseCurrentCredentials) + { + passwordValidationResult = await _credentialsValidationService.ValidatePasswordAsync(Project.Configuration.Credentials.Password); + ErrorMessageForPassword = passwordValidationResult.errorMessage; + } + + var credentialsValidationResult = (isValid: true, errorMessage: string.Empty); +#pragma warning restore IDE0042 // Deconstruct variable declaration + if (!Project.Configuration.Credentials.UseCurrentCredentials && domainValidationResult.isValid && usernameValidationResult.isValid && passwordValidationResult.isValid) + { + credentialsValidationResult = await _credentialsValidationService.ValidateCredentialsAsync(Project.Configuration.Credentials); + ErrorMessageForCredentials = credentialsValidationResult.errorMessage; + } + + if (credentialsValidationResult.isValid) + { + await _fileSystemStoreService.SaveProjectAsync(Project, ProjectPath); + + var parameters = new NavigationParameters + { + { nameof(ImportScopeViewModel.ProjectPath), ProjectPath }, + { nameof(ImportScopeViewModel.Project), Project } + }; + + _regionManager.RequestNavigate("ContentRegion", nameof(ImportScopeView), parameters); + } + + IsBusy = false; + } + + private async void OnNext() + { + IsBusy = true; + + ErrorMessageForDomain = string.Empty; + ErrorMessageForUsername = string.Empty; + ErrorMessageForPassword = string.Empty; + ErrorMessageForCredentials = string.Empty; + ErrorMessageForScope = string.Empty; + + var credentialsValidationResult = (isValid: true, errorMessage: string.Empty); + if (!Project.Configuration.IsReadOnly && !Project.Configuration.Credentials.UseCurrentCredentials) + { +#pragma warning disable IDE0042 // Deconstruct variable declaration + var domainValidationResult = await _credentialsValidationService.ValidateDomainAsync(Project.Configuration.Credentials.Domain); + ErrorMessageForDomain = domainValidationResult.errorMessage; + + var usernameValidationResult = await _credentialsValidationService.ValidateUsernameAsync(Project.Configuration.Credentials.Username); + ErrorMessageForUsername = usernameValidationResult.errorMessage; + + var passwordValidationResult = (isValid: true, errorMessage: string.Empty); + if (!Project.Configuration.Credentials.UseCurrentCredentials) + { + passwordValidationResult = await _credentialsValidationService.ValidatePasswordAsync(Project.Configuration.Credentials.Password); + ErrorMessageForPassword = passwordValidationResult.errorMessage; + } + + if (domainValidationResult.isValid && usernameValidationResult.isValid && passwordValidationResult.isValid) + { + credentialsValidationResult = await _credentialsValidationService.ValidateCredentialsAsync(Project.Configuration.Credentials); + ErrorMessageForCredentials = credentialsValidationResult.errorMessage; + } + } + + var scopeValidationResult = await _scopeValidationService.ValidateScopeAsync(Project.Configuration.Scope); +#pragma warning restore IDE0042 // Deconstruct variable declaration + ErrorMessageForScope = scopeValidationResult.errorMessage; + + if (scopeValidationResult.isValid) + { + Project.Configuration.Scope = await _scopeNormalizationService.NormalizeScopeAsync(Project.Configuration.Scope); + } + + if ((!Project.Configuration.IsReadOnly && credentialsValidationResult.isValid && scopeValidationResult.isValid) || (Project.Configuration.IsReadOnly && scopeValidationResult.isValid)) + { + if (Project.State < ProjectState.Configured) + { + Project.State = ProjectState.Configured; + } + + await _fileSystemStoreService.SaveProjectAsync(Project, ProjectPath); + + var parameters = new NavigationParameters + { + { nameof(ImportScopeViewModel.ProjectPath), ProjectPath }, + { nameof(ImportScopeViewModel.Project), Project } + }; + + _regionManager.RequestNavigate("ContentRegion", nameof(AuditView), parameters); + } + + IsBusy = false; + } + + private async void OnUseCurrentCredentials() + { + IsBusy = true; + + if (Project.Configuration.Credentials.UseCurrentCredentials) + { + (var domain, var username) = await _credentialsValidationService.GetCurrentUserInformation(); + Project.Configuration.Credentials.Domain = domain; + Project.Configuration.Credentials.Username = username; + Project.Configuration.Credentials.Password = string.Empty; + Project.Configuration.UseAlternateAuthenticationMethod = false; + } + else + { + Project.Configuration.Credentials.Domain = string.Empty; + Project.Configuration.Credentials.Username = string.Empty; + Project.Configuration.Credentials.Password = string.Empty; + Project.Configuration.UseAlternateAuthenticationMethod = true; + } + + IsBusy = false; + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/HelpViewModel.cs b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/HelpViewModel.cs new file mode 100644 index 0000000..04ab0bc --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/HelpViewModel.cs @@ -0,0 +1,24 @@ +using Dionach.ShareAudit.Modules.UserInterface.Views; +using Prism.Commands; +using Prism.Mvvm; +using Prism.Regions; +using System; + +namespace Dionach.ShareAudit.Modules.UserInterface.ViewModels +{ + public class HelpViewModel : BindableBase + { + private readonly IRegionManager _regionManager; + + public HelpViewModel(IRegionManager regionManager) + { + _regionManager = regionManager ?? throw new ArgumentNullException(nameof(regionManager)); + + Back = new DelegateCommand(OnBack, () => true); + } + + public DelegateCommand Back { get; } + + private void OnBack() => _regionManager.RequestNavigate("ContentRegion", nameof(WelcomeView)); + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ImportScopeViewModel.cs b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ImportScopeViewModel.cs new file mode 100644 index 0000000..75ee97e --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/ImportScopeViewModel.cs @@ -0,0 +1,123 @@ +using Dionach.ShareAudit.Model; +using Dionach.ShareAudit.Modules.Services; +using Dionach.ShareAudit.Modules.UserInterface.Views; +using Prism.Commands; +using Prism.Mvvm; +using Prism.Regions; +using System; + +namespace Dionach.ShareAudit.Modules.UserInterface.ViewModels +{ + public class ImportScopeViewModel : BindableBase, INavigationAware + { + private readonly IFileSystemStoreService _fileSystemStoreService; + private readonly IImportScopeFromActiveDirectoryService _importScopeFromActiveDirectoryService; + private readonly IRegionManager _regionManager; + private string _domain; + private ImportComputerType _importComputerType = ImportComputerType.Servers; + private bool _isBusy; + private Project _project; + private string _projectPath; + + public ImportScopeViewModel( + IRegionManager regionManager, + IFileSystemStoreService fileSystemStoreService, + IImportScopeFromActiveDirectoryService importScopeFromActiveDirectoryService) + { + _regionManager = regionManager ?? throw new ArgumentNullException(nameof(regionManager)); + _fileSystemStoreService = fileSystemStoreService ?? throw new ArgumentNullException(nameof(fileSystemStoreService)); + _importScopeFromActiveDirectoryService = importScopeFromActiveDirectoryService ?? throw new ArgumentNullException(nameof(importScopeFromActiveDirectoryService)); + + Cancel = new DelegateCommand(OnCancel, CanCancel).ObservesProperty(() => IsBusy); + Import = new DelegateCommand(OnImport, CanImport).ObservesProperty(() => IsBusy); + } + + public DelegateCommand Cancel { get; } + + public string Domain + { + get => _domain; + set => SetProperty(ref _domain, value); + } + + public DelegateCommand Import { get; } + + public ImportComputerType ImportComputerType + { + get => _importComputerType; + set => SetProperty(ref _importComputerType, value); + } + + public bool IsBusy + { + get => _isBusy; + private set => SetProperty(ref _isBusy, value); + } + + public Project Project + { + get => _project; + private set => SetProperty(ref _project, value); + } + + public string ProjectPath + { + get => _projectPath; + private set => SetProperty(ref _projectPath, value); + } + + public bool IsNavigationTarget(NavigationContext navigationContext) => false; + + public void OnNavigatedFrom(NavigationContext navigationContext) + { + } + + public void OnNavigatedTo(NavigationContext navigationContext) + { + ProjectPath = navigationContext.Parameters.GetValue(nameof(ProjectPath)); + Project = navigationContext.Parameters.GetValue(nameof(Project)); + Domain = Project.Configuration.Credentials.Domain; + } + + private bool CanCancel() => !IsBusy; + + private bool CanImport() => !IsBusy; + + private void OnCancel() + { + var parameters = new NavigationParameters + { + { nameof(ConfigurationViewModel.ProjectPath), ProjectPath }, + { nameof(ConfigurationViewModel.Project), Project } + }; + + _regionManager.RequestNavigate("ContentRegion", nameof(ConfigurationView), parameters); + } + + private async void OnImport() + { + IsBusy = true; + + if (Project.Configuration.Credentials.UseCurrentCredentials) + { + Project.Configuration.Scope = await _importScopeFromActiveDirectoryService.Import(Domain, ImportComputerType); + } + else + { + Project.Configuration.Scope = await _importScopeFromActiveDirectoryService.Import(Domain, Project.Configuration.Credentials.Username, Project.Configuration.Credentials.Password, ImportComputerType); + } + + await _fileSystemStoreService.SaveProjectAsync(Project, ProjectPath); + + var parameters = new NavigationParameters + { + { nameof(ConfigurationViewModel.ProjectPath), ProjectPath }, + { nameof(ConfigurationViewModel.Project), Project } + }; + + _regionManager.RequestNavigate("ContentRegion", nameof(ConfigurationView), parameters); + + IsBusy = false; + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/WelcomeViewModel.cs b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/WelcomeViewModel.cs new file mode 100644 index 0000000..e20bc7a --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/ViewModels/WelcomeViewModel.cs @@ -0,0 +1,97 @@ +using Dionach.ShareAudit.Modules.Services; +using Dionach.ShareAudit.Modules.UserInterface.Views; +using Microsoft.Win32; +using Prism.Commands; +using Prism.Mvvm; +using Prism.Regions; +using System; + +namespace Dionach.ShareAudit.Modules.UserInterface.ViewModels +{ + public class WelcomeViewModel : BindableBase + { + private readonly IFileSystemStoreService _fileSystemStoreService; + private readonly IRegionManager _regionManager; + private bool _isBusy; + + public WelcomeViewModel(IRegionManager regionManager, IFileSystemStoreService fileSystemStoreService) + { + _regionManager = regionManager ?? throw new ArgumentNullException(nameof(regionManager)); + _fileSystemStoreService = fileSystemStoreService ?? throw new ArgumentNullException(nameof(fileSystemStoreService)); + + Banner = "/Dionach.ShareAudit.Modules.UserInterface;component/Images/Banner.png"; + New = new DelegateCommand(OnNew, CanNew).ObservesProperty(() => IsBusy); + Load = new DelegateCommand(OnLoad, CanLoad).ObservesProperty(() => IsBusy); + Help = new DelegateCommand(OnHelp, CanHelp).ObservesProperty(() => IsBusy); + } + + public string Banner { get; } + + public DelegateCommand Help { get; } + + public bool IsBusy + { + get => _isBusy; + set => SetProperty(ref _isBusy, value); + } + + public DelegateCommand Load { get; } + + public DelegateCommand New { get; } + + private bool CanHelp() => !IsBusy; + + private bool CanLoad() => !IsBusy; + + private bool CanNew() => !IsBusy; + + private void OnHelp() => _regionManager.RequestNavigate("ContentRegion", nameof(HelpView)); + + private void OnLoad() + { + IsBusy = true; + + var dialog = new OpenFileDialog + { + Filter = _fileSystemStoreService.ShareAuditFilter, + }; + + if (dialog.ShowDialog() == true) + { + var parameters = new NavigationParameters + { + { nameof(ConfigurationViewModel.ProjectPath), dialog.FileName } + }; + + _regionManager.RequestNavigate("ContentRegion", nameof(ConfigurationView), parameters); + } + + IsBusy = false; + } + + private async void OnNew() + { + IsBusy = true; + + var dialog = new SaveFileDialog + { + Filter = _fileSystemStoreService.ShareAuditFilter, + FileName = _fileSystemStoreService.ShareAuditDefaultFilename + }; + + if (dialog.ShowDialog() == true) + { + await _fileSystemStoreService.CreateProjectAsync(dialog.FileName); + + var parameters = new NavigationParameters + { + { nameof(ConfigurationViewModel.ProjectPath), dialog.FileName } + }; + + _regionManager.RequestNavigate("ContentRegion", nameof(ConfigurationView), parameters); + } + + IsBusy = false; + } + } +} diff --git a/src/Dionach.ShareAudit.Modules.UserInterface/Views/AuditView.xaml b/src/Dionach.ShareAudit.Modules.UserInterface/Views/AuditView.xaml new file mode 100644 index 0000000..93ea707 --- /dev/null +++ b/src/Dionach.ShareAudit.Modules.UserInterface/Views/AuditView.xaml @@ -0,0 +1,219 @@ + + + + + + +