├── .gitignore
├── .nuget
└── NuGet.Config
├── ConfigureServer.ps1
├── GenerateTokenizedConfig.ps1
├── LICENSE
├── Microsoft.Web.XmlTransform.dll
├── README.md
├── RutaHttpModule.sln
├── RutaHttpModule
├── AdInteraction.cs
├── IAdInteraction.cs
├── IRutaHttpContext.cs
├── ISettings.cs
├── ISonarAuthPassthroughHttpContext.cs
├── ITraceSource.cs
├── LdapExtensions.cs
├── Properties
│ ├── AssemblyInfo.cs
│ ├── Settings.Designer.cs
│ └── Settings.settings
├── RutaHttpContext.cs
├── RutaHttpModule.csproj
├── RutaModule.cs
├── RutaTraceSource.cs
├── SettingsWrapper.cs
├── SonarAuthPassthroughHttpContext.cs
├── SonarAuthPassthroughModule.cs
└── app.config
├── RutaHttpModuleTest
├── AdInteractionTest.cs
├── Properties
│ └── AssemblyInfo.cs
├── RutaHttpModuleTest.csproj
├── RutaModuleTest.cs
└── SonarAuthPassthroughModuleTest.cs
├── SonarQubeScanner
├── MSBuild.SonarQube.Internal.PostProcess.exe
├── MSBuild.SonarQube.Internal.PreProcess.exe
├── MSBuild.SonarQube.Runner.exe
├── Newtonsoft.Json.dll
├── SonarQube.Analysis.xml
├── SonarQube.Common.dll
├── SonarQube.Integration.Tasks.dll
├── SonarQube.MSBuild.PreProcessor.exe
├── SonarQube.Scanner.MSBuild.exe
├── SonarScanner.Shim.dll
├── SupportedBootstrapperVersions.xml
├── Targets
│ ├── SonarQube.Integration.ImportBefore.targets
│ └── SonarQube.Integration.targets
├── TeamBuild.SonarQube.Integration.dll
└── sonar-scanner-3.0.1.733
│ ├── bin
│ ├── sonar-runner
│ ├── sonar-runner.bat
│ ├── sonar-scanner
│ ├── sonar-scanner-debug
│ ├── sonar-scanner-debug.bat
│ └── sonar-scanner.bat
│ ├── conf
│ └── sonar-scanner.properties
│ └── lib
│ └── sonar-scanner-cli-3.0.1.733.jar
├── appveyor.yml
├── test.runsettings
├── web-scanner.config
├── web-user-transform.xml
└── web-user.config
/.gitignore:
--------------------------------------------------------------------------------
1 | ## Ignore Visual Studio temporary files, build results, and
2 | ## files generated by popular Visual Studio add-ons.
3 |
4 | # User-specific files
5 | *.suo
6 | *.user
7 | *.userosscache
8 | *.sln.docstates
9 | web-user-tokenized.config
10 |
11 | # User-specific files (MonoDevelop/Xamarin Studio)
12 | *.userprefs
13 |
14 | # Build results
15 | [Dd]ebug/
16 | [Dd]ebugPublic/
17 | [Rr]elease/
18 | [Rr]eleases/
19 | x64/
20 | x86/
21 | bld/
22 | [Bb]in/
23 | [Oo]bj/
24 | [Ll]og/
25 |
26 | # Visual Studio 2015 cache/options directory
27 | .vs/
28 | # Uncomment if you have tasks that create the project's static files in wwwroot
29 | #wwwroot/
30 |
31 | # MSTest test Results
32 | [Tt]est[Rr]esult*/
33 | [Bb]uild[Ll]og.*
34 |
35 | # NUNIT
36 | *.VisualState.xml
37 | TestResult.xml
38 |
39 | # Build Results of an ATL Project
40 | [Dd]ebugPS/
41 | [Rr]eleasePS/
42 | dlldata.c
43 |
44 | # DNX
45 | project.lock.json
46 | artifacts/
47 |
48 | *_i.c
49 | *_p.c
50 | *_i.h
51 | *.ilk
52 | *.meta
53 | *.obj
54 | *.pch
55 | *.pdb
56 | *.pgc
57 | *.pgd
58 | *.rsp
59 | *.sbr
60 | *.tlb
61 | *.tli
62 | *.tlh
63 | *.tmp
64 | *.tmp_proj
65 | *.log
66 | *.vspscc
67 | *.vssscc
68 | .builds
69 | *.pidb
70 | *.svclog
71 | *.scc
72 |
73 | # Chutzpah Test files
74 | _Chutzpah*
75 |
76 | # Visual C++ cache files
77 | ipch/
78 | *.aps
79 | *.ncb
80 | *.opendb
81 | *.opensdf
82 | *.sdf
83 | *.cachefile
84 | *.VC.db
85 | *.VC.VC.opendb
86 |
87 | # Visual Studio profiler
88 | *.psess
89 | *.vsp
90 | *.vspx
91 | *.sap
92 |
93 | # TFS 2012 Local Workspace
94 | $tf/
95 |
96 | # Guidance Automation Toolkit
97 | *.gpState
98 |
99 | # ReSharper is a .NET coding add-in
100 | _ReSharper*/
101 | *.[Rr]e[Ss]harper
102 | *.DotSettings.user
103 |
104 | # JustCode is a .NET coding add-in
105 | .JustCode
106 |
107 | # TeamCity is a build add-in
108 | _TeamCity*
109 |
110 | # DotCover is a Code Coverage Tool
111 | *.dotCover
112 |
113 | # NCrunch
114 | _NCrunch_*
115 | .*crunch*.local.xml
116 | nCrunchTemp_*
117 |
118 | # MightyMoose
119 | *.mm.*
120 | AutoTest.Net/
121 |
122 | # Web workbench (sass)
123 | .sass-cache/
124 |
125 | # Installshield output folder
126 | [Ee]xpress/
127 |
128 | # DocProject is a documentation generator add-in
129 | DocProject/buildhelp/
130 | DocProject/Help/*.HxT
131 | DocProject/Help/*.HxC
132 | DocProject/Help/*.hhc
133 | DocProject/Help/*.hhk
134 | DocProject/Help/*.hhp
135 | DocProject/Help/Html2
136 | DocProject/Help/html
137 |
138 | # Click-Once directory
139 | publish/
140 |
141 | # Publish Web Output
142 | *.[Pp]ublish.xml
143 | *.azurePubxml
144 | # TODO: Comment the next line if you want to checkin your web deploy settings
145 | # but database connection strings (with potential passwords) will be unencrypted
146 | *.pubxml
147 | *.publishproj
148 |
149 | # Microsoft Azure Web App publish settings. Comment the next line if you want to
150 | # checkin your Azure Web App publish settings, but sensitive information contained
151 | # in these scripts will be unencrypted
152 | PublishScripts/
153 |
154 | # NuGet Packages
155 | *.nupkg
156 | # The packages folder can be ignored because of Package Restore
157 | **/packages/*
158 | # except build/, which is used as an MSBuild target.
159 | !**/packages/build/
160 | # Uncomment if necessary however generally it will be regenerated when needed
161 | #!**/packages/repositories.config
162 | # NuGet v3's project.json files produces more ignoreable files
163 | *.nuget.props
164 | *.nuget.targets
165 |
166 | # Microsoft Azure Build Output
167 | csx/
168 | *.build.csdef
169 |
170 | # Microsoft Azure Emulator
171 | ecf/
172 | rcf/
173 |
174 | # Windows Store app package directories and files
175 | AppPackages/
176 | BundleArtifacts/
177 | Package.StoreAssociation.xml
178 | _pkginfo.txt
179 |
180 | # Visual Studio cache files
181 | # files ending in .cache can be ignored
182 | *.[Cc]ache
183 | # but keep track of directories ending in .cache
184 | !*.[Cc]ache/
185 |
186 | # Others
187 | ClientBin/
188 | ~$*
189 | *~
190 | *.dbmdl
191 | *.dbproj.schemaview
192 | *.pfx
193 | *.publishsettings
194 | node_modules/
195 | orleans.codegen.cs
196 |
197 | # Since there are multiple workflows, uncomment next line to ignore bower_components
198 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
199 | #bower_components/
200 |
201 | # RIA/Silverlight projects
202 | Generated_Code/
203 |
204 | # Backup & report files from converting an old project file
205 | # to a newer Visual Studio version. Backup files are not needed,
206 | # because we have git ;-)
207 | _UpgradeReport_Files/
208 | Backup*/
209 | UpgradeLog*.XML
210 | UpgradeLog*.htm
211 |
212 | # SQL Server files
213 | *.mdf
214 | *.ldf
215 |
216 | # Business Intelligence projects
217 | *.rdl.data
218 | *.bim.layout
219 | *.bim_*.settings
220 |
221 | # Microsoft Fakes
222 | FakesAssemblies/
223 |
224 | # GhostDoc plugin setting file
225 | *.GhostDoc.xml
226 |
227 | # Node.js Tools for Visual Studio
228 | .ntvs_analysis.dat
229 |
230 | # Visual Studio 6 build log
231 | *.plg
232 |
233 | # Visual Studio 6 workspace options file
234 | *.opt
235 |
236 | # Visual Studio LightSwitch build output
237 | **/*.HTMLClient/GeneratedArtifacts
238 | **/*.DesktopClient/GeneratedArtifacts
239 | **/*.DesktopClient/ModelManifest.xml
240 | **/*.Server/GeneratedArtifacts
241 | **/*.Server/ModelManifest.xml
242 | _Pvt_Extensions
243 |
244 | # Paket dependency manager
245 | .paket/paket.exe
246 | paket-files/
247 |
248 | # FAKE - F# Make
249 | .fake/
250 |
251 | # JetBrains Rider
252 | .idea/
253 | *.sln.iml
254 |
255 | # SonarQube folder
256 | .sonarqube
--------------------------------------------------------------------------------
/.nuget/NuGet.Config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
--------------------------------------------------------------------------------
/ConfigureServer.ps1:
--------------------------------------------------------------------------------
1 |
2 | $webpi_download_path = Join-Path $env:TEMP "arr.msi"
3 | $url = "http://download.microsoft.com/download/C/F/F/CFF3A0B8-99D4-41A2-AE1A-496C08BEB904/WebPlatformInstaller_amd64_en-US.msi"
4 |
5 | wget -TimeoutSec ([int]::MaxValue) -OutFile "$webpi_download_path" "$url" -UseBasicParsing
6 |
7 | import-module Dism
8 |
9 | Write-Verbose "Installing IIS"
10 | Enable-windowsoptionalfeature -All -Online -FeatureName IIS-HttpRedirect, IIS-ASPNET45, IIS-WebServerManagementTools, IIS-HttpTracing, IIS-WindowsAuthentication, IIS-NetFxExtensibility45, IIS-ApplicationDevelopment
11 |
12 | Import-Module WebAdministration
13 |
14 | Write-Verbose "Installing Arr"
15 | Start-Process $webpi_download_path '/qn' -PassThru | Wait-Process
16 | cd 'C:/Program Files/Microsoft/Web Platform Installer'; .\WebpiCmd.exe /Install /Products:'UrlRewrite2,ARRv3_0' /AcceptEULA
17 | Set-WebConfiguration system.webServer/proxy -value @{ enabled = "true" }
18 |
19 | Get-WebConfiguration `
20 | -pspath 'MACHINE/WEBROOT/APPHOST' `
21 | -filter "system.webServer/modules/add" -recurse | `
22 | where {$_.PSPath -eq 'MACHINE/WEBROOT/APPHOST' -and $_.Type -eq ''} `
23 | | foreach {
24 | $filter = "system.webServer/modules/add[@name='" + $_.Name + "']"
25 | Remove-WebConfigurationLock -filter $filter -verbose
26 | }
27 |
28 | & $env:windir\system32\inetsrv\appcmd unlock config /section:windowsAuthentication
29 | & $env:windir\system32\inetsrv\appcmd unlock config /section:anonymousAuthentication
--------------------------------------------------------------------------------
/GenerateTokenizedConfig.ps1:
--------------------------------------------------------------------------------
1 | function XmlDocTransform($xml, $xdt)
2 | {
3 | if (!$xml -or !(Test-Path -path $xml -PathType Leaf)) {
4 | throw "File not found. $xml";
5 | }
6 | if (!$xdt -or !(Test-Path -path $xdt -PathType Leaf)) {
7 | throw "File not found. $xdt";
8 | }
9 |
10 | Add-Type -LiteralPath "$PSScriptRoot\Microsoft.Web.XmlTransform.dll"
11 |
12 | $xmldoc = New-Object Microsoft.Web.XmlTransform.XmlTransformableDocument;
13 | $xmldoc.PreserveWhitespace = $true
14 | $xmldoc.Load($xml);
15 |
16 | $transf = New-Object Microsoft.Web.XmlTransform.XmlTransformation($xdt);
17 | if ($transf.Apply($xmldoc) -eq $false)
18 | {
19 | throw "Transformation failed."
20 | }
21 | $xmldoc.Save($xml);
22 | }
23 |
24 | Copy-Item "$PSScriptRoot\web-user.config" "$PSScriptRoot\web-user-tokenized.config" -Force
25 |
26 | XmlDocTransform "$PSScriptRoot\web-user-tokenized.config" "$PSScriptRoot\web-user-transform.xml"
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Mike
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 |
--------------------------------------------------------------------------------
/Microsoft.Web.XmlTransform.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/Microsoft.Web.XmlTransform.dll
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Iis Remote User Token Authentication aka: Single Sign using HTTP headers.
2 |
3 | A Custom Http Handler that implements RUTA for: https://jira.sonarsource.com/browse/SONAR-5430, This will allow single sign on for windows active directory users. Note: This was previously supported by: https://github.com/SonarQubeCommunity/sonar-activedirectory however immediatly after development, that plugin was abandoned.
4 |
5 | [](https://ci.appveyor.com/project/jabbera/iisremoteusertokenauthentication)
6 |
7 | # Administrivia
8 |
9 | SonarQube scanners DO NOT support anything other then basic\token based authentication. I've created a module that attempts to detect when the connecting application is a scanner or includes a token. When a scanner is detected the module will then bypass the windows authentication process. Right now the bypass conditions are:
10 | * If there is an Authorization header with Basic auth
11 | * this indicates a token is present
12 | * If the user agent of the request starts with any of the case-sensitive agent strings listed in the web.config setting: PassThruAgents
13 | * Initial configuration: sonar.scanner.app, SonarQubeScanner, ScannerCli, ScannerCLI, ScannerMSBuild, ScannerAzureDevOps
14 |
15 | I've only tested this with the MsBuild scanner so the agent list may need to be expanded.
16 |
17 | Previously the only way to enable single sign on was to have 2 websites, one for windows authentication, and one for token based authentication. The bypass module SHOULD remove the need for that second site. Until there is more exhaustive testing I will still include the multi-site installation instructions and artifacts. My plan is to remove those once the single site method is proven stable.
18 |
19 |
20 |
21 | # Installation (Single Site)
22 |
23 | These are the prefered installation directions.
24 |
25 | `Note: This configuration assumes sonarqube is running on the same server as IIS on port 9000. If this is not correct you will need to edit the reverse proxy rules in the web.config file to match your configuration.`
26 |
27 | 1) Configure sonarqube for RUTA per: https://jira.sonarsource.com/browse/SONAR-5430 (If default settings are used all you should need to do is add: sonar.web.sso.enable=true to the sonar.properties file and restart sonarqube.)
28 |
29 | 2) Download the current release and extract to [EXTRACT_FOLDER]
30 |
31 | 3) Run: ConfigureServer.ps1
32 | `Note: This installs the following windows features: IIS-HttpRedirect, IIS-ASPNET45, IIS-WebServerManagementTools, IIS-HttpTracing, IIS-WindowsAuthentication, IIS-NetFxExtensibility45, IIS-ApplicationDevelopment. It unlocks the IIS module ordering system wide as well as the authentication module configuration. It also installs ARR and UrlRewrite server wide.`
33 |
34 | 4) Create a website [WWWROOT], ssl required, pointing to a directory [WWWROOT_DIRECTORY] with a test file in it. Make sure you can browse to that file via your browser.
35 |
36 | 5) Copy: [EXTRACT_FOLDER]\inetpub-user to: [WWWROOT_DIRECTORY]
37 |
38 | 6) Browse to https://[WWWROOT] You should hopefully be signed in.
39 |
40 | 7) Test a scanner run with the url https://[WWWROOT]
41 |
42 | # Installation (Multi Site)
43 |
44 | These directions are only if you run into trouble with the single site method.
45 |
46 | The first site is for the browser and supports single sign on. [WWWROOT_USER] You will also need an unauthenticated one for supporting scanners [WWWROOT_SCANNER]. Please setup these websites ahead of time (ssl required) and make sure you can access index.html. DO NOT USE AN SNI based website for WWWROOT_SCANNER. Run it on a different port then 443. There is a bug that makes it unsupported.
47 |
48 | 1) Configure sonarqube for RUTA per: https://jira.sonarsource.com/browse/SONAR-5430 (If default settings are used all you should need to do is add: sonar.web.sso.enable=true to the sonar.properties file and restart sonarqube.)
49 |
50 | 2) Download the current release and extract to [EXTRACT_FOLDER]
51 |
52 | 3) Run: ConfigureServer.ps1
53 | `Note: This installs the following windows features: IIS-HttpRedirect, IIS-ASPNET45, IIS-WebServerManagementTools, IIS-HttpTracing, IIS-WindowsAuthentication, IIS-NetFxExtensibility45, IIS-ApplicationDevelopment. It unlocks the IIS module ordering system wide as well as the authentication module configuration. It also installs ARR and UrlRewrite server wide.`
54 |
55 | 4) Copy: [EXTRACT_FOLDER]\inetpub-user to: [WWWROOT_USER]
56 |
57 | 5) Remove the line: from the web.config file.
58 |
59 | 6) Browse to https://[WWWROOT_USER_URL] You should hopefully be signed in.
60 |
61 | `Note: This configuration assumes sonarqube is running on the same server as IIS on port 9000. If this is not correct you will need to edit the reverse proxy rules in the web.config file to match your configuration.`
62 |
63 | 7) Once you have the SSO working the only thing left is to configure the reverse proxy on [WWWROOT_SCANNER].
64 |
65 | 8) Copy:[EXTRACT_FOLDER]\inetpub-scanner to: [WWWROOT_SCANNER]
66 |
67 | 8) You should now be able to run a scanner configured to point at: https://[WWWROOT_SCANNER_URL] with token based authentication.
68 |
69 | # Configuration Options
70 |
71 | While this should work fine out of the box there are a few options that can be used all of which are configured by editing the applicationSettings section of the web.config file.
72 |
73 | 1) All of the the header names are configurable via the following settings that should be self explanitory: LoginHeader, NameHeader, EmailHeader, and GroupsHeader. Be sure the values match what SonarQube is expecting. (The defaults work if nothing is changed.)
74 |
75 | 2) DowncaseUsers - This will downcase all the usernames reguardless of what is in AD.
76 |
77 | 3) DowncaseGroups - This will downcase all the group names reguardless of what is in AD. (Useful for people migrating from the AD plugin who used the setting.)
78 |
79 | 4) AppendString - This will append whatever value of text you want to the end of each login and group. (Useful for people migrating from the AD plugin to append @domain)
80 |
81 | 5) AdUserBaseDsn - Only search for users under this specified OU
82 |
83 | 6) AdGroupBaseDsn - For users, only return groups that are in the following OU.
84 |
85 | 7) PassThruAgents - If you discover new user agent strings that are not bypassing windows authentication add them to this list. (Please open an issue or pull request also.)
86 |
87 | Note: For large AD trees setting AdUserBaseDsn and AdGroupBaseDsn can greatly improve performance.
88 |
89 |
--------------------------------------------------------------------------------
/RutaHttpModule.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 15
4 | VisualStudioVersion = 15.0.26403.7
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RutaHttpModule", "RutaHttpModule\RutaHttpModule.csproj", "{F582ADD6-A40A-45F2-A046-ABC48A350B16}"
7 | EndProject
8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RutaHttpModuleTest", "RutaHttpModuleTest\RutaHttpModuleTest.csproj", "{0B78B5EA-211C-411E-A43F-C87C9300CEAB}"
9 | EndProject
10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "runsettings", "runsettings", "{990AD533-A485-446D-9883-F694C7C24FB7}"
11 | ProjectSection(SolutionItems) = preProject
12 | test.runsettings = test.runsettings
13 | EndProjectSection
14 | EndProject
15 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0BCA4263-3544-4706-BFCB-664DEACB36EC}"
16 | ProjectSection(SolutionItems) = preProject
17 | ConfigureServer.ps1 = ConfigureServer.ps1
18 | GenerateTokenizedConfig.ps1 = GenerateTokenizedConfig.ps1
19 | README.md = README.md
20 | web-scanner.config = web-scanner.config
21 | web-user-transform.xml = web-user-transform.xml
22 | web-user.config = web-user.config
23 | EndProjectSection
24 | EndProject
25 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{618F4EA0-1C05-4DEF-961D-3E16BBE98ED2}"
26 | ProjectSection(SolutionItems) = preProject
27 | .nuget\NuGet.Config = .nuget\NuGet.Config
28 | EndProjectSection
29 | EndProject
30 | Global
31 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
32 | Debug|Any CPU = Debug|Any CPU
33 | Release|Any CPU = Release|Any CPU
34 | EndGlobalSection
35 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
36 | {F582ADD6-A40A-45F2-A046-ABC48A350B16}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
37 | {F582ADD6-A40A-45F2-A046-ABC48A350B16}.Debug|Any CPU.Build.0 = Debug|Any CPU
38 | {F582ADD6-A40A-45F2-A046-ABC48A350B16}.Release|Any CPU.ActiveCfg = Release|Any CPU
39 | {F582ADD6-A40A-45F2-A046-ABC48A350B16}.Release|Any CPU.Build.0 = Release|Any CPU
40 | {0B78B5EA-211C-411E-A43F-C87C9300CEAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
41 | {0B78B5EA-211C-411E-A43F-C87C9300CEAB}.Debug|Any CPU.Build.0 = Debug|Any CPU
42 | {0B78B5EA-211C-411E-A43F-C87C9300CEAB}.Release|Any CPU.ActiveCfg = Release|Any CPU
43 | {0B78B5EA-211C-411E-A43F-C87C9300CEAB}.Release|Any CPU.Build.0 = Release|Any CPU
44 | EndGlobalSection
45 | GlobalSection(SolutionProperties) = preSolution
46 | HideSolutionNode = FALSE
47 | EndGlobalSection
48 | EndGlobal
49 |
--------------------------------------------------------------------------------
/RutaHttpModule/AdInteraction.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.Diagnostics.CodeAnalysis;
6 | using System.DirectoryServices.AccountManagement;
7 | using System.Linq;
8 |
9 | internal class AdInteraction : IAdInteraction
10 | {
11 | private readonly ISettings settings;
12 |
13 | [ExcludeFromCodeCoverage]
14 | internal AdInteraction()
15 | : this(new SettingsWrapper())
16 | {
17 | }
18 |
19 | internal AdInteraction(ISettings settings) => this.settings = settings ?? throw new ArgumentNullException(nameof(settings));
20 |
21 | public (string login, string name, string email, IEnumerable groups) GetUserInformation(string domainUsername)
22 | {
23 | if (string.IsNullOrWhiteSpace(domainUsername)) throw new ArgumentNullException(nameof(domainUsername));
24 |
25 | string usernameOnly = domainUsername.RemoveDomain();
26 |
27 | using (PrincipalContext context = new PrincipalContext(ContextType.Domain))
28 | using (UserPrincipal user = UserPrincipal.FindByIdentity(context, IdentityType.SamAccountName, usernameOnly))
29 | {
30 | if (user == null || !MatchesOneUserDn(user.DistinguishedName))
31 | {
32 | return (null, null, null, null);
33 | }
34 |
35 | string login = usernameOnly;
36 | string name = user.Name;
37 | string email = user.EmailAddress;
38 | string[] groups = user.GetGroupsFast(this.settings.AdGroupBaseDn).ToArray();
39 |
40 | return (login, name, email, groups);
41 | }
42 | }
43 |
44 | private bool MatchesOneUserDn(string userDn)
45 | {
46 | if (this.settings.AdUserBaseDns.Length == 0)
47 | {
48 | return true;
49 | }
50 |
51 | return this.settings.AdUserBaseDns.Any(x => userDn.EndsWith(x, StringComparison.OrdinalIgnoreCase));
52 | }
53 | }
54 | }
--------------------------------------------------------------------------------
/RutaHttpModule/IAdInteraction.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | using System.Collections.Generic;
4 |
5 | internal interface IAdInteraction
6 | {
7 | (string login, string name, string email, IEnumerable groups) GetUserInformation(string domainUsername);
8 | }
9 | }
--------------------------------------------------------------------------------
/RutaHttpModule/IRutaHttpContext.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | internal interface IRutaHttpContext
4 | {
5 | bool IsWindowsUser { get; }
6 | bool IsAuthenticated { get; }
7 | string DomainUserName { get; }
8 | void RemoveRequestHeader(string header);
9 | void AddRequestHeader(string header, string value);
10 | }
11 | }
--------------------------------------------------------------------------------
/RutaHttpModule/ISettings.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | internal interface ISettings
4 | {
5 | string LoginHeader { get; }
6 | string NameHeader { get; }
7 | string EmailHeader { get; }
8 | string GroupsHeader { get; }
9 | bool DowncaseUsers { get; }
10 | bool DowncaseGroups { get; }
11 | string AppendString { get; }
12 | string[] AdUserBaseDns { get; }
13 | string AdGroupBaseDn { get; }
14 | string[] PassThruUserAgents { get; }
15 | }
16 | }
--------------------------------------------------------------------------------
/RutaHttpModule/ISonarAuthPassthroughHttpContext.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | using System.Security.Principal;
4 |
5 | internal interface ISonarAuthPassthroughHttpContext
6 | {
7 | IPrincipal User { get; set; }
8 | bool HasTokenHeader { get; }
9 | string UserAgent { get; }
10 | bool SkipAuthorization { get; set; }
11 | }
12 | }
--------------------------------------------------------------------------------
/RutaHttpModule/ITraceSource.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | using System.Diagnostics;
4 |
5 | ///
6 | /// Interface for implementations sending trace messages.
7 | ///
8 | internal interface ITraceSource
9 | {
10 | ///
11 | /// Writes a trace event message to the trace listeners using the specified event type,
12 | /// event identifier, and message.
13 | ///
14 | /// One of the enumeration values that specifies the event type of the trace data.
15 | /// An event identifier.
16 | /// The trace message to write.
17 | void TraceEvent(TraceEventType eventType, int id, string message);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/RutaHttpModule/LdapExtensions.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | using System;
4 | using System.Collections.Generic;
5 | using System.DirectoryServices;
6 | using System.DirectoryServices.AccountManagement;
7 | using System.DirectoryServices.ActiveDirectory;
8 |
9 | /// This monstrosity exists because is so slow. (20+ seconds)
10 | internal static class LdapExtensions
11 | {
12 | private const string MembershipFilterFormatStringAllGroups = "(&(|(samAccountType=268435456)(samAccountType=268435457)(samAccountType=536870912)(samAccountType=536870913))(member:1.2.840.113556.1.4.1941:={0}))";
13 | private const string ADAttribute_CommonName = "cn";
14 | private const string LDAPPathPrefix = "LDAP://";
15 |
16 | internal static IEnumerable GetGroupsFast(this UserPrincipal user, string groupsContainer)
17 | {
18 | if (user == null) throw new ArgumentNullException(nameof(user));
19 |
20 | using (var groupsDirectoryEntry = BindToContainer(groupsContainer))
21 | {
22 | return SearchForUsersGroupCommonNames(groupsDirectoryEntry, user.DistinguishedName);
23 | }
24 | }
25 |
26 | internal static string RemoveDomain(this string domainUsername)
27 | {
28 | string[] parts = domainUsername.Split('\\');
29 | if (parts.Length != 2)
30 | {
31 | return domainUsername;
32 | }
33 |
34 | return parts[1];
35 | }
36 |
37 | private static IEnumerable SearchForUsersGroupCommonNames(DirectoryEntry groupContainer, string userDistinguishedName)
38 | {
39 | if (userDistinguishedName == null) throw new ArgumentNullException(nameof(userDistinguishedName));
40 |
41 | using (var groupSearcher = new DirectorySearcher(groupContainer))
42 | {
43 | groupSearcher.Filter = string.Format(MembershipFilterFormatStringAllGroups, userDistinguishedName);
44 | groupSearcher.SearchRoot = groupContainer;
45 | groupSearcher.PropertiesToLoad.AddRange(new[] { ADAttribute_CommonName });
46 | using (var searchResultCollection = groupSearcher.FindAll())
47 | {
48 | var groupNames = new List(searchResultCollection.Count);
49 | foreach (SearchResult searchResult in searchResultCollection)
50 | {
51 | var groupName = searchResult.Properties[ADAttribute_CommonName][0] as string;
52 | groupNames.Add(groupName);
53 | }
54 | return groupNames;
55 | }
56 | }
57 | }
58 |
59 | private static DirectoryEntry BindToContainer(string container)
60 | {
61 | string path = null;
62 | if (!string.IsNullOrWhiteSpace(container))
63 | {
64 | path = LDAPPathPrefix + container;
65 | }
66 |
67 | return new DirectoryEntry(path, null, null,
68 | AuthenticationTypes.FastBind
69 | | AuthenticationTypes.Sealing
70 | | AuthenticationTypes.ReadonlyServer // request closest read-only directory service
71 | | AuthenticationTypes.Signing);
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/RutaHttpModule/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | [assembly: AssemblyTitle("RutaHttpModule")]
6 | [assembly: AssemblyDescription("")]
7 | [assembly: AssemblyConfiguration("")]
8 | [assembly: AssemblyCompany("")]
9 | [assembly: AssemblyProduct("RutaHttpModule")]
10 | [assembly: AssemblyCopyright("Copyright © 2016")]
11 | [assembly: AssemblyTrademark("")]
12 | [assembly: AssemblyCulture("")]
13 | [assembly: ComVisible(false)]
14 | [assembly: Guid("f582add6-a40a-45f2-a046-abc48a350b16")]
15 |
16 | [assembly: AssemblyVersion("1.0.0.1")]
17 | [assembly: AssemblyFileVersion("1.0.0.1")]
18 | [assembly: AssemblyInformationalVersion("1.0.0.1")]
19 |
20 | [assembly: InternalsVisibleTo("RutaHttpModuleTest")]
21 | [assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
--------------------------------------------------------------------------------
/RutaHttpModule/Properties/Settings.Designer.cs:
--------------------------------------------------------------------------------
1 | //------------------------------------------------------------------------------
2 | //
3 | // This code was generated by a tool.
4 | // Runtime Version:4.0.30319.42000
5 | //
6 | // Changes to this file may cause incorrect behavior and will be lost if
7 | // the code is regenerated.
8 | //
9 | //------------------------------------------------------------------------------
10 |
11 | namespace RutaHttpModule.Properties {
12 |
13 |
14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()]
15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "15.1.0.0")]
16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase {
17 |
18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings())));
19 |
20 | public static Settings Default {
21 | get {
22 | return defaultInstance;
23 | }
24 | }
25 |
26 | [global::System.Configuration.ApplicationScopedSettingAttribute()]
27 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
28 | [global::System.Configuration.DefaultSettingValueAttribute("X-Forwarded-Login")]
29 | public string LoginHeader {
30 | get {
31 | return ((string)(this["LoginHeader"]));
32 | }
33 | }
34 |
35 | [global::System.Configuration.ApplicationScopedSettingAttribute()]
36 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
37 | [global::System.Configuration.DefaultSettingValueAttribute("X-Forwarded-Name")]
38 | public string NameHeader {
39 | get {
40 | return ((string)(this["NameHeader"]));
41 | }
42 | }
43 |
44 | [global::System.Configuration.ApplicationScopedSettingAttribute()]
45 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
46 | [global::System.Configuration.DefaultSettingValueAttribute("X-Forwarded-Email")]
47 | public string EmailHeader {
48 | get {
49 | return ((string)(this["EmailHeader"]));
50 | }
51 | }
52 |
53 | [global::System.Configuration.ApplicationScopedSettingAttribute()]
54 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
55 | [global::System.Configuration.DefaultSettingValueAttribute("X-Forwarded-Groups")]
56 | public string GroupsHeader {
57 | get {
58 | return ((string)(this["GroupsHeader"]));
59 | }
60 | }
61 |
62 | [global::System.Configuration.ApplicationScopedSettingAttribute()]
63 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
64 | [global::System.Configuration.DefaultSettingValueAttribute("True")]
65 | public bool DowncaseUsers {
66 | get {
67 | return ((bool)(this["DowncaseUsers"]));
68 | }
69 | }
70 |
71 | [global::System.Configuration.ApplicationScopedSettingAttribute()]
72 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
73 | [global::System.Configuration.DefaultSettingValueAttribute("\r\n")]
75 | public global::System.Collections.Specialized.StringCollection AdUserBaseDns {
76 | get {
77 | return ((global::System.Collections.Specialized.StringCollection)(this["AdUserBaseDns"]));
78 | }
79 | }
80 |
81 | [global::System.Configuration.ApplicationScopedSettingAttribute()]
82 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
83 | [global::System.Configuration.DefaultSettingValueAttribute("")]
84 | public string AdGroupBaseDn {
85 | get {
86 | return ((string)(this["AdGroupBaseDn"]));
87 | }
88 | }
89 |
90 | [global::System.Configuration.ApplicationScopedSettingAttribute()]
91 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
92 | [global::System.Configuration.DefaultSettingValueAttribute("True")]
93 | public bool DowncaseGroups {
94 | get {
95 | return ((bool)(this["DowncaseGroups"]));
96 | }
97 | }
98 |
99 | [global::System.Configuration.ApplicationScopedSettingAttribute()]
100 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
101 | [global::System.Configuration.DefaultSettingValueAttribute("")]
102 | public string AppendString {
103 | get {
104 | return ((string)(this["AppendString"]));
105 | }
106 | }
107 |
108 | [global::System.Configuration.ApplicationScopedSettingAttribute()]
109 | [global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
110 | [global::System.Configuration.DefaultSettingValueAttribute(@"
111 |
112 | sonar.scanner.app
113 | SonarQubeScanner
114 | ScannerCli
115 | ")]
116 | public global::System.Collections.Specialized.StringCollection PassThruAgents {
117 | get {
118 | return ((global::System.Collections.Specialized.StringCollection)(this["PassThruAgents"]));
119 | }
120 | }
121 | }
122 | }
123 |
--------------------------------------------------------------------------------
/RutaHttpModule/Properties/Settings.settings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | X-Forwarded-Login
7 |
8 |
9 | X-Forwarded-Name
10 |
11 |
12 | X-Forwarded-Email
13 |
14 |
15 | X-Forwarded-Groups
16 |
17 |
18 | True
19 |
20 |
21 | <?xml version="1.0" encoding="utf-16"?>
22 | <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" />
23 |
24 |
25 |
26 |
27 |
28 | True
29 |
30 |
31 |
32 |
33 |
34 | <?xml version="1.0" encoding="utf-16"?>
35 | <ArrayOfString xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
36 | <string>sonar.scanner.app</string>
37 | <string>SonarQubeScanner</string>
38 | <string>ScannerCli</string>
39 | </ArrayOfString>
40 |
41 |
42 |
--------------------------------------------------------------------------------
/RutaHttpModule/RutaHttpContext.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | using System;
4 | using System.Diagnostics.CodeAnalysis;
5 | using System.Security.Principal;
6 | using System.Web;
7 |
8 | [ExcludeFromCodeCoverage]
9 | internal class RutaHttpContext : IRutaHttpContext
10 | {
11 | private readonly HttpContext httpContext;
12 |
13 | internal RutaHttpContext(HttpContext httpContext) => this.httpContext = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
14 |
15 | public bool IsWindowsUser => this.httpContext.User is WindowsPrincipal;
16 | public string DomainUserName => this.httpContext.User?.Identity?.Name;
17 | public bool IsAuthenticated => this.httpContext.User?.Identity?.IsAuthenticated ?? false;
18 |
19 | public void RemoveRequestHeader(string header) => this.httpContext.Request.Headers.Remove(header);
20 | public void AddRequestHeader(string header, string value) => this.httpContext.Request.Headers.Add(header, value);
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/RutaHttpModule/RutaHttpModule.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | AnyCPU
7 | {F582ADD6-A40A-45F2-A046-ABC48A350B16}
8 | Library
9 | Properties
10 | RutaHttpModule
11 | RutaHttpModule
12 | v4.5
13 | 512
14 |
15 |
16 |
17 |
18 |
19 | true
20 | full
21 | false
22 | bin\Debug\
23 | DEBUG;TRACE
24 | prompt
25 | 4
26 | true
27 |
28 |
29 | pdbonly
30 | true
31 | bin\Release\
32 | TRACE
33 | prompt
34 | 4
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | True
60 | True
61 | Settings.settings
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | SettingsSingleFileGenerator
75 | Settings.Designer.cs
76 |
77 |
78 |
79 |
80 | 4.3.0
81 |
82 |
83 |
84 |
--------------------------------------------------------------------------------
/RutaHttpModule/RutaModule.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Concurrent;
2 |
3 | namespace RutaHttpModule
4 | {
5 | using System;
6 | using System.Diagnostics;
7 | using System.Diagnostics.CodeAnalysis;
8 | using System.Linq;
9 | using System.Web;
10 |
11 | ///
12 | /// This module changes adds all header information needed for interaction with SonarQube and
13 | /// provide a SSO experience. An existing Authorization header will be removed.
14 | ///
15 | ///
16 | /// See also https://www.iis.net/learn/develop/runtime-extensibility/how-to-add-tracing-to-iis-managed-modules
17 | ///
18 | public sealed class RutaModule : IHttpModule
19 | {
20 | ///
21 | /// Reference to the implementation needed to query the AD.
22 | ///
23 | private readonly IAdInteraction adInteraction;
24 |
25 | ///
26 | /// Reference to the settings needed for constructing the right header.
27 | ///
28 | private readonly ISettings settings;
29 |
30 | ///
31 | /// Reference to the tracing object.
32 | ///
33 | private readonly ITraceSource traceSource;
34 |
35 | private readonly ConcurrentDictionary cache =
36 | new ConcurrentDictionary();
37 |
38 | ///
39 | /// Initializes a new instance of the a object
40 | ///
41 | [ExcludeFromCodeCoverage]
42 | public RutaModule()
43 | : this(new AdInteraction(), new SettingsWrapper(), new RutaTraceSource(nameof(RutaHttpModule)))
44 | {
45 | }
46 |
47 | ///
48 | /// Initializes a new instance of the a object
49 | ///
50 | /// Reference to the implementation needed to query the AD.
51 | /// Reference to the settings needed for constructing the right header.
52 | /// Reference to the tracing object.
53 | internal RutaModule(IAdInteraction adInteraction, ISettings settings, ITraceSource traceSource)
54 | {
55 | this.adInteraction = adInteraction ?? throw new ArgumentNullException(nameof(adInteraction));
56 | this.settings = settings ?? throw new ArgumentNullException(nameof(settings));
57 | this.traceSource = traceSource ?? throw new ArgumentNullException(nameof(traceSource));
58 | }
59 |
60 | ///
61 | /// The name of the module
62 | ///
63 | public string ModuleName => nameof(RutaModule);
64 |
65 | ///
66 | /// Initializes a module and prepares it to handle requests (part of the interface).
67 | ///
68 | /// An that provides access to the methods, properties,
69 | /// and events common to all application objects within an ASP.NET application
70 | public void Init(HttpApplication application) => application.AuthorizeRequest += AuthorizeRequest;
71 |
72 | ///
73 | /// Disposes of the resources (other than memory) used this module (part of the interface).
74 | ///
75 | public void Dispose() { }
76 |
77 | ///
78 | /// Handler executed when a security module has verified user authorization. During execution of the handler the
79 | /// httpContext will be changed.
80 | ///
81 | /// The source of the event.
82 | /// An object that contains no event data.
83 | [ExcludeFromCodeCoverage]
84 | private void AuthorizeRequest(object source, EventArgs e)
85 | {
86 | var application = source as HttpApplication;
87 | HandleAuthorizeRequest(new RutaHttpContext(application.Context));
88 | }
89 |
90 | ///
91 | /// Handle an authorizeRequest using the .
92 | ///
93 | /// The context to be used when handling an AuthorizeRequest
94 | internal void HandleAuthorizeRequest(IRutaHttpContext context)
95 | {
96 | try
97 | {
98 | traceSource.TraceEvent(TraceEventType.Start, 0, "START AuthorizeRequest");
99 | HandleAuthorizeRequestInternal(context);
100 | }
101 | catch (Exception ex)
102 | {
103 | traceSource.TraceEvent(TraceEventType.Error, 0, $"ERROR AuthorizeRequest: ExceptionData: '{ex}' ");
104 | throw;
105 | }
106 | finally
107 | {
108 | traceSource.TraceEvent(TraceEventType.Stop, 0, "END AuthorizeRequest");
109 | }
110 | }
111 |
112 | ///
113 | /// Replace the current context with a version that can be processed by SonarQube. That is done only
114 | /// if:
115 | ///
116 | /// - The user is authenticated.
117 | /// - The context contains a DomainUserName.
118 | /// - The Ldap information could be extracted.
119 | /// - The user information can be retrieved from AD.
120 | ///
121 | ///
122 | /// The context on which the action should be performed.
123 | private void HandleAuthorizeRequestInternal(IRutaHttpContext context)
124 | {
125 | if (!context.IsAuthenticated)
126 | {
127 | traceSource.TraceEvent(TraceEventType.Information, 0, "Not authenticated");
128 | return;
129 | }
130 |
131 | if (!context.IsWindowsUser)
132 | {
133 | traceSource.TraceEvent(TraceEventType.Information, 0, "Not a windows user");
134 | return;
135 | }
136 |
137 | string userName = context.DomainUserName;
138 | if (string.IsNullOrWhiteSpace(userName))
139 | {
140 | traceSource.TraceEvent(TraceEventType.Information, 0, "No username in context");
141 | return;
142 | }
143 |
144 | (string loginToSend, string name, string email, string[] groupsToSend) = this.GetUserInformation(userName);
145 | if (loginToSend == null || name == null || email == null)
146 | {
147 | traceSource.TraceEvent(TraceEventType.Information, 0, "No data available");
148 | return;
149 | }
150 |
151 | if (groupsToSend == null)
152 | {
153 | groupsToSend = new string[0];
154 | }
155 |
156 | traceSource.TraceEvent(TraceEventType.Information, 0, "Set headers");
157 |
158 | context.RemoveRequestHeader("Authorization"); // Remove the authorzation header since we are in charge of authentication
159 | context.AddRequestHeader(this.settings.LoginHeader, loginToSend);
160 | context.AddRequestHeader(this.settings.NameHeader, name);
161 | if (!string.IsNullOrWhiteSpace(email))
162 | {
163 | context.AddRequestHeader(this.settings.EmailHeader, email);
164 | }
165 |
166 | if (groupsToSend?.Length > 0)
167 | {
168 | context.AddRequestHeader(this.settings.GroupsHeader, string.Join(",", groupsToSend));
169 | }
170 | }
171 |
172 | private (string login, string name, string email, string[] groups) GetUserInformation(string userName)
173 | {
174 | if (this.cache.TryGetValue(userName, out var cachedValues))
175 | {
176 | traceSource.TraceEvent(TraceEventType.Information, 0, "Returning cached data.");
177 | return cachedValues;
178 | }
179 |
180 | var userInformation = this.adInteraction.GetUserInformation(userName);
181 | if (string.IsNullOrWhiteSpace(userInformation.login))
182 | {
183 | traceSource.TraceEvent(TraceEventType.Information, 0, "No user information in context");
184 | return (null, null, null, null);
185 | }
186 |
187 | traceSource.TraceEvent(TraceEventType.Information, 0, "Bulding header results and caching");
188 |
189 | string loginToSend = ApplyUserSettings(userInformation.login);
190 | string[] groupsToSend = userInformation.groups.Where(group => !string.IsNullOrWhiteSpace(group))
191 | .Select(ApplyGroupSettings)
192 | .ToArray();
193 |
194 | this.cache.TryAdd(userName, (loginToSend, userInformation.name, userInformation.email, groupsToSend));
195 |
196 | return (loginToSend, userInformation.name, userInformation.email, groupsToSend);
197 | }
198 |
199 | ///
200 | /// Returns a copy of the object adjusted as necessary based on
201 | /// the settings (DowncaseGroups and AppendString).
202 | ///
203 | /// A on which the action should be performed.
204 | /// The modified version of .
205 | private string ApplyGroupSettings(string group) => AppendIfNeeded(LowercaseIfNeeded(group, this.settings.DowncaseGroups));
206 |
207 | ///
208 | /// Returns a copy of the object adjusted as necessary based on
209 | /// the settings (DowncaseUsers and AppendString).
210 | ///
211 | /// A on which the action should be performed.
212 | /// The modified version of .
213 | private string ApplyUserSettings(string user) => AppendIfNeeded(LowercaseIfNeeded(user, this.settings.DowncaseUsers));
214 |
215 | ///
216 | /// Returns a copy of the object appended with the contents of the
217 | /// AppendString settings. The action will not be performed if or
218 | /// AppendString are null or containing only whitespace. In all other cases
219 | /// will be returned.
220 | ///
221 | /// A on which the action should be performed.
222 | /// The modified version of .
223 | private string AppendIfNeeded(string source) => string.IsNullOrWhiteSpace(this.settings.AppendString) ? source : $"{source}{this.settings.AppendString}";
224 |
225 | ///
226 | /// Returns a copy of the object converted to lowercase
227 | /// using the casing rules of the invariant culture if indicated by .
228 | /// In all other cases will be returned.
229 | ///
230 | /// A on which the action should be performed.
231 | /// Should the method return a lowercase version of ?
232 | /// The modified version of .
233 | private static string LowercaseIfNeeded(string source, bool applyLowercase) => applyLowercase ? source.ToLowerInvariant() : source;
234 | }
235 | }
236 |
--------------------------------------------------------------------------------
/RutaHttpModule/RutaTraceSource.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | using System;
4 | using System.Diagnostics;
5 | using System.Diagnostics.CodeAnalysis;
6 |
7 | ///
8 | /// Wrapper class for using .
9 | ///
10 | [ExcludeFromCodeCoverage]
11 | internal class RutaTraceSource : ITraceSource
12 | {
13 | ///
14 | /// The name of the trace source (typically, the name of the application).
15 | ///
16 | private readonly string tracesourceName;
17 |
18 | ///
19 | /// Internal reference.
20 | ///
21 | private readonly TraceSource traceSource;
22 |
23 | ///
24 | /// Creates a based on the supplied
25 | /// .
26 | ///
27 | /// The name of the trace source.
28 | internal RutaTraceSource(string tracesourceName)
29 | {
30 | this.traceSource = new TraceSource(tracesourceName);
31 | this.tracesourceName = tracesourceName;
32 | }
33 |
34 | ///
35 | /// Writes a trace event message to the trace listeners using the specified event type,
36 | /// event identifier, and message.
37 | ///
38 | /// One of the enumeration values that specifies the event type of the trace data.
39 | /// An event identifier.
40 | /// The trace message to write.
41 | public void TraceEvent(TraceEventType eventType, int id, string message)
42 | {
43 | traceSource.TraceEvent(eventType, id, $"[{tracesourceName} MODULE] {message}");
44 | }
45 | }
46 | }
--------------------------------------------------------------------------------
/RutaHttpModule/SettingsWrapper.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | using System.Diagnostics.CodeAnalysis;
4 | using System.Linq;
5 |
6 | [ExcludeFromCodeCoverage]
7 | internal class SettingsWrapper : ISettings
8 | {
9 | public string AdGroupBaseDn => Properties.Settings.Default.AdGroupBaseDn;
10 | public string[] AdUserBaseDns => Properties.Settings.Default.AdUserBaseDns.Cast().ToArray();
11 | public bool DowncaseUsers => Properties.Settings.Default.DowncaseUsers;
12 | public bool DowncaseGroups => Properties.Settings.Default.DowncaseGroups;
13 | public string EmailHeader => Properties.Settings.Default.EmailHeader;
14 | public string GroupsHeader => Properties.Settings.Default.GroupsHeader;
15 | public string LoginHeader => Properties.Settings.Default.LoginHeader;
16 | public string NameHeader => Properties.Settings.Default.NameHeader;
17 | public string AppendString => Properties.Settings.Default.AppendString;
18 | public string[] PassThruUserAgents { get; } = Properties.Settings.Default.PassThruAgents.Cast().ToArray();
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/RutaHttpModule/SonarAuthPassthroughHttpContext.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | using System;
4 | using System.Diagnostics.CodeAnalysis;
5 | using System.Security.Principal;
6 | using System.Web;
7 |
8 | [ExcludeFromCodeCoverage]
9 | internal class SonarAuthPassthroughHttpContext : ISonarAuthPassthroughHttpContext
10 | {
11 | private const string AUTHORIZATION_HEADER_NAME = "Authorization";
12 | private const string BASIC_PREAMBLE = "Basic ";
13 | private const string USER_AGENT_HEADER_NAME = "User-Agent";
14 |
15 | private static readonly string[] PASS_THRU_AGENT_NAMES = { };
16 |
17 | private readonly HttpContext httpContext;
18 |
19 | internal SonarAuthPassthroughHttpContext(HttpContext httpContext) => this.httpContext = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
20 |
21 | public IPrincipal User
22 | {
23 | get { return this.httpContext.User; }
24 | set { this.httpContext.User = value; }
25 | }
26 |
27 | public bool HasTokenHeader => httpContext.Request?.Headers[AUTHORIZATION_HEADER_NAME]?.StartsWith(BASIC_PREAMBLE) ?? false;
28 |
29 | public string UserAgent => httpContext.Request?.Headers["User-Agent"];
30 | public bool SkipAuthorization
31 | {
32 | get { return httpContext.SkipAuthorization; }
33 | set { httpContext.SkipAuthorization = value; }
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/RutaHttpModule/SonarAuthPassthroughModule.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModule
2 | {
3 | using System;
4 | using System.Diagnostics;
5 | using System.Diagnostics.CodeAnalysis;
6 | using System.Linq;
7 | using System.Security.Principal;
8 | using System.Web;
9 |
10 | ///
11 | /// The goal of this class is to determine which traffic is from a sonar scanner\lint and allow it to pass through since it's likely
12 | /// using token based auth and\or accesssing an unrestricted endpoint.
13 | ///
14 | public sealed class SonarAuthPassthroughModule : IHttpModule
15 | {
16 | private static readonly IPrincipal passThruUser = new PassThruUser();
17 |
18 | private readonly ISettings settings;
19 | private readonly ITraceSource traceSource;
20 |
21 | ///
22 | /// Initializes a new instance of the a object
23 | ///
24 | [ExcludeFromCodeCoverage]
25 | public SonarAuthPassthroughModule()
26 | : this(new SettingsWrapper(), new RutaTraceSource(nameof(SonarAuthPassthroughModule)))
27 | {
28 | }
29 |
30 | ///
31 | /// Initializes a new instance of the a object
32 | ///
33 | /// Reference to the tracing object.
34 | internal SonarAuthPassthroughModule(ISettings settings, ITraceSource traceSource)
35 | {
36 | this.settings = settings ?? throw new ArgumentNullException(nameof(settings));
37 | this.traceSource = traceSource ?? throw new ArgumentNullException(nameof(traceSource));
38 | }
39 |
40 | ///
41 | /// Initializes a module and prepares it to handle requests (part of the interface).
42 | ///
43 | /// An that provides access to the methods, properties,
44 | /// and events common to all application objects within an ASP.NET application
45 | [ExcludeFromCodeCoverage]
46 | public void Init(HttpApplication application) => application.AuthenticateRequest += AuthenticateRequest;
47 |
48 | [ExcludeFromCodeCoverage]
49 | public void Dispose() { }
50 |
51 | [ExcludeFromCodeCoverage]
52 | public string ModuleName => nameof(SonarAuthPassthroughModule);
53 |
54 | ///
55 | /// Handler executed when a security module has verified before authentication. During execution of the handler the
56 | /// httpContext will be changed.
57 | ///
58 | /// The source of the event.
59 | /// An object that contains no event data.
60 | [ExcludeFromCodeCoverage]
61 | private void AuthenticateRequest(object source, EventArgs e)
62 | {
63 | var application = source as HttpApplication;
64 | HandleAuthenticateRequest(new SonarAuthPassthroughHttpContext(application.Context));
65 | }
66 |
67 | ///
68 | /// Handle an authenticateRequest using the .
69 | ///
70 | /// The context to be used when handling an AuthorizeRequest
71 | internal void HandleAuthenticateRequest(ISonarAuthPassthroughHttpContext context)
72 | {
73 | try
74 | {
75 | traceSource.TraceEvent(TraceEventType.Start, 0, "START AuthenticateRequest");
76 | HandleAuthenticateRequestRequestInternal(context);
77 | }
78 | catch (Exception ex)
79 | {
80 | traceSource.TraceEvent(TraceEventType.Error, 0, $"ERROR AuthenticateRequest: ExceptionData: '{ex}' ");
81 | throw;
82 | }
83 | finally
84 | {
85 | traceSource.TraceEvent(TraceEventType.Stop, 0, "END AuthenticateRequest");
86 | }
87 | }
88 |
89 | private void HandleAuthenticateRequestRequestInternal(ISonarAuthPassthroughHttpContext context)
90 | {
91 | if (context.User != null)
92 | {
93 | traceSource.TraceEvent(TraceEventType.Information, 0, "Already authenticated");
94 | return;
95 | }
96 |
97 | // This is most efficent.
98 | if (context.HasTokenHeader)
99 | {
100 | traceSource.TraceEvent(TraceEventType.Information, 0, "Found token.");
101 | AssignPassThruUser(context);
102 | return;
103 | }
104 |
105 | // If we have no agent, or the agent does not match any of our pass thrus
106 | string userAgent = context.UserAgent;
107 | traceSource.TraceEvent(TraceEventType.Information, 0, $"UserAgent: '{userAgent}'");
108 |
109 | if (string.IsNullOrWhiteSpace(userAgent) || this.settings.PassThruUserAgents.Any(userAgent.StartsWith))
110 | {
111 | AssignPassThruUser(context);
112 | return;
113 | }
114 | }
115 |
116 | private void AssignPassThruUser(ISonarAuthPassthroughHttpContext context)
117 | {
118 | traceSource.TraceEvent(TraceEventType.Information, 0, "Assigning token user.");
119 |
120 | context.User = passThruUser;
121 | context.SkipAuthorization = true;
122 | }
123 |
124 | ///
125 | /// If the caller passes a token, create a fake authentication user so the call can just pass through to sonarqube.
126 | /// If the is set when the WindowsAuthentication module is called, it will
127 | /// just skip.
128 | ///
129 | private sealed class PassThruUser : IPrincipal
130 | {
131 | private class PassThruUserIdentity : IIdentity
132 | {
133 | public string AuthenticationType => "PassThru";
134 |
135 | public bool IsAuthenticated => true;
136 |
137 | public string Name => "Doesntmatter";
138 | }
139 |
140 | public IIdentity Identity => new PassThruUserIdentity();
141 |
142 | public bool IsInRole(string role)
143 | {
144 | throw new NotImplementedException();
145 | }
146 | }
147 | }
148 | }
--------------------------------------------------------------------------------
/RutaHttpModule/app.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | X-Forwarded-Login
12 |
13 |
14 | X-Forwarded-Name
15 |
16 |
17 | X-Forwarded-Email
18 |
19 |
20 | X-Forwarded-Groups
21 |
22 |
23 | True
24 |
25 |
26 |
27 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | True
36 |
37 |
38 |
39 |
40 |
41 |
42 |
44 | sonar.scanner.app
45 | SonarQubeScanner
46 | ScannerCli
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
--------------------------------------------------------------------------------
/RutaHttpModuleTest/AdInteractionTest.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Linq;
3 | using System.Security.Principal;
4 | using System.Text.RegularExpressions;
5 |
6 | using Microsoft.VisualStudio.TestTools.UnitTesting;
7 | using Moq;
8 | using RutaHttpModule;
9 |
10 | namespace RutaHttpModuleTest
11 | {
12 | [TestCategory("ActiveDirectoryRequired")]
13 | [TestClass]
14 | public class AdInteractionTest
15 | {
16 | private static Regex emailRegex = new Regex(@"[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", RegexOptions.Compiled);
17 |
18 | private AdInteraction adInteraction;
19 | private Mock settings;
20 |
21 | [TestInitialize]
22 | public void Init()
23 | {
24 | this.settings = new Mock();
25 | this.settings.SetupGet(x => x.AdGroupBaseDn).Returns(string.Empty);
26 | this.settings.SetupGet(x => x.AdUserBaseDns).Returns(new string[0]);
27 | this.settings.SetupGet(x => x.DowncaseUsers).Returns(true);
28 | this.settings.SetupGet(x => x.DowncaseGroups).Returns(true);
29 | this.settings.SetupGet(x => x.AppendString).Returns(string.Empty);
30 |
31 | this.adInteraction = new AdInteraction(this.settings.Object);
32 | }
33 |
34 | [TestMethod]
35 | [ExpectedException(typeof(ArgumentNullException))]
36 | public void NullSettingsTest()
37 | {
38 | new AdInteraction(null);
39 | }
40 |
41 | [TestMethod]
42 | public void BasicInfoTest()
43 | {
44 | var result = this.adInteraction.GetUserInformation(WindowsIdentity.GetCurrent().Name);
45 |
46 | Assert.IsTrue(WindowsIdentity.GetCurrent().Name.EndsWith(result.login, StringComparison.Ordinal));
47 | Assert.IsNotNull(result.name);
48 | Assert.IsTrue(emailRegex.IsMatch(result.email));
49 | Assert.IsTrue(result.groups?.Count()>= 0);
50 | }
51 |
52 | [TestMethod]
53 | [ExpectedException(typeof(ArgumentNullException))]
54 | public void NullDomainUserTest()
55 | {
56 | var result = this.adInteraction.GetUserInformation(null);
57 | }
58 |
59 | [TestMethod]
60 | public void NoSuchUserTest()
61 | {
62 | var result = this.adInteraction.GetUserInformation("Domain\\NoSuchUserShouldExist");
63 | Assert.IsNull(result.login);
64 | }
65 |
66 | [TestMethod]
67 | public void DefaultNoDomainTest()
68 | {
69 | var result = this.adInteraction.GetUserInformation(WindowsIdentity.GetCurrent().Name.Split('\\')[1]);
70 | Assert.IsTrue(WindowsIdentity.GetCurrent().Name.EndsWith(result.login, StringComparison.Ordinal));
71 | }
72 |
73 | [TestMethod]
74 | public void GroupDnFilterTest()
75 | {
76 | var result = this.adInteraction.GetUserInformation(WindowsIdentity.GetCurrent().Name);
77 |
78 | CollectionAssert.DoesNotContain(result.groups.ToArray(), "Domain Users");
79 | }
80 |
81 | [TestMethod]
82 | public void UserDnFilterTest()
83 | {
84 | this.settings.SetupGet(x => x.AdUserBaseDns).Returns(new[] {"DON'T MATCH"});
85 | var result = this.adInteraction.GetUserInformation(WindowsIdentity.GetCurrent().Name);
86 |
87 | Assert.IsNull(result.login);
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/RutaHttpModuleTest/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.InteropServices;
3 |
4 | [assembly: AssemblyTitle("RutaHttpModuleTest")]
5 | [assembly: AssemblyDescription("")]
6 | [assembly: AssemblyConfiguration("")]
7 | [assembly: AssemblyCompany("")]
8 | [assembly: AssemblyProduct("RutaHttpModuleTest")]
9 | [assembly: AssemblyCopyright("Copyright © 2016")]
10 | [assembly: AssemblyTrademark("")]
11 | [assembly: AssemblyCulture("")]
12 | [assembly: ComVisible(false)]
13 | [assembly: Guid("0b78b5ea-211c-411e-a43f-c87c9300ceab")]
14 |
15 | [assembly: AssemblyVersion("1.0.0.1")]
16 | [assembly: AssemblyFileVersion("1.0.0.1")]
17 |
--------------------------------------------------------------------------------
/RutaHttpModuleTest/RutaHttpModuleTest.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | {0B78B5EA-211C-411E-A43F-C87C9300CEAB}
7 | Library
8 | Properties
9 | RutaHttpModuleTest
10 | RutaHttpModuleTest
11 | v4.5
12 | 512
13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}
14 | 15.0
15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)
16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages
17 | False
18 | UnitTest
19 |
20 |
21 |
22 |
23 |
24 | true
25 | full
26 | false
27 | bin\Debug\
28 | DEBUG;TRACE
29 | prompt
30 | 4
31 |
32 |
33 | pdbonly
34 | true
35 | bin\Release\
36 | TRACE
37 | prompt
38 | 4
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 | 4.7.8
63 |
64 |
65 | 1.1.14
66 |
67 |
68 | 1.1.14
69 |
70 |
71 |
72 |
73 | {f582add6-a40a-45f2-a046-abc48a350b16}
74 | RutaHttpModule
75 |
76 |
77 |
78 |
79 |
--------------------------------------------------------------------------------
/RutaHttpModuleTest/RutaModuleTest.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModuleTest
2 | {
3 | using System;
4 | using System.Diagnostics;
5 | using System.Linq;
6 | using System.Web;
7 |
8 | using Microsoft.VisualStudio.TestTools.UnitTesting;
9 | using Moq;
10 | using RutaHttpModule;
11 |
12 | [TestClass]
13 | public class RutaModuleTest
14 | {
15 | private RutaModule rutaModule;
16 | private Mock settings;
17 | private Mock adInteraction;
18 | private Mock traceSource;
19 | private Mock httpContext;
20 |
21 | private (string header, string loginValue) login;
22 | private (string header, string nameValue) name;
23 | private (string header, string emailValue) email;
24 | private (string header, string[] groupsValue) groups;
25 |
26 | [TestInitialize]
27 | public void TestInit()
28 | {
29 | this.settings = new Mock();
30 | this.adInteraction = new Mock();
31 | this.traceSource = new Mock();
32 | this.httpContext = new Mock();
33 |
34 | login = ("1", "Mike");
35 | name = ("2", "Dane");
36 | email = ("3", "Mike@Dan.com");
37 | groups = ("4", new[] { "A", "B", "C" });
38 |
39 | this.rutaModule = new RutaModule(this.adInteraction.Object, this.settings.Object, this.traceSource.Object);
40 | }
41 |
42 | [TestMethod]
43 | public void NoAuthenticationTest()
44 | {
45 | // Arrange
46 | this.httpContext.SetupGet(x => x.IsAuthenticated).Returns(false);
47 |
48 | // Act
49 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
50 |
51 | // Assert
52 | this.httpContext.Verify(x => x.RemoveRequestHeader(It.IsAny()), Times.Never());
53 | this.httpContext.Verify(x => x.AddRequestHeader(It.IsAny(), It.IsAny()), Times.Never());
54 | }
55 |
56 | [TestMethod]
57 | public void NotWindowsUserTest()
58 | {
59 | // Arrange
60 | this.httpContext.SetupGet(x => x.IsWindowsUser).Returns(false);
61 |
62 | // Act
63 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
64 |
65 | // Assert
66 | this.httpContext.Verify(x => x.RemoveRequestHeader(It.IsAny()), Times.Never());
67 | this.httpContext.Verify(x => x.AddRequestHeader(It.IsAny(), It.IsAny()), Times.Never());
68 | }
69 |
70 | [TestMethod]
71 | public void NoDomainUserNameTest()
72 | {
73 | // Arrange
74 | this.httpContext.SetupGet(x => x.IsAuthenticated).Returns(true);
75 | this.httpContext.SetupGet(x => x.DomainUserName).Returns((string)null);
76 |
77 | // Act
78 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
79 |
80 | // Assert
81 | this.httpContext.Verify(x => x.RemoveRequestHeader(It.IsAny()), Times.Never());
82 | this.httpContext.Verify(x => x.AddRequestHeader(It.IsAny(), It.IsAny()), Times.Never());
83 | }
84 |
85 | [TestMethod]
86 | public void WhiteSpaceDomainUserNameTest()
87 | {
88 | // Arrange
89 | this.httpContext.SetupGet(x => x.IsAuthenticated).Returns(true);
90 | this.httpContext.SetupGet(x => x.DomainUserName).Returns(" ");
91 |
92 | // Act
93 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
94 |
95 | // Assert
96 | this.httpContext.Verify(x => x.RemoveRequestHeader(It.IsAny()), Times.Never());
97 | this.httpContext.Verify(x => x.AddRequestHeader(It.IsAny(), It.IsAny()), Times.Never());
98 | }
99 |
100 | [TestMethod]
101 | public void NoValidADUserTest()
102 | {
103 | // Arrange
104 | this.SetupNormalFlow();
105 | this.adInteraction.Setup(x => x.GetUserInformation(login.loginValue))
106 | .Returns(((string)null, name.nameValue, email.emailValue, groups.groupsValue));
107 |
108 | // Act
109 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
110 |
111 | // Assert
112 | this.httpContext.Verify(x => x.RemoveRequestHeader(It.IsAny()), Times.Never());
113 | this.httpContext.Verify(x => x.AddRequestHeader(It.IsAny(), It.IsAny()), Times.Never());
114 | }
115 |
116 | [TestMethod]
117 | public void WhiteSpaceNoValidADUserTest()
118 | {
119 | // Arrange
120 | this.SetupNormalFlow();
121 | this.adInteraction.Setup(x => x.GetUserInformation(login.loginValue))
122 | .Returns((" ", name.nameValue, email.emailValue, groups.groupsValue));
123 |
124 | // Act
125 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
126 |
127 | // Assert
128 | this.httpContext.Verify(x => x.RemoveRequestHeader(It.IsAny()), Times.Never());
129 | this.httpContext.Verify(x => x.AddRequestHeader(It.IsAny(), It.IsAny()), Times.Never());
130 | }
131 |
132 | [TestMethod]
133 | public void NormalFlowTest()
134 | {
135 | // Arrange
136 | this.SetupNormalFlow();
137 |
138 | // Act
139 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
140 |
141 | // Assert
142 | this.httpContext.Verify(x => x.RemoveRequestHeader("Authorization"), Times.Once());
143 | this.httpContext.Verify(x => x.AddRequestHeader(this.login.header, login.loginValue), Times.Once());
144 | this.httpContext.Verify(x => x.AddRequestHeader(this.name.header, name.nameValue), Times.Once());
145 | this.httpContext.Verify(x => x.AddRequestHeader(this.email.header, email.emailValue), Times.Once());
146 | this.httpContext.Verify(x => x.AddRequestHeader(this.groups.header, string.Join(",", groups.groupsValue)), Times.Once());
147 | }
148 |
149 | [TestMethod]
150 | public void CacheFlowTest()
151 | {
152 | // Arrange
153 | this.SetupNormalFlow();
154 |
155 | // Act
156 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
157 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
158 |
159 | // Assert
160 | this.adInteraction.Verify(x => x.GetUserInformation(It.IsAny()), Times.Once());
161 |
162 | this.httpContext.Verify(x => x.RemoveRequestHeader("Authorization"), Times.Exactly(2));
163 | this.httpContext.Verify(x => x.AddRequestHeader(this.login.header, login.loginValue), Times.Exactly(2));
164 | this.httpContext.Verify(x => x.AddRequestHeader(this.name.header, name.nameValue), Times.Exactly(2));
165 | this.httpContext.Verify(x => x.AddRequestHeader(this.email.header, email.emailValue), Times.Exactly(2));
166 | this.httpContext.Verify(x => x.AddRequestHeader(this.groups.header, string.Join(",", groups.groupsValue)), Times.Exactly(2));
167 |
168 | }
169 |
170 | [TestMethod]
171 | public void AppendStringUserTest()
172 | {
173 | // Arrange
174 | string extraTest = "@domain";
175 | string expectedOutput = $"{login.loginValue}{extraTest}";
176 |
177 | this.SetupNormalFlow();
178 | this.settings.SetupGet(x => x.AppendString).Returns(extraTest);
179 |
180 | // Act
181 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
182 |
183 | // Assert
184 | this.httpContext.Verify(x => x.AddRequestHeader(this.login.header, expectedOutput), Times.Once());
185 | }
186 |
187 | [TestMethod]
188 | public void WhiteSpaceAppendStringUserTest()
189 | {
190 | // Arrange
191 | string extraTest = " ";
192 | string expectedOutput = login.loginValue;
193 |
194 | this.SetupNormalFlow();
195 | this.settings.SetupGet(x => x.AppendString).Returns(extraTest);
196 |
197 | // Act
198 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
199 |
200 | // Assert
201 | this.httpContext.Verify(x => x.AddRequestHeader(this.login.header, expectedOutput), Times.Once());
202 | }
203 |
204 | [TestMethod]
205 | public void NullAppendStringUserTest()
206 | {
207 | // Arrange
208 | string expectedOutput = login.loginValue;
209 |
210 | this.SetupNormalFlow();
211 | this.settings.SetupGet(x => x.AppendString).Returns((string)null);
212 |
213 | // Act
214 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
215 |
216 | // Assert
217 | this.httpContext.Verify(x => x.AddRequestHeader(this.login.header, expectedOutput), Times.Once());
218 | }
219 |
220 | [TestMethod]
221 | public void AppendStringGroupTest()
222 | {
223 | // Arrange
224 | string extraTest = "@domain";
225 | string expectedOutput = string.Join(",", groups.groupsValue.Select(x => $"{x}{extraTest}"));
226 |
227 | this.SetupNormalFlow();
228 | this.settings.SetupGet(x => x.AppendString).Returns(extraTest);
229 |
230 | // Act
231 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
232 |
233 | // Assert
234 | this.httpContext.Verify(x => x.AddRequestHeader(this.groups.header, expectedOutput), Times.Once());
235 | }
236 |
237 | [TestMethod]
238 | public void WhiteSpaceAppendStringGroupTest()
239 | {
240 | // Arrange
241 | string extraTest = " ";
242 | string expectedOutput = string.Join(",", groups.groupsValue);
243 |
244 | this.SetupNormalFlow();
245 | this.settings.SetupGet(x => x.AppendString).Returns(extraTest);
246 |
247 | // Act
248 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
249 |
250 | // Assert
251 | this.httpContext.Verify(x => x.AddRequestHeader(this.groups.header, expectedOutput), Times.Once());
252 | }
253 |
254 | [TestMethod]
255 | public void NullAppendStringGroupTest()
256 | {
257 | // Arrange
258 | string expectedOutput = string.Join(",", groups.groupsValue);
259 |
260 | this.SetupNormalFlow();
261 | this.settings.SetupGet(x => x.AppendString).Returns((string)null);
262 |
263 | // Act
264 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
265 |
266 | // Assert
267 | this.httpContext.Verify(x => x.AddRequestHeader(this.groups.header, expectedOutput), Times.Once());
268 | }
269 |
270 | [TestMethod]
271 | public void LowercaseUserTest()
272 | {
273 | // Arrange
274 | string expectedOutput = login.loginValue.ToLower();
275 |
276 | this.SetupNormalFlow();
277 | this.settings.SetupGet(x => x.DowncaseUsers).Returns(true);
278 |
279 | // Act
280 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
281 |
282 | // Assert
283 | this.httpContext.Verify(x => x.AddRequestHeader(this.login.header, expectedOutput), Times.Once());
284 | }
285 |
286 | [TestMethod]
287 | public void LowercaseGroupTest()
288 | {
289 | // Arrange
290 | string expectedOutput = string.Join(",", groups.groupsValue.Select(x => x.ToLower()));
291 |
292 | this.SetupNormalFlow();
293 | this.settings.SetupGet(x => x.DowncaseGroups).Returns(true);
294 |
295 | // Act
296 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
297 |
298 | // Assert
299 | this.httpContext.Verify(x => x.AddRequestHeader(this.groups.header, expectedOutput), Times.Once());
300 | }
301 |
302 | [TestMethod]
303 | public void NullOrEmptyGroupTest()
304 | {
305 | // Arrange
306 | var testGroup = new[] { "A", null, " ", "C" };
307 | string expectedOutput = string.Join(",", new[] { "A", "C" });
308 |
309 | this.SetupNormalFlow();
310 | this.adInteraction.Setup(x => x.GetUserInformation(login.loginValue))
311 | .Returns((login.loginValue, name.nameValue, email.emailValue, testGroup));
312 |
313 | // Act
314 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
315 |
316 | // Assert
317 | this.httpContext.Verify(x => x.AddRequestHeader(this.groups.header, expectedOutput), Times.Once());
318 | }
319 |
320 | [TestMethod]
321 | public void DisposeNoExceptionTest()
322 | {
323 | // Arrange
324 | var module = new RutaModule(this.adInteraction.Object, this.settings.Object, this.traceSource.Object);
325 |
326 | // Act
327 | module.Dispose();
328 |
329 | // Assert
330 |
331 | // NoExceptionThrown
332 | }
333 |
334 | [TestMethod]
335 | public void ModuleNameTest()
336 | {
337 | // Arrange
338 | const string expectedValue = "RutaModule";
339 |
340 | // Act
341 | var foundValue = this.rutaModule.ModuleName;
342 |
343 | // Assert
344 | Assert.AreEqual(expectedValue, foundValue);
345 | }
346 |
347 | [TestMethod]
348 | public void InitNoExceptionTest()
349 | {
350 | // Arrange
351 | HttpApplication app = new HttpApplication();
352 |
353 | // Act
354 | this.rutaModule.Init(app);
355 |
356 | // Assert
357 |
358 | // NoExceptionThrown
359 | }
360 |
361 | [TestMethod]
362 | public void NormalTraceTest()
363 | {
364 | // Arrange
365 |
366 | // Act
367 | this.rutaModule.HandleAuthorizeRequest(this.httpContext.Object);
368 |
369 | // Assert
370 | this.traceSource.Verify(x => x.TraceEvent(TraceEventType.Start, 0, "START AuthorizeRequest"), Times.Once());
371 | this.traceSource.Verify(x => x.TraceEvent(TraceEventType.Error, 0, It.IsAny()), Times.Never());
372 | this.traceSource.Verify(x => x.TraceEvent(TraceEventType.Stop, 0, "END AuthorizeRequest"), Times.Once());
373 | }
374 |
375 | [TestMethod]
376 | public void ExceptionTraceTest()
377 | {
378 | // Arrange
379 | bool exceptionThrown = false;
380 | string exceptionMessage = null;
381 | this.traceSource.Setup(x => x.TraceEvent(TraceEventType.Error, 0, It.IsAny()))
382 | .Callback((TraceEventType type, int id, string msg) => exceptionMessage = msg);
383 |
384 | // Act
385 | try
386 | {
387 | this.rutaModule.HandleAuthorizeRequest(null);
388 | }
389 | catch (Exception)
390 | {
391 | exceptionThrown = true;
392 | }
393 |
394 | // Assert
395 | Assert.IsTrue(exceptionThrown);
396 |
397 | this.traceSource.Verify(x => x.TraceEvent(TraceEventType.Start, 0, "START AuthorizeRequest"), Times.Once());
398 | this.traceSource.Verify(x => x.TraceEvent(TraceEventType.Error, 0, It.IsAny()), Times.Once());
399 | this.traceSource.Verify(x => x.TraceEvent(TraceEventType.Stop, 0, "END AuthorizeRequest"), Times.Once());
400 |
401 | Assert.IsTrue(exceptionMessage.StartsWith("ERROR AuthorizeRequest: ExceptionData:"));
402 | }
403 |
404 | private void SetupNormalFlow()
405 | {
406 | this.httpContext.SetupGet(x => x.IsWindowsUser).Returns(true);
407 | this.httpContext.SetupGet(x => x.IsAuthenticated).Returns(true);
408 | this.httpContext.SetupGet(x => x.DomainUserName).Returns(login.loginValue);
409 |
410 | this.adInteraction.Setup(x => x.GetUserInformation(login.loginValue))
411 | .Returns((login.loginValue, name.nameValue, email.emailValue, groups.groupsValue));
412 |
413 | this.settings.SetupGet(x => x.LoginHeader).Returns(this.login.header);
414 | this.settings.SetupGet(x => x.NameHeader).Returns(this.name.header);
415 | this.settings.SetupGet(x => x.EmailHeader).Returns(this.email.header);
416 | this.settings.SetupGet(x => x.GroupsHeader).Returns(this.groups.header);
417 | }
418 | }
419 | }
420 |
--------------------------------------------------------------------------------
/RutaHttpModuleTest/SonarAuthPassthroughModuleTest.cs:
--------------------------------------------------------------------------------
1 | namespace RutaHttpModuleTest
2 | {
3 | using Microsoft.VisualStudio.TestTools.UnitTesting;
4 | using Moq;
5 | using RutaHttpModule;
6 | using System;
7 | using System.Collections.Generic;
8 | using System.Linq;
9 | using System.Security.Claims;
10 | using System.Security.Principal;
11 | using System.Text;
12 | using System.Threading.Tasks;
13 |
14 |
15 | [TestClass]
16 | public class SonarAuthPassthroughModuleTest
17 | {
18 | private SonarAuthPassthroughModule sonarAuthPassthroughModule;
19 | private Mock settings;
20 | private Mock traceSource;
21 | private Mock httpContext;
22 |
23 | [TestInitialize]
24 | public void TestInit()
25 | {
26 | this.settings = new Mock();
27 | this.traceSource = new Mock();
28 | this.httpContext = new Mock();
29 |
30 | this.sonarAuthPassthroughModule = new SonarAuthPassthroughModule(this.settings.Object, this.traceSource.Object);
31 | }
32 |
33 | [TestMethod]
34 | public void UserAlreadySetTest()
35 | {
36 | this.httpContext.SetupGet(x => x.User).Returns(ClaimsPrincipal.Current);
37 | this.sonarAuthPassthroughModule.HandleAuthenticateRequest(httpContext.Object);
38 |
39 | this.httpContext.VerifySet(x => x.SkipAuthorization = true, Times.Never);
40 | }
41 |
42 | [TestMethod]
43 | public void SetUserWhenTokenPresentTest()
44 | {
45 | this.httpContext.SetupProperty(x => x.User);
46 | this.httpContext.SetupGet(x => x.HasTokenHeader).Returns(true);
47 |
48 | this.sonarAuthPassthroughModule.HandleAuthenticateRequest(httpContext.Object);
49 |
50 | this.httpContext.VerifySet(x => x.SkipAuthorization = true, Times.Once());
51 | this.httpContext.VerifySet(x => x.User = It.IsAny(), Times.Once());
52 | Assert.IsTrue(this.httpContext.Object.User.Identity.IsAuthenticated);
53 | }
54 |
55 | [TestMethod]
56 | public void SetWhenUserAgentMatchesTest()
57 | {
58 | this.httpContext.SetupProperty(x => x.User);
59 | string agentName = "MATCHME";
60 |
61 | this.httpContext.SetupGet(x => x.UserAgent).Returns($"{agentName}_BLAH");
62 | this.settings.SetupGet(x => x.PassThruUserAgents).Returns(new[] { agentName });
63 |
64 | this.sonarAuthPassthroughModule.HandleAuthenticateRequest(httpContext.Object);
65 |
66 | this.httpContext.VerifySet(x => x.SkipAuthorization = true, Times.Once());
67 | Assert.IsTrue(this.httpContext.Object.User.Identity.IsAuthenticated);
68 | }
69 |
70 | [TestMethod]
71 | public void SetWhenUserAgentOnWhitespaceTest()
72 | {
73 | this.httpContext.SetupProperty(x => x.User);
74 | string agentName = string.Empty;
75 |
76 | this.httpContext.SetupGet(x => x.UserAgent).Returns(agentName);
77 | this.settings.SetupGet(x => x.PassThruUserAgents).Returns(new string[0]);
78 |
79 | this.sonarAuthPassthroughModule.HandleAuthenticateRequest(httpContext.Object);
80 |
81 | this.httpContext.VerifySet(x => x.SkipAuthorization = true, Times.Once());
82 | Assert.IsTrue(this.httpContext.Object.User.Identity.IsAuthenticated);
83 | }
84 |
85 | [TestMethod]
86 | public void DontSetWhenUserAgentMatchesTest()
87 | {
88 | string agentName = "MATCHME";
89 |
90 | this.httpContext.SetupGet(x => x.UserAgent).Returns($"DONTMATCH_{agentName}_BLAH");
91 | this.settings.SetupGet(x => x.PassThruUserAgents).Returns(new[] { agentName });
92 |
93 | this.sonarAuthPassthroughModule.HandleAuthenticateRequest(httpContext.Object);
94 |
95 | this.httpContext.VerifySet(x => x.SkipAuthorization = true, Times.Never());
96 | }
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/SonarQubeScanner/MSBuild.SonarQube.Internal.PostProcess.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/SonarQubeScanner/MSBuild.SonarQube.Internal.PostProcess.exe
--------------------------------------------------------------------------------
/SonarQubeScanner/MSBuild.SonarQube.Internal.PreProcess.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/SonarQubeScanner/MSBuild.SonarQube.Internal.PreProcess.exe
--------------------------------------------------------------------------------
/SonarQubeScanner/MSBuild.SonarQube.Runner.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/SonarQubeScanner/MSBuild.SonarQube.Runner.exe
--------------------------------------------------------------------------------
/SonarQubeScanner/Newtonsoft.Json.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/SonarQubeScanner/Newtonsoft.Json.dll
--------------------------------------------------------------------------------
/SonarQubeScanner/SonarQube.Analysis.xml:
--------------------------------------------------------------------------------
1 |
2 |
19 |
20 |
21 | http://localhost:9000
22 |
23 |
27 |
28 |
29 |
34 |
35 |
36 |
--------------------------------------------------------------------------------
/SonarQubeScanner/SonarQube.Common.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/SonarQubeScanner/SonarQube.Common.dll
--------------------------------------------------------------------------------
/SonarQubeScanner/SonarQube.Integration.Tasks.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/SonarQubeScanner/SonarQube.Integration.Tasks.dll
--------------------------------------------------------------------------------
/SonarQubeScanner/SonarQube.MSBuild.PreProcessor.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/SonarQubeScanner/SonarQube.MSBuild.PreProcessor.exe
--------------------------------------------------------------------------------
/SonarQubeScanner/SonarQube.Scanner.MSBuild.exe:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/SonarQubeScanner/SonarQube.Scanner.MSBuild.exe
--------------------------------------------------------------------------------
/SonarQubeScanner/SonarScanner.Shim.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/SonarQubeScanner/SonarScanner.Shim.dll
--------------------------------------------------------------------------------
/SonarQubeScanner/SupportedBootstrapperVersions.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 1.0
4 |
--------------------------------------------------------------------------------
/SonarQubeScanner/Targets/SonarQube.Integration.ImportBefore.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 | $(TF_BUILD_BUILDDIRECTORY)
9 |
10 | $(AGENT_BUILDDIRECTORY)
11 |
12 | $(MSBuildStartupDirectory)
13 |
14 | $(SonarQubeBuildDirectory)\.sonarqube
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 | $(SonarQubeTempPath)\bin\targets
23 |
24 |
25 |
26 |
27 |
28 |
29 | $(SonarQubeTargetsPath)\SonarQube.Integration.targets
30 |
31 |
32 |
33 |
34 |
41 |
42 |
43 |
44 |
45 | true
46 | true
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
58 |
59 |
62 |
64 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/SonarQubeScanner/Targets/SonarQube.Integration.targets:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
75 |
76 |
77 |
78 |
79 |
84 |
85 |
86 |
87 | true
88 |
89 |
90 |
91 |
92 | $(SonarQubeTempPath)\conf\
93 | $(SonarQubeTempPath)\out\
94 |
95 |
96 | Compile;Content;EmbeddedResource;None;ClCompile;ClInclude;Page;TypeScriptCompile
97 |
98 |
99 |
100 |
101 |
102 |
103 |
105 | $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), SonarQube.Integration.Tasks.dll))\SonarQube.Integration.Tasks.dll
106 |
107 |
108 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
140 |
142 |
143 |
144 |
145 | true
146 |
147 |
148 |
149 |
150 | true
151 |
152 |
153 | true
154 | true
155 |
156 |
157 |
158 |
159 | 3AC096D0-A1C2-E12C-1390-A8335801FDAB
160 | true
161 |
162 |
163 |
165 |
168 |
169 |
170 |
171 |
172 |
173 |
174 |
175 |
178 |
180 |
181 |
183 |
184 |
185 |
186 |
187 |
188 |
189 |
190 |
191 |
192 | $([System.IO.Path]::Combine($(MSBuildProjectDirectory),$(BaseIntermediateOutputPath)))
193 |
194 |
195 |
196 |
197 |
198 |
199 |
200 |
201 |
202 |
203 |
204 | true
205 |
206 |
207 |
208 |
209 |
210 |
211 |
212 |
213 |
214 |
215 |
216 |
217 |
218 |
219 |
220 |
221 |
222 |
223 |
224 |
225 |
226 |
227 |
228 | _$([System.DateTime]::Now.ToString("ffff"))
229 | $(SonarQubeOutputPath)\$(MSBuildProjectName)$(FolderDisambiguator)
230 | $(SonarQubeConfigPath)\$(MSBuildProjectName)$(FolderDisambiguator)
231 |
232 |
233 |
234 |
235 |
236 |
237 |
238 |
239 |
240 |
241 |
242 |
243 |
247 |
248 |
252 |
253 |
254 |
255 |
256 |
257 | $(ProjectSpecificOutDir)\FilesToAnalyze.txt
258 |
259 |
260 |
261 |
266 |
267 |
268 |
269 |
270 | FilesToAnalyze
271 |
272 |
273 |
274 |
276 |
277 |
288 |
289 |
290 |
291 |
292 |
293 |
307 |
308 |
309 |
310 |
311 |
326 |
327 |
328 |
329 |
330 |
331 | true
332 |
333 |
334 |
335 | cs
336 | vbnet
337 |
338 |
339 |
340 |
342 |
343 |
344 | SonarQubeFxCop-$(SQLanguage).ruleset
345 |
346 |
347 | $(SonarQubeConfigPath)\$(SonarQubeRulesetFileName)
348 | $([System.IO.File]::Exists($(SonarQubeRulesetFullName)))
349 |
350 |
351 |
352 |
353 |
356 |
357 |
358 |
359 | $(SonarQubeRulesetExists)
360 |
361 |
362 | false
363 |
364 |
365 | false
366 |
367 |
368 |
371 |
372 |
375 |
376 |
379 |
380 |
383 |
384 |
385 |
389 | $(SonarQubeRunMSCodeAnalysis)
390 |
391 | $(SonarQubeRulesetFullName)
392 | true
393 | true
394 | true
395 |
396 |
397 |
398 | false
399 |
400 |
401 |
402 |
403 |
404 |
407 |
408 |
409 |
410 |
412 |
413 |
414 |
415 |
416 |
417 | true
418 |
419 |
420 |
421 |
422 | FxCop
423 |
424 |
425 |
426 |
427 |
428 |
431 |
432 |
433 |
434 |
435 |
436 |
437 |
438 |
439 |
440 |
441 |
455 |
460 |
461 |
462 |
463 | true
464 |
465 |
466 | false
467 |
468 |
469 |
471 | 4
472 |
473 |
474 |
475 |
476 |
477 |
478 |
479 |
481 |
482 |
483 |
484 |
485 |
486 |
487 |
488 |
489 |
490 |
491 |
492 |
493 |
494 |
497 | $(TargetDir)$(TargetFileName).RoslynCA.json
498 | $(SQRuleSetFilePath)
499 | $(ProjectSpecificConfDir)\ProjectOutFolderPath.txt
500 |
501 |
502 |
505 |
506 |
507 |
508 |
509 |
510 |
511 |
512 |
513 |
514 |
515 |
516 |
517 |
518 |
519 |
520 |
524 |
525 |
526 |
527 |
529 | $(ErrorLog)
530 |
531 |
533 | $(ProjectSpecificOutDir)
534 |
535 |
536 |
537 |
538 |
539 |
540 |
541 |
542 |
543 |
544 |
546 |
549 |
550 |
551 |
552 | true
553 |
554 |
555 |
556 |
557 | $(MSBuildProjectFullPath)
558 |
559 |
560 |
561 |
563 |
564 |
566 |
567 |
568 |
569 |
570 |
571 |
--------------------------------------------------------------------------------
/SonarQubeScanner/TeamBuild.SonarQube.Integration.dll:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/SonarQubeScanner/TeamBuild.SonarQube.Integration.dll
--------------------------------------------------------------------------------
/SonarQubeScanner/sonar-scanner-3.0.1.733/bin/sonar-runner:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # SonarQube Runner Startup Script for Unix
4 | #
5 | # Optional ENV vars:
6 | # SONAR_RUNNER_OPTS - Parameters passed to the Java VM when running Sonar
7 | # JAVA_HOME - Location of Java's installation
8 |
9 | real_path () {
10 | target=$1
11 |
12 | (
13 | while true; do
14 | cd "$(dirname "$target")"
15 | target=$(basename "$target")
16 | link=$(readlink "$target")
17 | test "$link" || break
18 | target=$link
19 | done
20 |
21 | echo "$(pwd -P)/$target"
22 | )
23 | }
24 |
25 | echo WARN: 'sonar-runner' script is deprecated. Please use 'sonar-scanner' instead.
26 |
27 | script_path="$0"
28 |
29 | if [ -h "$script_path" ] ; then
30 | # resolve recursively symlinks
31 | script_path=$(real_path "$script_path")
32 | fi
33 |
34 | sonar_runner_home=$(dirname "$script_path")/..
35 |
36 | # make it fully qualified
37 | sonar_runner_home=$(cd "$sonar_runner_home" && pwd)
38 |
39 | jar_file=$sonar_runner_home/lib/sonar-scanner-cli-3.0.1.733.jar
40 |
41 | # check that sonar_runner_home has been correctly set
42 | if [ ! -f "$jar_file" ] ; then
43 | echo "File does not exist: $jar_file"
44 | echo "'$sonar_runner_home' does not point to a valid installation directory: $sonar_runner_home"
45 | exit 1
46 | fi
47 |
48 | if [ -n "$JAVA_HOME" ]
49 | then
50 | java_cmd="$JAVA_HOME/bin/java"
51 | else
52 | java_cmd="$(which java)"
53 | fi
54 |
55 | if [ -n "$SONAR_RUNNER_OPTS" ] ;
56 | then
57 | echo WARN: '$SONAR_RUNNER_OPTS' is deprecated. Please use '$SONAR_SCANNER_OPTS' instead.
58 | if [ -z "$SONAR_SCANNER_OPTS" ] ; then
59 | SONAR_SCANNER_OPTS=$SONAR_RUNNER_OPTS
60 | fi
61 | fi
62 |
63 | project_home=$(pwd)
64 |
65 | #echo "Info: Using sonar-runner at $sonar_runner_home"
66 | #echo "Info: Using java at $java_cmd"
67 | #echo "Info: Using classpath $jar_file"
68 | #echo "Info: Using project $project_home"
69 |
70 | exec "$java_cmd" \
71 | -Djava.awt.headless=true \
72 | $SONAR_SCANNER_OPTS \
73 | -classpath "$jar_file" \
74 | -Dscanner.home="$sonar_runner_home" \
75 | -Dproject.home="$project_home" \
76 | org.sonarsource.scanner.cli.Main "$@"
77 |
78 |
--------------------------------------------------------------------------------
/SonarQubeScanner/sonar-scanner-3.0.1.733/bin/sonar-runner.bat:
--------------------------------------------------------------------------------
1 | @REM SonarQube Runner Startup Script for Windows
2 | @REM
3 | @REM Required ENV vars:
4 | @REM JAVA_HOME - location of a JDK home dir
5 | @REM
6 | @REM Optional ENV vars:
7 | @REM SONAR_RUNNER_OPTS - parameters passed to the Java VM when running Sonar
8 |
9 | @echo off
10 |
11 | set ERROR_CODE=0
12 |
13 | @REM set local scope for the variables with windows NT shell
14 | @setlocal
15 |
16 | echo WARN: sonar-runner.bat script is deprecated. Please use sonar-scanner.bat instead.
17 |
18 | @REM ==== START VALIDATION ====
19 | @REM *** JAVA EXEC VALIDATION ***
20 | if not "%JAVA_HOME%" == "" goto foundJavaHome
21 |
22 | for %%i in (java.exe) do set JAVA_EXEC=%%~$PATH:i
23 |
24 | if not "%JAVA_EXEC%" == "" (
25 | set JAVA_EXEC="%JAVA_EXEC%"
26 | goto OkJava
27 | )
28 |
29 | if not "%JAVA_EXEC%" == "" goto OkJava
30 |
31 | echo.
32 | echo ERROR: JAVA_HOME not found in your environment, and no Java
33 | echo executable present in the PATH.
34 | echo Please set the JAVA_HOME variable in your environment to match the
35 | echo location of your Java installation, or add "java.exe" to the PATH
36 | echo.
37 | goto error
38 |
39 | :foundJavaHome
40 | if EXIST "%JAVA_HOME%\bin\java.exe" goto foundJavaExeFromJavaHome
41 |
42 | echo.
43 | echo ERROR: JAVA_HOME exists but does not point to a valid Java home
44 | echo folder. No "\bin\java.exe" file can be found there.
45 | echo.
46 | goto error
47 |
48 | :foundJavaExeFromJavaHome
49 | set JAVA_EXEC="%JAVA_HOME%\bin\java.exe"
50 |
51 | :OkJava
52 | set SONAR_RUNNER_HOME=%~dp0..
53 | goto sonarRunnerOpts
54 |
55 | @REM ==== HANDLE DEPRECATED SONAR_RUNNER_OPTS ====
56 | :sonarRunnerOpts
57 | if "%SONAR_RUNNER_OPTS%" == "" (
58 | goto run
59 | ) else (
60 | echo WARN: SONAR_RUNNER_OPTS is deprecated. Please use SONAR_SCANNER_OPTS instead.
61 | if "%SONAR_SCANNER_OPTS%" == "" (set SONAR_SCANNER_OPTS=%SONAR_RUNNER_OPTS%)
62 | )
63 |
64 | @REM ==== START RUN ====
65 | :run
66 | echo %SONAR_RUNNER_HOME%
67 |
68 | set PROJECT_HOME=%CD%
69 |
70 | %JAVA_EXEC% -Djava.awt.headless=true %SONAR_SCANNER_OPTS% -cp "%SONAR_RUNNER_HOME%\lib\sonar-scanner-cli-3.0.1.733.jar" "-Dscanner.home=%SONAR_RUNNER_HOME%" "-Dproject.home=%PROJECT_HOME%" org.sonarsource.scanner.cli.Main %*
71 | if ERRORLEVEL 1 goto error
72 | goto end
73 |
74 | :error
75 | set ERROR_CODE=1
76 |
77 | @REM ==== END EXECUTION ====
78 |
79 | :end
80 | @REM set local scope for the variables with windows NT shell
81 | @endlocal & set ERROR_CODE=%ERROR_CODE%
82 |
83 | @REM see http://code-bear.com/bearlog/2007/06/01/getting-the-exit-code-from-a-batch-file-that-is-run-from-a-python-program/
84 | goto exit
85 |
86 | :returncode
87 | exit /B %1
88 |
89 | :exit
90 | call :returncode %ERROR_CODE%
91 |
--------------------------------------------------------------------------------
/SonarQubeScanner/sonar-scanner-3.0.1.733/bin/sonar-scanner:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # SonarQube Scanner Startup Script for Unix
4 | #
5 | # Optional ENV vars:
6 | # SONAR_SCANNER_OPTS - Parameters passed to the Java VM when running SonarQube Scanner
7 | # SONAR_SCANNER_DEBUG_OPTS - Extra parameters passed to the Java VM for debugging
8 | # JAVA_HOME - Location of Java's installation
9 |
10 | real_path () {
11 | target=$1
12 |
13 | (
14 | while true; do
15 | cd "$(dirname "$target")"
16 | target=$(basename "$target")
17 | link=$(readlink "$target")
18 | test "$link" || break
19 | target=$link
20 | done
21 |
22 | echo "$(pwd -P)/$target"
23 | )
24 | }
25 |
26 | script_path="$0"
27 |
28 | if [ -h "$script_path" ] ; then
29 | # resolve recursively symlinks
30 | script_path=$(real_path "$script_path")
31 | fi
32 |
33 | sonar_scanner_home=$(dirname "$script_path")/..
34 |
35 | # make it fully qualified
36 | sonar_scanner_home=$(cd "$sonar_scanner_home" && pwd -P)
37 |
38 | jar_file=$sonar_scanner_home/lib/sonar-scanner-cli-3.0.1.733.jar
39 |
40 | # check that sonar_scanner_home has been correctly set
41 | if [ ! -f "$jar_file" ] ; then
42 | echo "File does not exist: $jar_file"
43 | echo "'$sonar_scanner_home' does not point to a valid installation directory: $sonar_scanner_home"
44 | exit 1
45 | fi
46 |
47 | use_embedded_jre=false
48 | if [ "$use_embedded_jre" = true ]; then
49 | export JAVA_HOME=$sonar_scanner_home/jre
50 | fi
51 |
52 | if [ -n "$JAVA_HOME" ]
53 | then
54 | java_cmd="$JAVA_HOME/bin/java"
55 | else
56 | java_cmd="$(which java)"
57 | fi
58 |
59 | project_home=$(pwd)
60 |
61 | #echo "Info: Using sonar-scanner at $sonar_scanner_home"
62 | #echo "Info: Using java at $java_cmd"
63 | #echo "Info: Using classpath $jar_file"
64 | #echo "Info: Using project $project_home"
65 |
66 | exec "$java_cmd" \
67 | -Djava.awt.headless=true \
68 | $SONAR_SCANNER_OPTS \
69 | $SONAR_SCANNER_DEBUG_OPTS \
70 | -classpath "$jar_file" \
71 | -Dscanner.home="$sonar_scanner_home" \
72 | -Dproject.home="$project_home" \
73 | org.sonarsource.scanner.cli.Main "$@"
74 |
75 |
--------------------------------------------------------------------------------
/SonarQubeScanner/sonar-scanner-3.0.1.733/bin/sonar-scanner-debug:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | #
3 | # SonarQube Scanner Startup Script for Unix
4 | #
5 | # Optional ENV vars:
6 | # SONAR_SCANNER_OPTS - parameters passed to the Java VM when running SonarQube Scanner
7 | # JAVA_HOME - Location of Java's installation
8 |
9 | SONAR_SCANNER_DEBUG_OPTS="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000"
10 |
11 | echo "Executing SonarQube Scanner in Debug Mode"
12 | echo "SONAR_SCANNER_DEBUG_OPTS=\"-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000\""
13 |
14 | env SONAR_SCANNER_OPTS="$SONAR_SCANNER_OPTS" SONAR_SCANNER_DEBUG_OPTS="$SONAR_SCANNER_DEBUG_OPTS" "$(dirname "$0")"/sonar-scanner "$@"
15 |
--------------------------------------------------------------------------------
/SonarQubeScanner/sonar-scanner-3.0.1.733/bin/sonar-scanner-debug.bat:
--------------------------------------------------------------------------------
1 | @REM SonarQube Scanner Startup Script for Windows
2 | @REM
3 | @REM Required ENV vars:
4 | @REM JAVA_HOME - location of a JDK home dir
5 | @REM
6 | @REM Optional ENV vars:
7 | @REM SONAR_SCANNER_HOME - location of runner's installed home dir
8 | @REM SONAR_SCANNER_OPTS - parameters passed to the Java VM when running Sonar
9 |
10 | @setlocal
11 | @set SONAR_SCANNER_DEBUG_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
12 | echo "Executing SonarQube Scanner in Debug Mode"
13 | echo "SONAR_SCANNER_DEBUG_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000"
14 | @call "%~dp0"sonar-scanner.bat %*
15 |
--------------------------------------------------------------------------------
/SonarQubeScanner/sonar-scanner-3.0.1.733/bin/sonar-scanner.bat:
--------------------------------------------------------------------------------
1 | @REM SonarQube Scanner Startup Script for Windows
2 | @REM
3 | @REM Required ENV vars:
4 | @REM JAVA_HOME - location of a JDK home dir
5 | @REM
6 | @REM Optional ENV vars:
7 | @REM SONAR_SCANNER_OPTS - parameters passed to the Java VM when running Sonar
8 |
9 | @echo off
10 |
11 | set ERROR_CODE=0
12 |
13 | @REM set local scope for the variables with windows NT shell
14 | @setlocal
15 |
16 | set SONAR_SCANNER_HOME=%~dp0..
17 |
18 | @REM ==== START VALIDATION ====
19 | @REM *** JAVA EXEC VALIDATION ***
20 |
21 | set use_embedded_jre=false
22 | if "%use_embedded_jre%" == "true" (
23 | set JAVA_HOME=%SONAR_SCANNER_HOME%\jre
24 | )
25 |
26 | if not "%JAVA_HOME%" == "" goto foundJavaHome
27 |
28 | for %%i in (java.exe) do set JAVA_EXEC=%%~$PATH:i
29 |
30 | if not "%JAVA_EXEC%" == "" (
31 | set JAVA_EXEC="%JAVA_EXEC%"
32 | goto OkJava
33 | )
34 |
35 | if not "%JAVA_EXEC%" == "" goto OkJava
36 |
37 | echo.
38 | echo ERROR: JAVA_HOME not found in your environment, and no Java
39 | echo executable present in the PATH.
40 | echo Please set the JAVA_HOME variable in your environment to match the
41 | echo location of your Java installation, or add "java.exe" to the PATH
42 | echo.
43 | goto error
44 |
45 | :foundJavaHome
46 | if EXIST "%JAVA_HOME%\bin\java.exe" goto foundJavaExeFromJavaHome
47 |
48 | echo.
49 | echo ERROR: JAVA_HOME exists but does not point to a valid Java home
50 | echo folder. No "\bin\java.exe" file can be found there.
51 | echo.
52 | goto error
53 |
54 | :foundJavaExeFromJavaHome
55 | set JAVA_EXEC="%JAVA_HOME%\bin\java.exe"
56 |
57 | :OkJava
58 | goto run
59 |
60 |
61 | @REM ==== START RUN ====
62 | :run
63 | echo %SONAR_SCANNER_HOME%
64 |
65 | set PROJECT_HOME=%CD%
66 |
67 | %JAVA_EXEC% -Djava.awt.headless=true %SONAR_SCANNER_DEBUG_OPTS% %SONAR_SCANNER_OPTS% -cp "%SONAR_SCANNER_HOME%\lib\sonar-scanner-cli-3.0.1.733.jar" "-Dscanner.home=%SONAR_SCANNER_HOME%" "-Dproject.home=%PROJECT_HOME%" org.sonarsource.scanner.cli.Main %*
68 | if ERRORLEVEL 1 goto error
69 | goto end
70 |
71 | :error
72 | set ERROR_CODE=1
73 |
74 | @REM ==== END EXECUTION ====
75 |
76 | :end
77 | @REM set local scope for the variables with windows NT shell
78 | @endlocal & set ERROR_CODE=%ERROR_CODE%
79 |
80 | @REM see http://code-bear.com/bearlog/2007/06/01/getting-the-exit-code-from-a-batch-file-that-is-run-from-a-python-program/
81 | goto exit
82 |
83 | :returncode
84 | exit /B %1
85 |
86 | :exit
87 | call :returncode %ERROR_CODE%
88 |
--------------------------------------------------------------------------------
/SonarQubeScanner/sonar-scanner-3.0.1.733/conf/sonar-scanner.properties:
--------------------------------------------------------------------------------
1 | #Configure here general information about the environment, such as SonarQube DB details for example
2 | #No information about specific project should appear here
3 |
4 | #----- Default SonarQube server
5 | #sonar.host.url=http://localhost:9000
6 |
7 | #----- Default source code encoding
8 | #sonar.sourceEncoding=UTF-8
9 |
10 |
--------------------------------------------------------------------------------
/SonarQubeScanner/sonar-scanner-3.0.1.733/lib/sonar-scanner-cli-3.0.1.733.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/jabbera/IisRemoteUserTokenAuthentication/4f9770f804f733744458533e17efa0610b96f1bc/SonarQubeScanner/sonar-scanner-3.0.1.733/lib/sonar-scanner-cli-3.0.1.733.jar
--------------------------------------------------------------------------------
/appveyor.yml:
--------------------------------------------------------------------------------
1 | version: 0.0.{build}
2 |
3 | cache:
4 | - packages -> **\packages.config # preserve "packages" directory in the root of build folder but will reset it if packages.config is modified
5 | - '%LocalAppData%\NuGet\Cache'
6 |
7 | skip_commits:
8 | files:
9 | - README.md
10 | - LICENSE
11 | - .gitignore
12 |
13 | image: Visual Studio 2017
14 |
15 | environment:
16 | SonarToken:
17 | secure: JPhTIkoIcnnn4eMzibtWuJAK67oiN7QJy9H2K7zesrxbzI6AEpBLhGc9hUmAW8GY
18 |
19 | before_build:
20 | - ps: .\GenerateTokenizedConfig.ps1
21 | - nuget restore
22 | - cmd: if defined SonarToken (SonarQubeScanner\SonarQube.Scanner.MSBuild.exe begin /k:"IisRemoteUserTokenAuthentication" /v:0.20 /d:"sonar.host.url=https://sonarqube.com" /d:"sonar.login=%SonarToken%" /d:"sonar.verbose=true")
23 |
24 | assembly_info:
25 | patch: true
26 | file: AssemblyInfo.*
27 | assembly_version: '{version}'
28 | assembly_file_version: '{version}'
29 | assembly_informational_version: '$(APPVEYOR_REPO_COMMIT)-rc'
30 |
31 |
32 | platform: Any CPU
33 | configuration: Release
34 |
35 | test:
36 | assemblies:
37 | - '**\RutaHttpModuleTest.dll'
38 | categories:
39 | except:
40 | - ActiveDirectoryRequired
41 |
42 | after_test:
43 | - vstest.console ".\RutaHttpModuleTest\bin\Release\RutaHttpModuleTest.dll" /logger:trx /TestCaseFilter:"TestCategory!=ActiveDirectoryRequired"
44 | - cmd: mkdir "%APPVEYOR_BUILD_FOLDER%\Staging"
45 | - cmd: mkdir "%APPVEYOR_BUILD_FOLDER%\Staging\inetpub-user"
46 | - cmd: mkdir "%APPVEYOR_BUILD_FOLDER%\Staging\inetpub-user\bin"
47 | - cmd: mkdir "%APPVEYOR_BUILD_FOLDER%\Staging\inetpub-scanner"
48 | - cmd: copy "%APPVEYOR_BUILD_FOLDER%\web-user.config" "%APPVEYOR_BUILD_FOLDER%\Staging\inetpub-user\web.config"
49 | - cmd: copy "%APPVEYOR_BUILD_FOLDER%\RutaHttpModule\bin\%CONFIGURATION%\*.dll" "%APPVEYOR_BUILD_FOLDER%\Staging\inetpub-user\bin\"
50 | - cmd: copy "%APPVEYOR_BUILD_FOLDER%\web-scanner.config" "%APPVEYOR_BUILD_FOLDER%\Staging\inetpub-scanner\web.config"
51 | - cmd: copy "%APPVEYOR_BUILD_FOLDER%\ConfigureServer.ps1" "%APPVEYOR_BUILD_FOLDER%\Staging\ConfigureServer.ps1"
52 | - 7z a RutaHttpModule.zip "%APPVEYOR_BUILD_FOLDER%\Staging\*"
53 | - cmd: copy "%APPVEYOR_BUILD_FOLDER%\web-user-tokenized.config" "%APPVEYOR_BUILD_FOLDER%\Staging\inetpub-user\web.config"
54 | - 7z a RutaHttpModule-Tokenized.zip "%APPVEYOR_BUILD_FOLDER%\Staging\*"
55 | - cmd: if defined SonarToken (SonarQubeScanner\SonarQube.Scanner.MSBuild.exe end /d:"sonar.login=%SonarToken%")
56 |
57 | artifacts:
58 | - path: RutaHttpModule.zip
59 | name: RutaHttpModule
60 | - path: RutaHttpModule-Tokenized.zip
61 | name: RutaHttpModule-Tokenized
62 |
63 | deploy:
64 | release: $(APPVEYOR_REPO_TAG_NAME)
65 | description: 'RUTA module for SSO IIS'
66 | provider: GitHub
67 | auth_token:
68 | secure: 8T/Hl3dGYqGdE8zjaQNpklrJUCgm3QdMNnQxQvim17w+se7YUPxTeWH4qEMJyQCL
69 | draft: true
70 | prerelease: true
71 | force_update: true
72 | on:
73 | appveyor_repo_tag: true
74 |
--------------------------------------------------------------------------------
/test.runsettings:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 | .*RutaHttpModule\.dll$
11 |
12 |
13 |
14 |
15 | ^System\.Diagnostics\.DebuggerHiddenAttribute$
16 | ^System\.Diagnostics\.DebuggerNonUserCodeAttribute$
17 | ^System\.Runtime\.CompilerServices.CompilerGeneratedAttribute$
18 | ^System\.CodeDom\.Compiler.GeneratedCodeAttribute$
19 | ^System\.Diagnostics\.CodeAnalysis.ExcludeFromCodeCoverageAttribute$
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/web-scanner.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/web-user-transform.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 |
7 |
8 | __DowncaseGroups__
9 |
10 |
11 | __AppendString__
12 |
13 |
14 |
15 |
17 | __AdUserBaseDn__
18 |
19 |
20 |
21 |
22 | __AdGroupBaseDn__
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/web-user.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 | X-Forwarded-Login
13 |
14 |
15 | X-Forwarded-Name
16 |
17 |
18 | X-Forwarded-Email
19 |
20 |
21 | X-Forwarded-Groups
22 |
23 |
24 | True
25 |
26 |
27 | True
28 |
29 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
50 | sonar.scanner.app
51 | SonarQubeScanner
52 | ScannerCli
53 | ScannerCLI
54 | ScannerMSBuild
55 | ScannerAzureDevOps
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 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
--------------------------------------------------------------------------------