From 61c6e5a1fd8bc5d6d90b60dc5b3d0efbdf5e1a6a Mon Sep 17 00:00:00 2001 From: cKey <35512826+colinin@users.noreply.github.com> Date: Tue, 19 May 2020 20:45:13 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E5=AE=A2=E6=88=B7=E7=AB=AF?= =?UTF-8?q?=E5=85=8B=E9=9A=86api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...ntityServerPermissionDefinitionProvider.cs | 1 + .../AbpIdentityServerPermissions.cs | 1 + .../Clients/Dto/ClientCloneInputDto.cs | 75 +++++++ .../Clients/IClientAppService.cs | 2 + .../Localization/Resources/en.json | 1 + .../Localization/Resources/zh-Hans.json | 1 + .../Clients/ClientAppService.cs | 120 +++++++++++ .../Clients/ClientController.cs | 7 + vueJs/src/api/clients.ts | 24 +++ vueJs/src/lang/zh.ts | 9 + .../client/components/ClientCloneForm.vue | 193 ++++++++++++++++++ .../components/ClientPermissionEditForm.vue | 1 - .../admin/identityServer/client/index.vue | 45 +++- 13 files changed, 478 insertions(+), 2 deletions(-) create mode 100644 aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Clients/Dto/ClientCloneInputDto.cs create mode 100644 vueJs/src/views/admin/identityServer/client/components/ClientCloneForm.vue diff --git a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/AbpIdentityServerPermissionDefinitionProvider.cs b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/AbpIdentityServerPermissionDefinitionProvider.cs index d412353b4..a6496de60 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/AbpIdentityServerPermissionDefinitionProvider.cs +++ b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/AbpIdentityServerPermissionDefinitionProvider.cs @@ -14,6 +14,7 @@ namespace LINGYUN.Abp.IdentityServer var clientPermissions = identityServerGroup.AddPermission(AbpIdentityServerPermissions.Clients.Default, L("Permissions:Clients")); clientPermissions.AddChild(AbpIdentityServerPermissions.Clients.Create, L("Permissions:Create")); clientPermissions.AddChild(AbpIdentityServerPermissions.Clients.Update, L("Permissions:Update")); + clientPermissions.AddChild(AbpIdentityServerPermissions.Clients.Clone, L("Permissions:Clone")); clientPermissions.AddChild(AbpIdentityServerPermissions.Clients.Enabled, L("Permissions:Enabled")); clientPermissions.AddChild(AbpIdentityServerPermissions.Clients.Disabled, L("Permissions:Disabled")); clientPermissions.AddChild(AbpIdentityServerPermissions.Clients.Delete, L("Permissions:Delete")); diff --git a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/AbpIdentityServerPermissions.cs b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/AbpIdentityServerPermissions.cs index b825716e4..f932ddcc2 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/AbpIdentityServerPermissions.cs +++ b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/AbpIdentityServerPermissions.cs @@ -10,6 +10,7 @@ public const string Create = Default + ".Create"; public const string Update = Default + ".Update"; public const string Delete = Default + ".Delete"; + public const string Clone = Default + ".Clone"; public const string Enabled = Default + ".Enabled"; public const string Disabled = Default + ".Disabled"; public const string ManagePermissions = Default + ".ManagePermissions"; diff --git a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Clients/Dto/ClientCloneInputDto.cs b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Clients/Dto/ClientCloneInputDto.cs new file mode 100644 index 000000000..26beea5d9 --- /dev/null +++ b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Clients/Dto/ClientCloneInputDto.cs @@ -0,0 +1,75 @@ +using System; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.IdentityServer.Clients; + +namespace LINGYUN.Abp.IdentityServer.Clients +{ + public class ClientCloneInputDto + { + /// + /// 来源客户端标识 + /// + [Required] + public Guid SourceClientId { get; set; } + /// + /// 客户端标识 + /// + [Required] + [StringLength(ClientConsts.ClientIdMaxLength)] + public string ClientId { get; set; } + /// + /// 客户端名称 + /// + [Required] + [StringLength(ClientConsts.ClientNameMaxLength)] + public string ClientName { get; set; } + /// + /// 说明 + /// + [StringLength(ClientConsts.DescriptionMaxLength)] + public string Description { get; set; } + /// + /// 复制客户端授权类型 + /// + public bool CopyAllowedGrantType { get; set; } + /// + /// 复制客户端重定向 Uri + /// + public bool CopyRedirectUri { get; set; } + /// + /// 复制客户端作用域 + /// + public bool CopyAllowedScope { get; set; } + /// + /// 复制客户端声明 + /// + public bool CopyClaim { get; set; } + /// + /// 复制客户端跨域来源 + /// + public bool CopyAllowedCorsOrigin { get; set; } + /// + /// 复制客户端注销重定向 Uri + /// + public bool CopyPostLogoutRedirectUri { get; set; } + /// + /// 客户端 IdP 限制 + /// + public bool CopyPropertie { get; set; } + /// + /// 复制客户端属性 + /// + public bool CopyIdentityProviderRestriction { get; set; } + public ClientCloneInputDto() + { + CopyAllowedCorsOrigin = true; + CopyAllowedGrantType = true; + CopyAllowedScope = true; + CopyClaim = true; + CopyIdentityProviderRestriction = true; + CopyPostLogoutRedirectUri = true; + CopyPropertie = true; + CopyRedirectUri = true; + } + } +} diff --git a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Clients/IClientAppService.cs b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Clients/IClientAppService.cs index 0b76d1bc8..1c774d7c4 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Clients/IClientAppService.cs +++ b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Clients/IClientAppService.cs @@ -14,6 +14,8 @@ namespace LINGYUN.Abp.IdentityServer.Clients Task UpdateAsync(ClientUpdateInputDto clientUpdateInput); + Task CloneAsync(ClientCloneInputDto clientCloneInput); + Task DeleteAsync(ClientGetByIdInputDto clientGetByIdInput); Task AddClaimAsync(ClientClaimCreateDto clientClaimCreate); diff --git a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Localization/Resources/en.json b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Localization/Resources/en.json index d854559a2..c106fa687 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Localization/Resources/en.json +++ b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Localization/Resources/en.json @@ -4,6 +4,7 @@ "Permissions:IdentityServer": "IdentityServer", "Permissions:Create": "Create", "Permissions:Update": "Update", + "Permissions:Clone": "Clone", "Permissions:Enabled": "Enabled", "Permissions:Disabled": "Disabled", "Permissions:Delete": "Delete", diff --git a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Localization/Resources/zh-Hans.json b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Localization/Resources/zh-Hans.json index 1d9082e91..285f91275 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Localization/Resources/zh-Hans.json +++ b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application.Contracts/LINGYUN/Abp/IdentityServer/Localization/Resources/zh-Hans.json @@ -4,6 +4,7 @@ "Permissions:IdentityServer": "IdentityServer管理", "Permissions:Create": "新增", "Permissions:Update": "修改", + "Permissions:Clone": "克隆", "Permissions:Enabled": "启用", "Permissions:Disabled": "停用", "Permissions:Delete": "删除", diff --git a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application/LINGYUN/Abp/IdentityServer/Clients/ClientAppService.cs b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application/LINGYUN/Abp/IdentityServer/Clients/ClientAppService.cs index f6d100596..01af8f7e4 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application/LINGYUN/Abp/IdentityServer/Clients/ClientAppService.cs +++ b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.Application/LINGYUN/Abp/IdentityServer/Clients/ClientAppService.cs @@ -276,6 +276,126 @@ namespace LINGYUN.Abp.IdentityServer.Clients return ObjectMapper.Map(client); } + /// + /// 克隆客户端 + /// + /// + /// 实现参考 Skoruba.IdentityServer4.Admin 项目 + /// https://github.com/skoruba/IdentityServer4.Admin.git + /// + /// + /// + [Authorize(AbpIdentityServerPermissions.Clients.Clone)] + public virtual async Task CloneAsync(ClientCloneInputDto clientCloneInput) + { + var clientIdExists = await ClientRepository.CheckClientIdExistAsync(clientCloneInput.ClientId); + if (clientIdExists) + { + throw new UserFriendlyException(L[AbpIdentityServerErrorConsts.ClientIdExisted, clientCloneInput.ClientId]); + } + var srcClient = await ClientRepository.GetAsync(clientCloneInput.SourceClientId); + + var client = new Client(GuidGenerator.Create(), clientCloneInput.ClientId); + client.ClientName = clientCloneInput.ClientName; + client.Description = clientCloneInput.Description; + client.AbsoluteRefreshTokenLifetime = srcClient.AbsoluteRefreshTokenLifetime; + client.AccessTokenLifetime = srcClient.AccessTokenLifetime; + client.AccessTokenType = srcClient.AccessTokenType; + client.AllowAccessTokensViaBrowser = srcClient.AllowAccessTokensViaBrowser; + client.AllowOfflineAccess = srcClient.AllowOfflineAccess; + client.AllowPlainTextPkce = srcClient.AllowPlainTextPkce; + client.AllowRememberConsent = srcClient.AllowRememberConsent; + client.AlwaysIncludeUserClaimsInIdToken = srcClient.AlwaysIncludeUserClaimsInIdToken; + client.AlwaysSendClientClaims = srcClient.AlwaysSendClientClaims; + client.AuthorizationCodeLifetime = srcClient.AuthorizationCodeLifetime; + client.BackChannelLogoutSessionRequired = srcClient.BackChannelLogoutSessionRequired; + + client.BackChannelLogoutUri = srcClient.BackChannelLogoutUri; + client.ClientClaimsPrefix = srcClient.ClientClaimsPrefix; + client.ConsentLifetime = srcClient.ConsentLifetime; + client.DeviceCodeLifetime = srcClient.DeviceCodeLifetime; + client.Enabled = srcClient.Enabled; + client.EnableLocalLogin = srcClient.EnableLocalLogin; + client.FrontChannelLogoutSessionRequired = srcClient.FrontChannelLogoutSessionRequired; + client.FrontChannelLogoutUri = srcClient.FrontChannelLogoutUri; + + client.IdentityTokenLifetime = srcClient.IdentityTokenLifetime; + client.IncludeJwtId = srcClient.IncludeJwtId; + client.LogoUri = srcClient.LogoUri; + client.PairWiseSubjectSalt = srcClient.PairWiseSubjectSalt; + client.ProtocolType = srcClient.ProtocolType; + client.RefreshTokenExpiration = srcClient.RefreshTokenExpiration; + client.RefreshTokenUsage = srcClient.RefreshTokenUsage; + client.RequireClientSecret = srcClient.RequireClientSecret; + client.RequireConsent = srcClient.RequireConsent; + + client.RequirePkce = srcClient.RequirePkce; + client.SlidingRefreshTokenLifetime = srcClient.SlidingRefreshTokenLifetime; + client.UpdateAccessTokenClaimsOnRefresh = srcClient.UpdateAccessTokenClaimsOnRefresh; + + client.UserCodeType = srcClient.UserCodeType; + client.UserSsoLifetime = srcClient.UserSsoLifetime; + + if (clientCloneInput.CopyAllowedCorsOrigin) + { + foreach(var corsOrigin in srcClient.AllowedCorsOrigins) + { + client.AddCorsOrigin(corsOrigin.Origin); + } + } + if (clientCloneInput.CopyAllowedGrantType) + { + foreach (var grantType in srcClient.AllowedGrantTypes) + { + client.AddGrantType(grantType.GrantType); + } + } + if (clientCloneInput.CopyAllowedScope) + { + foreach (var scope in srcClient.AllowedScopes) + { + client.AddScope(scope.Scope); + } + } + if (clientCloneInput.CopyClaim) + { + foreach (var claim in srcClient.Claims) + { + client.AddClaim(claim.Value, claim.Type); + } + } + if (clientCloneInput.CopyIdentityProviderRestriction) + { + foreach (var provider in srcClient.IdentityProviderRestrictions) + { + client.AddIdentityProviderRestriction(provider.Provider); + } + } + if (clientCloneInput.CopyPostLogoutRedirectUri) + { + foreach (var uri in srcClient.PostLogoutRedirectUris) + { + client.AddPostLogoutRedirectUri(uri.PostLogoutRedirectUri); + } + } + if (clientCloneInput.CopyPropertie) + { + foreach (var property in srcClient.Properties) + { + client.AddProperty(property.Key, property.Value); + } + } + if (clientCloneInput.CopyRedirectUri) + { + foreach (var uri in srcClient.RedirectUris) + { + client.AddRedirectUri(uri.RedirectUri); + } + } + client = await ClientRepository.InsertAsync(client); + return ObjectMapper.Map(client); + } + [Authorize(AbpIdentityServerPermissions.Clients.Claims.Update)] public virtual async Task UpdateClaimAsync(ClientClaimUpdateDto clientClaimUpdate) { diff --git a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.HttpApi/LINGYUN/Abp/IdentityServer/Clients/ClientController.cs b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.HttpApi/LINGYUN/Abp/IdentityServer/Clients/ClientController.cs index 88b8d9401..75d2071a6 100644 --- a/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.HttpApi/LINGYUN/Abp/IdentityServer/Clients/ClientController.cs +++ b/aspnet-core/modules/identityServer/LINGYUN.IdentityServer.HttpApi/LINGYUN/Abp/IdentityServer/Clients/ClientController.cs @@ -49,6 +49,13 @@ namespace LINGYUN.Abp.IdentityServer.Clients return await ClientAppService.UpdateAsync(clientUpdateInput); } + [HttpPost] + [Route("Clone")] + public virtual async Task CloneAsync(ClientCloneInputDto clientCloneInput) + { + return await ClientAppService.CloneAsync(clientCloneInput); + } + [HttpPost] [Route("Claims")] public virtual async Task AddClaimAsync(ClientClaimCreateDto clientClaimCreate) diff --git a/vueJs/src/api/clients.ts b/vueJs/src/api/clients.ts index 7a6e49655..2b6f44852 100644 --- a/vueJs/src/api/clients.ts +++ b/vueJs/src/api/clients.ts @@ -25,6 +25,11 @@ export default class ClientService { return ApiService.Post(_url, payload, serviceUrl) } + public static cloneClient(payload: ClientClone) { + const _url = '/api/IdentityServer/Clients/Clone' + return ApiService.Post(_url, payload, serviceUrl) + } + public static updateClient(payload: ClientUpdate) { const _url = '/api/IdentityServer/Clients' return ApiService.Put(_url, payload, serviceUrl) @@ -155,6 +160,25 @@ export class ClientCreate { } } +export class ClientClone { + sourceClientId = '' + clientId = '' + clientName = '' + description? = '' + copyAllowedGrantType = true + copyRedirectUri = true + copyAllowedScope = true + copyClaim = true + copyAllowedCorsOrigin = true + copyPostLogoutRedirectUri = true + copyPropertie = true + copyIdentityProviderRestriction = true + + public static empty() { + return new ClientClone() + } +} + export class Client extends FullAuditedEntityDto { id!: string clientId!: string diff --git a/vueJs/src/lang/zh.ts b/vueJs/src/lang/zh.ts index 6771186c8..cb447468f 100644 --- a/vueJs/src/lang/zh.ts +++ b/vueJs/src/lang/zh.ts @@ -405,6 +405,15 @@ export default { clientId: '客户端标识', clientName: '客户端名称', description: '客户端说明', + cloneClint: '克隆客户端', + copyAllowedGrantType: '复制客户端授权类型', + copyRedirectUri: '复制客户端重定向Uri', + copyAllowedScope: '复制客户端作用域', + copyClaim: '复制客户端声明', + copyAllowedCorsOrigin: '复制客户端跨域来源', + copyPostLogoutRedirectUri: '复制客户端注销重定向Uri', + copyPropertie: '复制客户端属性', + copyIdentityProviderRestriction: '身份提供程序限制', protocolType: '协议类型', requireClientSecret: '需要客户端密钥', requirePkce: '需要Pkce', diff --git a/vueJs/src/views/admin/identityServer/client/components/ClientCloneForm.vue b/vueJs/src/views/admin/identityServer/client/components/ClientCloneForm.vue new file mode 100644 index 000000000..6b56a5686 --- /dev/null +++ b/vueJs/src/views/admin/identityServer/client/components/ClientCloneForm.vue @@ -0,0 +1,193 @@ + + + + + diff --git a/vueJs/src/views/admin/identityServer/client/components/ClientPermissionEditForm.vue b/vueJs/src/views/admin/identityServer/client/components/ClientPermissionEditForm.vue index b6d9e087f..d9957bd8a 100644 --- a/vueJs/src/views/admin/identityServer/client/components/ClientPermissionEditForm.vue +++ b/vueJs/src/views/admin/identityServer/client/components/ClientPermissionEditForm.vue @@ -9,7 +9,6 @@ + {{ $t('identityServer.cloneClint') }} + + @@ -232,6 +239,23 @@ /> + + + + () + this.showCloneClientDialog = false this.showEditClientSecretDialog = false this.showEditClientClaimDialog = false this.showEditClientPropertyDialog = false @@ -426,6 +455,17 @@ export default class extends Vue { } } + private handleClientCloneFormClosed(changed: boolean) { + this.editClientTitle = '' + this.editClient = Client.empty() + this.showCloneClientDialog = false + const frmClient = this.$refs.formCloneClient as ClientCloneForm + frmClient.resetFields() + if (changed) { + this.handleGetClients() + } + } + private handleClientEditFormClosed(changed: boolean) { this.editClientTitle = '' this.editClient = Client.empty() @@ -475,6 +515,9 @@ export default class extends Vue { private handleCommand(command: {key: string, row: Client}) { this.editClient = command.row switch (command.key) { + case 'clone' : + this.showCloneClientDialog = true + break case 'secret' : this.showEditClientSecretDialog = true break