├── .gitattributes ├── .github └── workflows │ └── dotnet.yml ├── .gitignore ├── Fededim.Extensions.Configuration.Protected.ConsoleTest ├── AppSettings.cs ├── Fededim.Extensions.Configuration.Protected.ConsoleTest.csproj ├── Program.cs ├── Properties │ └── launchSettings.json ├── appsettings.development.json ├── appsettings.json └── appsettings.xml ├── Fededim.Extensions.Configuration.Protected.DataProtectionAPI ├── DataProtectionAPIProtectConfigurationData.cs ├── DataProtectionAPIProtectProvider.cs ├── Fededim.Extensions.Configuration.Protected.DataProtectionAPI.csproj ├── docs │ └── README.md └── license │ └── LICENSE.txt ├── Fededim.Extensions.Configuration.Protected.DataProtectionAPITest ├── DataProtectionAPIProtectProviderTest.cs ├── ExtendedStopwatch.cs ├── Fededim.Extensions.Configuration.Protected.DataProtectionAPITest.csproj ├── PassthroughProtectProviderTest.cs ├── ProtectedConfigurationBuilderTest.cs └── ReverseChainedProtectProvider.cs ├── Fededim.Extensions.Configuration.Protected.sln ├── Fededim.Extensions.Configuration.Protected ├── ConfigurationBuilderExtensions.cs ├── Fededim.Extensions.Configuration.Protected.csproj ├── PassthroughProtectConfigurationData.cs ├── PassthroughProtectProvider.cs ├── ProtectFileProcessors.cs ├── ProtectProviderConfigurationData.cs ├── ProtectedConfigurationBuilder.cs ├── ProtectedConfigurationProvider.cs ├── docs │ └── README.md └── license │ └── LICENSE.txt ├── Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest ├── AppSettings.cs ├── Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest.csproj ├── Keys │ └── key-40c2510c-f6c5-4c4f-9c18-defdc18fdb84.xml ├── Program.cs ├── Properties │ └── launchSettings.json ├── appsettings.development.json └── appsettings.json ├── Fededim.Extensions.Configuration.ProtectedJson ├── Fededim.Extensions.Configuration.ProtectedJson.csproj ├── ProtectedJsonConfiguration.cs ├── ProtectedJsonConfigurationExtensions.cs ├── docs │ └── README.md └── license │ └── LICENSE.txt ├── LICENSE.txt ├── README.md └── misc └── last_build_artifacts ├── Cobertura.xml ├── Fededim.Extensions.Configuration.Protected.DataProataProtectionAPIProtectConfigurationData.html ├── Fededim.Extensions.Configuration.Protected.DataProtectionAPI_DataProtectionAPIProtectProvider.html ├── Fededim.Extensions.Configuration.Protected_ConfigurationBuilderExtensions.html ├── Fededim.Extensions.Configuration.Protected_IProtectProviderConfigurationData.html ├── Fededim.Extensions.Configuration.Protected_JsonProtectFileProcessor.html ├── Fededim.Extensions.Configuration.Protected_JsonWithCommentsProtectFileProcessor.html ├── Fededim.Extensions.Configuration.Protected_PassthroughProtectConfigurationData.html ├── Fededim.Extensions.Configuration.Protected_PassthroughProtectProvider.html ├── Fededim.Extensions.Configuration.Protected_ProtectFileOptions.html ├── Fededim.Extensions.Configuration.Protected_ProtectProviderConfigurationData.html ├── Fededim.Extensions.Configuration.Protected_ProtectedConfigurationBuilder.html ├── Fededim.Extensions.Configuration.Protected_ProtectedConfigurationProvider.html ├── Fededim.Extensions.Configuration.Protected_RawProtectFileProcessor.html ├── Fededim.Extensions.Configuration.Protected_XmlProtectFileProcessor.html ├── SummaryGithub.md ├── TestResults-net462-Fededim.Extensions.Configuration.Protected.DataProtectionAPITest.trx ├── TestResults-net6.0-Fededim.Extensions.Configuration.Protected.DataProtectionAPITest.trx ├── TestResults-net8.0-Fededim.Extensions.Configuration.Protected.DataProtectionAPITest.trx ├── badge_branchcoverage.svg ├── badge_combined.svg ├── badge_linecoverage.svg ├── badge_methodcoverage.svg ├── badge_shieldsio_branchcoverage_blue.svg ├── badge_shieldsio_branchcoverage_brightgreen.svg ├── badge_shieldsio_branchcoverage_green.svg ├── badge_shieldsio_branchcoverage_lightgrey.svg ├── badge_shieldsio_branchcoverage_orange.svg ├── badge_shieldsio_branchcoverage_red.svg ├── badge_shieldsio_branchcoverage_yellow.svg ├── badge_shieldsio_branchcoverage_yellowgreen.svg ├── badge_shieldsio_linecoverage_blue.svg ├── badge_shieldsio_linecoverage_brightgreen.svg ├── badge_shieldsio_linecoverage_green.svg ├── badge_shieldsio_linecoverage_lightgrey.svg ├── badge_shieldsio_linecoverage_orange.svg ├── badge_shieldsio_linecoverage_red.svg ├── badge_shieldsio_linecoverage_yellow.svg ├── badge_shieldsio_linecoverage_yellowgreen.svg ├── badge_shieldsio_methodcoverage_blue.svg ├── badge_shieldsio_methodcoverage_brightgreen.svg ├── badge_shieldsio_methodcoverage_green.svg ├── badge_shieldsio_methodcoverage_lightgrey.svg ├── badge_shieldsio_methodcoverage_orange.svg ├── badge_shieldsio_methodcoverage_red.svg ├── badge_shieldsio_methodcoverage_yellow.svg ├── badge_shieldsio_methodcoverage_yellowgreen.svg ├── class.js ├── icon_cog.svg ├── icon_cog_dark.svg ├── icon_cube.svg ├── icon_cube_dark.svg ├── icon_fork.svg ├── icon_fork_dark.svg ├── icon_info-circled.svg ├── icon_info-circled_dark.svg ├── icon_minus.svg ├── icon_minus_dark.svg ├── icon_plus.svg ├── icon_plus_dark.svg ├── icon_search-minus.svg ├── icon_search-minus_dark.svg ├── icon_search-plus.svg ├── icon_search-plus_dark.svg ├── icon_sponsor.svg ├── icon_star.svg ├── icon_star_dark.svg ├── icon_up-dir.svg ├── icon_up-dir_active.svg ├── icon_up-down-dir.svg ├── icon_up-down-dir_dark.svg ├── icon_wrench.svg ├── icon_wrench_dark.svg ├── index.htm ├── index.html ├── main.js ├── report.css └── test_results.md /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.github/workflows/dotnet.yml: -------------------------------------------------------------------------------- 1 | name: "Main Build Process" 2 | 3 | # Runs on main branch commits, 4 | # every commit in a pull request, any published release. 5 | on: 6 | push: 7 | branches: ["master"] 8 | paths-ignore: 9 | - 'misc/last_build_artifacts/**' 10 | pull_request: 11 | branches: ["master"] 12 | paths-ignore: 13 | - 'misc/last_build_artifacts/**' 14 | release: 15 | types: [published] 16 | 17 | env: 18 | REGISTRY: ghcr.io 19 | IMAGE_NAME: ${{ github.repository }} 20 | jobs: 21 | build: 22 | name: "Build & Test" 23 | 24 | # Permissions this GitHub Action needs for other things in GitHub 25 | permissions: 26 | pull-requests: write 27 | contents: write 28 | checks: write # required if create-status-check: true 29 | 30 | runs-on: windows-latest 31 | 32 | steps: 33 | - name: "Check out the code" 34 | uses: actions/checkout@v4 35 | with: 36 | fetch-depth: 0 37 | 38 | 39 | - name: "Setup .NET" 40 | uses: actions/setup-dotnet@v4 41 | with: 42 | dotnet-version: | 43 | 8.x 44 | dotnet-quality: 'ga' 45 | 46 | 47 | # - name: "Install Tyrannoport" 48 | # run: dotnet tool install --global tyrannoport 49 | 50 | 51 | - name: "Restore/Build/Test" 52 | run: dotnet test --configuration Release --verbosity normal /p:CollectCoverage=true /p:CoverletOutputFormat=cobertura /p:CoverletOutput=TestResults/ 53 | 54 | 55 | - name: "Generate reports From Code Coverage" 56 | uses: danielpalme/ReportGenerator-GitHub-Action@5.3.8 57 | with: 58 | reports: "**/*.cobertura.xml" # REQUIRED # The coverage reports that should be parsed (separated by semicolon). Globbing is supported. 59 | targetdir: "${{ github.workspace }}/misc/last_build_artifacts" # REQUIRED # The directory where the generated report should be saved. 60 | reporttypes: Html;Cobertura;MarkdownSummaryGithub;Badges # The output formats and scope (separated by semicolon) Values: Badges, Clover, Cobertura, CsvSummary, Html, Html_Dark, Html_Light, Html_BlueRed, HtmlChart, HtmlInline, HtmlInline_AzurePipelines, HtmlInline_AzurePipelines_Dark, HtmlInline_AzurePipelines_Light, HtmlSummary, JsonSummary, Latex, LatexSummary, lcov, MarkdownSummary, MarkdownSummaryGithub, MarkdownDeltaSummary, MHtml, PngChart, SonarQube, TeamCitySummary, TextSummary, TextDeltaSummary, Xml, XmlSummary 61 | verbosity: "Info" # The verbosity level of the log messages. Values: Verbose, Info, Warning, Error, Off 62 | title: "Code Coverage and Reports" # Optional title. 63 | tag: "${{ github.run_number }}_${{ github.run_id }}" # Optional tag or build version. 64 | customSettings: "" # Optional custom settings (separated by semicolon). See: https://github.com/danielpalme/ReportGenerator/wiki/Settings. 65 | toolpath: "reportgeneratortool" # Default directory for installing the dotnet tool. 66 | 67 | 68 | #- name: "Generate HTML from Test Results" 69 | # run: gci -Path ${{ github.workspace }} -Filter *.trx -Recurse | foreach { tyrannoport -o misc/last_build_artifacts $_.FullName } 70 | 71 | 72 | - name: "Generate reports from Test Results" 73 | if: always() 74 | id: process-test 75 | uses: im-open/process-dotnet-test-results@v3 76 | with: 77 | github-token: ${{ secrets.GITHUB_TOKEN }} 78 | update-comment-if-one-exists: false 79 | #report-title-filter: 'Tests' 80 | 81 | 82 | - name: "Copy reports from Test Results to misc/last_build_artifacts folder" 83 | run: | 84 | cp "${{ steps.process-test.outputs.test-results-file-path }}" "${{ github.workspace }}/misc/last_build_artifacts/test_results.md" 85 | gci -Path "${{ github.workspace }}/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest" -Filter *.trx -Recurse | foreach { cp "$_" misc/last_build_artifacts/ } 86 | gci -Path "${{ github.workspace }}/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest" -Filter *.cobertura.xml -Recurse | foreach { cp "$_" misc/last_build_artifacts/ } 87 | 88 | 89 | - name: "Upload All Artifacts" 90 | uses: actions/upload-artifact@v4 91 | with: 92 | name: test-artifacts 93 | path: ${{ github.workspace }}/**/misc/last_build_artifacts/** 94 | 95 | 96 | - name: Commit and push last_build_artifacts to PR branch 97 | if: github.event_name == 'pull_request' 98 | run: | 99 | git checkout ${{ github.event.pull_request.head.ref }} 100 | git config --global user.name 'Build And Test Github Action' 101 | git config --global user.email 'fededim@users.noreply.github.com' 102 | git add ./misc/last_build_artifacts/* 103 | git commit -m "Updated Last Build artifacts" 104 | git push 105 | 106 | 107 | - name: Test Results Summary 108 | uses: dorny/test-reporter@v1 109 | with: 110 | artifact: test-artifacts # artifact name 111 | name: XUnit Tests # Name of the check run which will be created 112 | path: '**/*.trx' # Path to test results (inside artifact .zip) 113 | reporter: dotnet-trx # Format of test results 114 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | 365 | /Fededim.Extensions.Configuration.Protected.ConsoleTest/Keys 366 | /Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/Keys/ 367 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.ConsoleTest/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Fededim.Extensions.Configuration.Protected.ConsoleTest 8 | { 9 | public class NullableSettings 10 | { 11 | public Int32? Int { get; set; } 12 | public bool? Bool { get; set; } 13 | public Double? Double { get; set; } 14 | public DateTime? DateTime { get; set; } 15 | public Double[] DoubleArray { get; set; } 16 | 17 | public NullableSettings() 18 | { 19 | DoubleArray = new Double[0]; 20 | } 21 | 22 | } 23 | 24 | 25 | public class Logging 26 | { 27 | public Dictionary LogLevel { get; set; } 28 | } 29 | 30 | public class AppSettings 31 | { 32 | // settings defined inside JSON files 33 | public Int32 Int { get; set; } 34 | public bool Bool { get; set; } 35 | public Double Double { get; set; } 36 | public DateTime DateTime { get; set; } 37 | public Int32[] IntArray { get; set; } 38 | 39 | public NullableSettings Nullable { get; set; } 40 | 41 | public Dictionary ConnectionStrings { get; set; } 42 | 43 | public String PlainTextJsonSpecialCharacters { get; set; } 44 | public String EncryptedJsonSpecialCharacters { get; set; } 45 | 46 | 47 | public AppSettings() 48 | { 49 | IntArray = new Int32[0]; 50 | Nullable = new NullableSettings(); 51 | ConnectionStrings = new Dictionary(); 52 | } 53 | 54 | // settings defined inside XML file 55 | public String EncryptedXmlSecretKey { get; set; } 56 | public String PlainTextXmlSecretKey { get; set; } 57 | public Dictionary TransientFaultHandlingOptions { get; set; } 58 | public Logging Logging { get; set; } 59 | public String EmptyElement { get; set; } 60 | 61 | 62 | // settings defined in InMemoryCollection 63 | public String EncryptedInMemorySecretKey { get; set; } 64 | public String PlainTextInMemorySecretKey { get; set; } 65 | public String EncryptedInMemorySpecialCharacters { get; set; } 66 | public String PlainTextInMemorySpecialCharacters { get; set; } 67 | 68 | 69 | // settings defined in EnvironmentVariables 70 | public String EncryptedEnvironmentPassword { get; set; } 71 | public String PlainTextEnvironmentPassword { get; set; } 72 | 73 | 74 | // settings defined in Command Line Arguments 75 | public String EncryptedCommandLinePassword { get; set; } 76 | public String PlainTextCommandLinePassword { get; set; } 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.ConsoleTest/Fededim.Extensions.Configuration.Protected.ConsoleTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net8.0;net462 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | PreserveNewest 25 | 26 | 27 | PreserveNewest 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.ConsoleTest/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.AspNetCore.DataProtection; 4 | using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; 5 | using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; 6 | using Fededim.Extensions.Configuration.Protected.ConsoleTest; 7 | using Microsoft.Extensions.Options; 8 | using Fededim.Extensions.Configuration.Protected; 9 | using System.Diagnostics; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.IO; 13 | using System.Text.RegularExpressions; 14 | using System.Threading; 15 | using Fededim.Extensions.Configuration.Protected.DataProtectionAPI; 16 | 17 | public class Program 18 | { 19 | private static void ConfigureDataProtection(IDataProtectionBuilder builder) 20 | { 21 | builder.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration 22 | { 23 | EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, 24 | ValidationAlgorithm = ValidationAlgorithm.HMACSHA256, 25 | 26 | }).SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 15)).PersistKeysToFileSystem(new DirectoryInfo("..\\..\\..\\Keys")); 27 | } 28 | 29 | 30 | 31 | public static void Main(String[] args) 32 | { 33 | args = new String[] { "--EncryptedCommandLinePassword", "Protect:{secretArgPassword!\\*+?|{[()^$.#}", "--PlainTextCommandLinePassword", "secretArgPassword!\\*+?|{[()^$.#" }; 34 | 35 | // define the DI services: setup Data Protection API 36 | var servicesDataProtection = new ServiceCollection(); 37 | ConfigureDataProtection(servicesDataProtection.AddDataProtection()); 38 | var serviceProviderDataProtection = servicesDataProtection.BuildServiceProvider(); 39 | 40 | 41 | // creates all the DataProtectionAPIProtectConfigurationData classes specifying three different provider configurations 42 | 43 | // standard configuration using key number purpose 44 | var standardProtectConfigurationData = new DataProtectionAPIProtectConfigurationData(serviceProviderDataProtection); 45 | 46 | // standard configuration using key number purpose overridden with a custom tokenization 47 | var otherProtectedTokenizationProtectConfigurationData = new DataProtectionAPIProtectConfigurationData(serviceProviderDataProtection,2, protectRegexString: "OtherProtect(?(:{(?[^:}]+)})?):{(?.+?)}", protectedRegexString: "OtherProtected(?(:{(?[^:}]+)})?):{(?.+?)}", protectedReplaceString: "OtherProtected${subPurposePattern}:{${protectedData}}"); 48 | 49 | // standard configuration using string purpose 50 | var magicPurposeStringProtectConfigurationData = new DataProtectionAPIProtectConfigurationData(serviceProviderDataProtection, "MagicPurpose"); 51 | 52 | 53 | 54 | // activates JsonWithCommentsProtectFileProcessor 55 | ConfigurationBuilderExtensions.UseJsonWithCommentsProtectFileOption(); 56 | 57 | // define in-memory configuration key-value pairs to be encrypted 58 | var memoryConfiguration = new Dictionary 59 | { 60 | ["EncryptedInMemorySecretKey"] = "Protect:{InMemory MyKey Value}", 61 | ["PlainTextInMemorySecretKey"] = "InMemory MyKey Value", 62 | ["TransientFaultHandlingOptions:Enabled"] = bool.FalseString, 63 | ["Logging:LogLevel:Default"] = "Protect:{Warning}", 64 | ["UserDomain"] = "Protect:{DOMAIN\\USER}", 65 | ["EncryptedInMemorySpecialCharacters"] = "Protect:{\\!*+?|{[()^$.#}", 66 | ["PlainTextInMemorySpecialCharacters"] = "\\!*+?|{[()^$.#" 67 | }; 68 | 69 | // define an environment variable to be encrypted 70 | Environment.SetEnvironmentVariable("EncryptedEnvironmentPassword", "Protect:{SecretEnvPassword\\!*+?|{[()^$.#}", EnvironmentVariableTarget.Process); 71 | Environment.SetEnvironmentVariable("PlainTextEnvironmentPassword", "SecretEnvPassword\\!*+?|{[()^$.#", EnvironmentVariableTarget.Process); 72 | 73 | // encrypts all configuration sources (must be done before reading the configuration) 74 | 75 | // encrypts all Protect:{} token tags inside command line argument (you can use also the same method to encrypt String, IEnumerable, IDictionary value of any configuration source 76 | var encryptedArgs = standardProtectConfigurationData.ProtectConfigurationValue(args); 77 | 78 | // encrypts all Protect:{} token tags inside im-memory dictionary 79 | magicPurposeStringProtectConfigurationData.ProtectConfigurationValue(memoryConfiguration); 80 | 81 | // encrypts all Protect:{} token tags inside .json files and all OtherProtect:{} inside .xml files 82 | var encryptedJsonFiles = standardProtectConfigurationData.ProtectFiles("."); 83 | var encryptedXmlFiles = otherProtectedTokenizationProtectConfigurationData.ProtectFiles(".", searchPattern: "*.xml"); 84 | 85 | // encrypts all Protect:{} token tags inside environment variables 86 | magicPurposeStringProtectConfigurationData.ProtectEnvironmentVariables(); 87 | 88 | // please check that all configuration source defined above are encrypted (check also Environment.GetEnvironmentVariable("SecretEnvironmentPassword") in Watch window) 89 | // note the per key purpose string override in file appsettings.development.json inside Nullable:DoubleArray contains two elements one with "Protect:{3.14}" and one with "Protect:{%customSubPurpose%}:{3.14}", even though the value is the same (3.14) they are encrypted differently due to the custom key purpose string 90 | // note the per key purpose string override in file appsettings.xml inside TransientFaultHandlingOptions contains two elements AutoRetryDelay with "OtherProtect:{00:00:07}" and AutoRetryDelaySubPurpose with "OtherProtect:{sUbPuRpOsE}:{00:00:07}", even though the value is the same (00:00:07) they are encrypted differently due to the custom key purpose string 91 | Debugger.Break(); 92 | 93 | // define the application configuration using almost all possible known ConfigurationSources 94 | var configuration = new ProtectedConfigurationBuilder(standardProtectConfigurationData) // global configuration 95 | .AddCommandLine(encryptedArgs) 96 | .AddJsonFile("appsettings.json") 97 | .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNETCORE_ENVIRONMENT")}.json", false, true) 98 | .AddXmlFile("appsettings.xml").WithProtectedConfigurationOptions(otherProtectedTokenizationProtectConfigurationData) // overrides global configuration for XML file 99 | .AddInMemoryCollection(memoryConfiguration).WithProtectedConfigurationOptions(magicPurposeStringProtectConfigurationData) // overrides global configuration for in-memory collection file 100 | .AddEnvironmentVariables().WithProtectedConfigurationOptions(magicPurposeStringProtectConfigurationData) // overrides global configuration for enviroment variables file 101 | .Build(); 102 | 103 | // define other DI services: configure strongly typed AppSettings configuration class (must be done after having read the configuration) 104 | var services = new ServiceCollection(); 105 | services.Configure(configuration); 106 | var serviceProvider = services.BuildServiceProvider(); 107 | 108 | // retrieve the strongly typed AppSettings configuration class, we use IOptionsMonitor in order to be notified on any reloads of appSettings 109 | var optionsMonitor = serviceProvider.GetRequiredService>(); 110 | var appSettings = optionsMonitor.CurrentValue; 111 | optionsMonitor.OnChange(appSettingsReloaded => 112 | { 113 | // this breakpoint gets hit when the appsettings have changed due to a configuration reload, please check that the value of "Int" property inside appSettingsReloaded class is different from the one inside appSettings class 114 | // note that also there is an unavoidable framework bug on ChangeToken.OnChange which could get called multiple times when using FileSystemWatchers see https://github.com/dotnet/aspnetcore/issues/2542 115 | // see also the remarks section of FileSystemWatcher https://learn.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher.created?view=net-8.0#remarks 116 | Console.WriteLine($"OnChangeEvent: appsettings.{Environment.GetEnvironmentVariable("DOTNETCORE_ENVIRONMENT")}.json has been reloaded! appSettings Int {appSettings.Int} appSettingsReloaded {appSettingsReloaded.Int}"); 117 | Debugger.Break(); 118 | }); 119 | 120 | // please check that all values inside appSettings class are actually decrypted with the right value, make a note of the value of "Int" property it will change on the next second breakpoint 121 | Debugger.Break(); 122 | 123 | // added some simple assertions to test that decrypted value is the same as original plaintext one 124 | Debug.Assert(appSettings.EncryptedCommandLinePassword == appSettings.PlainTextCommandLinePassword); 125 | Debug.Assert(appSettings.EncryptedEnvironmentPassword == appSettings.PlainTextEnvironmentPassword); 126 | Debug.Assert(appSettings.EncryptedInMemorySecretKey == appSettings.PlainTextInMemorySecretKey); 127 | 128 | // appsettings.json assertions 129 | Debug.Assert(appSettings.EncryptedJsonSpecialCharacters == appSettings.PlainTextJsonSpecialCharacters); 130 | Debug.Assert(appSettings.ConnectionStrings["PartiallyEncryptedConnectionString"].Contains("(local)\\SECONDINSTANCE")); 131 | Debug.Assert(appSettings.ConnectionStrings["PartiallyEncryptedConnectionString"].Contains("Secret_Catalog")); 132 | Debug.Assert(appSettings.ConnectionStrings["PartiallyEncryptedConnectionString"].Contains("secret_user")); 133 | Debug.Assert(appSettings.ConnectionStrings["PartiallyEncryptedConnectionString"].Contains("secret_password")); 134 | Debug.Assert(appSettings.ConnectionStrings["FullyEncryptedConnectionString"].Contains("Data Source=server1\\THIRDINSTANCE; Initial Catalog=DB name; User ID=sa; Password=pass5678; MultipleActiveResultSets=True;")); 135 | 136 | // appsettings.development.json assertions 137 | Debug.Assert(appSettings.Nullable.DateTime.Value.ToUniversalTime() == new DateTime(2016, 10, 1, 20, 34, 56, 789, DateTimeKind.Utc)); 138 | Debug.Assert(appSettings.Nullable.Double == 123.456); 139 | Debug.Assert(appSettings.Nullable.Int == 98765); 140 | Debug.Assert(appSettings.Nullable.Bool == true); 141 | Debug.Assert(appSettings.Nullable.DoubleArray[1] == 3.14); 142 | Debug.Assert(appSettings.Nullable.DoubleArray[3] == 3.14); 143 | 144 | // appsettings.xml assertions 145 | Debug.Assert(appSettings.TransientFaultHandlingOptions["AutoRetryDelay"] == appSettings.TransientFaultHandlingOptions["AutoRetryDelaySubPurpose"]); 146 | Debug.Assert(appSettings.Logging.LogLevel["Microsoft"] == "Warning"); 147 | Debug.Assert(appSettings.EncryptedXmlSecretKey == appSettings.PlainTextXmlSecretKey); 148 | 149 | 150 | // multiple configuration reload example (in order to check that the ReloadToken re-registration works) 151 | int i = 0; 152 | while (i++ < 5) 153 | { 154 | // updates inside appsettings..json the property "Int": , --> "Int": "Protected:{}," 155 | var environmentAppSettings = File.ReadAllText($"appsettings.{Environment.GetEnvironmentVariable("DOTNETCORE_ENVIRONMENT")}.json"); 156 | environmentAppSettings = new Regex("\"Int\":.+?,").Replace(environmentAppSettings, $"\"Int\": \"{standardProtectConfigurationData.ProtectConfigurationValue($"\"Int\"", $"Protect:{{{new Random().Next(0, 1000000)}}}")}\","); 157 | File.WriteAllText($"appsettings.{Environment.GetEnvironmentVariable("DOTNETCORE_ENVIRONMENT")}.json", environmentAppSettings); 158 | 159 | // wait 5 seconds for the reload to take place, please check on this breakpoint that the value of "Int" property has changed in appSettings class and it is the same of appSettingsReloaded 160 | Thread.Sleep(5000); 161 | appSettings = optionsMonitor.CurrentValue; 162 | Console.WriteLine($"ConfigurationReloadLoop: appSettings Int {appSettings.Int}"); 163 | Debugger.Break(); 164 | } 165 | } 166 | } -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.ConsoleTest/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Fededim.Extensions.Configuration.Protected.ConsoleTest": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNETCORE_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.ConsoleTest/appsettings.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Int": 3, 3 | "DateTime": "2018-01-01T12:34:56.789", 4 | "IntArray": [ 4, 5, 6 ], 5 | "Nullable": { 6 | "DateTime": "Protect:{2016-10-01T20:34:56.789Z}", // protecting datetime 7 | "Double": "Protect:{123.456}", // protecting float 8 | "DoubleArray": [ "2.71", "Protect:{3.14}", "6.67", "Protect:{%customSubPurpose%}:{3.14}" ], // example of array element encryption and per configuration key subpurpose 9 | "Int": "Protect:{98765}", // protecting integer 10 | "Bool": "Protect:{true}" //protecting boolean 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.ConsoleTest/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Int": 1, 3 | "Double": 2.0, 4 | "Bool": false, 5 | "DateTime": "2016-01-01T12:34:56.789", 6 | "IntArray": [ 1, 2 ], 7 | "PlainTextJsonSpecialCharacters": "\\!*+?|{[()^$.#\u0022", 8 | "EncryptedJsonSpecialCharacters": "Protect:{\\!*+?|{[()^$.#\u0022}", // protecting encoded characters 9 | "Nullable": { 10 | "Int": null, 11 | "Double": null, // sample null value 12 | "Bool": null, 13 | "DateTime": null, 14 | "DoubleArray": [ 3.14, 2.71 ] 15 | }, 16 | "ConnectionStrings": { 17 | "PlainTextConnectionString": "Data Source=localhost\\SQLEXPRESS; Initial Catalog=DB name; User ID=sa; Password=pass1234; MultipleActiveResultSets=True;", 18 | "PartiallyEncryptedConnectionString": "Data Source=Protect:{(local)\\SECONDINSTANCE}; Initial Catalog=Protect:{Secret_Catalog}; User ID=Protect:{secret_user}; Password=Protect:{secret_password}; MultipleActiveResultSets=True;", 19 | "FullyEncryptedConnectionString": "Protect:{Data Source=server1\\THIRDINSTANCE; Initial Catalog=DB name; User ID=sa; Password=pass5678; MultipleActiveResultSets=True;}" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.ConsoleTest/appsettings.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | OtherProtect:{Xml Secret Key_\>!*+?|{[()^$.#} 4 | Xml Secret Key_\>!*+?|{[()^$.# 5 | 6 | true 7 | OtherProtect:{00:00:07} 8 | OtherProtect:{sUbPuRpOsE}:{00:00:07} 9 | 10 | 11 | 12 | Information 13 | OtherProtect:{Warning} 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.DataProtectionAPI/DataProtectionAPIProtectProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.DataProtection; 2 | using System; 3 | 4 | namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPI 5 | { 6 | /// 7 | /// The standard Microsoft DataProtectionAPI protect provider for Fededim.Extensions.Configuration.Protected, implementing the interface. 8 | /// 9 | public class DataProtectionAPIProtectProvider : IProtectProvider 10 | { 11 | public IDataProtector DataProtector { get; } 12 | 13 | 14 | /// 15 | /// The main constructor 16 | /// 17 | /// the interface obtained from Data Protection API 18 | public DataProtectionAPIProtectProvider(IDataProtector dataProtector) 19 | { 20 | DataProtector = dataProtector; 21 | } 22 | 23 | /// 24 | /// This methods create a new for supporting per configuration value encryption subkey (e.g. "subpurposes") 25 | /// 26 | /// the configuration key containing the encryption subkey 27 | /// the per configuration value encryption subkey 28 | /// a derived based on the parameter 29 | public IProtectProvider CreateNewProviderFromSubkey(String key, String subkey) 30 | { 31 | return new DataProtectionAPIProtectProvider(DataProtector.CreateProtector(subkey)); 32 | } 33 | 34 | 35 | /// 36 | /// This method decrypts an encrypted string 37 | /// 38 | /// the configuration key which needs to be decrypted 39 | /// the encrypted string to be decrypted 40 | /// the decrypted string 41 | public String Decrypt(String key, String encryptedValue) 42 | { 43 | return DataProtector.Unprotect(encryptedValue); 44 | } 45 | 46 | 47 | /// 48 | /// This method encrypts a plain-text string 49 | /// 50 | /// the configuration key which needs to be encrypted 51 | /// the plain-text string to be encrypted 52 | /// the encrypted string 53 | public String Encrypt(String key, String plainTextValue) 54 | { 55 | return DataProtector.Protect(plainTextValue); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.DataProtectionAPI/Fededim.Extensions.Configuration.Protected.DataProtectionAPI.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0;netstandard2.0;net462 5 | disable 6 | 7 | true 8 | 1.0.8 9 | Federico Di Marco <fededim@gmail.com> 10 | Fededim.Extensions.Configuration.Protected.DataProtectionAPI is the standard Microsoft DataProtectionAPI provider for the encryption/decryption of configuration values using Fededim.Extensions.Configuration.Protected. 11 | $([System.DateTime]::UtcNow.Year) 12 | true 13 | true 14 | true 15 | https://github.com/fededim/Fededim.Extensions.Configuration.Protected 16 | README.md 17 | LICENSE.txt 18 | $(AssemblyName) 19 | configuration;data;protection;appsettings;json;encrypted 20 | true 21 | true 22 | snupkg 23 | 24 | v1.0.0 25 | - Initial release: extracted Microsoft Data Protection API dependencies from Fededim.Extensions.Configuration.Protected version 1.0.11 into DataProtectionAPIProtectConfigurationData and DataProtectionAPIConfigurationBuilderExtensions and implemented DataProtectionAPIProtectProvider using the standard interface IProtectProvider. 26 | 27 | v1.0.1 28 | - Refinement: Just renamed from FileProtect... classes to ProtectFile... for naming consistency among code 29 | - Dependency: requires at least Fededim.Extensions.Configuration.Protected version 1.0.13 30 | 31 | v1.0.2 32 | - Dependency: requires at least Fededim.Extensions.Configuration.Protected version 1.0.14 33 | - Breaking change: removed anymore needed DataProtectionAPIConfigurationBuilderExtensions 34 | - Refinement: adapted DataProtectionAPIProtectConfigurationData according to the new IProtectProviderConfigurationData, streamlined its constructors and added missing comments 35 | - Improvement: added unit testing project Fededim.Extensions.Configuration.Protected.DataProtectionAPITest with an extensive test on all existing configuration providers 36 | 37 | v1.0.3 38 | - Dependency: requires at least Fededim.Extensions.Configuration.Protected version 1.0.15 39 | - Improvement: improved testing output, timing all phases with a Stopwatch and made plaintext-decrypted value comparison unbelievably fast using the Data dictionary safe hacky method 40 | 41 | v1.0.4 42 | - Dependency: requires at least Fededim.Extensions.Configuration.Protected version 1.0.16 43 | 44 | v1.0.5 45 | - Dependency: requires at least Fededim.Extensions.Configuration.Protected version 1.0.17 46 | 47 | v1.0.6 48 | - Dependency: requires at least Fededim.Extensions.Configuration.Protected version 1.0.18 49 | 50 | v1.0.7 51 | - Dependency: requires at least Fededim.Extensions.Configuration.Protected version 1.0.19 52 | - Update all Nuget packages to latest version 53 | - Updated project to net8.0 due to incoming net6.0 EOL 54 | 55 | v1.0.8 56 | - Dependency: requires at least Fededim.Extensions.Configuration.Protected version 1.0.20 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.DataProtectionAPI/license/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Federico Di Marco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/DataProtectionAPIProtectProviderTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; 2 | using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; 3 | using Microsoft.AspNetCore.DataProtection; 4 | using System; 5 | using Fededim.Extensions.Configuration.Protected.DataProtectionAPI; 6 | using System.IO; 7 | using Xunit.Abstractions; 8 | using Xunit; 9 | 10 | namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPITest 11 | { 12 | public abstract class DataProtectionAPIProtectProviderBaseTest : ProtectedConfigurationBuilderTest, IClassFixture 13 | { 14 | public DataProtectionAPIProtectProviderBaseTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper, IProtectProviderConfigurationData protectProviderConfigurationData) 15 | : base(context, testOutputHelper, protectProviderConfigurationData) 16 | { 17 | } 18 | 19 | protected static void ConfigureDataProtection(IDataProtectionBuilder builder) 20 | { 21 | builder.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration 22 | { 23 | EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, 24 | ValidationAlgorithm = ValidationAlgorithm.HMACSHA256, 25 | 26 | }).SetDefaultKeyLifetime(TimeSpan.FromDays(365 * 15)).PersistKeysToFileSystem(new DirectoryInfo("..\\..\\..\\Keys")); 27 | } 28 | 29 | 30 | 31 | protected override string TrimRegexCharsFromSubpurpose(string subpurpose) 32 | { 33 | return subpurpose.Replace(":", "*").Replace("}", "|"); 34 | } 35 | 36 | 37 | protected override string TrimRegexCharsFromProtectData(string value) 38 | { 39 | return value.Replace("}", "|"); 40 | } 41 | } 42 | 43 | public class DataProtectionAPIProtectProviderTest : DataProtectionAPIProtectProviderBaseTest 44 | { 45 | public DataProtectionAPIProtectProviderTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper) : base(context, testOutputHelper, new DataProtectionAPIProtectConfigurationData(ConfigureDataProtection)) 46 | { 47 | } 48 | } 49 | 50 | 51 | 52 | public class ReverseChainedDataProtectionAPIProtectProviderTest : DataProtectionAPIProtectProviderBaseTest 53 | { 54 | public ReverseChainedDataProtectionAPIProtectProviderTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper) : base(context, testOutputHelper, new DataProtectionAPIProtectConfigurationData(ConfigureDataProtection).Chain(pp => new ReverseChainedProtectProvider(pp))) 55 | { 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/ExtendedStopwatch.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using Xunit.Abstractions; 4 | 5 | namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPITest 6 | { 7 | public class ExtendedStopwatch : Stopwatch 8 | { 9 | private static Func EmptyDebugInfo = () => String.Empty; 10 | 11 | protected Func DebugInfo { get; set; } 12 | protected ITestOutputHelper TestOutputHelper { get; set; } 13 | 14 | public ExtendedStopwatch() : base() 15 | { 16 | } 17 | 18 | 19 | public ExtendedStopwatch(bool start=false,Func debugInfo = null, ITestOutputHelper testOutputHelper = null) : this() 20 | { 21 | DebugInfo = debugInfo; 22 | TestOutputHelper = testOutputHelper; 23 | 24 | if (start) 25 | Start(); 26 | } 27 | 28 | 29 | public void Step(String stepName) 30 | { 31 | 32 | Stop(); 33 | 34 | var message = $"{DateTime.Now}: {stepName} duration {(ElapsedMilliseconds<10000?$"{ElapsedMilliseconds}ms":Elapsed.ToString())} {((DebugInfo != null) ? $"({DebugInfo()})" : String.Empty)}"; 35 | 36 | if (TestOutputHelper != null) 37 | TestOutputHelper.WriteLine(message); 38 | else 39 | Debug.WriteLine(message); 40 | 41 | Restart(); 42 | } 43 | 44 | 45 | 46 | 47 | public void Lap(String stepName) 48 | { 49 | 50 | Stop(); 51 | 52 | var message = $"{DateTime.Now}: {stepName} duration {Elapsed} ({((DebugInfo != null) ? DebugInfo() : String.Empty)})"; 53 | 54 | if (TestOutputHelper != null) 55 | TestOutputHelper.WriteLine(message); 56 | else 57 | Debug.WriteLine(message); 58 | 59 | Start(); 60 | } 61 | 62 | 63 | 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/Fededim.Extensions.Configuration.Protected.DataProtectionAPITest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0;net462 5 | 6 | false 7 | true 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | runtime; build; native; contentfiles; analyzers; buildtransitive 24 | all 25 | 26 | 27 | runtime; build; native; contentfiles; analyzers; buildtransitive 28 | all 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | @(VSTestLogger) 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/PassthroughProtectProviderTest.cs: -------------------------------------------------------------------------------- 1 | using Xunit.Abstractions; 2 | using Xunit; 3 | 4 | namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPITest 5 | { 6 | 7 | public abstract class PassthroughProtectProviderBaseTest : ProtectedConfigurationBuilderTest, IClassFixture 8 | { 9 | public PassthroughProtectProviderBaseTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper, IProtectProviderConfigurationData protectProviderConfigurationData) 10 | : base(context, testOutputHelper, protectProviderConfigurationData) 11 | { 12 | } 13 | 14 | 15 | protected override string TrimRegexCharsFromSubpurpose(string subpurpose) 16 | { 17 | return subpurpose.Replace(":", "*").Replace("}", "|"); 18 | } 19 | 20 | 21 | protected override string TrimRegexCharsFromProtectData(string value) 22 | { 23 | return value.Replace("}", "|"); 24 | } 25 | } 26 | 27 | 28 | public class PassthroughProtectProviderTest : DataProtectionAPIProtectProviderBaseTest 29 | { 30 | public PassthroughProtectProviderTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper) : base(context, testOutputHelper, PassthroughProtectConfigurationData.CreateInstance) 31 | { 32 | } 33 | } 34 | 35 | 36 | public class ReverseChainedPassthroughProtectProviderTest : DataProtectionAPIProtectProviderBaseTest 37 | { 38 | public ReverseChainedPassthroughProtectProviderTest(ProtectedConfigurationBuilderTestFixture context, ITestOutputHelper testOutputHelper) : base(context, testOutputHelper, PassthroughProtectConfigurationData.CreateInstance.Chain(pp => new ReverseChainedProtectProvider(pp))) 39 | { 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.DataProtectionAPITest/ReverseChainedProtectProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Fededim.Extensions.Configuration.Protected.DataProtectionAPITest 8 | { 9 | public class ReverseChainedProtectProvider : IProtectProvider 10 | { 11 | public IProtectProvider ProtectProvider { get; protected set; } 12 | 13 | 14 | public ReverseChainedProtectProvider(IProtectProvider protectProvider) 15 | { 16 | ProtectProvider = protectProvider; 17 | } 18 | 19 | 20 | protected string Reverse(string text) 21 | { 22 | if (text == null) 23 | return null; 24 | 25 | char[] array = text.ToCharArray(); 26 | Array.Reverse(array); 27 | return new String(array); 28 | } 29 | 30 | 31 | public IProtectProvider CreateNewProviderFromSubkey(string key, string subkey) 32 | { 33 | return new ReverseChainedProtectProvider(ProtectProvider.CreateNewProviderFromSubkey(key, subkey)); 34 | } 35 | 36 | 37 | public string Decrypt(string key, string encryptedValue) 38 | { 39 | return ProtectProvider.Decrypt(key, Reverse(encryptedValue)); 40 | } 41 | 42 | 43 | public string Encrypt(string key, string plainTextValue) 44 | { 45 | return Reverse(ProtectProvider.Encrypt(key, plainTextValue)); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.6.33829.357 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fededim.Extensions.Configuration.Protected", "Fededim.Extensions.Configuration.Protected\Fededim.Extensions.Configuration.Protected.csproj", "{6C4745A9-A816-44C8-8817-6C12E8A61BCC}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fededim.Extensions.Configuration.Protected.ConsoleTest", "Fededim.Extensions.Configuration.Protected.ConsoleTest\Fededim.Extensions.Configuration.Protected.ConsoleTest.csproj", "{DA62A52E-376A-40AF-A276-151063553B88}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fededim.Extensions.Configuration.Protected.DataProtectionAPI", "Fededim.Extensions.Configuration.Protected.DataProtectionAPI\Fededim.Extensions.Configuration.Protected.DataProtectionAPI.csproj", "{BA2781C3-F027-490C-A01D-C52CCB0FE8A0}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fededim.Extensions.Configuration.Protected.DataProtectionAPITest", "Fededim.Extensions.Configuration.Protected.DataProtectionAPITest\Fededim.Extensions.Configuration.Protected.DataProtectionAPITest.csproj", "{898CD43E-C661-4CAF-BE48-293B2A4E0726}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {6C4745A9-A816-44C8-8817-6C12E8A61BCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {6C4745A9-A816-44C8-8817-6C12E8A61BCC}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {6C4745A9-A816-44C8-8817-6C12E8A61BCC}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {6C4745A9-A816-44C8-8817-6C12E8A61BCC}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {DA62A52E-376A-40AF-A276-151063553B88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {DA62A52E-376A-40AF-A276-151063553B88}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {DA62A52E-376A-40AF-A276-151063553B88}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {DA62A52E-376A-40AF-A276-151063553B88}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {BA2781C3-F027-490C-A01D-C52CCB0FE8A0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {BA2781C3-F027-490C-A01D-C52CCB0FE8A0}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {BA2781C3-F027-490C-A01D-C52CCB0FE8A0}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {BA2781C3-F027-490C-A01D-C52CCB0FE8A0}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {898CD43E-C661-4CAF-BE48-293B2A4E0726}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {898CD43E-C661-4CAF-BE48-293B2A4E0726}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {898CD43E-C661-4CAF-BE48-293B2A4E0726}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {898CD43E-C661-4CAF-BE48-293B2A4E0726}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {806820EF-822C-4677-8F9B-97A8868DE166} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected/Fededim.Extensions.Configuration.Protected.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net8.0;netstandard2.0;net462 5 | disable 6 | 7 | true 8 | 1.0.20 9 | Federico Di Marco <fededim@gmail.com> 10 | Fededim.Extensions.Configuration.Protected is an improved ConfigurationBuilder which allows partial or full encryption of configuration values stored inside any possible ConfigurationSource and fully integrated in the ASP.NET Core architecture. Basically, it implements a custom ConfigurationBuilder and a custom ConfigurationProvider defining a custom tokenization tag which whenever found decrypts the enclosed encrypted data using a provider implementing a standard interface IProtectProvider. 11 | $([System.DateTime]::UtcNow.Year) 12 | true 13 | true 14 | true 15 | https://github.com/fededim/Fededim.Extensions.Configuration.Protected 16 | README.md 17 | LICENSE.txt 18 | $(AssemblyName) 19 | configuration;data;protection;appsettings;json;encrypted 20 | true 21 | true 22 | snupkg 23 | 24 | v1.0.0 25 | - Initial commit: it does not support re-decryption on configuration reload 26 | 27 | v1.0.1 28 | - Added support for automatic re-decryption on configuration reload if underlying IConfigurationProvider supports it. 29 | - Cleaned code and added documentation on most methods. 30 | 31 | v1.0.2 32 | - Added more comments on code 33 | - Enabled SourceLink support to GitHub for debugging 34 | 35 | v1.0.3 36 | - SourceLink bugfix: removed SourceRevisionId tag in csproj 37 | 38 | v1.0.4 39 | - Commented initial unneeded code inside CreateProtectedConfigurationProvider method of ProtectedConfigurationBuilder 40 | 41 | v1.0.5 42 | - Commented other initial unneeded code inside CreateProtectedConfigurationProvider method of ProtectedConfigurationBuilder 43 | 44 | v1.0.6 45 | - Bugfix: the ProtectFiles method simply read the raw files which need to be encrypted using File.ReadAllText, whereas it should also decode the file according to its format. By default two decoders are now provided for both JSON and XML files and an extension point (FilesDecoding public property) if additional formats must be supported. 46 | 47 | v1.0.7 48 | - Improvement: the ProtectedConfigurationProvider.RegisterReloadCallback now uses the framework standard static utility class ChangeToken.OnChange to register the underlying provider configuration changes 49 | - Improvement: added two additional public static properties inside ConfigurationBuilderExtensions in order to allow them to be referenced if needed: JsonDecodingFunction and XmlDecodingFunction 50 | 51 | v1.0.8 52 | - Improvement: introduced FileProtectProcessor and IFileProtectProcessor interface for implementing a custom file protect processor used to read, encrypt and return the encrypted file as string. Json, Xml and Raw processors are provided by default. 53 | - Improvement: added custom string parameter purposeString in ProtectedConfigurationBuilder constructor in order to specify a custom purpose string for encryption/decryption, besides the integer keyNumber parameter. 54 | - Improvement: added subPurpose optional part in DefaultProtectRegexString, DefaultProtectedRegexString and DefaultProtectedReplaceString in order to allow an optional per key purpose string override. 55 | - Improvement: added some data to json (one element in Nullable:DoubleArray of appsettings.development.json) and xml file (AutoRetryDelaySubPurpose under TransientFaultHandlingOptions) of Fededim.Extensions.Configuration.Protected.ConsoleTest in order to exemplify the per key purpose string override. 56 | 57 | v1.0.9 58 | - No changes, just a rebuild due to a misalignment with symbols. 59 | 60 | v1.0.10 61 | - Improvement: Allow the specification of JsonSerializationOptions for JsonFileProtectProcessor to tweak its settings (comments inside JSON files are now skipped by default) 62 | - Improvement: Allow the specification of LoadOptions and SaveOptions for XmlFileProtectProcessor to tweak its settings 63 | 64 | v1.0.11 65 | - Improvement: Implemented additional JsonWithCommentsFileProtectProcessor ("hacky" optional FilerProtectProcessor) to allow the preservation of JSON comments when encrypting files using ProtectFiles 66 | - Improvement: Implemented UseJsonWithCommentsFileProtectOption extension method to replace JsonFileProtectProcessor (active by default for compliance with JSON standard of System.Text.Json) with JsonWithCommentsFileProtectProcessor 67 | 68 | v1.0.12 69 | - Improvement: Allow encryption/decryption to be pluggable with providers using a new interface IProtectProvider. Therefore all DataProtectionAPI dependencies have been moved to a new package Fededim.Extensions.Configuration.Protected.DataProtectionAPI, you can just use this one which requires Fededim.Extensions.Configuration.Protected. 70 | - Bugfix: Fixed a bug with the subPurpose section of the regexs which could lead to a greedy match instead of a lazy one. 71 | 72 | v1.0.13 73 | - Improvement: ProtectedConfigurationBuilder.CreateProtectedConfigurationProvider now raises an exception on invalid configuration instead of returning the original undecrypted provider. 74 | - Improvement: Removed duplicated and streamlined validations on configuration converting IProtectProviderConfigurationData.IsValid property to a method CheckConfigurationIsValid raising exception with the details of the errors. 75 | - Refinement: Just renamed from FileProtect... classes to ProtectFile... for naming consistency among code 76 | 77 | v1.0.14 78 | - Improvement: added both ProtectRegex and ProtectedReplaceString to abstract class IProtectProviderConfigurationData in order to specify all configuration in a single point 79 | - Breaking change: all ConfigurationBuilderExtensions.Protect... methods now extend the IProtectProviderConfigurationData abstract class instead of IProtectProvider interface, the parameters protectRegexString and protectedReplaceString have been removed since they are now specified inside IProtectProviderConfigurationData 80 | - Refinement: moved IConfigurationBuilder.WithProtectedConfigurationOptions inside Fededim.Extensions.Configuration.Protected.ConfigurationBuilderExtensions 81 | 82 | v1.0.15 83 | - Improvement: made child keys enumeration process unbelievably fast (if the provider is derived from ConfigurationProvider like all now existing providers, the child keys enumeration is now done with a safe hacky method accessing the Data dictionary through reflection which is unbelievably faster, otherwise the old method is used) 84 | 85 | v1.0.16 86 | - Bugfix: environment target was not passed on ProtectEnvironmentVariables while setting the encrypted variables 87 | 88 | v1.0.17 89 | - Refinement: made ProtectedConfigurationProvider.ProviderData property safer using as instead of a direct cast 90 | - Refinement: added virtual to various methods in order to allow extensibility 91 | 92 | v1.0.18 93 | - Security fix: updated System.Text.Json to 8.0.4 in order to fix the security issue CVE-2024-30105 94 | 95 | v1.0.19 96 | - Security fix: updated System.Text.Json to 8.0.5 in order to fix the security issue CVE-2024-43485 97 | - Updated project to net8.0 due to incoming net6.0 EOL 98 | - Removed obsolete projects Fededim.Extensions.Configuration.ProtectedJson and Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest from solution 99 | 100 | v1.0.20 101 | - Improved IProtectProvider interface by including also the key being encrypted / decrypted 102 | - Implemented a PassthroughProtectProvider and PassthroughProtectConfigurationData which does not encrypt / decrypt anything, useful for development and testing 103 | - Implemented a chain function in order to customize the behaviour of a IProtectProvider (e.g. try / catch to skip decryption exceptions, etc.) 104 | - Implemented ProtectFile method in ConfigurationBuilderExtensions to encrypt just a single file (it was missing) 105 | - Improved and added new tests for the chain function and the PassthroughProtectProvider, implemented a ProcessSafeRandomId for running tests in parallel 106 | - Update all NuGet packages to the latest version 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected/PassthroughProtectConfigurationData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Fededim.Extensions.Configuration.Protected 4 | { 5 | /// 6 | /// PassthroughProtectConfigurationData is the ProtectProviderConfigurationData class for the PassthroughProtectProvider 7 | /// 8 | public class PassthroughProtectConfigurationData : ProtectProviderConfigurationData 9 | { 10 | public static PassthroughProtectConfigurationData CreateInstance => new PassthroughProtectConfigurationData(); 11 | 12 | /// 13 | /// Main constructor for PassthroughProtectConfigurationData 14 | /// 15 | /// a regular expression which captures the data to be encrypted in a named group called protectData 16 | /// a regular expression which captures the data to be decrypted in a named group called protectedData 17 | /// a string replacement expression which captures the substitution which must be applied for transforming unencrypted tokenization into an encrypted tokenization 18 | /// if one of the input parameters is not well configured 19 | public PassthroughProtectConfigurationData(String protectRegexString = null, String protectedRegexString = null, String protectedReplaceString = null) 20 | : base(protectRegexString, protectedRegexString, protectedReplaceString, new PassthroughProtectProvider()) 21 | { 22 | 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected/PassthroughProtectProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Fededim.Extensions.Configuration.Protected 4 | { 5 | /// 6 | /// A passthrough protect provider for Fededim.Extensions.Configuration.Protected, implementing the interface. 7 | /// It does not encrypt or decrypt any entries, it leaves all the entries untouched, it can be used for development purposes. 8 | /// 9 | public class PassthroughProtectProvider : IProtectProvider 10 | { 11 | /// 12 | /// The main constructor 13 | /// 14 | public PassthroughProtectProvider() 15 | { 16 | } 17 | 18 | /// 19 | /// This methods create a new for supporting per configuration value encryption subkey (e.g. "subpurposes") 20 | /// 21 | /// the configuration key containing the encryption subkey 22 | /// the per configuration value encryption subkey 23 | /// a derived based on the parameter 24 | public IProtectProvider CreateNewProviderFromSubkey(String key, String subkey) 25 | { 26 | return this; 27 | } 28 | 29 | 30 | /// 31 | /// This method decrypts an encrypted string 32 | /// 33 | /// the configuration key which needs to be decrypted 34 | /// the encrypted string to be decrypted 35 | /// the decrypted string 36 | public String Decrypt(String key, String encryptedValue) 37 | { 38 | return encryptedValue; 39 | } 40 | 41 | 42 | /// 43 | /// This method encrypts a plain-text string 44 | /// 45 | /// the configuration key which needs to be encrypted 46 | /// the plain-text string to be encrypted 47 | /// the encrypted string 48 | public String Encrypt(String key, String plainTextValue) 49 | { 50 | return plainTextValue; 51 | } 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected/ProtectProviderConfigurationData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text.RegularExpressions; 4 | 5 | namespace Fededim.Extensions.Configuration.Protected 6 | { 7 | /// 8 | /// A standard interface which must implement by an encryption/decryption provider 9 | /// 10 | public interface IProtectProvider 11 | { 12 | /// 13 | /// This method encrypts a plain-text string 14 | /// 15 | /// the configuration key to be encrypted, it could be empty string in some cases 16 | /// the plain-text string to be encrypted 17 | /// the encrypted string or null if you do not want to encrypt it 18 | String Encrypt(String key, String plainTextValue); 19 | 20 | 21 | /// 22 | /// This method decrypts an encrypted string 23 | /// 24 | /// the configuration key to be decrypted 25 | /// the encrypted string to be decrypted 26 | /// the decrypted string 27 | String Decrypt(String key, String encryptedValue); 28 | 29 | 30 | /// 31 | /// This methods create a new for supporting per configuration value encryption subkey (e.g. "subpurposes") 32 | /// 33 | /// the configuration key to be encrypted, it could be empty string in some cases 34 | /// the per configuration value encryption subkey 35 | /// a derived based on the parameter 36 | IProtectProvider CreateNewProviderFromSubkey(String key, String subkey); 37 | } 38 | 39 | 40 | 41 | /// 42 | /// an abstract class for specifying the configuration data of the encryption/decryption provider 43 | /// 44 | public abstract class IProtectProviderConfigurationData 45 | { 46 | public const String DefaultProtectRegexString = "Protect(?(:{(?[^:}]+)})?):{(?.*?)}"; 47 | public const String DefaultProtectedRegexString = "Protected(?(:{(?[^:}]+)})?):{(?.*?)}"; 48 | public const String DefaultProtectedReplaceString = "Protected${subPurposePattern}:{${protectedData}}"; 49 | 50 | /// 51 | /// The actual provider performing the encryption/decryption, interface 52 | /// 53 | public IProtectProvider ProtectProvider { get; protected set; } 54 | 55 | /// 56 | /// a regular expression which captures the data to be decrypted in a named group called protectData 57 | /// 58 | public Regex ProtectedRegex { get; protected set; } 59 | 60 | 61 | /// 62 | /// a regular expression which captures the data to be encrypted in a named group called protectData 63 | /// 64 | public Regex ProtectRegex { get; protected set; } 65 | 66 | 67 | /// 68 | /// a string replacement expression which captures the substitution which must be applied for transforming unencrypted tokenization into an encrypted tokenization 69 | /// 70 | public String ProtectedReplaceString { get; protected set; } 71 | 72 | 73 | /// 74 | /// A helper overridable method for checking that the configuation data is valid (e.g. ProtectProvider is not null, ProtectedRegex and ProtectRegex contains both a regex group named protectedData) 75 | /// 76 | public virtual void CheckConfigurationIsValid() 77 | { 78 | ProtectRegex = ProtectRegex ?? new Regex(DefaultProtectRegexString); 79 | if (!ProtectRegex.GetGroupNames().Contains("protectData")) 80 | throw new ArgumentException("ProtectRegex must contain a group named protectedData!", nameof(ProtectRegex)); 81 | 82 | ProtectedRegex = ProtectedRegex ?? new Regex(DefaultProtectedRegexString); 83 | if (!ProtectedRegex.GetGroupNames().Contains("protectedData")) 84 | throw new ArgumentException("ProtectedRegex must contain a group named protectedData!", nameof(ProtectedRegex)); 85 | 86 | ProtectedReplaceString = !String.IsNullOrEmpty(ProtectedReplaceString) ? ProtectedReplaceString : DefaultProtectedReplaceString; 87 | if (!ProtectedReplaceString.Contains("${protectedData}")) 88 | throw new ArgumentException("ProtectedReplaceString must contain ${protectedData}!", nameof(ProtectedReplaceString)); 89 | 90 | if (ProtectProvider == null) 91 | throw new ArgumentException("ProtectProvider must not be null!", nameof(ProtectProvider)); 92 | } 93 | 94 | 95 | /// 96 | /// A helper overridable method which allows you to chain multiple IProtectProvider 97 | /// 98 | /// a chain function which creates a new IProtectProvider reusing an existing IProtectProvider 99 | /// a new IProtectProviderConfigurationData 100 | public virtual IProtectProviderConfigurationData Chain(Func chainFunction) 101 | { 102 | return new ProtectProviderConfigurationData(ProtectRegex.ToString(), ProtectedRegex.ToString(), ProtectedReplaceString, chainFunction(ProtectProvider)); 103 | } 104 | 105 | } 106 | 107 | 108 | 109 | /// 110 | /// ProtectedConfigurationData is a custom data structure which stores all configuration options needed by ProtectedConfigurationBuilder and ProtectConfigurationProvider 111 | /// 112 | public class ProtectProviderConfigurationData : IProtectProviderConfigurationData 113 | { 114 | /// 115 | /// Main constructor 116 | /// 117 | /// a regular expression which captures the data to be encrypted in a named group called protectData 118 | /// a regular expression which captures the data to be decrypted in a named group called protectData 119 | /// a string replacement expression which captures the substitution which must be applied for transforming unencrypted tokenization into an encrypted tokenization 120 | /// an IProtectProvider interface obtained from a one of the supported providers 121 | /// thrown if the Regex does not containg a group named protectedData 122 | public ProtectProviderConfigurationData(String protectRegexString, String protectedRegexString, String protectedReplaceString, IProtectProvider protectProvider) 123 | { 124 | if (!String.IsNullOrEmpty(protectRegexString)) 125 | ProtectRegex = new Regex(protectRegexString); 126 | 127 | if (!String.IsNullOrEmpty(protectedRegexString)) 128 | ProtectedRegex = new Regex(protectedRegexString); 129 | 130 | ProtectedReplaceString = protectedReplaceString; 131 | ProtectProvider = protectProvider; 132 | 133 | // check resulting configuration is valid, if it is not valid we raise an exception in order to be notified that something is wrong 134 | CheckConfigurationIsValid(); 135 | } 136 | 137 | 138 | /// 139 | /// A static helper method which calculates the merge of the global and local protected configuration data. The resulting configuration is checked for validity inside constructor. 140 | /// 141 | /// the global configuration data 142 | /// the local configuration data 143 | /// a new IProtectProviderConfigurationData 144 | public static IProtectProviderConfigurationData Merge(IProtectProviderConfigurationData global, IProtectProviderConfigurationData local) 145 | { 146 | if (local == null) 147 | return global; 148 | 149 | if (global == null) 150 | return local; 151 | 152 | // perform merge 153 | var result = new ProtectProviderConfigurationData(local.ProtectRegex?.ToString() ?? global.ProtectRegex?.ToString(), local.ProtectedRegex?.ToString() ?? global.ProtectedRegex?.ToString(), local.ProtectedReplaceString ?? global.ProtectedReplaceString, local.ProtectProvider ?? global.ProtectProvider); 154 | 155 | return result; 156 | } 157 | } 158 | 159 | } 160 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected/ProtectedConfigurationBuilder.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using Microsoft.Extensions.Configuration; 4 | 5 | namespace Fededim.Extensions.Configuration.Protected 6 | { 7 | /// 8 | /// IProtectedConfigurationBuilder derives from and adds a single method WithProtectedConfigurationOptions used to override the ProtectedConfigurationOptions for a particular provider (e.g. the last one added) 9 | /// 10 | public interface IProtectedConfigurationBuilder : IConfigurationBuilder 11 | { 12 | /// 13 | /// WithProtectedConfigurationOptions is used to override the ProtectedConfigurationOptions for a particular provider (e.g. the last one added) 14 | /// 15 | /// the local configuration data implemeting the interface 16 | /// The interface for method chaining 17 | IConfigurationBuilder WithProtectedConfigurationOptions(IProtectProviderConfigurationData protectProviderLocalConfigurationData); 18 | } 19 | 20 | 21 | 22 | /// 23 | /// ProtectedConfigurationBuilder is an improved which allows partial or full encryption of configuration values stored inside any possible ConfigurationSource and fully integrated in the ASP.NET Core architecture. 24 | /// 25 | public class ProtectedConfigurationBuilder : IProtectedConfigurationBuilder 26 | { 27 | /// 28 | /// A property used to store the global configuration data interface 29 | /// 30 | protected IProtectProviderConfigurationData ProtectedProviderGlobalConfigurationData { get; } 31 | 32 | /// 33 | /// A dictionary property used to store the local configuration data overriding the global one, interface 34 | /// 35 | protected IDictionary ProtectProviderLocalConfigurationData { get; } = new Dictionary(); 36 | 37 | 38 | protected readonly List _sources = new List(); 39 | 40 | 41 | /// 42 | /// This is the only constructor which takes in input the global configuration data specifying the regex and the encryption/decryption provider 43 | /// 44 | /// the global configuration data specifying the regex and the encryption/decryption provider 45 | public ProtectedConfigurationBuilder(IProtectProviderConfigurationData protectedProviderGlobalConfigurationData) 46 | { 47 | protectedProviderGlobalConfigurationData.CheckConfigurationIsValid(); 48 | ProtectedProviderGlobalConfigurationData = protectedProviderGlobalConfigurationData; 49 | } 50 | 51 | 52 | 53 | /// 54 | /// Returns the sources used to obtain configuration values. 55 | /// 56 | public IList Sources => _sources; 57 | 58 | 59 | 60 | /// 61 | /// Gets a key/value collection that can be used to share data between the 62 | /// and the registered s. 63 | /// 64 | public IDictionary Properties { get; } = new Dictionary(); 65 | 66 | 67 | 68 | 69 | /// 70 | /// Adds a new configuration source. 71 | /// 72 | /// The configuration source to add. 73 | /// The same . 74 | public virtual IConfigurationBuilder Add(IConfigurationSource source) 75 | { 76 | if (source == null) 77 | throw new ArgumentNullException(nameof(source)); 78 | 79 | _sources.Add(source); 80 | return this; 81 | } 82 | 83 | 84 | 85 | /// 86 | /// Builds an with keys and values from the set of configuration sources registered in . 87 | /// 88 | /// An with keys and values from the providers generated by registered configuration sources. 89 | public virtual IConfigurationRoot Build() 90 | { 91 | var providers = new List(); 92 | foreach (IConfigurationSource source in _sources) 93 | { 94 | IConfigurationProvider provider = source.Build(this); 95 | 96 | // if we have a custom configuration we move the index from the ConfigurationSource object to the newly created ConfigurationProvider object 97 | ProtectProviderLocalConfigurationData.TryGetValue(source.GetHashCode(), out var protectedConfigurationData); 98 | if (protectedConfigurationData != null) 99 | { 100 | ProtectProviderLocalConfigurationData[provider.GetHashCode()] = protectedConfigurationData; 101 | ProtectProviderLocalConfigurationData.Remove(source.GetHashCode()); 102 | } 103 | 104 | providers.Add(CreateProtectedConfigurationProvider(provider)); 105 | } 106 | return new ConfigurationRoot(providers); 107 | } 108 | 109 | 110 | 111 | /// 112 | /// WithProtectedConfigurationOptions is a helper method used to override the ProtectedGlobalConfigurationData for a particular provider (e.g. the last one added) 113 | /// 114 | /// the local configuration data overriding the global one, interface 115 | /// The interface for method chaining 116 | IConfigurationBuilder IProtectedConfigurationBuilder.WithProtectedConfigurationOptions(IProtectProviderConfigurationData protectProviderLocalConfigurationData) 117 | { 118 | protectProviderLocalConfigurationData.CheckConfigurationIsValid(); 119 | ProtectProviderLocalConfigurationData[Sources[Sources.Count - 1].GetHashCode()] = protectProviderLocalConfigurationData; 120 | 121 | return this; 122 | } 123 | 124 | 125 | 126 | /// 127 | /// CreateProtectedConfigurationProvider creates a new ProtectedConfigurationProvider using the composition approach 128 | /// 129 | /// an existing IConfigurationProvider to instrument in order to perform the decryption of the encrypted keys 130 | /// a newer decrypted if we have a valid protected configuration data, otherwise it returns the existing original undecrypted provider 131 | protected virtual IConfigurationProvider CreateProtectedConfigurationProvider(IConfigurationProvider provider) 132 | { 133 | // this code is an initial one of when I was thinking of casting IConfigurationProvider to ConfigurationProvider (all MS classes derive from this one) 134 | // in order to retrieve all configuration keys inside DecryptChildKeys using the Data property (through reflection since it is protected) without using the recursive "hack" of GetChildKeys 135 | // it has been commented because it is not needed anymore, but I keep it as workaround for accessing all configuration keys just in case MS changes the implementation of GetChildKeys "forbidding" the actual way 136 | //var providerType = provider.GetType(); 137 | 138 | //if (!providerType.IsSubclassOf(typeof(ConfigurationProvider))) 139 | // return provider; 140 | 141 | // we merge ProtectedProviderGlobalConfigurationData and ProtectProviderLocalConfigurationData 142 | var actualProtectedConfigurationData = ProtectProviderLocalConfigurationData.ContainsKey(provider.GetHashCode()) ? ProtectProviderConfigurationData.Merge(ProtectedProviderGlobalConfigurationData, ProtectProviderLocalConfigurationData[provider.GetHashCode()]) : ProtectedProviderGlobalConfigurationData; 143 | 144 | // we use composition to perform decryption of all provider values 145 | return new ProtectedConfigurationProvider(provider, actualProtectedConfigurationData); 146 | } 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected/ProtectedConfigurationProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.Primitives; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Diagnostics; 7 | using System.Threading; 8 | using System.Reflection; 9 | using System.Collections.ObjectModel; 10 | 11 | namespace Fededim.Extensions.Configuration.Protected 12 | { 13 | /// 14 | /// ProtectedConfigurationProvider is a custom which is actually responsible for real time decryption of configuration values for any existing or even future ConfigurationProviders. It uses the composition pattern. 15 | /// 16 | [DebuggerDisplay("Provider = Protected{Provider}")] 17 | public class ProtectedConfigurationProvider : IConfigurationProvider, IDisposable 18 | { 19 | protected IConfigurationProvider Provider { get; } 20 | protected IProtectProviderConfigurationData ProtectProviderConfigurationData { get; } 21 | 22 | protected ConfigurationReloadToken ReloadToken; 23 | 24 | protected IDisposable ProviderReloadTokenRegistration { get; set; } 25 | 26 | 27 | public ProtectedConfigurationProvider(IConfigurationProvider provider, IProtectProviderConfigurationData protectedConfigurationData) 28 | { 29 | Provider = provider; 30 | ProtectProviderConfigurationData = protectedConfigurationData; 31 | 32 | RegisterReloadCallback(); 33 | } 34 | 35 | 36 | 37 | /// 38 | /// Registers a reload callback which redecrypts all values if the underlying IConfigurationProvider supports it 39 | /// 40 | protected void RegisterReloadCallback() 41 | { 42 | // check if underlying provider supports reloading 43 | if (Provider.GetReloadToken() != null) 44 | { 45 | // Create our reload token 46 | ReloadToken = new ConfigurationReloadToken(); 47 | 48 | // registers Provider on Change event using framework static utility method ChangeToken.OnChange in order to be notified of configuration reload and redecrypts subsequently the needed keys 49 | ProviderReloadTokenRegistration = ChangeToken.OnChange(() => Provider.GetReloadToken(), (configurationProvider) => 50 | { 51 | var protectedConfigurationProvider = configurationProvider as ProtectedConfigurationProvider; 52 | 53 | // redecrypts all needed keys 54 | protectedConfigurationProvider.DecryptChildKeys(); 55 | 56 | // notifies all subscribes 57 | OnReload(); 58 | }, this); 59 | } 60 | } 61 | 62 | 63 | 64 | /// 65 | /// Dispatches all the callbacks waiting for the reload event from this configuration provider (and creates a new ReloadToken) 66 | /// 67 | protected virtual void OnReload() 68 | { 69 | ConfigurationReloadToken previousToken = Interlocked.Exchange(ref ReloadToken, new ConfigurationReloadToken()); 70 | previousToken.OnReload(); 71 | } 72 | 73 | 74 | 75 | 76 | /// 77 | /// Static PropertyInfo of protected property Data of Microsoft.Extensions.Configuration.ConfigurationProvider class (even though it is protected and not available here, you can use reflection in order to retrieve its value) 78 | /// 79 | public static PropertyInfo ConfigurationProviderDataProperty = typeof(ConfigurationProvider).GetProperty("Data", BindingFlags.NonPublic | BindingFlags.Instance); 80 | 81 | 82 | 83 | /// 84 | /// Hacky and fastest, tough safe method which gives access to the provider Data dictionary in readonly mode (it could be null in the future or for other providers not deriving from ConfigurationProvider, be sure to always check that it is not null!) 85 | /// 86 | public IReadOnlyDictionary ProviderDataReadOnly 87 | { 88 | get 89 | { 90 | IDictionary providerData = ProviderData; 91 | 92 | if (providerData != null) 93 | return new ReadOnlyDictionary(providerData); 94 | 95 | return null; 96 | } 97 | } 98 | 99 | 100 | 101 | 102 | /// 103 | /// Hacky and fastest, tough safe method which gives access to the provider Data dictionary (it could be null in the future or for other providers not deriving from ConfigurationProvider, be sure to always check that it is not null!) 104 | /// 105 | protected IDictionary ProviderData 106 | { 107 | get 108 | { 109 | IDictionary providerData = null; 110 | 111 | if (Provider is ConfigurationProvider && ConfigurationProviderDataProperty != null) 112 | providerData = ConfigurationProviderDataProperty.GetValue(Provider) as IDictionary; 113 | 114 | return providerData; 115 | } 116 | } 117 | 118 | 119 | 120 | /// 121 | /// This is a helper method actually responsible for the decryption of all configuration values. It decrypts all values using just IConfigurationBuilder interface methods so it should work on any existing or even future IConfigurationProvider

