From a6a124d2e65b062587ae684c8d8739453f38dfcf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=96mer=20Faruk=20Ayd=C4=B1n?= Date: Fri, 11 Aug 2023 19:07:39 +0200 Subject: [PATCH] feat: add role mappings for SAML configuration (#1210) --- ...reate_saml_auth_providers_role_mappings.ts | 24 +++++++ .../backend/src/graphql/mutation-resolvers.ts | 2 + .../mutations/upsert-saml-auth-provider.ee.ts | 6 +- ...rt-saml-auth-providers-role-mappings.ee.ts | 54 ++++++++++++++++ packages/backend/src/graphql/schema.graphql | 20 ++++++ ...find-or-create-user-by-saml-identity.ee.ts | 63 ++++++++++++------- .../src/models/saml-auth-provider.ee.ts | 10 +++ .../saml-auth-providers-role-mapping.ee.ts | 36 +++++++++++ 8 files changed, 190 insertions(+), 25 deletions(-) create mode 100644 packages/backend/src/db/migrations/20230811142340_create_saml_auth_providers_role_mappings.ts create mode 100644 packages/backend/src/graphql/mutations/upsert-saml-auth-providers-role-mappings.ee.ts create mode 100644 packages/backend/src/models/saml-auth-providers-role-mapping.ee.ts diff --git a/packages/backend/src/db/migrations/20230811142340_create_saml_auth_providers_role_mappings.ts b/packages/backend/src/db/migrations/20230811142340_create_saml_auth_providers_role_mappings.ts new file mode 100644 index 00000000..36949f5f --- /dev/null +++ b/packages/backend/src/db/migrations/20230811142340_create_saml_auth_providers_role_mappings.ts @@ -0,0 +1,24 @@ +import { Knex } from 'knex'; + +export async function up(knex: Knex): Promise { + return knex.schema.createTable( + 'saml_auth_providers_role_mappings', + (table) => { + table.uuid('id').primary().defaultTo(knex.raw('gen_random_uuid()')); + table + .uuid('saml_auth_provider_id') + .references('id') + .inTable('saml_auth_providers'); + table.uuid('role_id').references('id').inTable('roles'); + table.string('remote_role_name').notNullable(); + + table.unique(['saml_auth_provider_id', 'remote_role_name']); + + table.timestamps(true, true); + } + ); +} + +export async function down(knex: Knex): Promise { + return knex.schema.dropTable('saml_auth_providers_role_mappings'); +} diff --git a/packages/backend/src/graphql/mutation-resolvers.ts b/packages/backend/src/graphql/mutation-resolvers.ts index bd5e180c..41a75299 100644 --- a/packages/backend/src/graphql/mutation-resolvers.ts +++ b/packages/backend/src/graphql/mutation-resolvers.ts @@ -26,6 +26,7 @@ import updateRole from './mutations/update-role.ee'; import updateStep from './mutations/update-step'; import updateUser from './mutations/update-user.ee'; import upsertSamlAuthProvider from './mutations/upsert-saml-auth-provider.ee'; +import upsertSamlAuthProvidersRoleMappings from './mutations/upsert-saml-auth-providers-role-mappings.ee'; import verifyConnection from './mutations/verify-connection'; const mutationResolvers = { @@ -57,6 +58,7 @@ const mutationResolvers = { updateStep, updateUser, upsertSamlAuthProvider, + upsertSamlAuthProvidersRoleMappings, verifyConnection, }; diff --git a/packages/backend/src/graphql/mutations/upsert-saml-auth-provider.ee.ts b/packages/backend/src/graphql/mutations/upsert-saml-auth-provider.ee.ts index 7c8f4b9f..09dfc61f 100644 --- a/packages/backend/src/graphql/mutations/upsert-saml-auth-provider.ee.ts +++ b/packages/backend/src/graphql/mutations/upsert-saml-auth-provider.ee.ts @@ -33,17 +33,15 @@ const upsertSamlAuthProvider = async ( .limit(1) .first(); - let samlAuthProvider: SamlAuthProvider; - if (!existingSamlAuthProvider) { - samlAuthProvider = await SamlAuthProvider.query().insert( + const samlAuthProvider = await SamlAuthProvider.query().insert( samlAuthProviderPayload ); return samlAuthProvider; } - samlAuthProvider = await SamlAuthProvider.query().patchAndFetchById( + const samlAuthProvider = await SamlAuthProvider.query().patchAndFetchById( existingSamlAuthProvider.id, samlAuthProviderPayload ); diff --git a/packages/backend/src/graphql/mutations/upsert-saml-auth-providers-role-mappings.ee.ts b/packages/backend/src/graphql/mutations/upsert-saml-auth-providers-role-mappings.ee.ts new file mode 100644 index 00000000..32ca6426 --- /dev/null +++ b/packages/backend/src/graphql/mutations/upsert-saml-auth-providers-role-mappings.ee.ts @@ -0,0 +1,54 @@ +import SamlAuthProvider from '../../models/saml-auth-provider.ee'; +import SamlAuthProvidersRoleMapping from '../../models/saml-auth-providers-role-mapping.ee'; +import Context from '../../types/express/context'; + +type Params = { + input: { + samlAuthProviderId: string; + samlAuthProvidersRoleMappings: [ + { + roleId: string; + remoteRoleName: string; + } + ]; + }; +}; + +const upsertSamlAuthProvidersRoleMappings = async ( + _parent: unknown, + params: Params, + context: Context +) => { + context.currentUser.can('update', 'SamlAuthProvider'); + + const samlAuthProviderId = params.input.samlAuthProviderId; + + const samlAuthProvider = await SamlAuthProvider.query() + .findById(samlAuthProviderId) + .throwIfNotFound(); + + await samlAuthProvider + .$relatedQuery('samlAuthProvidersRoleMappings') + .delete(); + + if (!params.input.samlAuthProvidersRoleMappings) { + return []; + } + + const samlAuthProvidersRoleMappingsData = + params.input.samlAuthProvidersRoleMappings.map( + (samlAuthProvidersRoleMapping) => ({ + ...samlAuthProvidersRoleMapping, + samlAuthProviderId: samlAuthProvider.id, + }) + ); + + const samlAuthProvidersRoleMappings = + await SamlAuthProvidersRoleMapping.query().insert( + samlAuthProvidersRoleMappingsData + ); + + return samlAuthProvidersRoleMappings; +}; + +export default upsertSamlAuthProvidersRoleMappings; diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index ce93a5be..f4867c21 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -81,6 +81,9 @@ type Mutation { updateStep(input: UpdateStepInput): Step updateUser(input: UpdateUserInput): User upsertSamlAuthProvider(input: UpsertSamlAuthProviderInput): SamlAuthProvider + upsertSamlAuthProvidersRoleMappings( + input: UpsertSamlAuthProvidersRoleMappingsInput + ): [SamlAuthProvidersRoleMapping] verifyConnection(input: VerifyConnectionInput): Connection } @@ -307,6 +310,13 @@ type SamlAuthProvider { active: Boolean } +type SamlAuthProvidersRoleMapping { + id: String + samlAuthProviderId: String + roleId: String + remoteRoleName: String +} + type UserConnection { edges: [UserEdge] pageInfo: PageInfo @@ -352,6 +362,16 @@ input UpsertSamlAuthProviderInput { active: Boolean! } +input UpsertSamlAuthProvidersRoleMappingsInput { + samlAuthProviderId: String! + samlAuthProvidersRoleMappings: [SamlAuthProviderRoleMappingInput] +} + +input SamlAuthProviderRoleMappingInput { + roleId: String! + remoteRoleName: String! +} + input DeleteConnectionInput { id: String! } diff --git a/packages/backend/src/helpers/find-or-create-user-by-saml-identity.ee.ts b/packages/backend/src/helpers/find-or-create-user-by-saml-identity.ee.ts index 0af8cc73..a46ad8da 100644 --- a/packages/backend/src/helpers/find-or-create-user-by-saml-identity.ee.ts +++ b/packages/backend/src/helpers/find-or-create-user-by-saml-identity.ee.ts @@ -1,19 +1,27 @@ import SamlAuthProvider from '../models/saml-auth-provider.ee'; import User from '../models/user'; import Identity from '../models/identity.ee'; +import SamlAuthProvidersRoleMapping from '../models/saml-auth-providers-role-mapping.ee'; -const getUser = (user: Record, providerConfig: SamlAuthProvider) => ({ +const getUser = ( + user: Record, + providerConfig: SamlAuthProvider +) => ({ name: user[providerConfig.firstnameAttributeName], surname: user[providerConfig.surnameAttributeName], id: user.nameID, email: user[providerConfig.emailAttributeName], - role: user[providerConfig.roleAttributeName], -}) + role: user[providerConfig.roleAttributeName] as string | string[], +}); -const findOrCreateUserBySamlIdentity = async (userIdentity: Record, samlAuthProvider: SamlAuthProvider) => { +const findOrCreateUserBySamlIdentity = async ( + userIdentity: Record, + samlAuthProvider: SamlAuthProvider +) => { const mappedUser = getUser(userIdentity, samlAuthProvider); const identity = await Identity.query().findOne({ remote_id: mappedUser.id, + provider_type: 'saml', }); if (identity) { @@ -22,25 +30,38 @@ const findOrCreateUserBySamlIdentity = async (userIdentity: Record ({ + samlAuthProvider: { + relation: Base.BelongsToOneRelation, + modelClass: SamlAuthProvider, + join: { + from: 'saml_auth_providers_role_mappings.saml_auth_provider_id', + to: 'saml_auth_providers.id', + }, + }, + }); +} + +export default SamlAuthProvidersRoleMapping;