InvalidCastException on an LDAP implementation in ASP Boilerplate

ぐ巨炮叔叔 提交于 2020-06-17 09:08:50

问题


Good day all, recently had to implement an Active Directory for our application and we have come across an InvalidCastException when trying to log into the site for the past few days I have been at our wits end trying to figure out what is causing this error. The code does seems to run fine as there are no errors when loading

LdapAuthentication.cs:

 public abstract class LdapAuthenticationSource<TTenant, TUser> : DefaultExternalAuthenticationSource<TTenant, TUser>, ITransientDependency
        where TTenant : AbpTenant<TUser>
        where TUser : AbpUserBase, new()
    {

        /// <summary>
        /// LDAP
        /// </summary>
        public const string SourceName = "LDAP";

        public override string Name
        {
            get { return SourceName; }
        }

        private readonly ILdapSettings _settings;
        private readonly IAbpZeroLdapModuleConfig _ldapModuleConfig;

        protected LdapAuthenticationSource(ILdapSettings settings, IAbpZeroLdapModuleConfig ldapModuleConfig)
        {
            _settings = settings;
            _ldapModuleConfig = ldapModuleConfig;
        }

        /// <inheritdoc/>
        public override async Task<bool> TryAuthenticateAsync(string userNameOrEmailAddress, string plainPassword, TTenant tenant)
        {
            if (!_ldapModuleConfig.IsEnabled || !(await _settings.GetIsEnabled(GetIdOrNull(tenant))))
            {
                return false;
            }

            using (var principalContext = await CreatePrincipalContext(tenant))
            {
                return ValidateCredentials(principalContext, userNameOrEmailAddress, plainPassword);
            }
        }

        /// <inheritdoc/>
        public async override Task<TUser> CreateUserAsync(string userNameOrEmailAddress, TTenant tenant)
        {
            await CheckIsEnabled(tenant);

            var user = await base.CreateUserAsync(userNameOrEmailAddress, tenant);

            using (var principalContext = await CreatePrincipalContext(tenant))
            {
                var userPrincipal = UserPrincipal.FindByIdentity(principalContext, userNameOrEmailAddress);

                if (userPrincipal == null)
                {
                    throw new AbpException("Unknown LDAP user: " + userNameOrEmailAddress);
                }

                UpdateUserFromPrincipal(user, userPrincipal);

                user.IsEmailConfirmed = true;
                user.IsActive = true;

                return user;
            }
        }

        public async override Task UpdateUserAsync(TUser user, TTenant tenant)
        {
            await CheckIsEnabled(tenant);

            await base.UpdateUserAsync(user, tenant);

            using (var principalContext = await CreatePrincipalContext(tenant))
            {
                var userPrincipal = UserPrincipal.FindByIdentity(principalContext, user.UserName);

                if (userPrincipal == null)
                {
                    throw new AbpException("Unknown LDAP user: " + user.UserName);
                }

                UpdateUserFromPrincipal(user, userPrincipal);
            }
        }

        protected virtual bool ValidateCredentials(PrincipalContext principalContext, string userNameOrEmailAddress, string plainPassword)
        {
            return principalContext.ValidateCredentials(userNameOrEmailAddress, plainPassword, ContextOptions.Negotiate);
        }

        protected virtual void UpdateUserFromPrincipal(TUser user, UserPrincipal userPrincipal)
        {
            user.UserName = userPrincipal.SamAccountName;
            user.Name = userPrincipal.GivenName;
            user.Surname = userPrincipal.Surname;
            user.EmailAddress = userPrincipal.EmailAddress;

            if (userPrincipal.Enabled.HasValue)
            {
                user.IsActive = userPrincipal.Enabled.Value;
            }
        }

        protected virtual async Task<PrincipalContext> CreatePrincipalContext(TTenant tenant)
        {
            var tenantId = GetIdOrNull(tenant);

            return new PrincipalContext(
                await _settings.GetContextType(tenantId),
                ConvertToNullIfEmpty(await _settings.GetDomain(tenantId)),
                ConvertToNullIfEmpty(await _settings.GetContainer(tenantId)),
                ConvertToNullIfEmpty(await _settings.GetUserName(tenantId)),
                ConvertToNullIfEmpty(await _settings.GetPassword(tenantId))
                );
        }

        private async Task CheckIsEnabled(TTenant tenant)
        {
            if (!_ldapModuleConfig.IsEnabled)
            {
                throw new AbpException("Ldap Authentication module is disabled globally!");
            }

            var tenantId = GetIdOrNull(tenant);
            if (!await _settings.GetIsEnabled(tenantId))
            {
                throw new AbpException("Ldap Authentication is disabled for given tenant (id:" + tenantId + ")! You can enable it by setting '" + LdapSettingNames.IsEnabled + "' to true");
            }
        }

        private static int? GetIdOrNull(TTenant tenant)
        {
            return tenant == null
                ? (int?)null
                : tenant.Id;
        }

        private static string ConvertToNullIfEmpty(string str)
        {
            return str.IsNullOrWhiteSpace()
                ? null
                : str;
        }

    }
}

