Skip to content

Commit 45f9672

Browse files
committed
Refactor failed Auth handling to use registered handlers
1 parent 9f449f3 commit 45f9672

18 files changed

Lines changed: 110 additions & 126 deletions

src/ServiceStack.Mvc/RazorFormat.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -993,12 +993,12 @@ internal static void RedirectIfNotAuthenticated(this IRequest req, string redire
993993
if (req.GetSession().IsAuthenticated)
994994
return;
995995

996-
redirect = redirect
997-
?? AuthenticateService.HtmlRedirect
998-
?? HostContext.Config.DefaultRedirectPath
999-
?? HostContext.Config.WebHostUrl
1000-
?? "/";
1001-
AuthenticateAttribute.DoHtmlRedirect(redirect, req, req.Response, includeRedirectParam: true);
996+
var authFeature = HostContext.AppHost.AssertPlugin<AuthFeature>();
997+
redirect ??= authFeature.HtmlRedirect
998+
?? HostContext.Config.DefaultRedirectPath
999+
?? HostContext.Config.WebHostUrl
1000+
?? "/";
1001+
authFeature.DoHtmlRedirect(redirect, req, req.Response, includeRedirectParam: true);
10021002
throw new StopExecutionException();
10031003
}
10041004

src/ServiceStack.Razor/ViewPageBase.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -560,11 +560,12 @@ public void RedirectIfNotAuthenticated(string redirectUrl=null)
560560
{
561561
if (IsAuthenticated) return;
562562

563-
redirectUrl ??= AuthenticateService.HtmlRedirect
564-
?? HostContext.Config.DefaultRedirectPath
565-
?? HostContext.Config.WebHostUrl
566-
?? "/";
567-
AuthenticateAttribute.DoHtmlRedirect(redirectUrl, Request, Response, includeRedirectParam: true);
563+
var authFeature = HostContext.AppHost.AssertPlugin<AuthFeature>();
564+
redirectUrl ??= authFeature.HtmlRedirect
565+
?? HostContext.Config.DefaultRedirectPath
566+
?? HostContext.Config.WebHostUrl
567+
?? "/";
568+
authFeature.DoHtmlRedirect(redirectUrl, Request, Response, includeRedirectParam: true);
568569
throw new StopExecutionException();
569570
}
570571

src/ServiceStack/Auth/AuthProvider.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -356,17 +356,6 @@ public virtual Task OnFailedAuthentication(IAuthSession session, IRequest httpRe
356356
return HostContext.AppHost.HandleShortCircuitedErrors(httpReq, httpRes, httpReq.Dto);
357357
}
358358

