Requested changes

This commit is contained in:
Daniel Hougaard
2024-09-11 12:45:14 +04:00
parent 4dda270e8e
commit ea5e8e29e6
11 changed files with 74 additions and 162 deletions
@@ -3,6 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service"; import { TPermissionServiceFactory } from "../permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
import { TAuditLogDALFactory } from "./audit-log-dal"; import { TAuditLogDALFactory } from "./audit-log-dal";
@@ -11,7 +12,7 @@ import { EventType, TCreateAuditLogDTO, TListProjectAuditLogDTO } from "./audit-
type TAuditLogServiceFactoryDep = { type TAuditLogServiceFactoryDep = {
auditLogDAL: TAuditLogDALFactory; auditLogDAL: TAuditLogDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
auditLogQueue: TAuditLogQueueServiceFactory; auditLogQueue: TAuditLogQueueServiceFactory;
}; };
@@ -45,6 +46,16 @@ export const auditLogServiceFactory = ({
actorOrgId actorOrgId
); );
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.AuditLogs);
} else {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
} }
// If project ID is not provided, then we need to return all the audit logs for the organization itself. // If project ID is not provided, then we need to return all the audit logs for the organization itself.
@@ -6,7 +6,7 @@ import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
export type TListProjectAuditLogDTO = { export type TListProjectAuditLogDTO = {
auditLogActor?: string; auditLogActor?: string;
projectId: string | null; projectId?: string;
eventType?: string; eventType?: string;
startDate?: string; startDate?: string;
endDate?: string; endDate?: string;
+1
View File
@@ -464,6 +464,7 @@ export const registerRoutes = async (
userAliasDAL, userAliasDAL,
orgMembershipDAL, orgMembershipDAL,
tokenService, tokenService,
permissionService,
groupProjectDAL, groupProjectDAL,
smtpService, smtpService,
projectMembershipDAL projectMembershipDAL
@@ -121,8 +121,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
endDate: req.query.endDate, endDate: req.query.endDate,
startDate: req.query.startDate || getLastMidnightDateISO(), startDate: req.query.startDate || getLastMidnightDateISO(),
auditLogActor: req.query.actor, auditLogActor: req.query.actor,
actor: req.permission.type, actor: req.permission.type
projectId: null
}); });
return { auditLogs }; return { auditLogs };
} }
+9 -23
View File
@@ -1,6 +1,6 @@
import { z } from "zod"; import { z } from "zod";
import { ProjectsSchema, UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas"; import { UserEncryptionKeysSchema, UsersSchema } from "@app/db/schemas";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter"; import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -151,34 +151,20 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
id: z.string(), id: z.string(),
name: z.string(), name: z.string(),
slug: z.string(), slug: z.string(),
orgId: z.string(), orgId: z.string()
projectMemberships: z.array(
z.object({
id: z.string(),
project: ProjectsSchema.pick({ id: true, name: true, slug: true }),
roles: z.array(
z.object({
id: z.string(),
role: z.string(),
customRoleId: z.string().nullable(),
customRoleName: z.string().nullable(),
customRoleSlug: z.string().nullable(),
temporaryRange: z.string().nullable(),
temporaryMode: z.string().nullable(),
temporaryAccessEndTime: z.string().nullable(),
temporaryAccessStartTime: z.string().nullable(),
isTemporary: z.boolean()
})
)
})
)
}) })
.array() .array()
} }
}, },
onRequest: verifyAuth([AuthMode.JWT]), onRequest: verifyAuth([AuthMode.JWT]),
handler: async (req) => { handler: async (req) => {
const groupMemberships = await server.services.user.listUserGroups(req.params.username, req.permission.orgId); const groupMemberships = await server.services.user.listUserGroups({
username: req.params.username,
actorOrgId: req.permission.orgId,
actorId: req.permission.id,
actorAuthMethod: req.permission.authMethod,
actor: req.permission.type
});
return groupMemberships; return groupMemberships;
} }
@@ -106,100 +106,14 @@ export const groupProjectDALFactory = (db: TDbClient) => {
db.raw("?", [orgId]) db.raw("?", [orgId])
); );
}) })
.leftJoin(
TableName.GroupProjectMembership,
`${TableName.GroupProjectMembership}.groupId`,
`${TableName.Groups}.id`
)
.leftJoin(
TableName.GroupProjectMembershipRole,
`${TableName.GroupProjectMembershipRole}.projectMembershipId`,
`${TableName.GroupProjectMembership}.id`
)
.leftJoin(
TableName.ProjectRoles,
`${TableName.GroupProjectMembershipRole}.customRoleId`,
`${TableName.ProjectRoles}.id`
)
.leftJoin(TableName.Project, `${TableName.GroupProjectMembership}.projectId`, `${TableName.Project}.id`)
.select( .select(
db.ref("id").withSchema(TableName.Groups).as("groupId"), db.ref("id").withSchema(TableName.Groups),
db.ref("name").withSchema(TableName.Groups).as("groupName"), db.ref("name").withSchema(TableName.Groups),
db.ref("slug").withSchema(TableName.Groups).as("groupSlug"), db.ref("slug").withSchema(TableName.Groups),
db.ref("orgId").withSchema(TableName.Groups), db.ref("orgId").withSchema(TableName.Groups)
db.ref("id").withSchema(TableName.GroupProjectMembership).as("projectMembershipId"),
db.ref("id").withSchema(TableName.Project).as("projectId"),
db.ref("name").withSchema(TableName.Project).as("projectName"),
db.ref("slug").withSchema(TableName.Project).as("projectSlug"),
db.ref("role").withSchema(TableName.GroupProjectMembershipRole),
db.ref("id").withSchema(TableName.GroupProjectMembershipRole).as("membershipRoleId"),
db.ref("customRoleId").withSchema(TableName.GroupProjectMembershipRole),
db.ref("name").withSchema(TableName.ProjectRoles).as("customRoleName"),
db.ref("slug").withSchema(TableName.ProjectRoles).as("customRoleSlug"),
db.ref("temporaryMode").withSchema(TableName.GroupProjectMembershipRole),
db.ref("isTemporary").withSchema(TableName.GroupProjectMembershipRole),
db.ref("temporaryRange").withSchema(TableName.GroupProjectMembershipRole),
db.ref("temporaryAccessStartTime").withSchema(TableName.GroupProjectMembershipRole),
db.ref("temporaryAccessEndTime").withSchema(TableName.GroupProjectMembershipRole)
); );
const groupsWithProjects = sqlNestRelationships({ return docs;
data: docs,
parentMapper: ({ groupId, groupName, groupSlug, orgId: organizationId }) => ({
id: groupId,
name: groupName,
slug: groupSlug,
orgId: organizationId,
projectMemberships: []
}),
key: "groupId",
childrenMapper: [
{
label: "projectMemberships" as const,
key: "projectMembershipId",
mapper: ({ projectId, projectName, projectSlug, projectMembershipId }) => ({
id: projectMembershipId,
project: {
id: projectId,
name: projectName,
slug: projectSlug
},
roles: []
}),
childrenMapper: [
{
label: "roles" as const,
key: "membershipRoleId",
mapper: ({
role,
customRoleId,
customRoleName,
customRoleSlug,
membershipRoleId,
temporaryRange,
temporaryMode,
temporaryAccessEndTime,
temporaryAccessStartTime,
isTemporary
}) => ({
id: membershipRoleId,
role,
customRoleId,
customRoleName,
customRoleSlug,
temporaryRange,
temporaryMode,
temporaryAccessEndTime,
temporaryAccessStartTime,
isTemporary
})
}
]
}
]
});
return groupsWithProjects;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "FindByUserId" }); throw new DatabaseError({ error, name: "FindByUserId" });
} }
+21 -3
View File
@@ -1,4 +1,8 @@
import { ForbiddenError } from "@casl/ability";
import { SecretKeyEncoding } from "@app/db/schemas"; import { SecretKeyEncoding } from "@app/db/schemas";
import { OrgPermissionActions, OrgPermissionSubjects } from "@app/ee/services/permission/org-permission";
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption"; import { infisicalSymmetricDecrypt } from "@app/lib/crypto/encryption";
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service"; import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
@@ -11,6 +15,7 @@ import { AuthMethod } from "../auth/auth-type";
import { TGroupProjectDALFactory } from "../group-project/group-project-dal"; import { TGroupProjectDALFactory } from "../group-project/group-project-dal";
import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal"; import { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
import { TUserDALFactory } from "./user-dal"; import { TUserDALFactory } from "./user-dal";
import { TListUserGroupsDTO } from "./user-types";
type TUserServiceFactoryDep = { type TUserServiceFactoryDep = {
userDAL: Pick< userDAL: Pick<
@@ -33,6 +38,7 @@ type TUserServiceFactoryDep = {
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser" | "validateTokenForUser">; tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser" | "validateTokenForUser">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">; projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
smtpService: Pick<TSmtpService, "sendMail">; smtpService: Pick<TSmtpService, "sendMail">;
permissionService: TPermissionServiceFactory;
}; };
export type TUserServiceFactory = ReturnType<typeof userServiceFactory>; export type TUserServiceFactory = ReturnType<typeof userServiceFactory>;
@@ -44,7 +50,8 @@ export const userServiceFactory = ({
projectMembershipDAL, projectMembershipDAL,
groupProjectDAL, groupProjectDAL,
tokenService, tokenService,
smtpService smtpService,
permissionService
}: TUserServiceFactoryDep) => { }: TUserServiceFactoryDep) => {
const sendEmailVerificationCode = async (username: string) => { const sendEmailVerificationCode = async (username: string) => {
const user = await userDAL.findOne({ username }); const user = await userDAL.findOne({ username });
@@ -298,13 +305,24 @@ export const userServiceFactory = ({
return updatedOrgMembership.projectFavorites; return updatedOrgMembership.projectFavorites;
}; };
const listUserGroups = async (username: string, orgId: string) => { const listUserGroups = async ({ username, actorOrgId, actor, actorId, actorAuthMethod }: TListUserGroupsDTO) => {
const user = await userDAL.findOne({ const user = await userDAL.findOne({
username username
}); });
const memberships = await groupProjectDAL.findByUserId(user.id, orgId); // This makes it so the user can always read information about themselves, but no one else if they don't have the Members Read permission.
if (user.id !== actorId) {
const { permission } = await permissionService.getOrgPermission(
actor,
actorId,
actorOrgId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Read, OrgPermissionSubjects.Member);
}
const memberships = await groupProjectDAL.findByUserId(user.id, actorOrgId);
return memberships; return memberships;
}; };
+5
View File
@@ -0,0 +1,5 @@
import { TOrgPermission } from "@app/lib/types";
export type TListUserGroupsDTO = {
username: string;
} & Omit<TOrgPermission, "orgId">;
-20
View File
@@ -40,24 +40,4 @@ export type TGroupWithProjectMemberships = {
name: string; name: string;
slug: string; slug: string;
orgId: string; orgId: string;
projectMemberships: {
id: string;
project: {
id: string;
name: string;
slug: string;
};
roles: {
id: string;
role: string;
customRoleId: string | null;
customRoleName: string | null;
customRoleSlug: string | null;
temporaryRange: string | null;
temporaryMode: string | null;
temporaryAccessEndTime: string | null;
temporaryAccessStartTime: string | null;
isTemporary: boolean;
}[];
}[];
}; };
@@ -20,26 +20,24 @@ export const UserGroupsRow = ({ group, handlePopUpOpen }: Props) => {
> >
<Td>{group.name}</Td> <Td>{group.name}</Td>
<Td> <Td>
{true && ( <div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100"> <Tooltip content="Unassign user from group">
<Tooltip content="Remove user from group"> <IconButton
<IconButton colorSchema="danger"
colorSchema="danger" ariaLabel="copy icon"
ariaLabel="copy icon" variant="plain"
variant="plain" className="group relative"
className="group relative" onClick={(e) => {
onClick={(e) => { e.stopPropagation();
e.stopPropagation(); handlePopUpOpen("removeUserFromGroup", {
handlePopUpOpen("removeUserFromGroup", { groupSlug: group.slug
groupSlug: group.slug });
}); }}
}} >
> <FontAwesomeIcon icon={faTrash} />
<FontAwesomeIcon icon={faTrash} /> </IconButton>
</IconButton> </Tooltip>
</Tooltip> </div>
</div>
)}
</Td> </Td>
</Tr> </Tr>
</> </>
@@ -52,7 +52,7 @@ export const UserGroupsSection = ({ orgMembership }: Props) => {
<DeleteActionModal <DeleteActionModal
isOpen={popUp.removeUserFromGroup.isOpen} isOpen={popUp.removeUserFromGroup.isOpen}
title="Are you sure want to remove user from group?" title="Are you sure want to unassign user from group?"
onChange={(isOpen) => handlePopUpToggle("removeUserFromGroup", isOpen)} onChange={(isOpen) => handlePopUpToggle("removeUserFromGroup", isOpen)}
deleteKey="confirm" deleteKey="confirm"
onDeleteApproved={() => { onDeleteApproved={() => {