fix: allow protected hub self access management
This commit is contained in:
+88
-18
@@ -817,7 +817,7 @@ app.post("/api/admin/task-manager/workspace-memberships/ensure", requireLauncher
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageUser(req, res, user.id)) {
|
||||
if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageAccessForUser(req, res, user.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -878,7 +878,7 @@ app.post("/api/admin/task-manager/workspace-memberships/remove", requireLauncher
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageUser(req, res, user.id)) {
|
||||
if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageAccessForUser(req, res, user.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -959,7 +959,7 @@ app.post("/api/admin/task-manager/project-memberships/ensure", requireLauncherAd
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageUser(req, res, user.id)) {
|
||||
if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageAccessForUser(req, res, user.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1031,7 +1031,7 @@ app.post("/api/admin/task-manager/project-memberships/remove", requireLauncherAd
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageUser(req, res, user.id)) {
|
||||
if (!assertAdminCanManageClient(req, res, client.id) || !assertAdminCanManageAccessForUser(req, res, user.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1190,7 +1190,15 @@ app.patch("/api/admin/memberships/:membershipId", requireLauncherAdmin, asyncRou
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assertAdminCanManageMembership(req, res, membership)) {
|
||||
if (!assertAdminCanManageMembership(req, res, membership, { allowProtectedSelf: true })) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isProtectedSelfManageRequest(req, membership.userId) && req.body?.status && req.body.status !== "active") {
|
||||
res.status(403).json({
|
||||
error: "protected_self_status_locked",
|
||||
message: "Защищённый пользователь может менять себе роли, но не может отключить собственный контур.",
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1369,6 +1377,10 @@ app.post("/api/admin/groups", requireLauncherAdmin, asyncRoute(async (req, res)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assertAdminCanManageProtectedGroupMembers(req, res, [], req.body?.memberIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await controlPlaneStore.createGroup(req.body, req.nodedcSession.user);
|
||||
const syncResult = await syncUsersToAuthentik(result.data, result.group.memberIds, req.nodedcSession.user);
|
||||
publishControlPlaneEvent("admin.group.created", syncResult.userIds);
|
||||
@@ -1389,6 +1401,11 @@ app.patch("/api/admin/groups/:groupId", requireLauncherAdmin, asyncRoute(async (
|
||||
}
|
||||
|
||||
const previousMemberIds = group.memberIds;
|
||||
|
||||
if (!assertAdminCanManageProtectedGroupMembers(req, res, previousMemberIds, req.body?.memberIds)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await controlPlaneStore.updateGroup(req.params.groupId, req.body, req.nodedcSession.user);
|
||||
const syncResult = await syncUsersToAuthentik(
|
||||
result.data,
|
||||
@@ -1412,6 +1429,10 @@ app.delete("/api/admin/groups/:groupId", requireLauncherAdmin, asyncRoute(async
|
||||
return;
|
||||
}
|
||||
|
||||
if (!assertAdminCanManageProtectedGroupMembers(req, res, group.memberIds, [])) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = await controlPlaneStore.deleteGroup(req.params.groupId, req.nodedcSession.user);
|
||||
const syncResult = await syncUsersToAuthentik(result.data, result.group.memberIds, req.nodedcSession.user);
|
||||
publishControlPlaneEvent("admin.group.deleted", syncResult.userIds);
|
||||
@@ -1445,7 +1466,7 @@ app.delete("/api/admin/services/:serviceId", requireLauncherAdmin, requireRootLa
|
||||
app.post("/api/admin/access/grants", requireLauncherAdmin, asyncRoute(async (req, res) => {
|
||||
const snapshot = controlPlaneStore.getSnapshot(req.nodedcSession.user);
|
||||
|
||||
if (!assertAdminCanManageGrantTarget(req, res, snapshot.data, req.body?.targetType, req.body?.targetId)) {
|
||||
if (!assertAdminCanManageGrantTarget(req, res, snapshot.data, req.body?.targetType, req.body?.targetId, { allowProtectedSelf: true })) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1460,7 +1481,7 @@ app.post("/api/admin/access/grants", requireLauncherAdmin, asyncRoute(async (req
|
||||
}));
|
||||
|
||||
app.post("/api/admin/access/exceptions", requireLauncherAdmin, asyncRoute(async (req, res) => {
|
||||
if (!assertAdminCanManageUser(req, res, req.body?.userId)) {
|
||||
if (!assertAdminCanManageAccessForUser(req, res, req.body?.userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1471,7 +1492,7 @@ app.post("/api/admin/access/exceptions", requireLauncherAdmin, asyncRoute(async
|
||||
}));
|
||||
|
||||
app.post("/api/admin/access/user-service", requireLauncherAdmin, asyncRoute(async (req, res) => {
|
||||
if (!assertAdminCanManageUser(req, res, req.body?.userId)) {
|
||||
if (!assertAdminCanManageAccessForUser(req, res, req.body?.userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -1492,7 +1513,7 @@ app.post("/api/admin/access/user-service", requireLauncherAdmin, asyncRoute(asyn
|
||||
}));
|
||||
|
||||
app.post("/api/admin/access/service-modules", requireLauncherAdmin, asyncRoute(async (req, res) => {
|
||||
if (!assertAdminCanManageClient(req, res, req.body?.clientId) || !assertAdminCanManageUser(req, res, req.body?.userId)) {
|
||||
if (!assertAdminCanManageClient(req, res, req.body?.clientId) || !assertAdminCanManageAccessForUser(req, res, req.body?.userId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -3183,8 +3204,16 @@ function canAdminManageClient(req, clientId) {
|
||||
return Boolean(req.nodedcAdminScope?.isRoot || req.nodedcAdminScope?.clientIds.has(clientId));
|
||||
}
|
||||
|
||||
function canAdminManageUser(req, userId) {
|
||||
if (protectedLauncherUserIds.has(userId)) {
|
||||
function isProtectedLauncherUser(userId) {
|
||||
return protectedLauncherUserIds.has(userId);
|
||||
}
|
||||
|
||||
function isProtectedSelfManageRequest(req, userId) {
|
||||
return isProtectedLauncherUser(userId) && req.nodedcAdminScope?.actorId === userId;
|
||||
}
|
||||
|
||||
function canAdminManageUser(req, userId, options = {}) {
|
||||
if (isProtectedLauncherUser(userId) && !(options.allowProtectedSelf && isProtectedSelfManageRequest(req, userId))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -3206,8 +3235,8 @@ function assertAdminCanManageClient(req, res, clientId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function assertAdminCanManageUser(req, res, userId) {
|
||||
if (canAdminManageUser(req, userId)) {
|
||||
function assertAdminCanManageUser(req, res, userId, options = {}) {
|
||||
if (canAdminManageUser(req, userId, options)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3215,17 +3244,48 @@ function assertAdminCanManageUser(req, res, userId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
function assertAdminCanManageMembership(req, res, membership) {
|
||||
function assertAdminCanManageAccessForUser(req, res, userId) {
|
||||
return assertAdminCanManageUser(req, res, userId, { allowProtectedSelf: true });
|
||||
}
|
||||
|
||||
function assertAdminCanManageProtectedGroupMembers(req, res, previousMemberIds = [], nextMemberIds = []) {
|
||||
if (!Array.isArray(nextMemberIds)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
const previous = new Set(previousMemberIds);
|
||||
const next = new Set(nextMemberIds);
|
||||
const changedProtectedUserIds = [...protectedLauncherUserIds].filter((userId) => previous.has(userId) !== next.has(userId));
|
||||
const blockedUserId = changedProtectedUserIds.find((userId) => !isProtectedSelfManageRequest(req, userId));
|
||||
|
||||
if (!blockedUserId) {
|
||||
return true;
|
||||
}
|
||||
|
||||
res.status(403).json({
|
||||
error: "protected_user_group_membership_locked",
|
||||
message: "Защищённого пользователя может добавлять в группы или удалять из групп только он сам.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
function assertAdminCanManageMembership(req, res, membership, options = {}) {
|
||||
if (!assertAdminCanManageClient(req, res, membership.clientId)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return assertAdminCanManageUser(req, res, membership.userId);
|
||||
return assertAdminCanManageUser(req, res, membership.userId, options);
|
||||
}
|
||||
|
||||
function assertAdminCanManageGrantTarget(req, res, data, targetType, targetId) {
|
||||
function assertAdminCanManageGrantTarget(req, res, data, targetType, targetId, options = {}) {
|
||||
if (req.nodedcAdminScope?.isRoot) {
|
||||
return true;
|
||||
if (targetType === "client") {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (targetType === "user" && (!isProtectedLauncherUser(targetId) || isProtectedSelfManageRequest(req, targetId) || !options.allowProtectedSelf)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (targetType === "client") {
|
||||
@@ -3240,11 +3300,21 @@ function assertAdminCanManageGrantTarget(req, res, data, targetType, targetId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const blockedProtectedUserId = group.memberIds.find((userId) => isProtectedLauncherUser(userId) && !isProtectedSelfManageRequest(req, userId));
|
||||
|
||||
if (blockedProtectedUserId) {
|
||||
res.status(403).json({
|
||||
error: "protected_group_access_locked",
|
||||
message: "Гранты группы с защищённым пользователем может менять только сам защищённый пользователь.",
|
||||
});
|
||||
return false;
|
||||
}
|
||||
|
||||
return assertAdminCanManageClient(req, res, group.clientId);
|
||||
}
|
||||
|
||||
if (targetType === "user") {
|
||||
return assertAdminCanManageUser(req, res, targetId);
|
||||
return assertAdminCanManageUser(req, res, targetId, options);
|
||||
}
|
||||
|
||||
res.status(403).json({ error: "Недостаточно прав для управления этим доступом" });
|
||||
|
||||
Reference in New Issue
Block a user