Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(mis): 删除账户和用户-不删除数据 #1360

Closed
wants to merge 27 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2469bfb
feat(mis): 用户表增加状态字段何删除状态备注,账户表增加已删除状态值
cuvalign Jul 5, 2024
af8e57c
Merge branch 'master' into feat-delete-user-and-account
cuvalign Jul 8, 2024
a6a3605
feat(mis): 租户管理员删除用户模态框UI
cuvalign Jul 9, 2024
25df93e
feat(mis): 删除用户接口框架
cuvalign Jul 10, 2024
2c10f94
Merge branch 'master' into feat-delete-user-and-account
cuvalign Jul 10, 2024
52fb1f8
Merge branch 'master' into feat-delete-user-and-account
cuvalign Jul 11, 2024
00d70e5
feat(mis): 删除前判断有无未结束作业并一键封锁用户所有账户
cuvalign Jul 16, 2024
dc7b4fd
feat(mis): 一键删除用户所有账户关系
cuvalign Jul 17, 2024
e95eb7f
Merge branch 'master' into feat-delete-user-and-account
cuvalign Jul 17, 2024
132c629
feat(mis): 删除前确认用户是否为账户拥有者,不再保留一键封锁用户以及检测账户是否封锁功能
cuvalign Jul 18, 2024
615ae17
feat(mis): 根据7.18讨论需求调整UI逻辑
cuvalign Jul 22, 2024
d593947
feat(mis): 新增删除账户功能与UI框架,合并删除用户与账户UI公共组件,整合删除模态框国际化
cuvalign Jul 23, 2024
f59ab8a
feat(mis): 账户列表屏蔽已删除账户的各种操作,增加删除账户的tab,增加导出账户列表中拥有者ID和姓名的适配
cuvalign Jul 24, 2024
efa2748
feat: 删除用户账户列表操作UI屏蔽与账户移除拥有者以外用户
cuvalign Aug 5, 2024
82fd7d2
feat(mis&auth): 删除用户报错优化,认证系统修改用户shell为nologin
cuvalign Aug 6, 2024
3b6c1e8
feat(mis): 优化删除用户账户的httperror处理,调度器适配器删除用户账户,账户用户管理新增已删除用户报错
cuvalign Aug 8, 2024
1e5e290
feat(mis): 自行配置用户删除用户名标识符后缀,仪表盘已删除账户筛除
cuvalign Aug 9, 2024
bad690f
feat(mis): 删除账户时,即时屏蔽侧边栏菜单栏已删除的账户,防止URL进入用户管理造成破坏性修改。
cuvalign Aug 13, 2024
f9aae1a
feat(mis): 第一次梳理涉及账户用户修改状态相关接口,增加已删除状态判断
cuvalign Aug 14, 2024
e9a2cde
feat(mis): 账户管理url拦截与多用户操作不同页面时拦截
cuvalign Aug 15, 2024
f9a96b6
Merge branch 'master' into feat-delete-user-and-account
cuvalign Aug 15, 2024
27dbd03
feat&fix(auth&mis): 完善与修复删除用户账户相关测试文件
cuvalign Aug 15, 2024
e5de33e
Merge branch 'master' into feat-delete-user-and-account
cuvalign Aug 20, 2024
d70bb9b
增加changeset
cuvalign Aug 20, 2024
9055b36
Merge remote-tracking branch 'private-scow/master' into feat-delete-u…
cuvalign Aug 30, 2024
8958b93
合并的二次校正
cuvalign Aug 30, 2024
a0e9eee
合并的第三次校正
cuvalign Aug 30, 2024
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
13 changes: 13 additions & 0 deletions .changeset/lazy-lions-raise.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"@scow/lib-operation-log": minor
"@scow/audit-server": minor
"@scow/test-adapter": minor
"@scow/mis-server": minor
"@scow/demo-vagrant": minor
"@scow/mis-web": minor
"@scow/auth": minor
"@scow/lib-auth": minor
"@scow/cli": minor
---

新增删除用户账户功能以及用户账户的删除状态带来的其他相关接口与测试文件完善
5 changes: 5 additions & 0 deletions .changeset/nasty-hornets-yawn.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@scow/grpc-api": minor
---

新增删除用户账户相关接口
5 changes: 5 additions & 0 deletions .changeset/nine-owls-remember.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@scow/config": minor
---

