Browse Source

More OIDC options.

pull/387/head
Sebastian Stehle 7 years ago
parent
commit
dc5a684265
  1. 6
      src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs
  2. 6
      src/Squidex.Web/Pipeline/EnforceHttpsMiddleware.cs
  3. 8
      src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs
  4. 10
      src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs
  5. 14
      src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs
  6. 6
      src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs
  7. 44
      src/Squidex/Config/Authentication/OidcHandler.cs
  8. 12
      src/Squidex/Config/Authentication/OidcServices.cs
  9. 12
      src/Squidex/Config/MyIdentityOptions.cs
  10. 3
      src/Squidex/appsettings.json

6
src/Squidex.Infrastructure/Commands/ReadonlyCommandMiddleware.cs

@ -13,18 +13,18 @@ namespace Squidex.Infrastructure.Commands
{ {
public sealed class ReadonlyCommandMiddleware : ICommandMiddleware public sealed class ReadonlyCommandMiddleware : ICommandMiddleware
{ {
private readonly IOptions<ReadonlyOptions> options; private readonly ReadonlyOptions options;
public ReadonlyCommandMiddleware(IOptions<ReadonlyOptions> options) public ReadonlyCommandMiddleware(IOptions<ReadonlyOptions> options)
{ {
Guard.NotNull(options, nameof(options)); Guard.NotNull(options, nameof(options));
this.options = options; this.options = options.Value;
} }
public Task HandleAsync(CommandContext context, Func<Task> next) public Task HandleAsync(CommandContext context, Func<Task> next)
{ {
if (options.Value.IsReadonly) if (options.IsReadonly)
{ {
throw new DomainException("Application is in readonly mode at the moment."); throw new DomainException("Application is in readonly mode at the moment.");
} }

6
src/Squidex.Web/Pipeline/EnforceHttpsMiddleware.cs

@ -14,16 +14,16 @@ namespace Squidex.Web.Pipeline
{ {
public sealed class EnforceHttpsMiddleware : IMiddleware public sealed class EnforceHttpsMiddleware : IMiddleware
{ {
private readonly IOptions<UrlsOptions> urls; private readonly UrlsOptions urls;
public EnforceHttpsMiddleware(IOptions<UrlsOptions> urls) public EnforceHttpsMiddleware(IOptions<UrlsOptions> urls)
{ {
this.urls = urls; this.urls = urls.Value;
} }
public async Task InvokeAsync(HttpContext context, RequestDelegate next) public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{ {
if (!urls.Value.EnforceHTTPS) if (!urls.EnforceHTTPS)
{ {
await next(context); await next(context);
} }

8
src/Squidex/Areas/Api/Controllers/Assets/AssetsController.cs

@ -37,7 +37,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
private readonly IAssetQueryService assetQuery; private readonly IAssetQueryService assetQuery;
private readonly IAssetUsageTracker assetStatsRepository; private readonly IAssetUsageTracker assetStatsRepository;
private readonly IAppPlansProvider appPlansProvider; private readonly IAppPlansProvider appPlansProvider;
private readonly IOptions<MyContentsControllerOptions> controllerOptions; private readonly MyContentsControllerOptions controllerOptions;
private readonly ITagService tagService; private readonly ITagService tagService;
private readonly AssetOptions assetOptions; private readonly AssetOptions assetOptions;
@ -55,7 +55,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
this.assetQuery = assetQuery; this.assetQuery = assetQuery;
this.assetStatsRepository = assetStatsRepository; this.assetStatsRepository = assetStatsRepository;
this.appPlansProvider = appPlansProvider; this.appPlansProvider = appPlansProvider;
this.controllerOptions = controllerOptions; this.controllerOptions = controllerOptions.Value;
this.tagService = tagService; this.tagService = tagService;
} }
@ -110,7 +110,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
return AssetsDto.FromAssets(assets, this, app); return AssetsDto.FromAssets(assets, this, app);
}); });
if (controllerOptions.Value.EnableSurrogateKeys && assets.Count <= controllerOptions.Value.MaxItemsForSurrogateKeys) if (controllerOptions.EnableSurrogateKeys && assets.Count <= controllerOptions.MaxItemsForSurrogateKeys)
{ {
Response.Headers["Surrogate-Key"] = assets.ToSurrogateKeys(); Response.Headers["Surrogate-Key"] = assets.ToSurrogateKeys();
} }
@ -148,7 +148,7 @@ namespace Squidex.Areas.Api.Controllers.Assets
return AssetDto.FromAsset(asset, this, app); return AssetDto.FromAsset(asset, this, app);
}); });
if (controllerOptions.Value.EnableSurrogateKeys) if (controllerOptions.EnableSurrogateKeys)
{ {
Response.Headers["Surrogate-Key"] = asset.ToSurrogateKey(); Response.Headers["Surrogate-Key"] = asset.ToSurrogateKey();
} }

10
src/Squidex/Areas/Api/Controllers/Contents/ContentsController.cs

@ -25,7 +25,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
{ {
public sealed class ContentsController : ApiController public sealed class ContentsController : ApiController
{ {
private readonly IOptions<MyContentsControllerOptions> controllerOptions; private readonly MyContentsControllerOptions controllerOptions;
private readonly IContentQueryService contentQuery; private readonly IContentQueryService contentQuery;
private readonly IContentWorkflow contentWorkflow; private readonly IContentWorkflow contentWorkflow;
private readonly IGraphQLService graphQl; private readonly IGraphQLService graphQl;
@ -39,7 +39,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
{ {
this.contentQuery = contentQuery; this.contentQuery = contentQuery;
this.contentWorkflow = contentWorkflow; this.contentWorkflow = contentWorkflow;
this.controllerOptions = controllerOptions; this.controllerOptions = controllerOptions.Value;
this.graphQl = graphQl; this.graphQl = graphQl;
} }
@ -205,7 +205,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
var response = ContentDto.FromContent(Context, content, this); var response = ContentDto.FromContent(Context, content, this);
if (controllerOptions.Value.EnableSurrogateKeys) if (controllerOptions.EnableSurrogateKeys)
{ {
Response.Headers["Surrogate-Key"] = content.ToSurrogateKey(); Response.Headers["Surrogate-Key"] = content.ToSurrogateKey();
} }
@ -240,7 +240,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
var response = ContentDto.FromContent(Context, content, this); var response = ContentDto.FromContent(Context, content, this);
if (controllerOptions.Value.EnableSurrogateKeys) if (controllerOptions.EnableSurrogateKeys)
{ {
Response.Headers["Surrogate-Key"] = content.ToSurrogateKey(); Response.Headers["Surrogate-Key"] = content.ToSurrogateKey();
} }
@ -446,7 +446,7 @@ namespace Squidex.Areas.Api.Controllers.Contents
private bool ShouldProvideSurrogateKeys(IReadOnlyList<IContentEntity> response) private bool ShouldProvideSurrogateKeys(IReadOnlyList<IContentEntity> response)
{ {
return controllerOptions.Value.EnableSurrogateKeys && response.Count <= controllerOptions.Value.MaxItemsForSurrogateKeys; return controllerOptions.EnableSurrogateKeys && response.Count <= controllerOptions.MaxItemsForSurrogateKeys;
} }
} }
} }

14
src/Squidex/Areas/IdentityServer/Controllers/Account/AccountController.cs

@ -36,7 +36,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account
private readonly UserManager<IdentityUser> userManager; private readonly UserManager<IdentityUser> userManager;
private readonly IUserFactory userFactory; private readonly IUserFactory userFactory;
private readonly IUserEvents userEvents; private readonly IUserEvents userEvents;
private readonly IOptions<MyIdentityOptions> identityOptions; private readonly MyIdentityOptions identityOptions;
private readonly ISemanticLog log; private readonly ISemanticLog log;
private readonly IIdentityServerInteractionService interactions; private readonly IIdentityServerInteractionService interactions;
@ -54,7 +54,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account
this.userManager = userManager; this.userManager = userManager;
this.userFactory = userFactory; this.userFactory = userFactory;
this.interactions = interactions; this.interactions = interactions;
this.identityOptions = identityOptions; this.identityOptions = identityOptions.Value;
this.signInManager = signInManager; this.signInManager = signInManager;
} }
@ -97,7 +97,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account
[Route("account/consent/")] [Route("account/consent/")]
public IActionResult Consent(string returnUrl = null) public IActionResult Consent(string returnUrl = null)
{ {
return View(new ConsentVM { PrivacyUrl = identityOptions.Value.PrivacyUrl, ReturnUrl = returnUrl }); return View(new ConsentVM { PrivacyUrl = identityOptions.PrivacyUrl, ReturnUrl = returnUrl });
} }
[HttpPost] [HttpPost]
@ -116,7 +116,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account
if (!ModelState.IsValid) if (!ModelState.IsValid)
{ {
var vm = new ConsentVM { PrivacyUrl = identityOptions.Value.PrivacyUrl, ReturnUrl = returnUrl }; var vm = new ConsentVM { PrivacyUrl = identityOptions.PrivacyUrl, ReturnUrl = returnUrl };
return View(vm); return View(vm);
} }
@ -194,7 +194,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account
private async Task<IActionResult> LoginViewAsync(string returnUrl, bool isLogin, bool isFailed) private async Task<IActionResult> LoginViewAsync(string returnUrl, bool isLogin, bool isFailed)
{ {
var allowPasswordAuth = identityOptions.Value.AllowPasswordAuth; var allowPasswordAuth = identityOptions.AllowPasswordAuth;
var externalProviders = await signInManager.GetExternalProvidersAsync(); var externalProviders = await signInManager.GetExternalProvidersAsync();
@ -298,7 +298,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account
{ {
return RedirectToAction(nameof(Login)); return RedirectToAction(nameof(Login));
} }
else if (user != null && !user.HasConsent()) else if (user != null && !user.HasConsent() && !identityOptions.NoConsent)
{ {
return RedirectToAction(nameof(Consent), new { returnUrl }); return RedirectToAction(nameof(Consent), new { returnUrl });
} }
@ -327,7 +327,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Account
private Task<bool> LockAsync(UserWithClaims user, bool isFirst) private Task<bool> LockAsync(UserWithClaims user, bool isFirst)
{ {
if (isFirst || !identityOptions.Value.LockAutomatically) if (isFirst || !identityOptions.LockAutomatically)
{ {
return TaskHelper.True; return TaskHelper.True;
} }

6
src/Squidex/Areas/IdentityServer/Controllers/Profile/ProfileController.cs

@ -32,7 +32,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile
private readonly UserManager<IdentityUser> userManager; private readonly UserManager<IdentityUser> userManager;
private readonly IUserPictureStore userPictureStore; private readonly IUserPictureStore userPictureStore;
private readonly IAssetThumbnailGenerator assetThumbnailGenerator; private readonly IAssetThumbnailGenerator assetThumbnailGenerator;
private readonly IOptions<MyIdentityOptions> identityOptions; private readonly MyIdentityOptions identityOptions;
public ProfileController( public ProfileController(
SignInManager<IdentityUser> signInManager, SignInManager<IdentityUser> signInManager,
@ -42,7 +42,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile
IOptions<MyIdentityOptions> identityOptions) IOptions<MyIdentityOptions> identityOptions)
{ {
this.signInManager = signInManager; this.signInManager = signInManager;
this.identityOptions = identityOptions; this.identityOptions = identityOptions.Value;
this.userManager = userManager; this.userManager = userManager;
this.userPictureStore = userPictureStore; this.userPictureStore = userPictureStore;
this.assetThumbnailGenerator = assetThumbnailGenerator; this.assetThumbnailGenerator = assetThumbnailGenerator;
@ -198,7 +198,7 @@ namespace Squidex.Areas.IdentityServer.Controllers.Profile
DisplayName = user.DisplayName(), DisplayName = user.DisplayName(),
IsHidden = user.IsHidden(), IsHidden = user.IsHidden(),
HasPassword = taskForPassword.Result, HasPassword = taskForPassword.Result,
HasPasswordAuth = identityOptions.Value.AllowPasswordAuth, HasPasswordAuth = identityOptions.AllowPasswordAuth,
SuccessMessage = successMessage SuccessMessage = successMessage
}; };

44
src/Squidex/Config/Authentication/OidcHandler.cs

@ -0,0 +1,44 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Squidex.Shared.Identity;
namespace Squidex.Config.Authentication
{
public sealed class OidcHandler : OpenIdConnectEvents
{
private readonly MyIdentityOptions options;
public OidcHandler(MyIdentityOptions options )
{
this.options = options;
}
public override Task TokenValidated(TokenValidatedContext context)
{
var identity = (ClaimsIdentity)context.Principal.Identity;
if (!string.IsNullOrWhiteSpace(options.OidcRoleClaimType) && options.OidcRoleMapping?.Count >= 0)
{
var role = identity.FindFirst(x => x.Type == options.OidcRoleClaimType)?.Value;
if (!string.IsNullOrWhiteSpace(role) && options.OidcRoleMapping.TryGetValue(role, out var permissions) && permissions != null)
{
foreach (var permission in permissions)
{
identity.AddClaim(new Claim(SquidexClaimTypes.Permissions, permission));
}
}
}
return base.TokenValidated(context);
}
}
}

12
src/Squidex/Config/Authentication/OidcServices.cs

@ -8,6 +8,7 @@
using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect; using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Squidex.Infrastructure.Tasks;
using Squidex.Web; using Squidex.Web;
namespace Squidex.Config.Authentication namespace Squidex.Config.Authentication
@ -25,9 +26,16 @@ namespace Squidex.Config.Authentication
options.Authority = identityOptions.OidcAuthority; options.Authority = identityOptions.OidcAuthority;
options.ClientId = identityOptions.OidcClient; options.ClientId = identityOptions.OidcClient;
options.ClientSecret = identityOptions.OidcSecret; options.ClientSecret = identityOptions.OidcSecret;
options.Scope.Add(Constants.EmailScope);
options.Scope.Add(Constants.PermissionsScope);
options.RequireHttpsMetadata = false; options.RequireHttpsMetadata = false;
options.Events = new OidcHandler(identityOptions);
if (identityOptions.OidcScopes != null)
{
foreach (var scope in identityOptions.OidcScopes)
{
options.Scope.Add(scope);
}
}
}); });
} }

