check idp org ownership on save policy closes #3290

This commit is contained in:
miloschwartz
2026-07-29 09:38:15 -04:00
parent 9a9ae649ef
commit f47c94d05b
4 changed files with 52 additions and 37 deletions
+7 -14
View File
@@ -1,7 +1,5 @@
import { import {
db, db,
idp,
idpOrg,
resourcePolicies, resourcePolicies,
resourcePolicyHeaderAuth, resourcePolicyHeaderAuth,
resourcePolicyPassword, resourcePolicyPassword,
@@ -20,6 +18,7 @@ import { Config, ResourcePolicyData } from "./types";
import logger from "@server/logger"; import logger from "@server/logger";
import { getUniqueResourcePolicyName } from "@server/db/names"; import { getUniqueResourcePolicyName } from "@server/db/names";
import { hashPassword } from "@server/auth/password"; import { hashPassword } from "@server/auth/password";
import { idpExistsForOrg } from "@server/lib/idp/idpExistsForOrg";
import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators"; import { isValidCIDR, isValidIP, isValidUrlGlobPattern } from "../validators";
import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed"; import { isLicensedOrSubscribed } from "#dynamic/lib/isLicencedOrSubscribed";
import { tierMatrix } from "../billing/tierMatrix"; import { tierMatrix } from "../billing/tierMatrix";
@@ -71,19 +70,13 @@ export async function updateResourcePolicies(
// Validate auto-login-idp if provided // Validate auto-login-idp if provided
if (policyData["auto-login-idp"]) { if (policyData["auto-login-idp"]) {
const [provider] = await trx const providerExists = await idpExistsForOrg(
.select() policyData["auto-login-idp"],
.from(idp) orgId,
.innerJoin(idpOrg, eq(idpOrg.idpId, idp.idpId)) trx
.where( );
and(
eq(idp.idpId, policyData["auto-login-idp"]),
eq(idpOrg.orgId, orgId)
)
)
.limit(1);
if (!provider) { if (!providerExists) {
throw new Error( throw new Error(
`Identity provider not found for policy '${policyNiceId}' in this organization` `Identity provider not found for policy '${policyNiceId}' in this organization`
); );
+36
View File
@@ -0,0 +1,36 @@
import { db, idp, idpOrg, Transaction } from "@server/db";
import { and, eq } from "drizzle-orm";
export function isOrgIdentityProviderMode(): boolean {
return process.env.IDENTITY_PROVIDER_MODE === "org";
}
/**
* Checks whether an identity provider can be used for the given org.
* In org IdP mode, the provider must be linked via idpOrg.
* In global IdP mode, the provider only needs to exist.
*/
export async function idpExistsForOrg(
idpId: number,
orgId: string,
dbOrTrx: typeof db | Transaction = db
): Promise<boolean> {
if (isOrgIdentityProviderMode()) {
const [provider] = await dbOrTrx
.select({ idpId: idp.idpId })
.from(idp)
.innerJoin(idpOrg, eq(idpOrg.idpId, idp.idpId))
.where(and(eq(idp.idpId, idpId), eq(idpOrg.orgId, orgId)))
.limit(1);
return !!provider;
}
const [provider] = await dbOrTrx
.select({ idpId: idp.idpId })
.from(idp)
.where(eq(idp.idpId, idpId))
.limit(1);
return !!provider;
}
@@ -14,8 +14,6 @@
import { hashPassword } from "@server/auth/password"; import { hashPassword } from "@server/auth/password";
import { import {
db, db,
idp,
idpOrg,
orgs, orgs,
resourcePolicies, resourcePolicies,
resourcePolicyHeaderAuth, resourcePolicyHeaderAuth,
@@ -31,6 +29,7 @@ import {
type ResourcePolicy type ResourcePolicy
} from "@server/db"; } from "@server/db";
import { getUniqueResourcePolicyName } from "@server/db/names"; import { getUniqueResourcePolicyName } from "@server/db/names";
import { idpExistsForOrg } from "@server/lib/idp/idpExistsForOrg";
import response from "@server/lib/response"; import response from "@server/lib/response";
import { import {
getResourceRuleValueValidationError, getResourceRuleValueValidationError,
@@ -204,14 +203,9 @@ export async function createResourcePolicy(
// Check if Identity provider in `skipToIdpId` exists // Check if Identity provider in `skipToIdpId` exists
if (skipToIdpId) { if (skipToIdpId) {
const [provider] = await db const providerExists = await idpExistsForOrg(skipToIdpId, orgId);
.select()
.from(idp)
.innerJoin(idpOrg, eq(idpOrg.idpId, idp.idpId))
.where(and(eq(idp.idpId, skipToIdpId), eq(idpOrg.orgId, orgId)))
.limit(1);
if (!provider) { if (!providerExists) {
return next( return next(
createHttpError( createHttpError(
HttpCode.INTERNAL_SERVER_ERROR, HttpCode.INTERNAL_SERVER_ERROR,
@@ -2,8 +2,6 @@ import { Request, Response, NextFunction } from "express";
import { z } from "zod"; import { z } from "zod";
import { import {
db, db,
idp,
idpOrg,
resourcePolicies, resourcePolicies,
rolePolicies, rolePolicies,
roles, roles,
@@ -18,6 +16,7 @@ import logger from "@server/logger";
import { fromError } from "zod-validation-error"; import { fromError } from "zod-validation-error";
import { and, eq, inArray, ne } from "drizzle-orm"; import { and, eq, inArray, ne } from "drizzle-orm";
import { OpenAPITags, registry } from "@server/openApi"; import { OpenAPITags, registry } from "@server/openApi";
import { idpExistsForOrg } from "@server/lib/idp/idpExistsForOrg";
const setResourcePolicyAcccessControlBodySchema = z.strictObject({ const setResourcePolicyAcccessControlBodySchema = z.strictObject({
sso: z.boolean(), sso: z.boolean(),
@@ -27,7 +26,7 @@ const setResourcePolicyAcccessControlBodySchema = z.strictObject({
}), }),
skipToIdpId: z.int().positive().optional().nullable().openapi({ skipToIdpId: z.int().positive().optional().nullable().openapi({
type: "integer", type: "integer",
description: "Page number to retrieve" description: "Default identity provider ID to skip to on login"
}) })
}); });
@@ -36,8 +35,8 @@ const setResourcePolicyAccessControlParamsSchema = z.strictObject({
}); });
registry.registerPath({ registry.registerPath({
method: "post", method: "put",
path: "/resource-policy/{resourceId}/access-control", path: "/resource-policy/{resourcePolicyId}/access-control",
description: description:
"Set access control users for a resource policy, including SSO, users, roles, Identity provider.", "Set access control users for a resource policy, including SSO, users, roles, Identity provider.",
tags: [OpenAPITags.PublicResourcePolicyLegacy], tags: [OpenAPITags.PublicResourcePolicyLegacy],
@@ -163,16 +162,9 @@ export async function setResourcePolicyAccessControl(
// Check if Identity provider in `skipToIdpId` exists // Check if Identity provider in `skipToIdpId` exists
if (idpId) { if (idpId) {
const [provider] = await db const providerExists = await idpExistsForOrg(idpId, policy.orgId);
.select()
.from(idp)
.innerJoin(idpOrg, eq(idpOrg.idpId, idp.idpId))
.where(
and(eq(idp.idpId, idpId), eq(idpOrg.orgId, policy.orgId))
)
.limit(1);
if (!provider) { if (!providerExists) {
return next( return next(
createHttpError( createHttpError(
HttpCode.INTERNAL_SERVER_ERROR, HttpCode.INTERNAL_SERVER_ERROR,