public async Task OnGetAsync() { NameIdentifier = User.Claims.GetClaimsByType(".externalNamedIdentitier").FirstOrDefault().Value; var key = _oidcPipelineKey.GetOIDCPipeLineKey(); OriginalAuthorizationRequest = await _oidcPipelineStore.GetOriginalIdTokenRequestAsync(key); var queryScopes = (from item in OriginalAuthorizationRequest.Raw where item.Key == "scope" let scopes = item.Value.Split(" ") from cItem in scopes where cItem.StartsWith(ScopeBaseUrl) select cItem).ToList(); ConsentResponseContainers = new List <ConsentResponseContainer>(); ExternalServiceEntities = await _oidcConsentOrchestratorAdmin.GetAllExternalServiceEntitiesAsync(); foreach (var es in ExternalServiceEntities) { var queryScopesService = (from item in queryScopes where item.StartsWith($"{ScopeBaseUrl}{es.Name}") select item).ToList(); if (queryScopesService.Any()) { var discoCache = _consentDiscoveryCacheAccessor.GetConsentDiscoveryCache(es); var doco = await discoCache.GetAsync(); List <string> scopes = null; switch (doco.AuthorizationType) { case Constants.AuthorizationTypes.Implicit: scopes = null; break; case Constants.AuthorizationTypes.Subject: scopes = null; break; case Constants.AuthorizationTypes.SubjectAndScopes: scopes = queryScopes; break; } if (doco.AuthorizationType != Constants.AuthorizationTypes.Implicit) { var request = new ConsentAuthorizeRequest { AuthorizeType = doco.AuthorizationType, Scopes = scopes, Subject = NameIdentifier }; var response = await _consentExternalService.PostAuthorizationRequestAsync(doco, request); var consentResponseContainer = new ConsentResponseContainer() { ExternalServiceEntity = es, DiscoveryDocument = doco, Request = request, Response = response }; ConsentResponseContainers.Add(consentResponseContainer); } else { ConsentResponseContainers.Add(new ConsentResponseContainer { ExternalServiceEntity = es, DiscoveryDocument = doco, Request = null, Response = null }); } } } var finalScopes = (from item in ConsentResponseContainers where item.Response.Authorized == true from scope in item.Response.Scopes select scope).ToList(); var claims = (from item in ConsentResponseContainers where item.Response.Authorized == true && item.Response.Claims != null from claim in item.Response.Claims let c = new ConsentAuthorizeClaim { Type = $"{item.ExternalServiceEntity.Name}.{claim.Type}", Value = claim.Value } select c) .DistinctBy(p => new { p.Type, p.Value }) .ToList(); var customs = (from item in ConsentResponseContainers where item.Response.Authorized == true && item.Response.CustomPayload != null let c = new CustomPayloadContainer { Name = $"{item.ExternalServiceEntity.Name}", CustomPayload = item.Response.CustomPayload } select c).ToList(); var docoTokenService = await _tokenServiceDiscoveryCache.GetAsync(); ArbitraryTokenTokenRequestV2 = new ArbitraryTokenTokenRequestV2() { Address = docoTokenService.TokenEndpoint, ClientId = _FluffyBunny4TokenServiceConfiguration.ClientId, ClientSecret = _FluffyBunny4TokenServiceConfiguration.ClientSecret, Subject = NameIdentifier, Scope = new HashSet <string>(), ArbitraryClaims = new Dictionary <string, List <string> >(), ArbitraryAmrs = new List <string>(), ArbitraryAudiences = new List <string>(), CustomPayload = null }; if (customs.Any()) { Dictionary <string, object> customMap = new Dictionary <string, object>(); foreach (var custom in customs) { customMap[custom.Name] = custom.CustomPayload; } ArbitraryTokenTokenRequestV2.CustomPayload = customMap; } foreach (var item in finalScopes) { ArbitraryTokenTokenRequestV2.Scope.Add(item); } foreach (var claim in claims) { if (!ArbitraryTokenTokenRequestV2.ArbitraryClaims.ContainsKey(claim.Type)) { ArbitraryTokenTokenRequestV2.ArbitraryClaims[claim.Type] = new List <string>(); } ArbitraryTokenTokenRequestV2.ArbitraryClaims[claim.Type].Add(claim.Value); } JsonArbitraryTokenTokenRequestV2 = _serializer.Serialize(ArbitraryTokenTokenRequestV2); var httpClient = new HttpClient(); var tokenPayload = await _fluffyBunnyTokenService.RequestArbitraryTokenAsync(httpClient, ArbitraryTokenTokenRequestV2); }
public async Task ValidateAsync(ExtensionGrantValidationContext context) { var client = context.Request.Client as ClientExtra; _scopedTenantRequestContext.Context.Client = client; _scopedOverrideRawScopeValues.IsOverride = true; var externalServices = await _externalServicesStore.GetExternalServicesAsync(); var form = context.Request.Raw; var error = false; var los = new List <string>(); // make sure nothing is malformed bool err = false; // optional stuff; var accessTokenLifetimeOverride = form.Get(Constants.AccessTokenLifetime); if (!string.IsNullOrWhiteSpace(accessTokenLifetimeOverride)) { int accessTokenLifetime = 0; if (int.TryParse(accessTokenLifetimeOverride, out accessTokenLifetime)) { if (accessTokenLifetime > 0 && accessTokenLifetime <= client.AccessTokenLifetime) { context.Request.AccessTokenLifetime = accessTokenLifetime; } else { los.Add($"{Constants.AccessTokenLifetime}:{accessTokenLifetimeOverride} is out of range."); err = true; } } } error = error || err; err = false; // MUST have subject // ------------------------------------------------------------------- var subjectToken = form.Get(FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken); if (string.IsNullOrWhiteSpace(subjectToken)) { err = true; los.Add($"{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken} is required"); } error = error || err; err = false; // MUST have SubjectTokenType // ------------------------------------------------------------------- var subjectTokenType = form.Get(FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType); if (string.IsNullOrWhiteSpace(subjectTokenType)) { err = true; los.Add($"{FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType} is required"); } error = error || err; err = false; if (error) { context.Result.IsError = true; context.Result.Error = string.Join <string>(" | ", los); _logger.LogError($"context.Result.Error"); return; } DateTime tokenIssuedAtTime; List <Claim> claims; var subject = ""; switch (subjectTokenType) { case FluffyBunny4.Constants.TokenExchangeTypes.AccessToken: if (subjectToken.Contains('.')) { err = true; los.Add($"failed to validate, not a reference_token: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}"); } var validatedResultAccessToken = await _tokenValidator.ValidateAccessTokenAsync(subjectToken); if (validatedResultAccessToken.IsError) { err = true; los.Add($"failed to validate: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}"); } subject = validatedResultAccessToken.Claims .Where(item => item.Type == JwtClaimTypes.Subject) .Select(item => item.Value) .FirstOrDefault(); if (string.IsNullOrWhiteSpace(subject)) { err = true; los.Add($"subject does not exist: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}"); } claims = validatedResultAccessToken.Claims.ToList(); var amr = claims.FirstOrDefault(claim => claim.Type == JwtClaimTypes.AuthenticationMethod && claim.Value == Constants.GrantType.TokenExchange); if (amr == null) { err = true; los.Add($"failed to validate, missing amr={Constants.GrantType.TokenExchange}: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}"); } var issuedAt = claims.FirstOrDefault(claim => claim.Type == JwtClaimTypes.IssuedAt); if (issuedAt == null) { err = true; los.Add( $"failed to validate, {JwtClaimTypes.IssuedAt} is missing: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}"); } var unixSeconds = Convert.ToInt64(issuedAt.Value); DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeSeconds(unixSeconds); tokenIssuedAtTime = dateTimeOffset.UtcDateTime; var referenceTokenStoreGrantStoreHashAccessor = _referenceTokenStore as IGrantStoreHashAccessor; var fixedSubjectToken = subjectToken.Substring(2); var hashKey = referenceTokenStoreGrantStoreHashAccessor.GetHashedKey(fixedSubjectToken); var accessTokenPersitedGrant = await _persistedGrantStore.GetAsync(hashKey); if (accessTokenPersitedGrant == null) { err = true; los.Add($"failed to validate, accessTokenPersitedGrant is missing: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}"); } var accessTokenPersitedGrantExtra = accessTokenPersitedGrant as PersistedGrantExtra; _scopedStorage.AddOrUpdate(Constants.ScopedRequestType.SubjectToken, subjectToken); _scopedStorage.AddOrUpdate(Constants.ScopedRequestType.PersistedGrantExtra, accessTokenPersitedGrantExtra); var requestedScopes = context.Request.RequestedScopes.ToList(); if (!string.IsNullOrWhiteSpace(accessTokenPersitedGrantExtra.RefreshTokenKey)) { _scopedOverrideRawScopeValues.Scopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess); _scopedOptionalClaims.Claims.Add(new Claim(JwtClaimTypes.Scope, IdentityServerConstants.StandardScopes.OfflineAccess)); if (!requestedScopes.Contains(IdentityServerConstants.StandardScopes.OfflineAccess)) { requestedScopes.Add(IdentityServerConstants.StandardScopes.OfflineAccess); } } else { if (requestedScopes.Contains(IdentityServerConstants.StandardScopes.OfflineAccess)) { requestedScopes.Remove(IdentityServerConstants.StandardScopes.OfflineAccess); } } context.Request.RequestedScopes = requestedScopes; break; default: throw new Exception($"not supported: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}"); break; } error = error || err; err = false; if (error) { context.Result.IsError = true; context.Result.Error = string.Join <string>(" | ", los); _logger.LogError($"context.Result.Error"); return; } var finalCustomPayload = new Dictionary <string, object>(); var requestedScopesRaw = form[Constants.Scope].Split(' ').ToList(); var consentAuthorizeResponseTasks = new List <Task <ConsentAuthorizeResponseContainer <PostContext> > >(); var requestedServiceScopes = GetServiceToScopesFromRequest(requestedScopesRaw); foreach (var serviceScopeSet in requestedServiceScopes) { var externalService = await _externalServicesStore.GetExternalServiceByNameAsync(serviceScopeSet.Key); if (externalService == null) { _logger.LogError($"external service: {serviceScopeSet.Key} does not exist"); continue; } var discoCache = await _consentDiscoveryCacheAccessor.GetConsentDiscoveryCacheAsync(serviceScopeSet.Key); var doco = await discoCache.GetAsync(); if (doco.IsError) { // OPINION: If I have a lot of external services it it probably better to let this continue even it if // results in an access_token that is missing this bad service's scopes. _logger.LogError(doco.Error); continue; } List <string> scopes = null; switch (doco.AuthorizationType) { case Constants.AuthorizationTypes.Implicit: scopes = null; break; case Constants.AuthorizationTypes.SubjectAndScopes: scopes = serviceScopeSet.Value; break; } if (doco.AuthorizationType == Constants.AuthorizationTypes.Implicit) { _scopedOverrideRawScopeValues.Scopes.AddRange(serviceScopeSet.Value); } else { var request = new ConsentAuthorizeRequest { AuthorizeType = doco.AuthorizationType, Scopes = scopes, Subject = subject, Requester = new ConsentAuthorizeRequest.ClientRequester() { ClientDescription = client.Description, ClientId = client.ClientId, ClientName = client.ClientName, Namespace = client.Namespace, Tenant = client.TenantName } }; // How to send many requests in parallel in ASP.Net Core // https://www.michalbialecki.com/2018/04/19/how-to-send-many-requests-in-parallel-in-asp-net-core/ var task = _consentExternalService.PostAuthorizationRequestAsync(doco, request, new PostContext() { ServiceScopeSet = serviceScopeSet, DiscoveryDocument = doco }); consentAuthorizeResponseTasks.Add(task); } } var consentAuthorizeResponses = await Task.WhenAll(consentAuthorizeResponseTasks); foreach (var consentAuthorizeResponse in consentAuthorizeResponses) { var response = consentAuthorizeResponse.Response; var serviceScopeSet = consentAuthorizeResponse.Context.ServiceScopeSet; var doco = consentAuthorizeResponse.Context.DiscoveryDocument; if (response.Error != null) { _logger.LogError($"ExternalService:{serviceScopeSet.Key},Error:{response.Error.Message}"); } else if (response.Authorized) { switch (doco.AuthorizationType) { case Constants.AuthorizationTypes.SubjectAndScopes: // make sure no funny business is coming in from the auth call. var serviceRoot = $"{_tokenExchangeOptions.BaseScope}{serviceScopeSet.Key}"; var query = (from item in response.Scopes where item.StartsWith(serviceRoot) select item); _scopedOverrideRawScopeValues.Scopes.AddRange(query); if (response.Claims != null && response.Claims.Any()) { foreach (var cac in response.Claims) { // namespace the claims. _scopedOptionalClaims.Claims.Add(new Claim($"{serviceScopeSet.Key}.{cac.Type}", cac.Value)); } } if (response.CustomPayload != null) { finalCustomPayload.Add(serviceScopeSet.Key, response.CustomPayload); } break; } } _logger.LogInformation($"ExternalService:{serviceScopeSet.Key},Authorized:{response.Authorized}"); } if (finalCustomPayload.Any()) { _scopedOptionalClaims.Claims.Add(new Claim( Constants.CustomPayload, _serializer.Serialize(finalCustomPayload), IdentityServerConstants.ClaimValueTypes.Json)); } claims = new List <Claim> { // in this case we want to preserve that the original came from Constants.GrantType.TokenExchange new Claim(JwtClaimTypes.AuthenticationMethod, Constants.GrantType.TokenExchange) }; context.Result = new GrantValidationResult(subject, GrantType, tokenIssuedAtTime, claims); _scopedStorage.AddOrUpdate(Constants.ScopedRequestType.ExtensionGrantValidationContext, context); _scopedStorage.AddOrUpdate(Constants.ScopedRequestType.OverrideTokenIssuedAtTime, tokenIssuedAtTime); return; }
public async Task ValidateAsync(ExtensionGrantValidationContext context) { var client = context.Request.Client as ClientExtra; _scopedTenantRequestContext.Context.Client = client; var form = context.Request.Raw; var error = false; // make sure nothing is malformed bool err = false; var los = new List <string>(); // optional stuff; var accessTokenLifetimeOverride = form.Get(Constants.AccessTokenLifetime); if (!string.IsNullOrWhiteSpace(accessTokenLifetimeOverride)) { int accessTokenLifetime = 0; if (int.TryParse(accessTokenLifetimeOverride, out accessTokenLifetime)) { if (accessTokenLifetime > 0 && accessTokenLifetime <= client.AccessTokenLifetime) { context.Request.AccessTokenLifetime = accessTokenLifetime; } else { los.Add($"{Constants.AccessTokenLifetime}:{accessTokenLifetimeOverride} is out of range."); err = true; } } } error = error || err; err = false; /* * bool allowOfflineAccess = client.AccessTokenType == AccessTokenType.Jwt; * var accessTokenType = form.Get(Constants.AccessTokenType); * if (!string.IsNullOrWhiteSpace(accessTokenType)) * { * if (string.Compare(accessTokenType, "Reference", true) == 0) * { * allowOfflineAccess = false; * } * else if (string.Compare(accessTokenType, "Jwt", true) == 0) * { * allowOfflineAccess = true; * } * } * * if (!allowOfflineAccess) * { * * context.Request.ValidatedResources.ParsedScopes = context.Request.ValidatedResources.ParsedScopes.Where(item => * item.ParsedName != "offline_access").ToList(); * context.Request.RequestedScopes = context.Request.RequestedScopes.Where(item => * item != "offline_access"); * * } */ // VALIDATE if issuer must be allowed // ------------------------------------------------------------------- var issuer = form.Get("issuer"); if (!string.IsNullOrEmpty(issuer)) { issuer = issuer.ToLower(); var foundIssuer = client.AllowedArbitraryIssuers.FirstOrDefault(x => x == issuer); if (string.IsNullOrWhiteSpace(foundIssuer)) { error = true; los.Add($"issuer:{issuer} is NOT in the AllowedArbitraryIssuers collection."); } } _scopedTenantRequestContext.Context.Issuer = issuer; error = error || err; err = false; // MUST have subject var subjectToken = form.Get(FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken); if (string.IsNullOrWhiteSpace(subjectToken)) { err = true; los.Add($"{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken} is required"); } error = error || err; err = false; var subjectTokenType = form.Get(FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType); if (string.IsNullOrWhiteSpace(subjectTokenType)) { err = true; los.Add($"{FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType} is required"); } else { // check to see if the subjectTokenType is allowed var allowedSubjectTokenType = client.AllowedTokenExchangeSubjectTokenTypes.FirstOrDefault(x => x == subjectTokenType); if (string.IsNullOrWhiteSpace(allowedSubjectTokenType)) { // not here err = true; los.Add($"{subjectTokenType} is NOT allowed for this client"); } } error = error || err; err = false; var requestedScopesRaw = form[Constants.Scope].Split(' ').Distinct().ToList(); var requestedServiceScopes = GetServiceToScopesFromRequest(requestedScopesRaw); _scopedOverrideRawScopeValues.IsOverride = true; var externalServices = new Dictionary <string, ExternalService>(); // validate that this client is allowed to have access to external services foreach (var serviceScopeSet in requestedServiceScopes) { var externalService = await _externalServicesStore.GetExternalServiceByNameAsync(serviceScopeSet.Key); if (externalService == null) { _logger.LogError($"external service: {serviceScopeSet.Key} does not exist"); continue; } if (client.AllowedTokenExchangeExternalServices.Contains(externalService.Name)) { externalServices.Add(externalService.Name, externalService); } else { err = true; var message = $"external_service:{externalService.Name} is not allowed for this client"; los.Add(message); } } error = error || err; err = false; var subject = ""; if (!error) { switch (subjectTokenType) { case FluffyBunny4.Constants.TokenExchangeTypes.IdToken: var validatedResult = await _identityTokenValidator.ValidateIdTokenAsync(subjectToken, _tokenExchangeOptions.AuthorityKey); if (validatedResult.IsError) { err = true; los.Add($"failed to validate id_token"); _logger.LogError($"failed to validate: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}\nError={validatedResult.Error}"); } subject = SubjectFromClaimsPrincipal(validatedResult.User); if (string.IsNullOrWhiteSpace(subject)) { err = true; los.Add($"subject does not exist: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}"); } break; case FluffyBunny4.Constants.TokenExchangeTypes.AccessToken: var validatedResultAccessToken = await _tokenValidator.ValidateAccessTokenAsync(subjectToken); if (validatedResultAccessToken.IsError) { err = true; los.Add($"failed to validate: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.AccessToken}={subjectToken}"); } subject = validatedResultAccessToken.Claims .Where(item => item.Type == JwtClaimTypes.Subject) .Select(item => item.Value) .FirstOrDefault(); if (string.IsNullOrWhiteSpace(subject)) { err = true; los.Add($"subject does not exist: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}"); } break; default: err = true; los.Add($"not supported: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectTokenType}={subjectTokenType},{FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={subjectToken}"); break; } error = error || err; err = false; } if (error) { context.Result.IsError = true; context.Result.Error = string.Join <string>(" | ", los); _logger.LogError($"context.Result.Error"); return; } var consentAuthorizeResponseTasks = new List <Task <ConsentAuthorizeResponseContainer <PostContext> > >(); var finalCustomPayload = new Dictionary <string, object>(); foreach (var serviceScopeSet in requestedServiceScopes) { ExternalService externalService = null; externalServices.TryGetValue(serviceScopeSet.Key, out externalService); if (externalService == null) { _logger.LogError($"external service: {serviceScopeSet.Key} does not exist"); continue; } var discoCache = await _consentDiscoveryCacheAccessor.GetConsentDiscoveryCacheAsync(serviceScopeSet.Key); var doco = await discoCache.GetAsync(); if (doco.IsError) { // OPINION: If I have a lot of external services it it probably better to let this continue even it if // results in an access_token that is missing this bad service's scopes. _logger.LogError(doco.Error); continue; } List <string> scopes = null; switch (doco.AuthorizationType) { case Constants.AuthorizationTypes.Implicit: scopes = null; break; case Constants.AuthorizationTypes.SubjectAndScopes: scopes = serviceScopeSet.Value; break; } if (doco.AuthorizationType == Constants.AuthorizationTypes.Implicit) { _scopedOverrideRawScopeValues.Scopes.AddRange(serviceScopeSet.Value); } else { var request = new ConsentAuthorizeRequest { AuthorizeType = doco.AuthorizationType, Scopes = scopes, Subject = subject, Requester = new ConsentAuthorizeRequest.ClientRequester() { ClientDescription = client.Description, ClientId = client.ClientId, ClientName = client.ClientName, Namespace = client.Namespace, Tenant = client.TenantName } }; // How to send many requests in parallel in ASP.Net Core // https://www.michalbialecki.com/2018/04/19/how-to-send-many-requests-in-parallel-in-asp-net-core/ var task = _consentExternalService.PostAuthorizationRequestAsync(doco, request, new PostContext() { ServiceScopeSet = serviceScopeSet, DiscoveryDocument = doco }); consentAuthorizeResponseTasks.Add(task); } } var consentAuthorizeResponses = await Task.WhenAll(consentAuthorizeResponseTasks); foreach (var consentAuthorizeResponse in consentAuthorizeResponses) { var response = consentAuthorizeResponse.Response; var serviceScopeSet = consentAuthorizeResponse.Context.ServiceScopeSet; var doco = consentAuthorizeResponse.Context.DiscoveryDocument; if (response.Error != null) { _logger.LogError($"ExternalService:{serviceScopeSet.Key},Error:{response.Error.Message}"); } else if (response.Authorized) { switch (doco.AuthorizationType) { case Constants.AuthorizationTypes.SubjectAndScopes: // make sure no funny business is coming in from the auth call. var serviceRoot = $"{_tokenExchangeOptions.BaseScope}{serviceScopeSet.Key}"; var query = (from item in response.Scopes where item.StartsWith(serviceRoot) select item); _scopedOverrideRawScopeValues.Scopes.AddRange(query); if (response.Claims != null && response.Claims.Any()) { foreach (var cac in response.Claims) { // namespace the claims. _scopedOptionalClaims.Claims.Add(new Claim($"{serviceScopeSet.Key}.{cac.Type}", cac.Value)); } } if (response.CustomPayload != null) { finalCustomPayload.Add(serviceScopeSet.Key, response.CustomPayload); } break; } } } if (finalCustomPayload.Any()) { _scopedOptionalClaims.Claims.Add(new Claim( Constants.CustomPayload, _serializer.Serialize(finalCustomPayload), IdentityServerConstants.ClaimValueTypes.Json)); } _scopedOptionalClaims.Claims.Add(new Claim(JwtClaimTypes.AuthenticationMethod, GrantType)); var claims = new List <Claim> { new Claim(JwtClaimTypes.AuthenticationMethod, GrantType) }; context.Result = new GrantValidationResult(subject, GrantType, claims); _scopedStorage.AddOrUpdate(Constants.ScopedRequestType.ExtensionGrantValidationContext, context); return; }
public async Task <IActionResult> AuthorizeDeviceAsync([FromBody] AuthorizeDeviceRequest data) { // validate client var clientResult = await _clientValidator.ValidateAsync(this.HttpContext); if (clientResult.Client == null) { return(Unauthorized(OidcConstants.TokenErrors.InvalidClient)); } var client = clientResult.Client as ClientExtra; var grantType = clientResult.Client.AllowedGrantTypes.FirstOrDefault(agt => agt == OidcConstants.GrantTypes.DeviceCode); if (grantType == null) { return(Unauthorized(OidcConstants.TokenErrors.InvalidGrant)); } var deviceAuth = await _deviceFlowStore.FindByUserCodeAsync(data.UserCode.Sha256()); if (deviceAuth == null) { return(NotFound($"Invalid user code, Device authorization failure - user code is invalid")); } // VALIDATE if issuer must exist and must be allowed // ------------------------------------------------------------------- var issuer = data.Issuer; if (!string.IsNullOrEmpty(data.Issuer)) { issuer = issuer.ToLower(); var foundIssuer = client.AllowedArbitraryIssuers.FirstOrDefault(x => x == issuer); if (string.IsNullOrWhiteSpace(foundIssuer)) { return(NotFound($"issuer:{issuer} is NOT in the AllowedArbitraryIssuers collection.")); } } // VALIDATE if AccessTokenLifetime must exist and must be allowed // ------------------------------------------------------------------- int lifetime = client.AccessTokenLifetime; if (data.AccessTokenLifetime != null) { var requestedAccessTokenLifetime = (int)data.AccessTokenLifetime; if (requestedAccessTokenLifetime > 0 && requestedAccessTokenLifetime <= client.AccessTokenLifetime) { lifetime = requestedAccessTokenLifetime; } else { return(NotFound($"AccessTokenLifetime:{requestedAccessTokenLifetime} is NOT in range 0-{client.AccessTokenLifetime}.")); } } if (data.AccessTokenLifetime != null) { deviceAuth.Lifetime = (int)data.AccessTokenLifetime; } string subject = ""; try { var validatedResult = await _identityTokenValidator.ValidateIdTokenAsync(data.IdToken, _tokenExchangeOptions.AuthorityKey); if (validatedResult.IsError) { throw new Exception( $"failed to validate: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={data.IdToken}", new Exception(validatedResult.Error)); } subject = SubjectFromClaimsPrincipal(validatedResult.User); if (string.IsNullOrWhiteSpace(subject)) { throw new Exception( $"subject does not exist: {FluffyBunny4.Constants.TokenExchangeTypes.SubjectToken}={data.IdToken}"); } var claims = validatedResult.User.Claims.ToList(); claims.Add(new Claim(JwtClaimTypes.AuthenticationTime, claims.FirstOrDefault(c => c.Type == JwtClaimTypes.IssuedAt).Value)); claims.Add(new Claim(JwtClaimTypes.IdentityProvider, _tokenExchangeOptions.AuthorityKey)); var newClaimsIdentity = new ClaimsIdentity(claims); var subjectPrincipal = new ClaimsPrincipal(newClaimsIdentity); deviceAuth.Subject = subjectPrincipal; var requestedScopes = deviceAuth.RequestedScopes.ToList(); var offlineAccess = requestedScopes.FirstOrDefault(x => x == IdentityServerConstants.StandardScopes.OfflineAccess); var allowedScopes = new List <string>(); var allowedClaims = new List <Claim>(); var finalCustomPayload = new Dictionary <string, object>(); var consentAuthorizeResponseTasks = new List <Task <ConsentAuthorizeResponseContainer <PostContext> > >(); var requestedServiceScopes = GetServiceToScopesFromRequest(deviceAuth.RequestedScopes.ToList()); foreach (var serviceScopeSet in requestedServiceScopes) { var externalService = await _externalServicesStore.GetExternalServiceByNameAsync(serviceScopeSet.Key); if (externalService == null) { continue; } var discoCache = await _consentDiscoveryCacheAccessor.GetConsentDiscoveryCacheAsync(serviceScopeSet.Key); var doco = await discoCache.GetAsync(); if (doco.IsError) { _logger.LogError(doco.Error); continue; } List <string> scopes = null; switch (doco.AuthorizationType) { case Constants.AuthorizationTypes.Implicit: scopes = null; break; case Constants.AuthorizationTypes.SubjectAndScopes: scopes = serviceScopeSet.Value; break; } if (doco.AuthorizationType == Constants.AuthorizationTypes.Implicit) { allowedScopes.AddRange(serviceScopeSet.Value); } else { var request = new ConsentAuthorizeRequest { AuthorizeType = doco.AuthorizationType, Scopes = scopes, Subject = subject, Requester = new ConsentAuthorizeRequest.ClientRequester() { ClientDescription = client.Description, ClientId = client.ClientId, ClientName = client.ClientName, Namespace = client.Namespace, Tenant = client.TenantName } }; // How to send many requests in parallel in ASP.Net Core // https://www.michalbialecki.com/2018/04/19/how-to-send-many-requests-in-parallel-in-asp-net-core/ var task = _consentExternalService.PostAuthorizationRequestAsync(doco, request, new PostContext() { ServiceScopeSet = serviceScopeSet, DiscoveryDocument = doco }); consentAuthorizeResponseTasks.Add(task); } } var consentAuthorizeResponses = await Task.WhenAll(consentAuthorizeResponseTasks); foreach (var consentAuthorizeResponse in consentAuthorizeResponses) { var response = consentAuthorizeResponse.Response; var serviceScopeSet = consentAuthorizeResponse.Context.ServiceScopeSet; var doco = consentAuthorizeResponse.Context.DiscoveryDocument; if (response.Error != null) { _logger.LogError(response.Error.Message); continue; } if (response.Authorized) { switch (doco.AuthorizationType) { case Constants.AuthorizationTypes.SubjectAndScopes: // make sure no funny business is coming in from the auth call. var serviceRoot = $"{_tokenExchangeOptions.BaseScope}{serviceScopeSet.Key}"; var query = (from item in response.Scopes where item.StartsWith(serviceRoot) select item); allowedScopes.AddRange(query); if (response.Claims != null && response.Claims.Any()) { foreach (var cac in response.Claims) { // namespace the claims. allowedClaims.Add(new Claim($"{serviceScopeSet.Key}.{cac.Type}", cac.Value)); } } if (response.CustomPayload != null) { finalCustomPayload.Add(serviceScopeSet.Key, response.CustomPayload); } break; } } } if (finalCustomPayload.Any()) { allowedClaims.Add(new Claim( Constants.CustomPayload, _serializer.Serialize(finalCustomPayload), IdentityServerConstants.ClaimValueTypes.Json)); } deviceAuth.IsAuthorized = true; // deviceAuth.SessionId = sid; if (!string.IsNullOrWhiteSpace(offlineAccess)) { allowedScopes.Add(offlineAccess); } deviceAuth.AuthorizedScopes = allowedScopes; var deviceExtra = _coreMapperAccessor.Mapper.Map <DeviceCodeExtra>(deviceAuth); deviceExtra.AuthorizedClaims = allowedClaims; deviceExtra.Issuer = issuer; deviceExtra.AccessTokenLifetime = lifetime; await _deviceFlowStore.UpdateByUserCodeAsync(data.UserCode.Sha256(), deviceExtra); return(Ok()); } catch (Exception ex) { _logger.LogError(ex, ex.Message); return(StatusCode(500)); } }