359-
public static Task HandleFailedAuth(IAuthProvider authProvider,
360-
IAuthSession session, IRequest httpReq, IResponse httpRes)
361-
{
362-
if (authProvider is AuthProvider baseAuthProvider)
363-
return baseAuthProvider.OnFailedAuthentication(session, httpReq, httpRes);
364-
365-
httpRes.StatusCode = (int)HttpStatusCode.Unauthorized;
366-
httpRes.AddHeader(HttpHeaders.WwwAuthenticate, $"{authProvider.Provider} realm=\"{authProvider.AuthRealm}\"");
367-
return HostContext.AppHost.HandleShortCircuitedErrors(httpReq, httpRes, httpReq.Dto);
368-
}
369-
370359
protected virtual async Task<bool> UserNameAlreadyExistsAsync(IAuthRepositoryAsync authRepo, IUserAuth userAuth, IAuthTokens tokens = null, CancellationToken token=default)
371360
{
372361
if (tokens?.UserName != null)

src/ServiceStack/Auth/AuthProviderSync.cs

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -325,17 +325,6 @@ public virtual Task OnFailedAuthentication(IAuthSession session, IRequest httpRe
325325
return HostContext.AppHost.HandleShortCircuitedErrors(httpReq, httpRes, httpReq.Dto);
326326
}
327327

328-
public static Task HandleFailedAuth(IAuthProvider authProvider,
329-
IAuthSession session, IRequest httpReq, IResponse httpRes)
330-
{
331-
if (authProvider is AuthProvider baseAuthProvider)
332-
return baseAuthProvider.OnFailedAuthentication(session, httpReq, httpRes);
333-
334-
httpRes.StatusCode = (int)HttpStatusCode.Unauthorized;
335-
httpRes.AddHeader(HttpHeaders.WwwAuthenticate, $"{authProvider.Provider} realm=\"{authProvider.AuthRealm}\"");
336-
return HostContext.AppHost.HandleShortCircuitedErrors(httpReq, httpRes, httpReq.Dto);
337-
}
338-
339328
protected virtual bool UserNameAlreadyExists(IAuthRepository authRepo, IUserAuth userAuth, IAuthTokens tokens = null)
340329
{
341330
if (tokens?.UserName != null)

src/ServiceStack/Auth/DigestAuthProvider.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,7 @@ public override Task OnFailedAuthentication(IAuthSession session, IRequest httpR
181181
{
182182
var digestHelper = new DigestAuthFunctions();
183183
httpRes.StatusCode = (int)HttpStatusCode.Unauthorized;
184-
httpRes.AddHeader(
185-
HttpHeaders.WwwAuthenticate,
184+
httpRes.AddHeader(HttpHeaders.WwwAuthenticate,
186185
$"{Provider} realm=\"{AuthRealm}\", nonce=\"{digestHelper.GetNonce(httpReq.UserHostAddress, PrivateKey)}\", qop=\"auth\"");
187186
return HostContext.AppHost.HandleShortCircuitedErrors(httpReq, httpRes, httpReq.Dto);
188187
}

src/ServiceStack/AuthFeature.cs

Lines changed: 28 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,8 @@ public void Register(IAppHost appHost)
319319
appHost.Register<IAuthMetadataProvider>(new AuthMetadataProvider());
320320

321321
appHost.CustomErrorHttpHandlers[HttpStatusCode.Unauthorized] = new AuthFeatureUnauthorizedHttpHandler(this);
322-
appHost.CustomErrorHttpHandlers[HttpStatusCode.Forbidden] = new AuthFeatureForbiddenHttpHandler(this);
322+
appHost.CustomErrorHttpHandlers[HttpStatusCode.Forbidden] = new AuthFeatureAccessDeniedHttpHandler(this);
323+
appHost.CustomErrorHttpHandlers[HttpStatusCode.PaymentRequired] = new AuthFeatureAccessDeniedHttpHandler(this);
323324

324325
AuthProviders.OfType<IAuthPlugin>().Each(x => x.Register(appHost, this));
325326

@@ -401,6 +402,9 @@ public static string GetHtmlRedirect(this AuthFeature feature)
401402

402403
return "~/" + HostContext.ResolveLocalizedString(LocalizedStrings.Login);
403404
}
405+
406+
public static string GetHtmlRedirectUrl(this AuthFeature feature, IRequest req) =>
407+
feature.GetHtmlRedirectUrl(req, feature.HtmlRedirectAccessDenied ?? feature.HtmlRedirect, includeRedirectParam: true);
404408

405409
public static string GetHtmlRedirectUrl(this AuthFeature feature, IRequest req, string redirectUrl, bool includeRedirectParam)
406410
{
@@ -423,6 +427,12 @@ public static string GetHtmlRedirectUrl(this AuthFeature feature, IRequest req,
423427
return url;
424428
}
425429

430+
public static void DoHtmlRedirect(this AuthFeature feature, string redirectUrl, IRequest req, IResponse res, bool includeRedirectParam)
431+
{
432+
var url = feature.GetHtmlRedirectUrl(req, redirectUrl, includeRedirectParam);
433+
res.RedirectToUrl(url);
434+
}
435+
426436
private static string ToQueryString(NameValueCollection queryStringCollection)
427437
{
428438
if (queryStringCollection == null || queryStringCollection.Count == 0)
@@ -431,9 +441,8 @@ private static string ToQueryString(NameValueCollection queryStringCollection)
431441
return "?" + queryStringCollection.ToFormUrlEncoded();
432442
}
433443

434-
435444
//http://stackoverflow.com/questions/3588623/c-sharp-regex-for-a-username-with-a-few-restrictions
436-
public static Regex ValidUserNameRegEx = new Regex(@"^(?=.{3,20}$)([A-Za-z0-9][._-]?)*$", RegexOptions.Compiled);
445+
public static Regex ValidUserNameRegEx = new(@"^(?=.{3,20}$)([A-Za-z0-9][._-]?)*$", RegexOptions.Compiled);
437446

438447
public static bool IsValidUsername(this AuthFeature feature, string userName)
439448
{
@@ -489,6 +498,18 @@ public static IHttpResult SuccessAuthResult(this IHttpResult result, IServiceBas
489498
}
490499
return result;
491500
}
501+
502+
public static Task HandleFailedAuth(this IAuthProvider authProvider,
503+
IAuthSession session, IRequest httpReq, IResponse httpRes)
504+
{
505+
if (authProvider is AuthProvider baseAuthProvider)
506+
return baseAuthProvider.OnFailedAuthentication(session, httpReq, httpRes);
507+
508+
httpRes.StatusCode = (int)HttpStatusCode.Unauthorized;
509+
httpRes.AddHeader(HttpHeaders.WwwAuthenticate, $"{authProvider.Provider} realm=\"{authProvider.AuthRealm}\"");
510+
return HostContext.AppHost.HandleShortCircuitedErrors(httpReq, httpRes, httpReq.Dto);
511+
}
512+
492513
}
493514

494515
public class AuthFeatureUnauthorizedHttpHandler : HttpAsyncTaskHandler
@@ -498,32 +519,27 @@ public class AuthFeatureUnauthorizedHttpHandler : HttpAsyncTaskHandler
498519

499520
public override Task ProcessRequestAsync(IRequest req, IResponse res, string operationName)
500521
{
501-
if (feature.HtmlRedirectAccessDenied != null && req.ResponseContentType.MatchesContentType(MimeTypes.Html))
522+
if (feature.HtmlRedirect != null && req.ResponseContentType.MatchesContentType(MimeTypes.Html))
502523
{
503524
var url = feature.GetHtmlRedirectUrl(req, feature.HtmlRedirect, includeRedirectParam:true);
504525
res.RedirectToUrl(url);
505526
return TypeConstants.EmptyTask;
506527
}
507528

508529
var iAuthProvider = feature.AuthProviders.First();
509-
if (iAuthProvider is AuthProvider authProvider)
510-
return authProvider.OnFailedAuthentication(null, req, res);
511-
if (iAuthProvider is AuthProviderSync authProviderSync)
512-
return authProviderSync.OnFailedAuthentication(null, req, res);
513-
514530
res.StatusCode = (int)HttpStatusCode.Unauthorized;
515531
res.AddHeader(HttpHeaders.WwwAuthenticate, $"{iAuthProvider.Provider} realm=\"{iAuthProvider.AuthRealm}\"");
516-
return HostContext.AppHost.HandleShortCircuitedErrors(req, res, req.Dto);
532+
return res.EndHttpHandlerRequestAsync();
517533
}
518534

519535
public override bool IsReusable => true;
520536
public override bool RunAsAsync() => true;
521537
}
522538

523-
public class AuthFeatureForbiddenHttpHandler : ForbiddenHttpHandler
539+
public class AuthFeatureAccessDeniedHttpHandler : ForbiddenHttpHandler
524540
{
525541
private readonly AuthFeature feature;
526-
public AuthFeatureForbiddenHttpHandler(AuthFeature feature) => this.feature = feature;
542+
public AuthFeatureAccessDeniedHttpHandler(AuthFeature feature) => this.feature = feature;
527543

528544
public override Task ProcessRequestAsync(IRequest req, IResponse res, string operationName)
529545
{
@@ -537,5 +553,4 @@ public override Task ProcessRequestAsync(IRequest req, IResponse res, string ope
537553
}
538554
}
539555

540-
541556
}

src/ServiceStack/AuthenticateAttribute.cs

Lines changed: 6 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
using System;
22
using System.Collections.Generic;
3-
using System.Collections.Specialized;
43
using System.Linq;
54
using System.Net;
65
using System.Threading.Tasks;
@@ -80,10 +79,7 @@ public override async Task ExecuteAsync(IRequest req, IResponse res, object requ
8079
var session = await req.GetSessionAsync().ConfigAwait();
8180
if (session == null || !authProviders.Any(x => session.IsAuthorized(x.Provider)))
8281
{
83-
if (this.DoHtmlRedirectIfConfigured(req, res, true))
84-
return;
85-
86-
await AuthProvider.HandleFailedAuth(authProviders[0], session, req, res).ConfigAwait();
82+
await authProviders[0].HandleFailedAuth(session, req, res).ConfigAwait();
8783
}
8884
}
8985

@@ -219,36 +215,14 @@ internal static async Task PreAuthenticateAsync(IRequest req, IEnumerable<IAuthP
219215
}
220216
}
221217

222-
protected bool DoHtmlRedirectIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false)
218+
protected virtual Task HandleShortCircuitedErrors(IRequest req, IResponse res, object requestDto,
219+
HttpStatusCode statusCode, string statusDescription=null)
223220
{
224-
var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirect;
225-
if (htmlRedirect != null && req.ResponseContentType.MatchesContentType(MimeTypes.Html))
226-
{
227-
DoHtmlRedirect(htmlRedirect, req, res, includeRedirectParam);
228-
return true;
229-
}
230-
return false;
231-
}
221+
if (HtmlRedirect != null)
222+
req.Items[nameof(AuthFeature.HtmlRedirect)] = HtmlRedirect;
232223

