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

View File

@@ -3,6 +3,7 @@ import { ForbiddenError } from "@casl/ability";
import { getConfig } from "@app/lib/config/env";
import { BadRequestError } from "@app/lib/errors";
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
import { TPermissionServiceFactory } from "../permission/permission-service";
import { ProjectPermissionActions, ProjectPermissionSub } from "../permission/project-permission";
import { TAuditLogDALFactory } from "./audit-log-dal";
@@ -11,7 +12,7 @@ import { EventType, TCreateAuditLogDTO, TListProjectAuditLogDTO } from "./audit-
type TAuditLogServiceFactoryDep = {
auditLogDAL: TAuditLogDALFactory;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission" | "getOrgPermission">;
auditLogQueue: TAuditLogQueueServiceFactory;
};
@@ -45,6 +46,16 @@ export const auditLogServiceFactory = ({
actorOrgId
);
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.

View File

@@ -6,7 +6,7 @@ import { PkiItemType } from "@app/services/pki-collection/pki-collection-types";
export type TListProjectAuditLogDTO = {
auditLogActor?: string;
projectId: string | null;
projectId?: string;
eventType?: string;
startDate?: string;
endDate?: string;

View File

@@ -464,6 +464,7 @@ export const registerRoutes = async (
userAliasDAL,
orgMembershipDAL,
tokenService,
permissionService,
groupProjectDAL,
smtpService,
projectMembershipDAL

View File

@@ -121,8 +121,7 @@ export const registerOrgRouter = async (server: FastifyZodProvider) => {
endDate: req.query.endDate,
startDate: req.query.startDate || getLastMidnightDateISO(),
auditLogActor: req.query.actor,
actor: req.permission.type,
projectId: null
actor: req.permission.type
});
return { auditLogs };
}

View File

@@ -1,6 +1,6 @@
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 { logger } from "@app/lib/logger";
import { authRateLimit, readLimit, writeLimit } from "@app/server/config/rateLimiter";
@@ -151,34 +151,20 @@ export const registerUserRouter = async (server: FastifyZodProvider) => {
id: z.string(),
name: z.string(),
slug: 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()
})
)
})
)
orgId: z.string()
})
.array()
}
},
onRequest: verifyAuth([AuthMode.JWT]),
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;
}

View File

@@ -106,100 +106,14 @@ export const groupProjectDALFactory = (db: TDbClient) => {
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(
db.ref("id").withSchema(TableName.Groups).as("groupId"),
db.ref("name").withSchema(TableName.Groups).as("groupName"),
db.ref("slug").withSchema(TableName.Groups).as("groupSlug"),
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)
db.ref("id").withSchema(TableName.Groups),
db.ref("name").withSchema(TableName.Groups),
db.ref("slug").withSchema(TableName.Groups),
db.ref("orgId").withSchema(TableName.Groups)
);
const groupsWithProjects = sqlNestRelationships({
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;
return docs;
} catch (error) {
throw new DatabaseError({ error, name: "FindByUserId" });
}

View File

@@ -1,4 +1,8 @@
import { ForbiddenError } from "@casl/ability";
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 { BadRequestError } from "@app/lib/errors";
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 { TProjectMembershipDALFactory } from "../project-membership/project-membership-dal";
import { TUserDALFactory } from "./user-dal";
import { TListUserGroupsDTO } from "./user-types";
type TUserServiceFactoryDep = {
userDAL: Pick<
@@ -33,6 +38,7 @@ type TUserServiceFactoryDep = {
tokenService: Pick<TAuthTokenServiceFactory, "createTokenForUser" | "validateTokenForUser">;
projectMembershipDAL: Pick<TProjectMembershipDALFactory, "find">;
smtpService: Pick<TSmtpService, "sendMail">;
permissionService: TPermissionServiceFactory;
};
export type TUserServiceFactory = ReturnType<typeof userServiceFactory>;
@@ -44,7 +50,8 @@ export const userServiceFactory = ({
projectMembershipDAL,
groupProjectDAL,
tokenService,
smtpService
smtpService,
permissionService
}: TUserServiceFactoryDep) => {
const sendEmailVerificationCode = async (username: string) => {
const user = await userDAL.findOne({ username });
@@ -298,13 +305,24 @@ export const userServiceFactory = ({
return updatedOrgMembership.projectFavorites;
};
const listUserGroups = async (username: string, orgId: string) => {
const listUserGroups = async ({ username, actorOrgId, actor, actorId, actorAuthMethod }: TListUserGroupsDTO) => {
const user = await userDAL.findOne({
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;
};

View File

@@ -0,0 +1,5 @@
import { TOrgPermission } from "@app/lib/types";
export type TListUserGroupsDTO = {
username: string;
} & Omit<TOrgPermission, "orgId">;

View File

@@ -40,24 +40,4 @@ export type TGroupWithProjectMemberships = {
name: string;
slug: 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;
}[];
}[];
};

View File

@@ -20,26 +20,24 @@ export const UserGroupsRow = ({ group, handlePopUpOpen }: Props) => {
>
<Td>{group.name}</Td>
<Td>
{true && (
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
<Tooltip content="Remove user from group">
<IconButton
colorSchema="danger"
ariaLabel="copy icon"
variant="plain"
className="group relative"
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("removeUserFromGroup", {
groupSlug: group.slug
});
}}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</Tooltip>
</div>
)}
<div className="opacity-0 transition-opacity duration-300 group-hover:opacity-100">
<Tooltip content="Unassign user from group">
<IconButton
colorSchema="danger"
ariaLabel="copy icon"
variant="plain"
className="group relative"
onClick={(e) => {
e.stopPropagation();
handlePopUpOpen("removeUserFromGroup", {
groupSlug: group.slug
});
}}
>
<FontAwesomeIcon icon={faTrash} />
</IconButton>
</Tooltip>
</div>
</Td>
</Tr>
</>

View File

@@ -52,7 +52,7 @@ export const UserGroupsSection = ({ orgMembership }: Props) => {
<DeleteActionModal
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)}
deleteKey="confirm"
onDeleteApproved={() => {