Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions api/sdk/src/main/java/com/ke/bella/openapi/apikey/AkOperation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.ke.bella.openapi.apikey;

public enum AkOperation {
QUERY,
RESET,
RENAME,
UPDATE_ROLE,
CERTIFY,
UPDATE_QUOTA,
UPDATE_QPS,
CHANGE_STATUS,
CREATE_CHILD,
TRANSFER,
VIEW_TRANSFER_HISTORY,
BIND_SERVICE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package com.ke.bella.openapi.apikey;

import com.ke.bella.openapi.common.EntityConstants;

import java.util.Collections;
import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

public final class AkPermissionMatrix {

private AkPermissionMatrix() {}

static final Set<AkOperation> ALL_OPS =
Collections.unmodifiableSet(EnumSet.allOf(AkOperation.class));
static final Set<AkOperation> NO_OPS =
Collections.unmodifiableSet(EnumSet.noneOf(AkOperation.class));

private static final Map<String, Map<AkRelation, Set<AkOperation>>> MATRIX;

static {
Map<String, Map<AkRelation, Set<AkOperation>>> m = new HashMap<>();

// all:全部放行
Map<AkRelation, Set<AkOperation>> allMap = new EnumMap<>(AkRelation.class);
for (AkRelation r : AkRelation.values()) allMap.put(r, ALL_OPS);
m.put(EntityConstants.ALL, Collections.unmodifiableMap(allMap));

// console
Set<AkOperation> consoleSameOrg = Collections.unmodifiableSet(EnumSet.of(
AkOperation.QUERY, AkOperation.RESET, AkOperation.RENAME, AkOperation.CHANGE_STATUS));
Set<AkOperation> consoleUnrelated = Collections.unmodifiableSet(EnumSet.of(AkOperation.QUERY));
Map<AkRelation, Set<AkOperation>> consoleMap = new EnumMap<>(AkRelation.class);
consoleMap.put(AkRelation.OWNER, ALL_OPS);
consoleMap.put(AkRelation.MANAGER, ALL_OPS);
consoleMap.put(AkRelation.SAME_ORG, consoleSameOrg);
consoleMap.put(AkRelation.UNRELATED, consoleUnrelated);
m.put(EntityConstants.CONSOLE, Collections.unmodifiableMap(consoleMap));

// high
Set<AkOperation> highOwner = Collections.unmodifiableSet(EnumSet.of(
AkOperation.QUERY, AkOperation.RESET, AkOperation.RENAME,
AkOperation.CHANGE_STATUS, AkOperation.CREATE_CHILD, AkOperation.TRANSFER,
AkOperation.VIEW_TRANSFER_HISTORY));
Map<AkRelation, Set<AkOperation>> highMap = new EnumMap<>(AkRelation.class);
highMap.put(AkRelation.OWNER, highOwner);
highMap.put(AkRelation.MANAGER, ALL_OPS);
highMap.put(AkRelation.SAME_ORG, NO_OPS);
highMap.put(AkRelation.UNRELATED, NO_OPS);
m.put(EntityConstants.HIGH, Collections.unmodifiableMap(highMap));

// low
Set<AkOperation> lowOwner = Collections.unmodifiableSet(EnumSet.of(
AkOperation.QUERY, AkOperation.RESET, AkOperation.RENAME,
AkOperation.CHANGE_STATUS, AkOperation.CREATE_CHILD, AkOperation.TRANSFER,
AkOperation.VIEW_TRANSFER_HISTORY));
Map<AkRelation, Set<AkOperation>> lowMap = new EnumMap<>(AkRelation.class);
lowMap.put(AkRelation.OWNER, lowOwner);
lowMap.put(AkRelation.MANAGER, ALL_OPS);
lowMap.put(AkRelation.SAME_ORG, NO_OPS);
lowMap.put(AkRelation.UNRELATED, NO_OPS);
m.put(EntityConstants.LOW, Collections.unmodifiableMap(lowMap));

MATRIX = Collections.unmodifiableMap(m);
}

public static boolean isAllowed(String roleCode, AkRelation relation, AkOperation operation) {
Map<AkRelation, Set<AkOperation>> inner = MATRIX.get(roleCode);
if (inner == null) return false;
Set<AkOperation> ops = inner.get(relation);
return ops != null && ops.contains(operation);
}

public static Set<AkOperation> getAllowedOps(String roleCode, AkRelation relation) {
Map<AkRelation, Set<AkOperation>> inner = MATRIX.get(roleCode);
if (inner == null) return NO_OPS;
Set<AkOperation> ops = inner.get(relation);
return ops != null ? ops : NO_OPS;
}
}
19 changes: 19 additions & 0 deletions api/sdk/src/main/java/com/ke/bella/openapi/apikey/AkRelation.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.ke.bella.openapi.apikey;

public enum AkRelation {
/** 调用方是目标 AK 的所有者(ownerCode 相同) */
OWNER,
/**
* 调用方被授权为目标 AK 的 manager。
* 当前版本预留,resolveRelation 中暂不实际解析,始终跳过。
* 未来只需实现 isManager() 并取消注释即可生效。
*/
MANAGER,
/**
* 调用方与目标 AK 同属一个 org。
* 沿用现有 TODO 逻辑(orgCodes 始终为空),当前不会命中此分支。
*/
SAME_ORG,
/** 调用方与目标 AK 无关联 */
UNRELATED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
package com.ke.bella.openapi.service;

import com.ke.bella.openapi.BellaContext;
import com.ke.bella.openapi.EndpointContext;
import com.ke.bella.openapi.Operator;
import com.ke.bella.openapi.apikey.AkOperation;
import com.ke.bella.openapi.apikey.AkPermissionMatrix;
import com.ke.bella.openapi.apikey.AkRelation;
import com.ke.bella.openapi.apikey.ApikeyInfo;
import com.ke.bella.openapi.common.exception.BellaException;
import com.ke.bella.openapi.tables.pojos.ApikeyDB;
import org.springframework.stereotype.Component;

import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.Set;

import static com.ke.bella.openapi.common.EntityConstants.CONSOLE;
import static com.ke.bella.openapi.common.EntityConstants.ORG;
import static com.ke.bella.openapi.common.EntityConstants.PERSON;
import static com.ke.bella.openapi.common.EntityConstants.SYSTEM;

@Component
public class AkPermissionChecker {

/** operator(Console 登录用户)作为 AK 所有者时,可执行的操作集合 */
private static final Set<AkOperation> OPERATOR_OWNER_OPS = Collections.unmodifiableSet(
EnumSet.of(
AkOperation.QUERY,
AkOperation.RESET,
AkOperation.RENAME,
AkOperation.CERTIFY,
AkOperation.UPDATE_QPS,
AkOperation.CHANGE_STATUS,
AkOperation.TRANSFER,
AkOperation.VIEW_TRANSFER_HISTORY
)
);

/**
* 主入口:接受 ApikeyDB(从 queryByUniqueKey 得到)
*/
public void check(ApikeyDB targetDb, AkOperation operation) {
ApikeyInfo caller = EndpointContext.getApikeyIgnoreNull();

if (caller == null) {
checkOperatorPermission(targetDb, operation);
return;
}

// system ownerType:目标非 system 则全放行,否则拒绝
if (SYSTEM.equals(caller.getOwnerType())) {
if (SYSTEM.equals(targetDb.getOwnerType())) {
throw new BellaException.AuthorizationException("没有操作权限");
}
return;
}

AkRelation relation = resolveRelation(caller, targetDb);
if (!AkPermissionMatrix.isAllowed(caller.getRoleCode(), relation, operation)) {
throw new BellaException.AuthorizationException("没有操作权限");
}
}

/**
* 重载:接受 ApikeyInfo(从 queryByCode 得到,transferApikeyOwner / getTransferHistory 使用)
*/
public void check(ApikeyInfo targetInfo, AkOperation operation) {
ApikeyDB db = new ApikeyDB();
db.setOwnerType(targetInfo.getOwnerType());
db.setOwnerCode(targetInfo.getOwnerCode());
check(db, operation);
}

/**
* operator(Console 登录用户,无 AK)的独立权限分支。
* ownerCode == userId 且 ownerType ∈ {PERSON, CONSOLE} → 允许 OPERATOR_OWNER_OPS 中的操作。
* 其余情况拒绝(预留:未来 Manager 关系在此扩展)。
*/
private void checkOperatorPermission(ApikeyDB targetDb, AkOperation operation) {
Operator op = BellaContext.getOperator();
String userId = op.getUserId().toString();

boolean isOwner = (PERSON.equals(targetDb.getOwnerType()) || CONSOLE.equals(targetDb.getOwnerType()))
&& userId.equals(targetDb.getOwnerCode());

if (isOwner) {
if (!OPERATOR_OWNER_OPS.contains(operation)) {
throw new BellaException.AuthorizationException("没有操作权限");
}
return;
}

// 非自己的 AK:当前不允许(TODO: isManager 时开放部分权限)
throw new BellaException.AuthorizationException("没有操作权限");
}

/**
* 解析调用方 AK 与目标 AK 的关系。
* 优先级:OWNER > MANAGER(预留)> SAME_ORG > UNRELATED
*/
AkRelation resolveRelation(ApikeyInfo caller, ApikeyDB target) {
if (isOwner(caller, target)) {
return AkRelation.OWNER;
}

// MANAGER 预留扩展点:实现 isManager() 后取消注释
// if (isManager(caller, target)) { return AkRelation.MANAGER; }

if (isSameOrg(caller, target)) {
return AkRelation.SAME_ORG;
}

return AkRelation.UNRELATED;
}

private static final Set<String> PERSONAL_OWNER_TYPES = Collections.unmodifiableSet(
new HashSet<>(Arrays.asList(PERSON, CONSOLE)));

private boolean isOwner(ApikeyInfo caller, ApikeyDB target) {
if (!caller.getOwnerCode().equals(target.getOwnerCode())) {
return false;
}
// person 和 console 同属一个自然人,ownerType 可以互通
if (PERSONAL_OWNER_TYPES.contains(caller.getOwnerType())
&& PERSONAL_OWNER_TYPES.contains(target.getOwnerType())) {
return true;
}
return caller.getOwnerType().equals(target.getOwnerType());
}

/**
* SAME_ORG 判断:保留现有 TODO 结构,orgCodes 始终为空集,当前不会命中。
* 未来在此填充 org 查询逻辑即可。
*/
private boolean isSameOrg(ApikeyInfo caller, ApikeyDB target) {
if (!ORG.equals(target.getOwnerType())) {
return false;
}
// TODO: 获取调用方所属的所有 orgCodes
Set<String> orgCodes = new HashSet<>();
return orgCodes.contains(target.getOwnerCode());
}
}
Loading
Loading