233-
protected bool DoHtmlRedirectAccessDeniedIfConfigured(IRequest req, IResponse res, bool includeRedirectParam = false)
234-
{
235-
var htmlRedirect = this.HtmlRedirect ?? AuthenticateService.HtmlRedirectAccessDenied ?? AuthenticateService.HtmlRedirect;
236-
if (htmlRedirect != null && req.ResponseContentType.MatchesContentType(MimeTypes.Html))
237-
{
238-
DoHtmlRedirect(htmlRedirect, req, res, includeRedirectParam);
239-
return true;
240-
}
241-
return false;
242-
}
243-
244-
public static void DoHtmlRedirect(string redirectUrl, IRequest req, IResponse res, bool includeRedirectParam)
245-
{
246-
var url = HostContext.AssertPlugin<AuthFeature>().GetHtmlRedirectUrl(req, redirectUrl, includeRedirectParam);
247-
res.RedirectToUrl(url);
224+
return HostContext.AppHost.HandleShortCircuitedErrors(req, res, requestDto, statusCode, statusDescription);
248225
}
249-
250-
public static string GetHtmlRedirectUrl(IRequest req) =>
251-
HostContext.AssertPlugin<AuthFeature>().GetHtmlRedirectUrl(req, AuthenticateService.HtmlRedirectAccessDenied ?? AuthenticateService.HtmlRedirect, includeRedirectParam: true);
252226

