From 41101164fac9d3158d754eedaa12b1ff8d2c348c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ram=C3=B3n=20Tom=C3=A1s?= Date: Mon, 5 Jun 2017 21:54:03 +0200 Subject: [PATCH] Persist identity grant store to db Persist machine Keys to Redis --- k8s/deployments.yaml | 1 + .../Identity.API/Configuration/Config.cs | 1 + .../DataProtectionBuilderExtensions.cs | 96 ++++ .../Extensions/RedisXmlRepository.cs | 214 +++++++ .../Identity/Identity.API/Identity.API.csproj | 1 + ...604151240_Init-persisted-grant.Designer.cs | 52 ++ .../20170604151240_Init-persisted-grant.cs | 40 ++ ...70604151338_Init-configuration.Designer.cs | 539 ++++++++++++++++++ .../20170604151338_Init-configuration.cs | 501 ++++++++++++++++ .../ConfigurationDbContextModelSnapshot.cs | 538 +++++++++++++++++ .../PersistedGrantDbContextModelSnapshot.cs | 51 ++ src/Services/Identity/Identity.API/Startup.cs | 81 ++- .../WebMVC/Controllers/AccountController.cs | 11 +- src/Web/WebMVC/Startup.cs | 4 +- .../FunctionalTests/FunctionalTests.csproj | 4 + .../IntegrationTests/IntegrationTests.csproj | 4 + test/Services/UnitTest/UnitTest.csproj | 4 + 17 files changed, 2121 insertions(+), 21 deletions(-) create mode 100644 src/Services/Identity/Identity.API/Extensions/DataProtectionBuilderExtensions.cs create mode 100644 src/Services/Identity/Identity.API/Extensions/RedisXmlRepository.cs create mode 100644 src/Services/Identity/Identity.API/Migrations/20170604151240_Init-persisted-grant.Designer.cs create mode 100644 src/Services/Identity/Identity.API/Migrations/20170604151240_Init-persisted-grant.cs create mode 100644 src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20170604151338_Init-configuration.Designer.cs create mode 100644 src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20170604151338_Init-configuration.cs create mode 100644 src/Services/Identity/Identity.API/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs create mode 100644 src/Services/Identity/Identity.API/Migrations/PersistedGrantDbContextModelSnapshot.cs diff --git a/k8s/deployments.yaml b/k8s/deployments.yaml index 1bb406390..a0b1df135 100644 --- a/k8s/deployments.yaml +++ b/k8s/deployments.yaml @@ -69,6 +69,7 @@ kind: Deployment metadata: name: identity spec: + replicas: 3 paused: true template: metadata: diff --git a/src/Services/Identity/Identity.API/Configuration/Config.cs b/src/Services/Identity/Identity.API/Configuration/Config.cs index 744d0a0ce..b11b972db 100644 --- a/src/Services/Identity/Identity.API/Configuration/Config.cs +++ b/src/Services/Identity/Identity.API/Configuration/Config.cs @@ -80,6 +80,7 @@ namespace Identity.API.Configuration }, ClientUri = $"{clientsUrl["Mvc"]}", // public uri of the client AllowedGrantTypes = GrantTypes.Hybrid, + AllowAccessTokensViaBrowser = false, RequireConsent = false, AllowOfflineAccess = true, RedirectUris = new List diff --git a/src/Services/Identity/Identity.API/Extensions/DataProtectionBuilderExtensions.cs b/src/Services/Identity/Identity.API/Extensions/DataProtectionBuilderExtensions.cs new file mode 100644 index 000000000..11fc8342e --- /dev/null +++ b/src/Services/Identity/Identity.API/Extensions/DataProtectionBuilderExtensions.cs @@ -0,0 +1,96 @@ +namespace DataProtectionExtensions +{ + using System; + using System.Linq; + using System.Security.Cryptography.X509Certificates; + using Microsoft.AspNetCore.DataProtection; + using Microsoft.AspNetCore.DataProtection.Repositories; + using Microsoft.AspNetCore.DataProtection.XmlEncryption; + using Microsoft.Extensions.DependencyInjection; + using Microsoft.Extensions.Logging; + using System.Net; + + /// + /// Extension methods for for configuring + /// data protection options. + /// + public static class DataProtectionBuilderExtensions + { + /// + /// Sets up data protection to persist session keys in Redis. + /// + /// The used to set up data protection options. + /// The connection string specifying the Redis instance and database for key storage. + /// + /// The for continued configuration. + /// + /// + /// Thrown if or is . + /// + /// + /// Thrown if is empty. + /// + public static IDataProtectionBuilder PersistKeysToRedis(this IDataProtectionBuilder builder, string redisConnectionString) + { + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (redisConnectionString == null) + { + throw new ArgumentNullException(nameof(redisConnectionString)); + } + + if (redisConnectionString.Length == 0) + { + throw new ArgumentException("Redis connection string may not be empty.", nameof(redisConnectionString)); + } + + var ips = Dns.GetHostAddressesAsync(redisConnectionString).Result; + + return builder.Use(ServiceDescriptor.Singleton(services => new RedisXmlRepository(ips.First().ToString(), services.GetRequiredService>()))); + } + + /// + /// Updates an to use the service of + /// a specific type, removing all other services of that type. + /// + /// The that should use the specified service. + /// The with the service the should use. + /// + /// The for continued configuration. + /// + /// + /// Thrown if or is . + /// + public static IDataProtectionBuilder Use(this IDataProtectionBuilder builder, ServiceDescriptor descriptor) + { + // This algorithm of removing all other services of a specific type + // before adding the new/replacement service is how the base ASP.NET + // DataProtection bits work. Due to some of the differences in how + // that base set of bits handles DI, it's better to follow suit + // and work in the same way than to try and debug weird issues. + if (builder == null) + { + throw new ArgumentNullException(nameof(builder)); + } + + if (descriptor == null) + { + throw new ArgumentNullException(nameof(descriptor)); + } + + for (int i = builder.Services.Count - 1; i >= 0; i--) + { + if (builder.Services[i]?.ServiceType == descriptor.ServiceType) + { + builder.Services.RemoveAt(i); + } + } + + builder.Services.Add(descriptor); + return builder; + } + } +} diff --git a/src/Services/Identity/Identity.API/Extensions/RedisXmlRepository.cs b/src/Services/Identity/Identity.API/Extensions/RedisXmlRepository.cs new file mode 100644 index 000000000..ebb36c1b7 --- /dev/null +++ b/src/Services/Identity/Identity.API/Extensions/RedisXmlRepository.cs @@ -0,0 +1,214 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; +using System.IO; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using Microsoft.AspNetCore.DataProtection.Repositories; +using Microsoft.Extensions.Logging; +using StackExchange.Redis; + +namespace DataProtectionExtensions +{ + /// + /// Key repository that stores XML encrypted keys in a Redis distributed cache. + /// + /// + /// + /// The values stored in Redis are XML documents that contain encrypted session + /// keys used for the protection of things like session state. The document contents + /// are double-encrypted - first with a changing session key; then by a master key. + /// As such, there's no risk in storing the keys in Redis - even if someone can crack + /// the master key, they still need to also crack the session key. (Other solutions + /// for sharing keys across a farm environment include writing them to files + /// on a file share.) + /// + /// + /// While the repository uses a hash to keep the set of encrypted keys separate, you + /// can further separate these items from other items in Redis by specifying a unique + /// database in the connection string. + /// + /// + /// Consumers of the repository are responsible for caching the XML items as needed. + /// Typically repositories are consumed by things like + /// which generates + /// values that get cached. The mechanism is already optimized for caching so there's + /// no need to create a redundant cache. + /// + /// + /// + /// + public class RedisXmlRepository : IXmlRepository, IDisposable + { + /// + /// The root cache key for XML items stored in Redis + /// + public static readonly string RedisHashKey = "DataProtectionXmlRepository"; + + /// + /// The connection to the Redis backing store. + /// + private IConnectionMultiplexer _connection; + + /// + /// Flag indicating whether the object has been disposed. + /// + private bool _disposed = false; + + /// + /// Initializes a new instance of the class. + /// + /// + /// The Redis connection string. + /// + /// + /// The used to log diagnostic messages. + /// + /// + /// Thrown if or is . + /// + public RedisXmlRepository(string connectionString, ILogger logger) + : this(ConnectionMultiplexer.Connect(connectionString), logger) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// + /// The Redis database connection. + /// + /// + /// The used to log diagnostic messages. + /// + /// + /// Thrown if or is . + /// + public RedisXmlRepository(IConnectionMultiplexer connection, ILogger logger) + { + if (connection == null) + { + throw new ArgumentNullException(nameof(connection)); + } + + if (logger == null) + { + throw new ArgumentNullException(nameof(logger)); + } + + this._connection = connection; + this.Logger = logger; + + // Mask the password so it doesn't get logged. + var configuration = Regex.Replace(this._connection.Configuration, @"password\s*=\s*[^,]*", "password=****", RegexOptions.IgnoreCase); + this.Logger.LogDebug("Storing data protection keys in Redis: {RedisConfiguration}", configuration); + } + + /// + /// Gets the logger. + /// + /// + /// The used to log diagnostic messages. + /// + public ILogger Logger { get; private set; } + + /// + /// Performs application-defined tasks associated with freeing, releasing, + /// or resetting unmanaged resources. + /// + public void Dispose() + { + this.Dispose(true); + } + + /// + /// Gets all top-level XML elements in the repository. + /// + /// + /// An with the set of elements + /// stored in the repository. + /// + public IReadOnlyCollection GetAllElements() + { + var database = this._connection.GetDatabase(); + var hash = database.HashGetAll(RedisHashKey); + var elements = new List(); + + if (hash == null || hash.Length == 0) + { + return elements.AsReadOnly(); + } + + foreach (var item in hash.ToStringDictionary()) + { + elements.Add(XElement.Parse(item.Value)); + } + + this.Logger.LogDebug("Read {XmlElementCount} XML elements from Redis.", elements.Count); + return elements.AsReadOnly(); + } + + /// + /// Adds a top-level XML element to the repository. + /// + /// The element to add. + /// + /// An optional name to be associated with the XML element. + /// For instance, if this repository stores XML files on disk, the friendly name may + /// be used as part of the file name. Repository implementations are not required to + /// observe this parameter even if it has been provided by the caller. + /// + /// + /// The parameter must be unique if specified. + /// For instance, it could be the ID of the key being stored. + /// + /// + /// Thrown if is . + /// + public void StoreElement(XElement element, string friendlyName) + { + if (element == null) + { + throw new ArgumentNullException(nameof(element)); + } + + if (string.IsNullOrEmpty(friendlyName)) + { + // The framework always passes in a name, but + // the contract indicates this may be null or empty. + friendlyName = Guid.NewGuid().ToString(); + } + + this.Logger.LogDebug("Storing XML element with friendly name {XmlElementFriendlyName}.", friendlyName); + + this._connection.GetDatabase().HashSet(RedisHashKey, friendlyName, element.ToString()); + } + + /// + /// Releases unmanaged and - optionally - managed resources. + /// + /// + /// to release both managed and unmanaged resources; + /// to release only unmanaged resources. + /// + protected virtual void Dispose(bool disposing) + { + if (!this._disposed) + { + if (disposing) + { + if (this._connection != null) + { + this._connection.Close(); + this._connection.Dispose(); + } + } + + this._connection = null; + this._disposed = true; + } + } + } +} \ No newline at end of file diff --git a/src/Services/Identity/Identity.API/Identity.API.csproj b/src/Services/Identity/Identity.API/Identity.API.csproj index 003b5cb30..8874cabbc 100644 --- a/src/Services/Identity/Identity.API/Identity.API.csproj +++ b/src/Services/Identity/Identity.API/Identity.API.csproj @@ -38,6 +38,7 @@ + diff --git a/src/Services/Identity/Identity.API/Migrations/20170604151240_Init-persisted-grant.Designer.cs b/src/Services/Identity/Identity.API/Migrations/20170604151240_Init-persisted-grant.Designer.cs new file mode 100644 index 000000000..a2b93219b --- /dev/null +++ b/src/Services/Identity/Identity.API/Migrations/20170604151240_Init-persisted-grant.Designer.cs @@ -0,0 +1,52 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using IdentityServer4.EntityFramework.DbContexts; + +namespace Identity.API.Migrations +{ + [DbContext(typeof(PersistedGrantDbContext))] + [Migration("20170604151240_Init-persisted-grant")] + partial class Initpersistedgrant + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.2") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Key") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200); + + b.Property("CreationTime"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000); + + b.Property("Expiration"); + + b.Property("SubjectId") + .HasMaxLength(200); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("PersistedGrants"); + }); + } + } +} diff --git a/src/Services/Identity/Identity.API/Migrations/20170604151240_Init-persisted-grant.cs b/src/Services/Identity/Identity.API/Migrations/20170604151240_Init-persisted-grant.cs new file mode 100644 index 000000000..51c896f5f --- /dev/null +++ b/src/Services/Identity/Identity.API/Migrations/20170604151240_Init-persisted-grant.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Identity.API.Migrations +{ + public partial class Initpersistedgrant : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "PersistedGrants", + columns: table => new + { + Key = table.Column(maxLength: 200, nullable: false), + ClientId = table.Column(maxLength: 200, nullable: false), + CreationTime = table.Column(nullable: false), + Data = table.Column(maxLength: 50000, nullable: false), + Expiration = table.Column(nullable: true), + SubjectId = table.Column(maxLength: 200, nullable: true), + Type = table.Column(maxLength: 50, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_PersistedGrants", x => x.Key); + }); + + migrationBuilder.CreateIndex( + name: "IX_PersistedGrants_SubjectId_ClientId_Type", + table: "PersistedGrants", + columns: new[] { "SubjectId", "ClientId", "Type" }); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "PersistedGrants"); + } + } +} diff --git a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20170604151338_Init-configuration.Designer.cs b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20170604151338_Init-configuration.Designer.cs new file mode 100644 index 000000000..9e6eb2500 --- /dev/null +++ b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20170604151338_Init-configuration.Designer.cs @@ -0,0 +1,539 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using IdentityServer4.EntityFramework.DbContexts; + +namespace Identity.API.Migrations.ConfigurationDb +{ + [DbContext(typeof(ConfigurationDbContext))] + [Migration("20170604151338_Init-configuration")] + partial class Initconfiguration + { + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.2") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Enabled"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiResourceId") + .IsRequired(); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiResourceId") + .IsRequired(); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Emphasize"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.Property("Required"); + + b.Property("ShowInDiscoveryDocument"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiScopeId") + .IsRequired(); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId"); + + b.ToTable("ApiScopeClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiResourceId") + .IsRequired(); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("Expiration"); + + b.Property("Type") + .HasMaxLength(250); + + b.Property("Value") + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AbsoluteRefreshTokenLifetime"); + + b.Property("AccessTokenLifetime"); + + b.Property("AccessTokenType"); + + b.Property("AllowAccessTokensViaBrowser"); + + b.Property("AllowOfflineAccess"); + + b.Property("AllowPlainTextPkce"); + + b.Property("AllowRememberConsent"); + + b.Property("AlwaysIncludeUserClaimsInIdToken"); + + b.Property("AlwaysSendClientClaims"); + + b.Property("AuthorizationCodeLifetime"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200); + + b.Property("ClientName") + .HasMaxLength(200); + + b.Property("ClientUri") + .HasMaxLength(2000); + + b.Property("EnableLocalLogin"); + + b.Property("Enabled"); + + b.Property("IdentityTokenLifetime"); + + b.Property("IncludeJwtId"); + + b.Property("LogoUri"); + + b.Property("LogoutSessionRequired"); + + b.Property("LogoutUri"); + + b.Property("PrefixClientClaims"); + + b.Property("ProtocolType") + .IsRequired() + .HasMaxLength(200); + + b.Property("RefreshTokenExpiration"); + + b.Property("RefreshTokenUsage"); + + b.Property("RequireClientSecret"); + + b.Property("RequireConsent"); + + b.Property("RequirePkce"); + + b.Property("SlidingRefreshTokenLifetime"); + + b.Property("UpdateAccessTokenClaimsOnRefresh"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(250); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("Origin") + .IsRequired() + .HasMaxLength(150); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientCorsOrigins"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("GrantType") + .IsRequired() + .HasMaxLength(250); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientGrantTypes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientIdPRestrictions"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("PostLogoutRedirectUri") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientPostLogoutRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("RedirectUri") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("Description") + .HasMaxLength(2000); + + b.Property("Expiration"); + + b.Property("Type") + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("IdentityResourceId") + .IsRequired(); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Emphasize"); + + b.Property("Enabled"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.Property("Required"); + + b.Property("ShowInDiscoveryDocument"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("IdentityResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Scopes") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope") + .WithMany("UserClaims") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Secrets") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Claims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedCorsOrigins") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("PostLogoutRedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("UserClaims") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20170604151338_Init-configuration.cs b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20170604151338_Init-configuration.cs new file mode 100644 index 000000000..8cf20e865 --- /dev/null +++ b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/20170604151338_Init-configuration.cs @@ -0,0 +1,501 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Metadata; + +namespace Identity.API.Migrations.ConfigurationDb +{ + public partial class Initconfiguration : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "ApiResources", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Description = table.Column(maxLength: 1000, nullable: true), + DisplayName = table.Column(maxLength: 200, nullable: true), + Enabled = table.Column(nullable: false), + Name = table.Column(maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "Clients", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + AbsoluteRefreshTokenLifetime = table.Column(nullable: false), + AccessTokenLifetime = table.Column(nullable: false), + AccessTokenType = table.Column(nullable: false), + AllowAccessTokensViaBrowser = table.Column(nullable: false), + AllowOfflineAccess = table.Column(nullable: false), + AllowPlainTextPkce = table.Column(nullable: false), + AllowRememberConsent = table.Column(nullable: false), + AlwaysIncludeUserClaimsInIdToken = table.Column(nullable: false), + AlwaysSendClientClaims = table.Column(nullable: false), + AuthorizationCodeLifetime = table.Column(nullable: false), + ClientId = table.Column(maxLength: 200, nullable: false), + ClientName = table.Column(maxLength: 200, nullable: true), + ClientUri = table.Column(maxLength: 2000, nullable: true), + EnableLocalLogin = table.Column(nullable: false), + Enabled = table.Column(nullable: false), + IdentityTokenLifetime = table.Column(nullable: false), + IncludeJwtId = table.Column(nullable: false), + LogoUri = table.Column(nullable: true), + LogoutSessionRequired = table.Column(nullable: false), + LogoutUri = table.Column(nullable: true), + PrefixClientClaims = table.Column(nullable: false), + ProtocolType = table.Column(maxLength: 200, nullable: false), + RefreshTokenExpiration = table.Column(nullable: false), + RefreshTokenUsage = table.Column(nullable: false), + RequireClientSecret = table.Column(nullable: false), + RequireConsent = table.Column(nullable: false), + RequirePkce = table.Column(nullable: false), + SlidingRefreshTokenLifetime = table.Column(nullable: false), + UpdateAccessTokenClaimsOnRefresh = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_Clients", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "IdentityResources", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Description = table.Column(maxLength: 1000, nullable: true), + DisplayName = table.Column(maxLength: 200, nullable: true), + Emphasize = table.Column(nullable: false), + Enabled = table.Column(nullable: false), + Name = table.Column(maxLength: 200, nullable: false), + Required = table.Column(nullable: false), + ShowInDiscoveryDocument = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityResources", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ApiClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ApiResourceId = table.Column(nullable: false), + Type = table.Column(maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiClaims", x => x.Id); + table.ForeignKey( + name: "FK_ApiClaims_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiScopes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ApiResourceId = table.Column(nullable: false), + Description = table.Column(maxLength: 1000, nullable: true), + DisplayName = table.Column(maxLength: 200, nullable: true), + Emphasize = table.Column(nullable: false), + Name = table.Column(maxLength: 200, nullable: false), + Required = table.Column(nullable: false), + ShowInDiscoveryDocument = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopes", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopes_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiSecrets", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ApiResourceId = table.Column(nullable: false), + Description = table.Column(maxLength: 1000, nullable: true), + Expiration = table.Column(nullable: true), + Type = table.Column(maxLength: 250, nullable: true), + Value = table.Column(maxLength: 2000, nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiSecrets", x => x.Id); + table.ForeignKey( + name: "FK_ApiSecrets_ApiResources_ApiResourceId", + column: x => x.ApiResourceId, + principalTable: "ApiResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ClientId = table.Column(nullable: false), + Type = table.Column(maxLength: 250, nullable: false), + Value = table.Column(maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientClaims", x => x.Id); + table.ForeignKey( + name: "FK_ClientClaims_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientCorsOrigins", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ClientId = table.Column(nullable: false), + Origin = table.Column(maxLength: 150, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientCorsOrigins", x => x.Id); + table.ForeignKey( + name: "FK_ClientCorsOrigins_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientGrantTypes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ClientId = table.Column(nullable: false), + GrantType = table.Column(maxLength: 250, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientGrantTypes", x => x.Id); + table.ForeignKey( + name: "FK_ClientGrantTypes_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientIdPRestrictions", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ClientId = table.Column(nullable: false), + Provider = table.Column(maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientIdPRestrictions", x => x.Id); + table.ForeignKey( + name: "FK_ClientIdPRestrictions_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientPostLogoutRedirectUris", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ClientId = table.Column(nullable: false), + PostLogoutRedirectUri = table.Column(maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientPostLogoutRedirectUris", x => x.Id); + table.ForeignKey( + name: "FK_ClientPostLogoutRedirectUris_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientRedirectUris", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ClientId = table.Column(nullable: false), + RedirectUri = table.Column(maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientRedirectUris", x => x.Id); + table.ForeignKey( + name: "FK_ClientRedirectUris_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientScopes", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ClientId = table.Column(nullable: false), + Scope = table.Column(maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientScopes", x => x.Id); + table.ForeignKey( + name: "FK_ClientScopes_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ClientSecrets", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ClientId = table.Column(nullable: false), + Description = table.Column(maxLength: 2000, nullable: true), + Expiration = table.Column(nullable: true), + Type = table.Column(maxLength: 250, nullable: true), + Value = table.Column(maxLength: 2000, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ClientSecrets", x => x.Id); + table.ForeignKey( + name: "FK_ClientSecrets_Clients_ClientId", + column: x => x.ClientId, + principalTable: "Clients", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "IdentityClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + IdentityResourceId = table.Column(nullable: false), + Type = table.Column(maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_IdentityClaims", x => x.Id); + table.ForeignKey( + name: "FK_IdentityClaims_IdentityResources_IdentityResourceId", + column: x => x.IdentityResourceId, + principalTable: "IdentityResources", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateTable( + name: "ApiScopeClaims", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ApiScopeId = table.Column(nullable: false), + Type = table.Column(maxLength: 200, nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ApiScopeClaims", x => x.Id); + table.ForeignKey( + name: "FK_ApiScopeClaims_ApiScopes_ApiScopeId", + column: x => x.ApiScopeId, + principalTable: "ApiScopes", + principalColumn: "Id", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_ApiResources_Name", + table: "ApiResources", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ApiClaims_ApiResourceId", + table: "ApiClaims", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopes_ApiResourceId", + table: "ApiScopes", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopes_Name", + table: "ApiScopes", + column: "Name", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ApiScopeClaims_ApiScopeId", + table: "ApiScopeClaims", + column: "ApiScopeId"); + + migrationBuilder.CreateIndex( + name: "IX_ApiSecrets_ApiResourceId", + table: "ApiSecrets", + column: "ApiResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_Clients_ClientId", + table: "Clients", + column: "ClientId", + unique: true); + + migrationBuilder.CreateIndex( + name: "IX_ClientClaims_ClientId", + table: "ClientClaims", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientCorsOrigins_ClientId", + table: "ClientCorsOrigins", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientGrantTypes_ClientId", + table: "ClientGrantTypes", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientIdPRestrictions_ClientId", + table: "ClientIdPRestrictions", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientPostLogoutRedirectUris_ClientId", + table: "ClientPostLogoutRedirectUris", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientRedirectUris_ClientId", + table: "ClientRedirectUris", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientScopes_ClientId", + table: "ClientScopes", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_ClientSecrets_ClientId", + table: "ClientSecrets", + column: "ClientId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityClaims_IdentityResourceId", + table: "IdentityClaims", + column: "IdentityResourceId"); + + migrationBuilder.CreateIndex( + name: "IX_IdentityResources_Name", + table: "IdentityResources", + column: "Name", + unique: true); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "ApiClaims"); + + migrationBuilder.DropTable( + name: "ApiScopeClaims"); + + migrationBuilder.DropTable( + name: "ApiSecrets"); + + migrationBuilder.DropTable( + name: "ClientClaims"); + + migrationBuilder.DropTable( + name: "ClientCorsOrigins"); + + migrationBuilder.DropTable( + name: "ClientGrantTypes"); + + migrationBuilder.DropTable( + name: "ClientIdPRestrictions"); + + migrationBuilder.DropTable( + name: "ClientPostLogoutRedirectUris"); + + migrationBuilder.DropTable( + name: "ClientRedirectUris"); + + migrationBuilder.DropTable( + name: "ClientScopes"); + + migrationBuilder.DropTable( + name: "ClientSecrets"); + + migrationBuilder.DropTable( + name: "IdentityClaims"); + + migrationBuilder.DropTable( + name: "ApiScopes"); + + migrationBuilder.DropTable( + name: "Clients"); + + migrationBuilder.DropTable( + name: "IdentityResources"); + + migrationBuilder.DropTable( + name: "ApiResources"); + } + } +} diff --git a/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs new file mode 100644 index 000000000..7c725f5ae --- /dev/null +++ b/src/Services/Identity/Identity.API/Migrations/ConfigurationDb/ConfigurationDbContextModelSnapshot.cs @@ -0,0 +1,538 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using IdentityServer4.EntityFramework.DbContexts; + +namespace Identity.API.Migrations.ConfigurationDb +{ + [DbContext(typeof(ConfigurationDbContext))] + partial class ConfigurationDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.2") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Enabled"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiResourceId") + .IsRequired(); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiResourceId") + .IsRequired(); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Emphasize"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.Property("Required"); + + b.Property("ShowInDiscoveryDocument"); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ApiScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiScopeId") + .IsRequired(); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ApiScopeId"); + + b.ToTable("ApiScopeClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiResourceId") + .IsRequired(); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("Expiration"); + + b.Property("Type") + .HasMaxLength(250); + + b.Property("Value") + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ApiResourceId"); + + b.ToTable("ApiSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.Client", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AbsoluteRefreshTokenLifetime"); + + b.Property("AccessTokenLifetime"); + + b.Property("AccessTokenType"); + + b.Property("AllowAccessTokensViaBrowser"); + + b.Property("AllowOfflineAccess"); + + b.Property("AllowPlainTextPkce"); + + b.Property("AllowRememberConsent"); + + b.Property("AlwaysIncludeUserClaimsInIdToken"); + + b.Property("AlwaysSendClientClaims"); + + b.Property("AuthorizationCodeLifetime"); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200); + + b.Property("ClientName") + .HasMaxLength(200); + + b.Property("ClientUri") + .HasMaxLength(2000); + + b.Property("EnableLocalLogin"); + + b.Property("Enabled"); + + b.Property("IdentityTokenLifetime"); + + b.Property("IncludeJwtId"); + + b.Property("LogoUri"); + + b.Property("LogoutSessionRequired"); + + b.Property("LogoutUri"); + + b.Property("PrefixClientClaims"); + + b.Property("ProtocolType") + .IsRequired() + .HasMaxLength(200); + + b.Property("RefreshTokenExpiration"); + + b.Property("RefreshTokenUsage"); + + b.Property("RequireClientSecret"); + + b.Property("RequireConsent"); + + b.Property("RequirePkce"); + + b.Property("SlidingRefreshTokenLifetime"); + + b.Property("UpdateAccessTokenClaimsOnRefresh"); + + b.HasKey("Id"); + + b.HasIndex("ClientId") + .IsUnique(); + + b.ToTable("Clients"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("Type") + .IsRequired() + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(250); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("Origin") + .IsRequired() + .HasMaxLength(150); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientCorsOrigins"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("GrantType") + .IsRequired() + .HasMaxLength(250); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientGrantTypes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("Provider") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientIdPRestrictions"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("PostLogoutRedirectUri") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientPostLogoutRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("RedirectUri") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientRedirectUris"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("Scope") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientScopes"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ClientId") + .IsRequired(); + + b.Property("Description") + .HasMaxLength(2000); + + b.Property("Expiration"); + + b.Property("Type") + .HasMaxLength(250); + + b.Property("Value") + .IsRequired() + .HasMaxLength(2000); + + b.HasKey("Id"); + + b.HasIndex("ClientId"); + + b.ToTable("ClientSecrets"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("IdentityResourceId") + .IsRequired(); + + b.Property("Type") + .IsRequired() + .HasMaxLength(200); + + b.HasKey("Id"); + + b.HasIndex("IdentityResourceId"); + + b.ToTable("IdentityClaims"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityResource", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("Description") + .HasMaxLength(1000); + + b.Property("DisplayName") + .HasMaxLength(200); + + b.Property("Emphasize"); + + b.Property("Enabled"); + + b.Property("Name") + .IsRequired() + .HasMaxLength(200); + + b.Property("Required"); + + b.Property("ShowInDiscoveryDocument"); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("IdentityResources"); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiResourceClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("UserClaims") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Scopes") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiScopeClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiScope", "ApiScope") + .WithMany("UserClaims") + .HasForeignKey("ApiScopeId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ApiSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.ApiResource", "ApiResource") + .WithMany("Secrets") + .HasForeignKey("ApiResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("Claims") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientCorsOrigin", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedCorsOrigins") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientGrantType", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedGrantTypes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientIdPRestriction", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("IdentityProviderRestrictions") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientPostLogoutRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("PostLogoutRedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientRedirectUri", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("RedirectUris") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientScope", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("AllowedScopes") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.ClientSecret", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.Client", "Client") + .WithMany("ClientSecrets") + .HasForeignKey("ClientId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.IdentityClaim", b => + { + b.HasOne("IdentityServer4.EntityFramework.Entities.IdentityResource", "IdentityResource") + .WithMany("UserClaims") + .HasForeignKey("IdentityResourceId") + .OnDelete(DeleteBehavior.Cascade); + }); + } + } +} diff --git a/src/Services/Identity/Identity.API/Migrations/PersistedGrantDbContextModelSnapshot.cs b/src/Services/Identity/Identity.API/Migrations/PersistedGrantDbContextModelSnapshot.cs new file mode 100644 index 000000000..ffe6bc35a --- /dev/null +++ b/src/Services/Identity/Identity.API/Migrations/PersistedGrantDbContextModelSnapshot.cs @@ -0,0 +1,51 @@ +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; +using IdentityServer4.EntityFramework.DbContexts; + +namespace Identity.API.Migrations +{ + [DbContext(typeof(PersistedGrantDbContext))] + partial class PersistedGrantDbContextModelSnapshot : ModelSnapshot + { + protected override void BuildModel(ModelBuilder modelBuilder) + { + modelBuilder + .HasAnnotation("ProductVersion", "1.1.2") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("IdentityServer4.EntityFramework.Entities.PersistedGrant", b => + { + b.Property("Key") + .HasMaxLength(200); + + b.Property("ClientId") + .IsRequired() + .HasMaxLength(200); + + b.Property("CreationTime"); + + b.Property("Data") + .IsRequired() + .HasMaxLength(50000); + + b.Property("Expiration"); + + b.Property("SubjectId") + .HasMaxLength(200); + + b.Property("Type") + .IsRequired() + .HasMaxLength(50); + + b.HasKey("Key"); + + b.HasIndex("SubjectId", "ClientId", "Type"); + + b.ToTable("PersistedGrants"); + }); + } + } +} diff --git a/src/Services/Identity/Identity.API/Startup.cs b/src/Services/Identity/Identity.API/Startup.cs index 981e305c8..98ed8578d 100644 --- a/src/Services/Identity/Identity.API/Startup.cs +++ b/src/Services/Identity/Identity.API/Startup.cs @@ -19,6 +19,11 @@ using Microsoft.eShopOnContainers.Services.Catalog.API.Infrastructure; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.HealthChecks; using Identity.API.Certificate; +using DataProtectionExtensions; +using System.Reflection; +using IdentityServer4.Test; +using IdentityServer4.EntityFramework.DbContexts; +using IdentityServer4.EntityFramework.Mappers; namespace eShopOnContainers.Identity { @@ -34,7 +39,7 @@ namespace eShopOnContainers.Identity if (env.IsDevelopment()) { // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709 - builder.AddUserSecrets(); + //builder.AddUserSecrets(); } builder.AddEnvironmentVariables(); @@ -45,8 +50,7 @@ namespace eShopOnContainers.Identity // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) - { - + { // Add framework services. services.AddDbContext(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); @@ -57,12 +61,12 @@ namespace eShopOnContainers.Identity services.Configure(Configuration); + services.AddMvc(); + services.AddDataProtection(opts => { opts.ApplicationDiscriminator = "eshop.identity"; - }); - - services.AddMvc(); + });//.PersistKeysToRedis("basket.data"); services.AddHealthChecks(checks => { @@ -79,20 +83,20 @@ namespace eShopOnContainers.Identity services.AddTransient, EFLoginService>(); services.AddTransient(); - //callbacks urls from config: - Dictionary clientUrls = new Dictionary(); - clientUrls.Add("Mvc", Configuration.GetValue("MvcClient")); - clientUrls.Add("Spa", Configuration.GetValue("SpaClient")); - clientUrls.Add("Xamarin", Configuration.GetValue("XamarinCallback")); + var connectionString = Configuration.GetConnectionString("DefaultConnection"); + var migrationsAssembly = typeof(Startup).GetTypeInfo().Assembly.GetName().Name; // Adds IdentityServer services.AddIdentityServer(x => x.IssuerUri = "null") - .AddSigningCredential(Certificate.Get()) - .AddInMemoryApiResources(Config.GetApis()) - .AddInMemoryIdentityResources(Config.GetResources()) - .AddInMemoryClients(Config.GetClients(clientUrls)) + .AddSigningCredential(Certificate.Get()) .AddAspNetIdentity() - .Services.AddTransient(); + .AddConfigurationStore(builder => + builder.UseSqlServer(connectionString, options => + options.MigrationsAssembly(migrationsAssembly))) + .AddOperationalStore(builder => + builder.UseSqlServer(connectionString, options => + options.MigrationsAssembly(migrationsAssembly))) + .Services.AddTransient(); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. @@ -134,10 +138,55 @@ namespace eShopOnContainers.Identity template: "{controller=Home}/{action=Index}/{id?}"); }); + InitializeDatabase(app); + //Seed Data var hasher = new PasswordHasher(); new ApplicationContextSeed(hasher).SeedAsync(app, loggerFactory) .Wait(); } + + private void InitializeDatabase(IApplicationBuilder app) + { + //callbacks urls from config: + Dictionary clientUrls = new Dictionary(); + clientUrls.Add("Mvc", Configuration.GetValue("MvcClient")); + clientUrls.Add("Spa", Configuration.GetValue("SpaClient")); + clientUrls.Add("Xamarin", Configuration.GetValue("XamarinCallback")); + + using (var serviceScope = app.ApplicationServices.GetService().CreateScope()) + { + serviceScope.ServiceProvider.GetRequiredService().Database.Migrate(); + var context = serviceScope.ServiceProvider.GetRequiredService(); + context.Database.Migrate(); + + if (!context.Clients.Any()) + { + foreach (var client in Config.GetClients(clientUrls)) + { + context.Clients.Add(client.ToEntity()); + } + context.SaveChanges(); + } + + if (!context.IdentityResources.Any()) + { + foreach (var resource in Config.GetResources()) + { + context.IdentityResources.Add(resource.ToEntity()); + } + context.SaveChanges(); + } + + if (!context.ApiResources.Any()) + { + foreach (var api in Config.GetApis()) + { + context.ApiResources.Add(api.ToEntity()); + } + context.SaveChanges(); + } + } + } } } diff --git a/src/Web/WebMVC/Controllers/AccountController.cs b/src/Web/WebMVC/Controllers/AccountController.cs index 0dee2ccfb..531c6f547 100644 --- a/src/Web/WebMVC/Controllers/AccountController.cs +++ b/src/Web/WebMVC/Controllers/AccountController.cs @@ -4,6 +4,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.eShopOnContainers.WebMVC.ViewModels; using Microsoft.eShopOnContainers.WebMVC.Services; using Microsoft.AspNetCore.Http.Authentication; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; namespace Microsoft.eShopOnContainers.WebMVC.Controllers { @@ -17,15 +19,16 @@ namespace Microsoft.eShopOnContainers.WebMVC.Controllers public ActionResult Index() => View(); [Authorize] - public IActionResult SignIn(string returnUrl) + public async Task SignIn(string returnUrl) { var user = User as ClaimsPrincipal; - + var token = await HttpContext.Authentication.GetTokenAsync("access_token"); + //TODO - Not retrieving AccessToken yet - var token = user.FindFirst("access_token"); + //var token = user.FindFirst("access_token"); if (token != null) { - ViewData["access_token"] = token.Value; + ViewData["access_token"] = token; } // "Catalog" because UrlHelper doesn't support nameof() for controllers diff --git a/src/Web/WebMVC/Startup.cs b/src/Web/WebMVC/Startup.cs index ba0b1244f..1a6fc240b 100644 --- a/src/Web/WebMVC/Startup.cs +++ b/src/Web/WebMVC/Startup.cs @@ -44,12 +44,14 @@ namespace Microsoft.eShopOnContainers.WebMVC // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddMvc(); + services.AddDataProtection(opts => { opts.ApplicationDiscriminator = "eshop.webmvc"; }); - services.AddMvc(); + services.Configure(Configuration); services.AddHealthChecks(checks => diff --git a/test/Services/FunctionalTests/FunctionalTests.csproj b/test/Services/FunctionalTests/FunctionalTests.csproj index 2b8ee1f44..54a74beda 100644 --- a/test/Services/FunctionalTests/FunctionalTests.csproj +++ b/test/Services/FunctionalTests/FunctionalTests.csproj @@ -38,4 +38,8 @@ + + + + \ No newline at end of file diff --git a/test/Services/IntegrationTests/IntegrationTests.csproj b/test/Services/IntegrationTests/IntegrationTests.csproj index b71e1d4c4..60911dd4d 100644 --- a/test/Services/IntegrationTests/IntegrationTests.csproj +++ b/test/Services/IntegrationTests/IntegrationTests.csproj @@ -46,4 +46,8 @@ + + + + diff --git a/test/Services/UnitTest/UnitTest.csproj b/test/Services/UnitTest/UnitTest.csproj index 3a80e594b..8955e4a45 100644 --- a/test/Services/UnitTest/UnitTest.csproj +++ b/test/Services/UnitTest/UnitTest.csproj @@ -28,4 +28,8 @@ + + + +