mirror of
https://github.com/Unleash/unleash.git
synced 2025-05-22 01:16:07 +02:00
fix: return 400 on invalid POST data to project access endpoint (#5610)
This PR fixes the issue discussed in SR-234, where you would get a 200 OK response even if your POST request to `/api/admin/projects/<project-name>/access` contains invalid data (and nothing is persisted).
This commit is contained in:
parent
8961a6e1db
commit
8e430810ef
@ -2,6 +2,7 @@ import dbInit from '../../test/e2e/helpers/database-init';
|
|||||||
import getLogger from '../../test/fixtures/no-logger';
|
import getLogger from '../../test/fixtures/no-logger';
|
||||||
import { PermissionRef } from 'lib/services/access-service';
|
import { PermissionRef } from 'lib/services/access-service';
|
||||||
import { AccessStore } from './access-store';
|
import { AccessStore } from './access-store';
|
||||||
|
import { BadDataError } from '../../lib/error';
|
||||||
|
|
||||||
let db;
|
let db;
|
||||||
|
|
||||||
@ -104,3 +105,29 @@ test.each([
|
|||||||
expect(result).toStrictEqual(expected);
|
expect(result).toStrictEqual(expected);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
describe('addAccessToProject', () => {
|
||||||
|
test.each(['roles', 'groups', 'users'])(
|
||||||
|
'should throw a bad data error if there is invalid data in the %s property of the addAccessToProject payload',
|
||||||
|
async (property) => {
|
||||||
|
const access = db.stores.accessStore as AccessStore;
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
roles: [4, 5],
|
||||||
|
groups: [],
|
||||||
|
users: [],
|
||||||
|
[property]: [123456789],
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(() =>
|
||||||
|
access.addAccessToProject(
|
||||||
|
payload.roles,
|
||||||
|
payload.groups,
|
||||||
|
payload.users,
|
||||||
|
'projectId',
|
||||||
|
'createdBy',
|
||||||
|
),
|
||||||
|
).rejects.toThrow(BadDataError);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
@ -26,6 +26,7 @@ import {
|
|||||||
PermissionRef,
|
PermissionRef,
|
||||||
} from 'lib/services/access-service';
|
} from 'lib/services/access-service';
|
||||||
import { inTransaction } from './transaction';
|
import { inTransaction } from './transaction';
|
||||||
|
import BadDataError from '../error/bad-data-error';
|
||||||
|
|
||||||
const T = {
|
const T = {
|
||||||
ROLE_USER: 'role_user',
|
ROLE_USER: 'role_user',
|
||||||
@ -618,6 +619,18 @@ export class AccessStore implements IAccessStore {
|
|||||||
.whereIn('type', PROJECT_ROLE_TYPES)
|
.whereIn('type', PROJECT_ROLE_TYPES)
|
||||||
.pluck('id');
|
.pluck('id');
|
||||||
|
|
||||||
|
if (validatedProjectRoleIds.length !== roles.length) {
|
||||||
|
const invalidRoles = roles.filter(
|
||||||
|
(role) => !validatedProjectRoleIds.includes(role),
|
||||||
|
);
|
||||||
|
|
||||||
|
throw new BadDataError(
|
||||||
|
`You can't add access to a project with roles that aren't project roles or that don't exist. These roles are not valid: ${invalidRoles.join(
|
||||||
|
', ',
|
||||||
|
)}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const groupRows = groups.flatMap((group) =>
|
const groupRows = groups.flatMap((group) =>
|
||||||
validatedProjectRoleIds.map((role) => ({
|
validatedProjectRoleIds.map((role) => ({
|
||||||
group_id: group,
|
group_id: group,
|
||||||
@ -636,17 +649,54 @@ export class AccessStore implements IAccessStore {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await inTransaction(this.db, async (tx) => {
|
await inTransaction(this.db, async (tx) => {
|
||||||
|
const errors: string[] = [];
|
||||||
if (groupRows.length > 0) {
|
if (groupRows.length > 0) {
|
||||||
await tx(T.GROUP_ROLE)
|
await tx(T.GROUP_ROLE)
|
||||||
.insert(groupRows)
|
.insert(groupRows)
|
||||||
.onConflict(['project', 'role_id', 'group_id'])
|
.onConflict(['project', 'role_id', 'group_id'])
|
||||||
.merge();
|
.merge()
|
||||||
|
.catch((err) => {
|
||||||
|
if (
|
||||||
|
err.message.includes(
|
||||||
|
`violates foreign key constraint "group_role_group_id_fkey"`,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
errors.push(
|
||||||
|
`Your request contains one or more group IDs that do not exist. You sent these group IDs: ${groups.join(
|
||||||
|
', ',
|
||||||
|
)}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
if (userRows.length > 0) {
|
if (userRows.length > 0) {
|
||||||
await tx(T.ROLE_USER)
|
await tx(T.ROLE_USER)
|
||||||
.insert(userRows)
|
.insert(userRows)
|
||||||
.onConflict(['project', 'role_id', 'user_id'])
|
.onConflict(['project', 'role_id', 'user_id'])
|
||||||
.merge();
|
.merge()
|
||||||
|
.catch((err) => {
|
||||||
|
if (
|
||||||
|
err.message.includes(
|
||||||
|
`violates foreign key constraint "role_user_user_id_fkey"`,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
errors.push(
|
||||||
|
`Your request contains one or more user IDs that do not exist. You sent these user IDs: ${users.join(
|
||||||
|
', ',
|
||||||
|
)}.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errors.length) {
|
||||||
|
const mapped = errors.map((message) => ({
|
||||||
|
message,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// because TS doesn't understand that the non-empty
|
||||||
|
// array is guaranteed to have at least one element
|
||||||
|
throw new BadDataError('', [mapped[0], ...mapped.slice(1)]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import { IRole } from '../../lib/types/stores/access-store';
|
|||||||
import { IGroup, ROLE_CREATED } from '../../lib/types';
|
import { IGroup, ROLE_CREATED } from '../../lib/types';
|
||||||
import EventService from './event-service';
|
import EventService from './event-service';
|
||||||
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
|
import FakeFeatureTagStore from '../../test/fixtures/fake-feature-tag-store';
|
||||||
|
import BadDataError from '../../lib/error/bad-data-error';
|
||||||
|
|
||||||
function getSetup(customRootRolesKillSwitch: boolean = true) {
|
function getSetup(customRootRolesKillSwitch: boolean = true) {
|
||||||
const config = createTestConfig({
|
const config = createTestConfig({
|
||||||
@ -265,3 +266,18 @@ test('throws error when trying to delete a project role in use by group', async
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('addAccessToProject', () => {
|
||||||
|
test('should throw an error when you try add access with an empty list of roles', async () => {
|
||||||
|
const { accessService } = getSetup();
|
||||||
|
await expect(() =>
|
||||||
|
accessService.addAccessToProject(
|
||||||
|
[],
|
||||||
|
[1],
|
||||||
|
[1],
|
||||||
|
'projectId',
|
||||||
|
'createdBy',
|
||||||
|
),
|
||||||
|
).rejects.toThrow(BadDataError);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
@ -277,6 +277,11 @@ export class AccessService {
|
|||||||
projectId: string,
|
projectId: string,
|
||||||
createdBy: string,
|
createdBy: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
|
if (roles.length === 0) {
|
||||||
|
throw new BadDataError(
|
||||||
|
"You can't grant access without any roles. The roles array you sent was empty.",
|
||||||
|
);
|
||||||
|
}
|
||||||
return this.store.addAccessToProject(
|
return this.store.addAccessToProject(
|
||||||
roles,
|
roles,
|
||||||
groups,
|
groups,
|
||||||
|
@ -21,6 +21,7 @@ import {
|
|||||||
createFeatureToggleService,
|
createFeatureToggleService,
|
||||||
createProjectService,
|
createProjectService,
|
||||||
} from '../../../lib/features';
|
} from '../../../lib/features';
|
||||||
|
import { BadDataError } from '../../../lib/error';
|
||||||
|
|
||||||
let db: ITestDb;
|
let db: ITestDb;
|
||||||
let stores: IUnleashStores;
|
let stores: IUnleashStores;
|
||||||
@ -1382,13 +1383,15 @@ test('calling add access with invalid project role ids should not assign those r
|
|||||||
|
|
||||||
const adminRootRole = await accessService.getRoleByName(RoleName.ADMIN);
|
const adminRootRole = await accessService.getRoleByName(RoleName.ADMIN);
|
||||||
|
|
||||||
|
await expect(() =>
|
||||||
accessService.addAccessToProject(
|
accessService.addAccessToProject(
|
||||||
[adminRootRole.id, 9999],
|
[adminRootRole.id, 9999],
|
||||||
[],
|
[],
|
||||||
[emptyUser.id],
|
[emptyUser.id],
|
||||||
projectName,
|
projectName,
|
||||||
'some-admin-user',
|
'some-admin-user',
|
||||||
);
|
),
|
||||||
|
).rejects.toThrow(BadDataError);
|
||||||
|
|
||||||
const newAssignedPermissions =
|
const newAssignedPermissions =
|
||||||
await accessService.getPermissionsForUser(emptyUser);
|
await accessService.getPermissionsForUser(emptyUser);
|
||||||
|
Loading…
Reference in New Issue
Block a user