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.Security.Cryptography.X509Certificates;
using IdentityServer4.Models;
using IdentityServer4.Stores;
using IdentityServer4.Stores.InMemory;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Identity.MongoDB;
using Microsoft.Extensions.DependencyInjection;
@ -24,10 +26,14 @@ namespace Squidex.Configurations.Identity
var certificate = new X509Certificate2(certPath, "password");
services.AddIdentityServer()
.SetSigningCredential(certificate)
.AddInMemoryScopes(GetScopes())
.AddInMemoryClients(GetClients())
services.AddSingleton(
GetScopes());
services.AddSingleton<IClientStore,
LazyClientStore>();
services.AddSingleton<IScopeStore,
InMemoryScopeStore>();
services.AddIdentityServer().SetSigningCredential(certificate)
.AddAspNetIdentity<IdentityUser>();
return services;
@ -47,35 +53,9 @@ namespace Squidex.Configurations.Identity
{
StandardScopes.OpenId,
StandardScopes.Profile,
new Scope
{
Name = "api1",
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
Name = Constants.ApiScope, Type = ScopeType.Resource
}
};
}

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

@ -24,17 +24,12 @@ namespace Squidex.Configurations.Identity
{
app.UseIdentity();
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = "Cookies"
});
return app;
}
public static IApplicationBuilder UseMyIdentityServer(this IApplicationBuilder app)
{
app.UseIdentityServer();
app.UseIdentityServer();
return app;
}
@ -85,18 +80,19 @@ namespace Squidex.Configurations.Identity
public static IApplicationBuilder UseMyApiProtection(this IApplicationBuilder app)
{
const string apiScope = Constants.ApiScope;
var options = app.ApplicationServices.GetService<IOptions<MyIdentityOptions>>().Value;
if (!string.IsNullOrWhiteSpace(options.BaseUrl))
{
app.Map("/api", api =>
var apiAuthorityUrl = options.BuildUrl(Constants.IdentityPrefix);
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
api.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = options.BaseUrl,
ScopeName = "api",
RequireHttpsMetadata = options.RequiresHttps
});
Authority = apiAuthorityUrl,
ScopeName = apiScope,
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
// All rights reserved.
// ==========================================================================
namespace Squidex.Configurations.Identity
{
public sealed class MyIdentityOptions
{
public string BaseUrl { get; set; }
public string DefaultUsername { get; set; }
public string DefaultPassword { get; set; }
@ -17,8 +20,11 @@ namespace Squidex.Configurations.Identity
public string GoogleSecret { get; set; }
public string BaseUrl { 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
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using Microsoft.AspNetCore.Builder;
using Autofac;
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>();
return app;
builder.RegisterType<AppFilterAttribute>()
.AsSelf()
.SingleInstance();
}
}
}

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

@ -23,7 +23,6 @@ namespace Squidex.Modules.Api.Apps
{
[Authorize]
[ApiExceptionFilter]
[DeactivateForAppDomain]
public class AppController : ControllerBase
{
private readonly IAppRepository appRepository;
@ -35,7 +34,7 @@ namespace Squidex.Modules.Api.Apps
}
[HttpGet]
[Route("api/apps/")]
[Route("apps/")]
public async Task<List<ListAppDto>> Query()
{
var schemas = await appRepository.QueryAllAsync();
@ -44,7 +43,7 @@ namespace Squidex.Modules.Api.Apps
}
[HttpPost]
[Route("api/apps/")]
[Route("apps/")]
public async Task<IActionResult> Create([FromBody] CreateAppDto model)
{
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 Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Squidex.Infrastructure.CQRS.Commands;
using Squidex.Infrastructure.Reflection;
@ -16,7 +17,10 @@ using Squidex.Write.Schemas.Commands;
namespace Squidex.Modules.Api.Schemas
{
[Authorize]
[ApiExceptionFilter]
[ServiceFilter(typeof(AppFilterAttribute))]
[Route("api/apps/{app}")]
public class SchemasFieldsController : ControllerBase
{
public SchemasFieldsController(ICommandBus commandBus)
@ -25,7 +29,7 @@ namespace Squidex.Modules.Api.Schemas
}
[HttpPost]
[Route("api/schemas/{name}/fields/")]
[Route("schemas/{name}/fields/")]
public Task Add(string name, [FromBody] CreateFieldDto model)
{
var command = SimpleMapper.Map(model, new AddField());
@ -34,7 +38,7 @@ namespace Squidex.Modules.Api.Schemas
}
[HttpPut]
[Route("api/schemas/{name}/fields/{fieldId:long}/")]
[Route("schemas/{name}/fields/{fieldId:long}/")]
public Task Update(string name, long fieldId, [FromBody] UpdateFieldDto model)
{
var command = SimpleMapper.Map(model, new UpdateField());
@ -43,7 +47,7 @@ namespace Squidex.Modules.Api.Schemas
}
[HttpPut]
[Route("api/schemas/{name}/fields/{fieldId:long}/hide/")]
[Route("schemas/{name}/fields/{fieldId:long}/hide/")]
public Task Hide(string name, long fieldId)
{
var command = new HideField { FieldId = fieldId };
@ -52,7 +56,7 @@ namespace Squidex.Modules.Api.Schemas
}
[HttpPut]
[Route("api/schemas/{name}/fields/{fieldId:long}/show/")]
[Route("schemas/{name}/fields/{fieldId:long}/show/")]
public Task Show(string name, long fieldId)
{
var command = new ShowField { FieldId = fieldId };
@ -61,7 +65,7 @@ namespace Squidex.Modules.Api.Schemas
}
[HttpPut]
[Route("api/schemas/{name}/fields/{fieldId:long}/enable/")]
[Route("schemas/{name}/fields/{fieldId:long}/enable/")]
public Task Enable(string name, long fieldId)
{
var command = new EnableField { FieldId = fieldId };
@ -70,7 +74,7 @@ namespace Squidex.Modules.Api.Schemas
}
[HttpPut]
[Route("api/schemas/{name}/fields/{fieldId:long}/disable/")]
[Route("schemas/{name}/fields/{fieldId:long}/disable/")]
public Task Disable(string name, long fieldId)
{
var command = new DisableField { FieldId = fieldId };
@ -79,7 +83,7 @@ namespace Squidex.Modules.Api.Schemas
}
[HttpDelete]
[Route("api/schemas/{name}/fields/{fieldId:long}/")]
[Route("schemas/{name}/fields/{fieldId:long}/")]
public Task Delete(string name, long 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]
[Route("account/client-silent/")]
[Route("client-callback-silent/")]
public IActionResult ClientSilent()
{
return View();
}
[HttpGet]
[Route("account/client-popup/")]
[Route("client-callback-popup/")]
public IActionResult ClientPopup()
{
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;
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.DependencyInjection;
using Microsoft.Extensions.Logging;
using Squidex.Configurations;
using Squidex.Configurations.Domain;
using Squidex.Configurations.EventStore;
using Squidex.Configurations.Identity;
using Squidex.Configurations.Web;
using Squidex.Store.MongoDb;
using System.Linq;
using Microsoft.AspNetCore.Http;
// ReSharper disable ConvertClosureToMethodGroup
// ReSharper disable AccessToModifiedClosure
namespace Squidex
{
public class Startup
{
private static readonly string[] IdentityServerPaths =
{
"/client-callback-popup",
"/client-callback-silent",
"/account"
};
public IConfigurationRoot Configuration { get; }
public IHostingEnvironment Environment { get; }
@ -65,10 +75,11 @@ namespace Squidex
Configuration.GetSection("identity"));
var builder = new ContainerBuilder();
builder.RegisterModule<InfrastructureModule>();
builder.RegisterModule<EventStoreModule>();
builder.RegisterModule<InfrastructureModule>();
builder.RegisterModule<MongoDbModule>();
builder.RegisterModule<ReadModule>();
builder.RegisterModule<WebModule>();
builder.RegisterModule<WriteModule>();
builder.Populate(services);
@ -91,16 +102,70 @@ namespace Squidex
app.UseDefaultFiles(new DefaultFilesOptions { DefaultFileNames = new List<string> { "build/index.html" } });
}
app.UseMyDefaultUser();
app.UseMyEventStore();
app.UseMyIdentity();
app.UseMyIdentityServer();
app.UseMyApiProtection();
app.UseMyGoogleAuthentication();
app.UseMyApps();
UseIdentity(app);
UseApi(app);
UseFrontend(app);
}
private void UseIdentity(IApplicationBuilder app)
{
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.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;
this.userManager = new UserManager({
client_id: 'management-portal',
silent_redirect_uri: apiUrl.buildUrl('account/client-silent'),
popup_redirect_uri: apiUrl.buildUrl('account/client-popup'),
authority: apiUrl.buildUrl('/'),
client_id: 'squidex-frontend',
silent_redirect_uri: apiUrl.buildUrl('identity-server/client-callback-silent/'),
popup_redirect_uri: apiUrl.buildUrl('identity-server/client-callback-popup/'),
authority: apiUrl.buildUrl('identity-server/'),
response_type: 'id_token token',
scope: 'openid profile squidex-api'
});
this.userManager.getUser()

Loading…
Cancel
Save