新增 ldap 的用户 loginShell 属性与删除用户时删除标识配置
5 changes: 5 additions & 0 deletions .changeset/orange-vans-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@scow/scheduler-adapter-protos": minor
---

**删除账户用户功能**需要**1.7.0 及以上版本**的接口
7 changes: 7 additions & 0 deletions .changeset/thin-vans-rhyme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@scow/audit-server": patch
"@scow/mis-server": patch
"@scow/mis-web": patch
---

账户列表导出时增加拥有者 ID 和姓名筛选,操作日志修正为导出账户
2 changes: 2 additions & 0 deletions apps/audit-server/src/services/statistic.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ export const statisticServiceServer = plugin((server) => {
const misOperationType: OperationType[] = [
"setJobTimeLimit",
"createUser",
"deleteUser",
"addUserToAccount",
"removeUserFromAccount",
"setAccountAdmin",
Expand All @@ -109,6 +110,7 @@ export const statisticServiceServer = plugin((server) => {
"unsetTenantFinance",
"tenantChangePassword",
"createAccount",
"deleteAccount",
"addAccountToWhitelist",
"removeAccountFromWhitelist",
"accountPay",
Expand Down
1 change: 1 addition & 0 deletions apps/auth/config/auth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,7 @@ ldap:
uid: uid
name: cn
mail: mail
loginShell: loginShell

ssh:
baseNode: localhost:22222
Expand Down
2 changes: 2 additions & 0 deletions apps/auth/src/auth/AuthProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export type CreateUserResult = "AlreadyExists" | "OK";
export type ChangePasswordResult = "NotFound" | "OK";
export type CheckPasswordResult = "NotFound" | "Match" | "NotMatch";
export type ChangeEmailResult = "NotFound" | "OK";
export type DeleteUserResult = "NotFound" | "OK" | "Faild";

export interface UserInfo {
identityId: string;
Expand All @@ -46,4 +47,5 @@ export interface AuthProvider {
=> Promise<CheckPasswordResult>);
changeEmail: undefined | ((id: string, newEmail: string,
req: FastifyRequest) => Promise<ChangeEmailResult>);
deleteUser: undefined | ((identityId: string, req: FastifyRequest) => Promise<DeleteUserResult>);
}
68 changes: 68 additions & 0 deletions apps/auth/src/auth/ldap/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Copyright (c) 2022 Peking University and Peking University Institute for Computing and Digital Economy
* SCOW is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/

/**
* References
* https://datatracker.ietf.org/doc/html/rfc3062
* https://stackoverflow.com/questions/65745679/how-do-i-pass-parameters-to-the-ldapjs-exop-function
*/

import { FastifyBaseLogger } from "fastify";
import ldapjs from "ldapjs";
import { useLdap } from "src/auth/ldap/helpers";
import { LdapConfigSchema } from "src/config/auth";
import { promisify } from "util";

function handleIfInvalidCredentials(e: any) {
if (e.message === "Invalid Credentials") {
return false;
} else {
throw e;
}
}

export async function modifyNoLoginBase(
userId: string, client: ldapjs.Client,
): Promise<boolean> {

try {
const modify = promisify(client.modify.bind(client));
await modify(userId, new ldapjs.Change({
operation: "replace",
modification: {
"loginShell": "/sbin/nologin",
},
}),
);
return true;

} catch (e: any) {
return handleIfInvalidCredentials(e);
}
}

export async function modifyNoLogin(
log: FastifyBaseLogger,
ldap: LdapConfigSchema,
userDn: string,
): Promise<boolean> {
try {

return await useLdap(log, ldap)(async (client) => {

await modifyNoLoginBase(userDn, client);
return true;
});
} catch (e: any) {
return handleIfInvalidCredentials(e);
}
}
3 changes: 2 additions & 1 deletion apps/auth/src/auth/ldap/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -132,8 +132,9 @@ export const extractUserInfoFromEntry = (

const name = config.attrs.name ? takeOne(extractAttr(entry, config.attrs.name)) : undefined;
const mail = config.attrs.mail ? takeOne(extractAttr(entry, config.attrs.mail)) : undefined;
const loginShell = config.attrs.loginShell ? takeOne(extractAttr(entry, config.attrs.loginShell)) : undefined;

return { identityId, name, mail };
return { identityId, name, mail, loginShell };
};

export function takeOne(val: string | string[] | undefined) {
Expand Down
13 changes: 13 additions & 0 deletions apps/auth/src/auth/ldap/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import { FastifyInstance } from "fastify";
import { AuthProvider } from "src/auth/AuthProvider";
import { createUser } from "src/auth/ldap/createUser";
import { modifyNoLogin } from "src/auth/ldap/delete";
import { modifyEmailAsSelf } from "src/auth/ldap/email";
import { findUser, useLdap } from "src/auth/ldap/helpers";
import { checkPassword, modifyPassword } from "src/auth/ldap/password";
Expand Down Expand Up @@ -70,6 +71,18 @@ export const createLdapAuthProvider = (f: FastifyInstance) => {
return result ? "OK" : "Wrong";
});
},
deleteUser: async (identityId, req) => {
return useLdap(req.log, ldap)(async (client) => {
const user = await findUser(req.log, ldap, client, identityId);
if (!user) {
return "NotFound";
}

const result = await modifyNoLogin(req.log, ldap, user.dn);

return result ? "OK" : "Faild";
});
},
} as AuthProvider;

};
2 changes: 1 addition & 1 deletion apps/auth/src/auth/ldap/postHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ export function registerPostHandler(f: FastifyInstance, ldapConfig: LdapConfigSc

const user = await findUser(logger, ldapConfig, client, username);

if (!user) {
if (!user || user.loginShell === "/sbin/nologin") { // 用户删除即禁止用户登录后视为不存在该用户
logger.info("Didn't find user with %s=%s", ldapConfig.attrs.uid, username);
await serveLoginHtml(true, callbackUrl, req, res);
return;
Expand Down
1 change: 1 addition & 0 deletions apps/auth/src/auth/ssh/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ export const createSshAuthProvider = (f: FastifyInstance) => {
changePassword: undefined,
checkPassword: undefined,
changeEmail: undefined,
deleteUser: undefined,
} satisfies AuthProvider;

};
1 change: 1 addition & 0 deletions apps/auth/src/config/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,7 @@ export const LdapConfigSchema = Type.Object({
3. 管理系统添加用户时,不验证ID与姓名是否匹配
` })),
mail: Type.Optional(Type.String({ description: "LDAP中对应用户的邮箱的属性名。可不填。此字段只用于在创建用户的时候把邮件信息填入LDAP。" })),
loginShell: Type.Optional(Type.String({ description: "LDAP中对应用户在 Unix/Linux 系统上的默认shel。当前仅用于判断用户是否被禁止登录" })),
}, { description: "属性映射" }),
}, { description: "LDAP配置" });

Expand Down
2 changes: 2 additions & 0 deletions apps/auth/src/routes/capabilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const CapabilitiesSchema = Type.Object({
getUser: Type.Optional(Type.Boolean({ description: "是否可以查询用户" })),
accountUserRelation: Type.Optional(Type.Boolean({ description: "是否可以管理账户用户关系" })),
checkPassword: Type.Optional(Type.Boolean({ description: "是否可以验证密码" })),
deleteUser: Type.Optional(Type.Boolean({ description: "是否可以删除用户" })),
});

export type Capabilities = Static<typeof CapabilitiesSchema>;
Expand Down Expand Up @@ -51,6 +52,7 @@ export const getCapabilitiesRoute = fp(async (f) => {
changeEmail: provider.changeEmail !== undefined,
getUser: provider.getUser !== undefined,
accountUserRelation: false,
deleteUser: provider.deleteUser !== undefined,
};
},
);
Expand Down
60 changes: 60 additions & 0 deletions apps/auth/src/routes/deleteUser.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/**
* Copyright (c) 2022 Peking University and Peking University Institute for Computing and Digital Economy
* SCOW is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/

import { Static, Type } from "@sinclair/typebox";
import fp from "fastify-plugin";
import { DeleteUserResult } from "src/auth/AuthProvider";

const QuerystringSchema = Type.Object({
identityId: Type.String({ description: "用户ID" }),
});

const ResponsesSchema = Type.Object({
204: Type.Null({ description: "删除成功" }),
404: Type.Null({ description: "未找到该用户" }),
501: Type.Null({ description: "不支持ldap禁止用户登录" }),
});

const codes: Record<DeleteUserResult, number> = {
NotFound: 404,
OK: 204,
Faild: 501,
};

/**
* 删除用户,其实是改变用户状态
*/
export const deleteUserRoute = fp(async (f) => {
f.delete<{
Querystring: Static<typeof QuerystringSchema>
Responses: Static<typeof ResponsesSchema>,
}>(
"/user/delete",
{
schema: {
querystring: QuerystringSchema,
response: ResponsesSchema.properties,
},
},
async (req, rep) => {
if (!f.auth.deleteUser) {
return await rep.code(501).send({ code: "NOT_SUPPORTED" });
}

const { identityId } = req.query;

const result = await f.auth.deleteUser(identityId, req);

return await rep.status(codes[result]).send(null);
},
);
});
2 changes: 2 additions & 0 deletions apps/auth/src/routes/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { changeEmailRoute } from "src/routes/changeEmail";
import { changePasswordRoute } from "src/routes/changePassword";
import { checkPasswordRoute } from "src/routes/checkPassword";
import { createUserRoute } from "src/routes/createUser";
import { deleteUserRoute } from "src/routes/deleteUser";
import { getUserRoute } from "src/routes/getUser";
import { logoutRoute } from "src/routes/logout";

Expand All @@ -33,4 +34,5 @@ export const routes = [
getUserRoute,
changeEmailRoute,
checkPasswordRoute,
deleteUserRoute,
];
53 changes: 53 additions & 0 deletions apps/auth/tests/ldap/ldap.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ it("creates user and group if groupStrategy is newGroupPerUser", async () => {
expect(responseUser).toEqual({
dn: userDn,
identityId: user.identityId,
loginShell: "/bin/bash",
mail: savedUserMail,
name: user.name,
});
Expand Down Expand Up @@ -377,3 +378,55 @@ it("change password", async () => {
expect(notExistedResp.statusCode).toBe(404);
expect(notExistedResp.json()).toBe(null);
});

it("delete user", async () => {
await createUser();

const { payload, headers } = createFormData({
username: user.identityId,
password: user.password,
callbackUrl,
token: user.captchaToken,
code: user.captchaCode,
});
await saveCaptchaText(server, user.captchaCode, user.captchaToken);

const resp = await server.inject({
method: "POST",
url: "/public/auth",
payload,
headers,
});

expect(resp.statusCode).toBe(302);

const deleteUserResp = await server.inject({
method: "DELETE",
url: "/user/delete",
query: { identityId: user.identityId },
});

expect(deleteUserResp.statusCode).toBe(204);

const newResp = await server.inject({
method: "POST",
url: "/public/auth",
payload,
headers,
});

expect(newResp.statusCode).toBe(400);
// 只是设置了loginshell,并没有在ldap中真正清除用户
const userInfo = await server.inject({
method: "GET",
url: "/user",
query: { identityId: user.identityId },
});

expect(userInfo.statusCode).toBe(200);
expect(userInfo.json()).toEqual({ user: {
identityId: user.identityId,
name: user.name,
mail: savedUserMail,
} });
});
2 changes: 2 additions & 0 deletions apps/cli/assets/init-full/config/auth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ authType: ssh

# # LDAP中对应用户的邮箱的属性名。可不填。此字段只用于在创建用户的时候把邮件信息填入LDAP。
# # mail: mail
# # LDAP中对应用户在 Unix/Linux 系统上的默认shel。当前仅用于判断用户是否被禁止登录。
# # loginShell: loginShell

# # 添加用户的相关配置。可不填,不填的话SCOW不支持创建用户。
# addUser:
Expand Down
1 change: 1 addition & 0 deletions apps/cli/assets/init/config/auth.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ ldap:
uid: uid
name: cn
mail: mail
loginShell: loginShell

addUser:
userBase: "ou=People,ou=hpc,o=pku"
Expand Down
1 change: 1 addition & 0 deletions apps/mis-server/src/entities/Account.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ export enum AccountState {
NORMAL = "NORMAL",
FROZEN = "FROZEN",
BLOCKED_BY_ADMIN = "BLOCKED_BY_ADMIN",
DELETED = "DELETED",
}

@Entity()
Expand Down
Loading
Loading