LdapSettings.cs

public class LdapSettings: ILdapSettings, ITransientDependency
    {

        protected ISettingManager SettingManager { get; }

        public LdapSettings(ISettingManager settingManager)
        {
            SettingManager = settingManager;
        }

        public virtual Task<bool> GetIsEnabled(int? tenantId)
        {
            return tenantId.HasValue
                ? SettingManager.GetSettingValueForTenantAsync<bool>(AppSettingNames.IsEnabled, tenantId.Value)
                : SettingManager.GetSettingValueForApplicationAsync<bool>(AppSettingNames.IsEnabled);
        }

        public virtual async Task<ContextType> GetContextType(int? tenantId)
        {
            return tenantId.HasValue
                ? (await SettingManager.GetSettingValueForTenantAsync(AppSettingNames.ContextType, tenantId.Value)).ToEnum<ContextType>()
                : (await SettingManager.GetSettingValueForApplicationAsync(AppSettingNames.ContextType)).ToEnum<ContextType>();
        }

        public virtual Task<string> GetContainer(int? tenantId)
        {
            return tenantId.HasValue
                ? SettingManager.GetSettingValueForTenantAsync(AppSettingNames.Container, tenantId.Value)
                : SettingManager.GetSettingValueForApplicationAsync(AppSettingNames.Container);
        }

        public virtual Task<string> GetDomain(int? tenantId)
        {
            return tenantId.HasValue
                ? SettingManager.GetSettingValueForTenantAsync(AppSettingNames.Domain, tenantId.Value)
                : SettingManager.GetSettingValueForApplicationAsync(AppSettingNames.Domain);
        }

        public virtual Task<string> GetUserName(int? tenantId)
        {
            return tenantId.HasValue
                ? SettingManager.GetSettingValueForTenantAsync(AppSettingNames.UserName, tenantId.Value)
                : SettingManager.GetSettingValueForApplicationAsync(AppSettingNames.UserName);
        }

        public virtual Task<string> GetPassword(int? tenantId)
        {
            return tenantId.HasValue
                ? SettingManager.GetSettingValueForTenantAsync(AppSettingNames.Password, tenantId.Value)
                : SettingManager.GetSettingValueForApplicationAsync(AppSettingNames.Password);
        }
    }
}

