├── .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 | [![Build status](https://ci.appveyor.com/api/projects/status/n3cgxias5t3mfybr?svg=true)](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 | --------------------------------------------------------------------------------