Browse Source

Fix

pull/1/head
Sebastian 9 years ago
parent
commit
74b7c43b5b
  1. 21
      src/Squidex/Configurations/Constants.cs
  2. 42
      src/Squidex/Configurations/Identity/IdentityServices.cs
  3. 22
      src/Squidex/Configurations/Identity/IdentityUsage.cs
  4. 65
      src/Squidex/Configurations/Identity/LazyClientStore.cs
  5. 10
      src/Squidex/Configurations/Identity/MyIdentityOptions.cs
  6. 16
      src/Squidex/Configurations/Web/WebModule.cs
  7. 5
      src/Squidex/Modules/Api/Apps/AppController.cs
  8. 18
      src/Squidex/Modules/Api/Schemas/SchemaFieldsController.cs
  9. 4
      src/Squidex/Modules/UI/Account/AccountController.cs
  10. 8
      src/Squidex/Pipeline/AppFeature.cs
  11. 51
      src/Squidex/Pipeline/AppFilterAttribute.cs
  12. 46
      src/Squidex/Pipeline/AppMiddleware.cs
  13. 85
      src/Squidex/Startup.cs
  14. 10
      src/Squidex/app/shared/services/auth.service.ts

21
src/Squidex/Configurations/Constants.cs

@ -0,0 +1,21 @@
// ==========================================================================
// Constants.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
namespace Squidex.Configurations
{
public class Constants
{
public const string ApiPrefix = "/api";
public const string ApiScope = "squidex-api";
public const string FrontendClient = "squidex-frontend";
public const string IdentityPrefix = "/identity-server";
}
}

42
src/Squidex/Configurations/Identity/IdentityServices.cs

@ -10,6 +10,8 @@ using System.Collections.Generic;
using System.IO; using System.IO;
using System.Security.Cryptography.X509Certificates; using System.Security.Cryptography.X509Certificates;
using IdentityServer4.Models; using IdentityServer4.Models;
using IdentityServer4.Stores;
using IdentityServer4.Stores.InMemory;
using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity.MongoDB; using Microsoft.AspNetCore.Identity.MongoDB;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
@ -24,10 +26,14 @@ namespace Squidex.Configurations.Identity
var certificate = new X509Certificate2(certPath, "password"); var certificate = new X509Certificate2(certPath, "password");
services.AddIdentityServer() services.AddSingleton(
.SetSigningCredential(certificate) GetScopes());
.AddInMemoryScopes(GetScopes()) services.AddSingleton<IClientStore,
.AddInMemoryClients(GetClients()) LazyClientStore>();
services.AddSingleton<IScopeStore,
InMemoryScopeStore>();
services.AddIdentityServer().SetSigningCredential(certificate)
.AddAspNetIdentity<IdentityUser>(); .AddAspNetIdentity<IdentityUser>();
return services; return services;
@ -47,35 +53,9 @@ namespace Squidex.Configurations.Identity
{ {
StandardScopes.OpenId, StandardScopes.OpenId,
StandardScopes.Profile, StandardScopes.Profile,
new Scope new Scope
{ {
Name = "api1", Name = Constants.ApiScope, Type = ScopeType.Resource
Description = "My API"
}
};
}
public static IEnumerable<Client> GetClients()
{
return new List<Client>
{
new Client
{
ClientId = "management-portal",
ClientName = "MVC Client",
RedirectUris = new List<string>
{
"http://localhost:5000/account/client-silent",
"http://localhost:5000/account/client-popup"
},
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes = new List<string>
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name
},
RequireConsent = false
} }
}; };
} }

22
src/Squidex/Configurations/Identity/IdentityUsage.cs

@ -24,17 +24,12 @@ namespace Squidex.Configurations.Identity
{ {
app.UseIdentity(); app.UseIdentity();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies"
});
return app; return app;
} }
public static IApplicationBuilder UseMyIdentityServer(this IApplicationBuilder app) public static IApplicationBuilder UseMyIdentityServer(this IApplicationBuilder app)
{ {
app.UseIdentityServer(); app.UseIdentityServer();
return app; return app;
} }
@ -85,18 +80,19 @@ namespace Squidex.Configurations.Identity
public static IApplicationBuilder UseMyApiProtection(this IApplicationBuilder app) public static IApplicationBuilder UseMyApiProtection(this IApplicationBuilder app)
{ {
const string apiScope = Constants.ApiScope;
var options = app.ApplicationServices.GetService<IOptions<MyIdentityOptions>>().Value; var options = app.ApplicationServices.GetService<IOptions<MyIdentityOptions>>().Value;
if (!string.IsNullOrWhiteSpace(options.BaseUrl)) if (!string.IsNullOrWhiteSpace(options.BaseUrl))
{ {
app.Map("/api", api => var apiAuthorityUrl = options.BuildUrl(Constants.IdentityPrefix);
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{ {
api.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions Authority = apiAuthorityUrl,
{ ScopeName = apiScope,
Authority = options.BaseUrl, RequireHttpsMetadata = options.RequiresHttps
ScopeName = "api",
RequireHttpsMetadata = options.RequiresHttps
});
}); });
} }

