From c437d096eeb724137fe541437de162d51fc9a27f Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 2 Feb 2019 17:01:59 +0100 Subject: [PATCH] Custom admin client. --- .../Security/Extensions.cs | 5 +++ .../Identity/ClaimsPrincipalExtensions.cs | 11 ++++- .../Identity/SquidexClaimTypes.cs | 4 ++ .../Api/Controllers/Apps/AppsController.cs | 6 +-- .../Controllers/Backups/RestoreController.cs | 4 +- .../IdentityServer/Config/LazyClientStore.cs | 40 ++++++++++++++++--- src/Squidex/Config/Logging.cs | 9 ++++- src/Squidex/Config/MyIdentityOptions.cs | 9 +++++ src/Squidex/Pipeline/Extensions.cs | 11 +++-- src/Squidex/appsettings.json | 10 +++++ 10 files changed, 93 insertions(+), 16 deletions(-) diff --git a/src/Squidex.Infrastructure/Security/Extensions.cs b/src/Squidex.Infrastructure/Security/Extensions.cs index 25b9a8b88..2f28f2fb2 100644 --- a/src/Squidex.Infrastructure/Security/Extensions.cs +++ b/src/Squidex.Infrastructure/Security/Extensions.cs @@ -23,6 +23,11 @@ namespace Squidex.Infrastructure.Security return principal.Claims.FirstOrDefault(x => x.Type == OpenIdClaims.ClientId)?.Value; } + public static string UserOrClientId(this ClaimsPrincipal principal) + { + return principal.OpenIdSubject() ?? principal.OpenIdClientId(); + } + public static string OpenIdPreferredUserName(this ClaimsPrincipal principal) { return principal.Claims.FirstOrDefault(x => x.Type == OpenIdClaims.PreferredUserName)?.Value; diff --git a/src/Squidex.Shared/Identity/ClaimsPrincipalExtensions.cs b/src/Squidex.Shared/Identity/ClaimsPrincipalExtensions.cs index cb4511d41..0995e9a2e 100644 --- a/src/Squidex.Shared/Identity/ClaimsPrincipalExtensions.cs +++ b/src/Squidex.Shared/Identity/ClaimsPrincipalExtensions.cs @@ -27,12 +27,19 @@ namespace Squidex.Shared.Identity public static PermissionSet Permissions(this ClaimsPrincipal principal) { - return new PermissionSet(principal.Claims.Where(x => x.Type == SquidexClaimTypes.Permissions).Select(x => new Permission(x.Value))); + return new PermissionSet(principal.Claims + .Where(x => + x.Type == SquidexClaimTypes.Permissions || + x.Type == SquidexClaimTypes.PermissionsClient) + .Select(x => new Permission(x.Value))); } public static IEnumerable GetSquidexClaims(this ClaimsPrincipal principal) { - return principal.Claims.Where(c => c.Type.StartsWith(SquidexClaimTypes.Prefix, StringComparison.Ordinal)); + return principal.Claims + .Where(x => + x.Type.StartsWith(SquidexClaimTypes.Prefix, StringComparison.Ordinal) || + x.Type.StartsWith(SquidexClaimTypes.PrefixClient, StringComparison.Ordinal)); } } } diff --git a/src/Squidex.Shared/Identity/SquidexClaimTypes.cs b/src/Squidex.Shared/Identity/SquidexClaimTypes.cs index bd32adb0d..32ba4f90c 100644 --- a/src/Squidex.Shared/Identity/SquidexClaimTypes.cs +++ b/src/Squidex.Shared/Identity/SquidexClaimTypes.cs @@ -21,8 +21,12 @@ namespace Squidex.Shared.Identity public static readonly string Permissions = "urn:squidex:permissions"; + public static readonly string PermissionsClient = "client_urn:squidex:permissions"; + public static readonly string Prefix = "urn:squidex:"; + public static readonly string PrefixClient = "client_urn:squidex:"; + public static readonly string PictureUrlStore = "store"; } } diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 5deacb593..fb765658c 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -57,12 +57,12 @@ namespace Squidex.Areas.Api.Controllers.Apps [ApiCosts(0)] public async Task GetApps() { - var userId = HttpContext.User.OpenIdSubject(); + var userOrClientId = HttpContext.User.UserOrClientId(); var userPermissions = HttpContext.User.Permissions(); - var entities = await appProvider.GetUserApps(userId, userPermissions); + var entities = await appProvider.GetUserApps(userOrClientId, userPermissions); - var response = entities.ToArray(a => AppDto.FromApp(a, userId, userPermissions, appPlansProvider)); + var response = entities.ToArray(a => AppDto.FromApp(a, userOrClientId, userPermissions, appPlansProvider)); Response.Headers[HeaderNames.ETag] = response.ToManyEtag(); diff --git a/src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs b/src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs index a8a63b02b..10c519799 100644 --- a/src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs +++ b/src/Squidex/Areas/Api/Controllers/Backups/RestoreController.cs @@ -43,7 +43,7 @@ namespace Squidex.Areas.Api.Controllers.Backups [ApiPermission(Permissions.AdminRestoreRead)] public async Task GetJob() { - var restoreGrain = grainFactory.GetGrain(User.OpenIdSubject()); + var restoreGrain = grainFactory.GetGrain(User.UserOrClientId()); var job = await restoreGrain.GetJobAsync(); @@ -69,7 +69,7 @@ namespace Squidex.Areas.Api.Controllers.Backups [ApiPermission(Permissions.AdminRestoreCreate)] public async Task PostRestore([FromBody] RestoreRequest request) { - var restoreGrain = grainFactory.GetGrain(User.OpenIdSubject()); + var restoreGrain = grainFactory.GetGrain(User.UserOrClientId()); await restoreGrain.RestoreAsync(request.Url, request.Name); diff --git a/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs b/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs index adc1451ad..af60dc5e3 100644 --- a/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs +++ b/src/Squidex/Areas/IdentityServer/Config/LazyClientStore.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; +using System.Security.Claims; using System.Threading.Tasks; using IdentityServer4; using IdentityServer4.Models; @@ -17,6 +18,8 @@ using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities; using Squidex.Infrastructure; using Squidex.Pipeline; +using Squidex.Shared; +using Squidex.Shared.Identity; namespace Squidex.Areas.IdentityServer.Config { @@ -25,14 +28,17 @@ namespace Squidex.Areas.IdentityServer.Config private readonly IAppProvider appProvider; private readonly Dictionary staticClients = new Dictionary(StringComparer.OrdinalIgnoreCase); - public LazyClientStore(IOptions urlsOptions, IAppProvider appProvider) + public LazyClientStore( + IOptions urlsOptions, + IOptions identityOptions, + IAppProvider appProvider) { Guard.NotNull(urlsOptions, nameof(urlsOptions)); Guard.NotNull(appProvider, nameof(appProvider)); this.appProvider = appProvider; - CreateStaticClients(urlsOptions); + CreateStaticClients(urlsOptions, identityOptions); } public async Task FindClientByIdAsync(string clientId) @@ -83,15 +89,15 @@ namespace Squidex.Areas.IdentityServer.Config }; } - private void CreateStaticClients(IOptions urlsOptions) + private void CreateStaticClients(IOptions urlsOptions, IOptions identityOptions) { - foreach (var client in CreateStaticClients(urlsOptions.Value)) + foreach (var client in CreateStaticClients(urlsOptions.Value, identityOptions.Value)) { staticClients[client.ClientId] = client; } } - private static IEnumerable CreateStaticClients(MyUrlsOptions urlsOptions) + private static IEnumerable CreateStaticClients(MyUrlsOptions urlsOptions, MyIdentityOptions identityOptions) { var frontendId = Constants.FrontendClient; @@ -150,6 +156,30 @@ namespace Squidex.Areas.IdentityServer.Config }, RequireConsent = false }; + + if (identityOptions.IsAdminClientConfigured()) + { + var id = identityOptions.AdminClientId; + + yield return new Client + { + ClientId = id, + ClientName = id, + ClientSecrets = new List { new Secret(identityOptions.AdminClientSecret.Sha256()) }, + AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds, + AllowedGrantTypes = GrantTypes.ClientCredentials, + AllowedScopes = new List + { + Constants.ApiScope, + Constants.RoleScope, + Constants.PermissionsScope + }, + Claims = new List + { + new Claim(SquidexClaimTypes.Permissions, Permissions.Admin) + } + }; + } } } } diff --git a/src/Squidex/Config/Logging.cs b/src/Squidex/Config/Logging.cs index bea1bbd60..ca61f4a2a 100644 --- a/src/Squidex/Config/Logging.cs +++ b/src/Squidex/Config/Logging.cs @@ -5,6 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +#define LOG_ALL_IDENTITY_SERVER_NONE + using System; using Microsoft.Extensions.Logging; @@ -40,7 +42,12 @@ namespace Squidex.Config { return level > LogLevel.Information; } - +#if LOG_ALL_IDENTITY_SERVER + if (category.StartsWith("IdentityServer4.", StringComparison.OrdinalIgnoreCase)) + { + return true; + } +#endif return level >= LogLevel.Information; }); } diff --git a/src/Squidex/Config/MyIdentityOptions.cs b/src/Squidex/Config/MyIdentityOptions.cs index 01a3045b8..e8547ff90 100644 --- a/src/Squidex/Config/MyIdentityOptions.cs +++ b/src/Squidex/Config/MyIdentityOptions.cs @@ -13,6 +13,10 @@ namespace Squidex.Config public string AdminPassword { get; set; } + public string AdminClientId { get; set; } + + public string AdminClientSecret { get; set; } + public string GithubClient { get; set; } public string GithubSecret { get; set; } @@ -48,6 +52,11 @@ namespace Squidex.Config return !string.IsNullOrWhiteSpace(AdminEmail) && !string.IsNullOrWhiteSpace(AdminPassword); } + public bool IsAdminClientConfigured() + { + return !string.IsNullOrWhiteSpace(AdminClientId) && !string.IsNullOrWhiteSpace(AdminClientSecret); + } + public bool IsOidcConfigured() { return !string.IsNullOrWhiteSpace(OidcAuthority) && !string.IsNullOrWhiteSpace(OidcClient) && !string.IsNullOrWhiteSpace(OidcSecret); diff --git a/src/Squidex/Pipeline/Extensions.cs b/src/Squidex/Pipeline/Extensions.cs index 35db046e0..98f693727 100644 --- a/src/Squidex/Pipeline/Extensions.cs +++ b/src/Squidex/Pipeline/Extensions.cs @@ -29,12 +29,17 @@ namespace Squidex.Pipeline { var parts = clientId.Split(':', '~'); - if (parts.Length != 2) + if (parts.Length == 1) { - return (null, null); + return (null, parts[0]); } - return (parts[0], parts[1]); + if (parts.Length == 2) + { + return (parts[0], parts[1]); + } + + return (null, null); } } } diff --git a/src/Squidex/appsettings.json b/src/Squidex/appsettings.json index 1b571af54..14e289162 100644 --- a/src/Squidex/appsettings.json +++ b/src/Squidex/appsettings.json @@ -263,6 +263,16 @@ * Enable password auth. Set this to false if you want to disable local login, leaving only 3rd party login options. */ "allowPasswordAuth": true, + /* + * Initial admin user. + */ + "adminEmail": "", + "adminPassword": "", + /* + * Client with all admin permissions. + */ + "adminClientId": "", + "adminClientSecret": "", /* * Settings for Google auth (keep empty to disable). */