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.
This commit is contained in:
pnoker
2026-06-22 10:47:43 +08:00
parent 43d9175746
commit e7ff516abd
16 changed files with 505 additions and 38 deletions
@@ -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<String> 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<String> scopes = normalizeSet(request.getScope());
if (scopes.isEmpty()) {
scopes = Set.of(McpConstant.Scope.TOOLS_LIST, McpConstant.Scope.TOOLS_CALL);
}
Set<String> 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<String, Object> token(Map<String, String> 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<String, Object> clientCredentialsToken(Map<String, String> 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<String> 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<String, String> 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");
@@ -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
*
@@ -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
*
@@ -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
*
@@ -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
*
@@ -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;
@@ -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;
@@ -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;
@@ -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;
+6
View File
@@ -74,6 +74,12 @@
<artifactId>mybatis-plus-annotation</artifactId>
</dependency>
<!-- Jackson Annotation: @JsonValue keeps enum JSON on its db/wire value (e.g. grantType lowercase) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
</dependency>
</dependencies>
</project>
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<McpAuditStatusEnum> any = Arrays.stream(values())
.filter(type -> type.getValue().equals(value))
.findFirst();
return any.orElse(null);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<McpConfirmationStatusEnum> any = Arrays.stream(values())
.filter(type -> type.getValue().equals(value))
.findFirst();
return any.orElse(null);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<McpRiskLevelEnum> any = Arrays.stream(values())
.filter(type -> type.getValue().equals(value))
.findFirst();
return any.orElse(null);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<OAuthClientTypeEnum> any = Arrays.stream(values())
.filter(type -> type.getValue().equals(value))
.findFirst();
return any.orElse(null);
}
}
@@ -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 <https://www.gnu.org/licenses/>.
*/
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<OAuthGrantTypeEnum> any = Arrays.stream(values())
.filter(type -> type.getValue().equals(value))
.findFirst();
return any.orElse(null);
}
}
@@ -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,