1
0
mirror of https://github.com/Unleash/unleash.git synced 2025-02-23 00:22:19 +01:00

fix: User audit events (create, update, delete) should include rootRole. (#5399)

Audit events for USER_CREATE, USER_UPDATE and USER_DELETE did not
include the users rootRole.


![image](https://github.com/Unleash/unleash/assets/158948/fcbc1407-e4f0-438f-86cf-7073205cd8c2)

---------

Co-authored-by: Gastón Fournier <gaston@getunleash.io>
This commit is contained in:
Ivar Conradi Østhus 2023-11-24 16:06:37 +01:00 committed by GitHub
parent 47e214d96f
commit f00eac0881
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 252 additions and 99 deletions

View File

@ -12,7 +12,7 @@ import {
IUserRole, IUserRole,
IUserWithProjectRoles, IUserWithProjectRoles,
} from '../types/stores/access-store'; } from '../types/stores/access-store';
import { IPermission, IUserAccessOverview } from '../types/model'; import { IPermission, IUserAccessOverview, RoleType } from '../types/model';
import NotFoundError from '../error/notfound-error'; import NotFoundError from '../error/notfound-error';
import { import {
ENVIRONMENT_PERMISSION_TYPE, ENVIRONMENT_PERMISSION_TYPE,
@ -360,6 +360,7 @@ export class AccessStore implements IAccessStore {
.andWhere('ru.project', projectId); .andWhere('ru.project', projectId);
return rows.map((r) => ({ return rows.map((r) => ({
userId: r.user_id, userId: r.user_id,
roleId,
addedAt: r.created_at, addedAt: r.created_at,
})); }));
} }
@ -400,6 +401,16 @@ export class AccessStore implements IAccessStore {
.where('ru.user_id', '=', userId); .where('ru.user_id', '=', userId);
} }
async getRootRoleForUser(userId: number): Promise<IRole | undefined> {
return this.db
.select(['id', 'name', 'type', 'description'])
.from<IRole[]>(T.ROLES)
.innerJoin(`${T.ROLE_USER} as ru`, 'ru.role_id', 'id')
.where('ru.user_id', '=', userId)
.andWhere('type', '=', RoleType.ROOT)
.first();
}
async getUserIdsForRole(roleId: number): Promise<number[]> { async getUserIdsForRole(roleId: number): Promise<number[]> {
const rows = await this.db const rows = await this.db
.select(['user_id']) .select(['user_id'])

View File

@ -170,8 +170,7 @@ class UserController extends Controller {
const projects = await this.projectService.getProjectsByUser(user.id); const projects = await this.projectService.getProjectsByUser(user.id);
const roles = await this.accessService.getUserRootRoles(user.id); const rootRole = await this.accessService.getRootRoleForUser(user.id);
const { project, ...rootRole } = roles[0];
const responseData: ProfileSchema = { const responseData: ProfileSchema = {
projects, projects,
rootRole, rootRole,

View File

@ -18,6 +18,13 @@ describe('Public Signup API', () => {
...stores.accessStore, ...stores.accessStore,
addUserToRole: jest.fn(), addUserToRole: jest.fn(),
removeRolesOfTypeForUser: jest.fn(), removeRolesOfTypeForUser: jest.fn(),
getRolesForUserId: () => Promise.resolve([]),
getRootRoleForUser: () =>
Promise.resolve({
id: -1,
name: RoleName.VIEWER,
type: RoleType.ROOT,
}),
}; };
const services = createServices(stores, config); const services = createServices(stores, config);

View File

@ -190,9 +190,8 @@ test('user with custom root role should get a user root role', async () => {
}; };
await accessService.setUserRootRole(user.id, customRootRole.id); await accessService.setUserRootRole(user.id, customRootRole.id);
const roles = await accessService.getUserRootRoles(user.id); const role = await accessService.getRootRoleForUser(user.id);
expect(roles).toHaveLength(1); expect(role.name).toBe('custom-root-role');
expect(roles[0].name).toBe('custom-root-role');
const events = await eventStore.getEvents(); const events = await eventStore.getEvents();
expect(events).toHaveLength(1); expect(events).toHaveLength(1);
expect(events[0]).toEqual({ expect(events[0]).toEqual({

View File

@ -8,7 +8,6 @@ import {
IRole, IRole,
IRoleDescriptor, IRoleDescriptor,
IRoleWithPermissions, IRoleWithPermissions,
IRoleWithProject,
IUserPermission, IUserPermission,
IUserRole, IUserRole,
IUserWithProjectRoles, IUserWithProjectRoles,
@ -362,9 +361,13 @@ export class AccessService {
} }
} }
async getUserRootRoles(userId: number): Promise<IRoleWithProject[]> { async getRootRoleForUser(userId: number): Promise<IRole> {
const userRoles = await this.store.getRolesForUserId(userId); const rootRole = await this.store.getRootRoleForUser(userId);
return userRoles.filter(({ type }) => ROOT_ROLE_TYPES.includes(type)); if (!rootRole) {
const defaultRole = await this.getPredefinedRole(RoleName.VIEWER);
return defaultRole;
}
return rootRole;
} }
async removeUserFromRole( async removeUserFromRole(
@ -602,9 +605,20 @@ export class AccessService {
return role; return role;
} }
async getRootRole(roleName: RoleName): Promise<IRole | undefined> { /*
const roles = await this.roleStore.getRootRoles(); This method is intended to give a predicable way to fetch
return roles.find((r) => r.name === roleName); pre-defined roles defined in the RoleName enum. This method
should not be used to fetch custom root or project roles.
*/
async getPredefinedRole(roleName: RoleName): Promise<IRole> {
const roles = await this.roleStore.getRoles();
const role = roles.find((r) => r.name === roleName);
if (!role) {
throw new BadDataError(
`Could not find pre-defined role with name ${RoleName}`,
);
}
return role;
} }
async getAllRoles(): Promise<ICustomRole[]> { async getAllRoles(): Promise<ICustomRole[]> {

View File

@ -34,7 +34,7 @@ export class AccountService {
async getAll(): Promise<IUserWithRole[]> { async getAll(): Promise<IUserWithRole[]> {
const accounts = await this.store.getAll(); const accounts = await this.store.getAll();
const defaultRole = await this.accessService.getRootRole( const defaultRole = await this.accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const userRoles = await this.accessService.getRootRoleForAllUsers(); const userRoles = await this.accessService.getRootRoleForAllUsers();

View File

@ -4,7 +4,7 @@ import Joi from 'joi';
import { URL } from 'url'; import { URL } from 'url';
import { Logger } from '../logger'; import { Logger } from '../logger';
import User, { IUser } from '../types/user'; import User, { IUser, IUserWithRootRole } from '../types/user';
import isEmail from '../util/is-email'; import isEmail from '../util/is-email';
import { AccessService } from './access-service'; import { AccessService } from './access-service';
import ResetTokenService from './reset-token-service'; import ResetTokenService from './reset-token-service';
@ -16,7 +16,14 @@ import { IAuthOption, IUnleashConfig } from '../types/option';
import SessionService from './session-service'; import SessionService from './session-service';
import { IUnleashStores } from '../types/stores'; import { IUnleashStores } from '../types/stores';
import PasswordUndefinedError from '../error/password-undefined'; import PasswordUndefinedError from '../error/password-undefined';
import { USER_UPDATED, USER_CREATED, USER_DELETED } from '../types/events'; import {
USER_UPDATED,
USER_CREATED,
USER_DELETED,
UserCreatedEvent,
UserUpdatedEvent,
UserDeletedEvent,
} from '../types/events';
import { IUserStore } from '../types/stores/user-store'; import { IUserStore } from '../types/stores/user-store';
import { RoleName } from '../types/model'; import { RoleName } from '../types/model';
import SettingService from './setting-service'; import SettingService from './setting-service';
@ -53,10 +60,6 @@ export interface ILoginUserRequest {
autoCreate?: boolean; autoCreate?: boolean;
} }
interface IUserWithRole extends IUser {
rootRole: number;
}
const saltRounds = 10; const saltRounds = 10;
class UserService { class UserService {
@ -173,9 +176,9 @@ class UserService {
} }
} }
async getAll(): Promise<IUserWithRole[]> { async getAll(): Promise<IUserWithRootRole[]> {
const users = await this.store.getAll(); const users = await this.store.getAll();
const defaultRole = await this.accessService.getRootRole( const defaultRole = await this.accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const userRoles = await this.accessService.getRootRoleForAllUsers(); const userRoles = await this.accessService.getRootRoleForAllUsers();
@ -187,14 +190,10 @@ class UserService {
return usersWithRootRole; return usersWithRootRole;
} }
async getUser(id: number): Promise<IUserWithRole> { async getUser(id: number): Promise<IUserWithRootRole> {
const roles = await this.accessService.getUserRootRoles(id);
const defaultRole = await this.accessService.getRootRole(
RoleName.VIEWER,
);
const roleId = roles.length > 0 ? roles[0].id : defaultRole.id;
const user = await this.store.get(id); const user = await this.store.get(id);
return { ...user, rootRole: roleId }; const rootRole = await this.accessService.getRootRoleForUser(id);
return { ...user, rootRole: rootRole.id };
} }
async search(query: string): Promise<IUser[]> { async search(query: string): Promise<IUser[]> {
@ -208,7 +207,7 @@ class UserService {
async createUser( async createUser(
{ username, email, name, password, rootRole }: ICreateUser, { username, email, name, password, rootRole }: ICreateUser,
updatedBy?: IUser, updatedBy?: IUser,
): Promise<IUser> { ): Promise<IUserWithRootRole> {
if (!username && !email) { if (!username && !email) {
throw new BadDataError('You must specify username or email'); throw new BadDataError('You must specify username or email');
} }
@ -235,36 +234,27 @@ class UserService {
await this.store.setPasswordHash(user.id, passwordHash); await this.store.setPasswordHash(user.id, passwordHash);
} }
await this.eventService.storeEvent({ const userCreated = await this.getUser(user.id);
type: USER_CREATED,
createdBy: this.getCreatedBy(updatedBy),
data: this.mapUserToData(user),
});
return user; await this.eventService.storeEvent(
new UserCreatedEvent({
createdBy: this.getCreatedBy(updatedBy),
userCreated,
}),
);
return userCreated;
} }
private getCreatedBy(updatedBy: IUser = systemUser) { private getCreatedBy(updatedBy: IUser = systemUser) {
return updatedBy.username || updatedBy.email; return updatedBy.username || updatedBy.email;
} }
private mapUserToData(user?: IUser): any {
if (!user) {
return undefined;
}
return {
id: user.id,
name: user.name,
username: user.username,
email: user.email,
};
}
async updateUser( async updateUser(
{ id, name, email, rootRole }: IUpdateUser, { id, name, email, rootRole }: IUpdateUser,
updatedBy?: IUser, updatedBy?: IUser,
): Promise<IUser> { ): Promise<IUserWithRootRole> {
const preUser = await this.store.get(id); const preUser = await this.getUser(id);
if (email) { if (email) {
Joi.assert(email, Joi.string().email(), 'Email'); Joi.assert(email, Joi.string().email(), 'Email');
@ -284,28 +274,32 @@ class UserService {
? await this.store.update(id, payload) ? await this.store.update(id, payload)
: preUser; : preUser;
await this.eventService.storeEvent({ const storedUser = await this.getUser(user.id);
type: USER_UPDATED,
createdBy: this.getCreatedBy(updatedBy),
data: this.mapUserToData(user),
preData: this.mapUserToData(preUser),
});
return user; await this.eventService.storeEvent(
new UserUpdatedEvent({
createdBy: this.getCreatedBy(updatedBy),
preUser: preUser,
postUser: storedUser,
}),
);
return storedUser;
} }
async deleteUser(userId: number, updatedBy?: IUser): Promise<void> { async deleteUser(userId: number, updatedBy?: IUser): Promise<void> {
const user = await this.store.get(userId); const user = await this.getUser(userId);
await this.accessService.wipeUserPermissions(userId); await this.accessService.wipeUserPermissions(userId);
await this.sessionService.deleteSessionsForUser(userId); await this.sessionService.deleteSessionsForUser(userId);
await this.store.delete(userId); await this.store.delete(userId);
await this.eventService.storeEvent({ await this.eventService.storeEvent(
type: USER_DELETED, new UserDeletedEvent({
createdBy: this.getCreatedBy(updatedBy), createdBy: this.getCreatedBy(updatedBy),
preData: this.mapUserToData(user), deletedUser: user,
}); }),
);
} }
async loginUser(usernameOrEmail: string, password: string): Promise<IUser> { async loginUser(usernameOrEmail: string, password: string): Promise<IUser> {
@ -479,5 +473,4 @@ class UserService {
} }
} }
module.exports = UserService;
export default UserService; export default UserService;

View File

@ -1,7 +1,7 @@
import { extractUsernameFromUser } from '../util'; import { extractUsernameFromUser } from '../util';
import { FeatureToggle, IStrategyConfig, ITag, IVariant } from './model'; import { FeatureToggle, IStrategyConfig, ITag, IVariant } from './model';
import { IApiToken } from './models/api-token'; import { IApiToken } from './models/api-token';
import { IUser } from './user'; import { IUser, IUserWithRootRole } from './user';
export const APPLICATION_CREATED = 'application-created' as const; export const APPLICATION_CREATED = 'application-created' as const;
@ -1096,3 +1096,52 @@ export class PotentiallyStaleOnEvent extends BaseEvent {
this.project = eventData.project; this.project = eventData.project;
} }
} }
export class UserCreatedEvent extends BaseEvent {
readonly data: IUserWithRootRole;
constructor(eventData: {
createdBy: string | IUser;
userCreated: IUserWithRootRole;
}) {
super(USER_CREATED, eventData.createdBy);
this.data = mapUserToData(eventData.userCreated);
}
}
export class UserUpdatedEvent extends BaseEvent {
readonly data: IUserWithRootRole;
readonly preData: IUserWithRootRole;
constructor(eventData: {
createdBy: string | IUser;
preUser: IUserWithRootRole;
postUser: IUserWithRootRole;
}) {
super(USER_UPDATED, eventData.createdBy);
this.preData = mapUserToData(eventData.preUser);
this.data = mapUserToData(eventData.postUser);
}
}
export class UserDeletedEvent extends BaseEvent {
readonly preData: IUserWithRootRole;
constructor(eventData: {
createdBy: string | IUser;
deletedUser: IUserWithRootRole;
}) {
super(USER_DELETED, eventData.createdBy);
this.preData = mapUserToData(eventData.deletedUser);
}
}
function mapUserToData(user: IUserWithRootRole): any {
return {
id: user.id,
name: user.name,
username: user.username,
email: user.email,
rootRole: user.rootRole,
};
}

View File

@ -49,7 +49,7 @@ export interface IAccessInfo {
} }
export interface IUserRole { export interface IUserRole {
roleId?: number; roleId: number;
userId: number; userId: number;
addedAt?: Date; addedAt?: Date;
} }
@ -188,6 +188,7 @@ export interface IAccessStore extends Store<IRole, number> {
projectId: string, projectId: string,
userId: number, userId: number,
): Promise<number[]>; ): Promise<number[]>;
getRootRoleForUser(userId: number): Promise<IRole | undefined>;
setProjectRolesForGroup( setProjectRolesForGroup(
projectId: string, projectId: string,
groupId: number, groupId: number,

View File

@ -92,4 +92,8 @@ export default class User implements IUser {
} }
} }
export interface IUserWithRootRole extends IUser {
rootRole: number;
}
module.exports = User; module.exports = User;

View File

@ -37,7 +37,7 @@ test('editor users should only get client or frontend tokens', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole(RoleName.EDITOR); const role = await accessService.getPredefinedRole(RoleName.EDITOR);
const user = await userService.createUser({ const user = await userService.createUser({
email: 'editor@example.com', email: 'editor@example.com',
rootRole: role.id, rootRole: role.id,
@ -85,7 +85,7 @@ test('viewer users should not be allowed to fetch tokens', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole(RoleName.VIEWER); const role = await accessService.getPredefinedRole(RoleName.VIEWER);
const user = await userService.createUser({ const user = await userService.createUser({
email: 'viewer@example.com', email: 'viewer@example.com',
rootRole: role.id, rootRole: role.id,
@ -122,7 +122,7 @@ test('Only token-admins should be allowed to create token', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole(RoleName.EDITOR); const role = await accessService.getPredefinedRole(RoleName.EDITOR);
req.user = await userService.createUser({ req.user = await userService.createUser({
email: 'editor2@example.com', email: 'editor2@example.com',
rootRole: role.id, rootRole: role.id,
@ -150,7 +150,7 @@ test('Token-admin should be allowed to create token', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole(RoleName.ADMIN); const role = await accessService.getPredefinedRole(RoleName.ADMIN);
req.user = await userService.createUser({ req.user = await userService.createUser({
email: 'admin@example.com', email: 'admin@example.com',
rootRole: role.id, rootRole: role.id,
@ -185,7 +185,9 @@ test('A role with only CREATE_PROJECT_API_TOKEN can create project tokens', asyn
}: { userService: UserService; accessService: AccessService }, }: { userService: UserService; accessService: AccessService },
) => { ) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = (await accessService.getRootRole(RoleName.VIEWER))!; const role = (await accessService.getPredefinedRole(
RoleName.VIEWER,
))!;
const user = await userService.createUser({ const user = await userService.createUser({
email: 'powerpuffgirls_viewer@example.com', email: 'powerpuffgirls_viewer@example.com',
rootRole: role.id, rootRole: role.id,
@ -230,7 +232,7 @@ describe('Fine grained API token permissions', () => {
test('should be allowed to create client tokens', async () => { test('should be allowed to create client tokens', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const builtInRole = await accessService.getRootRole( const builtInRole = await accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -275,7 +277,7 @@ describe('Fine grained API token permissions', () => {
test('should NOT be allowed to create frontend tokens', async () => { test('should NOT be allowed to create frontend tokens', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -319,7 +321,7 @@ describe('Fine grained API token permissions', () => {
test('should NOT be allowed to create ADMIN tokens', async () => { test('should NOT be allowed to create ADMIN tokens', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -365,7 +367,7 @@ describe('Fine grained API token permissions', () => {
test('READ_FRONTEND_API_TOKEN should be able to see FRONTEND tokens', async () => { test('READ_FRONTEND_API_TOKEN should be able to see FRONTEND tokens', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -429,7 +431,7 @@ describe('Fine grained API token permissions', () => {
test('READ_CLIENT_API_TOKEN should be able to see CLIENT tokens', async () => { test('READ_CLIENT_API_TOKEN should be able to see CLIENT tokens', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -488,7 +490,7 @@ describe('Fine grained API token permissions', () => {
test('Admin users should be able to see all tokens', async () => { test('Admin users should be able to see all tokens', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.ADMIN, RoleName.ADMIN,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -531,7 +533,7 @@ describe('Fine grained API token permissions', () => {
test('Editor users should be able to see all tokens except ADMIN tokens', async () => { test('Editor users should be able to see all tokens except ADMIN tokens', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.EDITOR, RoleName.EDITOR,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -585,7 +587,7 @@ describe('Fine grained API token permissions', () => {
{ userService, accessService }, { userService, accessService },
) => { ) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -634,7 +636,7 @@ describe('Fine grained API token permissions', () => {
{ userService, accessService }, { userService, accessService },
) => { ) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -684,7 +686,7 @@ describe('Fine grained API token permissions', () => {
{ userService, accessService }, { userService, accessService },
) => { ) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -737,7 +739,7 @@ describe('Fine grained API token permissions', () => {
{ userService, accessService }, { userService, accessService },
) => { ) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -786,7 +788,7 @@ describe('Fine grained API token permissions', () => {
{ userService, accessService }, { userService, accessService },
) => { ) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const user = await userService.createUser({ const user = await userService.createUser({
@ -835,7 +837,7 @@ describe('Fine grained API token permissions', () => {
{ userService, accessService }, { userService, accessService },
) => { ) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole( const role = await accessService.getPredefinedRole(
RoleName.VIEWER, RoleName.VIEWER,
); );
const user = await userService.createUser({ const user = await userService.createUser({

View File

@ -35,7 +35,7 @@ test('admin users should be able to create a token', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole(RoleName.ADMIN); const role = await accessService.getPredefinedRole(RoleName.ADMIN);
const user = await userService.createUser({ const user = await userService.createUser({
email: 'admin@example.com', email: 'admin@example.com',
rootRole: role.id, rootRole: role.id,
@ -69,7 +69,7 @@ test('admin users should be able to create a token', async () => {
test('no permission to validate a token', async () => { test('no permission to validate a token', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const admin = await accessService.getRootRole(RoleName.ADMIN); const admin = await accessService.getPredefinedRole(RoleName.ADMIN);
await userService.createUser({ await userService.createUser({
email: 'admin@example.com', email: 'admin@example.com',
username: 'admin@example.com', username: 'admin@example.com',
@ -97,7 +97,7 @@ test('no permission to validate a token', async () => {
test('should return 400 if token can not be validate', async () => { test('should return 400 if token can not be validate', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const admin = await accessService.getRootRole(RoleName.ADMIN); const admin = await accessService.getPredefinedRole(RoleName.ADMIN);
await userService.createUser({ await userService.createUser({
email: 'admin@example.com', email: 'admin@example.com',
username: 'admin@example.com', username: 'admin@example.com',
@ -119,7 +119,7 @@ test('users can signup with invite-link', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const admin = await accessService.getRootRole(RoleName.ADMIN); const admin = await accessService.getPredefinedRole(RoleName.ADMIN);
await userService.createUser({ await userService.createUser({
email: 'admin@example.com', email: 'admin@example.com',
username: 'admin@example.com', username: 'admin@example.com',
@ -164,7 +164,7 @@ test('can get a token with users', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole(RoleName.ADMIN); const role = await accessService.getPredefinedRole(RoleName.ADMIN);
const user = await userService.createUser({ const user = await userService.createUser({
email: 'admin@example.com', email: 'admin@example.com',
rootRole: role.id, rootRole: role.id,
@ -209,7 +209,7 @@ test('can get a token with users', async () => {
test('should not be able to set expiry further than 1 month', async () => { test('should not be able to set expiry further than 1 month', async () => {
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole(RoleName.ADMIN); const role = await accessService.getPredefinedRole(RoleName.ADMIN);
const user = await userService.createUser({ const user = await userService.createUser({
email: 'admin@example.com', email: 'admin@example.com',
rootRole: role.id, rootRole: role.id,

View File

@ -80,13 +80,13 @@ beforeAll(async () => {
settingService, settingService,
}); });
resetTokenService = new ResetTokenService(stores, config); resetTokenService = new ResetTokenService(stores, config);
const adminRole = (await accessService.getRootRole(RoleName.ADMIN))!; const adminRole = (await accessService.getPredefinedRole(RoleName.ADMIN))!;
adminUser = await userService.createUser({ adminUser = await userService.createUser({
username: 'admin@test.com', username: 'admin@test.com',
rootRole: adminRole.id, rootRole: adminRole.id,
})!; })!;
const userRole = (await accessService.getRootRole(RoleName.EDITOR))!; const userRole = (await accessService.getPredefinedRole(RoleName.EDITOR))!;
user = await userService.createUser({ user = await userService.createUser({
username: 'test@test.com', username: 'test@test.com',
email: 'test@test.com', email: 'test@test.com',

View File

@ -56,7 +56,7 @@ beforeAll(async () => {
sessionService, sessionService,
settingService, settingService,
}); });
const adminRole = await accessService.getRootRole(RoleName.ADMIN); const adminRole = await accessService.getPredefinedRole(RoleName.ADMIN);
adminUser = await userService.createUser({ adminUser = await userService.createUser({
username: 'admin@test.com', username: 'admin@test.com',
email: 'admin@test.com', email: 'admin@test.com',

View File

@ -7,7 +7,7 @@ let stores;
const preHook = (app, config, { userService, accessService }) => { const preHook = (app, config, { userService, accessService }) => {
app.use('/api/admin/', async (req, res, next) => { app.use('/api/admin/', async (req, res, next) => {
const role = await accessService.getRootRole(RoleName.EDITOR); const role = await accessService.getPredefinedRole(RoleName.EDITOR);
req.user = await userService.createUser({ req.user = await userService.createUser({
email: 'editor2@example.com', email: 'editor2@example.com',
rootRole: role.id, rootRole: role.id,

View File

@ -18,6 +18,7 @@ import { randomId } from '../../../lib/util/random-id';
import { BadDataError } from '../../../lib/error'; import { BadDataError } from '../../../lib/error';
import PasswordMismatch from '../../../lib/error/password-mismatch'; import PasswordMismatch from '../../../lib/error/password-mismatch';
import { EventService } from '../../../lib/services'; import { EventService } from '../../../lib/services';
import { USER_CREATED, USER_DELETED, USER_UPDATED } from '../../../lib/types';
let db; let db;
let stores; let stores;
@ -27,12 +28,13 @@ let adminRole: IRole;
let viewerRole: IRole; let viewerRole: IRole;
let sessionService: SessionService; let sessionService: SessionService;
let settingService: SettingService; let settingService: SettingService;
let eventService: EventService;
beforeAll(async () => { beforeAll(async () => {
db = await dbInit('user_service_serial', getLogger); db = await dbInit('user_service_serial', getLogger);
stores = db.stores; stores = db.stores;
const config = createTestConfig(); const config = createTestConfig();
const eventService = new EventService(stores, config); eventService = new EventService(stores, config);
const groupService = new GroupService(stores, config, eventService); const groupService = new GroupService(stores, config, eventService);
const accessService = new AccessService( const accessService = new AccessService(
stores, stores,
@ -124,6 +126,49 @@ test('should create user with password', async () => {
expect(user.username).toBe('test'); expect(user.username).toBe('test');
}); });
test('should create user with rootRole in audit-log', async () => {
const user = await userService.createUser({
username: 'test',
rootRole: viewerRole.id,
});
const { events } = await eventService.getEvents();
expect(events[0].type).toBe(USER_CREATED);
expect(events[0].data.id).toBe(user.id);
expect(events[0].data.username).toBe('test');
expect(events[0].data.rootRole).toBe(viewerRole.id);
});
test('should update user with rootRole in audit-log', async () => {
const user = await userService.createUser({
username: 'test',
rootRole: viewerRole.id,
});
await userService.updateUser({ id: user.id, rootRole: adminRole.id });
const { events } = await eventService.getEvents();
expect(events[0].type).toBe(USER_UPDATED);
expect(events[0].data.id).toBe(user.id);
expect(events[0].data.username).toBe('test');
expect(events[0].data.rootRole).toBe(adminRole.id);
});
test('should remove user with rootRole in audit-log', async () => {
const user = await userService.createUser({
username: 'test',
rootRole: viewerRole.id,
});
await userService.deleteUser(user.id);
const { events } = await eventService.getEvents();
expect(events[0].type).toBe(USER_DELETED);
expect(events[0].preData.id).toBe(user.id);
expect(events[0].preData.username).toBe('test');
expect(events[0].preData.rootRole).toBe(viewerRole.id);
});
test('should not be able to login with deleted user', async () => { test('should not be able to login with deleted user', async () => {
const user = await userService.createUser({ const user = await userService.createUser({
username: 'deleted_user', username: 'deleted_user',

View File

@ -7,7 +7,7 @@ import {
import User from '../../lib/types/user'; import User from '../../lib/types/user';
import noLoggerProvider from './no-logger'; import noLoggerProvider from './no-logger';
import { IRole } from '../../lib/types/stores/access-store'; import { IRole } from '../../lib/types/stores/access-store';
import { IAvailablePermissions } from '../../lib/types/model'; import { IAvailablePermissions, RoleName } from '../../lib/types/model';
class AccessServiceMock extends AccessService { class AccessServiceMock extends AccessService {
constructor() { constructor() {
@ -69,7 +69,11 @@ class AccessServiceMock extends AccessService {
} }
getRolesForUser(userId: number): Promise<IRole[]> { getRolesForUser(userId: number): Promise<IRole[]> {
throw new Error('Method not implemented.'); return Promise.resolve([{ id: 1, name: 'Admin', type: 'root' }]);
}
getUserRootRoles(userId: number): Promise<IRole[]> {
return Promise.resolve([{ id: 1, name: 'Admin', type: 'root' }]);
} }
getUsersForRole(roleId: any): Promise<User[]> { getUsersForRole(roleId: any): Promise<User[]> {
@ -87,6 +91,14 @@ class AccessServiceMock extends AccessService {
removeDefaultProjectRoles(owner: User, projectId: string): Promise<void> { removeDefaultProjectRoles(owner: User, projectId: string): Promise<void> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
getRootRole(roleName: RoleName): Promise<IRole> {
return Promise.resolve({ id: 1, name: roleName, type: 'root' });
}
getRootRoleForUser(userId: number): Promise<IRole> {
return Promise.resolve({ id: 1, name: RoleName.VIEWER, type: 'root' });
}
} }
export default AccessServiceMock; export default AccessServiceMock;

View File

@ -9,8 +9,13 @@ import {
IUserRole, IUserRole,
IUserWithProjectRoles, IUserWithProjectRoles,
} from '../../lib/types/stores/access-store'; } from '../../lib/types/stores/access-store';
import { IPermission } from 'lib/types/model'; import { IPermission } from '../../lib/types/model';
import { IRoleStore, IUserAccessOverview } from 'lib/types'; import {
IRoleStore,
IUserAccessOverview,
RoleName,
RoleType,
} from '../../lib/types';
import FakeRoleStore from './fake-role-store'; import FakeRoleStore from './fake-role-store';
import { PermissionRef } from 'lib/services/access-service'; import { PermissionRef } from 'lib/services/access-service';
import { P } from 'ts-toolbelt/out/Object/_api'; import { P } from 'ts-toolbelt/out/Object/_api';
@ -302,6 +307,18 @@ class AccessStoreMock implements IAccessStore {
getUserAccessOverview(): Promise<IUserAccessOverview[]> { getUserAccessOverview(): Promise<IUserAccessOverview[]> {
throw new Error('Method not implemented.'); throw new Error('Method not implemented.');
} }
getRootRoleForUser(userId: number): Promise<IRole> {
const roleId = this.userToRoleMap.get(userId);
if (roleId !== undefined) {
return Promise.resolve(this.fakeRolesStore.get(roleId));
} else {
return Promise.resolve({
id: -1,
name: RoleName.VIEWER,
type: RoleType.ROOT,
});
}
}
} }
module.exports = AccessStoreMock; module.exports = AccessStoreMock;