65
src/Squidex/Configurations/Identity/LazyClientStore.cs

@ -0,0 +1,65 @@
// ==========================================================================
// LazyClientStore.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using Microsoft.Extensions.Options;
using Squidex.Infrastructure;
namespace Squidex.Configurations.Identity
{
public class LazyClientStore : IClientStore
{
private readonly Dictionary<string, Client> clients = new Dictionary<string, Client>(StringComparer.OrdinalIgnoreCase);
public LazyClientStore(IOptions<MyIdentityOptions> identityOptions)
{
Guard.NotNull(identityOptions, nameof(identityOptions));
foreach (var client in CreateClients(identityOptions.Value))
{
clients[client.ClientId] = client;
}
}
public Task<Client> FindClientByIdAsync(string clientId)
{
var client = clients.GetOrDefault(clientId);
return Task.FromResult(client);
}
private static IEnumerable<Client> CreateClients(MyIdentityOptions options)
{
const string id = Constants.FrontendClient;
yield return new Client
{
ClientId = id,
ClientName = id,
RedirectUris = new List<string>
{
options.BuildUrl("identity-server/client-callback-silent/"),
options.BuildUrl("identity-server/client-callback-popup/")
},
AllowAccessTokensViaBrowser = true,
AllowedGrantTypes = GrantTypes.Implicit,
AllowedScopes = new List<string>
{
StandardScopes.OpenId.Name,
StandardScopes.Profile.Name,
Constants.ApiScope
},
RequireConsent = false
};
}
}
}

10
src/Squidex/Configurations/Identity/MyIdentityOptions.cs

@ -5,10 +5,13 @@
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
namespace Squidex.Configurations.Identity namespace Squidex.Configurations.Identity
{ {
public sealed class MyIdentityOptions public sealed class MyIdentityOptions
{ {
public string BaseUrl { get; set; }
public string DefaultUsername { get; set; } public string DefaultUsername { get; set; }
public string DefaultPassword { get; set; } public string DefaultPassword { get; set; }
@ -17,8 +20,11 @@ namespace Squidex.Configurations.Identity
public string GoogleSecret { get; set; } public string GoogleSecret { get; set; }
public string BaseUrl { get; set; }
public bool RequiresHttps { get; set; } public bool RequiresHttps { get; set; }
public string BuildUrl(string path)
{
return $"{BaseUrl.TrimEnd('/')}/{path.Trim('/')}/";
}
} }
} }

16
src/Squidex/Configurations/Domain/InfrastructureUsage.cs → src/Squidex/Configurations/Web/WebModule.cs

@ -1,23 +1,23 @@
// ========================================================================== // ==========================================================================
// InfrastructureUsage.cs // WebModule.cs
// Squidex Headless CMS // Squidex Headless CMS
// ========================================================================== // ==========================================================================
// Copyright (c) Squidex Group // Copyright (c) Squidex Group
// All rights reserved. // All rights reserved.
// ========================================================================== // ==========================================================================
using Microsoft.AspNetCore.Builder; using Autofac;
using Squidex.Pipeline; using Squidex.Pipeline;
namespace Squidex.Configurations.Domain namespace Squidex.Configurations.Web
{ {
public static class InfrastructureUsage public class WebModule : Module
{ {
public static IApplicationBuilder UseMyApps(this IApplicationBuilder app) protected override void Load(ContainerBuilder builder)
{ {
app.UseMiddleware<AppMiddleware>(); builder.RegisterType<AppFilterAttribute>()
.AsSelf()
return app; .SingleInstance();
} }
} }
} }

5
src/Squidex/Modules/Api/Apps/AppController.cs