253227
protected bool Equals(AuthenticateAttribute other)
254228
{

src/ServiceStack/ErrorMessages.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ public static class ErrorMessages
3434

3535
public static string InvalidRole = "Invalid Role";
3636
public static string InvalidPermission = "Invalid Permission";
37+
public static string WebSudoRequired = "Web Sudo Required";
3738

3839
public static string ClaimDoesNotExistFmt = "Claim '{0}' with '{1}' does not exist";
3940

src/ServiceStack/RequiredClaimAttribute.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,8 @@ public override async Task ExecuteAsync(IRequest req, IResponse res, object requ
3636
if (HasClaim(req, Type, Value))
3737
return;
3838

39-
if (DoHtmlRedirectAccessDeniedIfConfigured(req, res))
40-
return;
41-
42-
res.StatusCode = (int)HttpStatusCode.Forbidden;
43-
res.StatusDescription = ErrorMessages.ClaimDoesNotExistFmt.LocalizeFmt(req, Type, Value);
44-
await HostContext.AppHost.HandleShortCircuitedErrors(req, res, requestDto);
39+
await HostContext.AppHost.HandleShortCircuitedErrors(req, res, requestDto,
40+
HttpStatusCode.Forbidden, ErrorMessages.ClaimDoesNotExistFmt.LocalizeFmt(req, Type, Value));
4541
}
4642

4743
public static bool HasClaim(IRequest req, string type, string value)

src/ServiceStack/RequiredPermissionAttribute.cs

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,8 @@ public override async Task ExecuteAsync(IRequest req, IResponse res, object requ
4343
if (await HasAllPermissionsAsync(req, await req.GetSessionAsync().ConfigAwait(), RequiredPermissions).ConfigAwait())
4444
return;
4545

46-
if (DoHtmlRedirectAccessDeniedIfConfigured(req, res))
47-
return;
48-
49-
res.StatusCode = (int)HttpStatusCode.Forbidden;
50-
res.StatusDescription = ErrorMessages.InvalidPermission.Localize(req);
51-
await HostContext.AppHost.HandleShortCircuitedErrors(req, res, requestDto).ConfigAwait();
46+
await HostContext.AppHost.HandleShortCircuitedErrors(req, res, requestDto,
47+
HttpStatusCode.Forbidden, ErrorMessages.InvalidPermission.Localize(req)).ConfigAwait();
5248
}
5349

5450
[Obsolete("Use HasAllPermissionsAsync")]

0 commit comments

Comments
 (0)