feat: add role mappings for SAML configuration (#1210)

This commit is contained in:
Ömer Faruk Aydın
2023-08-11 19:07:39 +02:00
committed by GitHub
parent c7e1d30553
commit a6a124d2e6
8 changed files with 190 additions and 25 deletions

View File

@@ -0,0 +1,24 @@
import { Knex } from 'knex';
export async function up(knex: Knex): Promise<void> {
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<void> {
return knex.schema.dropTable('saml_auth_providers_role_mappings');
}

View File

@@ -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,
};

View File

@@ -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
);

View File

@@ -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;

View File

@@ -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!
}

View File

@@ -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<string, unknown>, providerConfig: SamlAuthProvider) => ({
const getUser = (
user: Record<string, unknown>,
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<string, unknown>, samlAuthProvider: SamlAuthProvider) => {
const findOrCreateUserBySamlIdentity = async (
userIdentity: Record<string, unknown>,
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<string, unkno
return user;
}
const createdUser = await User.query().insertGraph({
fullName: [
mappedUser.name,
mappedUser.surname
]
const mappedRoles = Array.isArray(mappedUser.role)
? mappedUser.role
: [mappedUser.role];
const samlAuthProviderRoleMapping = await samlAuthProvider
.$relatedQuery('samlAuthProvidersRoleMappings')
.whereIn('remote_role_name', mappedRoles)
.limit(1)
.first();
const createdUser = await User.query()
.insertGraph(
{
fullName: [mappedUser.name, mappedUser.surname]
.filter(Boolean)
.join(' '),
email: mappedUser.email as string,
roleId: samlAuthProvider.defaultRoleId,
roleId:
samlAuthProviderRoleMapping.roleId || samlAuthProvider.defaultRoleId,
identities: [
{
remoteId: mappedUser.id as string,
providerId: samlAuthProvider.id,
providerType: 'saml'
providerType: 'saml',
},
],
},
{
relate: ['identities'],
}
]
}, {
relate: ['identities']
}).returning('*');
)
.returning('*');
return createdUser;
};

View File

@@ -3,6 +3,7 @@ import type { SamlConfig } from '@node-saml/passport-saml';
import appConfig from '../config/app';
import Base from './base';
import Identity from './identity.ee';
import SamlAuthProvidersRoleMapping from './saml-auth-providers-role-mapping.ee';
class SamlAuthProvider extends Base {
id!: string;
@@ -17,6 +18,7 @@ class SamlAuthProvider extends Base {
roleAttributeName: string;
defaultRoleId: string;
active: boolean;
samlAuthProvidersRoleMappings?: SamlAuthProvidersRoleMapping[];
static tableName = 'saml_auth_providers';
@@ -63,6 +65,14 @@ class SamlAuthProvider extends Base {
to: 'saml_auth_providers.id',
},
},
samlAuthProvidersRoleMappings: {
relation: Base.HasManyRelation,
modelClass: SamlAuthProvidersRoleMapping,
join: {
from: 'saml_auth_providers.id',
to: 'saml_auth_providers_role_mappings.saml_auth_provider_id',
},
},
});
get config(): SamlConfig {

View File

@@ -0,0 +1,36 @@
import Base from './base';
import SamlAuthProvider from './saml-auth-provider.ee';
class SamlAuthProvidersRoleMapping extends Base {
id!: string;
samlAuthProviderId: string;
roleId: string;
remoteRoleName: string;
static tableName = 'saml_auth_providers_role_mappings';
static jsonSchema = {
type: 'object',
required: ['samlAuthProviderId', 'roleId', 'remoteRoleName'],
properties: {
id: { type: 'string', format: 'uuid' },
samlAuthProviderId: { type: 'string', format: 'uuid' },
roleId: { type: 'string', format: 'uuid' },
remoteRoleName: { type: 'string', minLength: 1 },
},
};
static relationMappings = () => ({
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;