1
0
mirror of https://github.com/Unleash/unleash.git synced 2024-12-22 19:07:54 +01:00

fix: admin token requests does not automatically have id (#6501)

To check that users do indeed have permissions to update the roles from
project-service, we've been depending on req.user.id.
We had one error on Friday March 8th, where we managed to send
undefined/null to a method that requires a number. This PR assumes that
if we have an API token, and we have admin permissions and userId is not
set we're a legacy admin token.

It uses the util method for extractUserId(req: IAuthRequest | IApiRequest), so if we've passed through the apiTokenMiddleware first, we'll have userId -42, if we haven't, we'll get -1337.
This commit is contained in:
Christopher Kolstad 2024-03-12 10:39:37 +01:00 committed by GitHub
parent faf2153423
commit 55da9b8133
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 81 additions and 8 deletions

View File

@ -8,6 +8,7 @@ import { nameType } from '../../routes/util';
import { projectSchema } from '../../services/project-schema'; import { projectSchema } from '../../services/project-schema';
import NotFoundError from '../../error/notfound-error'; import NotFoundError from '../../error/notfound-error';
import { import {
ADMIN_TOKEN_USER,
CreateProject, CreateProject,
DEFAULT_PROJECT, DEFAULT_PROJECT,
FeatureToggle, FeatureToggle,
@ -44,6 +45,7 @@ import {
ProjectUserUpdateRoleEvent, ProjectUserUpdateRoleEvent,
RoleName, RoleName,
SYSTEM_USER, SYSTEM_USER,
SYSTEM_USER_ID,
} from '../../types'; } from '../../types';
import { import {
IProjectAccessModel, IProjectAccessModel,
@ -701,8 +703,12 @@ export default class ProjectService {
); );
} }
private isAdmin(roles: IRoleWithProject[]): boolean { private isAdmin(userId: number, roles: IRoleWithProject[]): boolean {
return roles.some((r) => r.name === RoleName.ADMIN); return (
userId === SYSTEM_USER_ID ||
userId === ADMIN_TOKEN_USER.id ||
roles.some((r) => r.name === RoleName.ADMIN)
);
} }
private isProjectOwner( private isProjectOwner(
@ -723,7 +729,7 @@ export default class ProjectService {
projectId, projectId,
); );
if ( if (
this.isAdmin(userRoles) || this.isAdmin(userAddingAccess, userRoles) ||
this.isProjectOwner(userRoles, projectId) this.isProjectOwner(userRoles, projectId)
) { ) {
return true; return true;

View File

@ -7,7 +7,7 @@ import ApiUser from '../types/api-user';
import { IFeatureToggleStore } from '../features/feature-toggle/types/feature-toggle-store-type'; import { IFeatureToggleStore } from '../features/feature-toggle/types/feature-toggle-store-type';
import FakeFeatureToggleStore from '../features/feature-toggle/fakes/fake-feature-toggle-store'; import FakeFeatureToggleStore from '../features/feature-toggle/fakes/fake-feature-toggle-store';
import { ApiTokenType } from '../types/models/api-token'; import { ApiTokenType } from '../types/models/api-token';
import { ISegmentStore } from '../types'; import { ISegmentStore, SYSTEM_USER_ID } from '../types';
import FakeSegmentStore from '../../test/fixtures/fake-segment-store'; import FakeSegmentStore from '../../test/fixtures/fake-segment-store';
let config: IUnleashConfig; let config: IUnleashConfig;
@ -73,6 +73,65 @@ test('should give api-user ADMIN permission', async () => {
expect(hasAccess).toBe(true); expect(hasAccess).toBe(true);
}); });
describe('ADMIN tokens should have user id -1337 when only passed through rbac-middleware', () => {
/// Will be -42 (ADMIN_USER.id) when we have the api-token-middleware run first
test('Should give ADMIN api-user userid -1337 (SYSTEM_USER_ID)', async () => {
const accessService = {
hasPermission: jest.fn(),
};
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);
const cb = jest.fn();
const req: any = {
user: new ApiUser({
tokenName: 'api',
permissions: [perms.ADMIN],
project: '*',
environment: '*',
type: ApiTokenType.ADMIN,
secret: 'a',
}),
};
func(req, undefined, cb);
const hasAccess = await req.checkRbac(perms.ADMIN);
expect(req.user.id).toBe(SYSTEM_USER_ID);
expect(hasAccess).toBe(true);
});
/// Will be -42 (ADMIN_USER.id) when we have the api-token-middleware run first
test('Also when checking against permission NONE, userid should still be -1337', async () => {
const accessService = {
hasPermission: jest.fn(),
};
const func = rbacMiddleware(
config,
{ featureToggleStore, segmentStore },
accessService,
);
const cb = jest.fn();
const req: any = {
user: new ApiUser({
tokenName: 'api',
permissions: [perms.ADMIN],
project: '*',
environment: '*',
type: ApiTokenType.ADMIN,
secret: 'a',
}),
};
func(req, undefined, cb);
const hasAccess = await req.checkRbac(perms.NONE);
expect(req.user.id).toBe(SYSTEM_USER_ID);
expect(hasAccess).toBe(true);
});
});
test('should not give api-user ADMIN permission', async () => { test('should not give api-user ADMIN permission', async () => {
const accessService = { const accessService = {
hasPermission: jest.fn(), hasPermission: jest.fn(),

View File

@ -1,7 +1,7 @@
import { import {
ADMIN,
CREATE_FEATURE, CREATE_FEATURE,
DELETE_FEATURE, DELETE_FEATURE,
ADMIN,
UPDATE_FEATURE, UPDATE_FEATURE,
UPDATE_PROJECT_SEGMENT, UPDATE_PROJECT_SEGMENT,
} from '../types/permissions'; } from '../types/permissions';
@ -9,6 +9,7 @@ import { IUnleashConfig } from '../types/option';
import { IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types/stores';
import User from '../types/user'; import User from '../types/user';
import { Request } from 'express'; import { Request } from 'express';
import { extractUserId } from '../util';
interface PermissionChecker { interface PermissionChecker {
hasPermission( hasPermission(
@ -56,7 +57,14 @@ const rbacMiddleware = (
} }
if (user.isAPI) { if (user.isAPI) {
return user.permissions.includes(ADMIN); if (user.permissions.includes(ADMIN)) {
if (!req.user.id) {
req.user.id = extractUserId(req);
}
return true;
} else {
return false;
}
} }
if (!user.id) { if (!user.id) {

View File

@ -31,8 +31,8 @@ import { roleSchema } from '../schema/role-schema';
import { import {
ALL_ENVS, ALL_ENVS,
ALL_PROJECTS, ALL_PROJECTS,
CUSTOM_ROOT_ROLE_TYPE,
CUSTOM_PROJECT_ROLE_TYPE, CUSTOM_PROJECT_ROLE_TYPE,
CUSTOM_ROOT_ROLE_TYPE,
ROOT_ROLE_TYPES, ROOT_ROLE_TYPES,
} from '../util/constants'; } from '../util/constants';
import { DEFAULT_PROJECT } from '../types/project'; import { DEFAULT_PROJECT } from '../types/project';
@ -219,7 +219,7 @@ export class AccessService {
/** /**
* Returns all roles the user has in the project. * Returns all roles the user has in the project.
* Including roles via groups. * Including roles via groups.
* In addition it includes root roles * In addition, it includes root roles
* @param userId user to find roles for * @param userId user to find roles for
* @param project project to find roles for * @param project project to find roles for
*/ */