CoreModule.cs

    [DependsOn(typeof(AbpZeroLdapModule))]
    public class TestApp2020CoreModule : AbpModule
    {
        public override void PreInitialize()
        {

            Configuration.Auditing.IsEnabledForAnonymousUsers = true;

            // Declare entity types
            Configuration.Modules.Zero().EntityTypes.Tenant = typeof(Tenant);
            Configuration.Modules.Zero().EntityTypes.Role = typeof(Role);
            Configuration.Modules.Zero().EntityTypes.User = typeof(User);

            TestApp2020LocalizationConfigurer.Configure(Configuration.Localization);

            // Enable this line to create a multi-tenant application.
            Configuration.MultiTenancy.IsEnabled = TestApp2020Consts.MultiTenancyEnabled;

            // IocManager.Register<ILdapSettings, MyLdapSettings>(); //change default setting source
            IocManager.Register<ILdapSettings, LdapSettings>();
            Configuration.Modules.ZeroLdap().Enable(typeof(LdapSettings));
            // Configure roles
            AppRoleConfig.Configure(Configuration.Modules.Zero().RoleManagement);

            Configuration.Settings.Providers.Add<AppSettingProvider>();
        }

        public override void Initialize()
        {
            IocManager.RegisterAssemblyByConvention(typeof(TestApp2020CoreModule).GetAssembly());
        }

        public override void PostInitialize()
        {
            IocManager.Resolve<AppTimes>().StartupTime = Clock.Now;
            SettingManager settingsManager = IocManager.Resolve<SettingManager>();
            settingsManager.ChangeSettingForApplication(AppSettingNames.IsEnabled, "true");
        }
    }
}

The application loads but the an error here prevents logging in

And this is what is showing in the logs

Any help would be greatly appreciated thanks.


回答1:


tl;dr;

Your problem is in CoreModule.cs

Configuration.Modules.ZeroLdap().Enable(typeof(LdapSettings));

According to the docs the Enable method takes an auth source type as a parameter, but you've passed a settings type. Change it to use LdapAuthenticationSource instead.

How you could have figured this out

The error message says there was a failed cast from LdapSettings to IExternalAuthenticationSource. That's strange because there's no reason your code should be trying to cast between those types!

If you look down the stack, you can see the error is happening inside your TokenAuthController's Authenticate / GetLoginResultAsync method. You could check the code in that method, you probably won't find any direct mention of either LdapSettings or IExternalAuthenticationSource. You will however find a call to ApbLoginManger.LoginAsync. Follow that back up the stack and you can see ApbLoginManager uses IoC to resolve an auth source, and the exception is thrown in the ResolveAsDisposable method of IoC!

It gets a bit trickier here. The bug is presenting itself deep inside ABP and the IoC framework. It's possible there's an obscure bug in one of those frameworks causing the problem, but it's much more likely to be a configuration error. That means the next step is to look through your configuration code for anywhere you may have told the IoC framework to use LdapSettings for an IExternalAuthenticationSource.

All the config happens in the CoreModule.cs file, so let's look there. You have a call to

IocManager.Register<ILdapSettings, LdapSettings>();

which seems to properly register LdapSettings for ILdapSettings. The only other call to IocManager is the standard call to IocManager.RegisterAssemblyByConvention in the Initialize method. No obvious misconfiguration there. There is however a call that uses typeof(LdapSettings) as a parameter.

Configuration.Modules.ZeroLdap().Enable(typeof(LdapSettings));

It's not obvious from the method call what that parameter is for, and LdapSettings is definitely a reasonable possibility for the correct parameter. However, there are two good reasons to look into this method further.

  1. Because the parameter is a Type, there won't be compile time checking if we've passed an appropriate type.
  2. LdapSettings is part of the actual exception so any method that uses it is suspect

That brings us to the documentation where we see the problem. We need to pass the auth source, not the settings.

Why the code "seems to run fine"

The configuration used a Type parameter instead of generics. That means there's no compile time checking if you've passed a valid type (as mentioned above). The program compile and run fine until you try to use the misconfigured code. In this case, the misconifguration won't be used until you try to login, which triggers the IoC resolver, which accesses the config, and throws the error.



来源:https://stackoverflow.com/questions/62189660/invalidcastexception-on-an-ldap-implementation-in-asp-boilerplate

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!