122 | /// Note: unluckily there Data dictionary property of ConfigurationProvider is not exposed on the interface IConfigurationProvider, but we can manage to get all keys by using the GetChildKeys methods, look at its implementation

123 | /// The only drawback of this method is that it returns the child keys of the level of the hierarchy specified by the parentPath parameter (it's at line 84 in MS source code "Segment(kv.Key, parentPath.Length + 1)" )
124 | /// So you have to use a recursive function to gather all existing keys and also to issue a distinct due to the way the GetChildKeys method has been implemented
125 | ///
126 | /// 127 | protected void DecryptChildKeys(String parentPath = null) 128 | { 129 | IDictionary dataProperty; 130 | 131 | // this is a hacky yet safe way to speed up key enumeration 132 | // we access the Data dictionary of ConfigurationProvider using reflection avoiding enumerating all keys with recursive function 133 | // the speed improvement is more than 3000 times! 134 | if (((dataProperty = ProviderData) != null)) 135 | { 136 | foreach (var key in dataProperty.Keys.ToList()) 137 | { 138 | if (!String.IsNullOrEmpty(dataProperty[key])) 139 | Provider.Set(key, ProtectProviderConfigurationData.ProtectedRegex.Replace(dataProperty[key], me => 140 | { 141 | 142 | var subPurposePresent = !String.IsNullOrEmpty(me.Groups["subPurpose"]?.Value); 143 | 144 | IProtectProvider protectProvider = ProtectProviderConfigurationData.ProtectProvider; 145 | 146 | if (subPurposePresent) 147 | protectProvider = protectProvider.CreateNewProviderFromSubkey(key, me.Groups["subPurpose"].Value); 148 | 149 | return protectProvider.Decrypt(key, me.Groups["protectedData"].Value); 150 | })); 151 | } 152 | } 153 | else 154 | { 155 | foreach (var key in Provider.GetChildKeys(new List(), parentPath).Distinct()) 156 | { 157 | var fullKey = parentPath != null ? $"{parentPath}:{key}" : key; 158 | if (Provider.TryGet(fullKey, out var value)) 159 | { 160 | if (!String.IsNullOrEmpty(value)) 161 | Provider.Set(fullKey, ProtectProviderConfigurationData.ProtectedRegex.Replace(value, me => 162 | { 163 | 164 | var subPurposePresent = !String.IsNullOrEmpty(me.Groups["subPurpose"]?.Value); 165 | 166 | IProtectProvider protectProvider = ProtectProviderConfigurationData.ProtectProvider; 167 | 168 | if (subPurposePresent) 169 | protectProvider = protectProvider.CreateNewProviderFromSubkey(fullKey, me.Groups["subPurpose"].Value); 170 | 171 | return protectProvider.Decrypt(fullKey, me.Groups["protectedData"].Value); 172 | })); 173 | } 174 | else DecryptChildKeys(fullKey); 175 | } 176 | } 177 | } 178 | 179 | 180 | 181 | /// 182 | /// Returns our reload token 183 | /// 184 | /// the 185 | public IChangeToken GetReloadToken() 186 | { 187 | return ReloadToken; 188 | } 189 | 190 | 191 | 192 | /// 193 | /// Calls the underlying provider Load method in order to load configuration values and then decrypts them by calling DecryptChildKeys helper method 194 | /// 195 | public virtual void Load() 196 | { 197 | Provider.Load(); 198 | 199 | // call DecryptChildKeys after Load 200 | DecryptChildKeys(); 201 | } 202 | 203 | 204 | 205 | 206 | /// 207 | /// Calls the underlying provider GetChildKeys method 208 | /// 209 | /// 210 | /// 211 | /// the child keys. 212 | 213 | public IEnumerable GetChildKeys(IEnumerable earlierKeys, String parentPath) 214 | { 215 | return Provider.GetChildKeys(earlierKeys, parentPath); 216 | } 217 | 218 | 219 | 220 | /// 221 | /// Calls the underlying provider Set method 222 | /// 223 | /// the configuration key to update 224 | /// the value to be set in the configuration key 225 | public void Set(String key, String value) 226 | { 227 | Provider.Set(key, value); 228 | } 229 | 230 | 231 | 232 | /// 233 | /// Calls the underlying provider TryGet method 234 | /// 235 | /// the configuration key to retrieve 236 | /// the retrieved value of the configuration key 237 | /// True if a value for the specified key was found, otherwise false. 238 | public bool TryGet(String key, out String value) 239 | { 240 | return Provider.TryGet(key, out value); 241 | } 242 | 243 | 244 | 245 | /// 246 | /// Disposes the potential provider reload token (when supported) 247 | /// 248 | public void Dispose() 249 | { 250 | ProviderReloadTokenRegistration?.Dispose(); 251 | } 252 | } 253 | } -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.Protected/license/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Federico Di Marco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest 8 | { 9 | public class NullableSettings 10 | { 11 | public Int32? Int { get; set; } 12 | public bool? Bool { get; set; } 13 | public Double? Double { get; set; } 14 | public DateTime? DateTime { get; set; } 15 | public Double[] DoubleArray { get; set; } 16 | public NullableSettings() 17 | { 18 | DoubleArray = new Double[0]; 19 | } 20 | 21 | } 22 | 23 | 24 | 25 | public class AppSettings 26 | { 27 | public Int32 Int { get; set; } 28 | public bool Bool { get; set; } 29 | public Double Double { get; set; } 30 | public DateTime DateTime { get; set; } 31 | public Int32[] IntArray { get; set; } 32 | 33 | public NullableSettings Nullable { get; set; } 34 | 35 | public Dictionary ConnectionStrings { get; set; } 36 | 37 | public AppSettings() 38 | { 39 | IntArray = new Int32[0]; 40 | Nullable = new NullableSettings(); 41 | ConnectionStrings = new Dictionary(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest/Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0;net462 6 | disable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | PreserveNewest 21 | 22 | 23 | PreserveNewest 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest/Keys/key-40c2510c-f6c5-4c4f-9c18-defdc18fdb84.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 2023-11-20T23:07:59.8982288Z 4 | 2023-11-20T23:07:59.873419Z 5 | 2038-11-16T23:07:59.873419Z 6 | 7 | 8 | 9 | 10 | 11 | 12 | gis8PGh17aTIAVKeB7lCD6vnDLT1fUjyoY5cmzFEnwvTOKNwYrC9J74U5RMTQ+6B/CWoPgtufFk5NwQ61kG+MQ== 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Fededim.Extensions.Configuration.ProtectedJson; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.AspNetCore.DataProtection; 5 | using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel; 6 | using Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption; 7 | using Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest; 8 | using Microsoft.Extensions.Options; 9 | using System; 10 | using System.IO; 11 | 12 | public class Program 13 | { 14 | private static void ConfigureDataProtection(IDataProtectionBuilder builder) 15 | { 16 | builder.UseCryptographicAlgorithms(new AuthenticatedEncryptorConfiguration 17 | { 18 | EncryptionAlgorithm = EncryptionAlgorithm.AES_256_CBC, 19 | ValidationAlgorithm = ValidationAlgorithm.HMACSHA256, 20 | 21 | }).SetDefaultKeyLifetime(TimeSpan.FromDays(365*15)).PersistKeysToFileSystem(new DirectoryInfo("..\\..\\..\\Keys")); 22 | } 23 | 24 | 25 | public static void Main(String[] args) 26 | { 27 | // define the DI services: Data Protection API 28 | var servicesDataProtection = new ServiceCollection(); 29 | ConfigureDataProtection(servicesDataProtection.AddDataProtection()); 30 | var serviceProviderDataProtection = servicesDataProtection.BuildServiceProvider(); 31 | 32 | // retrieve IDataProtector interface for encrypting data 33 | var dataProtector = serviceProviderDataProtection.GetRequiredService().CreateProtector(ProtectedJsonConfigurationProvider.DataProtectionPurpose); 34 | 35 | // encrypt all Protect:{} token tags of all .json files (must be done before reading the configuration) 36 | var encryptedFiles = dataProtector.ProtectFiles("."); 37 | 38 | // define the application configuration and read .json files 39 | var configuration = new ConfigurationBuilder() 40 | .AddCommandLine(args) 41 | .AddProtectedJsonFile("appsettings.json", serviceProviderDataProtection) 42 | .AddProtectedJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNETCORE_ENVIRONMENT")}.json", serviceProviderDataProtection) 43 | .AddEnvironmentVariables() 44 | .Build(); 45 | 46 | // define other DI services: configure strongly typed AppSettings configuration class (must be done after having read the configuration) 47 | var services = new ServiceCollection(); 48 | services.Configure(configuration); 49 | var serviceProvider = services.BuildServiceProvider(); 50 | 51 | // retrieve the strongly typed AppSettings configuration class 52 | var appSettings = serviceProvider.GetRequiredService>().Value; 53 | } 54 | } -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest": { 4 | "commandName": "Project", 5 | "environmentVariables": { 6 | "DOTNETCORE_ENVIRONMENT": "Development" 7 | } 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest/appsettings.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Int": 3, 3 | "DateTime": "2018-01-01T12:34:56.789", 4 | "IntArray": [ 4, 5, 6 ], 5 | "Nullable": { 6 | "DateTime": "Protect:{2016-10-01T20:34:56.789Z}", 7 | "Double": "Protect:{123.456}", 8 | "DoubleArray": [ "2.71", "Protect:{3.14}", "6.67" ], 9 | "Int": "Protect:{98765}", 10 | "Bool": "Protect:{true}" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson.ConsoleTest/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Int": 1, 3 | "Double": 2.0, 4 | "Bool": false, 5 | "DateTime": "2016-01-01T12:34:56.789", 6 | "IntArray": [ 1, 2 ], 7 | "Nullable": { 8 | "Int": null, 9 | "Double": null, 10 | "Bool": null, 11 | "DateTime": null, 12 | "DoubleArray": [ 3.14, 2.71 ] 13 | }, 14 | "ConnectionStrings": { 15 | "PlainTextConnectionString": "Data Source=localhost; Initial Catalog=DB name; User ID=sa; Password=pass1234; MultipleActiveResultSets=True;", 16 | "PartiallyEncryptedConnectionString": "Data Source=Protect:{local}; Initial Catalog=Protect:{Secret_Catalog}; User ID=Protect:{secret_user}; Password=Protect:{secret_password}; MultipleActiveResultSets=True;", 17 | "FullyEncryptedConnectionString": "Protect:{Data Source=server1; Initial Catalog=DB name; User ID=sa; Password=pass5678; MultipleActiveResultSets=True;}" 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson/Fededim.Extensions.Configuration.ProtectedJson.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net6.0;netstandard2.0;net462 5 | disable 6 | 7 | true 8 | 1.0.2 9 | build_$([System.DateTime]::UtcNow.ToString("yyyy_MM_dd_HH_mm_ss")) 10 | Federico Di Marco <fededim@gmail.com> 11 | ProtectedJson is an improved JSON configuration provider which allows partial or full encryption of configuration values stored in appsettings.json files and fully integrated in the ASP.NET Core architecture. Basically, it implements a custom ConfigurationSource and a custom ConfigurationProvider defining a custom tokenization tag which whenever found decrypts the enclosed encrypted data using ASP.NET Core Data Protection API. 12 | $([System.DateTime]::UtcNow.Year) 13 | https://github.com/fededim/Fededim.Extensions.Configuration.Protected/tree/master/Fededim.Extensions.Configuration.ProtectedJson 14 | git 15 | https://github.com/fededim/Fededim.Extensions.Configuration.Protected/tree/master/Fededim.Extensions.Configuration.ProtectedJson 16 | README.md 17 | LICENSE.txt 18 | $(AssemblyName) 19 | configuration;dataprotection;appsettings;json;encrypted 20 | true 21 | true 22 | snupkg 23 | 24 | v1.0.0 25 | - Initial commit 26 | 27 | v1.0.1 28 | - Targeting multi frameworks: net6.0, netstandard2.0 and net462 29 | 30 | v1.0.2 31 | - Renamed GitHub repository, just updated the NuGet README file. This package is however obsolete and it has been replaced by the more versatile [Fededim.Extensions.Configuration.Protected](https://www.nuget.org/packages/Fededim.Extensions.Configuration.Protected). 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson/ProtectedJsonConfiguration.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.DataProtection; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Configuration.Json; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using System; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Text.RegularExpressions; 9 | 10 | namespace Fededim.Extensions.Configuration.ProtectedJson 11 | { 12 | public class ProtectedJsonStreamConfigurationProvider : JsonStreamConfigurationProvider 13 | { 14 | protected IDataProtector DataProtector { get; set; } 15 | 16 | public ProtectedJsonStreamConfigurationProvider(ProtectedJsonStreamConfigurationSource source) : base(source) 17 | { 18 | // configure data protection 19 | if (source.DataProtectionBuildAction != null) 20 | { 21 | var services = new ServiceCollection(); 22 | source.DataProtectionBuildAction(services.AddDataProtection()); 23 | source.ServiceProvider = services.BuildServiceProvider(); 24 | } 25 | else if (source.ServiceProvider == null) 26 | throw new ArgumentNullException(nameof(source.ServiceProvider)); 27 | 28 | DataProtector = source.ServiceProvider.GetRequiredService().CreateProtector(ProtectedJsonConfigurationProvider.DataProtectionPurpose); 29 | } 30 | 31 | public override void Load(Stream stream) 32 | { 33 | base.Load(stream); 34 | 35 | var protectedSource = (ProtectedJsonStreamConfigurationSource)Source; 36 | 37 | // decrypt needed values 38 | foreach (var kvp in Data) 39 | { 40 | if (!String.IsNullOrEmpty(kvp.Value)) 41 | Data[kvp.Key] = protectedSource.ProtectedRegex.Replace(kvp.Value, me => DataProtector.Unprotect(me.Value)); 42 | } 43 | } 44 | } 45 | 46 | 47 | public class ProtectedJsonStreamConfigurationSource : JsonStreamConfigurationSource 48 | { 49 | public Regex ProtectedRegex { get; set; } 50 | public Action DataProtectionBuildAction { get; set; } 51 | public IServiceProvider ServiceProvider { get; set; } 52 | 53 | 54 | public ProtectedJsonStreamConfigurationSource():this(null) 55 | { 56 | 57 | } 58 | 59 | public ProtectedJsonStreamConfigurationSource(String protectedRegexString = null) 60 | { 61 | var protectedRegex = new Regex(protectedRegexString ?? ProtectedJsonConfigurationSource.DefaultProtectedRegexString); 62 | if (!protectedRegex.GetGroupNames().Contains("protectedData")) 63 | throw new ArgumentException("Regex must contain a group named protectedData!", nameof(protectedRegexString)); 64 | 65 | ProtectedRegex = protectedRegex; 66 | } 67 | 68 | 69 | public override IConfigurationProvider Build(IConfigurationBuilder builder) 70 | => new ProtectedJsonStreamConfigurationProvider(this); 71 | } 72 | 73 | 74 | public class ProtectedJsonConfigurationProvider : JsonConfigurationProvider 75 | { 76 | public const String DataProtectionPurpose = "ProtectedJsonConfigurationProvider"; 77 | 78 | protected IDataProtector DataProtector { get; set; } 79 | 80 | public ProtectedJsonConfigurationProvider(ProtectedJsonConfigurationSource source) : base(source) 81 | { 82 | // configure data protection 83 | if (source.DataProtectionBuildAction != null) 84 | { 85 | var services = new ServiceCollection(); 86 | source.DataProtectionBuildAction(services.AddDataProtection()); 87 | source.ServiceProvider = services.BuildServiceProvider(); 88 | } 89 | else if (source.ServiceProvider==null) 90 | throw new ArgumentNullException(nameof(source.ServiceProvider)); 91 | 92 | DataProtector = source.ServiceProvider.GetRequiredService().CreateProtector(DataProtectionPurpose); 93 | } 94 | 95 | public override void Load() 96 | { 97 | base.Load(); 98 | 99 | var protectedSource = (ProtectedJsonConfigurationSource)Source; 100 | 101 | // decrypt needed values 102 | foreach (var key in Data.Keys.ToList()) 103 | { 104 | if (!String.IsNullOrEmpty(Data[key])) 105 | Data[key] = protectedSource.ProtectedRegex.Replace(Data[key], me => DataProtector.Unprotect(me.Groups["protectedData"].Value)); 106 | } 107 | } 108 | } 109 | 110 | 111 | public class ProtectedJsonConfigurationSource : JsonConfigurationSource 112 | { 113 | public const String DefaultProtectedRegexString = "Protected:{(?.+?)}"; 114 | public const String DefaultProtectRegexString = "Protect:{(?.+?)}"; 115 | 116 | public Regex ProtectedRegex { get; set; } 117 | public Action DataProtectionBuildAction { get; set; } 118 | public IServiceProvider ServiceProvider { get; set; } 119 | 120 | public ProtectedJsonConfigurationSource(String protectedRegexString = null) 121 | { 122 | var protectedRegex = new Regex(protectedRegexString ?? DefaultProtectedRegexString); 123 | if (!protectedRegex.GetGroupNames().Contains("protectedData")) 124 | throw new ArgumentException("Regex must contain a group named protectedData!", nameof(protectedRegexString)); 125 | 126 | ProtectedRegex = protectedRegex; 127 | } 128 | 129 | 130 | public override IConfigurationProvider Build(IConfigurationBuilder builder) 131 | { 132 | ResolveFileProvider(); 133 | EnsureDefaults(builder); 134 | return new ProtectedJsonConfigurationProvider(this); 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson/ProtectedJsonConfigurationExtensions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.FileProviders; 3 | using System.Text.RegularExpressions; 4 | using Microsoft.AspNetCore.DataProtection; 5 | using System; 6 | using System.IO; 7 | using System.Linq; 8 | using System.Collections.Generic; 9 | 10 | namespace Fededim.Extensions.Configuration.ProtectedJson 11 | { 12 | public static class ProtectedJsonConfigurationExtensions 13 | { 14 | public static IConfigurationBuilder AddProtectedJsonFile(this IConfigurationBuilder builder, String path, Action dataProtectionConfigureAction) 15 | { 16 | return AddProtectedJsonFile(builder, provider: null, path: path, optional: false, reloadOnChange: false, dataProtectionConfigureAction: dataProtectionConfigureAction); 17 | } 18 | 19 | public static IConfigurationBuilder AddProtectedJsonFile(this IConfigurationBuilder builder, String path, IServiceProvider serviceProvider) 20 | { 21 | return AddProtectedJsonFile(builder, provider: null, path: path, optional: false, reloadOnChange: false, serviceProvider: serviceProvider); 22 | } 23 | 24 | 25 | 26 | public static IConfigurationBuilder AddProtectedJsonFile(this IConfigurationBuilder builder, String path, bool optional, Action dataProtectionConfigureAction) 27 | { 28 | return AddProtectedJsonFile(builder, provider: null, path: path, optional: optional, reloadOnChange: false, dataProtectionConfigureAction: dataProtectionConfigureAction); 29 | } 30 | 31 | public static IConfigurationBuilder AddProtectedJsonFile(this IConfigurationBuilder builder, String path, bool optional, IServiceProvider serviceProvider) 32 | { 33 | return AddProtectedJsonFile(builder, provider: null, path: path, optional: optional, reloadOnChange: false, serviceProvider: serviceProvider); 34 | } 35 | 36 | 37 | 38 | public static IConfigurationBuilder AddProtectedJsonFile(this IConfigurationBuilder builder, String path, bool optional, bool reloadOnChange, Action dataProtectionConfigureAction) 39 | { 40 | return AddProtectedJsonFile(builder, provider: null, path: path, optional: optional, reloadOnChange: reloadOnChange, dataProtectionConfigureAction: dataProtectionConfigureAction); 41 | } 42 | 43 | public static IConfigurationBuilder AddProtectedJsonFile(this IConfigurationBuilder builder, String path, bool optional, bool reloadOnChange, IServiceProvider serviceProvider) 44 | { 45 | return AddProtectedJsonFile(builder, provider: null, path: path, optional: optional, reloadOnChange: reloadOnChange, serviceProvider: serviceProvider); 46 | } 47 | 48 | 49 | 50 | public static IConfigurationBuilder AddProtectedJsonFile(this IConfigurationBuilder builder, IFileProvider provider, String path, bool optional, bool reloadOnChange, String protectedRegexString = null, IServiceProvider serviceProvider = null, Action dataProtectionConfigureAction = null) 51 | { 52 | if (builder == null) 53 | throw new ArgumentNullException(nameof(builder)); 54 | 55 | 56 | if (serviceProvider == null && dataProtectionConfigureAction == null) 57 | throw new ArgumentException("Either serviceProvider or dataProtectionConfigureAction must not be null", serviceProvider == null ? nameof(serviceProvider) : nameof(dataProtectionConfigureAction)); 58 | 59 | if (String.IsNullOrEmpty(path)) 60 | throw new ArgumentException("Invalid file path", nameof(path)); 61 | 62 | var protectedRegex = new Regex(protectedRegexString ?? ProtectedJsonConfigurationSource.DefaultProtectedRegexString); 63 | if (!protectedRegex.GetGroupNames().Contains("protectedData")) 64 | throw new ArgumentException("Regex must contain a group named protectedData!", nameof(protectedRegexString)); 65 | 66 | return builder.AddProtectedJsonFile(s => 67 | { 68 | s.FileProvider = provider; 69 | s.Path = path; 70 | s.Optional = optional; 71 | s.ReloadOnChange = reloadOnChange; 72 | s.ProtectedRegex = protectedRegex; 73 | s.DataProtectionBuildAction = dataProtectionConfigureAction; 74 | s.ServiceProvider = serviceProvider; 75 | s.ResolveFileProvider(); 76 | }); 77 | } 78 | 79 | 80 | public static IConfigurationBuilder AddProtectedJsonFile(this IConfigurationBuilder builder, Action configureSource) 81 | { 82 | var configurationSource = new ProtectedJsonConfigurationSource(); 83 | configureSource(configurationSource); 84 | return builder.Add(configurationSource); 85 | } 86 | 87 | 88 | 89 | public static IConfigurationBuilder AddProtectedJsonStream(this IConfigurationBuilder builder, Action configureSource) 90 | { 91 | var configurationSource = new ProtectedJsonStreamConfigurationSource(); 92 | configureSource(configurationSource); 93 | return builder.Add(configurationSource); 94 | } 95 | 96 | public static IConfigurationBuilder AddProtectedJsonStream(this IConfigurationBuilder builder, Stream stream, String protectedRegexString = ProtectedJsonConfigurationSource.DefaultProtectedRegexString, IServiceProvider serviceProvider = null, Action dataProtectionConfigureAction = null) 97 | { 98 | if (builder == null) 99 | throw new ArgumentNullException(nameof(builder)); 100 | 101 | if (serviceProvider == null && dataProtectionConfigureAction == null) 102 | throw new ArgumentException("Either serviceProvider or dataProtectionConfigureAction must not be null", serviceProvider == null ? nameof(serviceProvider) : nameof(dataProtectionConfigureAction)); 103 | 104 | var protectedRegex = new Regex(protectedRegexString ?? ProtectedJsonConfigurationSource.DefaultProtectedRegexString); 105 | if (!protectedRegex.GetGroupNames().Contains("protectedData")) 106 | throw new ArgumentException("Regex must contain a group named protectedData!", nameof(protectedRegexString)); 107 | 108 | return builder.Add(s => 109 | { 110 | s.Stream = stream; 111 | s.ProtectedRegex = protectedRegex; 112 | s.DataProtectionBuildAction = dataProtectionConfigureAction; 113 | s.ServiceProvider = serviceProvider; 114 | } 115 | ); 116 | } 117 | 118 | 119 | /// 120 | /// Perform wildcard search of files in path encrypting any data enclosed by protectRegexString the inside files with the protectedReplaceString 121 | /// 122 | /// directory to be searched 123 | /// wildcard pattern to filter files 124 | /// search options 125 | /// a regular expression which captures the data to be encrypted in a named group called protectData 126 | /// a string expression used to generate the final encrypted String using ${protectedData} as a placeholder parameter for encrypted data 127 | /// boolean which indicates whether to make a backupof original file with extension .bak 128 | /// a list of filenames which have been successfully encrypted 129 | /// 130 | public static IList ProtectFiles(this IDataProtector dataProtector, String path, String searchPattern = "*.json", SearchOption searchOption = SearchOption.TopDirectoryOnly, String protectRegexString = null, String protectedReplaceString = "Protected:{${protectedData}}", bool backupOriginalFile = true) 131 | { 132 | var protectRegex = new Regex(protectRegexString ?? ProtectedJsonConfigurationSource.DefaultProtectRegexString); 133 | if (!protectRegex.GetGroupNames().Contains("protectData")) 134 | throw new ArgumentException("Regex must contain a group named protectData!", nameof(protectRegexString)); 135 | 136 | var result = new List(); 137 | 138 | foreach (var f in Directory.EnumerateFiles(path, searchPattern, searchOption)) 139 | { 140 | var fileContent = File.ReadAllText(f); 141 | 142 | var replacedContent = protectRegex.Replace(fileContent, (me) => 143 | protectedReplaceString.Replace("${protectedData}", dataProtector.Protect(me.Groups["protectData"].Value))); 144 | 145 | if (replacedContent != fileContent) 146 | { 147 | if (backupOriginalFile) 148 | File.Copy(f, f + ".bak",true); 149 | 150 | File.WriteAllText(f, replacedContent); 151 | 152 | result.Add(f); 153 | } 154 | } 155 | 156 | return result; 157 | } 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson/docs/README.md: -------------------------------------------------------------------------------- 1 | # About 2 | Fededim.Extensions.Configuration.ProtectedJson is an improved JSON configuration provider which allows partial or full encryption of configuration values stored in appsettings.json files and fully integrated in the ASP.NET Core architecture. Basically, it implements a custom ConfigurationSource and a custom ConfigurationProvider defining a custom tokenization tag which whenever found decrypts the enclosed encrypted data using ASP.NET Core Data Protection API. 3 | 4 | This package is however deprecated in favour of the more versatile [Fededim.Extensions.Configuration.Protected](https://www.nuget.org/packages/Fededim.Extensions.Configuration.Protected). 5 | 6 | # Key Features 7 | - Encrypt partially or fully a configuration value 8 | - Trasparent in memory decryption of encrypted values without almost any additional line of code 9 | 10 | # How to Use 11 | 12 | - Modify appsettings JSON files by enclose with the encryption tokenization tag (e.g. Protect:{().CreateProtector(ProtectedJsonConfigurationProvider.DataProtectionPurpose); 52 | 53 | // encrypt all Protect:{} token tags of all .json files (must be done before reading the configuration) 54 | var encryptedFiles = dataProtector.ProtectFiles("."); 55 | 56 | // define the application configuration and read .json files 57 | var configuration = new ConfigurationBuilder() 58 | .AddCommandLine(args) 59 | .AddProtectedJsonFile("appsettings.json", ConfigureDataProtection) 60 | .AddProtectedJsonFile($"appsettings.{Environment.GetEnvironmentVariable("DOTNETCORE_ENVIRONMENT")}.json", ConfigureDataProtection) 61 | .AddEnvironmentVariables() 62 | .Build(); 63 | 64 | // define other DI services: configure strongly typed AppSettings configuration class (must be done after having read the configuration) 65 | var services = new ServiceCollection(); 66 | services.Configure(configuration); 67 | var serviceProvider = services.BuildServiceProvider(); 68 | 69 | // retrieve the strongly typed AppSettings configuration class 70 | var appSettings = serviceProvider.GetRequiredService>().Value; 71 | } 72 | } 73 | 74 | ``` 75 | 76 | The main types provided by this library are: 77 | 78 | - Fededim.Extensions.Configuration.ProtectedJson.ProtectedJsonStreamConfigurationProvider 79 | - Fededim.Extensions.Configuration.ProtectedJson.ProtectedJsonStreamConfigurationSource 80 | - Fededim.Extensions.Configuration.ProtectedJson.ProtectedJsonConfigurationProvider 81 | - Fededim.Extensions.Configuration.ProtectedJson.ProtectedJsonConfigurationSource 82 | 83 | # Version History 84 | 85 | v1.0.0 86 | - Initial commit 87 | 88 | v1.0.1 89 | - Targeting multi frameworks: net6.0, netstandard2.0 and net462 90 | 91 | v1.0.2 92 | - Renamed GitHub repository, just updated the NuGet README file. This package is however obsolete and it has been replaced by the more versatile [Fededim.Extensions.Configuration.Protected](https://www.nuget.org/packages/Fededim.Extensions.Configuration.Protected). 93 | 94 | # Detailed guide 95 | 96 | You can find a [detailed article on CodeProject](https://www.codeproject.com/Articles/5372873/ProtectedJson-Integrating-ASP-NET-Core-Configurati) explaning the origin, how to use it and the main point of the implementation. 97 | 98 | # Feedback & Contributing 99 | Fededim.Extensions.Configuration.ProtectedJson is released as open source under the MIT license. Bug reports and contributions are welcome at the [GitHub repository](https://github.com/fededim/Fededim.Extensions.Configuration.Protected/tree/master/Fededim.Extensions.Configuration.ProtectedJson). -------------------------------------------------------------------------------- /Fededim.Extensions.Configuration.ProtectedJson/license/LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Federico Di Marco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Federico Di Marco 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Overview 2 | 3 | In this repository you will find the source code of my NuGet package: Fededim.Extensions.Configuration.Protected 4 | 5 | [![Build status](https://github.com/fededim/Fededim.Extensions.Configuration.Protected/actions/workflows/dotnet.yml/badge.svg)](https://github.com/fededim/Fededim.Extensions.Configuration.Protected/actions/workflows/dotnet.yml?query=branch%3Amaster) 6 | [![Test Coverage](https://raw.githubusercontent.com/fededim/Fededim.Extensions.Configuration.Protected/master/misc/last_build_artifacts/badge_combined.svg)](https://htmlpreview.github.io/?https://raw.githubusercontent.com/fededim/Fededim.Extensions.Configuration.Protected/master/misc/last_build_artifacts/index.html) 7 | 8 | 9 | # Fededim.Extensions.Configuration.Protected 10 | 11 | Fededim.Extensions.Configuration.Protected is an improved ConfigurationBuilder which allows partial or full encryption of configuration values stored inside any possible ConfigurationSource and fully integrated in the ASP.NET Core architecture. Fededim.Extensions.Configuration.Protected implements a custom ConfigurationBuilder and a custom ConfigurationProvider defining a custom tokenization tag which whenever found inside a configuration value decrypts the enclosed encrypted data using a pluggable encryption/decryption provider (a default one based on ASP.NET Core Data Protection API is provided). 12 | 13 | You can find the source code here [Fededim.Extensions.Configuration.Protected](https://github.com/fededim/Fededim.Extensions.Configuration.Protected/tree/master/Fededim.Extensions.Configuration.Protected) 14 | 15 | 16 | # Fededim.Extensions.Configuration.Protected.DataProtectionAPI 17 | 18 | Fededim.Extensions.Configuration.Protected.DataProtectionAPI is the standard Microsoft Data Protection API encryption/decryption provider for Fededim.Extensions.Configuration.Protected 19 | 20 | You can find the source code here [Fededim.Extensions.Configuration.Protected.DataProtectionAPI](https://github.com/fededim/Fededim.Extensions.Configuration.Protected/tree/master/Fededim.Extensions.Configuration.Protected.DataProtectionAPI) 21 | 22 | You can find a [detailed article on CodeProject](https://www.codeproject.com/Articles/5374311/Fededim-Extensions-Configuration-Protected) explaning the origin, how to use it and the main point of the implementation. 23 | 24 | 25 | # Fededim.Extensions.Configuration.Protected.DataProtectionAPITest 26 | This a xUnit test project which tests thoroughly the two above packages in order to improve the reliability and the code quality. It creates sample data for all ConfigurationSources provided by Microsoft .NET (a JSON file, a XML file, environment variables, an in-memory dictionary and command line arguments) containing a 2\*fixed set of entries (10000), one in plaintext with random datatype and value and another with the same value but encrypted. It loads then the sample data with ProtectedConfigurationBuilder in order to decrypt it and tests that all plaintext values are the same as those that have been decrypted. For example a test case on the JsonConfigurationProvider generated a plain-text file with a total size of 60MB and an encrypted file with a total size of 91MB, the test has ended in around 10 seconds for generating the random JSON file, encrypting it, decrypting it using the ProtectedConfigurationBuilder (in order to decrypt 250k encrypted values this step took around 5 seconds in .Net462 and around 3 seconds in net6.0 which is faster) and checking that every decrypted key was equal to the plaintext one. Moreover all the whole set of five test cases was repeated for 1000 iterations (Test Explorer Run Until Failure, unluckily it is not available for all tests, I had to do it separately), both for net462 (total runtime 705 minutes) and net6.0 (total runtime 424 minutes) without raising any error as you can see in the pictures below. 27 | 28 | Net462 Endurance Test 29 | ![image](https://github.com/user-attachments/assets/7675c2aa-b24f-4e09-8422-55f531e6ca30) 30 | 31 | Net6.0 Endurance Test 32 | ![image](https://github.com/user-attachments/assets/fc73e3ef-e5e6-4b1c-a4bd-d1a2dbf30e10) 33 | 34 | 35 | # Fededim.Extensions.Configuration.ProtectedJson (OBSOLETE PLEASE USE Fededim.Extensions.Configuration.Protected.DataProtectionAPI) 36 | 37 | Fededim.Extensions.Configuration.ProtectedJson is my first package and it is an improved JSON configuration provider which allows partial or full encryption of configuration values stored in appsettings.json files and fully integrated in the ASP.NET Core architecture. Basically, it implements a custom ConfigurationSource and a custom ConfigurationProvider defining a custom tokenization tag which whenever found decrypts the enclosed encrypted data using ASP.NET Core Data Protection API. 38 | 39 | You can find the source code here [Fededim.Extensions.Configuration.ProtectedJson](https://github.com/fededim/Fededim.Extensions.Configuration.Protected/tree/master/Fededim.Extensions.Configuration.ProtectedJson) but this package has 40 | become however obsolete in favour of the more versatile [Fededim.Extensions.Configuration.Protected.DataProtectionAPI](https://github.com/fededim/Fededim.Extensions.Configuration.Protected/tree/master/Fededim.Extensions.Configuration.Protected.DataProtectionAPI). 41 | 42 | You can find a [detailed article on CodeProject](https://www.codeproject.com/Articles/5372873/ProtectedJson-Integrating-ASP-NET-Core-Configurati) explaning the origin, how to use it and the main point of the implementation. 43 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/SummaryGithub.md: -------------------------------------------------------------------------------- 1 | # Summary - Code Coverage and Reports 2 |
Summary 3 | 4 | ||| 5 | |:---|:---| 6 | | Generated on: | 12/8/2024 - 8:13:59 PM | 7 | | Coverage date: | 12/8/2024 - 8:13:21 PM - 12/8/2024 - 8:13:55 PM | 8 | | Parser: | MultiReport (2x Cobertura) | 9 | | Assemblies: | 2 | 10 | | Classes: | 14 | 11 | | Files: | 9 | 12 | | **Line coverage:** | 71.8% (247 of 344) | 13 | | Covered lines: | 247 | 14 | | Uncovered lines: | 97 | 15 | | Coverable lines: | 344 | 16 | | Total lines: | 1489 | 17 | | **Branch coverage:** | 50.5% (89 of 176) | 18 | | Covered branches: | 89 | 19 | | Total branches: | 176 | 20 | | **Method coverage:** | [Feature is only available for sponsors](https://reportgenerator.io/pro) | 21 | | Tag: | 70_12224880118 | 22 | 23 |
24 | 25 | ## Coverage 26 |
Fededim.Extensions.Configuration.Protected - 73% 27 | 28 | |**Name**|**Line**|**Branch**| 29 | |:---|---:|---:| 30 | |**Fededim.Extensions.Configuration.Protected**|**73%**|**49.3%**| 31 | |Fededim.Extensions.Configuration.Protected.ConfigurationBuilderExtensions|82.3%|70%| 32 | |Fededim.Extensions.Configuration.Protected.IProtectProviderConfigurationDat
a|76.4%|71.4%| 33 | |Fededim.Extensions.Configuration.Protected.JsonProtectFileProcessor|27.2%|8.3%| 34 | |Fededim.Extensions.Configuration.Protected.JsonWithCommentsProtectFileProce
ssor|95.6%|75%| 35 | |Fededim.Extensions.Configuration.Protected.PassthroughProtectConfigurationD
ata|100%|| 36 | |Fededim.Extensions.Configuration.Protected.PassthroughProtectProvider|100%|| 37 | |Fededim.Extensions.Configuration.Protected.ProtectedConfigurationBuilder|74%|62.5%| 38 | |Fededim.Extensions.Configuration.Protected.ProtectedConfigurationProvider|68.4%|46.8%| 39 | |Fededim.Extensions.Configuration.Protected.ProtectFileOptions|100%|| 40 | |Fededim.Extensions.Configuration.Protected.ProtectProviderConfigurationData|60%|16.6%| 41 | |Fededim.Extensions.Configuration.Protected.RawProtectFileProcessor|0%|| 42 | |Fededim.Extensions.Configuration.Protected.XmlProtectFileProcessor|96%|91.6%| 43 | 44 |
45 |
Fededim.Extensions.Configuration.Protected.DataProtectionAPI - 63.6% 46 | 47 | |**Name**|**Line**|**Branch**| 48 | |:---|---:|---:| 49 | |**Fededim.Extensions.Configuration.Protected.DataProtectionAPI**|**63.6%**|**61.1%**| 50 | |Fededim.Extensions.Configuration.Protected.DataProtectionAPI.DataProtection
APIProtectConfigurationData|56.7%|61.1%| 51 | |Fededim.Extensions.Configuration.Protected.DataProtectionAPI.DataProtection
APIProtectProvider|100%|| 52 | 53 |
54 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_branchcoverage.svg: -------------------------------------------------------------------------------- 1 | 2 | 48 | Code coverage 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | Generated by: ReportGenerator 5.3.8.0 98 | 99 | 100 | 101 | Coverage 102 | Coverage 103 | 104 | 50.5%50.5% 105 | 106 | 107 | 108 | 109 | 110 | Branch coverage 111 | 112 | 113 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_combined.svg: -------------------------------------------------------------------------------- 1 | 2 | 48 | Code coverage 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | Generated by: ReportGenerator 5.3.8.0 98 | 99 | 100 | 101 | Coverage 102 | Coverage 103 | 71.8%71.8% 104 | 50.5%50.5% 105 | 77.2%77.2% 106 | 107 | 108 | 109 | Line coverage 110 | Branch coverage 111 | Method coverage 112 | 113 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_linecoverage.svg: -------------------------------------------------------------------------------- 1 | 2 | 48 | Code coverage 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | Generated by: ReportGenerator 5.3.8.0 98 | 99 | 100 | 101 | Coverage 102 | Coverage 103 | 71.8%71.8% 104 | 105 | 106 | 107 | 108 | 109 | Line coverage 110 | 111 | 112 | 113 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_methodcoverage.svg: -------------------------------------------------------------------------------- 1 | 2 | 48 | Code coverage 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | Generated by: ReportGenerator 5.3.8.0 98 | 99 | 100 | 101 | Coverage 102 | Coverage 103 | 104 | 105 | 77.2%77.2% 106 | 107 | 108 | 109 | 110 | 111 | Method coverage 112 | 113 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_branchcoverage_blue.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage50%50% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_branchcoverage_brightgreen.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage50%50% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_branchcoverage_green.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage50%50% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_branchcoverage_lightgrey.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage50%50% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_branchcoverage_orange.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage50%50% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_branchcoverage_red.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage50%50% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_branchcoverage_yellow.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage50%50% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_branchcoverage_yellowgreen.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage50%50% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_linecoverage_blue.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage71%71% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_linecoverage_brightgreen.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage71%71% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_linecoverage_green.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage71%71% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_linecoverage_lightgrey.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage71%71% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_linecoverage_orange.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage71%71% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_linecoverage_red.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage71%71% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_linecoverage_yellow.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage71%71% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_linecoverage_yellowgreen.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage71%71% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_methodcoverage_blue.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage77%77% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_methodcoverage_brightgreen.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage77%77% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_methodcoverage_green.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage77%77% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_methodcoverage_lightgrey.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage77%77% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_methodcoverage_orange.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage77%77% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_methodcoverage_red.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage77%77% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_methodcoverage_yellow.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage77%77% -------------------------------------------------------------------------------- /misc/last_build_artifacts/badge_shieldsio_methodcoverage_yellowgreen.svg: -------------------------------------------------------------------------------- 1 | coveragecoverage77%77% -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_cog.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_cog_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_cube.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_cube_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_fork.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_fork_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_info-circled.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_info-circled_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_minus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_minus_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_plus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_plus_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_search-minus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_search-minus_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_search-plus.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_search-plus_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_sponsor.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_star.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_star_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_up-dir.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_up-dir_active.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_up-down-dir.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_up-down-dir_dark.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_wrench.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/icon_wrench_dark.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /misc/last_build_artifacts/test_results.md: -------------------------------------------------------------------------------- 1 | 2 | # FEDEDIM.EXTENSIONS.CONFIGURATION.PROTECTED.DATAPROTECTIONAPITEST 3 | 4 | ![Generic badge](https://img.shields.io/badge/24/24-PASSED-brightgreen.svg) 5 |
6 | Duration: 154.941 seconds 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
Start:2024-12-08 20:11:20.597 UTC
Creation:2024-12-08 20:11:33.782 UTC
Queuing:2024-12-08 20:11:33.782 UTC
Finish:2024-12-08 20:13:55.538 UTC
Duration:154.941 seconds
29 |
30 |
31 | Outcome: Completed | Total Tests: 24 | Passed: 24 | Failed: 0 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 |
Total:24
Executed:24
Passed:24
Failed:0
50 |
51 | 52 | 53 | # FEDEDIM.EXTENSIONS.CONFIGURATION.PROTECTED.DATAPROTECTIONAPITEST 54 | 55 | ![Generic badge](https://img.shields.io/badge/24/24-PASSED-brightgreen.svg) 56 |
57 | Duration: 121.047 seconds 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 |
Start:2024-12-08 20:11:20.598 UTC
Creation:2024-12-08 20:11:43.109 UTC
Queuing:2024-12-08 20:11:43.109 UTC
Finish:2024-12-08 20:13:21.645 UTC
Duration:121.047 seconds
80 |
81 |
82 | Outcome: Completed | Total Tests: 24 | Passed: 24 | Failed: 0 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 |
Total:24
Executed:24
Passed:24
Failed:0
101 |
102 | 103 | 104 | # FEDEDIM.EXTENSIONS.CONFIGURATION.PROTECTED.DATAPROTECTIONAPITEST 105 | 106 | ![Generic badge](https://img.shields.io/badge/24/24-PASSED-brightgreen.svg) 107 |
108 | Duration: 150.192 seconds 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 |
Start:2024-12-08 19:55:46.119 UTC
Creation:2024-12-08 19:55:57.897 UTC
Queuing:2024-12-08 19:55:57.897 UTC
Finish:2024-12-08 19:58:16.311 UTC
Duration:150.192 seconds
131 |
132 |
133 | Outcome: Completed | Total Tests: 24 | Passed: 24 | Failed: 0 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 |
Total:24
Executed:24
Passed:24
Failed:0
152 |
153 | 154 | 155 | # FEDEDIM.EXTENSIONS.CONFIGURATION.PROTECTED.DATAPROTECTIONAPITEST 156 | 157 | ![Generic badge](https://img.shields.io/badge/6/6-PASSED-brightgreen.svg) 158 |
159 | Duration: 50.014 seconds 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 |
Start:2024-09-30 20:58:43.225 UTC
Creation:2024-09-30 20:58:56.758 UTC
Queuing:2024-09-30 20:58:56.758 UTC
Finish:2024-09-30 20:59:33.239 UTC
Duration:50.014 seconds
182 |
183 |
184 | Outcome: Completed | Total Tests: 6 | Passed: 6 | Failed: 0 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 |
Total:6
Executed:6
Passed:6
Failed:0
203 |
204 | 205 | 206 | # FEDEDIM.EXTENSIONS.CONFIGURATION.PROTECTED.DATAPROTECTIONAPITEST 207 | 208 | ![Generic badge](https://img.shields.io/badge/24/24-PASSED-brightgreen.svg) 209 |
210 | Duration: 88.555 seconds 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 |
Start:2024-12-08 19:55:46.121 UTC
Creation:2024-12-08 19:55:54.709 UTC
Queuing:2024-12-08 19:55:54.709 UTC
Finish:2024-12-08 19:57:14.676 UTC
Duration:88.555 seconds
233 |
234 |
235 | Outcome: Completed | Total Tests: 24 | Passed: 24 | Failed: 0 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 |
Total:24
Executed:24
Passed:24
Failed:0
254 |
255 | --------------------------------------------------------------------------------