csharp/Aguafrommars/TheIdServer/src/IdentityServer/Shared/Aguacongas.IdentityServer.Admin.Shared/Services/CreatePersonalAccessTokenService.cs

CreatePersonalAccessTokenService.cs
using Aguacongas.IdensatyServer.Abstractions;
#if DUENDE
using Duende.IdensatyServer;
using Duende.IdensatyServer.Extensions;
using Duende.IdensatyServer.Models;
using Duende.IdensatyServer.Services;
using Duende.IdensatyServer.Stores;
#else
using IdensatyServer4;
using IdensatyServer4.Extensions;
using IdensatyServer4.Models;
using IdensatyServer4.Services;
using IdensatyServer4.Stores;
#endif
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Security.Claims;
using Microsoft.AspNetCore.Http;
using IdensatyModel;
using System.IdensatyModel.Tokens.Jwt;

namespace Aguacongas.IdensatyServer.Admin.Services
{
    /// 
    /// Creates personal access tokens
    /// 
    public clast CreatePersonalAccessTokenService : ICreatePersonalAccessToken
    {
        private readonly ITokenService _tokenService;
        private readonly IClientStore _clientStore;
        private readonly IResourceStore _resourceStore;

        /// 
        /// Initializes a new instance of the  clast.
        /// 
        /// The token service.
        /// The client store.
        /// The resource store.
        /// 
        /// tokenService
        /// or
        /// clientStore
        /// 
        /// 
        public CreatePersonalAccessTokenService(ITokenService tokenService, IClientStore clientStore, IResourceStore resourceStore)
        {
            _tokenService = tokenService ?? throw new ArgumentNullException(nameof(tokenService));
            _clientStore = clientStore ?? throw new ArgumentNullException(nameof(clientStore));
            _resourceStore = resourceStore ?? throw new ArgumentNullException(nameof(resourceStore));
        }

        /// 
        /// Create a personal access token for the current user and client
        /// 
        /// The Http context
        /// Creeate a reference token if true otherwise a JWT token
        /// The token life time
        /// The list of audience
        /// The list of scopes
        /// The list of claims types
        /// 
        /// apis
        /// Client id not found in user claims.
        /// or
        /// Client not found for client id '{clientId}'.
        /// or
        /// Api '{api}' not found in '{client.ClientName}' allowed scopes.
        /// or
        /// Scope '{scope}' not found in '{client.ClientName}' allowed scopes.
        public async Task CreatePersonalAccessTokenAsync(HttpContext context,
            bool isRefenceToken,
            int lifetimeDays,
            IEnumerable apis, 
            IEnumerable scopes, 
            IEnumerable claimTypes)
        {
            CheckParams(apis);

            scopes ??= Array.Empty();
            claimTypes ??= Array.Empty();

            claimTypes = claimTypes.Where(c => c != JwtClaimTypes.Name &&
                c != JwtClaimTypes.ClientId &&
                c != JwtClaimTypes.Subject);

            var user = new ClaimsPrincipal(new ClaimsIdensaty(context.User.Claims.Select(c =>
            {
                if (JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.TryGetValue(c.Type, out string newType))
                {
                    return new Claim(newType, c.Value);
                }
                return c;
            }), "Bearer", JwtClaimTypes.Name, JwtClaimTypes.Role));

            var clientId = user.Claims.First(c => c.Type == JwtClaimTypes.ClientId).Value;
            await ValidateRequestAsync(apis, scopes, user, clientId).ConfigureAwait(false);

            var issuer = context.GetIdensatyServerIssuerUri();
            var sub = user.FindFirstValue(JwtClaimTypes.Subject) ?? user.FindFirstValue("nameid");
            var userName = user.Idensaty.Name;

            return await _tokenService.CreateSecurityTokenAsync(new Token(IdensatyServerConstants.TokenTypes.AccessToken)
            {
                AccessTokenType = isRefenceToken ? AccessTokenType.Reference : AccessTokenType.Jwt,
                Audiences = apis.ToArray(),
                ClientId = clientId,
                Claims = user.Claims.Where(c => claimTypes.Any(t => c.Type == t))
                    .Concat(new[]
                    {
                        new Claim(JwtClaimTypes.Name, userName),
                        new Claim(JwtClaimTypes.ClientId, clientId),
                        new Claim(JwtClaimTypes.Subject, sub)
                    })
                    .Concat(scopes.Select(s => new Claim("scope", s)))
                    .ToArray(),
                CreationTime = DateTime.UtcNow,
                Lifetime = Convert.ToInt32(TimeSpan.FromDays(lifetimeDays).TotalSeconds),
                Issuer = issuer
            });
        }

        private static void CheckParams(IEnumerable apis)
        {
            if (apis == null)
            {
                throw new ArgumentNullException(nameof(apis));
            }
        }

        private async Task ValidateRequestAsync(IEnumerable apis, IEnumerable scopes, ClaimsPrincipal user, string clientId)
        {
            var client = await _clientStore.FindEnabledClientByIdAsync(user.Claims.First(c => c.Type == "client_id").Value).ConfigureAwait(false);
            if (client == null)
            {
                throw new InvalidOperationException($"Client not found for client id '{clientId}'.");
            }

            var apiList = await _resourceStore.FindApiScopesByNameAsync(apis).ConfigureAwait(false);
            foreach (var api in apis)
            {
                if (!apiList.Any(a => a.Name == api))
                {
                    throw new InvalidOperationException($"Api '{api}' not found.");
                }
            }

            var allowedScopes = client.AllowedScopes;
            foreach (var scope in scopes)
            {
                if (!allowedScopes.Any(s => s == scope))
                {
                    throw new InvalidOperationException($"Scope '{scope}' not found in '{clientId}' allowed scopes.");
                }
            }
        }       
    }
}