refactor: use permission check service for isTeamAdmin (#24026)
* saml * addGuests.handler.ts * rename canAccess to canAccessOrganization and use organization.read * update usages * team update handler * team member invite * event type update * remove isTeamAdmin util * updateInternalNotesPresets * setInviteExpiration * event type update * deleteInvite * createInvite * team publish * fixes * final * address feedback * update test
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { canAccess } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { canAccessOrganization } from "@calcom/features/ee/sso/lib/saml";
|
||||
import prisma from "@calcom/prisma";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
@@ -16,7 +16,7 @@ const userCanCreateTeamGroupMapping = async (
|
||||
});
|
||||
}
|
||||
|
||||
const { message, access } = await canAccess(user, organizationId);
|
||||
const { message, access } = await canAccessOrganization(user, organizationId);
|
||||
if (!access) {
|
||||
throw new TRPCError({
|
||||
code: "BAD_REQUEST",
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import type { SAMLSSORecord, OIDCSSORecord } from "@boxyhq/saml-jackson";
|
||||
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { HOSTED_CAL_FEATURES } from "@calcom/lib/constants";
|
||||
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
export const samlDatabaseUrl = process.env.SAML_DATABASE_URL || "";
|
||||
export const isSAMLLoginEnabled = samlDatabaseUrl.length > 0;
|
||||
@@ -28,7 +29,7 @@ export const isSAMLAdmin = (email: string) => {
|
||||
return false;
|
||||
};
|
||||
|
||||
export const canAccess = async (user: { id: number; email: string }, teamId: number | null) => {
|
||||
export const canAccessOrganization = async (user: { id: number; email: string }, teamId: number | null) => {
|
||||
const { id: userId, email } = user;
|
||||
|
||||
if (!isSAMLLoginEnabled) {
|
||||
@@ -40,7 +41,22 @@ export const canAccess = async (user: { id: number; email: string }, teamId: num
|
||||
|
||||
// Hosted
|
||||
if (HOSTED_CAL_FEATURES) {
|
||||
if (teamId === null || !(await isTeamAdmin(userId, teamId))) {
|
||||
if (teamId === null) {
|
||||
return {
|
||||
message: "dont_have_permission",
|
||||
access: false,
|
||||
};
|
||||
}
|
||||
|
||||
const permissionCheckService = new PermissionCheckService();
|
||||
const hasPermission = await permissionCheckService.checkPermission({
|
||||
userId,
|
||||
teamId,
|
||||
permission: "organization.read",
|
||||
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||
});
|
||||
|
||||
if (!hasPermission) {
|
||||
return {
|
||||
message: "dont_have_permission",
|
||||
access: false,
|
||||
|
||||
@@ -385,29 +385,6 @@ export async function getTeamWithoutMembers(args: {
|
||||
};
|
||||
}
|
||||
|
||||
// also returns team
|
||||
export async function isTeamAdmin(userId: number, teamId: number) {
|
||||
const team = await prisma.membership.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
teamId,
|
||||
accepted: true,
|
||||
OR: [{ role: "ADMIN" }, { role: "OWNER" }],
|
||||
},
|
||||
include: {
|
||||
team: {
|
||||
select: {
|
||||
metadata: true,
|
||||
parentId: true,
|
||||
isOrganization: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
if (!team) return false;
|
||||
return team;
|
||||
}
|
||||
|
||||
export async function isTeamOwner(userId: number, teamId: number) {
|
||||
return !!(await prisma.membership.findFirst({
|
||||
where: {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { FeaturesRepository } from "@calcom/features/flags/features.repository";
|
||||
import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations";
|
||||
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
@@ -97,12 +96,23 @@ class PBACRoleManager implements IRoleManager {
|
||||
class LegacyRoleManager implements IRoleManager {
|
||||
public isPBACEnabled = false;
|
||||
async checkPermissionToChangeRole(userId: number, targetId: number, scope: "org" | "team"): Promise<void> {
|
||||
const membership =
|
||||
scope === "team"
|
||||
? !!(await isTeamAdmin(userId, targetId))
|
||||
: !!(await isOrganisationAdmin(userId, targetId));
|
||||
let hasPermission = false;
|
||||
if (scope === "team") {
|
||||
const team = await prisma.membership.findFirst({
|
||||
where: {
|
||||
userId,
|
||||
teamId: targetId,
|
||||
accepted: true,
|
||||
OR: [{ role: "ADMIN" }, { role: "OWNER" }],
|
||||
},
|
||||
});
|
||||
hasPermission = !!team;
|
||||
} else {
|
||||
hasPermission = !!(await isOrganisationAdmin(userId, targetId));
|
||||
}
|
||||
|
||||
// Only OWNER/ADMIN can update role
|
||||
if (!membership) {
|
||||
if (!hasPermission) {
|
||||
throw new RoleManagementError(
|
||||
"Only owners or admin can update roles",
|
||||
RoleManagementErrorCode.UNAUTHORIZED
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
import dayjs from "@calcom/dayjs";
|
||||
import { sendAddGuestsEmails } from "@calcom/emails";
|
||||
import EventManager from "@calcom/features/bookings/lib/EventManager";
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { parseRecurringEvent } from "@calcom/lib/isRecurringEvent";
|
||||
import { getUsersCredentialsIncludeServiceAccountKey } from "@calcom/lib/server/getUsersCredentials";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { isTeamAdmin, isTeamOwner } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { BookingResponses } from "@calcom/prisma/zod-utils";
|
||||
import type { CalendarEvent } from "@calcom/types/Calendar";
|
||||
|
||||
@@ -44,15 +45,21 @@ export const addGuestsHandler = async ({ ctx, input }: AddGuestsOptions) => {
|
||||
|
||||
if (!booking) throw new TRPCError({ code: "NOT_FOUND", message: "booking_not_found" });
|
||||
|
||||
const isTeamAdminOrOwner =
|
||||
(await isTeamAdmin(user.id, booking.eventType?.teamId ?? 0)) ||
|
||||
(await isTeamOwner(user.id, booking.eventType?.teamId ?? 0));
|
||||
|
||||
const isOrganizer = booking.userId === user.id;
|
||||
|
||||
const isAttendee = !!booking.attendees.find((attendee) => attendee.email === user.email);
|
||||
|
||||
if (!isTeamAdminOrOwner && !isOrganizer && !isAttendee) {
|
||||
let hasBookingUpdatePermission = false;
|
||||
if (booking.eventType?.teamId) {
|
||||
const permissionCheckService = new PermissionCheckService();
|
||||
hasBookingUpdatePermission = await permissionCheckService.checkPermission({
|
||||
userId: user.id,
|
||||
teamId: booking.eventType.teamId,
|
||||
permission: "booking.update",
|
||||
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||
});
|
||||
}
|
||||
|
||||
if (!hasBookingUpdatePermission && !isOrganizer && !isAttendee) {
|
||||
throw new TRPCError({ code: "FORBIDDEN", message: "you_do_not_have_permission" });
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { DirectoryType } from "@boxyhq/saml-jackson";
|
||||
|
||||
import jackson from "@calcom/features/ee/sso/lib/jackson";
|
||||
import { canAccess, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { canAccessOrganization, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -21,7 +21,7 @@ export const createHandler = async ({ ctx, input }: Options) => {
|
||||
const { organizationId } = input;
|
||||
const { dsyncController } = await jackson();
|
||||
|
||||
const { message, access } = await canAccess(ctx.user, organizationId);
|
||||
const { message, access } = await canAccessOrganization(ctx.user, organizationId);
|
||||
|
||||
if (!access) {
|
||||
throw new TRPCError({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import jackson from "@calcom/features/ee/sso/lib/jackson";
|
||||
import { canAccess } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { canAccessOrganization } from "@calcom/features/ee/sso/lib/saml";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -18,7 +18,7 @@ type Options = {
|
||||
export const deleteHandler = async ({ ctx, input }: Options) => {
|
||||
const { dsyncController } = await jackson();
|
||||
|
||||
const { message, access } = await canAccess(ctx.user, input.organizationId);
|
||||
const { message, access } = await canAccessOrganization(ctx.user, input.organizationId);
|
||||
|
||||
if (!access) {
|
||||
throw new TRPCError({
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import jackson from "@calcom/features/ee/sso/lib/jackson";
|
||||
import { canAccess } from "@calcom/features/ee/sso/lib/saml";
|
||||
import { canAccessOrganization } from "@calcom/features/ee/sso/lib/saml";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -18,7 +18,7 @@ type Options = {
|
||||
export const getHandler = async ({ ctx, input }: Options) => {
|
||||
const { dsyncController } = await jackson();
|
||||
|
||||
const { message, access } = await canAccess(ctx.user, input.organizationId);
|
||||
const { message, access } = await canAccessOrganization(ctx.user, input.organizationId);
|
||||
|
||||
if (!access) {
|
||||
throw new TRPCError({
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import jackson from "@calcom/features/ee/sso/lib/jackson";
|
||||
import { canAccess, samlProductID, samlTenantID, tenantPrefix } from "@calcom/features/ee/sso/lib/saml";
|
||||
import {
|
||||
canAccessOrganization,
|
||||
samlProductID,
|
||||
samlTenantID,
|
||||
tenantPrefix,
|
||||
} from "@calcom/features/ee/sso/lib/saml";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
@@ -18,7 +23,7 @@ export const deleteHandler = async ({ ctx, input }: DeleteOptions) => {
|
||||
|
||||
const { teamId } = input;
|
||||
|
||||
const { message, access } = await canAccess(ctx.user, teamId);
|
||||
const { message, access } = await canAccessOrganization(ctx.user, teamId);
|
||||
|
||||
if (!access) {
|
||||
throw new TRPCError({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import jackson from "@calcom/features/ee/sso/lib/jackson";
|
||||
import type { SSOConnection } from "@calcom/features/ee/sso/lib/saml";
|
||||
import {
|
||||
canAccess,
|
||||
canAccessOrganization,
|
||||
oidcPath,
|
||||
samlProductID,
|
||||
samlTenantID,
|
||||
@@ -23,7 +23,7 @@ type GetOptions = {
|
||||
export const getHandler = async ({ ctx, input }: GetOptions): Promise<SSOConnection | null> => {
|
||||
const { teamId } = input;
|
||||
|
||||
const { message, access } = await canAccess(ctx.user, teamId);
|
||||
const { message, access } = await canAccessOrganization(ctx.user, teamId);
|
||||
|
||||
if (!access) {
|
||||
throw new TRPCError({
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import jackson from "@calcom/features/ee/sso/lib/jackson";
|
||||
import { canAccess, samlProductID, samlTenantID, tenantPrefix } from "@calcom/features/ee/sso/lib/saml";
|
||||
import {
|
||||
canAccessOrganization,
|
||||
samlProductID,
|
||||
samlTenantID,
|
||||
tenantPrefix,
|
||||
} from "@calcom/features/ee/sso/lib/saml";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
@@ -18,7 +23,7 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
|
||||
const { encodedRawMetadata, teamId } = input;
|
||||
|
||||
const { message, access } = await canAccess(ctx.user, teamId);
|
||||
const { message, access } = await canAccessOrganization(ctx.user, teamId);
|
||||
|
||||
if (!access) {
|
||||
throw new TRPCError({
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import jackson from "@calcom/features/ee/sso/lib/jackson";
|
||||
import { canAccess, samlProductID, samlTenantID, tenantPrefix } from "@calcom/features/ee/sso/lib/saml";
|
||||
import {
|
||||
canAccessOrganization,
|
||||
samlProductID,
|
||||
samlTenantID,
|
||||
tenantPrefix,
|
||||
} from "@calcom/features/ee/sso/lib/saml";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
@@ -16,7 +21,7 @@ type UpdateOIDCOptions = {
|
||||
export const updateOIDCHandler = async ({ ctx, input }: UpdateOIDCOptions) => {
|
||||
const { teamId, clientId, clientSecret, wellKnownUrl } = input;
|
||||
|
||||
const { message, access } = await canAccess(ctx.user, teamId);
|
||||
const { message, access } = await canAccessOrganization(ctx.user, teamId);
|
||||
|
||||
if (!access) {
|
||||
throw new TRPCError({
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { randomBytes } from "crypto";
|
||||
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -18,10 +19,26 @@ type CreateInviteOptions = {
|
||||
|
||||
export const createInviteHandler = async ({ ctx, input }: CreateInviteOptions) => {
|
||||
const { teamId } = input;
|
||||
const membership = await isTeamAdmin(ctx.user.id, teamId);
|
||||
|
||||
if (!membership || !membership?.team) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
const isOrganizationOrATeamInOrganization = !!(membership.team?.parentId || membership.team.isOrganization);
|
||||
const team = await prisma.team.findUnique({
|
||||
where: { id: teamId },
|
||||
select: { parentId: true, isOrganization: true },
|
||||
});
|
||||
|
||||
if (!team) throw new TRPCError({ code: "NOT_FOUND" });
|
||||
|
||||
const permissionCheckService = new PermissionCheckService();
|
||||
const permission = team.isOrganization ? "organization.invite" : "team.invite";
|
||||
const hasInvitePermission = await permissionCheckService.checkPermission({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
permission,
|
||||
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||
});
|
||||
|
||||
if (!hasInvitePermission) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
|
||||
const isOrganizationOrATeamInOrganization = !!(team.parentId || team.isOrganization);
|
||||
|
||||
if (input.token) {
|
||||
const existingToken = await prisma.verificationToken.findFirst({
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -23,12 +24,26 @@ export const deleteInviteHandler = async ({ ctx, input }: DeleteInviteOptions) =
|
||||
select: {
|
||||
teamId: true,
|
||||
id: true,
|
||||
team: {
|
||||
select: { isOrganization: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!verificationToken) throw new TRPCError({ code: "NOT_FOUND" });
|
||||
if (!verificationToken.teamId || !(await isTeamAdmin(ctx.user.id, verificationToken.teamId)))
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
if (!verificationToken.teamId) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
|
||||
const permissionCheckService = new PermissionCheckService();
|
||||
const isOrgContext = !!verificationToken.team?.isOrganization;
|
||||
const permission = isOrgContext ? "organization.invite" : "team.invite";
|
||||
const hasInvitePermission = await permissionCheckService.checkPermission({
|
||||
userId: ctx.user.id,
|
||||
teamId: verificationToken.teamId,
|
||||
permission,
|
||||
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||
});
|
||||
|
||||
if (!hasInvitePermission) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
|
||||
await prisma.verificationToken.delete({ where: { id: verificationToken.id } });
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { describe, it, vi, expect } from "vitest";
|
||||
|
||||
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations";
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -25,12 +24,6 @@ vi.mock("@calcom/prisma", () => {
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@calcom/features/ee/teams/lib/queries", () => {
|
||||
return {
|
||||
isTeamAdmin: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("@calcom/lib/server/queries/organisations", () => {
|
||||
return {
|
||||
isOrganisationAdmin: vi.fn(),
|
||||
@@ -38,42 +31,11 @@ vi.mock("@calcom/lib/server/queries/organisations", () => {
|
||||
};
|
||||
});
|
||||
|
||||
const mockedReturnSuccessCheckPerms = {
|
||||
accepted: true,
|
||||
disableImpersonation: false,
|
||||
id: 1,
|
||||
role: MembershipRole.ADMIN,
|
||||
userId: 1,
|
||||
teamId: 1,
|
||||
createdAt: null,
|
||||
updatedAt: null,
|
||||
customRoleId: null,
|
||||
team: {
|
||||
id: 1,
|
||||
name: "Team A",
|
||||
slug: null,
|
||||
logo: null,
|
||||
appLogo: null,
|
||||
appIconLogo: null,
|
||||
bio: null,
|
||||
hideBranding: false,
|
||||
hideBookATeamMember: false,
|
||||
createdAt: new Date(),
|
||||
brandColor: "#292929",
|
||||
darkBrandColor: "#fafafa",
|
||||
timeZone: "Europe/London",
|
||||
weekStart: "Sunday",
|
||||
theme: null,
|
||||
timeFormat: null,
|
||||
metadata: null,
|
||||
parentId: null,
|
||||
parent: null,
|
||||
isPrivate: false,
|
||||
logoUrl: "",
|
||||
calVideoLogo: "",
|
||||
isOrganization: false,
|
||||
},
|
||||
};
|
||||
vi.mock("@calcom/features/pbac/services/permission-check.service", () => {
|
||||
return {
|
||||
PermissionCheckService: vi.fn(),
|
||||
};
|
||||
});
|
||||
|
||||
const mockedRegularTeam: TeamWithParent = {
|
||||
id: 1,
|
||||
@@ -142,27 +104,45 @@ const userInTeamNotAccepted: UserWithMembership = {
|
||||
describe("Invite Member Utils", () => {
|
||||
describe("ensureAtleastAdminPermissions", () => {
|
||||
it("It should throw an error if the user is not an admin of the ORG", async () => {
|
||||
vi.mocked(isOrganisationAdmin).mockResolvedValue(false);
|
||||
const mockCheckPermission = vi.fn().mockResolvedValue(false);
|
||||
vi.mocked(PermissionCheckService).mockImplementation(
|
||||
() =>
|
||||
({
|
||||
checkPermission: mockCheckPermission,
|
||||
} as any)
|
||||
);
|
||||
|
||||
await expect(ensureAtleastAdminPermissions({ userId: 1, teamId: 1, isOrg: true })).rejects.toThrow(
|
||||
"UNAUTHORIZED"
|
||||
);
|
||||
|
||||
expect(mockCheckPermission).toHaveBeenCalledWith({
|
||||
userId: 1,
|
||||
teamId: 1,
|
||||
permission: "organization.invite",
|
||||
fallbackRoles: ["OWNER", "ADMIN"],
|
||||
});
|
||||
});
|
||||
|
||||
it("It should NOT throw an error if the user is an admin of the ORG", async () => {
|
||||
vi.mocked(isOrganisationAdmin).mockResolvedValue(mockedReturnSuccessCheckPerms);
|
||||
const mockCheckPermission = vi.fn().mockResolvedValue(true);
|
||||
vi.mocked(PermissionCheckService).mockImplementation(
|
||||
() =>
|
||||
({
|
||||
checkPermission: mockCheckPermission,
|
||||
} as any)
|
||||
);
|
||||
|
||||
await expect(
|
||||
ensureAtleastAdminPermissions({ userId: 1, teamId: 1, isOrg: true })
|
||||
).resolves.not.toThrow();
|
||||
});
|
||||
|
||||
it("It should throw an error if the user is not an admin of the team", async () => {
|
||||
vi.mocked(isTeamAdmin).mockResolvedValue(false);
|
||||
await expect(ensureAtleastAdminPermissions({ userId: 1, teamId: 1 })).rejects.toThrow("UNAUTHORIZED");
|
||||
});
|
||||
|
||||
it("It should NOT throw an error if the user is an admin of a team", async () => {
|
||||
vi.mocked(isTeamAdmin).mockResolvedValue(mockedReturnSuccessCheckPerms);
|
||||
await expect(ensureAtleastAdminPermissions({ userId: 1, teamId: 1 })).resolves.not.toThrow();
|
||||
expect(mockCheckPermission).toHaveBeenCalledWith({
|
||||
userId: 1,
|
||||
teamId: 1,
|
||||
permission: "organization.invite",
|
||||
fallbackRoles: ["OWNER", "ADMIN"],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -4,15 +4,14 @@ import type { TFunction } from "i18next";
|
||||
import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { sendTeamInviteEmail } from "@calcom/emails";
|
||||
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
|
||||
import { updateNewTeamMemberEventTypes } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { DEFAULT_SCHEDULE, getAvailabilityFromSchedule } from "@calcom/lib/availability";
|
||||
import { ENABLE_PROFILE_SWITCHER, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { createAProfileForAnExistingUser } from "@calcom/lib/createAProfileForAnExistingUser";
|
||||
import logger from "@calcom/lib/logger";
|
||||
import { safeStringify } from "@calcom/lib/safeStringify";
|
||||
import { getTranslation } from "@calcom/lib/server/i18n";
|
||||
import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations";
|
||||
import { updateNewTeamMemberEventTypes } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { ProfileRepository } from "@calcom/lib/server/repository/profile";
|
||||
import { getParsedTeam } from "@calcom/lib/server/repository/teamUtils";
|
||||
import { UserRepository } from "@calcom/lib/server/repository/user";
|
||||
@@ -66,12 +65,20 @@ export async function ensureAtleastAdminPermissions({
|
||||
teamId: number;
|
||||
isOrg?: boolean;
|
||||
}) {
|
||||
const permissionCheckService = new PermissionCheckService();
|
||||
|
||||
// Checks if the team they are inviting to IS the org. Not a child team
|
||||
if (isOrg) {
|
||||
if (!(await isOrganisationAdmin(userId, teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
} else {
|
||||
// TODO: do some logic here to check if the user is inviting a NEW user to a team that ISNT in the same org
|
||||
if (!(await isTeamAdmin(userId, teamId))) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
// TODO: do some logic here to check if the user is inviting a NEW user to a team that ISNT in the same org
|
||||
const permission = isOrg ? "organization.invite" : "team.invite";
|
||||
const hasInvitePermission = await permissionCheckService.checkPermission({
|
||||
userId,
|
||||
teamId,
|
||||
permission,
|
||||
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||
});
|
||||
|
||||
if (!hasInvitePermission) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { purchaseTeamOrOrgSubscription } from "@calcom/features/ee/teams/lib/payments";
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { Redirect } from "@calcom/lib/redirect";
|
||||
import { isOrganisationAdmin } from "@calcom/lib/server/queries/organisations";
|
||||
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { TeamService } from "@calcom/lib/server/service/teamService";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
import type { TrpcSessionUser } from "../../../types";
|
||||
import type { TPublishInputSchema } from "./publish.schema";
|
||||
import { Prisma } from "@calcom/prisma/client";
|
||||
|
||||
type PublishOptions = {
|
||||
ctx: {
|
||||
@@ -54,10 +54,20 @@ const generateCheckoutSession = async ({
|
||||
|
||||
async function checkPermissions({ ctx, input }: PublishOptions) {
|
||||
const { profile } = ctx.user;
|
||||
if (profile?.organizationId && !isOrganisationAdmin(ctx.user.id, profile.organizationId))
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
if (!profile?.organizationId && !(await isTeamAdmin(ctx.user.id, input.teamId)))
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
const permissionCheckService = new PermissionCheckService();
|
||||
|
||||
const isOrg = !!profile?.organizationId;
|
||||
const permission = isOrg ? "organization.update" : "team.update";
|
||||
const teamId = isOrg ? profile.organizationId : input.teamId;
|
||||
|
||||
const hasUpdatePermission = await permissionCheckService.checkPermission({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
permission,
|
||||
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||
});
|
||||
|
||||
if (!hasUpdatePermission) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
|
||||
export const publishHandler = async ({ ctx, input }: PublishOptions) => {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { isTeamAdmin, isTeamOwner } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import prisma from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
|
||||
@@ -15,11 +16,17 @@ type RemoveHostsFromEventTypes = {
|
||||
|
||||
export async function removeHostsFromEventTypesHandler({ ctx, input }: RemoveHostsFromEventTypes) {
|
||||
const { userIds, eventTypeIds, teamId } = input;
|
||||
const isTeamAdminOrOwner =
|
||||
(await isTeamAdmin(ctx.user.id, teamId)) || (await isTeamOwner(ctx.user.id, teamId));
|
||||
|
||||
// check if user is admin or owner of team
|
||||
if (!isTeamAdminOrOwner) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
const permissionCheckService = new PermissionCheckService();
|
||||
const hasEventTypeUpdatePermission = await permissionCheckService.checkPermission({
|
||||
userId: ctx.user.id,
|
||||
teamId,
|
||||
permission: "eventType.update",
|
||||
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||
});
|
||||
|
||||
// check if user has permission to update event types
|
||||
if (!hasEventTypeUpdatePermission) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
|
||||
return await prisma.host.deleteMany({
|
||||
where: {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -22,12 +23,26 @@ export const setInviteExpirationHandler = async ({ ctx, input }: SetInviteExpira
|
||||
},
|
||||
select: {
|
||||
teamId: true,
|
||||
team: {
|
||||
select: { isOrganization: true },
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
if (!verificationToken) throw new TRPCError({ code: "NOT_FOUND" });
|
||||
if (!verificationToken.teamId || !(await isTeamAdmin(ctx.user.id, verificationToken.teamId)))
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
if (!verificationToken.teamId) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
|
||||
const permissionCheckService = new PermissionCheckService();
|
||||
const isOrgContext = !!verificationToken.team?.isOrganization;
|
||||
const permission = isOrgContext ? "organization.invite" : "team.invite";
|
||||
const hasInvitePermission = await permissionCheckService.checkPermission({
|
||||
userId: ctx.user.id,
|
||||
teamId: verificationToken.teamId,
|
||||
permission,
|
||||
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||
});
|
||||
|
||||
if (!hasInvitePermission) throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
|
||||
const oneDay = 24 * 60 * 60 * 1000;
|
||||
const expires = expiresInDays
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
import { getOrgFullOrigin } from "@calcom/ee/organizations/lib/orgDomains";
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
|
||||
import type { IntervalLimit } from "@calcom/lib/intervalLimits/intervalLimitSchema";
|
||||
import { validateIntervalLimitOrder } from "@calcom/lib/intervalLimits/validateIntervalLimitOrder";
|
||||
import { uploadLogo } from "@calcom/lib/server/avatar";
|
||||
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import type { Prisma } from "@calcom/prisma/client";
|
||||
import { RedirectType, RRTimestampBasis } from "@calcom/prisma/enums";
|
||||
import { MembershipRole, RedirectType, RRTimestampBasis } from "@calcom/prisma/enums";
|
||||
import { teamMetadataStrictSchema } from "@calcom/prisma/zod-utils";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -25,7 +25,15 @@ export const updateHandler = async ({ ctx, input }: UpdateOptions) => {
|
||||
const isOrgAdmin = ctx.user?.organization?.isOrgAdmin;
|
||||
|
||||
if (!isOrgAdmin) {
|
||||
if (!(await isTeamAdmin(ctx.user?.id, input.id))) {
|
||||
const permissionCheckService = new PermissionCheckService();
|
||||
const hasTeamUpdatePermission = await permissionCheckService.checkPermission({
|
||||
userId: ctx.user?.id || 0,
|
||||
teamId: input.id,
|
||||
permission: "team.update",
|
||||
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||
});
|
||||
|
||||
if (!hasTeamUpdatePermission) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { isTeamAdmin } from "@calcom/features/ee/teams/lib/queries";
|
||||
import { PermissionCheckService } from "@calcom/features/pbac/services/permission-check.service";
|
||||
import { prisma } from "@calcom/prisma";
|
||||
import { MembershipRole } from "@calcom/prisma/enums";
|
||||
import type { TrpcSessionUser } from "@calcom/trpc/server/types";
|
||||
|
||||
import { TRPCError } from "@trpc/server";
|
||||
@@ -20,7 +21,15 @@ export const updateInternalNotesPresetsHandler = async ({
|
||||
const isOrgAdmin = ctx.user?.organization?.isOrgAdmin;
|
||||
|
||||
if (!isOrgAdmin) {
|
||||
if (!(await isTeamAdmin(ctx.user?.id, input.teamId))) {
|
||||
const permissionCheckService = new PermissionCheckService();
|
||||
const hasTeamUpdatePermission = await permissionCheckService.checkPermission({
|
||||
userId: ctx.user?.id || 0,
|
||||
teamId: input.teamId,
|
||||
permission: "team.update",
|
||||
fallbackRoles: [MembershipRole.OWNER, MembershipRole.ADMIN],
|
||||
});
|
||||
|
||||
if (!hasTeamUpdatePermission) {
|
||||
throw new TRPCError({ code: "UNAUTHORIZED" });
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user