mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
feat: added saml parsing attributes and injecting to metadata of a user in org scoped
This commit is contained in:
@@ -8,13 +8,12 @@ export async function up(knex: Knex): Promise<void> {
|
|||||||
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
tb.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
|
||||||
tb.string("key").notNullable();
|
tb.string("key").notNullable();
|
||||||
tb.string("value").notNullable();
|
tb.string("value").notNullable();
|
||||||
tb.uuid("userOrgMembershipId");
|
tb.uuid("orgId").notNullable();
|
||||||
tb.foreign("userOrgMembershipId").references("id").inTable(TableName.OrgMembership).onDelete("CASCADE");
|
tb.foreign("orgId").references("id").inTable(TableName.Organization).onDelete("CASCADE");
|
||||||
tb.uuid("identityOrgMembershipId");
|
tb.uuid("userId");
|
||||||
tb.foreign("identityOrgMembershipId")
|
tb.foreign("userId").references("id").inTable(TableName.Users).onDelete("CASCADE");
|
||||||
.references("id")
|
tb.uuid("identityId");
|
||||||
.inTable(TableName.IdentityOrgMembership)
|
tb.foreign("identityId").references("id").inTable(TableName.Identity).onDelete("CASCADE");
|
||||||
.onDelete("CASCADE");
|
|
||||||
tb.timestamps(true, true, true);
|
tb.timestamps(true, true, true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,8 +11,9 @@ export const IdentityMetadataSchema = z.object({
|
|||||||
id: z.string().uuid(),
|
id: z.string().uuid(),
|
||||||
key: z.string(),
|
key: z.string(),
|
||||||
value: z.string(),
|
value: z.string(),
|
||||||
userOrgMembershipId: z.string().uuid().nullable().optional(),
|
orgId: z.string().uuid(),
|
||||||
identityOrgMembershipId: z.string().uuid().nullable().optional(),
|
userId: z.string().uuid().nullable().optional(),
|
||||||
|
identityId: z.string().uuid().nullable().optional(),
|
||||||
createdAt: z.date(),
|
createdAt: z.date(),
|
||||||
updatedAt: z.date()
|
updatedAt: z.date()
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -100,6 +100,7 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
async (req, profile, cb) => {
|
async (req, profile, cb) => {
|
||||||
try {
|
try {
|
||||||
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
if (!profile) throw new BadRequestError({ message: "Missing profile" });
|
||||||
|
|
||||||
const email =
|
const email =
|
||||||
profile?.email ??
|
profile?.email ??
|
||||||
// entra sends data in this format
|
// entra sends data in this format
|
||||||
@@ -123,6 +124,14 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userMetadata = Object.keys(profile.attributes || {})
|
||||||
|
.map((key) => {
|
||||||
|
// for the ones like in format: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/email
|
||||||
|
const formatedKey = key.startsWith("http") ? key.split("/").at(-1) || "" : key;
|
||||||
|
return { key: formatedKey, value: String((profile.attributes as Record<string, string>)[key]) };
|
||||||
|
})
|
||||||
|
.filter((el) => el.key && !["email", "firstName", "lastName"].includes(el.key));
|
||||||
|
|
||||||
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
const { isUserCompleted, providerAuthToken } = await server.services.saml.samlLogin({
|
||||||
externalId: profile.nameID,
|
externalId: profile.nameID,
|
||||||
email,
|
email,
|
||||||
@@ -130,7 +139,8 @@ export const registerSamlRouter = async (server: FastifyZodProvider) => {
|
|||||||
lastName: lastName as string,
|
lastName: lastName as string,
|
||||||
relayState: (req.body as { RelayState?: string }).RelayState,
|
relayState: (req.body as { RelayState?: string }).RelayState,
|
||||||
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
authProvider: (req as unknown as FastifyRequest).ssoConfig?.authProvider as string,
|
||||||
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string
|
orgId: (req as unknown as FastifyRequest).ssoConfig?.orgId as string,
|
||||||
|
metadata: userMetadata
|
||||||
});
|
});
|
||||||
cb(null, { isUserCompleted, providerAuthToken });
|
cb(null, { isUserCompleted, providerAuthToken });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -21,14 +21,14 @@ export const getDefaultOnPremFeatures = (): TFeatureSet => ({
|
|||||||
secretVersioning: true,
|
secretVersioning: true,
|
||||||
pitRecovery: false,
|
pitRecovery: false,
|
||||||
ipAllowlisting: false,
|
ipAllowlisting: false,
|
||||||
rbac: false,
|
rbac: true,
|
||||||
customRateLimits: false,
|
customRateLimits: false,
|
||||||
customAlerts: false,
|
customAlerts: false,
|
||||||
auditLogs: false,
|
auditLogs: false,
|
||||||
auditLogsRetentionDays: 0,
|
auditLogsRetentionDays: 0,
|
||||||
auditLogStreams: false,
|
auditLogStreams: false,
|
||||||
auditLogStreamLimit: 3,
|
auditLogStreamLimit: 3,
|
||||||
samlSSO: false,
|
samlSSO: true,
|
||||||
oidcSSO: false,
|
oidcSSO: false,
|
||||||
scim: false,
|
scim: false,
|
||||||
ldap: false,
|
ldap: false,
|
||||||
|
|||||||
@@ -168,6 +168,11 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
})
|
})
|
||||||
.join<TProjects>(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId]))
|
.join<TProjects>(TableName.Project, `${TableName.Project}.id`, db.raw("?", [projectId]))
|
||||||
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
.join(TableName.Organization, `${TableName.Project}.orgId`, `${TableName.Organization}.id`)
|
||||||
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
|
void queryBuilder
|
||||||
|
.on(`${TableName.Users}.id`, `${TableName.IdentityMetadata}.userId`)
|
||||||
|
.andOn(`${TableName.Organization}.id`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
|
})
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.Users).as("userId"),
|
db.ref("id").withSchema(TableName.Users).as("userId"),
|
||||||
db.ref("username").withSchema(TableName.Users).as("username"),
|
db.ref("username").withSchema(TableName.Users).as("username"),
|
||||||
@@ -258,6 +263,9 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
.withSchema(TableName.ProjectUserAdditionalPrivilege)
|
||||||
.as("userAdditionalPrivilegesTemporaryAccessEndTime"),
|
.as("userAdditionalPrivilegesTemporaryAccessEndTime"),
|
||||||
// general
|
// general
|
||||||
|
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||||
|
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||||
|
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue"),
|
||||||
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
db.ref("authEnforced").withSchema(TableName.Organization).as("orgAuthEnforced"),
|
||||||
db.ref("orgId").withSchema(TableName.Project),
|
db.ref("orgId").withSchema(TableName.Project),
|
||||||
db.ref("id").withSchema(TableName.Project).as("projectId")
|
db.ref("id").withSchema(TableName.Project).as("projectId")
|
||||||
@@ -357,6 +365,15 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
temporaryAccessEndTime: userAdditionalPrivilegesTemporaryAccessEndTime,
|
temporaryAccessEndTime: userAdditionalPrivilegesTemporaryAccessEndTime,
|
||||||
isTemporary: userAdditionalPrivilegesIsTemporary
|
isTemporary: userAdditionalPrivilegesIsTemporary
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "metadataId",
|
||||||
|
label: "metadata" as const,
|
||||||
|
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||||
|
id: metadataId,
|
||||||
|
key: metadataKey,
|
||||||
|
value: metadataValue
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
@@ -419,6 +436,11 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.IdentityProjectMembership}.projectId`,
|
`${TableName.IdentityProjectMembership}.projectId`,
|
||||||
`${TableName.Project}.id`
|
`${TableName.Project}.id`
|
||||||
)
|
)
|
||||||
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
|
void queryBuilder
|
||||||
|
.on(`${TableName.Identity}.id`, `${TableName.IdentityMetadata}.identityId`)
|
||||||
|
.andOn(`${TableName.Project}.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
|
})
|
||||||
.where("identityId", identityId)
|
.where("identityId", identityId)
|
||||||
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
.where(`${TableName.IdentityProjectMembership}.projectId`, projectId)
|
||||||
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
|
.select(selectAllTableCols(TableName.IdentityProjectMembershipRole))
|
||||||
@@ -448,7 +470,10 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
db
|
db
|
||||||
.ref("temporaryAccessEndTime")
|
.ref("temporaryAccessEndTime")
|
||||||
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
.withSchema(TableName.IdentityProjectAdditionalPrivilege)
|
||||||
.as("identityApTemporaryAccessEndTime")
|
.as("identityApTemporaryAccessEndTime"),
|
||||||
|
db.ref("id").withSchema(TableName.IdentityMetadata).as("metadataId"),
|
||||||
|
db.ref("key").withSchema(TableName.IdentityMetadata).as("metadataKey"),
|
||||||
|
db.ref("value").withSchema(TableName.IdentityMetadata).as("metadataValue")
|
||||||
);
|
);
|
||||||
|
|
||||||
const permission = sqlNestRelationships({
|
const permission = sqlNestRelationships({
|
||||||
@@ -495,6 +520,15 @@ export const permissionDALFactory = (db: TDbClient) => {
|
|||||||
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
|
temporaryAccessStartTime: identityApTemporaryAccessStartTime,
|
||||||
isTemporary: identityApIsTemporary
|
isTemporary: identityApIsTemporary
|
||||||
})
|
})
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "metadataId",
|
||||||
|
label: "metadata" as const,
|
||||||
|
mapper: ({ metadataKey, metadataValue, metadataId }) => ({
|
||||||
|
id: metadataId,
|
||||||
|
key: metadataKey,
|
||||||
|
value: metadataValue
|
||||||
|
})
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import {
|
|||||||
} from "@app/db/schemas";
|
} from "@app/db/schemas";
|
||||||
import { conditionsMatcher } from "@app/lib/casl";
|
import { conditionsMatcher } from "@app/lib/casl";
|
||||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||||
|
import { objectify } from "@app/lib/fn";
|
||||||
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
import { ActorAuthMethod, ActorType } from "@app/services/auth/auth-type";
|
||||||
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
import { TOrgRoleDALFactory } from "@app/services/org/org-role-dal";
|
||||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||||
@@ -223,9 +224,20 @@ export const permissionServiceFactory = ({
|
|||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
||||||
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
|
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false, strict: true });
|
||||||
|
const metadataKeyValuePair = objectify(
|
||||||
|
userProjectPermission.metadata,
|
||||||
|
(i) => i.key,
|
||||||
|
(i) => i.value
|
||||||
|
);
|
||||||
const interpolateRules = templatedRules(
|
const interpolateRules = templatedRules(
|
||||||
{ identity: { id: userProjectPermission.userId, username: userProjectPermission.username } },
|
{
|
||||||
|
identity: {
|
||||||
|
id: userProjectPermission.userId,
|
||||||
|
username: userProjectPermission.username,
|
||||||
|
metadata: metadataKeyValuePair
|
||||||
|
}
|
||||||
|
},
|
||||||
{ data: false }
|
{ data: false }
|
||||||
);
|
);
|
||||||
const permission = createMongoAbility<ProjectPermissionSet>(
|
const permission = createMongoAbility<ProjectPermissionSet>(
|
||||||
@@ -275,9 +287,20 @@ export const permissionServiceFactory = ({
|
|||||||
})) || [];
|
})) || [];
|
||||||
|
|
||||||
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
const rules = buildProjectPermissionRules(rolePermissions.concat(additionalPrivileges));
|
||||||
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false });
|
const templatedRules = handlebars.compile(JSON.stringify(rules), { data: false, strict: true });
|
||||||
|
const metadataKeyValuePair = objectify(
|
||||||
|
identityProjectPermission.metadata,
|
||||||
|
(i) => i.key,
|
||||||
|
(i) => i.value
|
||||||
|
);
|
||||||
const interpolateRules = templatedRules(
|
const interpolateRules = templatedRules(
|
||||||
{ identity: { id: identityProjectPermission.identityId, username: identityProjectPermission.username } },
|
{
|
||||||
|
identity: {
|
||||||
|
id: identityProjectPermission.identityId,
|
||||||
|
username: identityProjectPermission.username,
|
||||||
|
metadata: metadataKeyValuePair
|
||||||
|
}
|
||||||
|
},
|
||||||
{ data: false }
|
{ data: false }
|
||||||
);
|
);
|
||||||
const permission = createMongoAbility<ProjectPermissionSet>(
|
const permission = createMongoAbility<ProjectPermissionSet>(
|
||||||
|
|||||||
@@ -117,10 +117,7 @@ const SecretConditionSchema = z
|
|||||||
.object({
|
.object({
|
||||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN]
|
||||||
[PermissionConditionOperators.$ALL]: PermissionConditionSchema[PermissionConditionOperators.$ALL],
|
|
||||||
[PermissionConditionOperators.$REGEX]: PermissionConditionSchema[PermissionConditionOperators.$REGEX],
|
|
||||||
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
]),
|
]),
|
||||||
@@ -131,21 +128,6 @@ const SecretConditionSchema = z
|
|||||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
||||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
||||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
||||||
[PermissionConditionOperators.$ALL]: PermissionConditionSchema[PermissionConditionOperators.$ALL],
|
|
||||||
[PermissionConditionOperators.$REGEX]: PermissionConditionSchema[PermissionConditionOperators.$REGEX],
|
|
||||||
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
|
||||||
})
|
|
||||||
.partial()
|
|
||||||
]),
|
|
||||||
secretName: z.union([
|
|
||||||
z.string(),
|
|
||||||
z
|
|
||||||
.object({
|
|
||||||
[PermissionConditionOperators.$EQ]: PermissionConditionSchema[PermissionConditionOperators.$EQ],
|
|
||||||
[PermissionConditionOperators.$NEQ]: PermissionConditionSchema[PermissionConditionOperators.$NEQ],
|
|
||||||
[PermissionConditionOperators.$IN]: PermissionConditionSchema[PermissionConditionOperators.$IN],
|
|
||||||
[PermissionConditionOperators.$ALL]: PermissionConditionSchema[PermissionConditionOperators.$ALL],
|
|
||||||
[PermissionConditionOperators.$REGEX]: PermissionConditionSchema[PermissionConditionOperators.$REGEX],
|
|
||||||
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
[PermissionConditionOperators.$GLOB]: PermissionConditionSchema[PermissionConditionOperators.$GLOB]
|
||||||
})
|
})
|
||||||
.partial()
|
.partial()
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/
|
|||||||
import { AuthTokenType } from "@app/services/auth/auth-type";
|
import { AuthTokenType } from "@app/services/auth/auth-type";
|
||||||
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
import { TAuthTokenServiceFactory } from "@app/services/auth-token/auth-token-service";
|
||||||
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
import { TokenType } from "@app/services/auth-token/auth-token-types";
|
||||||
|
import { TIdentityMetadataDALFactory } from "@app/services/identity/identity-metadata-dal";
|
||||||
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
import { TOrgBotDALFactory } from "@app/services/org/org-bot-dal";
|
||||||
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
import { TOrgDALFactory } from "@app/services/org/org-dal";
|
||||||
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
import { TOrgMembershipDALFactory } from "@app/services/org-membership/org-membership-dal";
|
||||||
@@ -51,6 +52,8 @@ type TSamlConfigServiceFactoryDep = {
|
|||||||
TOrgDALFactory,
|
TOrgDALFactory,
|
||||||
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
"createMembership" | "updateMembershipById" | "findMembership" | "findOrgById" | "findOne" | "updateById"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
identityMetadataDAL: Pick<TIdentityMetadataDALFactory, "delete" | "insertMany" | "transaction">;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "create">;
|
||||||
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
|
orgBotDAL: Pick<TOrgBotDALFactory, "findOne" | "create" | "transaction">;
|
||||||
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
permissionService: Pick<TPermissionServiceFactory, "getOrgPermission">;
|
||||||
@@ -71,7 +74,8 @@ export const samlConfigServiceFactory = ({
|
|||||||
permissionService,
|
permissionService,
|
||||||
licenseService,
|
licenseService,
|
||||||
tokenService,
|
tokenService,
|
||||||
smtpService
|
smtpService,
|
||||||
|
identityMetadataDAL
|
||||||
}: TSamlConfigServiceFactoryDep) => {
|
}: TSamlConfigServiceFactoryDep) => {
|
||||||
const createSamlCfg = async ({
|
const createSamlCfg = async ({
|
||||||
cert,
|
cert,
|
||||||
@@ -332,7 +336,8 @@ export const samlConfigServiceFactory = ({
|
|||||||
lastName,
|
lastName,
|
||||||
authProvider,
|
authProvider,
|
||||||
orgId,
|
orgId,
|
||||||
relayState
|
relayState,
|
||||||
|
metadata
|
||||||
}: TSamlLoginDTO) => {
|
}: TSamlLoginDTO) => {
|
||||||
const appCfg = getConfig();
|
const appCfg = getConfig();
|
||||||
const serverCfg = await getServerCfg();
|
const serverCfg = await getServerCfg();
|
||||||
@@ -386,6 +391,21 @@ export const samlConfigServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (metadata && foundUser.id) {
|
||||||
|
await identityMetadataDAL.delete({ userId: foundUser.id, orgId }, tx);
|
||||||
|
if (metadata.length) {
|
||||||
|
await identityMetadataDAL.insertMany(
|
||||||
|
metadata.map(({ key, value }) => ({
|
||||||
|
userId: foundUser.id,
|
||||||
|
orgId,
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return foundUser;
|
return foundUser;
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -474,6 +494,20 @@ export const samlConfigServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (metadata && newUser.id) {
|
||||||
|
await identityMetadataDAL.delete({ userId: newUser.id, orgId }, tx);
|
||||||
|
if (metadata.length) {
|
||||||
|
await identityMetadataDAL.insertMany(
|
||||||
|
metadata.map(({ key, value }) => ({
|
||||||
|
userId: newUser?.id,
|
||||||
|
orgId,
|
||||||
|
key,
|
||||||
|
value
|
||||||
|
})),
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
return newUser;
|
return newUser;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,4 +53,5 @@ export type TSamlLoginDTO = {
|
|||||||
orgId: string;
|
orgId: string;
|
||||||
// saml thingy
|
// saml thingy
|
||||||
relayState?: string;
|
relayState?: string;
|
||||||
|
metadata?: { key: string; value: string }[];
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -52,3 +52,21 @@ export const unique = <T, K extends string | number | symbol>(array: readonly T[
|
|||||||
);
|
);
|
||||||
return Object.values(valueMap);
|
return Object.values(valueMap);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert an array to a dictionary by mapping each item
|
||||||
|
* into a dictionary key & value
|
||||||
|
*/
|
||||||
|
export const objectify = <T, Key extends string | number | symbol, Value = T>(
|
||||||
|
array: readonly T[],
|
||||||
|
getKey: (item: T) => Key,
|
||||||
|
getValue: (item: T) => Value = (item) => item as unknown as Value
|
||||||
|
): Record<Key, Value> => {
|
||||||
|
return array.reduce(
|
||||||
|
(acc, item) => {
|
||||||
|
acc[getKey(item)] = getValue(item);
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as Record<Key, Value>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
@@ -388,6 +388,7 @@ export const registerRoutes = async (
|
|||||||
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
|
const tokenService = tokenServiceFactory({ tokenDAL: authTokenDAL, userDAL, orgMembershipDAL });
|
||||||
|
|
||||||
const samlService = samlConfigServiceFactory({
|
const samlService = samlConfigServiceFactory({
|
||||||
|
identityMetadataDAL,
|
||||||
permissionService,
|
permissionService,
|
||||||
orgBotDAL,
|
orgBotDAL,
|
||||||
orgDAL,
|
orgDAL,
|
||||||
|
|||||||
@@ -56,11 +56,11 @@ export const identityOrgDALFactory = (db: TDbClient) => {
|
|||||||
queryBuilder.on(`${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`);
|
queryBuilder.on(`${TableName.IdentityOrgMembership}.identityId`, `${TableName.Identity}.id`);
|
||||||
})
|
})
|
||||||
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
|
.leftJoin(TableName.OrgRoles, `${TableName.IdentityOrgMembership}.roleId`, `${TableName.OrgRoles}.id`)
|
||||||
.leftJoin(
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
TableName.IdentityMetadata,
|
void queryBuilder
|
||||||
`${TableName.IdentityMetadata}.identityOrgMembershipId`,
|
.on(`${TableName.IdentityOrgMembership}.identityId`, `${TableName.IdentityMetadata}.identityId`)
|
||||||
`${TableName.IdentityOrgMembership}.id`
|
.andOn(`${TableName.IdentityOrgMembership}.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
)
|
})
|
||||||
.select(selectAllTableCols(TableName.IdentityOrgMembership))
|
.select(selectAllTableCols(TableName.IdentityOrgMembership))
|
||||||
// cr stands for custom role
|
// cr stands for custom role
|
||||||
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
|
.select(db.ref("id").as("crId").withSchema(TableName.OrgRoles))
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export const identityServiceFactory = ({
|
|||||||
|
|
||||||
const identity = await identityDAL.transaction(async (tx) => {
|
const identity = await identityDAL.transaction(async (tx) => {
|
||||||
const newIdentity = await identityDAL.create({ name }, tx);
|
const newIdentity = await identityDAL.create({ name }, tx);
|
||||||
const identityOrgMembership = await identityOrgMembershipDAL.create(
|
await identityOrgMembershipDAL.create(
|
||||||
{
|
{
|
||||||
identityId: newIdentity.id,
|
identityId: newIdentity.id,
|
||||||
orgId,
|
orgId,
|
||||||
@@ -85,7 +85,8 @@ export const identityServiceFactory = ({
|
|||||||
if (metadata && metadata.length) {
|
if (metadata && metadata.length) {
|
||||||
await identityMetadataDAL.insertMany(
|
await identityMetadataDAL.insertMany(
|
||||||
metadata.map(({ key, value }) => ({
|
metadata.map(({ key, value }) => ({
|
||||||
identityOrgMembershipId: identityOrgMembership.id,
|
identityId: newIdentity.id,
|
||||||
|
orgId,
|
||||||
key,
|
key,
|
||||||
value
|
value
|
||||||
})),
|
})),
|
||||||
@@ -159,11 +160,12 @@ export const identityServiceFactory = ({
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
await identityMetadataDAL.delete({ identityOrgMembershipId: identityOrgMembership.id }, tx);
|
await identityMetadataDAL.delete({ orgId: identityOrgMembership.orgId, identityId: id }, tx);
|
||||||
if (metadata.length) {
|
if (metadata.length) {
|
||||||
await identityMetadataDAL.insertMany(
|
await identityMetadataDAL.insertMany(
|
||||||
metadata.map(({ key, value }) => ({
|
metadata.map(({ key, value }) => ({
|
||||||
identityOrgMembershipId: identityOrgMembership.id,
|
identityId: newIdentity.id,
|
||||||
|
orgId: identityOrgMembership.orgId,
|
||||||
key,
|
key,
|
||||||
value
|
value
|
||||||
})),
|
})),
|
||||||
|
|||||||
@@ -19,11 +19,11 @@ export const orgMembershipDALFactory = (db: TDbClient) => {
|
|||||||
`${TableName.UserEncryptionKey}.userId`,
|
`${TableName.UserEncryptionKey}.userId`,
|
||||||
`${TableName.Users}.id`
|
`${TableName.Users}.id`
|
||||||
)
|
)
|
||||||
.leftJoin(
|
.leftJoin(TableName.IdentityMetadata, (queryBuilder) => {
|
||||||
TableName.IdentityMetadata,
|
void queryBuilder
|
||||||
`${TableName.IdentityMetadata}.userOrgMembershipId`,
|
.on(`${TableName.OrgMembership}.userId`, `${TableName.IdentityMetadata}.userId`)
|
||||||
`${TableName.OrgMembership}.id`
|
.andOn(`${TableName.OrgMembership}.orgId`, `${TableName.IdentityMetadata}.orgId`);
|
||||||
)
|
})
|
||||||
.select(
|
.select(
|
||||||
db.ref("id").withSchema(TableName.OrgMembership),
|
db.ref("id").withSchema(TableName.OrgMembership),
|
||||||
db.ref("inviteEmail").withSchema(TableName.OrgMembership),
|
db.ref("inviteEmail").withSchema(TableName.OrgMembership),
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ type TOrgServiceFactoryDep = {
|
|||||||
"findProjectMembershipsByUserId" | "delete" | "create" | "find" | "insertMany" | "transaction"
|
"findProjectMembershipsByUserId" | "delete" | "create" | "find" | "insertMany" | "transaction"
|
||||||
>;
|
>;
|
||||||
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "insertMany" | "findLatestProjectKey">;
|
projectKeyDAL: Pick<TProjectKeyDALFactory, "find" | "delete" | "insertMany" | "findLatestProjectKey">;
|
||||||
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOne">;
|
orgMembershipDAL: Pick<TOrgMembershipDALFactory, "findOrgMembershipById" | "findOne" | "findById">;
|
||||||
incidentContactDAL: TIncidentContactsDALFactory;
|
incidentContactDAL: TIncidentContactsDALFactory;
|
||||||
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne" | "findEnforceableSamlCfg">;
|
samlConfigDAL: Pick<TSamlConfigDALFactory, "findOne" | "findEnforceableSamlCfg">;
|
||||||
smtpService: TSmtpService;
|
smtpService: TSmtpService;
|
||||||
@@ -413,11 +413,10 @@ export const orgServiceFactory = ({
|
|||||||
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
const { permission } = await permissionService.getUserOrgPermission(userId, orgId, actorAuthMethod, actorOrgId);
|
||||||
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
|
ForbiddenError.from(permission).throwUnlessCan(OrgPermissionActions.Edit, OrgPermissionSubjects.Member);
|
||||||
|
|
||||||
const foundMembership = await orgMembershipDAL.findOne({
|
const foundMembership = await orgMembershipDAL.findById(membershipId);
|
||||||
id: membershipId,
|
|
||||||
orgId
|
|
||||||
});
|
|
||||||
if (!foundMembership) throw new NotFoundError({ message: "Failed to find organization membership" });
|
if (!foundMembership) throw new NotFoundError({ message: "Failed to find organization membership" });
|
||||||
|
if (foundMembership.orgId !== orgId)
|
||||||
|
throw new UnauthorizedError({ message: "Updated org member doesn't belong to the organization" });
|
||||||
if (foundMembership.userId === userId)
|
if (foundMembership.userId === userId)
|
||||||
throw new UnauthorizedError({ message: "Cannot update own organization membership" });
|
throw new UnauthorizedError({ message: "Cannot update own organization membership" });
|
||||||
|
|
||||||
@@ -444,11 +443,12 @@ export const orgServiceFactory = ({
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (metadata) {
|
if (metadata) {
|
||||||
await identityMetadataDAL.delete({ userOrgMembershipId: updatedOrgMembership.id }, tx);
|
await identityMetadataDAL.delete({ userId: updatedOrgMembership.userId, orgId }, tx);
|
||||||
if (metadata.length) {
|
if (metadata.length) {
|
||||||
await identityMetadataDAL.insertMany(
|
await identityMetadataDAL.insertMany(
|
||||||
metadata.map(({ key, value }) => ({
|
metadata.map(({ key, value }) => ({
|
||||||
userOrgMembershipId: updatedOrgMembership.id,
|
userId: updatedOrgMembership.userId,
|
||||||
|
orgId,
|
||||||
key,
|
key,
|
||||||
value
|
value
|
||||||
})),
|
})),
|
||||||
|
|||||||
Reference in New Issue
Block a user