From 9e6c7f33a5b0d4298c763a98fc6aa03d824c5a1c Mon Sep 17 00:00:00 2001 From: littlefoot Date: Mon, 18 Oct 2021 11:06:44 -0400 Subject: [PATCH] Added userinfo endpoint usage and api now properly creates necessary claims to start doing database stuff --- Web/Startup.cs | 11 ++-- WebAPI/Auth/CustomAuthorizationFilter.cs | 64 ------------------- ...AuthHandler.cs => OIDCTokenAuthHandler.cs} | 21 ++++-- WebAPI/Controllers/BaseController.cs | 7 +- WebAPI/Controllers/HelloWorldController.cs | 12 +++- WebAPI/Data/AppSettings.cs | 1 + WebAPI/Data/Dto/OIDC/OIDCUserInfoResponse.cs | 34 ++++++++++ .../Data/Dto/PterodactylCreateUserRequest.cs | 16 +++++ .../Data/Dto/PterodactylCreateUserResponse.cs | 11 ++++ WebAPI/Data/OIDCService.cs | 16 +++++ WebAPI/Data/PterodactylService.cs | 19 +++++- WebAPI/Startup.cs | 4 -- WebAPI/appsettings.json | 1 + 13 files changed, 129 insertions(+), 88 deletions(-) delete mode 100644 WebAPI/Auth/CustomAuthorizationFilter.cs rename WebAPI/Auth/{OIDCAuthHandler.cs => OIDCTokenAuthHandler.cs} (74%) create mode 100644 WebAPI/Data/Dto/OIDC/OIDCUserInfoResponse.cs create mode 100644 WebAPI/Data/Dto/PterodactylCreateUserRequest.cs create mode 100644 WebAPI/Data/Dto/PterodactylCreateUserResponse.cs diff --git a/Web/Startup.cs b/Web/Startup.cs index cc62f02..c20d0a3 100644 --- a/Web/Startup.cs +++ b/Web/Startup.cs @@ -36,22 +36,21 @@ namespace Web services.AddAuthentication(options => { options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; - options.DefaultChallengeScheme = "oidc"; + options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme; }) .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme) - .AddOpenIdConnect(options => + .AddOpenIdConnect(OpenIdConnectDefaults.AuthenticationScheme,options => { options.Authority = Configuration["oidc:authority"]; options.ClientId = Configuration["oidc:client_id"]; options.ClientSecret = Configuration["oidc:client_secret"]; options.ResponseType = OpenIdConnectResponseType.Code; - options.GetClaimsFromUserInfoEndpoint = true; + options.GetClaimsFromUserInfoEndpoint = false; options.SaveTokens = true; options.UseTokenLifetime = true; - options.Scope.Add("openid"); - options.Scope.Add("profile"); - options.Scope.Add("email"); + options.Scope.Add(OpenIdConnectScope.OpenIdProfile); + options.Scope.Add(OpenIdConnectScope.OpenId); options.TokenValidationParameters = new TokenValidationParameters { diff --git a/WebAPI/Auth/CustomAuthorizationFilter.cs b/WebAPI/Auth/CustomAuthorizationFilter.cs deleted file mode 100644 index 698e510..0000000 --- a/WebAPI/Auth/CustomAuthorizationFilter.cs +++ /dev/null @@ -1,64 +0,0 @@ -using System; -using System.Linq; -using System.Net.Http.Headers; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Mvc; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.Extensions.Logging; -using Microsoft.Net.Http.Headers; -using WebAPI.Data; - -namespace WebAPI.Auth -{ - public class OIDCAuthorization : ServiceFilterAttribute - { - public OIDCAuthorization() : base(typeof(CustomAuthorizationFilter)) - { - - } - } - /// - /// Authorization filter for checking the bearer token against the OIDC service. - /// Will short circuit on an invalid token - /// - /// - public class CustomAuthorizationFilter: IAsyncAuthorizationFilter - { - private readonly ILogger _logger; - private readonly OIDCService _oidcService; - private readonly AppDbContext _appDbContext; - - public CustomAuthorizationFilter(ILogger logger, OIDCService oidcService, AppDbContext appDbContext) - { - _logger = logger; - _oidcService = oidcService; - _appDbContext = appDbContext; - } - - public async Task OnAuthorizationAsync(AuthorizationFilterContext context) - { - try - { - var httpContext = context.HttpContext; - var bearerToken = httpContext?.Request?.Headers[HeaderNames.Authorization].FirstOrDefault(); - if (string.IsNullOrEmpty(bearerToken)) - { - throw new Exception("Need a token"); - } - - string token = bearerToken.Split(" ").ElementAt(1); - if (!await _oidcService.ValidateAccessToken(token)) - { - throw new Exception("bad token"); - } - - } - catch (Exception e) - { - context.Result = new ForbidResult(e.Message); - } - } - } -} \ No newline at end of file diff --git a/WebAPI/Auth/OIDCAuthHandler.cs b/WebAPI/Auth/OIDCTokenAuthHandler.cs similarity index 74% rename from WebAPI/Auth/OIDCAuthHandler.cs rename to WebAPI/Auth/OIDCTokenAuthHandler.cs index 53021aa..d2362ae 100644 --- a/WebAPI/Auth/OIDCAuthHandler.cs +++ b/WebAPI/Auth/OIDCTokenAuthHandler.cs @@ -34,9 +34,17 @@ namespace WebAPI.Auth return AuthenticateResult.Fail("failed to validate token"); } + var userInfo = await _oidcService.GetTokenDetails(token); + if (userInfo == null) + { + return AuthenticateResult.Fail("Failed to get info for token"); + } var identity = new ClaimsIdentity(new[] { - new Claim(ClaimTypes.Authentication, token) + new Claim(ClaimTypes.Authentication, token), + new Claim(ClaimTypes.Name, userInfo.Name), + new Claim(OIDCClaimTypes.Username, userInfo.PreferredUsername), + new Claim(OIDCClaimTypes.Subject, userInfo.Sub) }, Scheme.Name); var principal = new ClaimsPrincipal(identity); return AuthenticateResult.Success(new AuthenticationTicket(principal, Scheme.Name)); @@ -45,14 +53,17 @@ namespace WebAPI.Auth public class OIDCTokenAuthenticationOptions : AuthenticationSchemeOptions { - public string OIDCIntrospectionEndpoint { get; set; } - public string OIDCClientId { get; set; } - public string OIDCClientSecret { get; set; } - + } public class OIDCTokenAuthenticationDefaults { public static string DefaultScheme => "OIDCAuthentication"; } + + public static class OIDCClaimTypes + { + public static string Username => "Username"; + public static string Subject => "Subject"; + } } \ No newline at end of file diff --git a/WebAPI/Controllers/BaseController.cs b/WebAPI/Controllers/BaseController.cs index cdefec4..2e84a12 100644 --- a/WebAPI/Controllers/BaseController.cs +++ b/WebAPI/Controllers/BaseController.cs @@ -1,5 +1,7 @@ using System; using System.Linq; +using System.Security.Claims; +using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Net.Http.Headers; @@ -7,10 +9,5 @@ namespace WebAPI.Controllers { public class BaseController : ControllerBase { - protected string BearerToken => - Request.Headers.Keys.Contains(HeaderNames.Authorization) && - Request.Headers[HeaderNames.Authorization].Count > 0 - ? Request.Headers[HeaderNames.Authorization].First().Split(" ")[1] - : String.Empty; } } \ No newline at end of file diff --git a/WebAPI/Controllers/HelloWorldController.cs b/WebAPI/Controllers/HelloWorldController.cs index ea37667..6fa777a 100644 --- a/WebAPI/Controllers/HelloWorldController.cs +++ b/WebAPI/Controllers/HelloWorldController.cs @@ -5,12 +5,13 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Newtonsoft.Json; using WebAPI.Auth; using WebAPI.Data; namespace WebAPI.Controllers { - [Authorize(Policy = "test")] + [Authorize] [Route("api/[controller]")] [ApiController] public class HelloWorldController : BaseController @@ -25,7 +26,14 @@ namespace WebAPI.Controllers [HttpGet] public async Task HelloWorld() { - return "Success"; + return JsonConvert.SerializeObject(User.Claims.Select(claim => new {claim.Type, claim.Value})); + } + + [HttpGet] + [Route("NameRequired")] + public string NameRequired() + { + return "success"; } } diff --git a/WebAPI/Data/AppSettings.cs b/WebAPI/Data/AppSettings.cs index f8ef49e..ed7a184 100644 --- a/WebAPI/Data/AppSettings.cs +++ b/WebAPI/Data/AppSettings.cs @@ -10,6 +10,7 @@ namespace WebAPI.Data public static string OIDCIntrospectionEndpoint { get; private set; } public static string OIDCClientId { get; private set; } public static string OIDCClientSecret { get; set; } + public static string OIDCUserInfoEndpoint { get; set; } public static void Init(IConfiguration configuration) { var fields = typeof(AppSettings).GetProperties(); diff --git a/WebAPI/Data/Dto/OIDC/OIDCUserInfoResponse.cs b/WebAPI/Data/Dto/OIDC/OIDCUserInfoResponse.cs new file mode 100644 index 0000000..677d241 --- /dev/null +++ b/WebAPI/Data/Dto/OIDC/OIDCUserInfoResponse.cs @@ -0,0 +1,34 @@ +using Newtonsoft.Json; + +namespace WebAPI.Data.Dto.OIDC +{ + public class OIDCUserInfoResponse + { + [JsonProperty("email")] + public string Email { get; set; } + + [JsonProperty("email_verified")] + public bool EmailVerified { get; set; } + + [JsonProperty("name")] + public string Name { get; set; } + + [JsonProperty("given_name")] + public string GivenName { get; set; } + + [JsonProperty("family_name")] + public string FamilyName { get; set; } + + [JsonProperty("preferred_username")] + public string PreferredUsername { get; set; } + + [JsonProperty("nickname")] + public string Nickname { get; set; } + + [JsonProperty("groups")] + public string[] Groups { get; set; } + + [JsonProperty("sub")] + public string Sub { get; set; } + } +} \ No newline at end of file diff --git a/WebAPI/Data/Dto/PterodactylCreateUserRequest.cs b/WebAPI/Data/Dto/PterodactylCreateUserRequest.cs new file mode 100644 index 0000000..3d95908 --- /dev/null +++ b/WebAPI/Data/Dto/PterodactylCreateUserRequest.cs @@ -0,0 +1,16 @@ +using Newtonsoft.Json; + +namespace WebAPI.Data.Dto +{ + public class PterodactylCreateUserRequest + { + public string Email { get; set; } + public string Username { get; set; } + [JsonProperty("external_id")] + public string ExternalId { get; set; } + [JsonProperty("first_name")] + public string FirstName { get; set; } + [JsonProperty("last_name")] + public string LastName { get; set; } + } +} \ No newline at end of file diff --git a/WebAPI/Data/Dto/PterodactylCreateUserResponse.cs b/WebAPI/Data/Dto/PterodactylCreateUserResponse.cs new file mode 100644 index 0000000..a155fde --- /dev/null +++ b/WebAPI/Data/Dto/PterodactylCreateUserResponse.cs @@ -0,0 +1,11 @@ +namespace WebAPI.Data.Dto +{ + public class PterodactylCreateUserResponseAttributes + { + public int Id { get; set; } + } + public class PterodactylCreateUserResponse + { + public PterodactylCreateUserResponseAttributes Attributes { get; set; } + } +} \ No newline at end of file diff --git a/WebAPI/Data/OIDCService.cs b/WebAPI/Data/OIDCService.cs index de8a665..e46aad3 100644 --- a/WebAPI/Data/OIDCService.cs +++ b/WebAPI/Data/OIDCService.cs @@ -7,6 +7,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using WebAPI.Data.Dto.OIDC; namespace WebAPI.Data { @@ -49,6 +50,21 @@ namespace WebAPI.Data var responsecontent = await response.Content.ReadAsStringAsync(); return JsonConvert.DeserializeObject(responsecontent).Active; } + + public async Task GetTokenDetails(string accessToken) + { + HttpRequestMessage requestMessage = + new HttpRequestMessage(HttpMethod.Get, $"https://{AppSettings.OIDCUserInfoEndpoint}"); + requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken); + HttpResponseMessage response = await _httpClient.SendAsync(requestMessage); + if (!response.IsSuccessStatusCode) + { + return null; + } + + var responsecontent = await response.Content.ReadAsStringAsync(); + return JsonConvert.DeserializeObject(responsecontent); + } } } \ No newline at end of file diff --git a/WebAPI/Data/PterodactylService.cs b/WebAPI/Data/PterodactylService.cs index 1e4c7e2..624e308 100644 --- a/WebAPI/Data/PterodactylService.cs +++ b/WebAPI/Data/PterodactylService.cs @@ -6,6 +6,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Components; using Microsoft.Extensions.Logging; using Newtonsoft.Json; +using WebAPI.Data.Dto; namespace WebAPI.Data { @@ -46,7 +47,7 @@ namespace WebAPI.Data return JsonConvert.DeserializeObject(responsedata); } - public async Task SendGet(string endpoint, IEnumerable includeParameters, bool client=false) + private async Task SendGet(string endpoint, IEnumerable includeParameters, bool client=false) { try { @@ -61,7 +62,7 @@ namespace WebAPI.Data } - public async Task SendPost(string endpoint, object obj, IEnumerable includeParameters, bool client = false) + private async Task SendPost(string endpoint, object obj, IEnumerable includeParameters, bool client = false) { try { @@ -75,5 +76,19 @@ namespace WebAPI.Data return default; } } + + public async Task SendPterodactylUserCreate(string username, string email, + string firstname, string lastname, string externalId) + { + var requestObj = new PterodactylCreateUserRequest() + { + Email = email, + ExternalId = externalId, + FirstName = firstname, + LastName = lastname, + Username = username + }; + return await SendPost("users", requestObj, null); + } } } \ No newline at end of file diff --git a/WebAPI/Startup.cs b/WebAPI/Startup.cs index 37c54ac..4202b96 100644 --- a/WebAPI/Startup.cs +++ b/WebAPI/Startup.cs @@ -68,7 +68,6 @@ namespace WebAPI services.AddDbContext(options => options.UseNpgsql(Configuration.GetConnectionString("DefaultConnection"))); services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddAuthentication(opt => { opt.DefaultScheme = OIDCTokenAuthenticationDefaults.DefaultScheme; @@ -77,9 +76,6 @@ namespace WebAPI OIDCTokenAuthenticationDefaults.DefaultScheme, opt => { - opt.OIDCClientId = AppSettings.OIDCClientId; - opt.OIDCClientSecret = AppSettings.OIDCClientSecret; - opt.OIDCIntrospectionEndpoint = AppSettings.OIDCIntrospectionEndpoint; }); } diff --git a/WebAPI/appsettings.json b/WebAPI/appsettings.json index 36832eb..15ae1fa 100644 --- a/WebAPI/appsettings.json +++ b/WebAPI/appsettings.json @@ -13,6 +13,7 @@ "PterodactylAPIKey": "REPLACE_ME", "PterodactylPanelURL": "https://panel.orfl.xyz", "OIDCIntrospectionEndpoint": "authentik.mattstop.com/application/o/introspect/", + "OIDCUserInfoEndpoint": "authentik.mattstop.com/application/o/userinfo/", "OIDCClientId": "", "OIDCClientSecret": "" }