Skip to content

Commit 8ebc706

Browse files
authored
Merge pull request #8 from jabbera/updatePullReq
Update RutaModule
2 parents 51411b1 + 7606ee1 commit 8ebc706

7 files changed

Lines changed: 484 additions & 94 deletions

File tree

RutaHttpModule.sln

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
33
# Visual Studio 15
4-
VisualStudioVersion = 15.0.25914.0
4+
VisualStudioVersion = 15.0.26020.0
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RutaHttpModule", "RutaHttpModule\RutaHttpModule.csproj", "{F582ADD6-A40A-45F2-A046-ABC48A350B16}"
77
EndProject
@@ -15,6 +15,7 @@ EndProject
1515
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0BCA4263-3544-4706-BFCB-664DEACB36EC}"
1616
ProjectSection(SolutionItems) = preProject
1717
ConfigureServer.ps1 = ConfigureServer.ps1
18+
README.md = README.md
1819
web-scanner.config = web-scanner.config
1920
web-user.config = web-user.config
2021
EndProjectSection

RutaHttpModule/ITraceSource.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
namespace RutaHttpModule
2+
{
3+
using System.Diagnostics;
4+
5+
/// <summary>
6+
/// Interface for implementations sending trace messages.
7+
/// </summary>
8+
internal interface ITraceSource
9+
{
10+
/// <summary>
11+
/// Writes a trace event message to the trace listeners using the specified event type,
12+
/// event identifier, and message.
13+
/// </summary>
14+
/// <param name="eventType">One of the enumeration values that specifies the event type of the trace data.</param>
15+
/// <param name="id">An event identifier.</param>
16+
/// <param name="message">The trace message to write.</param>
17+
void TraceEvent(TraceEventType eventType, int id, string message);
18+
}
19+
}

RutaHttpModule/RutaHttpModule.csproj

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
<Compile Include="IAdInteraction.cs" />
5757
<Compile Include="IRutaHttpContext.cs" />
5858
<Compile Include="ISettings.cs" />
59+
<Compile Include="ITraceSource.cs" />
5960
<Compile Include="LdapExtensions.cs" />
6061
<Compile Include="Properties\Settings.Designer.cs">
6162
<AutoGen>True</AutoGen>
@@ -65,6 +66,7 @@
6566
<Compile Include="RutaHttpContext.cs" />
6667
<Compile Include="RutaModule.cs" />
6768
<Compile Include="Properties\AssemblyInfo.cs" />
69+
<Compile Include="RutaTraceSource.cs" />
6870
<Compile Include="SettingsWrapper.cs" />
6971
</ItemGroup>
7072
<ItemGroup>

RutaHttpModule/RutaModule.cs

