djanosik / Moon.AspNetCore

Extensions for ASP.NET Core framework
MIT License
3 stars 0 forks source link

Does not support injected BasicAuthenticationEvents #1

Open marcrocny opened 6 years ago

marcrocny commented 6 years ago

As an alternative to setting OnSignIn directly, it should be possible to inherit BasicAuthenticationEvents and override SignInAsync(), register the new events-type in ConfigureSerivces and set it on AuthenticationSchemeOptions.EventsType. The security infrastructure will inject it to AuthenticationHandler.Events when the sheme's AuthenticationHandler is resolved by dependency injection (see here).

However, BasicAuthenticationHandler short-circuits this functionality by calling Options.Events.SignInAsync(context). It should be just Events.SignInAsync(context)

djanosik commented 6 years ago

Thank you! Can you send a PR?

marcrocny commented 6 years ago

I'd need a day, unless: is there a way to submit a PR directly without forking?

marcrocny commented 6 years ago

To be honest, I'm hesitant because of the lack of tests on the project. This seems like an innocuous change, but with the level of "magic" in aspnet/Security I'd be worried about breaking something else without some regression testing.

I could probably at least create another "sample" project that exercises the feature. If you're okay with reviewing the change against that and the existing example, then that's okay with me.

primalfear commented 6 years ago

There is a very "Ugly" hack to get this working.

public static class AuthenticationServices
    {
        public static IServiceCollection AddAuthenticationServices(this IServiceCollection services)
        {
            if (services == null)
                return null;

            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();           
            services.AddTransient<IPrincipalProvider, HttpContextPrincipalProvider>();
            services.AddTransient<Authenticator>();

            var provider = services.BuildServiceProvider();
            services.AddAuthentication("Basic")
                .AddBasic(o =>
                {
                    o.Realm = "Sphinx";
                    o.Events = provider.CreateScope().ServiceProvider.GetRequiredService<Authenticator>();
                });

            return services;
        }
    }

public class Authenticator : BasicAuthenticationEvents
    {
        public IRepository Repository { get; }
        public IMemoryCache MemoryCache { get; }

        public Authenticator(IRepository repository) {
            this.Repository = repository ?? throw new ArgumentNullException(nameof(repository));
        }

        public override async Task SignInAsync(BasicSignInContext context) {

            //Check Cache
            var cacheKey = Extensions.CacheExtensions.GenerateCacheKey(context.UserName, context.Password);
            var data = await this.CreateClaimsFor(context);
            if(data == null)
                return;

            var identity = new ClaimsIdentity(data, context.Scheme.Name);
            context.Principal = new ClaimsPrincipal(identity);
        }

        private async Task<Claim[]> CreateClaimsFor(BasicSignInContext context) {

            if (!context.UserName.Equals("apikey", StringComparison.InvariantCultureIgnoreCase))
                return null;

            var intergration = await this.Repository.GetByApiKeyAsync(context.Password);

            if (intergration == null || intergration.Status != Constants.Integration.Status.Active)
                return null;

            if (intergration.ApiKey != context.Password)
                return null;

            var claims = new[] {
                new Claim("sub", intergration.Uuid.ToString("N")),
                new Claim("name", intergration.Name),
                new Claim("status", intergration.Status),
                new Claim("organisation", intergration.Organisation.Uuid.ToString("N"))
            };

            return claims;
        }

    }