diff --git a/packages/backend/src/db/migrations/20230807114158_seed_saml_permissions_to_admin.ts b/packages/backend/src/db/migrations/20230807114158_seed_saml_permissions_to_admin.ts new file mode 100644 index 00000000..584127ce --- /dev/null +++ b/packages/backend/src/db/migrations/20230807114158_seed_saml_permissions_to_admin.ts @@ -0,0 +1,33 @@ +import { Knex } from 'knex'; + +const getPermissionForRole = ( + roleId: string, + subject: string, + actions: string[] +) => + actions.map((action) => ({ + role_id: roleId, + subject, + action, + conditions: [], + })); + +export async function up(knex: Knex): Promise { + const role = (await knex('roles') + .first(['id', 'key']) + .where({ key: 'admin' }) + .limit(1)) as { id: string; key: string }; + + await knex('permissions').insert( + getPermissionForRole(role.id, 'SamlAuthProvider', [ + 'create', + 'read', + 'delete', + 'update', + ]) + ); +} + +export async function down(knex: Knex): Promise { + await knex('permissions').where({ subject: 'SamlAuthProvider' }).delete(); +} diff --git a/packages/backend/src/graphql/mutation-resolvers.ts b/packages/backend/src/graphql/mutation-resolvers.ts index de67248d..5411cc05 100644 --- a/packages/backend/src/graphql/mutation-resolvers.ts +++ b/packages/backend/src/graphql/mutation-resolvers.ts @@ -25,6 +25,7 @@ import updateRole from './mutations/update-role.ee'; import updateStep from './mutations/update-step'; import updateUser from './mutations/update-user.ee'; import verifyConnection from './mutations/verify-connection'; +import createSamlAuthProvider from './mutations/create-saml-auth-provider.ee'; const mutationResolvers = { createConnection, @@ -54,6 +55,7 @@ const mutationResolvers = { updateRole, updateStep, verifyConnection, + createSamlAuthProvider, }; export default mutationResolvers; diff --git a/packages/backend/src/graphql/mutations/create-saml-auth-provider.ee.ts b/packages/backend/src/graphql/mutations/create-saml-auth-provider.ee.ts new file mode 100644 index 00000000..11d74f43 --- /dev/null +++ b/packages/backend/src/graphql/mutations/create-saml-auth-provider.ee.ts @@ -0,0 +1,54 @@ +import type { SamlConfig } from '@node-saml/passport-saml'; +import SamlAuthProvider from '../../models/saml-auth-provider.ee'; +import Context from '../../types/express/context'; + +type Params = { + input: { + name: string; + certificate: string; + signatureAlgorithm: SamlConfig['signatureAlgorithm']; + issuer: string; + entryPoint: string; + firstnameAttributeName: string; + surnameAttributeName: string; + emailAttributeName: string; + roleAttributeName: string; + defaultRoleId: string; + active: boolean; + }; +}; + +const createSamlAuthProvider = async ( + _parent: unknown, + params: Params, + context: Context +) => { + context.currentUser.can('create', 'SamlAuthProvider'); + + const samlAuthProviderPayload: Partial = { + ...params.input, + }; + + const existingSamlAuthProvider = await SamlAuthProvider.query() + .limit(1) + .first(); + + let samlAuthProvider: SamlAuthProvider; + + if (!existingSamlAuthProvider) { + samlAuthProvider = await SamlAuthProvider.query().insert( + samlAuthProviderPayload + ); + + return samlAuthProvider; + } + + samlAuthProvider = await SamlAuthProvider.query().patchAndFetchById( + existingSamlAuthProvider.id, + samlAuthProviderPayload + ); + + return samlAuthProvider; +}; + +export default createSamlAuthProvider; diff --git a/packages/backend/src/graphql/schema.graphql b/packages/backend/src/graphql/schema.graphql index a8dda7db..87a28d16 100644 --- a/packages/backend/src/graphql/schema.graphql +++ b/packages/backend/src/graphql/schema.graphql @@ -42,10 +42,7 @@ type Query { getTrialStatus: GetTrialStatus getSubscriptionStatus: GetSubscriptionStatus getSamlAuthProviders: [GetSamlAuthProviders] - getUsers( - limit: Int! - offset: Int! - ): UserConnection + getUsers(limit: Int!, offset: Int!): UserConnection getUser(id: String!): User getRoles: [Role] getRole(id: String!): Role @@ -81,6 +78,7 @@ type Mutation { updateStep(input: UpdateStepInput): Step updateUser(input: UpdateUserInput): User verifyConnection(input: VerifyConnectionInput): Connection + createSamlAuthProvider(input: CreateSamlAuthProviderInput): SamlAuthProvider } """ @@ -292,6 +290,20 @@ type Execution { flow: Flow } +type SamlAuthProvider { + id: String + name: String + certificate: String + signatureAlgorithm: String + issuer: String + entryPoint: String + firstnameAttributeName: String + surnameAttributeName: String + emailAttributeName: String + roleAttributeName: String + active: Boolean +} + type UserConnection { edges: [UserEdge] pageInfo: PageInfo @@ -323,6 +335,20 @@ input VerifyConnectionInput { id: String! } +input CreateSamlAuthProviderInput { + name: String! + certificate: String! + signatureAlgorithm: String! + issuer: String! + entryPoint: String! + firstnameAttributeName: String! + surnameAttributeName: String! + emailAttributeName: String! + roleAttributeName: String! + defaultRoleId: String! + active: Boolean! +} + input DeleteConnectionInput { id: String! } diff --git a/packages/backend/src/models/saml-auth-provider.ee.ts b/packages/backend/src/models/saml-auth-provider.ee.ts index 7eb80c47..6f028a26 100644 --- a/packages/backend/src/models/saml-auth-provider.ee.ts +++ b/packages/backend/src/models/saml-auth-provider.ee.ts @@ -8,7 +8,7 @@ class SamlAuthProvider extends Base { id!: string; name: string; certificate: string; - signatureAlgorithm: SamlConfig["signatureAlgorithm"]; + signatureAlgorithm: SamlConfig['signatureAlgorithm']; issuer: string; entryPoint: string; firstnameAttributeName: string; @@ -39,7 +39,10 @@ class SamlAuthProvider extends Base { id: { type: 'string', format: 'uuid' }, name: { type: 'string', minLength: 1 }, certificate: { type: 'string', minLength: 1 }, - signatureAlgorithm: { type: 'string', enum: ['sha1', 'sha256', 'sha512'] }, + signatureAlgorithm: { + type: 'string', + enum: ['sha1', 'sha256', 'sha512'], + }, issuer: { type: 'string', minLength: 1 }, entryPoint: { type: 'string', minLength: 1 }, firstnameAttributeName: { type: 'string', minLength: 1 }, @@ -74,7 +77,7 @@ class SamlAuthProvider extends Base { entryPoint: this.entryPoint, issuer: this.issuer, signatureAlgorithm: this.signatureAlgorithm, - } + }; } } diff --git a/packages/backend/src/models/user.ts b/packages/backend/src/models/user.ts index b0917dfd..7afee61b 100644 --- a/packages/backend/src/models/user.ts +++ b/packages/backend/src/models/user.ts @@ -1,10 +1,7 @@ import bcrypt from 'bcrypt'; import { DateTime } from 'luxon'; import crypto from 'node:crypto'; -import { - ModelOptions, - QueryContext -} from 'objection'; +import { ModelOptions, QueryContext } from 'objection'; import appConfig from '../config/app'; import checkLicense from '../helpers/check-license.ee'; @@ -164,8 +161,8 @@ class User extends Base { join: { from: 'identities.user_id', to: 'users.id', - } - } + }, + }, }); login(password: string) { @@ -299,8 +296,10 @@ class User extends Base { if (Array.isArray(this.permissions)) { this.permissions = this.permissions.filter((permission) => { const isRolePermission = permission.subject === 'Role'; + const isSamlAuthProviderPermission = + permission.subject === 'SamlAuthProvider'; - return !isRolePermission; + return !isRolePermission && !isSamlAuthProviderPermission; }); } @@ -318,11 +317,10 @@ class User extends Base { const relevantRule = this.ability.relevantRuleFor(action, subject); - const conditions = relevantRule?.conditions as string[] || []; - const conditionMap: Record = Object - .fromEntries( - conditions.map((condition) => [condition, true]) - ) + const conditions = (relevantRule?.conditions as string[]) || []; + const conditionMap: Record = Object.fromEntries( + conditions.map((condition) => [condition, true]) + ); return conditionMap; }