From e7ff516abd00ec7979743e41a4ce8bb24e258281 Mon Sep 17 00:00:00 2001 From: pnoker Date: Mon, 22 Jun 2026 10:47:43 +0800 Subject: [PATCH] refactor(auth): use domain enums for oauth/mcp VO fields Replace bare String fields with domain enums on the read-response VOs and in service-internal comparisons, removing scattered McpConstant string literals. Five value-typed enums (OAuthGrantType/OAuthClientType/McpRiskLevel/McpAuditStatus/McpConfirmationStatus) land in common-constant with @EnumValue + @JsonValue + ofValue, registered in IndexedEnumContractTest. VO fields: McpConnectionVO{principalType,grantType}, McpToolVO.riskLevel, McpAuditVO{principalType,riskLevel,status}, OAuthClientVO.clientType. Builders convert via @AfterMapping (Enum.ofValue); the *Record projections keep String/Byte untouched (OAuthMcpMapper custom-SQL resultType boundary). Service compares/assigns via enum.getValue(); Confirmation.DECISION_* kept (gateway-shared). common-constant gains jackson-annotations because grantType's wire value is lowercase and must serialize via @JsonValue, not the uppercase enum name. --- .../biz/impl/OAuthMcpRuntimeServiceImpl.java | 67 +++++++------- .../auth/entity/builder/McpAuditBuilder.java | 16 ++++ .../entity/builder/McpConnectionBuilder.java | 13 +++ .../auth/entity/builder/McpToolBuilder.java | 10 +++ .../entity/builder/OAuthClientBuilder.java | 10 +++ .../common/auth/entity/vo/McpAuditVO.java | 9 +- .../auth/entity/vo/McpConnectionVO.java | 6 +- .../common/auth/entity/vo/McpToolVO.java | 3 +- .../common/auth/entity/vo/OAuthClientVO.java | 3 +- dc3-common/dc3-common-constant/pom.xml | 6 ++ .../common/enums/McpAuditStatusEnum.java | 89 +++++++++++++++++++ .../enums/McpConfirmationStatusEnum.java | 74 +++++++++++++++ .../pnoker/common/enums/McpRiskLevelEnum.java | 79 ++++++++++++++++ .../common/enums/OAuthClientTypeEnum.java | 74 +++++++++++++++ .../common/enums/OAuthGrantTypeEnum.java | 79 ++++++++++++++++ .../common/enums/IndexedEnumContractTest.java | 5 ++ 16 files changed, 505 insertions(+), 38 deletions(-) create mode 100644 dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpAuditStatusEnum.java create mode 100644 dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpConfirmationStatusEnum.java create mode 100644 dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpRiskLevelEnum.java create mode 100644 dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/OAuthClientTypeEnum.java create mode 100644 dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/OAuthGrantTypeEnum.java diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/biz/impl/OAuthMcpRuntimeServiceImpl.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/biz/impl/OAuthMcpRuntimeServiceImpl.java index 0b2488c63..a1d23a676 100644 --- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/biz/impl/OAuthMcpRuntimeServiceImpl.java +++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/biz/impl/OAuthMcpRuntimeServiceImpl.java @@ -58,6 +58,11 @@ import io.github.pnoker.common.entity.dto.McpToolAuthorizeResponseDTO; import io.github.pnoker.common.entity.dto.McpToolDefinitionDTO; import io.github.pnoker.common.entity.dto.McpToolResolveResponseDTO; import io.github.pnoker.common.enums.EnableFlagEnum; +import io.github.pnoker.common.enums.McpAuditStatusEnum; +import io.github.pnoker.common.enums.McpConfirmationStatusEnum; +import io.github.pnoker.common.enums.McpRiskLevelEnum; +import io.github.pnoker.common.enums.OAuthClientTypeEnum; +import io.github.pnoker.common.enums.OAuthGrantTypeEnum; import io.github.pnoker.common.enums.PrincipalTypeEnum; import io.github.pnoker.common.utils.DecodeUtil; import io.github.pnoker.common.utils.JsonUtil; @@ -223,9 +228,9 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { "revocation_endpoint", issuer + McpConstant.OAUTH2_REVOKE, "registration_endpoint", issuer + McpConstant.OAUTH2_REGISTER, "response_types_supported", List.of(McpConstant.OAuth.RESPONSE_TYPE_CODE), - "grant_types_supported", List.of(McpConstant.OAuth.GRANT_AUTHORIZATION_CODE, - McpConstant.OAuth.GRANT_CLIENT_CREDENTIALS, - McpConstant.OAuth.GRANT_REFRESH_TOKEN), + "grant_types_supported", List.of(OAuthGrantTypeEnum.AUTHORIZATION_CODE.getValue(), + OAuthGrantTypeEnum.CLIENT_CREDENTIALS.getValue(), + OAuthGrantTypeEnum.REFRESH_TOKEN.getValue()), "code_challenge_methods_supported", List.of(McpConstant.OAuth.CODE_CHALLENGE_METHOD_S256), "scopes_supported", McpConstant.Scope.SUPPORTED, "token_endpoint_auth_methods_supported", List.of(McpConstant.OAuth.AUTH_METHOD_CLIENT_SECRET_BASIC, @@ -256,24 +261,24 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { throw oauthError(BAD_REQUEST.value(), "invalid_client_metadata", "client_name is required"); } String clientType = StringUtils.defaultIfBlank(stringValue(request.getClientType()), - McpConstant.OAuth.CLIENT_TYPE_PUBLIC).toUpperCase(); - if (!Set.of(McpConstant.OAuth.CLIENT_TYPE_PUBLIC, McpConstant.OAuth.CLIENT_TYPE_CONFIDENTIAL) + OAuthClientTypeEnum.PUBLIC.getValue()).toUpperCase(); + if (!Set.of(OAuthClientTypeEnum.PUBLIC.getValue(), OAuthClientTypeEnum.CONFIDENTIAL.getValue()) .contains(clientType)) { throw oauthError(BAD_REQUEST.value(), "invalid_client_metadata", "unsupported client_type"); } Set grants = normalizeSet(request.getGrantTypes()); if (grants.isEmpty()) { - grants = McpConstant.OAuth.CLIENT_TYPE_PUBLIC.equals(clientType) - ? Set.of(McpConstant.OAuth.GRANT_AUTHORIZATION_CODE) - : Set.of(McpConstant.OAuth.GRANT_CLIENT_CREDENTIALS); + grants = OAuthClientTypeEnum.PUBLIC.getValue().equals(clientType) + ? Set.of(OAuthGrantTypeEnum.AUTHORIZATION_CODE.getValue()) + : Set.of(OAuthGrantTypeEnum.CLIENT_CREDENTIALS.getValue()); } Set scopes = normalizeSet(request.getScope()); if (scopes.isEmpty()) { scopes = Set.of(McpConstant.Scope.TOOLS_LIST, McpConstant.Scope.TOOLS_CALL); } Set redirects = normalizeSet(request.getRedirectUris()); - if (grants.contains(McpConstant.OAuth.GRANT_AUTHORIZATION_CODE) && redirects.isEmpty()) { + if (grants.contains(OAuthGrantTypeEnum.AUTHORIZATION_CODE.getValue()) && redirects.isEmpty()) { throw oauthError(BAD_REQUEST.value(), "invalid_redirect_uri", "redirect_uris is required"); } @@ -286,7 +291,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { Long ownerPrincipalId = principalHeader.getPrincipalId(); Long tenantId = principalHeader.getTenantId(); Long serviceAccountPrincipalId = request.getServiceAccountPrincipalId(); - if (grants.contains(McpConstant.OAuth.GRANT_CLIENT_CREDENTIALS)) { + if (grants.contains(OAuthGrantTypeEnum.CLIENT_CREDENTIALS.getValue())) { validateServiceAccountClient(serviceAccountPrincipalId, tenantId); } @@ -294,7 +299,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { String clientSecret = null; String secretHash = ""; String authMethods = McpConstant.OAuth.AUTH_METHOD_NONE; - if (McpConstant.OAuth.CLIENT_TYPE_CONFIDENTIAL.equals(clientType)) { + if (OAuthClientTypeEnum.CONFIDENTIAL.getValue().equals(clientType)) { clientSecret = randomToken(); secretHash = PasswordUtil.encode(clientSecret); authMethods = McpConstant.OAuth.AUTH_METHOD_CLIENT_SECRET_BASIC + ' ' @@ -315,7 +320,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { client.setAuthorizationGrantTypes(String.join(" ", grants)); client.setRedirectUris(String.join(" ", redirects)); client.setScopes(String.join(" ", scopes)); - client.setRequirePkce((byte) (grants.contains(McpConstant.OAuth.GRANT_AUTHORIZATION_CODE) ? 1 : 0)); + client.setRequirePkce((byte) (grants.contains(OAuthGrantTypeEnum.AUTHORIZATION_CODE.getValue()) ? 1 : 0)); client.setRequireConsent((byte) 1); client.setEnableFlag((byte) 0); oauthMcpMapper.insertClient(client); @@ -327,7 +332,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { .grantTypes(grants) .redirectUris(redirects) .scope(String.join(" ", scopes)) - .tokenEndpointAuthMethod(McpConstant.OAuth.CLIENT_TYPE_CONFIDENTIAL.equals(clientType) + .tokenEndpointAuthMethod(OAuthClientTypeEnum.CONFIDENTIAL.getValue().equals(clientType) ? McpConstant.OAuth.AUTH_METHOD_CLIENT_SECRET_BASIC : McpConstant.OAuth.AUTH_METHOD_NONE) .clientSecret(clientSecret) .build(); @@ -350,7 +355,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { throw oauthError(BAD_REQUEST.value(), "unsupported_response_type", "only code is supported"); } OAuthRegisteredClientRecord client = requireClient(params.get(McpConstant.Field.CLIENT_ID)); - requireGrant(client, McpConstant.OAuth.GRANT_AUTHORIZATION_CODE); + requireGrant(client, OAuthGrantTypeEnum.AUTHORIZATION_CODE.getValue()); String redirectUri = params.get(McpConstant.Field.REDIRECT_URI); if (!splitValues(client.getRedirectUris()).contains(redirectUri)) { throw oauthError(BAD_REQUEST.value(), "invalid_request", "redirect_uri mismatch"); @@ -374,10 +379,10 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { Long connectionId = longValue(params.get(McpConstant.Field.MCP_CONNECTION_ID)); McpConnectionRecord connection = connectionId == null || connectionId == 0 ? oauthMcpMapper.selectActiveConnection(client.getClientId(), principalHeader.getPrincipalId(), - tenantId, McpConstant.OAuth.GRANT_AUTHORIZATION_CODE) + tenantId, OAuthGrantTypeEnum.AUTHORIZATION_CODE.getValue()) : oauthMcpMapper.selectConnectionById(connectionId); validateConnection(connection, client.getClientId(), principalHeader.getPrincipalId(), tenantId, - McpConstant.OAuth.GRANT_AUTHORIZATION_CODE); + OAuthGrantTypeEnum.AUTHORIZATION_CODE.getValue()); String code = randomToken(); OAuthAuthorizationRecord authorization = new OAuthAuthorizationRecord(); @@ -389,7 +394,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { PrincipalTypeEnum.USER.getValue())); authorization.setTenantId(tenantId); authorization.setMcpConnectionId(connection.getId()); - authorization.setAuthorizationGrantType(McpConstant.OAuth.GRANT_AUTHORIZATION_CODE); + authorization.setAuthorizationGrantType(OAuthGrantTypeEnum.AUTHORIZATION_CODE.getValue()); authorization.setAuthorizedScopes(String.join(" ", scopes)); authorization.setStateHash(sha256(params.get("state"))); authorization.setAuthorizationCodeHash(sha256(code)); @@ -415,13 +420,13 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { @Transactional(rollbackFor = Exception.class) public Map token(Map form, String authorizationHeader) { String grantType = form.get(McpConstant.Field.GRANT_TYPE); - if (McpConstant.OAuth.GRANT_AUTHORIZATION_CODE.equals(grantType)) { + if (OAuthGrantTypeEnum.AUTHORIZATION_CODE.getValue().equals(grantType)) { return authorizationCodeToken(form, authorizationHeader); } - if (McpConstant.OAuth.GRANT_CLIENT_CREDENTIALS.equals(grantType)) { + if (OAuthGrantTypeEnum.CLIENT_CREDENTIALS.getValue().equals(grantType)) { return clientCredentialsToken(form, authorizationHeader); } - if (McpConstant.OAuth.GRANT_REFRESH_TOKEN.equals(grantType)) { + if (OAuthGrantTypeEnum.REFRESH_TOKEN.getValue().equals(grantType)) { return refreshToken(form, authorizationHeader); } throw oauthError(BAD_REQUEST.value(), "unsupported_grant_type", "unsupported grant_type"); @@ -543,7 +548,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { McpConnectionRecord connection = mcpConnectionBuilder.buildRecordByAddVO(entityVO); OAuthRegisteredClientRecord client = requireClient(connection.getClientId()); String grantType = StringUtils.defaultIfBlank(connection.getGrantType(), - McpConstant.OAuth.GRANT_AUTHORIZATION_CODE); + OAuthGrantTypeEnum.AUTHORIZATION_CODE.getValue()); requireGrant(client, grantType); Long tenantId = Objects.requireNonNullElse(connection.getTenantId(), principalHeader.getTenantId()); @@ -551,7 +556,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { String principalType = StringUtils.defaultIfBlank(connection.getPrincipalType(), StringUtils.defaultIfBlank(principalHeader.getPrincipalType(), PrincipalTypeEnum.USER.getValue())); - if (McpConstant.OAuth.GRANT_CLIENT_CREDENTIALS.equals(grantType)) { + if (OAuthGrantTypeEnum.CLIENT_CREDENTIALS.getValue().equals(grantType)) { if (!PrincipalTypeEnum.SERVICE_ACCOUNT.getValue().equals(principalType)) { throw oauthError(BAD_REQUEST.value(), "invalid_request", "client_credentials connection requires a service account principal"); @@ -673,7 +678,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { request.getMcpConnectionId(), request.getToolName(), scopes); // Only HIGH-risk tools require platform confirmation; the rest pass straight through. - if (!McpConstant.RiskLevel.HIGH.equals(tool.getRiskLevel())) { + if (!McpRiskLevelEnum.HIGH.getValue().equals(tool.getRiskLevel())) { return authorizeDecision(McpConstant.Confirmation.DECISION_AUTHORIZED, "", "authorized", tool.getRiskLevel()); } @@ -702,7 +707,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { ticket.setArgumentDigest(argumentDigest); ticket.setIdempotencyKey(idempotencyKey); ticket.setRiskLevel(tool.getRiskLevel()); - ticket.setStatus(McpConstant.Confirmation.STATUS_PENDING); + ticket.setStatus(McpConfirmationStatusEnum.PENDING.getValue()); ticket.setExpireTime(LocalDateTime.now().plus(confirmTtl)); oauthMcpMapper.insertConfirmation(ticket); return authorizeDecision(McpConstant.Confirmation.DECISION_CONFIRM_REQUIRED, issuedConfirmId, @@ -729,7 +734,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { if (ticket == null) { return "confirmation does not exist"; } - if (!McpConstant.Confirmation.STATUS_PENDING.equals(ticket.getStatus())) { + if (!McpConfirmationStatusEnum.PENDING.getValue().equals(ticket.getStatus())) { return "confirmation has already been used"; } if (ticket.getExpireTime() == null || ticket.getExpireTime().isBefore(LocalDateTime.now())) { @@ -765,7 +770,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { command.setConfirmId(StringUtils.defaultString(command.getConfirmId())); command.setIdempotencyKey(StringUtils.defaultString(command.getIdempotencyKey())); command.setArgumentDigest(StringUtils.defaultString(command.getArgumentDigest())); - command.setStatus(StringUtils.defaultIfBlank(command.getStatus(), McpConstant.Audit.UNKNOWN)); + command.setStatus(StringUtils.defaultIfBlank(command.getStatus(), McpAuditStatusEnum.UNKNOWN.getValue())); command.setErrorCode(StringUtils.defaultString(command.getErrorCode())); command.setDurationMs(Objects.requireNonNullElse(command.getDurationMs(), 0L)); command.setClientName(StringUtils.defaultString(command.getClientName())); @@ -799,13 +804,13 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { private Map clientCredentialsToken(Map form, String authorizationHeader) { OAuthRegisteredClientRecord client = requireClient(resolveClientId(form, authorizationHeader)); authenticateClient(client, form, authorizationHeader, true); - requireGrant(client, McpConstant.OAuth.GRANT_CLIENT_CREDENTIALS); + requireGrant(client, OAuthGrantTypeEnum.CLIENT_CREDENTIALS.getValue()); validateServiceAccountClient(client.getServiceAccountPrincipalId(), client.getTenantId()); McpConnectionRecord connection = oauthMcpMapper.selectActiveConnection(client.getClientId(), client.getServiceAccountPrincipalId(), client.getTenantId(), - McpConstant.OAuth.GRANT_CLIENT_CREDENTIALS); + OAuthGrantTypeEnum.CLIENT_CREDENTIALS.getValue()); validateConnection(connection, client.getClientId(), client.getServiceAccountPrincipalId(), - client.getTenantId(), McpConstant.OAuth.GRANT_CLIENT_CREDENTIALS); + client.getTenantId(), OAuthGrantTypeEnum.CLIENT_CREDENTIALS.getValue()); Set scopes = requestedScopes(form.get(McpConstant.Field.SCOPE), client); OAuthAuthorizationRecord authorization = new OAuthAuthorizationRecord(); @@ -816,7 +821,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { authorization.setPrincipalType(PrincipalTypeEnum.SERVICE_ACCOUNT.getValue()); authorization.setTenantId(client.getTenantId()); authorization.setMcpConnectionId(connection.getId()); - authorization.setAuthorizationGrantType(McpConstant.OAuth.GRANT_CLIENT_CREDENTIALS); + authorization.setAuthorizationGrantType(OAuthGrantTypeEnum.CLIENT_CREDENTIALS.getValue()); authorization.setAuthorizedScopes(String.join(" ", scopes)); authorization.setStateHash(""); authorization.setAuthorizationCodeHash(""); @@ -976,7 +981,7 @@ public class OAuthMcpRuntimeServiceImpl implements OAuthMcpRuntimeService { private void authenticateClient(OAuthRegisteredClientRecord client, Map form, String authorizationHeader, boolean confidentialRequired) { - if (McpConstant.OAuth.CLIENT_TYPE_PUBLIC.equals(client.getClientType())) { + if (OAuthClientTypeEnum.PUBLIC.getValue().equals(client.getClientType())) { if (confidentialRequired || !splitValues(client.getClientAuthMethods()).contains(McpConstant.OAuth.AUTH_METHOD_NONE)) { throw oauthError(UNAUTHORIZED.value(), "invalid_client", "confidential client is required"); diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpAuditBuilder.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpAuditBuilder.java index 0113e380f..2e1c51548 100644 --- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpAuditBuilder.java +++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpAuditBuilder.java @@ -19,8 +19,14 @@ package io.github.pnoker.common.auth.entity.builder; import io.github.pnoker.common.auth.entity.oauth.McpAuditCommand; import io.github.pnoker.common.auth.entity.vo.McpAuditVO; +import io.github.pnoker.common.enums.McpAuditStatusEnum; +import io.github.pnoker.common.enums.McpRiskLevelEnum; +import io.github.pnoker.common.enums.PrincipalTypeEnum; import io.github.pnoker.common.utils.MapStructUtil; +import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import java.util.List; @@ -40,8 +46,18 @@ public interface McpAuditBuilder { * @param entityRecord EntityRecord * @return EntityVO */ + @Mapping(target = "principalType", ignore = true) + @Mapping(target = "riskLevel", ignore = true) + @Mapping(target = "status", ignore = true) McpAuditVO buildVOByRecord(McpAuditCommand entityRecord); + @AfterMapping + default void afterProcess(McpAuditCommand entityRecord, @MappingTarget McpAuditVO entityVO) { + entityVO.setPrincipalType(PrincipalTypeEnum.ofValue(entityRecord.getPrincipalType())); + entityVO.setRiskLevel(McpRiskLevelEnum.ofValue(entityRecord.getRiskLevel())); + entityVO.setStatus(McpAuditStatusEnum.ofValue(entityRecord.getStatus())); + } + /** * RecordList to VOList * diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpConnectionBuilder.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpConnectionBuilder.java index e245172f0..7e49f5faf 100644 --- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpConnectionBuilder.java +++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpConnectionBuilder.java @@ -20,8 +20,13 @@ package io.github.pnoker.common.auth.entity.builder; import io.github.pnoker.common.auth.entity.oauth.McpConnectionRecord; import io.github.pnoker.common.auth.entity.vo.McpConnectionAddVO; import io.github.pnoker.common.auth.entity.vo.McpConnectionVO; +import io.github.pnoker.common.enums.OAuthGrantTypeEnum; +import io.github.pnoker.common.enums.PrincipalTypeEnum; import io.github.pnoker.common.utils.MapStructUtil; +import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import java.util.List; @@ -41,8 +46,16 @@ public interface McpConnectionBuilder { * @param entityRecord EntityRecord * @return EntityVO */ + @Mapping(target = "principalType", ignore = true) + @Mapping(target = "grantType", ignore = true) McpConnectionVO buildVOByRecord(McpConnectionRecord entityRecord); + @AfterMapping + default void afterProcess(McpConnectionRecord entityRecord, @MappingTarget McpConnectionVO entityVO) { + entityVO.setPrincipalType(PrincipalTypeEnum.ofValue(entityRecord.getPrincipalType())); + entityVO.setGrantType(OAuthGrantTypeEnum.ofValue(entityRecord.getGrantType())); + } + /** * RecordList to VOList * diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpToolBuilder.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpToolBuilder.java index da4eb4c40..a60549354 100644 --- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpToolBuilder.java +++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/McpToolBuilder.java @@ -19,8 +19,12 @@ package io.github.pnoker.common.auth.entity.builder; import io.github.pnoker.common.auth.entity.oauth.McpToolRecord; import io.github.pnoker.common.auth.entity.vo.McpToolVO; +import io.github.pnoker.common.enums.McpRiskLevelEnum; import io.github.pnoker.common.utils.MapStructUtil; +import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import java.util.List; @@ -40,8 +44,14 @@ public interface McpToolBuilder { * @param entityRecord EntityRecord * @return EntityVO */ + @Mapping(target = "riskLevel", ignore = true) McpToolVO buildVOByRecord(McpToolRecord entityRecord); + @AfterMapping + default void afterProcess(McpToolRecord entityRecord, @MappingTarget McpToolVO entityVO) { + entityVO.setRiskLevel(McpRiskLevelEnum.ofValue(entityRecord.getRiskLevel())); + } + /** * RecordList to VOList * diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/OAuthClientBuilder.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/OAuthClientBuilder.java index 0d39af335..e5bd76655 100644 --- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/OAuthClientBuilder.java +++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/builder/OAuthClientBuilder.java @@ -19,8 +19,12 @@ package io.github.pnoker.common.auth.entity.builder; import io.github.pnoker.common.auth.entity.oauth.OAuthRegisteredClientRecord; import io.github.pnoker.common.auth.entity.vo.OAuthClientVO; +import io.github.pnoker.common.enums.OAuthClientTypeEnum; import io.github.pnoker.common.utils.MapStructUtil; +import org.mapstruct.AfterMapping; import org.mapstruct.Mapper; +import org.mapstruct.Mapping; +import org.mapstruct.MappingTarget; import java.util.List; @@ -40,8 +44,14 @@ public interface OAuthClientBuilder { * @param entityRecord EntityRecord * @return EntityVO */ + @Mapping(target = "clientType", ignore = true) OAuthClientVO buildVOByRecord(OAuthRegisteredClientRecord entityRecord); + @AfterMapping + default void afterProcess(OAuthRegisteredClientRecord entityRecord, @MappingTarget OAuthClientVO entityVO) { + entityVO.setClientType(OAuthClientTypeEnum.ofValue(entityRecord.getClientType())); + } + /** * RecordList to VOList * diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpAuditVO.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpAuditVO.java index e50a97fc2..c5330950c 100644 --- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpAuditVO.java +++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpAuditVO.java @@ -19,6 +19,9 @@ package io.github.pnoker.common.auth.entity.vo; import com.fasterxml.jackson.annotation.JsonInclude; import io.github.pnoker.common.entity.base.BaseVO; +import io.github.pnoker.common.enums.McpAuditStatusEnum; +import io.github.pnoker.common.enums.McpRiskLevelEnum; +import io.github.pnoker.common.enums.PrincipalTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -52,7 +55,7 @@ public class McpAuditVO extends BaseVO { private Long principalId; @Schema(description = "Principal type: USER or SERVICE_ACCOUNT.", example = "USER") - private String principalType; + private PrincipalTypeEnum principalType; @Schema(description = "OAuth client identifier.", example = "mcp-abcd1234") private String clientId; @@ -70,7 +73,7 @@ public class McpAuditVO extends BaseVO { private String permissionCode; @Schema(description = "Tool risk level: LOW, MEDIUM or HIGH.", example = "LOW") - private String riskLevel; + private McpRiskLevelEnum riskLevel; @Schema(description = "Confirmation id for high-risk calls.", example = "cfm-abcd1234") private String confirmId; @@ -82,7 +85,7 @@ public class McpAuditVO extends BaseVO { private String argumentDigest; @Schema(description = "Invocation outcome: SUCCESS, DENIED, POLICY_DENIED, ERROR or UNKNOWN.", example = "SUCCESS") - private String status; + private McpAuditStatusEnum status; @Schema(description = "Error code when the call failed.", example = "policy_denied") private String errorCode; diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpConnectionVO.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpConnectionVO.java index fb4d9722d..58a08ea70 100644 --- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpConnectionVO.java +++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpConnectionVO.java @@ -19,6 +19,8 @@ package io.github.pnoker.common.auth.entity.vo; import com.fasterxml.jackson.annotation.JsonInclude; import io.github.pnoker.common.entity.base.BaseVO; +import io.github.pnoker.common.enums.OAuthGrantTypeEnum; +import io.github.pnoker.common.enums.PrincipalTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -54,13 +56,13 @@ public class McpConnectionVO extends BaseVO { private Long principalId; @Schema(description = "Principal type: USER or SERVICE_ACCOUNT.", example = "USER") - private String principalType; + private PrincipalTypeEnum principalType; @Schema(description = "Tenant the connection belongs to.", example = "1") private Long tenantId; @Schema(description = "Authorization grant type.", example = "authorization_code") - private String grantType; + private OAuthGrantTypeEnum grantType; @Schema(description = "Enable flag: 0 enabled, 1 disabled.", example = "0") private Byte enableFlag; diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpToolVO.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpToolVO.java index ae50e743f..213f9e23b 100644 --- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpToolVO.java +++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/McpToolVO.java @@ -19,6 +19,7 @@ package io.github.pnoker.common.auth.entity.vo; import com.fasterxml.jackson.annotation.JsonInclude; import io.github.pnoker.common.entity.base.BaseVO; +import io.github.pnoker.common.enums.McpRiskLevelEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -73,7 +74,7 @@ public class McpToolVO extends BaseVO { private String schemaHash; @Schema(description = "Tool risk level: LOW, MEDIUM or HIGH.", example = "LOW") - private String riskLevel; + private McpRiskLevelEnum riskLevel; @Schema(description = "Read-only hint: 1 yes, 0 no.", example = "1") private Byte readOnlyHint; diff --git a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/OAuthClientVO.java b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/OAuthClientVO.java index 7471c5079..e604aad87 100644 --- a/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/OAuthClientVO.java +++ b/dc3-common/dc3-common-auth/src/main/java/io/github/pnoker/common/auth/entity/vo/OAuthClientVO.java @@ -19,6 +19,7 @@ package io.github.pnoker.common.auth.entity.vo; import com.fasterxml.jackson.annotation.JsonInclude; import io.github.pnoker.common.entity.base.BaseVO; +import io.github.pnoker.common.enums.OAuthClientTypeEnum; import io.swagger.v3.oas.annotations.media.Schema; import lombok.AllArgsConstructor; import lombok.Getter; @@ -51,7 +52,7 @@ public class OAuthClientVO extends BaseVO { private String clientName; @Schema(description = "Client type: PUBLIC or CONFIDENTIAL.", example = "CONFIDENTIAL") - private String clientType; + private OAuthClientTypeEnum clientType; @Schema(description = "Principal that owns the client.", example = "1024") private Long ownerPrincipalId; diff --git a/dc3-common/dc3-common-constant/pom.xml b/dc3-common/dc3-common-constant/pom.xml index 00e69f974..6764c8793 100644 --- a/dc3-common/dc3-common-constant/pom.xml +++ b/dc3-common/dc3-common-constant/pom.xml @@ -74,6 +74,12 @@ mybatis-plus-annotation + + + com.fasterxml.jackson.core + jackson-annotations + + diff --git a/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpAuditStatusEnum.java b/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpAuditStatusEnum.java new file mode 100644 index 000000000..8465057ee --- /dev/null +++ b/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpAuditStatusEnum.java @@ -0,0 +1,89 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.github.pnoker.common.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Optional; + +/** + * MCP tool-call audit outcome. + * + * @author pnoker + * @version 2026.6.12 + * @since 2026.6.12 + */ +@Getter +@AllArgsConstructor +public enum McpAuditStatusEnum { + + /** + * Tool call succeeded. + */ + SUCCESS("SUCCESS", "success", "Tool call succeeded"), + + /** + * Caller denied by authorization. + */ + DENIED("DENIED", "denied", "Denied by authorization"), + + /** + * Denied by risk/confirmation policy. + */ + POLICY_DENIED("POLICY_DENIED", "policy_denied", "Denied by policy"), + + /** + * Tool call errored. + */ + ERROR("ERROR", "error", "Tool call errored"), + + /** + * Outcome unknown. + */ + UNKNOWN("UNKNOWN", "unknown", "Unknown outcome"), + ; + + /** + * Database / wire value. + */ + @EnumValue + @JsonValue + private final String value; + + /** + * Code string. + */ + private final String code; + + /** + * Human-readable description. + */ + private final String remark; + + public static McpAuditStatusEnum ofValue(String value) { + Optional any = Arrays.stream(values()) + .filter(type -> type.getValue().equals(value)) + .findFirst(); + return any.orElse(null); + } + +} diff --git a/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpConfirmationStatusEnum.java b/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpConfirmationStatusEnum.java new file mode 100644 index 000000000..deb9bcdd7 --- /dev/null +++ b/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpConfirmationStatusEnum.java @@ -0,0 +1,74 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.github.pnoker.common.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Optional; + +/** + * MCP high-risk tool confirmation ticket status (persisted lifecycle). + * + * @author pnoker + * @version 2026.6.12 + * @since 2026.6.12 + */ +@Getter +@AllArgsConstructor +public enum McpConfirmationStatusEnum { + + /** + * Ticket issued, awaiting consumption. + */ + PENDING("PENDING", "pending", "Pending consumption"), + + /** + * Ticket consumed (one-time use spent). + */ + CONSUMED("CONSUMED", "consumed", "Consumed"), + ; + + /** + * Database / wire value. + */ + @EnumValue + @JsonValue + private final String value; + + /** + * Code string. + */ + private final String code; + + /** + * Human-readable description. + */ + private final String remark; + + public static McpConfirmationStatusEnum ofValue(String value) { + Optional any = Arrays.stream(values()) + .filter(type -> type.getValue().equals(value)) + .findFirst(); + return any.orElse(null); + } + +} diff --git a/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpRiskLevelEnum.java b/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpRiskLevelEnum.java new file mode 100644 index 000000000..4934bcfc7 --- /dev/null +++ b/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/McpRiskLevelEnum.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.github.pnoker.common.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Optional; + +/** + * MCP tool risk level (governs confirmation and visibility policy). + * + * @author pnoker + * @version 2026.6.12 + * @since 2026.6.12 + */ +@Getter +@AllArgsConstructor +public enum McpRiskLevelEnum { + + /** + * Low risk: read-only, always visible. + */ + LOW("LOW", "low", "Low risk"), + + /** + * Medium risk: hidden unless explicitly enabled. + */ + MEDIUM("MEDIUM", "medium", "Medium risk"), + + /** + * High risk: hidden by default, requires confirmation. + */ + HIGH("HIGH", "high", "High risk"), + ; + + /** + * Database / wire value. + */ + @EnumValue + @JsonValue + private final String value; + + /** + * Code string. + */ + private final String code; + + /** + * Human-readable description. + */ + private final String remark; + + public static McpRiskLevelEnum ofValue(String value) { + Optional any = Arrays.stream(values()) + .filter(type -> type.getValue().equals(value)) + .findFirst(); + return any.orElse(null); + } + +} diff --git a/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/OAuthClientTypeEnum.java b/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/OAuthClientTypeEnum.java new file mode 100644 index 000000000..390bd04f1 --- /dev/null +++ b/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/OAuthClientTypeEnum.java @@ -0,0 +1,74 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.github.pnoker.common.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Optional; + +/** + * OAuth registered client type. + * + * @author pnoker + * @version 2026.6.12 + * @since 2026.6.12 + */ +@Getter +@AllArgsConstructor +public enum OAuthClientTypeEnum { + + /** + * Public client (no secret, PKCE required). + */ + PUBLIC("PUBLIC", "public", "Public client"), + + /** + * Confidential client (holds a secret). + */ + CONFIDENTIAL("CONFIDENTIAL", "confidential", "Confidential client"), + ; + + /** + * Database / wire value. + */ + @EnumValue + @JsonValue + private final String value; + + /** + * Code string. + */ + private final String code; + + /** + * Human-readable description. + */ + private final String remark; + + public static OAuthClientTypeEnum ofValue(String value) { + Optional any = Arrays.stream(values()) + .filter(type -> type.getValue().equals(value)) + .findFirst(); + return any.orElse(null); + } + +} diff --git a/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/OAuthGrantTypeEnum.java b/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/OAuthGrantTypeEnum.java new file mode 100644 index 000000000..e539423d5 --- /dev/null +++ b/dc3-common/dc3-common-constant/src/main/java/io/github/pnoker/common/enums/OAuthGrantTypeEnum.java @@ -0,0 +1,79 @@ +/* + * Copyright 2016-present the IoT DC3 original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +package io.github.pnoker.common.enums; + +import com.baomidou.mybatisplus.annotation.EnumValue; +import com.fasterxml.jackson.annotation.JsonValue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Optional; + +/** + * OAuth 2.1 authorization grant type. + * + * @author pnoker + * @version 2026.6.12 + * @since 2026.6.12 + */ +@Getter +@AllArgsConstructor +public enum OAuthGrantTypeEnum { + + /** + * Authorization code (with PKCE) grant. + */ + AUTHORIZATION_CODE("authorization_code", "authorization_code", "Authorization code grant"), + + /** + * Client credentials (machine-to-machine) grant. + */ + CLIENT_CREDENTIALS("client_credentials", "client_credentials", "Client credentials grant"), + + /** + * Refresh token grant. + */ + REFRESH_TOKEN("refresh_token", "refresh_token", "Refresh token grant"), + ; + + /** + * Database / wire value (lowercase, per OAuth spec). + */ + @EnumValue + @JsonValue + private final String value; + + /** + * Code string. + */ + private final String code; + + /** + * Human-readable description. + */ + private final String remark; + + public static OAuthGrantTypeEnum ofValue(String value) { + Optional any = Arrays.stream(values()) + .filter(type -> type.getValue().equals(value)) + .findFirst(); + return any.orElse(null); + } + +} diff --git a/dc3-common/dc3-common-constant/src/test/java/io/github/pnoker/common/enums/IndexedEnumContractTest.java b/dc3-common/dc3-common-constant/src/test/java/io/github/pnoker/common/enums/IndexedEnumContractTest.java index f7a81e65d..1ce03c78b 100644 --- a/dc3-common/dc3-common-constant/src/test/java/io/github/pnoker/common/enums/IndexedEnumContractTest.java +++ b/dc3-common/dc3-common-constant/src/test/java/io/github/pnoker/common/enums/IndexedEnumContractTest.java @@ -109,12 +109,17 @@ class IndexedEnumContractTest { EventLevelEnum.class, EventTypeFlagEnum.class, ExpireTypeEnum.class, + McpAuditStatusEnum.class, + McpConfirmationStatusEnum.class, + McpRiskLevelEnum.class, MenuLevelEnum.class, MenuTypeFlagEnum.class, MetadataOperateTypeEnum.class, MetadataTypeEnum.class, NotifyChannelTypeEnum.class, NotifyHistoryStatusEnum.class, + OAuthClientTypeEnum.class, + OAuthGrantTypeEnum.class, ParamDirectionTypeEnum.class, PointCommandSourceEnum.class, PointCommandStatusEnum.class,