12
src/Squidex/Config/MyIdentityOptions.cs

@ -5,6 +5,8 @@
// All rights reserved. Licensed under the MIT license. // All rights reserved. Licensed under the MIT license.
// ========================================================================== // ==========================================================================
using System.Collections.Generic;
namespace Squidex.Config namespace Squidex.Config
{ {
public sealed class MyIdentityOptions public sealed class MyIdentityOptions
@ -37,6 +39,12 @@ namespace Squidex.Config
public string OidcAuthority { get; set; } public string OidcAuthority { get; set; }
public string OidcRoleClaimType { get; set; }
public string[] OidcScopes { get; set; }
public Dictionary<string, string[]> OidcRoleMapping { get; set; }
public string AuthorityUrl { get; set; } public string AuthorityUrl { get; set; }
public string PrivacyUrl { get; set; } public string PrivacyUrl { get; set; }
@ -47,6 +55,8 @@ namespace Squidex.Config
public bool LockAutomatically { get; set; } public bool LockAutomatically { get; set; }
public bool NoConsent { get; set; }
public bool IsAdminConfigured() public bool IsAdminConfigured()
{ {
return !string.IsNullOrWhiteSpace(AdminEmail) && !string.IsNullOrWhiteSpace(AdminPassword); return !string.IsNullOrWhiteSpace(AdminEmail) && !string.IsNullOrWhiteSpace(AdminPassword);
@ -59,7 +69,7 @@ namespace Squidex.Config
public bool IsOidcConfigured() public bool IsOidcConfigured()
{ {
return !string.IsNullOrWhiteSpace(OidcAuthority) && !string.IsNullOrWhiteSpace(OidcClient) && !string.IsNullOrWhiteSpace(OidcSecret); return !string.IsNullOrWhiteSpace(OidcAuthority) && !string.IsNullOrWhiteSpace(OidcClient);
} }
public bool IsGithubAuthConfigured() public bool IsGithubAuthConfigured()

3
src/Squidex/appsettings.json

@ -440,6 +440,9 @@
"oidcAuthority": "", "oidcAuthority": "",
"oidcClient": "", "oidcClient": "",
"oidcSecret": "", "oidcSecret": "",
"oidcScopes": [
"email"
],
/* /*
* Lock new users automatically, the administrator must unlock them. * Lock new users automatically, the administrator must unlock them.
*/ */

Loading…
Cancel
Save