@ -23,7 +23,6 @@ namespace Squidex.Modules.Api.Apps
{ {
[Authorize] [Authorize]
[ApiExceptionFilter] [ApiExceptionFilter]
[DeactivateForAppDomain]
public class AppController : ControllerBase public class AppController : ControllerBase
{ {
private readonly IAppRepository appRepository; private readonly IAppRepository appRepository;
@ -35,7 +34,7 @@ namespace Squidex.Modules.Api.Apps
} }
[HttpGet] [HttpGet]
[Route("api/apps/")] [Route("apps/")]
public async Task<List<ListAppDto>> Query() public async Task<List<ListAppDto>> Query()
{ {
var schemas = await appRepository.QueryAllAsync(); var schemas = await appRepository.QueryAllAsync();
@ -44,7 +43,7 @@ namespace Squidex.Modules.Api.Apps
} }
[HttpPost] [HttpPost]
[Route("api/apps/")] [Route("apps/")]
public async Task<IActionResult> Create([FromBody] CreateAppDto model) public async Task<IActionResult> Create([FromBody] CreateAppDto model)
{ {
var command = SimpleMapper.Map(model, new CreateApp { AggregateId = Guid.NewGuid() }); var command = SimpleMapper.Map(model, new CreateApp { AggregateId = Guid.NewGuid() });

18
src/Squidex/Modules/Api/Schemas/SchemaFieldsController.cs

@ -7,6 +7,7 @@
// ========================================================================== // ==========================================================================
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Squidex.Infrastructure.CQRS.Commands; using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection; using Squidex.Infrastructure.Reflection;
@ -16,7 +17,10 @@ using Squidex.Write.Schemas.Commands;
namespace Squidex.Modules.Api.Schemas namespace Squidex.Modules.Api.Schemas
{ {
[Authorize]
[ApiExceptionFilter] [ApiExceptionFilter]
[ServiceFilter(typeof(AppFilterAttribute))]
[Route("api/apps/{app}")]
public class SchemasFieldsController : ControllerBase public class SchemasFieldsController : ControllerBase
{ {
public SchemasFieldsController(ICommandBus commandBus) public SchemasFieldsController(ICommandBus commandBus)
@ -25,7 +29,7 @@ namespace Squidex.Modules.Api.Schemas
} }
[HttpPost] [HttpPost]
[Route("api/schemas/{name}/fields/")] [Route("schemas/{name}/fields/")]
public Task Add(string name, [FromBody] CreateFieldDto model) public Task Add(string name, [FromBody] CreateFieldDto model)
{ {
var command = SimpleMapper.Map(model, new AddField()); var command = SimpleMapper.Map(model, new AddField());
@ -34,7 +38,7 @@ namespace Squidex.Modules.Api.Schemas
} }
[HttpPut] [HttpPut]
[Route("api/schemas/{name}/fields/{fieldId:long}/")] [Route("schemas/{name}/fields/{fieldId:long}/")]
public Task Update(string name, long fieldId, [FromBody] UpdateFieldDto model) public Task Update(string name, long fieldId, [FromBody] UpdateFieldDto model)
{ {
var command = SimpleMapper.Map(model, new UpdateField()); var command = SimpleMapper.Map(model, new UpdateField());
@ -43,7 +47,7 @@ namespace Squidex.Modules.Api.Schemas
} }
[HttpPut] [HttpPut]
[Route("api/schemas/{name}/fields/{fieldId:long}/hide/")] [Route("schemas/{name}/fields/{fieldId:long}/hide/")]
public Task Hide(string name, long fieldId) public Task Hide(string name, long fieldId)
{ {
var command = new HideField { FieldId = fieldId }; var command = new HideField { FieldId = fieldId };
@ -52,7 +56,7 @@ namespace Squidex.Modules.Api.Schemas
} }
[HttpPut] [HttpPut]
[Route("api/schemas/{name}/fields/{fieldId:long}/show/")] [Route("schemas/{name}/fields/{fieldId:long}/show/")]
public Task Show(string name, long fieldId) public Task Show(string name, long fieldId)
{ {
var command = new ShowField { FieldId = fieldId }; var command = new ShowField { FieldId = fieldId };
@ -61,7 +65,7 @@ namespace Squidex.Modules.Api.Schemas
} }
[HttpPut] [HttpPut]
[Route("api/schemas/{name}/fields/{fieldId:long}/enable/")] [Route("schemas/{name}/fields/{fieldId:long}/enable/")]
public Task Enable(string name, long fieldId) public Task Enable(string name, long fieldId)
{ {
var command = new EnableField { FieldId = fieldId }; var command = new EnableField { FieldId = fieldId };
@ -70,7 +74,7 @@ namespace Squidex.Modules.Api.Schemas
} }
[HttpPut] [HttpPut]
[Route("api/schemas/{name}/fields/{fieldId:long}/disable/")] [Route("schemas/{name}/fields/{fieldId:long}/disable/")]
public Task Disable(string name, long fieldId) public Task Disable(string name, long fieldId)
{ {
var command = new DisableField { FieldId = fieldId }; var command = new DisableField { FieldId = fieldId };
@ -79,7 +83,7 @@ namespace Squidex.Modules.Api.Schemas
} }
[HttpDelete] [HttpDelete]
[Route("api/schemas/{name}/fields/{fieldId:long}/")] [Route("schemas/{name}/fields/{fieldId:long}/")]
public Task Delete(string name, long fieldId) public Task Delete(string name, long fieldId)
{ {
var command = new DeleteField { FieldId = fieldId }; var command = new DeleteField { FieldId = fieldId };

4
src/Squidex/Modules/UI/Account/AccountController.cs

@ -38,14 +38,14 @@ namespace Squidex.Modules.UI.Account
} }
[HttpGet] [HttpGet]
[Route("account/client-silent/")] [Route("client-callback-silent/")]
public IActionResult ClientSilent() public IActionResult ClientSilent()
{ {
return View(); return View();
} }
[HttpGet] [HttpGet]
[Route("account/client-popup/")] [Route("client-callback-popup/")]
public IActionResult ClientPopup() public IActionResult ClientPopup()
{ {
return View(); return View();

8
src/Squidex/Pipeline/AppFeature.cs

@ -1,3 +1,11 @@
// ==========================================================================
// AppFeature.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System; using System;
namespace Squidex.Pipeline namespace Squidex.Pipeline

51
src/Squidex/Pipeline/AppFilterAttribute.cs

@ -0,0 +1,51 @@
// ==========================================================================
// AppMiddleware.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Squidex.Read.Apps.Services;
namespace Squidex.Pipeline
{
public sealed class AppFilterAttribute : ActionFilterAttribute
{
private readonly IAppProvider appProvider;
public AppFilterAttribute(IAppProvider appProvider)
{
this.appProvider = appProvider;
}
public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
var appName = context.RouteData.Values["app"]?.ToString();
if (!string.IsNullOrWhiteSpace(appName))
{
var appId = await appProvider.FindAppIdByNameAsync(appName);
if (!appId.HasValue)
{
context.Result = new NotFoundResult();
return;
}
if (!context.HttpContext.User.HasClaim("app", appName))
{
context.Result = new NotFoundResult();
return;
}
context.HttpContext.Features.Set<IAppFeature>(new AppFeature(appId.Value));
}
await next();
}
}
}

46
src/Squidex/Pipeline/AppMiddleware.cs

@ -1,46 +0,0 @@
// ==========================================================================
// AppMiddleware.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System.Threading.Tasks;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Squidex.Read.Apps.Services;
namespace Squidex.Pipeline
{
public sealed class AppMiddleware
{
private readonly IAppProvider appProvider;
private readonly IHostingEnvironment appEnvironment;
private readonly RequestDelegate next;
public AppMiddleware(RequestDelegate next, IAppProvider appProvider, IHostingEnvironment appEnvironment)
{
this.next = next;
this.appProvider = appProvider;
this.appEnvironment = appEnvironment;
}
public async Task Invoke(HttpContext context)
{
var hostParts = context.Request.Host.ToString().Split('.');
if (appEnvironment.IsDevelopment() || hostParts.Length >= 3)
{
var appId = await appProvider.FindAppIdByNameAsync(hostParts[0]);
if (appId.HasValue)
{
context.Features.Set<IAppFeature>(new AppFeature(appId.Value));
}
}
await next(context);
}
}
}

85
src/Squidex/Startup.cs

@ -15,18 +15,28 @@ using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Squidex.Configurations;
using Squidex.Configurations.Domain; using Squidex.Configurations.Domain;
using Squidex.Configurations.EventStore; using Squidex.Configurations.EventStore;
using Squidex.Configurations.Identity; using Squidex.Configurations.Identity;
using Squidex.Configurations.Web; using Squidex.Configurations.Web;
using Squidex.Store.MongoDb; using Squidex.Store.MongoDb;
using System.Linq;
using Microsoft.AspNetCore.Http;
// ReSharper disable ConvertClosureToMethodGroup
// ReSharper disable AccessToModifiedClosure // ReSharper disable AccessToModifiedClosure
namespace Squidex namespace Squidex
{ {
public class Startup public class Startup
{ {
private static readonly string[] IdentityServerPaths =
{
"/client-callback-popup",
"/client-callback-silent",
"/account"
};
public IConfigurationRoot Configuration { get; } public IConfigurationRoot Configuration { get; }
public IHostingEnvironment Environment { get; } public IHostingEnvironment Environment { get; }
@ -65,10 +75,11 @@ namespace Squidex
Configuration.GetSection("identity")); Configuration.GetSection("identity"));
var builder = new ContainerBuilder(); var builder = new ContainerBuilder();
builder.RegisterModule<InfrastructureModule>();
builder.RegisterModule<EventStoreModule>(); builder.RegisterModule<EventStoreModule>();
builder.RegisterModule<InfrastructureModule>();
builder.RegisterModule<MongoDbModule>(); builder.RegisterModule<MongoDbModule>();
builder.RegisterModule<ReadModule>(); builder.RegisterModule<ReadModule>();
builder.RegisterModule<WebModule>();
builder.RegisterModule<WriteModule>(); builder.RegisterModule<WriteModule>();
builder.Populate(services); builder.Populate(services);
@ -91,16 +102,70 @@ namespace Squidex
app.UseDefaultFiles(new DefaultFilesOptions { DefaultFileNames = new List<string> { "build/index.html" } }); app.UseDefaultFiles(new DefaultFilesOptions { DefaultFileNames = new List<string> { "build/index.html" } });
} }
app.UseMyDefaultUser(); UseIdentity(app);
app.UseMyEventStore(); UseApi(app);
app.UseMyIdentity(); UseFrontend(app);
app.UseMyIdentityServer(); }
app.UseMyApiProtection();
app.UseMyGoogleAuthentication(); private void UseIdentity(IApplicationBuilder app)
app.UseMyApps(); {
app.Map(Constants.IdentityPrefix, identityApp =>
{
if (Environment.IsDevelopment())
{
identityApp.UseDeveloperExceptionPage();
}
identityApp.UseMyIdentity();
identityApp.UseMyIdentityServer();
identityApp.UseMyApiProtection();
identityApp.UseMyGoogleAuthentication();
identityApp.UseStaticFiles();
identityApp.MapWhen(x => IsIdentityRequest(x), mvcApp =>
{
mvcApp.UseMvc();
});
});
}
private void UseApi(IApplicationBuilder app)
{
app.Map(Constants.ApiPrefix, appApi =>
{
if (Environment.IsDevelopment())
{
appApi.UseDeveloperExceptionPage();
}
appApi.UseMyApiProtection();
appApi.MapWhen(x => !IsIdentityRequest(x), mvcApp =>
{
mvcApp.UseMvc();
});
});
}
private void UseFrontend(IApplicationBuilder app)
{
if (Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseWebpackProxy();
app.UseDefaultFiles();
}
else
{
app.UseDefaultFiles(new DefaultFilesOptions { DefaultFileNames = new List<string> { "build/index.html" } });
}
app.UseStaticFiles(); app.UseStaticFiles();
app.UseMvc(); }
private static bool IsIdentityRequest(HttpContext context)
{
return IdentityServerPaths.Any(p => context.Request.Path.StartsWithSegments(p));
} }
} }
} }

10
src/Squidex/app/shared/services/auth.service.ts

@ -34,10 +34,12 @@ export class AuthService {
Log.logger = console; Log.logger = console;
this.userManager = new UserManager({ this.userManager = new UserManager({
client_id: 'management-portal', client_id: 'squidex-frontend',
silent_redirect_uri: apiUrl.buildUrl('account/client-silent'), silent_redirect_uri: apiUrl.buildUrl('identity-server/client-callback-silent/'),
popup_redirect_uri: apiUrl.buildUrl('account/client-popup'), popup_redirect_uri: apiUrl.buildUrl('identity-server/client-callback-popup/'),
authority: apiUrl.buildUrl('/'), authority: apiUrl.buildUrl('identity-server/'),
response_type: 'id_token token',
scope: 'openid profile squidex-api'
}); });
this.userManager.getUser() this.userManager.getUser()

Loading…
Cancel
Save