Update MI authz logic

This commit is contained in:
Tuan Dang
2023-11-27 14:19:10 +07:00
parent 99c41bb63b
commit 6579b3c93f
15 changed files with 49 additions and 29 deletions
+1 -1
View File
@@ -26,7 +26,7 @@ declare module "jsonwebtoken" {
refreshVersion?: number; refreshVersion?: number;
} }
export interface MachineRefreshTokenJwtPayload extends jwt.JwtPayload { export interface MachineRefreshTokenJwtPayload extends jwt.JwtPayload {
serviceTokenDataId: string; _id: string;
authTokenType: string; authTokenType: string;
tokenVersion: number; tokenVersion: number;
} }
@@ -4,7 +4,7 @@ import { IUser, Key, Membership, MembershipOrg, User, Workspace } from "../../mo
import { EventType, Role } from "../../ee/models"; import { EventType, Role } from "../../ee/models";
import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership"; import { deleteMembership as deleteMember, findMembership } from "../../helpers/membership";
import { sendMail } from "../../helpers/nodemailer"; import { sendMail } from "../../helpers/nodemailer";
import { ACCEPTED, ADMIN, CUSTOM, MEMBER, VIEWER } from "../../variables"; import { ACCEPTED, ADMIN, CUSTOM, MEMBER, NO_ACCESS, VIEWER } from "../../variables";
import { getSiteURL } from "../../config"; import { getSiteURL } from "../../config";
import { EEAuditLogService, EELicenseService } from "../../ee/services"; import { EEAuditLogService, EELicenseService } from "../../ee/services";
import { validateRequest } from "../../helpers/validation"; import { validateRequest } from "../../helpers/validation";
@@ -129,7 +129,7 @@ export const changeMembershipRole = async (req: Request, res: Response) => {
ProjectPermissionSub.Member ProjectPermissionSub.Member
); );
const isCustomRole = ![ADMIN, MEMBER, VIEWER].includes(role); const isCustomRole = ![ADMIN, MEMBER, VIEWER, NO_ACCESS].includes(role);
if (isCustomRole) { if (isCustomRole) {
const wsRole = await Role.findOne({ const wsRole = await Role.findOne({
slug: role, slug: role,
@@ -516,7 +516,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
body: { body: {
role role
} }
} = await validateRequest(reqValidator.AddWorkspaceServiceMemberV2, req); } = await validateRequest(reqValidator.AddMachineToWorkspaceV2, req);
const { permission } = await getAuthDataProjectPermissions({ const { permission } = await getAuthDataProjectPermissions({
authData: req.authData, authData: req.authData,
@@ -574,7 +574,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
role: customRole ? CUSTOM : role, role: customRole ? CUSTOM : role,
customRole customRole
}).save(); }).save();
return res.status(200).send({ return res.status(200).send({
machineMembership machineMembership
}); });
@@ -592,7 +592,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
body: { body: {
role role
} }
} = await validateRequest(reqValidator.AddWorkspaceServiceMemberV2, req); } = await validateRequest(reqValidator.UpdateMachineWorkspaceRoleV2, req);
const { permission } = await getAuthDataProjectPermissions({ const { permission } = await getAuthDataProjectPermissions({
authData: req.authData, authData: req.authData,
@@ -650,7 +650,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
}, },
{ {
role, role: customRole ? CUSTOM : role,
customRole customRole
}, },
{ {
@@ -672,7 +672,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
export const deleteMachineFromWorkspace = async (req: Request, res: Response) => { export const deleteMachineFromWorkspace = async (req: Request, res: Response) => {
const { const {
params: { workspaceId, machineId } params: { workspaceId, machineId }
} = await validateRequest(reqValidator.DeleteWorkspaceServiceMemberV2, req); } = await validateRequest(reqValidator.DeleteMachineFromWorkspaceV2, req);
const { permission } = await getAuthDataProjectPermissions({ const { permission } = await getAuthDataProjectPermissions({
authData: req.authData, authData: req.authData,
@@ -705,7 +705,7 @@ export const addMachineToWorkspace = async (req: Request, res: Response) => {
export const getWorkspaceMachineMemberships = async (req: Request, res: Response) => { export const getWorkspaceMachineMemberships = async (req: Request, res: Response) => {
const { const {
params: { workspaceId } params: { workspaceId }
} = await validateRequest(reqValidator.GetWorkspaceServiceMembersV2, req); } = await validateRequest(reqValidator.GetWorkspaceMachineMembersV2, req);
const { permission } = await getAuthDataProjectPermissions({ const { permission } = await getAuthDataProjectPermissions({
authData: req.authData, authData: req.authData,
@@ -82,7 +82,7 @@ export const updateRole = async (req: Request, res: Response) => {
body: { name, description, slug, permissions, workspaceId, orgId } body: { name, description, slug, permissions, workspaceId, orgId }
} = await validateRequest(UpdateRoleSchema, req); } = await validateRequest(UpdateRoleSchema, req);
const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule const isOrgRole = !workspaceId; // if workspaceid is provided then its a workspace rule
if (isOrgRole) { if (isOrgRole) {
const { permission } = await getUserOrgPermissions(req.user.id, orgId); const { permission } = await getUserOrgPermissions(req.user.id, orgId);
if (permission.cannot(OrgPermissionActions.Edit, OrgPermissionSubjects.Role)) { if (permission.cannot(OrgPermissionActions.Edit, OrgPermissionSubjects.Role)) {
@@ -757,19 +757,19 @@ export const getWorkspaceAuditLogActorFilterOpts = async (req: Request, res: Res
} }
})); }));
const serviceV3Actors: MachineActor[] = ( const machineActors: MachineActor[] = (
await MachineIdentity.find({ await MachineIdentity.find({
workspace: new Types.ObjectId(workspaceId) workspace: new Types.ObjectId(workspaceId)
}) })
).map((machineIdentity) => ({ ).map((machineIdentity) => ({
type: ActorType.MACHINE, type: ActorType.MACHINE,
metadata: { metadata: {
serviceId: machineIdentity._id.toString(), machineId: machineIdentity._id.toString(),
name: machineIdentity.name name: machineIdentity.name
} }
})); }));
const actors = [...userActors, ...serviceActors, ...serviceV3Actors]; const actors = [...userActors, ...serviceActors, ...machineActors];
return res.status(200).send({ return res.status(200).send({
actors actors
@@ -43,7 +43,7 @@ import { ForbiddenError } from "@casl/ability";
refreshToken refreshToken
} }
} = await validateRequest(reqValidator.RefreshTokenV3, req); } = await validateRequest(reqValidator.RefreshTokenV3, req);
const decodedToken = <jwt.MachineRefreshTokenJwtPayload>( const decodedToken = <jwt.MachineRefreshTokenJwtPayload>(
jwt.verify(refreshToken, await getAuthSecret()) jwt.verify(refreshToken, await getAuthSecret())
); );
@@ -51,7 +51,7 @@ import { ForbiddenError } from "@casl/ability";
if (decodedToken.authTokenType !== AuthTokenType.MACHINE_REFRESH_TOKEN) throw UnauthorizedRequestError(); if (decodedToken.authTokenType !== AuthTokenType.MACHINE_REFRESH_TOKEN) throw UnauthorizedRequestError();
let machineIdentity = await MachineIdentity.findOne({ let machineIdentity = await MachineIdentity.findOne({
_id: new Types.ObjectId(decodedToken.serviceTokenDataId), _id: new Types.ObjectId(decodedToken._id),
isActive: true isActive: true
}); });
@@ -101,7 +101,7 @@ import { ForbiddenError } from "@casl/ability";
response.accessToken = createToken({ response.accessToken = createToken({
payload: { payload: {
_id: machineIdentity._id.toString(), // TODO: fix this _id: machineIdentity._id.toString(),
authTokenType: AuthTokenType.MACHINE_ACCESS_TOKEN, authTokenType: AuthTokenType.MACHINE_ACCESS_TOKEN,
tokenVersion: machineIdentity.tokenVersion tokenVersion: machineIdentity.tokenVersion
}, },
@@ -332,6 +332,7 @@ export const getAuthDataProjectPermissions = async ({
role = machineMembership.role; role = machineMembership.role;
customRole = machineMembership.customRole; customRole = machineMembership.customRole;
break; break;
} }
default: default:
+13 -3
View File
@@ -280,7 +280,7 @@ export const ToggleAutoCapitalizationV2 = z.object({
}) })
}); });
export const AddWorkspaceServiceMemberV2 = z.object({ export const AddMachineToWorkspaceV2 = z.object({
params: z.object({ params: z.object({
workspaceId: z.string().trim(), workspaceId: z.string().trim(),
machineId: z.string().trim() machineId: z.string().trim()
@@ -290,14 +290,24 @@ export const AddWorkspaceServiceMemberV2 = z.object({
}) })
}); });
export const DeleteWorkspaceServiceMemberV2 = z.object({ export const UpdateMachineWorkspaceRoleV2 = z.object({
params: z.object({
workspaceId: z.string().trim(),
machineId: z.string().trim()
}),
body: z.object({
role: z.string().trim().min(1).default(NO_ACCESS),
})
});
export const DeleteMachineFromWorkspaceV2 = z.object({
params: z.object({ params: z.object({
workspaceId: z.string().trim(), workspaceId: z.string().trim(),
machineId: z.string().trim() machineId: z.string().trim()
}) })
}); });
export const GetWorkspaceServiceMembersV2 = z.object({ export const GetWorkspaceMachineMembersV2 = z.object({
params: z.object({ params: z.object({
workspaceId: z.string().trim() workspaceId: z.string().trim()
}), }),
@@ -31,7 +31,7 @@ export const MachineIdentitySection = withPermission(
machineId machineId
}); });
createNotification({ createNotification({
text: "Successfully deleted service token v3", text: "Successfully deleted machine identity",
type: "success" type: "success"
}); });
@@ -39,7 +39,7 @@ export const MachineIdentitySection = withPermission(
} catch (err) { } catch (err) {
console.error(err); console.error(err);
createNotification({ createNotification({
text: "Failed to delete service token v3", text: "Failed to delete machine identity",
type: "error" type: "error"
}); });
} }
@@ -103,13 +103,13 @@ export const MachineIdentityTable = ({
// }); // });
// createNotification({ // createNotification({
// text: `Successfully ${isActive ? "enabled" : "disabled"} service token v3`, // text: `Successfully ${isActive ? "enabled" : "disabled"} machine identity`,
// type: "success" // type: "success"
// }); // });
// } catch (err) { // } catch (err) {
// console.log(err); // console.log(err);
// createNotification({ // createNotification({
// text: `Failed to ${isActive ? "enable" : "disable"} service token v3`, // text: `Failed to ${isActive ? "enable" : "disable"} machine identity`,
// type: "error" // type: "error"
// }); // });
// } // }
@@ -84,7 +84,7 @@ const SIMPLE_PERMISSION_OPTIONS = [
] as const; ] as const;
export const OrgRoleModifySection = ({ role, onGoBack }: Props) => { export const OrgRoleModifySection = ({ role, onGoBack }: Props) => {
const isNonEditable = ["owner", "admin", "member"].includes(role?.slug || ""); const isNonEditable = ["owner", "admin", "member", "no-access"].includes(role?.slug || "");
const isNewRole = !role?.slug; const isNewRole = !role?.slug;
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
@@ -33,7 +33,7 @@ export const LogsTableRow = ({
return ( return (
<Td> <Td>
<p>{`${actor.metadata.name}`}</p> <p>{`${actor.metadata.name}`}</p>
<p>Service token V3</p> <p>Machine identity</p>
</Td> </Td>
); );
default: default:
@@ -27,7 +27,7 @@ import { UsePopUpState } from "@app/hooks/usePopUp";
const schema = yup.object({ const schema = yup.object({
machineId: yup.string().required("Machine identity id is required"), machineId: yup.string().required("Machine identity id is required"),
role: yup.string().required("Machine identity role is required") role: yup.string()
}).required(); }).required();
export type FormData = yup.InferType<typeof schema>; export type FormData = yup.InferType<typeof schema>;
@@ -85,10 +85,11 @@ export const AddMachineIdentityModal = ({
role role
}: FormData) => { }: FormData) => {
try { try {
await addMachineToWorkspaceMutateAsync({ await addMachineToWorkspaceMutateAsync({
workspaceId, workspaceId,
machineId, machineId,
role role: role || undefined
}); });
createNotification({ createNotification({
@@ -9,6 +9,7 @@ import {
faLock, faLock,
faNetworkWired, faNetworkWired,
faPuzzlePiece, faPuzzlePiece,
faServer,
faShield, faShield,
faTags, faTags,
faUser, faUser,
@@ -59,6 +60,12 @@ const SINGLE_PERMISSION_LIST = [
icon: faUser, icon: faUser,
formName: "member" formName: "member"
}, },
{
title: "Machine identity management",
subtitle: "Add, view, update and remove machine identities from the project",
icon: faServer,
formName: "machine-identity"
},
{ {
title: "Webhooks", title: "Webhooks",
subtitle: "Webhook management control", subtitle: "Webhook management control",
@@ -109,7 +116,7 @@ type Props = {
}; };
export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => { export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
const isNonEditable = ["admin", "member", "viewer"].includes(role?.slug || ""); const isNonEditable = ["admin", "member", "viewer", "no-access"].includes(role?.slug || "");
const isNewRole = !role?.slug; const isNewRole = !role?.slug;
const { createNotification } = useNotificationContext(); const { createNotification } = useNotificationContext();
@@ -133,7 +140,7 @@ export const ProjectRoleModifySection = ({ role, onGoBack }: Props) => {
const handleRoleUpdate = async (el: TFormSchema) => { const handleRoleUpdate = async (el: TFormSchema) => {
if (!role?._id) return; if (!role?._id) return;
try { try {
await updateRole({ await updateRole({
orgId, orgId,
@@ -33,6 +33,7 @@ export const formSchema = z.object({
.object({ .object({
secrets: z.record(multiEnvPermissionSchema).optional(), secrets: z.record(multiEnvPermissionSchema).optional(),
member: generalPermissionSchema, member: generalPermissionSchema,
"machine-identity": generalPermissionSchema,
role: generalPermissionSchema, role: generalPermissionSchema,
integrations: generalPermissionSchema, integrations: generalPermissionSchema,
webhooks: generalPermissionSchema, webhooks: generalPermissionSchema,