feat: write endpoints to update SamlAuthProvidersRoleMappings

This commit is contained in:
Ali BARIN
2024-09-02 15:29:16 +00:00
parent 53ce327516
commit 062199d0e3
6 changed files with 287 additions and 2 deletions

View File

@@ -0,0 +1,50 @@
import isEmpty from 'lodash/isEmpty.js';
import { renderObject } from '../../../../../helpers/renderer.js';
import SamlAuthProvider from '../../../../../models/saml-auth-provider.ee.js';
import SamlAuthProvidersRoleMapping from '../../../../../models/saml-auth-providers-role-mapping.ee.js';
export default async (request, response) => {
const samlAuthProviderId = request.params.samlAuthProviderId;
const samlAuthProvider = await SamlAuthProvider.query()
.findById(samlAuthProviderId)
.throwIfNotFound();
const samlAuthProvidersRoleMappings =
await SamlAuthProvidersRoleMapping.transaction(async (trx) => {
await samlAuthProvider
.$relatedQuery('samlAuthProvidersRoleMappings', trx)
.delete();
const roleMappings = samlAuthProvidersRoleMappingsParams(request);
if (isEmpty(roleMappings)) {
return [];
}
const samlAuthProvidersRoleMappingsData = roleMappings.map(
(samlAuthProvidersRoleMapping) => ({
...samlAuthProvidersRoleMapping,
samlAuthProviderId: samlAuthProvider.id,
})
);
const samlAuthProvidersRoleMappings =
await SamlAuthProvidersRoleMapping.query(trx).insertAndFetch(
samlAuthProvidersRoleMappingsData
);
return samlAuthProvidersRoleMappings;
});
renderObject(response, samlAuthProvidersRoleMappings);
};
const samlAuthProvidersRoleMappingsParams = (request) => {
const roleMappings = request.body;
return roleMappings.map(({ roleId, remoteRoleName }) => ({
roleId,
remoteRoleName,
}));
};

View File

