Beispiel #1
0
        private static void ConfigureAspNetIdentity(IServiceCollection services, TenantsBuilder tenantsBuilder, IConfiguration configuration)
        {
            // now, on second priority, comes the identity which we tweaked

            bool requireEmailConfirmed = configuration.GetValue <bool>("Identity:FeatureSet:RequireEmailConfirmed");

            var identityBuilder = services.AddIdentity <UserModel, IdentityRole>(options =>
            {
                options.SignIn.RequireConfirmedEmail = requireEmailConfirmed;
            });

            // IdentityModelEventSource.ShowPII = true;

            identityBuilder.AddEntityFrameworkStores <SqlServerIdentityDbContext>();
            identityBuilder.AddDefaultTokenProviders();

            if (tenantsBuilder == null)
            {
                string authCookieDomain = configuration[$"Identity:AuthCookie:Domain"];
                if (string.IsNullOrEmpty(authCookieDomain))
                {
                    throw new Exception("Identity auth cookie domain is empty");
                }

                services.ConfigureApplicationCookie(options =>
                {
                    options.Cookie.Domain = authCookieDomain;
                    options.Cookie.Name   = "HCore.Identity.session";
                });
            }
            else
            {
                tenantsBuilder.WithPerTenantOptions <CookieAuthenticationOptions>((options, tenantInfo) =>
                {
                    options.Cookie.Domain = tenantInfo.DeveloperAuthCookieDomain;

                    if (!tenantInfo.UsersAreExternallyManaged)
                    {
                        options.Cookie.Name = $"{tenantInfo.DeveloperUuid}.HCore.Identity.session";
                    }
                    else
                    {
                        options.Cookie.Name = $"{tenantInfo.DeveloperUuid}.{tenantInfo.TenantUuid}.HCore.Identity.Session";
                    }
                });
            }

            services.Configure <IdentityOptions>(options =>
            {
                options.User.RequireUniqueEmail = true;

                // add ':' for our external user names
                options.User.AllowedUserNameCharacters = IdentityServicesImpl.AllowedUserNameCharacters;

                options.Password.RequireDigit           = false;
                options.Password.RequireLowercase       = false;
                options.Password.RequireNonAlphanumeric = false;
                options.Password.RequireUppercase       = false;
            });
        }
Beispiel #2
0
        public static IServiceCollection AddCoreIdentity <TStartup>(this IServiceCollection services, IConfiguration configuration)
        {
            var migrationsAssembly = typeof(TStartup).GetTypeInfo().Assembly.GetName().Name;

            bool useIdentity = configuration.GetValue <bool>("Identity:UseIdentity");
            bool useTenants  = configuration.GetValue <bool>("Identity:UseTenants");

            TenantsBuilder tenantsBuilder = null;

            if (useTenants)
            {
                tenantsBuilder = services.AddTenants <TStartup>(configuration);
            }

            if (useIdentity)
            {
                ConfigureSqlServer <TStartup>(services, configuration);
                ConfigureDataProtection(services, configuration);

                ConfigureAspNetIdentity(services, tenantsBuilder, configuration);

                ConfigureIdentityServer(services, tenantsBuilder, migrationsAssembly, configuration);
            }

            bool useJwt = configuration.GetValue <bool>("Identity:UseJwt");

            if (useJwt)
            {
                ConfigureJwtAuthentication(services, tenantsBuilder, configuration);
            }

            // see https://github.com/IdentityServer/IdentityServer4.Samples/blob/release/Quickstarts/Combined_AspNetIdentity_and_EntityFrameworkStorage/src/IdentityServerWithAspIdAndEF/Startup.cs#L84

            services.Configure <IISOptions>(iis =>
            {
                iis.AuthenticationDisplayName = "Windows";
                iis.AutomaticAuthentication   = false;
            });

            if (useIdentity)
            {
                services.AddSingleton <IEmailSender, HCore.Identity.EmailSender.Impl.EmailSenderImpl>();

                services.AddSingleton <HCore.Identity.Providers.IConfigurationProvider, ConfigurationProviderImpl>();

                services.AddScoped <IAccessTokenProvider, AccessTokenProviderImpl>();
                services.AddScoped <IIdentityServices, IdentityServicesImpl>();
            }

            if (useIdentity || useJwt)
            {
                services.AddScoped <IAuthServices, AuthServicesImpl>();
            }

            return(services);
        }
        private static void ConfigureIdentityServer(IServiceCollection services, TenantsBuilder tenantsBuilder, string migrationsAssembly, IConfiguration configuration)
        {
            IIdentityServerBuilder identityServerBuilder;

            if (tenantsBuilder == null)
            {
                identityServerBuilder = services.AddIdentityServer(options =>
                {
                    options.Events.RaiseErrorEvents       = true;
                    options.Events.RaiseInformationEvents = true;
                    options.Events.RaiseFailureEvents     = true;
                    options.Events.RaiseSuccessEvents     = true;
                    options.UserInteraction.ErrorUrl      = "/Error";
                    options.UserInteraction.ConsentUrl    = "/Account/Consent";
                });
            }
            else
            {
                string defaultClientAuthority = configuration[$"Identity:DefaultClient:Authority"];
                if (string.IsNullOrEmpty(defaultClientAuthority))
                {
                    throw new Exception("Identity default client authority string is empty");
                }

                identityServerBuilder = services.AddIdentityServer(options =>
                {
                    options.Events.RaiseErrorEvents       = true;
                    options.Events.RaiseInformationEvents = true;
                    options.Events.RaiseFailureEvents     = true;
                    options.Events.RaiseSuccessEvents     = true;
                    options.UserInteraction.ErrorUrl      = "/Error";
                    options.UserInteraction.ConsentUrl    = "/Account/Consent";

                    options.IssuerUri = defaultClientAuthority;
                });
            }

            // see http://amilspage.com/signing-certificates-idsv4/

            identityServerBuilder.AddSigningCredential(GetSigningKeyCertificate(configuration));

            identityServerBuilder.AddAspNetIdentity <UserModel>();

            // this adds the config data from DB (clients, resources)
            identityServerBuilder.AddConfigurationStore <SqlServerConfigurationDbContext>(options =>
            {
                options.ConfigureDbContext = dbContextBuilder =>
                                             dbContextBuilder.AddSqlDatabase("Identity", configuration, migrationsAssembly);
            });

            services.AddDbContext <ConfigurationDbContext>(options =>
            {
                options.AddSqlDatabase("Identity", configuration, migrationsAssembly);
            });

            // this adds the operational data from DB (codes, tokens, consents)
            identityServerBuilder.AddOperationalStore <SqlServerPersistedGrantDbContext>(options =>
            {
                options.ConfigureDbContext = dbContextBuilder =>
                                             dbContextBuilder.AddSqlDatabase("Identity", configuration, migrationsAssembly);

                // this enables automatic token cleanup. this is optional.
                options.EnableTokenCleanup = true;
                // options.TokenCleanupInterval = 15; // frequency in seconds to cleanup stale grants. 15 is useful during debugging
            });

            services.AddDbContext <PersistedGrantDbContext>(options =>
            {
                options.AddSqlDatabase("Identity", configuration, migrationsAssembly);
            });

            identityServerBuilder.AddRedirectUriValidator <WildcardRedirectUriValidatorImpl>();
        }
        private static void ConfigureExternalAuthenticationServices(AuthenticationBuilder authenticationBuilder, TenantsBuilder tenantsBuilder, IConfiguration configuration)
        {
            if (tenantsBuilder != null)
            {
                authenticationBuilder.AddOpenIdConnect(IdentityCoreConstants.ExternalOidcScheme, openIdConnect =>
                {
                    // will be configured dynamically

                    openIdConnect.ClientId  = "N/A";
                    openIdConnect.Authority = "https://nowhere.nowhere";
                });

                tenantsBuilder.WithPerTenantOptions <OpenIdConnectOptions>((openIdConnect, tenantInfo, name) =>
                {
                    if (string.Equals(tenantInfo.ExternalAuthenticationMethod, TenantConstants.ExternalAuthenticationMethodOidc))
                    {
                        var oidcClientId          = tenantInfo.OidcClientId;
                        var oidcUsePkce           = tenantInfo.OidcUsePkce;
                        var oidcClientSecret      = tenantInfo.OidcClientSecret;
                        var oidcEndpointUrl       = tenantInfo.OidcEndpointUrl;
                        var oidcScopes            = tenantInfo.OidcScopes;
                        var oidcAcrValues         = tenantInfo.OidcAcrValues;
                        var oidcAcrValuesAppendix = tenantInfo.OidcAcrValuesAppendix;
                        var oidcTriggerAcrValuesAppendixByUrlParameter = tenantInfo.OidcTriggerAcrValuesAppendixByUrlParameter;

                        openIdConnect.SignInScheme  = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                        openIdConnect.SignOutScheme = IdentityServerConstants.SignoutScheme;

                        openIdConnect.Authority = oidcEndpointUrl;

                        openIdConnect.ClientId = oidcClientId;

                        if (!oidcUsePkce)
                        {
                            openIdConnect.ClientSecret = oidcClientSecret;
                        }
                        else
                        {
                            openIdConnect.ResponseType = OpenIdConnectResponseType.Code;

                            openIdConnect.UsePkce = true;
                        }

                        if (!string.IsNullOrEmpty(oidcAcrValues))
                        {
                            openIdConnect.Events = new OpenIdConnectEvents
                            {
                                OnRedirectToIdentityProvider = context =>
                                {
                                    context.ProtocolMessage.SetParameter("acr_values", AdjustAcrValues(oidcAcrValues, oidcAcrValuesAppendix, oidcTriggerAcrValuesAppendixByUrlParameter, context.Request));

                                    return(Task.CompletedTask);
                                }
                            };
                        }

                        // make sure we get user group membership information

                        openIdConnect.GetClaimsFromUserInfoEndpoint = true;

                        var scopes = new HashSet <string>();

                        scopes.Add("openid");
                        scopes.Add("email");
                        scopes.Add("phone");
                        scopes.Add("profile");

                        if (oidcScopes != null)
                        {
                            foreach (var oidcScope in oidcScopes)
                            {
                                scopes.Add(oidcScope);
                            }
                        }

                        foreach (var scope in scopes)
                        {
                            openIdConnect.Scope.Add(scope);
                        }
                    }
                });

                authenticationBuilder.AddSaml2(IdentityCoreConstants.ExternalSamlScheme, saml =>
                {
                    // will be configured dynamically
                });

                CryptoConfig.AddAlgorithm(typeof(RSAPKCS1SHA1SignatureDescription), SignedXml.XmlDsigRSASHA1Url);

                tenantsBuilder.WithPerTenantOptions <Saml2Options>((saml, tenantInfo, name) =>
                {
                    if (string.Equals(tenantInfo.ExternalAuthenticationMethod, TenantConstants.ExternalAuthenticationMethodSaml))
                    {
                        var samlEntityId = tenantInfo.SamlEntityId;

                        saml.SPOptions.EntityId = new EntityId(samlEntityId);

                        saml.SPOptions.ReturnUrl = new Uri($"{tenantInfo.WebUrl}Account/Login?ReturnUrl=%2F&handler=ExternalCallback");

                        if (tenantInfo.SamlAllowWeakSigningAlgorithm)
                        {
                            // OK this is less secure, but sometimes we're having trouble
                            // e.g. with SSO Circle that still uses SHA1

                            saml.SPOptions.MinIncomingSigningAlgorithm = SignedXml.XmlDsigRSASHA1Url;
                        }

                        var samlCertificate = tenantInfo.GetSamlCertificate();

                        if (samlCertificate != null)
                        {
                            saml.SPOptions.ServiceCertificates.Add(new ServiceCertificate()
                            {
                                Certificate = samlCertificate,
                                Use         = CertificateUse.Signing
                            });
                        }

                        var samlPeerEntityId = tenantInfo.SamlPeerEntityId;

                        var samlPeerIdpMetadataLocation = tenantInfo.SamlPeerIdpMetadataLocation;

                        if (!string.IsNullOrEmpty(samlPeerIdpMetadataLocation))
                        {
                            // the provider has configuration data available online

                            saml.IdentityProviders.Add(
                                new IdentityProvider(new EntityId(samlPeerEntityId), saml.SPOptions)
                            {
                                LoadMetadata     = true,
                                MetadataLocation = samlPeerIdpMetadataLocation,
                                AllowUnsolicitedAuthnResponse = true
                            }
                                );
                        }
                        else
                        {
                            // we need to get the configuration data from our DB
                            var samlPeerIdpMetadata = tenantInfo.SamlPeerIdpMetadata;

                            var metadata = Saml2MetadataLoader.LoadIdp(samlPeerIdpMetadata, saml.SPOptions.Compatibility.UnpackEntitiesDescriptorInIdentityProviderMetadata);

                            var identityProvider = new IdentityProvider(new EntityId(samlPeerEntityId), saml.SPOptions)
                            {
                                AllowUnsolicitedAuthnResponse = true
                            };

                            identityProvider.ReadMetadata(metadata);

                            saml.IdentityProviders.Add(identityProvider);
                        }

                        saml.SignInScheme  = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                        saml.SignOutScheme = IdentityServerConstants.SignoutScheme;

                        saml.Notifications.AcsCommandResultCreated = (commandResult, response) =>
                        {
                            if (response.Status == Saml2StatusCode.Success)
                            {
                                var identity = commandResult.Principal.Identity as ClaimsIdentity;

                                if (identity != null)
                                {
                                    Claim issuerClaim;

                                    while ((issuerClaim = identity.Claims.FirstOrDefault(claim => string.Equals(claim.Type, "issuer"))) != null)
                                    {
                                        identity.RemoveClaim(issuerClaim);
                                    }

                                    identity.AddClaim(new Claim("issuer", response.Issuer.Id));
                                }
                            }
                        };
                    }
                });
            }
        }
        private static void ConfigureExternalAuthenticationServices(IServiceCollection services, TenantsBuilder tenantsBuilder, IConfiguration configuration)
        {
            var authenticationBuilder = services.AddAuthentication();

            if (tenantsBuilder != null)
            {
                ConfigureExternalAuthenticationServices(authenticationBuilder, tenantsBuilder, configuration);
            }
        }
        private static void ConfigureJwtAuthentication(IServiceCollection services, TenantsBuilder tenantsBuilder, IConfiguration configuration)
        {
            var authenticationBuilder = services.AddAuthentication();

            JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

            string[] requiredScopesSplit = null;

            string requiredScopes = configuration["Identity:Jwt:RequiredScopes"];

            if (!string.IsNullOrEmpty(requiredScopes))
            {
                requiredScopesSplit = requiredScopes.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);

                if (requiredScopesSplit.Length == 0)
                {
                    requiredScopesSplit = null;
                }
            }

            if (tenantsBuilder == null)
            {
                string defaultClientAuthority = configuration[$"Identity:DefaultClient:Authority"];
                if (string.IsNullOrEmpty(defaultClientAuthority))
                {
                    throw new Exception("Identity default client authority string is empty");
                }

                string defaultClientAudience = configuration[$"Identity:DefaultClient:Audience"];
                if (string.IsNullOrEmpty(defaultClientAudience))
                {
                    throw new Exception("Identity default client audience string is empty");
                }

                authenticationBuilder.AddJwtBearer(IdentityCoreConstants.JwtScheme, jwt =>
                {
                    jwt.RequireHttpsMetadata = true;

                    jwt.Authority = defaultClientAuthority;
                    jwt.Audience  = defaultClientAudience;

                    jwt.TokenValidationParameters = new TokenValidationParameters()
                    {
                        ClockSkew             = TimeSpan.FromMinutes(5),
                        RequireSignedTokens   = true,
                        RequireExpirationTime = true,
                        ValidateLifetime      = true,
                        // audience validation will be done via scope, as recommended in
                        // https://github.com/IdentityServer/IdentityServer4/issues/127
                        ValidateAudience = false,
                        ValidateIssuer   = true,
                        ValidIssuer      = defaultClientAuthority
                    };
                });

                services.AddAuthorization(options =>
                {
                    options.AddPolicy(IdentityCoreConstants.JwtPolicy, policy =>
                    {
                        policy.AuthenticationSchemes.Add(IdentityCoreConstants.JwtScheme);

                        if (requiredScopesSplit != null)
                        {
                            policy.RequireAssertion(handler =>
                            {
                                return(CheckScopes(handler.User, requiredScopesSplit));
                            });
                        }

                        policy.RequireAuthenticatedUser();
                    });
                });
            }
            else
            {
                authenticationBuilder.AddJwtBearer(IdentityCoreConstants.JwtScheme, jwt =>
                {
                    jwt.RequireHttpsMetadata = true;
                });

                tenantsBuilder.WithPerTenantOptions <JwtBearerOptions>((jwt, tenantInfo, name) =>
                {
                    jwt.Authority = tenantInfo.DeveloperAuthority;
                    jwt.Audience  = tenantInfo.DeveloperAudience;

                    var tokenValidationParameters = new TokenValidationParameters()
                    {
                        ClockSkew             = TimeSpan.FromMinutes(5),
                        RequireSignedTokens   = true,
                        RequireExpirationTime = true,
                        ValidateLifetime      = true,
                        // audience validation will be done via scope, as recommended in
                        // https://github.com/IdentityServer/IdentityServer4/issues/127
                        ValidateAudience = false,
                        ValidateIssuer   = true,
                        ValidIssuer      = tenantInfo.DeveloperAuthority
                    };

                    var developerCertificate = tenantInfo.GetDeveloperCertificate();

                    if (developerCertificate != null)
                    {
                        // if we cannot resolve it from some discovery endpoint

                        tokenValidationParameters.IssuerSigningKey = new X509SecurityKey(developerCertificate);
                    }

                    jwt.TokenValidationParameters = tokenValidationParameters;
                });

                ConfigureExternalAuthenticationServices(authenticationBuilder, tenantsBuilder, configuration);

                services.AddAuthorization(options =>
                {
                    options.AddPolicy(IdentityCoreConstants.JwtPolicy, policy =>
                    {
                        policy.AuthenticationSchemes.Add(IdentityCoreConstants.JwtScheme);

                        policy.Requirements.Add(new ClientDeveloperUuidRequirement());

                        if (requiredScopesSplit != null)
                        {
                            policy.RequireAssertion(handler =>
                            {
                                return(CheckScopes(handler.User, requiredScopesSplit));
                            });
                        }

                        policy.RequireAuthenticatedUser();
                    });
                });

                services.AddSingleton <IAuthorizationHandler, ClientDeveloperUuidRequirementHandler>();
            }
        }
Beispiel #7
0
        private static void ConfigureExternalAuthenticationServices(AuthenticationBuilder authenticationBuilder, TenantsBuilder tenantsBuilder, IConfiguration configuration)
        {
            if (tenantsBuilder != null)
            {
                authenticationBuilder.AddOpenIdConnect(IdentityCoreConstants.ExternalOidcScheme, openIdConnect =>
                {
                    // will be configured dynamically

                    openIdConnect.ClientId  = "N/A";
                    openIdConnect.Authority = "https://nowhere.nowhere";
                });

                tenantsBuilder.WithPerTenantOptions <OpenIdConnectOptions>((openIdConnect, tenantInfo) =>
                {
                    if (string.Equals(tenantInfo.ExternalAuthenticationMethod, TenantConstants.ExternalAuthenticationMethodOidc))
                    {
                        var oidcClientId     = tenantInfo.OidcClientId;
                        var oidcClientSecret = tenantInfo.OidcClientSecret;
                        var oidcEndpointUrl  = tenantInfo.OidcEndpointUrl;

                        openIdConnect.SignInScheme  = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                        openIdConnect.SignOutScheme = IdentityServerConstants.SignoutScheme;

                        openIdConnect.Authority = oidcEndpointUrl;

                        openIdConnect.ClientId     = oidcClientId;
                        openIdConnect.ClientSecret = oidcClientSecret;

                        // make sure we get user group membership information

                        openIdConnect.GetClaimsFromUserInfoEndpoint = true;

                        openIdConnect.Scope.Add("openid");
                        openIdConnect.Scope.Add("email");
                        openIdConnect.Scope.Add("phone");
                        openIdConnect.Scope.Add("profile");
                    }
                });

                authenticationBuilder.AddSaml2(IdentityCoreConstants.ExternalSamlScheme, saml =>
                {
                    // will be configured dynamically
                });

                tenantsBuilder.WithPerTenantOptions <Saml2Options>((saml, tenantInfo) =>
                {
                    if (string.Equals(tenantInfo.ExternalAuthenticationMethod, TenantConstants.ExternalAuthenticationMethodSaml))
                    {
                        var samlEntityId         = tenantInfo.SamlEntityId;
                        var samlMetadataLocation = tenantInfo.SamlMetadataLocation;

                        var samlProviderUrl = tenantInfo.SamlProviderUrl;

                        var samlCertificate = tenantInfo.SamlCertificate;

                        saml.SPOptions.EntityId = new EntityId(samlEntityId);

                        if (tenantInfo.SamlAllowWeakSigningAlgorithm)
                        {
                            // OK this is less secure, but sometimes we're having trouble
                            // e.g. with SSO Circle that still uses SHA1

                            saml.SPOptions.MinIncomingSigningAlgorithm = "http://www.w3.org/2000/09/xmldsig#rsa-sha1";
                        }

                        saml.IdentityProviders.Add(
                            new IdentityProvider(new EntityId(samlProviderUrl), saml.SPOptions)
                        {
                            LoadMetadata     = true,
                            MetadataLocation = samlMetadataLocation
                        }
                            );

                        if (samlCertificate != null)
                        {
                            saml.SPOptions.ServiceCertificates.Add(new ServiceCertificate()
                            {
                                Certificate = samlCertificate,
                                Use         = CertificateUse.Signing
                            });
                        }

                        saml.SignInScheme  = IdentityServerConstants.ExternalCookieAuthenticationScheme;
                        saml.SignOutScheme = IdentityServerConstants.SignoutScheme;
                    }
                });
            }
        }