Lines changed: 132 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,198 @@
1-
using System;
2-
using System.Diagnostics;
3-
using System.Diagnostics.CodeAnalysis;
4-
using System.Linq;
5-
using System.Web;
6-
7-
namespace RutaHttpModule
1+
namespace RutaHttpModule
82
{
3+
using System;
4+
using System.Diagnostics;
5+
using System.Diagnostics.CodeAnalysis;
6+
using System.Linq;
7+
using System.Web;
8+
9+
/// <summary>
10+
/// This module changes adds all header information needed for interaction with SonarQube and
11+
/// provide a SSO experience. An existing Authorization header will be removed.
12+
/// </summary>
13+
/// <remarks>
14+
/// See also https://www.iis.net/learn/develop/runtime-extensibility/how-to-add-tracing-to-iis-managed-modules
15+
/// </remarks>
916
public sealed class RutaModule : IHttpModule
1017
{
18+
/// <summary>
19+
/// Reference to the implementation needed to query the AD.
20+
/// </summary>
1121
private readonly IAdInteraction adInteraction;
22+
23+
/// <summary>
24+
/// Reference to the settings needed for constructing the right header.
25+
/// </summary>
1226
private readonly ISettings settings;
13-
private TraceSource traceSource;
1427

28+
/// <summary>
29+
/// Reference to the tracing object.
30+
/// </summary>
31+
private ITraceSource traceSource;
32+
33+
/// <summary>
34+
/// Initializes a new instance of the a <see cref="RutaModule"/> object
35+
/// </summary>
1536
[ExcludeFromCodeCoverage]
1637
public RutaModule()
17-
: this(new AdInteraction(), new SettingsWrapper())
18-
{
38+
: this(new AdInteraction(), new SettingsWrapper(), new RutaTraceSource(nameof(RutaHttpModule)))
39+
{
1940
}
2041

21-
internal RutaModule(IAdInteraction adInteraction, ISettings settings)
42+
/// <summary>
43+
/// Initializes a new instance of the a <see cref="RutaModule"/> object
44+
/// </summary>
45+
/// <param name="adInteraction">Reference to the implementation needed to query the AD.</param>
46+
/// <param name="settings">Reference to the settings needed for constructing the right header.</param>
47+
/// <param name="traceSource">Reference to the tracing object.</param>
48+
internal RutaModule(IAdInteraction adInteraction, ISettings settings, ITraceSource traceSource)
2249
{
2350
this.adInteraction = adInteraction ?? throw new ArgumentNullException(nameof(adInteraction));
2451
this.settings = settings ?? throw new ArgumentNullException(nameof(settings));
52+
this.traceSource = traceSource ?? throw new ArgumentException(nameof(traceSource));
2553
}
2654

27-
[ExcludeFromCodeCoverage]
55+
/// <summary>
56+
/// The name of the module
57+
/// </summary>
2858
public string ModuleName => "RutaModule";
2959

30-
// In the Init function, register for HttpApplication
31-
// events by adding your handlers.
32-
[ExcludeFromCodeCoverage]
33-
public void Init(HttpApplication application)
34-
{
35-
application.AuthorizeRequest += AuthorizeRequest;
36-
this.traceSource = new TraceSource(nameof(RutaHttpModule)); // Do I need to do this here? https://www.iis.net/learn/develop/runtime-extensibility/how-to-add-tracing-to-iis-managed-modules
37-
}
60+
/// <summary>
61+
/// Initializes a module and prepares it to handle requests (part of the <see cref="IHttpHandler"/> interface).
62+
/// </summary>
63+
/// <param name="application">An <see cref="HttpApplication"/> that provides access to the methods, properties,
64+
/// and events common to all application objects within an ASP.NET application</param>
65+
public void Init(HttpApplication application) => application.AuthorizeRequest += AuthorizeRequest;
3866

39-
[ExcludeFromCodeCoverage]
67+
/// <summary>
68+
/// Disposes of the resources (other than memory) used this module (part of the <see cref="IHttpHandler"/> interface).
69+
/// </summary>
4070
public void Dispose() { }
4171

72+
/// <summary>
73+
/// Handler executed when a security module has verified user authorization. During execution of the handler the
74+
/// httpContext will be changed.
75+
/// </summary>
76+
/// <param name="source">The source of the event.</param>
77+
/// <param name="e">An object that contains no event data.</param>
4278
[ExcludeFromCodeCoverage]
4379
private void AuthorizeRequest(object source, EventArgs e)
80+
{
81+
var application = source as HttpApplication;
82+
HandleAuthorizeRequest(new RutaHttpContext(application.Context));
83+
}
84+
85+
/// <summary>
86+
/// Handle an authorizeRequest using the <paramref name="context"/>.
87+
/// </summary>
88+
/// <param name="context">The context to be used when handling an AuthorizeRequest</param>
89+
internal void HandleAuthorizeRequest(IRutaHttpContext context)
4490
{
4591
try
4692
{
47-
traceSource.TraceEvent(TraceEventType.Start, 0, $"[{nameof(RutaModule)} MODULE] START AuthorizeRequest");
48-
49-
HttpApplication application = (HttpApplication)source;
50-
AuthorizeRequest(new RutaHttpContext(application.Context));
93+
traceSource.TraceEvent(TraceEventType.Start, 0, "START AuthorizeRequest");
94+
HandleAuthorizeRequestInternal(context);
5195
}
52-
catch(Exception ex)
96+
catch (Exception ex)
5397
{
54-
traceSource.TraceEvent(TraceEventType.Error, 0, $"[{nameof(RutaModule)} ERROR AuthorizeRequest: ExceptionData: '{ex}' ");
98+
traceSource.TraceEvent(TraceEventType.Error, 0, $"ERROR AuthorizeRequest: ExceptionData: '{ex}' ");
5599
throw;
56100
}
57101
finally
58102
{
59-
traceSource.TraceEvent(TraceEventType.Stop, 0, $"[{nameof(RutaModule)} END AuthorizeRequest");
103+
traceSource.TraceEvent(TraceEventType.Stop, 0, "END AuthorizeRequest");
60104
}
61105
}
62106

63-
// Internal for testing purposes
64-
internal void AuthorizeRequest(IRutaHttpContext context)
107+
/// <summary>
108+
/// Replace the current context with a version that can be processed by SonarQube. That is done only
109+
/// if:<br/>
110+
/// <ul>
111+
/// <li>The user is authenticated.</li>
112+
/// <li>The context contains a DomainUserName.</li>
113+
/// <li>The Ldap information could be extracted.</li>
114+
/// <li>The user information can be retrieved from AD.</li>
115+
/// </ul>
116+
/// </summary>
117+
/// <param name="context">The context on which the action should be performed.</param>
118+
private void HandleAuthorizeRequestInternal(IRutaHttpContext context)
65119
{
66120
if (!context.IsAuthenticated)
67121
{
122+
traceSource.TraceEvent(TraceEventType.Information, 0, "Not authenticated");
68123
return;
69124
}
70125

71126
string userName = context.DomainUserName;
72-
73127
if (string.IsNullOrWhiteSpace(userName))
74128
{
129+
traceSource.TraceEvent(TraceEventType.Information, 0, "No username in context");
75130
return;
76131
}
77-
78-
context.RemoveRequestHeader("Authorization"); // Remove the authorzation header since we are in charge of authentication
132+
79133
var userInformation = this.adInteraction.GetUserInformation(userName);
80-
81134
if (string.IsNullOrWhiteSpace(userInformation.login))
82135
{
136+
traceSource.TraceEvent(TraceEventType.Information, 0, "No user information in context");
83137
return;
84138
}
85139

86-
string loginToSend = AppendIfNedded(DowncaseUserIfNeeded(userInformation.login));
87-
string[] groupsToSend = userInformation.groups.Where(x => x != null).Select(DowncaseGroupIfNeeded).Select(AppendIfNedded).ToArray();
140+
traceSource.TraceEvent(TraceEventType.Information, 0, "Set headers");
141+
142+
string loginToSend = ApplyUserSettings(userInformation.login);
143+
string[] groupsToSend = userInformation.groups.Where(group => !string.IsNullOrWhiteSpace(group))
144+
.Select(ApplyGroupSettings)
145+
.ToArray();
88146

147+
context.RemoveRequestHeader("Authorization"); // Remove the authorzation header since we are in charge of authentication
89148
context.AddRequestHeader(this.settings.LoginHeader, loginToSend);
90149
context.AddRequestHeader(this.settings.NameHeader, userInformation.name);
91150
context.AddRequestHeader(this.settings.EmailHeader, userInformation.email);
92151
context.AddRequestHeader(this.settings.GroupsHeader, string.Join(",", groupsToSend));
93152
}
94153

95-
private string DowncaseGroupIfNeeded(string s)
96-
{
97-
if (!this.settings.DowncaseGroups)
98-
{
99-
return s;
100-
}
101-
102-
return s.ToLower();
103-
}
104-
105-
private string DowncaseUserIfNeeded(string s)
154+
/// <summary>
155+
/// Returns a copy of the <paramref name="group"/> object adjusted as necessary based on
156+
/// the settings (DowncaseGroups and AppendString).
157+
/// </summary>
158+
/// <param name="group">A <see cref="String"/> on which the action should be performed.</param>
159+
/// <returns>The modified version of <paramref name="group"/>.</returns>
160+
private string ApplyGroupSettings(string group) => AppendIfNeeded(LowercaseIfNeeded(group, this.settings.DowncaseGroups));
161+
162+
/// <summary>
163+
/// Returns a copy of the <paramref name="user"/> object adjusted as necessary based on
164+
/// the settings (DowncaseUsers and AppendString).
165+
/// </summary>
166+
/// <param name="user">A <see cref="String"/> on which the action should be performed.</param>
167+
/// <returns>The modified version of <paramref name="user"/>.</returns>
168+
private string ApplyUserSettings(string user) => AppendIfNeeded(LowercaseIfNeeded(user, this.settings.DowncaseUsers));
169+
170+
/// <summary>
171+
/// Returns a copy of the <paramref name="source"/> object appended with the contents of the
172+
/// AppendString settings. The action will not be performed if <paramref name="source"/> or
173+
/// AppendString are null or containing only whitespace. In all other cases <paramref name="source"/>
174+
/// will be returned.
175+
/// </summary>
176+
/// <param name="source">A <see cref="String"/> on which the action should be performed.</param>
177+
/// <returns>The modified version of <paramref name="source"/>.</returns>
178+
private string AppendIfNeeded(string source)
106179
{
107-
if (!this.settings.DowncaseUsers)
180+
if (string.IsNullOrWhiteSpace(this.settings.AppendString))
108181
{
109-
return s;
182+
return source;
110183
}
111184

112-
return s.ToLower();
185+
return $"{source}{this.settings.AppendString}";
113186
}
114187

115-
private string AppendIfNedded(string s)
116-
{
117-
if (string.IsNullOrWhiteSpace(this.settings.AppendString) || string.IsNullOrWhiteSpace(s))
118-
{
119-
return s;
120-
}
121-
122-
return $"{s}{this.settings.AppendString}";
123-
}
188+
/// <summary>
189+
/// Returns a copy of the <paramref name="source"/> object converted to lowercase
190+
/// using the casing rules of the invariant culture if indicated by <paramref name="applyDowncase"/>.
191+
/// In all other cases <paramref name="source"/> will be returned.
192+
/// </summary>
193+
/// <param name="source">A <see cref="String"/> on which the action should be performed.</param>
194+
/// <param name="applyLowercase">Should the method return a lowercase version of <paramref name="source"/>?</param>
195+
/// <returns>The modified version of <paramref name="source"/>.</returns>
196+
private static string LowercaseIfNeeded(string source, bool applyLowercase) => applyLowercase ? source.ToLowerInvariant() : source;
124197
}
125198
}

RutaHttpModule/RutaTraceSource.cs

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace RutaHttpModule
2+
{
3+
using System;
4+
using System.Diagnostics;
5+
using System.Diagnostics.CodeAnalysis;
6+
7+
/// <summary>
8+
/// Wrapper class for using <see cref="TraceSource"/>.
9+
/// </summary>
10+
[ExcludeFromCodeCoverage]
11+
internal class RutaTraceSource : ITraceSource
12+
{
13+
/// <summary>
14+
/// The name of the trace source (typically, the name of the application).
15+
/// </summary>
16+
private readonly string tracesourceName;
17+
18+
/// <summary>
19+
/// Internal <see cref="TraceSource"/> reference.
20+
/// </summary>
21+
private readonly TraceSource traceSource;
22+
23+
/// <summary>
24+
/// Creates a <see cref="RutaTraceSource"/> based on the supplied
25+
/// <paramref name="tracesourceName"/>.
26+
/// </summary>
27+
/// <param name="tracesourceName">The name of the trace source.</param>
28+
internal RutaTraceSource(string tracesourceName)
29+
{
30+
this.traceSource = new TraceSource(tracesourceName);
31+
this.tracesourceName = tracesourceName;
32+
}
33+
34+
/// <summary>
35+
/// Writes a trace event message to the trace listeners using the specified event type,
36+
/// event identifier, and message.
37+
/// </summary>
38+
/// <param name="eventType">One of the enumeration values that specifies the event type of the trace data.</param>
39+
/// <param name="id">An event identifier.</param>
40+
/// <param name="message">The trace message to write.</param>
41+
public void TraceEvent(TraceEventType eventType, int id, string message)
42+
{
43+
traceSource.TraceEvent(eventType, id, $"[{tracesourceName} MODULE] {message}");
44+
}
45+
}
46+
}

RutaHttpModuleTest/RutaHttpModuleTest.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@
6262
<HintPath>..\packages\System.ValueTuple.4.3.0\lib\netstandard1.0\System.ValueTuple.dll</HintPath>
6363
<Private>True</Private>
6464
</Reference>
65+
<Reference Include="System.Web" />
6566
<Reference Include="System.Xml" />
6667
<Reference Include="System.Xml.Linq" />
6768
</ItemGroup>

0 commit comments

Comments
 (0)