@@ -0,0 +1,178 @@
import Crypto from 'node:crypto';
import { vi, describe, it, expect, beforeEach } from 'vitest';
import request from 'supertest';
import app from '../../../../../app.js';
import createAuthTokenByUserId from '../../../../../helpers/create-auth-token-by-user-id.js';
import { createRole } from '../../../../../../test/factories/role.js';
import { createUser } from '../../../../../../test/factories/user.js';
import { createSamlAuthProvider } from '../../../../../../test/factories/saml-auth-provider.ee.js';
import { createSamlAuthProvidersRoleMapping } from '../../../../../../test/factories/saml-auth-providers-role-mapping.js';
import createRoleMappingsMock from '../../../../../../test/mocks/rest/api/v1/admin/saml-auth-providers/update-role-mappings.ee.js';
import * as license from '../../../../../helpers/license.ee.js';
describe('PATCH /api/v1/admin/saml-auth-providers/:samlAuthProviderId/role-mappings', () => {
let samlAuthProvider, currentUser, userRole, token;
beforeEach(async () => {
vi.spyOn(license, 'hasValidLicense').mockResolvedValue(true);
userRole = await createRole({ key: 'admin' });
currentUser = await createUser({ roleId: userRole.id });
samlAuthProvider = await createSamlAuthProvider();
await createSamlAuthProvidersRoleMapping({
samlAuthProviderId: samlAuthProvider.id,
});
await createSamlAuthProvidersRoleMapping({
samlAuthProviderId: samlAuthProvider.id,
});
token = await createAuthTokenByUserId(currentUser.id);
});
it('should update role mappings', async () => {
const roleMappings = [
{
roleId: userRole.id,
remoteRoleName: 'Admin',
},
];
const response = await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.send(roleMappings)
.expect(200);
const expectedPayload = await createRoleMappingsMock([
{
roleId: userRole.id,
remoteRoleName: 'Admin',
id: response.body.data[0].id,
samlAuthProviderId: samlAuthProvider.id,
},
]);
expect(response.body).toStrictEqual(expectedPayload);
});
it('should delete role mappings when given empty role mappings', async () => {
const existingRoleMappings = await samlAuthProvider.$relatedQuery(
'samlAuthProvidersRoleMappings'
);
expect(existingRoleMappings.length).toBe(2);
const response = await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.send([])
.expect(200);
const expectedPayload = await createRoleMappingsMock([]);
expect(response.body).toStrictEqual({
...expectedPayload,
meta: {
...expectedPayload.meta,
type: 'Object',
},
});
});
it('should return unprocessable entity response for not existing role UUID', async () => {
const notExistingRoleUUID = Crypto.randomUUID();
const roleMappings = [
{
roleId: notExistingRoleUUID,
remoteRoleName: 'Admin',
},
];
await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.send(roleMappings)
.expect(500);
});
it('should return unprocessable entity response for invalid data', async () => {
const roleMappings = [
{
roleId: userRole.id,
remoteRoleName: {},
},
];
const response = await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.send(roleMappings)
.expect(422);
expect(response.body).toStrictEqual({
errors: {
remoteRoleName: ['must be string'],
},
meta: {
type: 'ModelValidation',
},
});
});
it('should return not found response for not existing SAML auth provider UUID', async () => {
const notExistingSamlAuthProviderUUID = Crypto.randomUUID();
const roleMappings = [
{
roleId: userRole.id,
remoteRoleName: 'Admin',
},
];
await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${notExistingSamlAuthProviderUUID}/role-mappings`
)
.set('Authorization', token)
.send(roleMappings)
.expect(404);
});
it('should not delete existing role mapping when error thrown', async () => {
const roleMappings = [
{
roleId: userRole.id,
remoteRoleName: {
invalid: 'data',
},
},
];
const roleMappingsBeforeRequest = await samlAuthProvider.$relatedQuery(
'samlAuthProvidersRoleMappings'
);
await request(app)
.patch(
`/api/v1/admin/saml-auth-providers/${samlAuthProvider.id}/role-mappings`
)
.set('Authorization', token)
.send(roleMappings)
.expect(422);
const roleMappingsAfterRequest = await samlAuthProvider.$relatedQuery(
'samlAuthProvidersRoleMappings'
);
expect(roleMappingsBeforeRequest).toStrictEqual(roleMappingsAfterRequest);
expect(roleMappingsAfterRequest.length).toBe(2);
});
});

View File

@@ -1,8 +1,13 @@
import logger from './logger.js';
import objection from 'objection';
import * as Sentry from './sentry.ee.js';
const { NotFoundError, DataError, ValidationError, UniqueViolationError } =
objection;
const {
NotFoundError,
DataError,
ForeignKeyViolationError,
ValidationError,
UniqueViolationError,
} = objection;
import NotAuthorizedError from '../errors/not-authorized.js';
import HttpError from '../errors/http.js';
import {
@@ -29,6 +34,10 @@ const errorHandler = (error, request, response, next) => {
renderUniqueViolationError(response, error);
}
if (error instanceof ForeignKeyViolationError) {
response.status(500).end();
}
if (error instanceof DataError) {
response.status(400).end();
}

View File

@@ -7,6 +7,7 @@ import updateSamlAuthProviderAction from '../../../../controllers/api/v1/admin/s
import getSamlAuthProvidersAction from '../../../../controllers/api/v1/admin/saml-auth-providers/get-saml-auth-providers.ee.js';
import getSamlAuthProviderAction from '../../../../controllers/api/v1/admin/saml-auth-providers/get-saml-auth-provider.ee.js';
import getRoleMappingsAction from '../../../../controllers/api/v1/admin/saml-auth-providers/get-role-mappings.ee.js';
import updateRoleMappingsAction from '../../../../controllers/api/v1/admin/saml-auth-providers/update-role-mappings.ee.js';
const router = Router();
@@ -50,4 +51,12 @@ router.patch(
updateSamlAuthProviderAction
);
router.patch(
'/:samlAuthProviderId/role-mappings',
authenticateUser,
authorizeAdmin,
checkIsEnterprise,
updateRoleMappingsAction
);
export default router;

View File

@@ -0,0 +1,16 @@
import { faker } from '@faker-js/faker';
import { createRole } from './role.js';
import SamlAuthProvidersRoleMapping from '../../src/models/saml-auth-providers-role-mapping.ee.js';
import { createSamlAuthProvider } from './saml-auth-provider.ee.js';
export const createSamlAuthProvidersRoleMapping = async (params = {}) => {
params.roleId = params.roleId || (await createRole()).id;
params.samlAuthProviderId =
params.samlAuthProviderId || (await createSamlAuthProvider()).id;
params.remoteRoleName = params.remoteRoleName || faker.person.jobType();
const samlAuthProvider =
await SamlAuthProvidersRoleMapping.query().insertAndFetch(params);
return samlAuthProvider;
};

View File

@@ -0,0 +1,23 @@
const createRoleMappingsMock = async (roleMappings) => {
const data = roleMappings.map((roleMapping) => {
return {
id: roleMapping.id,
samlAuthProviderId: roleMapping.samlAuthProviderId,
roleId: roleMapping.roleId,
remoteRoleName: roleMapping.remoteRoleName,
};
});
return {
data: data,
meta: {
count: data.length,
currentPage: null,
isArray: true,
totalPages: null,
type: 'SamlAuthProvidersRoleMapping',
},
};
};
export default createRoleMappingsMock;