Skip to content

Commit

Permalink
fix(auth)!: 认证系统查询已有用户的API归类到创建用户API中 (#313)
Browse files Browse the repository at this point in the history
#307
的设计不够全面。一些第三方的认证系统(例如学校的统一认证系统)一般都不会提供随意查询一个用户是否存在的接口。另外,此API也只会在创建用户的时候用到,用于查询用户是否已经存在,如果认证系统声明不支持创建用户,SCOW也不会主动去查询一个用户是否在认证系统中存在。

所以此PR修改了此设计,使`GET /user`接口从之前必须实现的改变为只有在支持创建用户的时候情况下才要求实现。


此PR还重新组织了自定义认证系统相关的文档,同时把`@scow/lib-auth`的`getUser.ts`文件合并到了`createUser.ts`下。
  • Loading branch information
ddadaal committed Nov 27, 2022
1 parent 5eb6c26 commit 9315a08
Show file tree
Hide file tree
Showing 14 changed files with 262 additions and 261 deletions.
6 changes: 4 additions & 2 deletions apps/auth/src/auth/AuthProvider.ts
Expand Up @@ -17,9 +17,11 @@ export type ChangePasswordResult = "NotFound" | "WrongOldPassword" | "OK";
export interface AuthProvider {
serveLoginHtml: (callbackUrl: string, req: FastifyRequest, rep: FastifyReply) => Promise<void>;
fetchAuthTokenInfo: (token: string, req: FastifyRequest) => Promise<string | undefined>;
getUser: (identityId: string, req: FastifyRequest) => Promise<{ identityId: string } | undefined>;
createUser: {
createUser: ((info: CreateUserInfo, req: FastifyRequest) => Promise<CreateUserResult>);
getUser: (identityId: string, req: FastifyRequest) => Promise<{ identityId: string } | undefined>;
} | undefined,
validateName: undefined | ((identityId: string, name: string, req: FastifyRequest) => Promise<ValidateNameResult>);
createUser: undefined | ((info: CreateUserInfo, req: FastifyRequest) => Promise<CreateUserResult>);
changePassword: undefined | ((id: string, oldPassword: string, newPassword: string,
req: FastifyRequest) => Promise<ChangePasswordResult>);
}
170 changes: 86 additions & 84 deletions apps/auth/src/auth/ldap/index.ts
Expand Up @@ -36,9 +36,6 @@ export const createLdapAuthProvider = (f: FastifyInstance) => {
registerPostHandler(f, ldap);

return <AuthProvider>{
getUser: async (identityId, req) => useLdap(req.log, ldap)(async (client) => (
findUser(req.log, ldap, client, identityId)
)),
serveLoginHtml: (callbackUrl, req, rep) => serveLoginHtml(false, callbackUrl, req, rep),
fetchAuthTokenInfo: async () => undefined,
validateName: ldap.attrs.name
Expand Down Expand Up @@ -71,115 +68,120 @@ export const createLdapAuthProvider = (f: FastifyInstance) => {
return "Match";
});
} : undefined,
createUser: async (info, req) => {
const id = info.id + ldap.addUser.uidStart;

await useLdap(req.log, ldap)(async (client) => {
const userDn =
createUser: {
getUser: async (identityId, req) => useLdap(req.log, ldap)(async (client) => (
findUser(req.log, ldap, client, identityId)
)),
createUser: async (info, req) => {
const id = info.id + ldap.addUser.uidStart;

await useLdap(req.log, ldap)(async (client) => {
const userDn =
`${ldap.addUser.userIdDnKey ?? ldap.attrs.uid}=${info.identityId},` +
`${ldap.addUser.userBase}`;
const userEntry: Record<string, string | string[] | number> = {
[ldap.attrs.uid]: info.identityId,
sn: info.identityId,
loginShell: "/bin/bash",
objectClass: ["inetOrgPerson", "posixAccount", "shadowAccount"],
homeDirectory: parsePlaceholder(ldap.addUser.homeDir, { userId: info.identityId }),
uidNumber: id,
gidNumber: id,
};

if (ldap.attrs.name) {
userEntry[ldap.attrs.name] = info.name;
}
const userEntry: Record<string, string | string[] | number> = {
[ldap.attrs.uid]: info.identityId,
sn: info.identityId,
loginShell: "/bin/bash",
objectClass: ["inetOrgPerson", "posixAccount", "shadowAccount"],
homeDirectory: parsePlaceholder(ldap.addUser.homeDir, { userId: info.identityId }),
uidNumber: id,
gidNumber: id,
};

if (ldap.attrs.mail) {
userEntry[ldap.attrs.mail] = info.mail;
}
if (ldap.attrs.name) {
userEntry[ldap.attrs.name] = info.name;
}

// parse attributes
if (ldap.addUser.extraProps) {
applyExtraProps(userEntry, ldap.addUser.extraProps, userEntry);
}
if (ldap.attrs.mail) {
userEntry[ldap.attrs.mail] = info.mail;
}

const add = promisify(client.add.bind(client));
// parse attributes
if (ldap.addUser.extraProps) {
applyExtraProps(userEntry, ldap.addUser.extraProps, userEntry);
}

const add = promisify(client.add.bind(client));

if (ldap.addUser.groupStrategy === NewUserGroupStrategy.newGroupPerUser) {

req.log.info("ldap.addUser.groupStrategy is newGroupPerUser. Creating new group for the user.");
if (ldap.addUser.groupStrategy === NewUserGroupStrategy.newGroupPerUser) {

const config = ldap.addUser.newGroupPerUser!;
req.log.info("ldap.addUser.groupStrategy is newGroupPerUser. Creating new group for the user.");

const groupDn = `${config.groupIdDnKey ?? ldap.attrs.uid}=${info.identityId},${config.groupBase}`;
const groupEntry = {
objectClass: ["posixGroup"],
memberUid: info.identityId,
gidNumber: id,
};
const config = ldap.addUser.newGroupPerUser!;

userEntry["gidNumber"] = id;
const groupDn = `${config.groupIdDnKey ?? ldap.attrs.uid}=${info.identityId},${config.groupBase}`;
const groupEntry = {
objectClass: ["posixGroup"],
memberUid: info.identityId,
gidNumber: id,
};

if (config.extraProps) {
applyExtraProps(groupEntry, config.extraProps, userEntry);
}
userEntry["gidNumber"] = id;

req.log.info("Adding group %s with entry info %o", groupDn, groupEntry);
await add(groupDn, groupEntry);
if (config.extraProps) {
applyExtraProps(groupEntry, config.extraProps, userEntry);
}

}
req.log.info("Adding group %s with entry info %o", groupDn, groupEntry);
await add(groupDn, groupEntry);

}

if (ldap.addUser.groupStrategy === NewUserGroupStrategy.oneGroupForAllUsers) {
const config = ldap.addUser.oneGroupForAllUsers!;
if (ldap.addUser.groupStrategy === NewUserGroupStrategy.oneGroupForAllUsers) {
const config = ldap.addUser.oneGroupForAllUsers!;

req.log.info("ldap.addUser.groupStrategy is one-group-for-all-users.");
req.log.info("Using existing group %s for the user", config.gidNumber);
req.log.info("ldap.addUser.groupStrategy is one-group-for-all-users.");
req.log.info("Using existing group %s for the user", config.gidNumber);

userEntry["gidNumber"] = config.gidNumber;
}
userEntry["gidNumber"] = config.gidNumber;
}

req.log.info("Adding people %s with entry info %o", userDn, userEntry);
await add(userDn, userEntry);
req.log.info("Adding people %s with entry info %o", userDn, userEntry);
await add(userDn, userEntry);

// set password as admin user
await modifyPassword(userDn, undefined, info.password, client);
// set password as admin user
await modifyPassword(userDn, undefined, info.password, client);

// Add user to ldap group
const addUserToLdapGroup = ldap.addUser.addUserToLdapGroup;
// Add user to ldap group
const addUserToLdapGroup = ldap.addUser.addUserToLdapGroup;

if (addUserToLdapGroup) {
if (addUserToLdapGroup) {
// get existing members
req.log.info("Adding %s to group %s", userDn, addUserToLdapGroup);

const members = await searchOne(req.log, client, addUserToLdapGroup, {
attributes: ["member"],
}, (entry) => {
const member = entry.attributes.find((x) => x.json.type === "member");
if (!member) {
return undefined;
req.log.info("Adding %s to group %s", userDn, addUserToLdapGroup);

const members = await searchOne(req.log, client, addUserToLdapGroup, {
attributes: ["member"],
}, (entry) => {
const member = entry.attributes.find((x) => x.json.type === "member");
if (!member) {
return undefined;
}

return { members: member.json.vals };
});

if (!members) {
req.log.error("Didn't find LDAP group %s", addUserToLdapGroup);
throw { code: "INTERNAL_ERROR" };
}

return { members: member.json.vals };
});

if (!members) {
req.log.error("Didn't find LDAP group %s", addUserToLdapGroup);
throw { code: "INTERNAL_ERROR" };
// add the dn of the new user to the value
const modify = promisify(client.modify.bind(client));
await modify(addUserToLdapGroup, new ldapjs.Change({
operation: "add",
modification: {
"member": members.members.concat(userDn),
},
}));
}

// add the dn of the new user to the value
const modify = promisify(client.modify.bind(client));
await modify(addUserToLdapGroup, new ldapjs.Change({
operation: "add",
modification: {
"member": members.members.concat(userDn),
},
}));
}


});
});

return "OK";
return "OK";
},
},
changePassword: async (id, oldPassword, newPassword, req) => {
return useLdap(req.log, ldap)(async (client) => {
Expand Down
9 changes: 0 additions & 9 deletions apps/auth/src/auth/ssh/index.ts
@@ -1,11 +1,9 @@
import { loggedExec, sshConnect } from "@scow/lib-ssh";
import { FastifyInstance } from "fastify";
import { AuthProvider } from "src/auth/AuthProvider";
import { serveLoginHtml } from "src/auth/loginHtml";
import { registerPostHandler } from "src/auth/ssh/postHandler";
import { authConfig, SshConfigSchema } from "src/config/auth";
import { clusters } from "src/config/clusters";
import { rootKeyPair } from "src/config/env";
import { ensureNotUndefined } from "src/utils/validations";

function checkLoginNode(sshConfig: SshConfigSchema) {
Expand Down Expand Up @@ -40,13 +38,6 @@ export const createSshAuthProvider = (f: FastifyInstance) => {
return <AuthProvider>{
serveLoginHtml: (callbackUrl, req, rep) => serveLoginHtml(false, callbackUrl, req, rep),
fetchAuthTokenInfo: async () => undefined,
getUser: async (identityId, req) => {
return await sshConnect(loginNode, "root", rootKeyPair, req.log, async (ssh) => {
return loggedExec(ssh, req.log, true, "id", [identityId])
.then(() => ({ identityId }))
.catch(() => undefined);
});
},
validateName: undefined,
createUser: undefined,
changePassword: undefined,
Expand Down
2 changes: 1 addition & 1 deletion apps/auth/src/routes/createUser.ts
Expand Up @@ -48,7 +48,7 @@ export const createUserRoute = fp(async (f) => {

const { ...rest } = req.body;

const result = await f.auth.createUser(rest, req);
const result = await f.auth.createUser.createUser(rest, req);

return await rep.status(codes[result]).send();
},
Expand Down
6 changes: 5 additions & 1 deletion apps/auth/src/routes/getUser.ts
Expand Up @@ -8,6 +8,7 @@ const QuerystringSchema = Type.Object({
const ResponsesSchema = Type.Object({
200: Type.Object({ user: Type.Object({ identityId: Type.String() }) }),
404: Type.Object({ code: Type.Literal("USER_NOT_FOUND") }),
501: Type.Null({ description: "此功能在当前服务器配置下不可用" }),
});

/**
Expand All @@ -26,10 +27,13 @@ export const getUserRoute = fp(async (f) => {
},
},
async (req, rep) => {
if (!f.auth.createUser) {
return await rep.code(501).send(null);
}

const { identityId } = req.query;

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

if (result) {
return rep.code(200).send({ user: { identityId: result.identityId } });
Expand Down
22 changes: 0 additions & 22 deletions apps/auth/tests/ssh.test.ts
Expand Up @@ -60,25 +60,3 @@ it("fails to login with wrong credentials", async () => {

expect(resp.statusCode).toBe(403);
});

it("gets user info", async () => {
const resp = await server.inject({
method: "GET",
url: "/user",
query: { identityId: username },
});

expect(resp.statusCode).toBe(200);
expect(resp.json()).toEqual({ user: { identityId: username } });
});

it("returns 404 if user doesn't exist", async () => {
const resp = await server.inject({
method: "GET",
url: "/user",
query: { identityId: username + "wrong" },
});

expect(resp.statusCode).toBe(404);
expect(resp.json()).toEqual({ code: "USER_NOT_FOUND" });
});
4 changes: 4 additions & 0 deletions docs/docs/common/auth/custom/_category_.json
@@ -0,0 +1,4 @@
{
"label": "自定义认证系统",
"position": 5
}

0 comments on commit 9315a08

Please sign in to comment.