11using Microsoft . AspNetCore . Authentication ;
22using Microsoft . AspNetCore . Authentication . Cookies ;
33using Microsoft . AspNetCore . Authentication . OpenIdConnect ;
4+ using Microsoft . AspNetCore . Authorization ;
45using Microsoft . IdentityModel . Protocols . OpenIdConnect ;
56using Microsoft . IdentityModel . Tokens ;
67using SAPSec . Core . Configuration ;
78using SAPSec . Core . Interfaces . Services ;
8- using SAPSec . Core . Model ;
99using SAPSec . Core . Services ;
10+ using SAPSec . Web . Authorization ;
11+ using SAPSec . Web . Constants ;
1012using System . Diagnostics . CodeAnalysis ;
1113
12- namespace SAPSec . Web . Extensions ;
14+ namespace SAPSec . Web . Authentication ;
1315
1416[ ExcludeFromCodeCoverage ]
1517public static class DsiAuthenticationExtensions
1618{
17- private static class Routes
18- {
19- public const string SignIn = "/Auth/sign-in" ;
20- public const string AccessDenied = "/Auth/access-denied" ;
21- public const string Home = "/" ;
22- public const string SchoolSearch = "/find-a-school" ;
23- public const string Error = "/error" ;
24- }
25-
2619 private static class CookieSettings
2720 {
2821 public const string Name = "SAPSec.Auth" ;
@@ -44,7 +37,6 @@ public static IServiceCollection AddDsiAuthentication(
4437 var dsiConfig = GetAndValidateConfiguration ( configuration ) ;
4538
4639 services . Configure < DsiConfiguration > ( configuration . GetSection ( "DsiConfiguration" ) ) ;
47- services . Configure < PrivateBetaRestrictedAccess > ( configuration . GetSection ( "PrivateBetaRestrictedAccess" ) ) ;
4840
4941 services
5042 . AddAuthentication ( ConfigureAuthenticationDefaults )
@@ -54,6 +46,17 @@ public static IServiceCollection AddDsiAuthentication(
5446
5547 RegisterServices ( services ) ;
5648
49+ services . AddAuthorization ( o =>
50+ {
51+ const string PolicyName = "DsiAuthorizationPolicy" ;
52+ o . AddPolicy ( PolicyName , policy => policy . AddRequirements ( new DsiAuthorizationRequirement ( ) ) ) ;
53+ var policy = o . GetPolicy ( PolicyName ) ;
54+ if ( policy is not null )
55+ {
56+ o . DefaultPolicy = policy ;
57+ }
58+ } ) ;
59+ services . AddScoped < IAuthorizationHandler , DsiAuthorizationHandler > ( ) ;
5760 return services ;
5861 }
5962
@@ -139,177 +142,16 @@ private static OpenIdConnectEvents CreateOpenIdConnectEvents(DsiConfiguration co
139142 {
140143 return new OpenIdConnectEvents
141144 {
142- OnRedirectToIdentityProvider = context => HandleRedirectToIdentityProvider ( context , config ) ,
143- OnMessageReceived = HandleMessageReceived ,
144- OnAuthorizationCodeReceived = HandleAuthorizationCodeReceived ,
145- OnTokenValidated = HandleTokenValidated ,
146- OnRemoteFailure = HandleRemoteFailure ,
147- OnAuthenticationFailed = HandleAuthenticationFailed ,
148- OnSignedOutCallbackRedirect = HandleSignedOutCallbackRedirect
145+ OnRedirectToIdentityProvider = context => DsiAuthenticationHandler . HandleRedirectToIdentityProvider ( context , config ) ,
146+ OnMessageReceived = DsiAuthenticationHandler . HandleMessageReceived ,
147+ OnAuthorizationCodeReceived = DsiAuthenticationHandler . HandleAuthorizationCodeReceived ,
148+ OnTokenValidated = DsiAuthenticationHandler . HandleTokenValidated ,
149+ OnRemoteFailure = DsiAuthenticationHandler . HandleRemoteFailure ,
150+ OnAuthenticationFailed = DsiAuthenticationHandler . HandleAuthenticationFailed ,
151+ OnSignedOutCallbackRedirect = DsiAuthenticationHandler . HandleSignedOutCallbackRedirect
149152 } ;
150153 }
151154
152- private static Task HandleRedirectToIdentityProvider (
153- RedirectContext context ,
154- DsiConfiguration config )
155- {
156- if ( IsNonLocalhost ( context ) )
157- {
158- SetProductionRedirectUri ( context , config ) ;
159- }
160-
161- return Task . CompletedTask ;
162- }
163-
164- private static bool IsNonLocalhost ( RedirectContext context )
165- {
166- return ! context . HttpContext . Request . Host . Host . Contains ( "localhost" ) ;
167- }
168-
169- private static void SetProductionRedirectUri ( RedirectContext context , DsiConfiguration config )
170- {
171- var host = context . HttpContext . Request . Host . ToUriComponent ( ) ;
172- context . ProtocolMessage . RedirectUri = $ "https://{ host } { config . CallbackPath } ";
173- }
174-
175- private static Task HandleMessageReceived ( MessageReceivedContext context )
176- {
177- if ( IsSpuriousAuthCallbackRequest ( context ) )
178- {
179- LogSpuriousRequest ( context ) ;
180- RedirectToHomeAndHandle ( context ) ;
181- }
182-
183- return Task . CompletedTask ;
184- }
185-
186- private static bool IsSpuriousAuthCallbackRequest ( MessageReceivedContext context )
187- {
188- var options = context . Options ;
189- return context . Request . Path == options . CallbackPath &&
190- context . Request . Method == "GET" &&
191- ! context . Request . Query . ContainsKey ( "code" ) ;
192- }
193-
194- private static void LogSpuriousRequest ( MessageReceivedContext context )
195- {
196- var logger = GetLogger ( context . HttpContext ) ;
197- logger . LogWarning (
198- "Spurious authentication callback request detected at {Path}" ,
199- context . Request . Path ) ;
200- }
201-
202- private static void RedirectToHomeAndHandle ( MessageReceivedContext context )
203- {
204- context . Response . Redirect ( Routes . Home ) ;
205- context . HandleResponse ( ) ;
206- }
207-
208- private static Task HandleAuthorizationCodeReceived ( AuthorizationCodeReceivedContext context )
209- {
210- return Task . CompletedTask ;
211- }
212-
213- private static async Task HandleTokenValidated ( TokenValidatedContext context )
214- {
215- var logger = GetLogger ( context . HttpContext ) ;
216-
217- try
218- {
219- await ProcessValidatedToken ( context ) ;
220- }
221- catch ( Exception ex )
222- {
223- logger . LogError ( ex , "Error processing validated token for user" ) ;
224- context . Fail ( ex ) ;
225- }
226- }
227-
228- private static async Task ProcessValidatedToken ( TokenValidatedContext context )
229- {
230- var userService = context . HttpContext . RequestServices
231- . GetRequiredService < IUserService > ( ) ;
232-
233- var user = await userService . GetUserFromClaimsAsync ( context . Principal ! ) ;
234-
235- if ( user == null )
236- {
237- return ;
238- }
239-
240- await SetOrganisationBasedOnCount ( context , userService , user ) ;
241- }
242-
243- private static async Task SetOrganisationBasedOnCount (
244- TokenValidatedContext context ,
245- IUserService userService ,
246- User user )
247- {
248- if ( user . Organisations . Count == 1 )
249- {
250- await userService . SetCurrentOrganisationAsync (
251- context . Principal ! ,
252- user . Organisations [ 0 ] . Id ) ;
253- }
254- else if ( user . Organisations . Count > 1 )
255- {
256- context . Properties ! . RedirectUri = Routes . SchoolSearch ;
257- }
258- }
259-
260- private static Task HandleRemoteFailure ( RemoteFailureContext context )
261- {
262- var logger = GetLogger ( context . HttpContext ) ;
263-
264- logger . LogError (
265- context . Failure ,
266- "DSI remote authentication failure: {Message}" ,
267- context . Failure ? . Message ) ;
268-
269- RedirectToErrorAndHandle ( context ) ;
270-
271- return Task . CompletedTask ;
272- }
273-
274- private static Task HandleAuthenticationFailed ( AuthenticationFailedContext context )
275- {
276- var logger = GetLogger ( context . HttpContext ) ;
277-
278- logger . LogError (
279- context . Exception ,
280- "DSI authentication failed: {Message}" ,
281- context . Exception . Message ) ;
282-
283- RedirectToErrorAndHandle ( context ) ;
284-
285- return Task . CompletedTask ;
286- }
287-
288- private static void RedirectToErrorAndHandle ( RemoteFailureContext context )
289- {
290- context . Response . Redirect ( Routes . Error ) ;
291- context . HandleResponse ( ) ;
292- }
293-
294- private static void RedirectToErrorAndHandle ( AuthenticationFailedContext context )
295- {
296- context . Response . Redirect ( Routes . Error ) ;
297- context . HandleResponse ( ) ;
298- }
299-
300- private static Task HandleSignedOutCallbackRedirect ( RemoteSignOutContext context )
301- {
302- context . Response . Redirect ( Routes . Home ) ;
303- context . HandleResponse ( ) ;
304-
305- return Task . CompletedTask ;
306- }
307-
308- private static ILogger < Program > GetLogger ( HttpContext httpContext )
309- {
310- return httpContext . RequestServices . GetRequiredService < ILogger < Program > > ( ) ;
311- }
312-
313155 private static void RegisterServices ( IServiceCollection services )
314156 {
315157 services . AddHttpContextAccessor ( ) ;
@@ -343,4 +185,4 @@ private static DsiConfiguration GetDsiConfigurationForApiClient(IServiceProvider
343185
344186 return dsiConfig ;
345187 }
346- }
188+ }
0 commit comments