I added these classes in the core project.
Error in Error Log :
<br> TokenAuthController' is waiting for the following dependencies: - Service 'DOCUHOLD.EDRMS.MultiTenancy.Cache.ITenantCacheExtensions' which was not registered.
Castle.MicroKernel.Handlers.HandlerException: Can't create component 'DOCUHOLD.EDRMS.Web.Controllers.TokenAuthController' as it has dependencies to be satisfied.
I know I am missing something somewhere but not sure where and how to register it.
public interface ITenantCacheExtensions
{
TenantCacheItemExtensions Get(int tenantId);
TenantCacheItemExtensions Get(string tenancyName);
TenantCacheItemExtensions GetOrNull(string tenancyName);
TenantCacheItemExtensions GetOrNull(int tenantId);
}
public class TenantCacheExtensions<TTenant, TUser> : ITenantCacheExtensions, IEventHandler<EntityChangedEventData<TTenant>>
where TTenant : AbpTenant<TUser>
where TUser : AbpUserBase
{
private readonly ICacheManager _cacheManager;
private readonly IRepository<TTenant> _tenantRepository;
private readonly IUnitOfWorkManager _unitOfWorkManager;
public TenantCacheExtensions(
ICacheManager cacheManager,
IRepository<TTenant> tenantRepository,
IUnitOfWorkManager unitOfWorkManager)
{
_cacheManager = cacheManager;
_tenantRepository = tenantRepository;
_unitOfWorkManager = unitOfWorkManager;
}
public virtual TenantCacheItemExtensions Get(int tenantId)
{
var cacheItem = GetOrNull(tenantId);
if (cacheItem == null)
{
throw new AbpException("There is no tenant with given id: " + tenantId);
}
return cacheItem;
}
public virtual TenantCacheItemExtensions Get(string tenancyName)
{
var cacheItem = GetOrNull(tenancyName);
if (cacheItem == null)
{
throw new AbpException("There is no tenant with given tenancy name: " + tenancyName);
}
return cacheItem;
}
public virtual TenantCacheItemExtensions GetOrNull(string tenancyName)
{
var tenantId = _cacheManager
.GetTenantByNameCache()
.Get(
tenancyName.ToLowerInvariant(),
() => GetTenantOrNull(tenancyName)?.Id
);
return tenantId == null ? null : Get(tenantId.Value);
}
public TenantCacheItemExtensions GetOrNull(int tenantId)
{
return _cacheManager
.GetTenantCache()
.Get(
tenantId,
() =>
{
var tenant = GetTenantOrNull(tenantId);
if (tenant == null)
{
return null;
}
return CreateTenantCacheItem(tenant);
}
);
}
protected virtual TenantCacheItemExtensions CreateTenantCacheItem(TTenant tenant)
{
return new TenantCacheItemExtensions
{
Id = tenant.Id,
Name = tenant.Name,
TenancyName = tenant.TenancyName,
EditionId = tenant.EditionId,
ConnectionString = SimpleStringCipher.Instance.Decrypt(tenant.ConnectionString),
IsActive = tenant.IsActive,
EncryptedAesSessionKey = tenant.EncryptedAesSessionKey,
KeyId = tenant.KeyId
};
}
[UnitOfWork]
protected virtual TTenant GetTenantOrNull(int tenantId)
{
using (_unitOfWorkManager.Current.SetTenantId(null))
{
return _tenantRepository.FirstOrDefault(tenantId);
}
}
[UnitOfWork]
protected virtual TTenant GetTenantOrNull(string tenancyName)
{
using (_unitOfWorkManager.Current.SetTenantId(null))
{
return _tenantRepository.FirstOrDefault(t => t.TenancyName == tenancyName);
}
}
public void HandleEvent(EntityChangedEventData<TTenant> eventData)
{
var existingCacheItem = _cacheManager.GetTenantCache().GetOrDefault(eventData.Entity.Id);
_cacheManager
.GetTenantByNameCache()
.Remove(
existingCacheItem != null
? existingCacheItem.TenancyName.ToLowerInvariant()
: eventData.Entity.TenancyName.ToLowerInvariant()
);
_cacheManager
.GetTenantCache()
.Remove(eventData.Entity.Id);
}
}
[Serializable]
public class TenantCacheItemExtensions
{
public const string CacheName = "AbpZeroTenantCache";
public const string ByNameCacheName = "AbpZeroTenantByNameCache";
public int Id { get; set; }
public string Name { get; set; }
public string TenancyName { get; set; }
public string ConnectionString { get; set; }
public int? EditionId { get; set; }
public bool IsActive { get; set; }
public object CustomData { get; set; }
public byte[] EncryptedAesSessionKey { get; set; }
public string KeyId { get; set; }
}
public static class TenantCacheManagerExtensions
{
public static ITypedCache<int, TenantCacheItemExtensions> GetTenantCache(this ICacheManager cacheManager)
{
return cacheManager.GetCache<int, TenantCacheItemExtensions>(TenantCacheItemExtensions.CacheName);
}
public static ITypedCache<string, int?> GetTenantByNameCache(this ICacheManager cacheManager)
{
return cacheManager.GetCache<string, int?>(Abp.MultiTenancy.TenantCacheItem.ByNameCacheName);
}
}
I have added these four classes :
`public interface ITenantCacheExtensions { TenantCacheItemExtensions Get(int tenantId);
TenantCacheItemExtensions Get(string tenancyName);
TenantCacheItemExtensions GetOrNull(string tenancyName);
TenantCacheItemExtensions GetOrNull(int tenantId);
}``
public class TenantCacheExtensions<TTenant, TUser> : ITenantCacheExtensions, IEventHandler<EntityChangedEventData>
where TTenant : Tenant where TUser : AbpUserBase { private readonly ICacheManager _cacheManager; private readonly IRepository _tenantRepository;
private readonly IUnitOfWorkManager _unitOfWorkManager;
public TenantCacheExtensions(
ICacheManager cacheManager,
IRepository<TTenant> tenantRepository,
IUnitOfWorkManager unitOfWorkManager)
{
_cacheManager = cacheManager;
_tenantRepository = tenantRepository;
_unitOfWorkManager = unitOfWorkManager;
}
public virtual TenantCacheItemExtensions Get(int tenantId)
{
var cacheItem = GetOrNull(tenantId);
if (cacheItem == null)
{
throw new AbpException("There is no tenant with given id: " + tenantId);
}
return cacheItem;
}
public virtual TenantCacheItemExtensions Get(string tenancyName)
{
var cacheItem = GetOrNull(tenancyName);
if (cacheItem == null)
{
throw new AbpException("There is no tenant with given tenancy name: " + tenancyName);
}
return cacheItem;
}
public virtual TenantCacheItemExtensions GetOrNull(string tenancyName)
{
var tenantId = _cacheManager
.GetTenantByNameCache()
.Get(
tenancyName.ToLowerInvariant(),
() => GetTenantOrNull(tenancyName)?.Id
);
return tenantId == null ? null : Get(tenantId.Value);
}
public TenantCacheItemExtensions GetOrNull(int tenantId)
{
return _cacheManager
.GetTenantCache()
.Get(
tenantId,
() =>
{
var tenant = GetTenantOrNull(tenantId);
if (tenant == null)
{
return null;
}
return CreateTenantCacheItem(tenant);
}
);
}
protected virtual TenantCacheItemExtensions CreateTenantCacheItem(TTenant tenant)
{
return new TenantCacheItemExtensions
{
Id = tenant.Id,
Name = tenant.Name,
TenancyName = tenant.TenancyName,
EditionId = tenant.EditionId,
ConnectionString = SimpleStringCipher.Instance.Decrypt(tenant.ConnectionString),
IsActive = tenant.IsActive,
EncryptedAesSessionKey = tenant.EncryptedAesSessionKey,
KeyId = tenant.KeyId
};
}
[UnitOfWork]
protected virtual TTenant GetTenantOrNull(int tenantId)
{
using (_unitOfWorkManager.Current.SetTenantId(null))
{
return _tenantRepository.FirstOrDefault(tenantId);
}
}
[UnitOfWork]
protected virtual TTenant GetTenantOrNull(string tenancyName)
{
using (_unitOfWorkManager.Current.SetTenantId(null))
{
return _tenantRepository.FirstOrDefault(t => t.TenancyName == tenancyName);
}
}
public void HandleEvent(EntityChangedEventData<TTenant> eventData)
{
var existingCacheItem = _cacheManager.GetTenantCache().GetOrDefault(eventData.Entity.Id);
_cacheManager
.GetTenantByNameCache()
.Remove(
existingCacheItem != null
? existingCacheItem.TenancyName.ToLowerInvariant()
: eventData.Entity.TenancyName.ToLowerInvariant()
);
_cacheManager
.GetTenantCache()
.Remove(eventData.Entity.Id);
}
}`
`
public object CustomData { get; set; }
public byte[] EncryptedAesSessionKey { get; set; }
public string KeyId { get; set; }
}`
`public static class TenantCacheManagerExtensions
{
public static ITypedCache<int, TenantCacheItemExtensions> GetTenantCache(this ICacheManager cacheManager)
{
return cacheManager.GetCache<int, TenantCacheItemExtensions>(TenantCacheItemExtensions.CacheName);
}
public static ITypedCache<string, int?> GetTenantByNameCache(this ICacheManager cacheManager)
{
return cacheManager.GetCache<string, int?>(Abp.MultiTenancy.TenantCacheItem.ByNameCacheName);
}
}`
Then I made changes to ITenanctCache in TokenAuthController
`public class TokenAuthController : EDRMSControllerBase { private const string UserIdentifierClaimType = "http://aspnetzero.com/claims/useridentifier";
private readonly LogInManager _logInManager;
private readonly ITenantCacheExtensions _tenantCache;`
When I do this I get Internal Server error, when I change it to ITenantCache it works fine.
Am i missing something ?
Why are you using unique_name claim when it is not a OpenID standard claim :
https://openid.net/specs/openid-connect-core-1_0.html#StandardClaims
Thank You.
No problem, thank you for the response.
Do you mind sharing the code changes you did in the angular side ?
Ok I figured out partially :
` public class OpenIdConnectAuthProviderApi : ExternalAuthProviderApiBase { private readonly IIocResolver _iocResolver; private readonly IExternalAuthConfiguration _externalAuthConfiguration;
public OpenIdConnectAuthProviderApi(IIocResolver iocResolver, IExternalAuthConfiguration externalAuthConfiguration)
{
_iocResolver = iocResolver;
_externalAuthConfiguration = externalAuthConfiguration;
}
public const string Name = "OpenIdConnect";
public async Task<ExternalAuthUserInfo> GetUserInfo( string provider, string token )
{
ExternalLoginProviderInfo providerInfo = _externalAuthConfiguration.Providers.FirstOrDefault<ExternalLoginProviderInfo>((Func<ExternalLoginProviderInfo, bool>)(p => p.Name == provider));
var issuer = providerInfo.AdditionalParams["Authority"];
if (string.IsNullOrEmpty(issuer))
{
throw new ApplicationException("Authentication:OpenId:Issuer configuration is required.");
}
var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
issuer + "/.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever(),
new HttpDocumentRetriever());
var validatedToken = await ValidateToken(token, issuer, configurationManager,providerInfo);
var fullName = validatedToken.Claims.First(c => c.Type == "fullname").Value;
var email = validatedToken.Claims.First(c => c.Type == "unique_name").Value;
var fullNameParts = fullName.Split('.');
return new ExternalAuthUserInfo
{
Provider = Name,
ProviderKey = validatedToken.Subject,
Name = fullNameParts[0],
Surname = fullNameParts[1],
EmailAddress = email
};
}
private async Task<JwtSecurityToken> ValidateToken(
string token,
string issuer,
IConfigurationManager<OpenIdConnectConfiguration> configurationManager,
ExternalLoginProviderInfo providerInfo,
CancellationToken ct = default(CancellationToken))
{
if (string.IsNullOrEmpty(token))
{
throw new ArgumentNullException(nameof(token));
}
if (string.IsNullOrEmpty(issuer))
{
throw new ArgumentNullException(nameof(issuer));
}
var discoveryDocument = await configurationManager.GetConfigurationAsync(ct);
var signingKeys = discoveryDocument.SigningKeys;
var validationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = issuer,
ValidateIssuerSigningKey = true,
IssuerSigningKeys = signingKeys,
ValidateLifetime = true,
ClockSkew = TimeSpan.FromMinutes(5),
ValidateAudience = false
};
var principal = new JwtSecurityTokenHandler().ValidateToken(token, validationParameters, out var rawValidatedToken);
//Validate clientId
if (providerInfo.ClientId != principal.Claims.First(c => c.Type == "aud").Value)
{
throw new ApplicationException("ClientId couldn't verified.");
}
return (JwtSecurityToken)rawValidatedToken;
}
public IDisposableDependencyObjectWrapper<IExternalAuthProviderApi> CreateProviderApi(string provider)
{
ExternalLoginProviderInfo providerInfo = _externalAuthConfiguration.Providers.FirstOrDefault<ExternalLoginProviderInfo>((Func<ExternalLoginProviderInfo, bool>)(p => p.Name == provider));
if (providerInfo == null)
throw new Exception("Unknown external auth provider: " + provider);
IDisposableDependencyObjectWrapper<IExternalAuthProviderApi> dependencyObjectWrapper = IocResolverExtensions.ResolveAsDisposable<IExternalAuthProviderApi>(this._iocResolver, providerInfo.ProviderApiType);
dependencyObjectWrapper.Object.Initialize(providerInfo);
return dependencyObjectWrapper;
}
public override Task<ExternalAuthUserInfo> GetUserInfo(string accessCode)
{
throw new NotImplementedException();
}
}
`
I had to add CreateProviderAPi else it was appearing Null with the code you provided.
Then the name token is not being passed as part of id token, which is why I have contacted Okta to get more details, but I guess we have to add the unique_name as one of the custom claims. SO when i changed the claim from name to fullName and added the custom claim it worked fine. Can you please review the code above and see if i am doing right.
I will update what Okta says about the name claim
Above the token preview which Okta is passing. I have added the name and unique_name claims.
Still I am getting the same error.
regarding the above code, can you provide more details on how to implement it. I have the same question regarding the GetuserInfo as asked by the previous user, how am I going to instatiate the providerinfo.
If this doesn't work I will send you my project.
@cmthomps I am currently trying to add custom claims to make the existing code work but I am struggling to get it right. If in the meantime you are able to use the custom code provided then let me know if it works.