mirror of https://github.com/Squidex/squidex.git
Browse Source
* Started with migration. * Progress * Refactorings. * Dont query scopes twice. * Fix tests * Test NPM bullshit. * Remove ls * Try older node version. * Update version. * Fixes for admin client. * Test * Logging * Fake https. * Run on http as well. * Clean up again.pull/708/head
committed by
GitHub
43 changed files with 1552 additions and 710 deletions
@ -1,113 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using IdentityServer4.Models; |
|||
using IdentityServer4.Stores; |
|||
using MongoDB.Bson; |
|||
using MongoDB.Bson.Serialization; |
|||
using MongoDB.Driver; |
|||
using Squidex.Infrastructure.MongoDb; |
|||
|
|||
namespace Squidex.Domain.Users.MongoDb.Infrastructure |
|||
{ |
|||
public class MongoPersistedGrantStore : MongoRepositoryBase<PersistedGrant>, IPersistedGrantStore |
|||
{ |
|||
static MongoPersistedGrantStore() |
|||
{ |
|||
BsonClassMap.RegisterClassMap<PersistedGrant>(cm => |
|||
{ |
|||
cm.AutoMap(); |
|||
|
|||
cm.MapIdProperty(x => x.Key); |
|||
}); |
|||
} |
|||
|
|||
public MongoPersistedGrantStore(IMongoDatabase database) |
|||
: base(database) |
|||
{ |
|||
} |
|||
|
|||
protected override string CollectionName() |
|||
{ |
|||
return "Identity_PersistedGrants"; |
|||
} |
|||
|
|||
protected override Task SetupCollectionAsync(IMongoCollection<PersistedGrant> collection, CancellationToken ct = default) |
|||
{ |
|||
return collection.Indexes.CreateManyAsync(new[] |
|||
{ |
|||
new CreateIndexModel<PersistedGrant>(Index.Ascending(x => x.ClientId)), |
|||
new CreateIndexModel<PersistedGrant>(Index.Ascending(x => x.SubjectId)) |
|||
}, ct); |
|||
} |
|||
|
|||
public async Task<IEnumerable<PersistedGrant>> GetAllAsync(string subjectId) |
|||
{ |
|||
return await Collection.Find(x => x.SubjectId == subjectId).ToListAsync(); |
|||
} |
|||
|
|||
public async Task<IEnumerable<PersistedGrant>> GetAllAsync(PersistedGrantFilter filter) |
|||
{ |
|||
return await Collection.Find(CreateFilter(filter)).ToListAsync(); |
|||
} |
|||
|
|||
public Task<PersistedGrant> GetAsync(string key) |
|||
{ |
|||
return Collection.Find(x => x.Key == key).FirstOrDefaultAsync(); |
|||
} |
|||
|
|||
public Task RemoveAllAsync(PersistedGrantFilter filter) |
|||
{ |
|||
return Collection.DeleteManyAsync(CreateFilter(filter)); |
|||
} |
|||
|
|||
public Task RemoveAsync(string key) |
|||
{ |
|||
return Collection.DeleteManyAsync(x => x.Key == key); |
|||
} |
|||
|
|||
public Task StoreAsync(PersistedGrant grant) |
|||
{ |
|||
return Collection.ReplaceOneAsync(x => x.Key == grant.Key, grant, UpsertReplace); |
|||
} |
|||
|
|||
private static FilterDefinition<PersistedGrant> CreateFilter(PersistedGrantFilter filter) |
|||
{ |
|||
var filters = new List<FilterDefinition<PersistedGrant>>(); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(filter.ClientId)) |
|||
{ |
|||
filters.Add(Filter.Eq(x => x.ClientId, filter.ClientId)); |
|||
} |
|||
|
|||
if (!string.IsNullOrWhiteSpace(filter.SessionId)) |
|||
{ |
|||
filters.Add(Filter.Eq(x => x.SessionId, filter.SessionId)); |
|||
} |
|||
|
|||
if (!string.IsNullOrWhiteSpace(filter.SubjectId)) |
|||
{ |
|||
filters.Add(Filter.Eq(x => x.SubjectId, filter.SubjectId)); |
|||
} |
|||
|
|||
if (!string.IsNullOrWhiteSpace(filter.Type)) |
|||
{ |
|||
filters.Add(Filter.Eq(x => x.Type, filter.Type)); |
|||
} |
|||
|
|||
if (filters.Count > 0) |
|||
{ |
|||
return Filter.And(filters); |
|||
} |
|||
|
|||
return new BsonDocument(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,58 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Immutable; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Text.Json; |
|||
using OpenIddict.Abstractions; |
|||
|
|||
namespace Squidex.Domain.Users.InMemory |
|||
{ |
|||
public sealed class ImmutableApplication |
|||
{ |
|||
public string Id { get; } |
|||
|
|||
public string? ClientId { get; } |
|||
|
|||
public string? ClientSecret { get; } |
|||
|
|||
public string? ConsentType { get; } |
|||
|
|||
public string? DisplayName { get; } |
|||
|
|||
public string? Type { get; } |
|||
|
|||
public ImmutableDictionary<CultureInfo, string> DisplayNames { get; } |
|||
|
|||
public ImmutableArray<string> Permissions { get; } |
|||
|
|||
public ImmutableArray<string> PostLogoutRedirectUris { get; } |
|||
|
|||
public ImmutableArray<string> RedirectUris { get; } |
|||
|
|||
public ImmutableArray<string> Requirements { get; } |
|||
|
|||
public ImmutableDictionary<string, JsonElement> Properties { get; } |
|||
|
|||
public ImmutableApplication(string id, OpenIddictApplicationDescriptor descriptor) |
|||
{ |
|||
Id = id; |
|||
ClientId = descriptor.ClientId; |
|||
ClientSecret = descriptor.ClientSecret; |
|||
ConsentType = descriptor.ConsentType; |
|||
DisplayName = descriptor.DisplayName; |
|||
DisplayNames = descriptor.DisplayNames.ToImmutableDictionary(); |
|||
Permissions = descriptor.Permissions.ToImmutableArray(); |
|||
PostLogoutRedirectUris = descriptor.PostLogoutRedirectUris.Select(x => x.ToString()).ToImmutableArray(); |
|||
Properties = descriptor.Properties.ToImmutableDictionary(); |
|||
RedirectUris = descriptor.RedirectUris.Select(x => x.ToString()).ToImmutableArray(); |
|||
Requirements = descriptor.Requirements.ToImmutableArray(); |
|||
Type = descriptor.Type; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,45 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Immutable; |
|||
using System.Globalization; |
|||
using System.Text.Json; |
|||
using OpenIddict.Abstractions; |
|||
|
|||
namespace Squidex.Domain.Users.InMemory |
|||
{ |
|||
public sealed class ImmutableScope |
|||
{ |
|||
public string Id { get; } |
|||
|
|||
public string? Name { get; } |
|||
|
|||
public string? Description { get; } |
|||
|
|||
public string? DisplayName { get; } |
|||
|
|||
public ImmutableDictionary<CultureInfo, string> Descriptions { get; } |
|||
|
|||
public ImmutableDictionary<CultureInfo, string> DisplayNames { get; } |
|||
|
|||
public ImmutableDictionary<string, JsonElement> Properties { get; } |
|||
|
|||
public ImmutableArray<string> Resources { get; } |
|||
|
|||
public ImmutableScope(string id, OpenIddictScopeDescriptor descriptor) |
|||
{ |
|||
Id = id; |
|||
Description = descriptor.Description; |
|||
Descriptions = descriptor.Descriptions.ToImmutableDictionary(); |
|||
Name = descriptor.Name; |
|||
DisplayName = descriptor.DisplayName; |
|||
DisplayNames = descriptor.DisplayNames.ToImmutableDictionary(); |
|||
Properties = descriptor.Properties.ToImmutableDictionary(); |
|||
Resources = descriptor.Resources.ToImmutableArray(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,241 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Text.Json; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using OpenIddict.Abstractions; |
|||
|
|||
namespace Squidex.Domain.Users.InMemory |
|||
{ |
|||
public class InMemoryApplicationStore : IOpenIddictApplicationStore<ImmutableApplication> |
|||
{ |
|||
private readonly List<ImmutableApplication> applications; |
|||
|
|||
public InMemoryApplicationStore(params (string Id, OpenIddictApplicationDescriptor Descriptor)[] applications) |
|||
{ |
|||
this.applications = applications.Select(x => new ImmutableApplication(x.Id, x.Descriptor)).ToList(); |
|||
} |
|||
|
|||
public InMemoryApplicationStore(IEnumerable<(string Id, OpenIddictApplicationDescriptor Descriptor)> applications) |
|||
{ |
|||
this.applications = applications.Select(x => new ImmutableApplication(x.Id, x.Descriptor)).ToList(); |
|||
} |
|||
|
|||
public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken) |
|||
{ |
|||
return new ValueTask<long>(applications.Count); |
|||
} |
|||
|
|||
public virtual ValueTask<long> CountAsync<TResult>(Func<IQueryable<ImmutableApplication>, IQueryable<TResult>> query, CancellationToken cancellationToken) |
|||
{ |
|||
return query(applications.AsQueryable()).LongCount().AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<TResult> GetAsync<TState, TResult>(Func<IQueryable<ImmutableApplication>, TState, IQueryable<TResult>> query, TState state, CancellationToken cancellationToken) |
|||
{ |
|||
var result = query(applications.AsQueryable(), state).First(); |
|||
|
|||
return result.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableApplication?> FindByIdAsync(string identifier, CancellationToken cancellationToken) |
|||
{ |
|||
var result = applications.Find(x => x.Id == identifier); |
|||
|
|||
return result.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableApplication?> FindByClientIdAsync(string identifier, CancellationToken cancellationToken) |
|||
{ |
|||
var result = applications.Find(x => x.ClientId == identifier); |
|||
|
|||
return result.AsValueTask(); |
|||
} |
|||
|
|||
public virtual async IAsyncEnumerable<ImmutableApplication> FindByPostLogoutRedirectUriAsync(string address, [EnumeratorCancellation] CancellationToken cancellationToken) |
|||
{ |
|||
var result = applications.Where(x => x.PostLogoutRedirectUris.Contains(address)); |
|||
|
|||
foreach (var item in result) |
|||
{ |
|||
yield return await Task.FromResult(item); |
|||
} |
|||
} |
|||
|
|||
public virtual async IAsyncEnumerable<ImmutableApplication> FindByRedirectUriAsync(string address, [EnumeratorCancellation] CancellationToken cancellationToken) |
|||
{ |
|||
var result = applications.Where(x => x.RedirectUris.Contains(address)); |
|||
|
|||
foreach (var item in result) |
|||
{ |
|||
yield return await Task.FromResult(item); |
|||
} |
|||
} |
|||
|
|||
public virtual async IAsyncEnumerable<ImmutableApplication> ListAsync(int? count, int? offset, [EnumeratorCancellation] CancellationToken cancellationToken) |
|||
{ |
|||
var result = applications; |
|||
|
|||
foreach (var item in result) |
|||
{ |
|||
yield return await Task.FromResult(item); |
|||
} |
|||
} |
|||
|
|||
public virtual async IAsyncEnumerable<TResult> ListAsync<TState, TResult>(Func<IQueryable<ImmutableApplication>, TState, IQueryable<TResult>> query, TState state, [EnumeratorCancellation] CancellationToken cancellationToken) |
|||
{ |
|||
var result = query(applications.AsQueryable(), state); |
|||
|
|||
foreach (var item in result) |
|||
{ |
|||
yield return await Task.FromResult(item); |
|||
} |
|||
} |
|||
|
|||
public virtual ValueTask<string?> GetIdAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return new ValueTask<string?>(application.Id); |
|||
} |
|||
|
|||
public virtual ValueTask<string?> GetClientIdAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return application.ClientId.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<string?> GetClientSecretAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return application.ClientSecret.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<string?> GetClientTypeAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return application.Type.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<string?> GetConsentTypeAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return application.ConsentType.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<string?> GetDisplayNameAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return application.DisplayName.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> GetDisplayNamesAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return application.DisplayNames.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableArray<string>> GetPermissionsAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return application.Permissions.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableArray<string>> GetPostLogoutRedirectUrisAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return application.PostLogoutRedirectUris.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableArray<string>> GetRedirectUrisAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return application.RedirectUris.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableArray<string>> GetRequirementsAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return application.Requirements.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
return application.Properties.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask CreateAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask UpdateAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask DeleteAsync(ImmutableApplication application, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableApplication> InstantiateAsync(CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetClientIdAsync(ImmutableApplication application, string? identifier, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetClientSecretAsync(ImmutableApplication application, string? secret, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetClientTypeAsync(ImmutableApplication application, string? type, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetConsentTypeAsync(ImmutableApplication application, string? type, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetDisplayNameAsync(ImmutableApplication application, string? name, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetDisplayNamesAsync(ImmutableApplication application, ImmutableDictionary<CultureInfo, string> names, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetPermissionsAsync(ImmutableApplication application, ImmutableArray<string> permissions, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetPostLogoutRedirectUrisAsync(ImmutableApplication application, ImmutableArray<string> addresses, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetRedirectUrisAsync(ImmutableApplication application, ImmutableArray<string> addresses, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetPropertiesAsync(ImmutableApplication application, ImmutableDictionary<string, JsonElement> properties, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetRequirementsAsync(ImmutableApplication application, ImmutableArray<string> requirements, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,201 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Collections.Immutable; |
|||
using System.Globalization; |
|||
using System.Linq; |
|||
using System.Runtime.CompilerServices; |
|||
using System.Text.Json; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using OpenIddict.Abstractions; |
|||
|
|||
namespace Squidex.Domain.Users.InMemory |
|||
{ |
|||
public class InMemoryScopeStore : IOpenIddictScopeStore<ImmutableScope> |
|||
{ |
|||
private readonly List<ImmutableScope> scopes; |
|||
|
|||
public InMemoryScopeStore(params (string Id, OpenIddictScopeDescriptor Descriptor)[] scopes) |
|||
{ |
|||
this.scopes = scopes.Select(x => new ImmutableScope(x.Id, x.Descriptor)).ToList(); |
|||
} |
|||
|
|||
public InMemoryScopeStore(IEnumerable<(string Id, OpenIddictScopeDescriptor Descriptor)> scopes) |
|||
{ |
|||
this.scopes = scopes.Select(x => new ImmutableScope(x.Id, x.Descriptor)).ToList(); |
|||
} |
|||
|
|||
public virtual ValueTask<long> CountAsync(CancellationToken cancellationToken) |
|||
{ |
|||
return new ValueTask<long>(scopes.Count); |
|||
} |
|||
|
|||
public virtual ValueTask<long> CountAsync<TResult>(Func<IQueryable<ImmutableScope>, IQueryable<TResult>> query, CancellationToken cancellationToken) |
|||
{ |
|||
return query(scopes.AsQueryable()).LongCount().AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<TResult> GetAsync<TState, TResult>(Func<IQueryable<ImmutableScope>, TState, IQueryable<TResult>> query, TState state, CancellationToken cancellationToken) |
|||
{ |
|||
var result = query(scopes.AsQueryable(), state).First(); |
|||
|
|||
return result.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableScope?> FindByIdAsync(string identifier, CancellationToken cancellationToken) |
|||
{ |
|||
var result = scopes.Find(x => x.Id == identifier); |
|||
|
|||
return result.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableScope?> FindByNameAsync(string name, CancellationToken cancellationToken) |
|||
{ |
|||
var result = scopes.Find(x => x.Name == name); |
|||
|
|||
return result.AsValueTask(); |
|||
} |
|||
|
|||
public virtual async IAsyncEnumerable<ImmutableScope> FindByNamesAsync(ImmutableArray<string> names, [EnumeratorCancellation] CancellationToken cancellationToken) |
|||
{ |
|||
var result = scopes.Where(x => x.Name != null && names.Contains(x.Name)); |
|||
|
|||
foreach (var item in result) |
|||
{ |
|||
yield return await Task.FromResult(item); |
|||
} |
|||
} |
|||
|
|||
public virtual async IAsyncEnumerable<ImmutableScope> FindByResourceAsync(string resource, [EnumeratorCancellation] CancellationToken cancellationToken) |
|||
{ |
|||
var result = scopes.Where(x => x.Resources.Contains(resource)); |
|||
|
|||
foreach (var item in result) |
|||
{ |
|||
yield return await Task.FromResult(item); |
|||
} |
|||
} |
|||
|
|||
public virtual async IAsyncEnumerable<ImmutableScope> ListAsync(int? count, int? offset, [EnumeratorCancellation] CancellationToken cancellationToken) |
|||
{ |
|||
var result = scopes; |
|||
|
|||
foreach (var item in result) |
|||
{ |
|||
yield return await Task.FromResult(item); |
|||
} |
|||
} |
|||
|
|||
public virtual async IAsyncEnumerable<TResult> ListAsync<TState, TResult>(Func<IQueryable<ImmutableScope>, TState, IQueryable<TResult>> query, TState state, [EnumeratorCancellation] CancellationToken cancellationToken) |
|||
{ |
|||
var result = query(scopes.AsQueryable(), state); |
|||
|
|||
foreach (var item in result) |
|||
{ |
|||
yield return await Task.FromResult(item); |
|||
} |
|||
} |
|||
|
|||
public virtual ValueTask<string?> GetIdAsync(ImmutableScope scope, CancellationToken cancellationToken) |
|||
{ |
|||
return new ValueTask<string?>(scope.Id); |
|||
} |
|||
|
|||
public virtual ValueTask<string?> GetNameAsync(ImmutableScope scope, CancellationToken cancellationToken) |
|||
{ |
|||
return scope.Name.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<string?> GetDescriptionAsync(ImmutableScope scope, CancellationToken cancellationToken) |
|||
{ |
|||
return scope.Description.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<string?> GetDisplayNameAsync(ImmutableScope scope, CancellationToken cancellationToken) |
|||
{ |
|||
return scope.DisplayName.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> GetDescriptionsAsync(ImmutableScope scope, CancellationToken cancellationToken) |
|||
{ |
|||
return scope.Descriptions.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableDictionary<CultureInfo, string>> GetDisplayNamesAsync(ImmutableScope scope, CancellationToken cancellationToken) |
|||
{ |
|||
return scope.DisplayNames.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableDictionary<string, JsonElement>> GetPropertiesAsync(ImmutableScope scope, CancellationToken cancellationToken) |
|||
{ |
|||
return scope.Properties.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableArray<string>> GetResourcesAsync(ImmutableScope scope, CancellationToken cancellationToken) |
|||
{ |
|||
return scope.Resources.AsValueTask(); |
|||
} |
|||
|
|||
public virtual ValueTask CreateAsync(ImmutableScope scope, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask UpdateAsync(ImmutableScope scope, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask DeleteAsync(ImmutableScope scope, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask<ImmutableScope> InstantiateAsync(CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetDescriptionAsync(ImmutableScope scope, string? description, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetDescriptionsAsync(ImmutableScope scope, ImmutableDictionary<CultureInfo, string> descriptions, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetDisplayNameAsync(ImmutableScope scope, string? name, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetDisplayNamesAsync(ImmutableScope scope, ImmutableDictionary<CultureInfo, string> names, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetNameAsync(ImmutableScope scope, string? name, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetPropertiesAsync(ImmutableScope scope, ImmutableDictionary<string, JsonElement> properties, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
|
|||
public virtual ValueTask SetResourcesAsync(ImmutableScope scope, ImmutableArray<string> resources, CancellationToken cancellationToken) |
|||
{ |
|||
throw new NotSupportedException(); |
|||
} |
|||
} |
|||
} |
|||
@ -1,31 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading.Tasks; |
|||
using IdentityServer4.Extensions; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Squidex.Web; |
|||
|
|||
namespace Squidex.Areas.Api.Config |
|||
{ |
|||
public sealed class IdentityServerPathMiddleware |
|||
{ |
|||
private readonly RequestDelegate next; |
|||
|
|||
public IdentityServerPathMiddleware(RequestDelegate next) |
|||
{ |
|||
this.next = next; |
|||
} |
|||
|
|||
public Task InvokeAsync(HttpContext context) |
|||
{ |
|||
context.SetIdentityServerBasePath(Constants.IdentityServerPrefix); |
|||
|
|||
return next(context); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,59 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Text.Json; |
|||
using OpenIddict.Abstractions; |
|||
using Squidex.Shared; |
|||
using Squidex.Shared.Identity; |
|||
using Squidex.Shared.Users; |
|||
|
|||
namespace Squidex.Areas.IdentityServer.Config |
|||
{ |
|||
public static class ApplicationExtensions |
|||
{ |
|||
public static OpenIddictApplicationDescriptor SetAdmin(this OpenIddictApplicationDescriptor application) |
|||
{ |
|||
application.Properties[SquidexClaimTypes.Permissions] = CreateParameter(Enumerable.Repeat(Permissions.All, 1)); |
|||
|
|||
return application; |
|||
} |
|||
|
|||
public static OpenIddictApplicationDescriptor CopyClaims(this OpenIddictApplicationDescriptor application, IUser claims) |
|||
{ |
|||
foreach (var group in claims.Claims.GroupBy(x => x.Type)) |
|||
{ |
|||
application.Properties[group.Key] = CreateParameter(group.Select(x => x.Value)); |
|||
} |
|||
|
|||
return application; |
|||
} |
|||
|
|||
private static JsonElement CreateParameter(IEnumerable<string> values) |
|||
{ |
|||
return (JsonElement)new OpenIddictParameter(values.ToArray()); |
|||
} |
|||
|
|||
public static IEnumerable<Claim> Claims(this IReadOnlyDictionary<string, JsonElement> properties) |
|||
{ |
|||
foreach (var (key, value) in properties) |
|||
{ |
|||
var values = (string[]?)new OpenIddictParameter(value); |
|||
|
|||
if (values != null) |
|||
{ |
|||
foreach (var claimValue in values) |
|||
{ |
|||
yield return new Claim(key, claimValue); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,33 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.Logging; |
|||
using Microsoft.Extensions.Options; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Core; |
|||
|
|||
namespace Squidex.Areas.IdentityServer.Config |
|||
{ |
|||
public sealed class ApplicationManager<T> : OpenIddictApplicationManager<T> where T : class |
|||
{ |
|||
public ApplicationManager( |
|||
IOptionsMonitor<OpenIddictCoreOptions> options, |
|||
IOpenIddictApplicationCache<T> cache, |
|||
IOpenIddictApplicationStoreResolver resolver, |
|||
ILogger<OpenIddictApplicationManager<T>> logger) |
|||
: base(cache, logger, options, resolver) |
|||
{ |
|||
} |
|||
|
|||
protected override ValueTask<bool> ValidateClientSecretAsync(string secret, string comparand, CancellationToken cancellationToken = default) |
|||
{ |
|||
return new ValueTask<bool>(string.Equals(secret, comparand)); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,243 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using OpenIddict.Abstractions; |
|||
using Squidex.Config; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Entities; |
|||
using Squidex.Domain.Users; |
|||
using Squidex.Domain.Users.InMemory; |
|||
using Squidex.Hosting; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Shared.Identity; |
|||
using Squidex.Shared.Users; |
|||
using Squidex.Web; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace Squidex.Areas.IdentityServer.Config |
|||
{ |
|||
public class DynamicApplicationStore : InMemoryApplicationStore |
|||
{ |
|||
private readonly IServiceProvider serviceProvider; |
|||
|
|||
public DynamicApplicationStore(IServiceProvider serviceProvider) |
|||
: base(CreateStaticClients(serviceProvider)) |
|||
{ |
|||
Guard.NotNull(serviceProvider, nameof(serviceProvider)); |
|||
|
|||
this.serviceProvider = serviceProvider; |
|||
} |
|||
|
|||
public override async ValueTask<ImmutableApplication?> FindByIdAsync(string identifier, CancellationToken cancellationToken) |
|||
{ |
|||
var application = await base.FindByIdAsync(identifier, cancellationToken); |
|||
|
|||
if (application == null) |
|||
{ |
|||
application = await GetDynamicAsync(identifier); |
|||
} |
|||
|
|||
return application; |
|||
} |
|||
|
|||
public override async ValueTask<ImmutableApplication?> FindByClientIdAsync(string identifier, CancellationToken cancellationToken) |
|||
{ |
|||
var application = await base.FindByClientIdAsync(identifier, cancellationToken); |
|||
|
|||
if (application == null) |
|||
{ |
|||
application = await GetDynamicAsync(identifier); |
|||
} |
|||
|
|||
return application; |
|||
} |
|||
|
|||
private async Task<ImmutableApplication?> GetDynamicAsync(string clientId) |
|||
{ |
|||
var (appName, appClientId) = clientId.GetClientParts(); |
|||
|
|||
var appProvider = serviceProvider.GetRequiredService<IAppProvider>(); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(appName) && !string.IsNullOrWhiteSpace(appClientId)) |
|||
{ |
|||
var app = await appProvider.GetAppAsync(appName, true); |
|||
|
|||
var appClient = app?.Clients.GetOrDefault(appClientId); |
|||
|
|||
if (appClient != null) |
|||
{ |
|||
return CreateClientFromApp(clientId, appClient); |
|||
} |
|||
} |
|||
|
|||
using (var scope = serviceProvider.CreateScope()) |
|||
{ |
|||
var userService = scope.ServiceProvider.GetRequiredService<IUserService>(); |
|||
|
|||
var user = await userService.FindByIdAsync(clientId); |
|||
|
|||
if (user == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var secret = user.Claims.ClientSecret(); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(secret)) |
|||
{ |
|||
return CreateClientFromUser(user, secret); |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private static ImmutableApplication CreateClientFromUser(IUser user, string secret) |
|||
{ |
|||
return new ImmutableApplication(user.Id, new OpenIddictApplicationDescriptor |
|||
{ |
|||
DisplayName = $"{user.Email} Client", |
|||
ClientId = user.Id, |
|||
ClientSecret = secret, |
|||
Permissions = |
|||
{ |
|||
Permissions.Endpoints.Token, |
|||
Permissions.GrantTypes.ClientCredentials, |
|||
Permissions.ResponseTypes.Token, |
|||
Permissions.Scopes.Email, |
|||
Permissions.Scopes.Profile, |
|||
Permissions.Scopes.Roles, |
|||
Permissions.Prefixes.Scope + Constants.ScopeApi, |
|||
Permissions.Prefixes.Scope + Constants.ScopePermissions |
|||
} |
|||
}.CopyClaims(user)); |
|||
} |
|||
|
|||
private static ImmutableApplication CreateClientFromApp(string id, AppClient appClient) |
|||
{ |
|||
return new ImmutableApplication(id, new OpenIddictApplicationDescriptor |
|||
{ |
|||
DisplayName = id, |
|||
ClientId = id, |
|||
ClientSecret = appClient.Secret, |
|||
Permissions = |
|||
{ |
|||
Permissions.Endpoints.Token, |
|||
Permissions.GrantTypes.ClientCredentials, |
|||
Permissions.ResponseTypes.Token, |
|||
Permissions.Scopes.Email, |
|||
Permissions.Scopes.Profile, |
|||
Permissions.Scopes.Roles, |
|||
Permissions.Prefixes.Scope + Constants.ScopeApi, |
|||
Permissions.Prefixes.Scope + Constants.ScopePermissions |
|||
} |
|||
}); |
|||
} |
|||
|
|||
private static IEnumerable<(string, OpenIddictApplicationDescriptor)> CreateStaticClients(IServiceProvider serviceProvider) |
|||
{ |
|||
var identityOptions = serviceProvider.GetRequiredService<IOptions<MyIdentityOptions>>().Value; |
|||
|
|||
var urlGenerator = serviceProvider.GetRequiredService<IUrlGenerator>(); |
|||
|
|||
var frontendId = Constants.ClientFrontendId; |
|||
|
|||
yield return (frontendId, new OpenIddictApplicationDescriptor |
|||
{ |
|||
DisplayName = "Frontend Client", |
|||
ClientId = frontendId, |
|||
ClientSecret = null, |
|||
RedirectUris = |
|||
{ |
|||
new Uri(urlGenerator.BuildUrl("login;")), |
|||
new Uri(urlGenerator.BuildUrl("client-callback-silent", false)), |
|||
new Uri(urlGenerator.BuildUrl("client-callback-popup", false)) |
|||
}, |
|||
PostLogoutRedirectUris = |
|||
{ |
|||
new Uri(urlGenerator.BuildUrl("logout", false)) |
|||
}, |
|||
Permissions = |
|||
{ |
|||
Permissions.Endpoints.Authorization, |
|||
Permissions.Endpoints.Logout, |
|||
Permissions.Endpoints.Token, |
|||
Permissions.GrantTypes.AuthorizationCode, |
|||
Permissions.GrantTypes.RefreshToken, |
|||
Permissions.ResponseTypes.Code, |
|||
Permissions.Scopes.Email, |
|||
Permissions.Scopes.Profile, |
|||
Permissions.Scopes.Roles, |
|||
Permissions.Prefixes.Scope + Constants.ScopeApi, |
|||
Permissions.Prefixes.Scope + Constants.ScopePermissions |
|||
}, |
|||
Type = ClientTypes.Public |
|||
}); |
|||
|
|||
var internalClientId = Constants.ClientInternalId; |
|||
|
|||
yield return (internalClientId, new OpenIddictApplicationDescriptor |
|||
{ |
|||
DisplayName = "Internal Client", |
|||
ClientId = internalClientId, |
|||
ClientSecret = Constants.ClientInternalSecret, |
|||
RedirectUris = |
|||
{ |
|||
new Uri(urlGenerator.BuildUrl($"{Constants.PrefixPortal}/signin-internal", false)), |
|||
new Uri(urlGenerator.BuildUrl($"{Constants.PrefixOrleans}/signin-internal", false)) |
|||
}, |
|||
Permissions = |
|||
{ |
|||
Permissions.Endpoints.Authorization, |
|||
Permissions.Endpoints.Logout, |
|||
Permissions.Endpoints.Token, |
|||
Permissions.GrantTypes.Implicit, |
|||
Permissions.ResponseTypes.IdToken, |
|||
Permissions.ResponseTypes.IdTokenToken, |
|||
Permissions.ResponseTypes.Token, |
|||
Permissions.Scopes.Email, |
|||
Permissions.Scopes.Profile, |
|||
Permissions.Scopes.Roles, |
|||
Permissions.Prefixes.Scope + Constants.ScopeApi, |
|||
Permissions.Prefixes.Scope + Constants.ScopePermissions |
|||
}, |
|||
Type = ClientTypes.Public |
|||
}); |
|||
|
|||
if (!identityOptions.IsAdminClientConfigured()) |
|||
{ |
|||
yield break; |
|||
} |
|||
|
|||
var adminClientId = identityOptions.AdminClientId; |
|||
|
|||
yield return (adminClientId, new OpenIddictApplicationDescriptor |
|||
{ |
|||
DisplayName = "Admin Client", |
|||
ClientId = adminClientId, |
|||
ClientSecret = identityOptions.AdminClientSecret, |
|||
Permissions = |
|||
{ |
|||
Permissions.Endpoints.Token, |
|||
Permissions.GrantTypes.ClientCredentials, |
|||
Permissions.ResponseTypes.Token, |
|||
Permissions.Scopes.Email, |
|||
Permissions.Scopes.Profile, |
|||
Permissions.Scopes.Roles, |
|||
Permissions.Prefixes.Scope + Constants.ScopeApi, |
|||
Permissions.Prefixes.Scope + Constants.ScopePermissions |
|||
} |
|||
}.SetAdmin()); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,38 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System.Collections.Generic; |
|||
using OpenIddict.Abstractions; |
|||
using Squidex.Domain.Users.InMemory; |
|||
using Squidex.Web; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace Squidex.Areas.IdentityServer.Config |
|||
{ |
|||
public static class IdentityServerConfiguration |
|||
{ |
|||
public sealed class Scopes : InMemoryScopeStore |
|||
{ |
|||
public Scopes() |
|||
: base(BuildScopes()) |
|||
{ |
|||
} |
|||
|
|||
private static IEnumerable<(string, OpenIddictScopeDescriptor)> BuildScopes() |
|||
{ |
|||
yield return (Constants.ScopeApi, new OpenIddictScopeDescriptor |
|||
{ |
|||
Name = Constants.ScopeApi, |
|||
Resources = |
|||
{ |
|||
Permissions.Prefixes.Scope + Constants.ScopeApi |
|||
} |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -1,253 +0,0 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using IdentityServer4; |
|||
using IdentityServer4.Models; |
|||
using IdentityServer4.Stores; |
|||
using Microsoft.Extensions.DependencyInjection; |
|||
using Microsoft.Extensions.Options; |
|||
using Squidex.Config; |
|||
using Squidex.Domain.Apps.Core.Apps; |
|||
using Squidex.Domain.Apps.Entities; |
|||
using Squidex.Domain.Users; |
|||
using Squidex.Hosting; |
|||
using Squidex.Infrastructure; |
|||
using Squidex.Infrastructure.Security; |
|||
using Squidex.Shared; |
|||
using Squidex.Shared.Identity; |
|||
using Squidex.Shared.Users; |
|||
using Squidex.Web; |
|||
|
|||
namespace Squidex.Areas.IdentityServer.Config |
|||
{ |
|||
public class LazyClientStore : IClientStore |
|||
{ |
|||
private readonly IServiceProvider serviceProvider; |
|||
private readonly Dictionary<string, Client> staticClients = new Dictionary<string, Client>(StringComparer.OrdinalIgnoreCase); |
|||
|
|||
public LazyClientStore(IServiceProvider serviceProvider) |
|||
{ |
|||
Guard.NotNull(serviceProvider, nameof(serviceProvider)); |
|||
|
|||
this.serviceProvider = serviceProvider; |
|||
|
|||
CreateStaticClients(); |
|||
} |
|||
|
|||
public async Task<Client?> FindClientByIdAsync(string clientId) |
|||
{ |
|||
var client = staticClients.GetOrDefault(clientId); |
|||
|
|||
if (client != null) |
|||
{ |
|||
return client; |
|||
} |
|||
|
|||
var (appName, appClientId) = clientId.GetClientParts(); |
|||
|
|||
var appProvider = serviceProvider.GetRequiredService<IAppProvider>(); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(appName) && !string.IsNullOrWhiteSpace(appClientId)) |
|||
{ |
|||
var app = await appProvider.GetAppAsync(appName, true); |
|||
|
|||
var appClient = app?.Clients.GetOrDefault(appClientId); |
|||
|
|||
if (appClient != null) |
|||
{ |
|||
return CreateClientFromApp(clientId, appClient); |
|||
} |
|||
} |
|||
|
|||
using (var scope = serviceProvider.CreateScope()) |
|||
{ |
|||
var userService = scope.ServiceProvider.GetRequiredService<IUserService>(); |
|||
|
|||
var user = await userService.FindByIdAsync(clientId); |
|||
|
|||
if (user == null) |
|||
{ |
|||
return null; |
|||
} |
|||
|
|||
var secret = user.Claims.ClientSecret(); |
|||
|
|||
if (!string.IsNullOrWhiteSpace(secret)) |
|||
{ |
|||
return CreateClientFromUser(user, secret); |
|||
} |
|||
} |
|||
|
|||
return null; |
|||
} |
|||
|
|||
private static Client CreateClientFromUser(IUser user, string secret) |
|||
{ |
|||
return new Client |
|||
{ |
|||
ClientId = user.Id, |
|||
ClientName = $"{user.Email} Client", |
|||
ClientClaimsPrefix = null, |
|||
ClientSecrets = new List<Secret> |
|||
{ |
|||
new Secret(secret.Sha256()) |
|||
}, |
|||
AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds, |
|||
AllowedGrantTypes = GrantTypes.ClientCredentials, |
|||
AllowedScopes = new List<string> |
|||
{ |
|||
Constants.ApiScope, |
|||
Constants.RoleScope, |
|||
Constants.PermissionsScope |
|||
}, |
|||
Claims = GetClaims(user) |
|||
}; |
|||
} |
|||
|
|||
private static Client CreateClientFromApp(string id, AppClient appClient) |
|||
{ |
|||
return new Client |
|||
{ |
|||
ClientId = id, |
|||
ClientName = id, |
|||
ClientSecrets = new List<Secret> |
|||
{ |
|||
new Secret(appClient.Secret.Sha256()) |
|||
}, |
|||
AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds, |
|||
AllowedGrantTypes = GrantTypes.ClientCredentials, |
|||
AllowedScopes = new List<string> |
|||
{ |
|||
Constants.ApiScope, |
|||
Constants.RoleScope, |
|||
Constants.PermissionsScope |
|||
} |
|||
}; |
|||
} |
|||
|
|||
private void CreateStaticClients() |
|||
{ |
|||
var identityOptions = serviceProvider.GetRequiredService<IOptions<MyIdentityOptions>>().Value; |
|||
|
|||
var urlGenerator = serviceProvider.GetRequiredService<IUrlGenerator>(); |
|||
|
|||
foreach (var client in CreateStaticClients(urlGenerator, identityOptions)) |
|||
{ |
|||
staticClients[client.ClientId] = client; |
|||
} |
|||
} |
|||
|
|||
private static IEnumerable<Client> CreateStaticClients(IUrlGenerator urlGenerator, MyIdentityOptions identityOptions) |
|||
{ |
|||
var frontendId = Constants.FrontendClient; |
|||
|
|||
yield return new Client |
|||
{ |
|||
ClientId = frontendId, |
|||
ClientName = frontendId, |
|||
RedirectUris = new List<string> |
|||
{ |
|||
urlGenerator.BuildUrl("login;"), |
|||
urlGenerator.BuildUrl("client-callback-silent", false), |
|||
urlGenerator.BuildUrl("client-callback-popup", false) |
|||
}, |
|||
PostLogoutRedirectUris = new List<string> |
|||
{ |
|||
urlGenerator.BuildUrl("logout", false) |
|||
}, |
|||
AllowAccessTokensViaBrowser = true, |
|||
AllowedGrantTypes = GrantTypes.Implicit, |
|||
AllowedScopes = new List<string> |
|||
{ |
|||
IdentityServerConstants.StandardScopes.OpenId, |
|||
IdentityServerConstants.StandardScopes.Profile, |
|||
IdentityServerConstants.StandardScopes.Email, |
|||
Constants.ApiScope, |
|||
Constants.PermissionsScope, |
|||
Constants.ProfileScope, |
|||
Constants.RoleScope |
|||
}, |
|||
RequireConsent = false |
|||
}; |
|||
|
|||
var internalClient = Constants.InternalClientId; |
|||
|
|||
yield return new Client |
|||
{ |
|||
ClientId = internalClient, |
|||
ClientName = internalClient, |
|||
ClientSecrets = new List<Secret> |
|||
{ |
|||
new Secret(Constants.InternalClientSecret) |
|||
}, |
|||
RedirectUris = new List<string> |
|||
{ |
|||
urlGenerator.BuildUrl($"{Constants.PortalPrefix}/signin-internal", false), |
|||
urlGenerator.BuildUrl($"{Constants.OrleansPrefix}/signin-internal", false) |
|||
}, |
|||
AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds, |
|||
AllowedGrantTypes = GrantTypes.ImplicitAndClientCredentials, |
|||
AllowedScopes = new List<string> |
|||
{ |
|||
IdentityServerConstants.StandardScopes.OpenId, |
|||
IdentityServerConstants.StandardScopes.Profile, |
|||
IdentityServerConstants.StandardScopes.Email, |
|||
Constants.ApiScope, |
|||
Constants.PermissionsScope, |
|||
Constants.ProfileScope, |
|||
Constants.RoleScope |
|||
}, |
|||
RequireConsent = false |
|||
}; |
|||
|
|||
if (identityOptions.IsAdminClientConfigured()) |
|||
{ |
|||
var id = identityOptions.AdminClientId; |
|||
|
|||
yield return new Client |
|||
{ |
|||
ClientId = id, |
|||
ClientName = id, |
|||
ClientSecrets = new List<Secret> |
|||
{ |
|||
new Secret(identityOptions.AdminClientSecret.Sha256()) |
|||
}, |
|||
AccessTokenLifetime = (int)TimeSpan.FromDays(30).TotalSeconds, |
|||
AllowedGrantTypes = GrantTypes.ClientCredentials, |
|||
AllowedScopes = new List<string> |
|||
{ |
|||
Constants.ApiScope, |
|||
Constants.RoleScope, |
|||
Constants.PermissionsScope |
|||
}, |
|||
Claims = new List<ClientClaim> |
|||
{ |
|||
new ClientClaim(SquidexClaimTypes.Permissions, Permissions.All) |
|||
} |
|||
}; |
|||
} |
|||
} |
|||
|
|||
private static List<ClientClaim> GetClaims(IUser user) |
|||
{ |
|||
var claims = new List<ClientClaim> |
|||
{ |
|||
new ClientClaim(OpenIdClaims.Subject, user.Id) |
|||
}; |
|||
|
|||
claims.AddRange( |
|||
user.Claims.Where(x => x.Type == SquidexClaimTypes.Permissions) |
|||
.Select(x => new ClientClaim(x.Type, x.Value))); |
|||
|
|||
return claims; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,287 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Security.Claims; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Http; |
|||
using Microsoft.AspNetCore.Identity; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.IdentityModel.Tokens; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Server.AspNetCore; |
|||
using Squidex.Areas.IdentityServer.Config; |
|||
using Squidex.Areas.IdentityServer.Controllers; |
|||
using Squidex.Domain.Users; |
|||
using Squidex.Shared.Identity; |
|||
using Squidex.Web; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace Notifo.Areas.Account.Controllers |
|||
{ |
|||
public class AuthorizationController : IdentityServerController |
|||
{ |
|||
private readonly IOpenIddictScopeManager scopeManager; |
|||
private readonly IOpenIddictApplicationManager applicationManager; |
|||
private readonly IUserService userService; |
|||
|
|||
public AuthorizationController( |
|||
IOpenIddictScopeManager scopeManager, |
|||
IOpenIddictApplicationManager applicationManager, |
|||
IUserService userService) |
|||
{ |
|||
this.scopeManager = scopeManager; |
|||
this.applicationManager = applicationManager; |
|||
this.userService = userService; |
|||
} |
|||
|
|||
[HttpPost("connect/token")] |
|||
[Produces("application/json")] |
|||
public async Task<IActionResult> Exchange() |
|||
{ |
|||
var request = HttpContext.GetOpenIddictServerRequest(); |
|||
|
|||
if (request == null) |
|||
{ |
|||
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); |
|||
} |
|||
|
|||
if (request.IsAuthorizationCodeGrantType() || request.IsRefreshTokenGrantType() || request.IsImplicitFlow()) |
|||
{ |
|||
var principal = (await HttpContext.AuthenticateAsync(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)).Principal; |
|||
|
|||
if (principal == null) |
|||
{ |
|||
throw new InvalidOperationException("The user details cannot be retrieved."); |
|||
} |
|||
|
|||
var user = await userService.GetAsync(principal); |
|||
|
|||
if (user == null) |
|||
{ |
|||
return Forbid( |
|||
new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The token is no longer valid." |
|||
}), |
|||
OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
if (!await SignInManager.CanSignInAsync((IdentityUser)user.Identity)) |
|||
{ |
|||
return Forbid( |
|||
new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidGrant, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is no longer allowed to sign in." |
|||
}), |
|||
OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
foreach (var claim in principal.Claims) |
|||
{ |
|||
claim.SetDestinations(GetDestinations(claim, principal, false)); |
|||
} |
|||
|
|||
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
if (request.IsClientCredentialsGrantType()) |
|||
{ |
|||
if (request.ClientId == null) |
|||
{ |
|||
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); |
|||
} |
|||
|
|||
var application = await applicationManager.FindByClientIdAsync(request.ClientId); |
|||
|
|||
if (application == null) |
|||
{ |
|||
throw new InvalidOperationException("The application details cannot be found in the database."); |
|||
} |
|||
|
|||
var principal = await CreateApplicationPrinicpalAsync(request, application); |
|||
|
|||
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
throw new InvalidOperationException("The specified grant type is not supported."); |
|||
} |
|||
|
|||
[HttpGet("connect/authorize")] |
|||
public async Task<IActionResult> Authorize() |
|||
{ |
|||
var request = HttpContext.GetOpenIddictServerRequest(); |
|||
if (request == null) |
|||
{ |
|||
throw new InvalidOperationException("The OpenID Connect request cannot be retrieved."); |
|||
} |
|||
|
|||
if (User.Identity?.IsAuthenticated != true) |
|||
{ |
|||
if (request.HasPrompt(Prompts.None)) |
|||
{ |
|||
var properties = new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.LoginRequired, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The user is not logged in." |
|||
}); |
|||
|
|||
return Forbid(properties, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
var query = QueryString.Create( |
|||
Request.HasFormContentType ? |
|||
Request.Form.ToList() : |
|||
Request.Query.ToList()); |
|||
|
|||
var redirectUri = Request.PathBase + Request.Path + query; |
|||
|
|||
return Challenge( |
|||
new AuthenticationProperties |
|||
{ |
|||
RedirectUri = redirectUri |
|||
}); |
|||
} |
|||
|
|||
var user = await userService.GetAsync(User); |
|||
|
|||
if (user == null) |
|||
{ |
|||
throw new InvalidOperationException("The user details cannot be retrieved."); |
|||
} |
|||
|
|||
var principal = await SignInManager.CreateUserPrincipalAsync((IdentityUser)user.Identity); |
|||
|
|||
await EnrichPrincipalAsync(request, principal, false); |
|||
|
|||
return SignIn(principal, OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
[HttpGet("connect/logout")] |
|||
public async Task<IActionResult> Logout() |
|||
{ |
|||
await SignInManager.SignOutAsync(); |
|||
|
|||
return SignOut(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
private async Task<ClaimsPrincipal> CreateApplicationPrinicpalAsync(OpenIddictRequest request, object application) |
|||
{ |
|||
var identity = new ClaimsIdentity( |
|||
TokenValidationParameters.DefaultAuthenticationType, |
|||
Claims.Name, |
|||
Claims.Role); |
|||
|
|||
var principal = new ClaimsPrincipal(identity); |
|||
|
|||
var clientId = request.ClientId; |
|||
var clientName = await applicationManager.GetDisplayNameAsync(application); |
|||
|
|||
if (clientId != null) |
|||
{ |
|||
identity.AddClaim(Claims.Subject, clientId, |
|||
Destinations.AccessToken, Destinations.IdentityToken); |
|||
} |
|||
|
|||
if (clientName != null) |
|||
{ |
|||
identity.AddClaim(Claims.Name, clientName, |
|||
Destinations.AccessToken, Destinations.IdentityToken); |
|||
} |
|||
|
|||
var properties = await applicationManager.GetPropertiesAsync(application); |
|||
|
|||
foreach (var claim in properties.Claims()) |
|||
{ |
|||
identity.AddClaim(claim); |
|||
} |
|||
|
|||
await EnrichPrincipalAsync(request, principal, true); |
|||
|
|||
return principal; |
|||
} |
|||
|
|||
private async Task EnrichPrincipalAsync(OpenIddictRequest request, ClaimsPrincipal principal, bool alwaysDeliverPermissions) |
|||
{ |
|||
var scopes = request.GetScopes(); |
|||
|
|||
principal.SetScopes(scopes); |
|||
principal.SetResources(await scopeManager.ListResourcesAsync(scopes).ToListAsync()); |
|||
|
|||
foreach (var claim in principal.Claims) |
|||
{ |
|||
claim.SetDestinations(GetDestinations(claim, principal, alwaysDeliverPermissions)); |
|||
} |
|||
} |
|||
|
|||
private static IEnumerable<string> GetDestinations(Claim claim, ClaimsPrincipal principal, bool alwaysDeliverPermissions) |
|||
{ |
|||
switch (claim.Type) |
|||
{ |
|||
case SquidexClaimTypes.DisplayName when principal.HasScope(Scopes.Profile): |
|||
yield return Destinations.IdentityToken; |
|||
yield break; |
|||
|
|||
case SquidexClaimTypes.PictureUrl when principal.HasScope(Scopes.Profile): |
|||
yield return Destinations.IdentityToken; |
|||
yield break; |
|||
|
|||
case SquidexClaimTypes.NotifoKey when principal.HasScope(Scopes.Profile): |
|||
yield return Destinations.IdentityToken; |
|||
yield break; |
|||
|
|||
case SquidexClaimTypes.Permissions when principal.HasScope(Constants.ScopePermissions) || alwaysDeliverPermissions: |
|||
yield return Destinations.AccessToken; |
|||
yield return Destinations.IdentityToken; |
|||
yield break; |
|||
|
|||
case Claims.Name: |
|||
yield return Destinations.AccessToken; |
|||
|
|||
if (principal.HasScope(Scopes.Profile)) |
|||
{ |
|||
yield return Destinations.IdentityToken; |
|||
} |
|||
|
|||
yield break; |
|||
|
|||
case Claims.Email: |
|||
yield return Destinations.AccessToken; |
|||
|
|||
if (principal.HasScope(Scopes.Email)) |
|||
{ |
|||
yield return Destinations.IdentityToken; |
|||
} |
|||
|
|||
yield break; |
|||
|
|||
case Claims.Role: |
|||
yield return Destinations.AccessToken; |
|||
|
|||
if (principal.HasScope(Scopes.Roles)) |
|||
{ |
|||
yield return Destinations.IdentityToken; |
|||
} |
|||
|
|||
yield break; |
|||
|
|||
case "AspNet.Identity.SecurityStamp": |
|||
yield break; |
|||
|
|||
default: |
|||
yield return Destinations.AccessToken; |
|||
yield break; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,68 @@ |
|||
// ==========================================================================
|
|||
// Squidex Headless CMS
|
|||
// ==========================================================================
|
|||
// Copyright (c) Squidex UG (haftungsbeschraenkt)
|
|||
// All rights reserved. Licensed under the MIT license.
|
|||
// ==========================================================================
|
|||
|
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Authentication; |
|||
using Microsoft.AspNetCore.Authorization; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using OpenIddict.Abstractions; |
|||
using OpenIddict.Server.AspNetCore; |
|||
using Squidex.Domain.Users; |
|||
using static OpenIddict.Abstractions.OpenIddictConstants; |
|||
|
|||
namespace Squidex.Areas.IdentityServer.Controllers.UserInfo |
|||
{ |
|||
public class UserInfoController : IdentityServerController |
|||
{ |
|||
private readonly IUserService userService; |
|||
|
|||
public UserInfoController(IUserService userService) |
|||
{ |
|||
this.userService = userService; |
|||
} |
|||
|
|||
[Authorize(AuthenticationSchemes = OpenIddictServerAspNetCoreDefaults.AuthenticationScheme)] |
|||
[HttpGet("connect/userinfo")] |
|||
[HttpPost("connect/userinfo")] |
|||
[Produces("application/json")] |
|||
public async Task<IActionResult> UserInfo() |
|||
{ |
|||
var user = await userService.GetAsync(User); |
|||
|
|||
if (user == null) |
|||
{ |
|||
return Challenge( |
|||
new AuthenticationProperties(new Dictionary<string, string?> |
|||
{ |
|||
[OpenIddictServerAspNetCoreConstants.Properties.Error] = Errors.InvalidToken, |
|||
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "The specified access token is bound to an account that no longer exists." |
|||
}), |
|||
OpenIddictServerAspNetCoreDefaults.AuthenticationScheme); |
|||
} |
|||
|
|||
var claims = new Dictionary<string, object>(StringComparer.Ordinal) |
|||
{ |
|||
[Claims.Subject] = user.Id |
|||
}; |
|||
|
|||
if (User.HasScope(Scopes.Email)) |
|||
{ |
|||
claims[Claims.Email] = user.Email; |
|||
claims[Claims.EmailVerified] = true; |
|||
} |
|||
|
|||
if (User.HasScope(Scopes.Roles)) |
|||
{ |
|||
claims[Claims.Role] = Array.Empty<string>(); |
|||
} |
|||
|
|||
return Ok(claims); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue