/// <summary>
        /// Configures per-tenant authentication behavior.
        /// </summary>
        /// <param name="config">Authentication options config</param>
        /// <returns>The same MultiTenantBuilder passed into the method.</returns>
        public static FinbuckleMultiTenantBuilder <TTenantInfo> WithPerTenantAuthentication <TTenantInfo>(this FinbuckleMultiTenantBuilder <TTenantInfo> builder, Action <MultiTenantAuthenticationOptions> config)
            where TTenantInfo : class, ITenantInfo, new()
        {
            builder.WithPerTenantAuthenticationCore(config);
            builder.WithPerTenantAuthenticationConventions();
            builder.WithRemoteAuthenticationCallbackStrategy();

            return(builder);
        }
예제 #2
0
        /// <summary>
        /// Configures authentication options to enable per-tenant behavior.
        /// </summary>
        /// <returns>The same MultiTenantBuilder passed into the method.</returns>
        public static FinbuckleMultiTenantBuilder <TTenantInfo> WithPerTenantAuthentication <TTenantInfo>(this FinbuckleMultiTenantBuilder <TTenantInfo> builder)
            where TTenantInfo : class, ITenantInfo, new()
        {
            builder.Services.ConfigureAll <CookieAuthenticationOptions>(options =>
            {
                // Validate that claimed tenant matches current tenant.
                var origOnValidatePrincipal        = options.Events.OnValidatePrincipal;
                options.Events.OnValidatePrincipal = async context =>
                {
                    await origOnValidatePrincipal(context);

                    // Skip if no principal or bypass set
                    if (context.Principal == null || context.HttpContext.Items.Keys.Contains($"{Constants.TenantToken}__bypass_validate_principle__"))
                    {
                        return;
                    }

                    var currentTenant = context.HttpContext.GetMultiTenantContext <TTenantInfo>()?.TenantInfo?.Identifier;

                    // If no current tenant and no tenant claim then OK
                    if (currentTenant == null && !context.Principal.Claims.Any(c => c.Type == Constants.TenantToken))
                    {
                        return;
                    }

                    // Does a tenant claim for the principle match the current tenant?
                    if (!context.Principal.Claims.Where(c => c.Type == Constants.TenantToken && String.Equals(c.Value, currentTenant, StringComparison.OrdinalIgnoreCase)).Any())
                    {
                        context.RejectPrincipal();
                    }
                };

                // Set the tenant claim when signing in.
                var origOnSigningIn        = options.Events.OnSigningIn;
                options.Events.OnSigningIn = async context =>
                {
                    await origOnSigningIn(context);

                    if (context.Principal == null)
                    {
                        return;
                    }

                    var identity      = (ClaimsIdentity)context.Principal.Identity;
                    var currentTenant = context.HttpContext.GetMultiTenantContext <TTenantInfo>()?.TenantInfo?.Identifier;

                    if (currentTenant != null &&
                        !identity.Claims.Where(c => c.Type == Constants.TenantToken && c.Value == currentTenant).Any())
                    {
                        identity.AddClaim(new Claim(Constants.TenantToken, currentTenant));
                    }
                };
            });

            // Set per-tenant cookie options by convention.
            builder.WithPerTenantOptions <CookieAuthenticationOptions>((options, tc) =>
            {
                var d = (dynamic)tc;
                try { options.LoginPath = ((string)d.CookieLoginPath).Replace(Constants.TenantToken, tc.Identifier); } catch { }
                try { options.LogoutPath = ((string)d.CookieLogoutPath).Replace(Constants.TenantToken, tc.Identifier); } catch { }
                try { options.AccessDeniedPath = ((string)d.CookieAccessDeniedPath).Replace(Constants.TenantToken, tc.Identifier); } catch { }
            });

            builder.WithRemoteAuthenticationCallbackStrategy();

            // We need to "decorate" IAuthenticationService so callbacks so that
            // remote authentication can get the tenant from the authentication
            // properties in the state parameter.
            if (!builder.Services.Where(s => s.ServiceType == typeof(IAuthenticationService)).Any())
            {
                throw new MultiTenantException("WithRemoteAuthenticationCallbackStrategy() must be called after AddAuthorization() in ConfigureServices.");
            }
            builder.Services.DecorateService <IAuthenticationService, MultiTenantAuthenticationService <TTenantInfo> >();

            // Set per-tenant OpenIdConnect options by convention.
            builder.WithPerTenantOptions <OpenIdConnectOptions>((options, tc) =>
            {
                var d = (dynamic)tc;
                try { options.Authority = d.OpenIdConnectAuthority; } catch { }
                try { options.ClientId = d.OpenIdConnectClientId; } catch { }
                try { options.ClientSecret = d.OpenIdConnectClientSecret; } catch { }
            });

            // Replace IAuthenticationSchemeProvider so that the options aren't
            // cached and can be used per-tenant.
            builder.Services.Replace(ServiceDescriptor.Singleton <IAuthenticationSchemeProvider, MultiTenantAuthenticationSchemeProvider>());
            var challengeSchemeProp = typeof(TTenantInfo).GetProperty("ChallengeScheme");

            if (challengeSchemeProp != null && challengeSchemeProp.PropertyType == typeof(string))
            {
                builder.WithPerTenantOptions <AuthenticationOptions>((options, tc)
                                                                     => options.DefaultChallengeScheme = (string)challengeSchemeProp.GetValue(tc) ?? options.DefaultChallengeScheme);
            }

            return(builder);
        }