mirror of
https://github.com/Unleash/unleash.git
synced 2025-04-24 01:18:01 +02:00
refactor: favor permission name over id (#5409)
https://linear.app/unleash/issue/2-1664/create-db-migration-that-favors-the-name-column-over-id-for Similar to https://github.com/Unleash/unleash/pull/5398, but non-breaking (semver). This keeps the permissions `id` column intact, however favors the permission name whenever possible.
This commit is contained in:
parent
b021e7cf85
commit
023db4e2c9
@ -114,7 +114,7 @@ export const RoleDescription = ({
|
|||||||
</StyledDescriptionHeader>
|
</StyledDescriptionHeader>
|
||||||
<StyledPermissionsList>
|
<StyledPermissionsList>
|
||||||
{permissions.map((permission) => (
|
{permissions.map((permission) => (
|
||||||
<li key={permission.id}>
|
<li key={permission.name}>
|
||||||
{permission.displayName}
|
{permission.displayName}
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
|
@ -10,7 +10,6 @@ export type PermissionType =
|
|||||||
| typeof ENVIRONMENT_PERMISSION_TYPE;
|
| typeof ENVIRONMENT_PERMISSION_TYPE;
|
||||||
|
|
||||||
export interface IPermission {
|
export interface IPermission {
|
||||||
id: number;
|
|
||||||
name: string;
|
name: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
type: PermissionType;
|
type: PermissionType;
|
||||||
|
@ -13,8 +13,8 @@ import cloneDeep from 'lodash.clonedeep';
|
|||||||
|
|
||||||
export const getRoleKey = (permission: IPermission): string => {
|
export const getRoleKey = (permission: IPermission): string => {
|
||||||
return permission.environment
|
return permission.environment
|
||||||
? `${permission.id}-${permission.environment}`
|
? `${permission.name}-${permission.environment}`
|
||||||
: `${permission.id}`;
|
: `${permission.name}`;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const permissionsToCheckedPermissions = (
|
export const permissionsToCheckedPermissions = (
|
||||||
|
@ -39,15 +39,14 @@ test('resolvePermissions returns empty list if empty list', async () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
args([{ id: 1 }]),
|
args([{ name: 'CREATE_CONTEXT_FIELD' }]),
|
||||||
args([{ id: 4, environment: 'development' }]),
|
args([{ name: 'CREATE_FEATURE', environment: 'development' }]),
|
||||||
args([{ id: 4, name: 'should keep the id' }]),
|
|
||||||
args([
|
args([
|
||||||
{ id: 1, environment: 'development' },
|
{ name: 'CREATE_CONTEXT_FIELD' },
|
||||||
{ id: 2, name: 'ignore this name' },
|
{ name: 'CREATE_FEATURE', environment: 'development' },
|
||||||
]),
|
]),
|
||||||
])(
|
])(
|
||||||
'resolvePermissions with permission ids (%o) returns the list unmodified',
|
'resolvePermissions with permission names (%o) will return the list unmodified',
|
||||||
async (permissions) => {
|
async (permissions) => {
|
||||||
const access = db.stores.accessStore as AccessStore;
|
const access = db.stores.accessStore as AccessStore;
|
||||||
const result = await access.resolvePermissions(permissions);
|
const result = await access.resolvePermissions(permissions);
|
||||||
@ -56,26 +55,23 @@ test.each([
|
|||||||
);
|
);
|
||||||
|
|
||||||
test.each([
|
test.each([
|
||||||
|
args([{ id: 1 }], [{ id: 1, name: 'ADMIN' }]),
|
||||||
args(
|
args(
|
||||||
[{ name: 'CREATE_CONTEXT_FIELD' }],
|
[{ id: 4, environment: 'development' }],
|
||||||
[{ id: 18, name: 'CREATE_CONTEXT_FIELD' }],
|
[{ id: 4, name: 'CREATE_ADDON', environment: 'development' }],
|
||||||
),
|
|
||||||
args(
|
|
||||||
[{ name: 'CREATE_FEATURE', environment: 'development' }],
|
|
||||||
[{ id: 2, name: 'CREATE_FEATURE', environment: 'development' }],
|
|
||||||
),
|
),
|
||||||
args(
|
args(
|
||||||
[
|
[
|
||||||
{ name: 'CREATE_CONTEXT_FIELD' },
|
{ id: 1, environment: 'development' },
|
||||||
{ name: 'CREATE_FEATURE', environment: 'development' },
|
{ id: 2, name: 'ignore this name' },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ id: 18, name: 'CREATE_CONTEXT_FIELD' },
|
{ id: 1, name: 'ADMIN', environment: 'development' },
|
||||||
{ id: 2, name: 'CREATE_FEATURE', environment: 'development' },
|
{ id: 2, name: 'ignore this name' },
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
])(
|
])(
|
||||||
'resolvePermissions with permission names (%o) will inject the ids',
|
'resolvePermissions with only permission ids (%o) will resolve to named permissions without an id',
|
||||||
async (permissions, expected) => {
|
async (permissions, expected) => {
|
||||||
const access = db.stores.accessStore as AccessStore;
|
const access = db.stores.accessStore as AccessStore;
|
||||||
const result = await access.resolvePermissions(permissions);
|
const result = await access.resolvePermissions(permissions);
|
||||||
@ -93,15 +89,15 @@ test.each([
|
|||||||
{ name: 'UPDATE_FEATURE', environment: 'development' },
|
{ name: 'UPDATE_FEATURE', environment: 'development' },
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
{ id: 18, name: 'CREATE_CONTEXT_FIELD' },
|
{ name: 'CREATE_CONTEXT_FIELD' },
|
||||||
{ id: 3 },
|
{ id: 3, name: 'CREATE_STRATEGY' },
|
||||||
{ id: 2, name: 'CREATE_FEATURE', environment: 'development' },
|
{ name: 'CREATE_FEATURE', environment: 'development' },
|
||||||
{ id: 15, environment: 'development' },
|
{ id: 15, name: 'UPDATE_STRATEGY', environment: 'development' },
|
||||||
{ id: 7, name: 'UPDATE_FEATURE', environment: 'development' },
|
{ name: 'UPDATE_FEATURE', environment: 'development' },
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
])(
|
])(
|
||||||
'resolvePermissions mixed ids and names (%o) will inject the ids where they are missing',
|
'resolvePermissions mixed ids and names (%o) will inject the names where they are missing',
|
||||||
async (permissions, expected) => {
|
async (permissions, expected) => {
|
||||||
const access = db.stores.accessStore as AccessStore;
|
const access = db.stores.accessStore as AccessStore;
|
||||||
const result = await access.resolvePermissions(permissions);
|
const result = await access.resolvePermissions(permissions);
|
||||||
|
@ -51,11 +51,7 @@ interface IPermissionRow {
|
|||||||
role_id: number;
|
role_id: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
type ResolvedPermission = {
|
type NameAndIdPermission = NamePermissionRef & IdPermissionRef;
|
||||||
id: number;
|
|
||||||
name?: string;
|
|
||||||
environment?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class AccessStore implements IAccessStore {
|
export class AccessStore implements IAccessStore {
|
||||||
private logger: Logger;
|
private logger: Logger;
|
||||||
@ -74,70 +70,71 @@ export class AccessStore implements IAccessStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private permissionHasId = (permission: PermissionRef): boolean => {
|
private permissionHasName = (permission: PermissionRef): boolean => {
|
||||||
return (permission as IdPermissionRef).id !== undefined;
|
return (permission as NamePermissionRef).name !== undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
private permissionNamesToIds = async (
|
private permissionIdsToNames = async (
|
||||||
permissions: NamePermissionRef[],
|
permissions: IdPermissionRef[],
|
||||||
): Promise<ResolvedPermission[]> => {
|
): Promise<NameAndIdPermission[]> => {
|
||||||
const permissionNames = (permissions ?? [])
|
const permissionIds = (permissions ?? [])
|
||||||
.filter((p) => p.name !== undefined)
|
.filter((p) => p.id !== undefined)
|
||||||
.map((p) => p.name);
|
.map((p) => p.id);
|
||||||
|
|
||||||
if (permissionNames.length === 0) {
|
if (permissionIds.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const stopTimer = this.timer('permissionNamesToIds');
|
const stopTimer = this.timer('permissionIdsToNames');
|
||||||
|
|
||||||
const rows = await this.db
|
const rows = await this.db
|
||||||
.select('id', 'permission')
|
.select('id', 'permission')
|
||||||
.from(T.PERMISSIONS)
|
.from(T.PERMISSIONS)
|
||||||
.whereIn('permission', permissionNames);
|
.whereIn('id', permissionIds);
|
||||||
|
|
||||||
const rowByPermissionName = rows.reduce((acc, row) => {
|
const rowByPermissionId = rows.reduce((acc, row) => {
|
||||||
acc[row.permission] = row;
|
acc[row.id] = row;
|
||||||
return acc;
|
return acc;
|
||||||
}, {} as Map<string, IPermissionRow>);
|
}, {} as Map<string, IPermissionRow>);
|
||||||
|
|
||||||
const permissionsWithIds = permissions.map((permission) => ({
|
const permissionsWithNames = permissions.map((permission) => ({
|
||||||
id: rowByPermissionName[permission.name].id,
|
name: rowByPermissionId[permission.id].permission,
|
||||||
...permission,
|
...permission,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
stopTimer();
|
stopTimer();
|
||||||
return permissionsWithIds;
|
return permissionsWithNames;
|
||||||
};
|
};
|
||||||
|
|
||||||
resolvePermissions = async (
|
resolvePermissions = async (
|
||||||
permissions: PermissionRef[],
|
permissions: PermissionRef[],
|
||||||
): Promise<ResolvedPermission[]> => {
|
): Promise<NamePermissionRef[]> => {
|
||||||
if (permissions === undefined || permissions.length === 0) {
|
if (permissions === undefined || permissions.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
// permissions without ids (just names)
|
// permissions without names (just ids)
|
||||||
const permissionsWithoutIds = permissions.filter(
|
const permissionsWithoutNames = permissions.filter(
|
||||||
(p) => !this.permissionHasId(p),
|
(p) => !this.permissionHasName(p),
|
||||||
) as NamePermissionRef[];
|
) as IdPermissionRef[];
|
||||||
const idPermissionsFromNamed = await this.permissionNamesToIds(
|
|
||||||
permissionsWithoutIds,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (permissionsWithoutIds.length === permissions.length) {
|
if (permissionsWithoutNames.length === permissions.length) {
|
||||||
// all named permissions without ids
|
// all permissions without names
|
||||||
return idPermissionsFromNamed;
|
return await this.permissionIdsToNames(permissionsWithoutNames);
|
||||||
} else if (permissionsWithoutIds.length === 0) {
|
} else if (permissionsWithoutNames.length === 0) {
|
||||||
// all permissions have ids
|
// all permissions have names
|
||||||
return permissions as ResolvedPermission[];
|
return permissions as NamePermissionRef[];
|
||||||
}
|
}
|
||||||
// some permissions have ids, some don't (should not happen!)
|
|
||||||
|
// some permissions have names, some don't (should not happen!)
|
||||||
|
const namedPermissionsFromIds = await this.permissionIdsToNames(
|
||||||
|
permissionsWithoutNames,
|
||||||
|
);
|
||||||
return permissions.map((permission) => {
|
return permissions.map((permission) => {
|
||||||
if (this.permissionHasId(permission)) {
|
if (this.permissionHasName(permission)) {
|
||||||
return permission as ResolvedPermission;
|
return permission as NamePermissionRef;
|
||||||
} else {
|
} else {
|
||||||
return idPermissionsFromNamed.find(
|
return namedPermissionsFromIds.find(
|
||||||
(p) => p.name === (permission as NamePermissionRef).name,
|
(p) => p.id === (permission as IdPermissionRef).id,
|
||||||
)!;
|
)!;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -204,20 +201,20 @@ export class AccessStore implements IAccessStore {
|
|||||||
let userPermissionQuery = this.db
|
let userPermissionQuery = this.db
|
||||||
.select(
|
.select(
|
||||||
'project',
|
'project',
|
||||||
'permission',
|
'rp.permission',
|
||||||
'environment',
|
'environment',
|
||||||
'type',
|
'type',
|
||||||
'ur.role_id',
|
'ur.role_id',
|
||||||
)
|
)
|
||||||
.from<IPermissionRow>(`${T.ROLE_PERMISSION} AS rp`)
|
.from<IPermissionRow>(`${T.ROLE_PERMISSION} AS rp`)
|
||||||
.join(`${T.ROLE_USER} AS ur`, 'ur.role_id', 'rp.role_id')
|
.join(`${T.ROLE_USER} AS ur`, 'ur.role_id', 'rp.role_id')
|
||||||
.join(`${T.PERMISSIONS} AS p`, 'p.id', 'rp.permission_id')
|
.join(`${T.PERMISSIONS} AS p`, 'p.permission', 'rp.permission')
|
||||||
.where('ur.user_id', '=', userId);
|
.where('ur.user_id', '=', userId);
|
||||||
|
|
||||||
userPermissionQuery = userPermissionQuery.union((db) => {
|
userPermissionQuery = userPermissionQuery.union((db) => {
|
||||||
db.select(
|
db.select(
|
||||||
'project',
|
'project',
|
||||||
'permission',
|
'rp.permission',
|
||||||
'environment',
|
'environment',
|
||||||
'p.type',
|
'p.type',
|
||||||
'gr.role_id',
|
'gr.role_id',
|
||||||
@ -226,14 +223,14 @@ export class AccessStore implements IAccessStore {
|
|||||||
.join(`${T.GROUPS} AS g`, 'g.id', 'gu.group_id')
|
.join(`${T.GROUPS} AS g`, 'g.id', 'gu.group_id')
|
||||||
.join(`${T.GROUP_ROLE} AS gr`, 'gu.group_id', 'gr.group_id')
|
.join(`${T.GROUP_ROLE} AS gr`, 'gu.group_id', 'gr.group_id')
|
||||||
.join(`${T.ROLE_PERMISSION} AS rp`, 'rp.role_id', 'gr.role_id')
|
.join(`${T.ROLE_PERMISSION} AS rp`, 'rp.role_id', 'gr.role_id')
|
||||||
.join(`${T.PERMISSIONS} AS p`, 'p.id', 'rp.permission_id')
|
.join(`${T.PERMISSIONS} AS p`, 'p.permission', 'rp.permission')
|
||||||
.andWhere('gu.user_id', '=', userId);
|
.andWhere('gu.user_id', '=', userId);
|
||||||
});
|
});
|
||||||
|
|
||||||
userPermissionQuery = userPermissionQuery.union((db) => {
|
userPermissionQuery = userPermissionQuery.union((db) => {
|
||||||
db.select(
|
db.select(
|
||||||
this.db.raw("'default' as project"),
|
this.db.raw("'default' as project"),
|
||||||
'permission',
|
'rp.permission',
|
||||||
'environment',
|
'environment',
|
||||||
'p.type',
|
'p.type',
|
||||||
'g.root_role_id as role_id',
|
'g.root_role_id as role_id',
|
||||||
@ -245,7 +242,7 @@ export class AccessStore implements IAccessStore {
|
|||||||
'rp.role_id',
|
'rp.role_id',
|
||||||
'g.root_role_id',
|
'g.root_role_id',
|
||||||
)
|
)
|
||||||
.join(`${T.PERMISSIONS} as p`, 'p.id', 'rp.permission_id')
|
.join(`${T.PERMISSIONS} as p`, 'p.permission', 'rp.permission')
|
||||||
.whereNotNull('g.root_role_id')
|
.whereNotNull('g.root_role_id')
|
||||||
.andWhere('gu.user_id', '=', userId);
|
.andWhere('gu.user_id', '=', userId);
|
||||||
});
|
});
|
||||||
@ -280,13 +277,13 @@ export class AccessStore implements IAccessStore {
|
|||||||
const rows = await this.db
|
const rows = await this.db
|
||||||
.select(
|
.select(
|
||||||
'p.id',
|
'p.id',
|
||||||
'p.permission',
|
'rp.permission',
|
||||||
'rp.environment',
|
'rp.environment',
|
||||||
'p.display_name',
|
'p.display_name',
|
||||||
'p.type',
|
'p.type',
|
||||||
)
|
)
|
||||||
.from<IPermission>(`${T.ROLE_PERMISSION} as rp`)
|
.from<IPermission>(`${T.ROLE_PERMISSION} as rp`)
|
||||||
.join(`${T.PERMISSIONS} as p`, 'p.id', 'rp.permission_id')
|
.join(`${T.PERMISSIONS} as p`, 'p.permission', 'rp.permission')
|
||||||
.where('rp.role_id', '=', roleId);
|
.where('rp.role_id', '=', roleId);
|
||||||
stopTimer();
|
stopTimer();
|
||||||
return rows.map((permission) => {
|
return rows.map((permission) => {
|
||||||
@ -304,12 +301,12 @@ export class AccessStore implements IAccessStore {
|
|||||||
role_id: number,
|
role_id: number,
|
||||||
permissions: PermissionRef[],
|
permissions: PermissionRef[],
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const resolvedPermission = await this.resolvePermissions(permissions);
|
const resolvedPermissions = await this.resolvePermissions(permissions);
|
||||||
|
|
||||||
const rows = resolvedPermission.map((permission) => {
|
const rows = resolvedPermissions.map((permission) => {
|
||||||
return {
|
return {
|
||||||
role_id,
|
role_id,
|
||||||
permission_id: permission.id,
|
permission: permission.name,
|
||||||
environment: permission.environment,
|
environment: permission.environment,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -808,14 +805,14 @@ export class AccessStore implements IAccessStore {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
// no need to pass down the environment in this particular case because it'll be overriden
|
// no need to pass down the environment in this particular case because it'll be overriden
|
||||||
const permissionsWithIds = await this.resolvePermissions(
|
const permissionsWithNames = await this.resolvePermissions(
|
||||||
permissionsAsRefs,
|
permissionsAsRefs,
|
||||||
);
|
);
|
||||||
|
|
||||||
const newRoles = permissionsWithIds.map((p) => ({
|
const newRoles = permissionsWithNames.map((p) => ({
|
||||||
role_id,
|
role_id,
|
||||||
environment,
|
environment,
|
||||||
permission_id: p.id,
|
permission: p.name,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return this.db.batchInsert(T.ROLE_PERMISSION, newRoles);
|
return this.db.batchInsert(T.ROLE_PERMISSION, newRoles);
|
||||||
@ -826,17 +823,10 @@ export class AccessStore implements IAccessStore {
|
|||||||
permission: string,
|
permission: string,
|
||||||
environment?: string,
|
environment?: string,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const rows = await this.db
|
|
||||||
.select('id as permissionId')
|
|
||||||
.from<number>(T.PERMISSIONS)
|
|
||||||
.where('permission', permission);
|
|
||||||
|
|
||||||
const permissionId = rows[0].permissionId;
|
|
||||||
|
|
||||||
return this.db(T.ROLE_PERMISSION)
|
return this.db(T.ROLE_PERMISSION)
|
||||||
.where({
|
.where({
|
||||||
role_id,
|
role_id,
|
||||||
permission_id: permissionId,
|
permission,
|
||||||
environment,
|
environment,
|
||||||
})
|
})
|
||||||
.delete();
|
.delete();
|
||||||
@ -856,8 +846,8 @@ export class AccessStore implements IAccessStore {
|
|||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
return this.db.raw(
|
return this.db.raw(
|
||||||
`insert into role_permission
|
`insert into role_permission
|
||||||
(role_id, permission_id, environment)
|
(role_id, permission, environment)
|
||||||
(select role_id, permission_id, ?
|
(select role_id, permission, ?
|
||||||
from ${T.ROLE_PERMISSION} where environment = ?)`,
|
from ${T.ROLE_PERMISSION} where environment = ?)`,
|
||||||
[destinationEnvironment, sourceEnvironment],
|
[destinationEnvironment, sourceEnvironment],
|
||||||
);
|
);
|
||||||
|
245
src/migrations/20231123155649-favor-permission-name-over-id.js
Normal file
245
src/migrations/20231123155649-favor-permission-name-over-id.js
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
exports.up = function (db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
-- STEP 1: Ensure 'permission' column contains unique values
|
||||||
|
ALTER TABLE permissions
|
||||||
|
ADD CONSTRAINT permission_unique UNIQUE (permission);
|
||||||
|
|
||||||
|
-- STEP 2: Add a new column for the new foreign key
|
||||||
|
ALTER TABLE role_permission
|
||||||
|
ADD COLUMN permission text;
|
||||||
|
|
||||||
|
-- STEP 3: Populate the new column with corresponding values from 'permissions'
|
||||||
|
UPDATE role_permission rp
|
||||||
|
SET permission = p.permission
|
||||||
|
FROM permissions p
|
||||||
|
WHERE rp.permission_id = p.id;
|
||||||
|
|
||||||
|
-- STEP 4: Drop the 'id' primary key constraint
|
||||||
|
ALTER TABLE permissions
|
||||||
|
DROP CONSTRAINT permissions_pkey;
|
||||||
|
|
||||||
|
-- STEP 5: Add the 'permission' primary key constraint
|
||||||
|
ALTER TABLE permissions
|
||||||
|
ADD PRIMARY KEY (permission);
|
||||||
|
|
||||||
|
-- STEP 6: Add the new foreign key constraint
|
||||||
|
ALTER TABLE role_permission
|
||||||
|
ADD CONSTRAINT fk_role_permission_permission FOREIGN KEY (permission) REFERENCES permissions(permission) ON DELETE CASCADE;
|
||||||
|
|
||||||
|
-- STEP 7: Update the assign_unleash_permission_to_role function to use permission name instead of id
|
||||||
|
CREATE OR REPLACE FUNCTION assign_unleash_permission_to_role(permission_name text, role_name text) returns void as
|
||||||
|
$$
|
||||||
|
declare
|
||||||
|
var_role_id int;
|
||||||
|
var_permission text;
|
||||||
|
BEGIN
|
||||||
|
var_role_id := (SELECT r.id FROM roles r WHERE r.name = role_name);
|
||||||
|
var_permission := (SELECT p.permission FROM permissions p WHERE p.permission = permission_name);
|
||||||
|
|
||||||
|
IF NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM role_permission AS rp
|
||||||
|
WHERE rp.role_id = var_role_id AND rp.permission = var_permission
|
||||||
|
) THEN
|
||||||
|
INSERT INTO role_permission(role_id, permission) VALUES (var_role_id, var_permission);
|
||||||
|
END IF;
|
||||||
|
END
|
||||||
|
$$ language plpgsql;
|
||||||
|
|
||||||
|
-- STEP 8: Create a new assign_unleash_permission_to_role_for_all_environments function
|
||||||
|
CREATE OR REPLACE FUNCTION assign_unleash_permission_to_role_for_all_environments(permission_name text, role_name text) returns void as
|
||||||
|
$$
|
||||||
|
declare
|
||||||
|
var_role_id int;
|
||||||
|
var_permission text;
|
||||||
|
BEGIN
|
||||||
|
var_role_id := (SELECT id FROM roles r WHERE r.name = role_name);
|
||||||
|
var_permission := (SELECT p.permission FROM permissions p WHERE p.permission = permission_name);
|
||||||
|
|
||||||
|
INSERT INTO role_permission (role_id, permission, environment)
|
||||||
|
SELECT var_role_id, var_permission, e.name
|
||||||
|
FROM environments e
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM role_permission rp
|
||||||
|
WHERE rp.role_id = var_role_id
|
||||||
|
AND rp.permission = var_permission
|
||||||
|
AND rp.environment = e.name
|
||||||
|
);
|
||||||
|
END;
|
||||||
|
$$ LANGUAGE plpgsql;
|
||||||
|
|
||||||
|
-- STEP 9: Ensure that all the expected permissions exist
|
||||||
|
INSERT INTO permissions (permission, display_name, type)
|
||||||
|
SELECT * FROM (VALUES
|
||||||
|
('ADMIN', 'Admin', 'root'),
|
||||||
|
('CREATE_FEATURE', 'Create feature toggles', 'project'),
|
||||||
|
('CREATE_STRATEGY', 'Create activation strategies', 'root'),
|
||||||
|
('CREATE_ADDON', 'Create addons', 'root'),
|
||||||
|
('DELETE_ADDON', 'Delete addons', 'root'),
|
||||||
|
('UPDATE_ADDON', 'Update addons', 'root'),
|
||||||
|
('UPDATE_FEATURE', 'Update feature toggles', 'project'),
|
||||||
|
('DELETE_FEATURE', 'Delete feature toggles', 'project'),
|
||||||
|
('UPDATE_APPLICATION', 'Update applications', 'root'),
|
||||||
|
('UPDATE_TAG_TYPE', 'Update tag types', 'root'),
|
||||||
|
('DELETE_TAG_TYPE', 'Delete tag types', 'root'),
|
||||||
|
('CREATE_PROJECT', 'Create projects', 'root'),
|
||||||
|
('UPDATE_PROJECT', 'Update project', 'project'),
|
||||||
|
('DELETE_PROJECT', 'Delete project', 'project'),
|
||||||
|
('UPDATE_STRATEGY', 'Update strategies', 'root'),
|
||||||
|
('DELETE_STRATEGY', 'Delete strategies', 'root'),
|
||||||
|
('UPDATE_CONTEXT_FIELD', 'Update context fields', 'root'),
|
||||||
|
('CREATE_CONTEXT_FIELD', 'Create context fields', 'root'),
|
||||||
|
('DELETE_CONTEXT_FIELD', 'Delete context fields', 'root'),
|
||||||
|
('READ_ROLE', 'Read roles', 'root'),
|
||||||
|
('CREATE_FEATURE_STRATEGY', 'Create activation strategies', 'environment'),
|
||||||
|
('UPDATE_FEATURE_STRATEGY', 'Update activation strategies', 'environment'),
|
||||||
|
('DELETE_FEATURE_STRATEGY', 'Delete activation strategies', 'environment'),
|
||||||
|
('CREATE_CLIENT_API_TOKEN', 'Create CLIENT API tokens', 'root'),
|
||||||
|
('UPDATE_FEATURE_VARIANTS', 'Create/edit variants', 'project'),
|
||||||
|
('MOVE_FEATURE_TOGGLE', 'Change feature toggle project', 'project'),
|
||||||
|
('CREATE_SEGMENT', 'Create segments', 'root'),
|
||||||
|
('UPDATE_SEGMENT', 'Edit segments', 'root'),
|
||||||
|
('DELETE_SEGMENT', 'Delete segments', 'root'),
|
||||||
|
('READ_PROJECT_API_TOKEN', 'Read api tokens for a specific project', 'project'),
|
||||||
|
('CREATE_PROJECT_API_TOKEN', 'Create api tokens for a specific project', 'project'),
|
||||||
|
('DELETE_PROJECT_API_TOKEN', 'Delete api tokens for a specific project', 'project'),
|
||||||
|
('UPDATE_FEATURE_ENVIRONMENT_VARIANTS', 'Update variants', 'environment'),
|
||||||
|
('UPDATE_FEATURE_ENVIRONMENT', 'Enable/disable toggles', 'environment'),
|
||||||
|
('APPLY_CHANGE_REQUEST', 'Apply change requests', 'environment'),
|
||||||
|
('UPDATE_CLIENT_API_TOKEN', 'Update CLIENT API tokens', 'root'),
|
||||||
|
('UPDATE_PROJECT_SEGMENT', 'Create/edit project segment', 'project'),
|
||||||
|
('SKIP_CHANGE_REQUEST', 'Skip change request process', 'environment'),
|
||||||
|
('DELETE_CLIENT_API_TOKEN', 'Delete CLIENT API tokens', 'root'),
|
||||||
|
('READ_CLIENT_API_TOKEN', 'Read CLIENT API tokens', 'root'),
|
||||||
|
('APPROVE_CHANGE_REQUEST', 'Approve/Reject change requests', 'environment'),
|
||||||
|
('CREATE_FRONTEND_API_TOKEN', 'Create FRONTEND API tokens', 'root'),
|
||||||
|
('UPDATE_FRONTEND_API_TOKEN', 'Update FRONTEND API tokens', 'root'),
|
||||||
|
('DELETE_FRONTEND_API_TOKEN', 'Delete FRONTEND API tokens', 'root'),
|
||||||
|
('READ_FRONTEND_API_TOKEN', 'Read FRONTEND API tokens', 'root'),
|
||||||
|
('UPDATE_FEATURE_DEPENDENCY', 'Update feature dependency', 'project'),
|
||||||
|
('CREATE_TAG_TYPE', 'Create tag types', 'root')
|
||||||
|
) AS new_permissions(permission, display_name, type)
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM permissions WHERE permission = new_permissions.permission
|
||||||
|
);
|
||||||
|
|
||||||
|
-- STEP 10: Ensure the default roles exist
|
||||||
|
INSERT INTO roles (name, description, type)
|
||||||
|
SELECT * FROM (VALUES
|
||||||
|
('Admin', 'Users with the root admin role have superuser access to Unleash and can perform any operation within the Unleash platform.', 'root'),
|
||||||
|
('Editor', 'Users with the root editor role have access to most features in Unleash, but can not manage users and roles in the root scope. Editors will be added as project owners when creating projects and get superuser rights within the context of these projects. Users with the editor role will also get access to most permissions on the default project by default.', 'root'),
|
||||||
|
('Viewer', 'Users with the root viewer role can only read root resources in Unleash. Viewers can be added to specific projects as project members. Users with the viewer role may not view API tokens.', 'root'),
|
||||||
|
('Owner', 'Users with the project owner role have full control over the project, and can add and manage other users within the project context, manage feature toggles within the project, and control advanced project features like archiving and deleting the project.', 'project'),
|
||||||
|
('Member', 'Users with the project member role are allowed to view, create, and update feature toggles within a project, but have limited permissions in regards to managing the project''s user access and can not archive or delete the project.', 'project')
|
||||||
|
) AS new_roles(name, description)
|
||||||
|
WHERE NOT EXISTS (
|
||||||
|
SELECT 1 FROM roles WHERE name = new_roles.name
|
||||||
|
);
|
||||||
|
|
||||||
|
-- STEP 11: Ensure the default roles have the correct permissions
|
||||||
|
SELECT assign_unleash_permission_to_role('ADMIN', 'Admin');
|
||||||
|
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_FEATURE', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_STRATEGY', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_ADDON', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_ADDON', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_ADDON', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_FEATURE', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_APPLICATION', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_TAG_TYPE', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_TAG_TYPE', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_PROJECT', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_PROJECT', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_PROJECT', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_STRATEGY', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_STRATEGY', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_CONTEXT_FIELD', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_CONTEXT_FIELD', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_CONTEXT_FIELD', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE_VARIANTS', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('CREATE_FEATURE_STRATEGY', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_STRATEGY', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('DELETE_FEATURE_STRATEGY', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT_VARIANTS', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('MOVE_FEATURE_TOGGLE', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_SEGMENT', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_SEGMENT', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_SEGMENT', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('READ_PROJECT_API_TOKEN', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_PROJECT_API_TOKEN', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_PROJECT_API_TOKEN', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('READ_CLIENT_API_TOKEN', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('READ_FRONTEND_API_TOKEN', 'Editor');
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_TAG_TYPE', 'Editor');
|
||||||
|
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_FEATURE', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_FEATURE', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_PROJECT', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_PROJECT', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE_VARIANTS', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('CREATE_FEATURE_STRATEGY', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_STRATEGY', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('DELETE_FEATURE_STRATEGY', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT_VARIANTS', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role('MOVE_FEATURE_TOGGLE', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role('READ_PROJECT_API_TOKEN', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_PROJECT_API_TOKEN', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_PROJECT_API_TOKEN', 'Owner');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE_DEPENDENCY', 'Owner');
|
||||||
|
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_FEATURE', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_FEATURE', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE_VARIANTS', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('CREATE_FEATURE_STRATEGY', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_STRATEGY', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('DELETE_FEATURE_STRATEGY', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role_for_all_environments('UPDATE_FEATURE_ENVIRONMENT_VARIANTS', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role('READ_PROJECT_API_TOKEN', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role('CREATE_PROJECT_API_TOKEN', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role('DELETE_PROJECT_API_TOKEN', 'Member');
|
||||||
|
SELECT assign_unleash_permission_to_role('UPDATE_FEATURE_DEPENDENCY', 'Member');
|
||||||
|
`,
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = function (db, cb) {
|
||||||
|
db.runSql(
|
||||||
|
`
|
||||||
|
-- STEP 1: Undo foreign key constraint on 'role_permission'
|
||||||
|
ALTER TABLE role_permission
|
||||||
|
DROP CONSTRAINT fk_role_permission_permission;
|
||||||
|
|
||||||
|
-- STEP 2: Undo primary key constraint on 'permissions'
|
||||||
|
ALTER TABLE permissions
|
||||||
|
DROP CONSTRAINT permissions_pkey;
|
||||||
|
|
||||||
|
-- STEP 3: Add the 'id' primary key constraint
|
||||||
|
ALTER TABLE permissions
|
||||||
|
ADD PRIMARY KEY (id);
|
||||||
|
|
||||||
|
-- STEP 4: Re-add the permissions by id
|
||||||
|
UPDATE role_permission rp
|
||||||
|
SET permission_id = p.id
|
||||||
|
FROM permissions p
|
||||||
|
WHERE rp.permission = p.permission;
|
||||||
|
|
||||||
|
-- STEP 5: Drop the new 'permission' column
|
||||||
|
ALTER TABLE role_permission
|
||||||
|
DROP COLUMN permission;
|
||||||
|
|
||||||
|
-- STEP 6: Drop the unique constraint on 'permission'
|
||||||
|
ALTER TABLE permissions
|
||||||
|
DROP CONSTRAINT permission_unique;
|
||||||
|
`,
|
||||||
|
cb
|
||||||
|
);
|
||||||
|
};
|
@ -0,0 +1,736 @@
|
|||||||
|
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||||
|
|
||||||
|
exports[`Favor permission name over id migration correctly assigns permissions by name 1`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"display_name": "A test permission",
|
||||||
|
"permission": "TEST_PERMISSION_1",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "A test permission",
|
||||||
|
"permission": "TEST_PERMISSION_2",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "A test permission",
|
||||||
|
"permission": "TEST_PERMISSION_3",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "A test permission",
|
||||||
|
"permission": "TEST_PERMISSION_4",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Admin",
|
||||||
|
"permission": "ADMIN",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create feature toggles",
|
||||||
|
"permission": "CREATE_FEATURE",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create activation strategies",
|
||||||
|
"permission": "CREATE_STRATEGY",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create addons",
|
||||||
|
"permission": "CREATE_ADDON",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Delete addons",
|
||||||
|
"permission": "DELETE_ADDON",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update addons",
|
||||||
|
"permission": "UPDATE_ADDON",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update feature toggles",
|
||||||
|
"permission": "UPDATE_FEATURE",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Delete feature toggles",
|
||||||
|
"permission": "DELETE_FEATURE",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update applications",
|
||||||
|
"permission": "UPDATE_APPLICATION",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update tag types",
|
||||||
|
"permission": "UPDATE_TAG_TYPE",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Delete tag types",
|
||||||
|
"permission": "DELETE_TAG_TYPE",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create projects",
|
||||||
|
"permission": "CREATE_PROJECT",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update project",
|
||||||
|
"permission": "UPDATE_PROJECT",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Delete project",
|
||||||
|
"permission": "DELETE_PROJECT",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update strategies",
|
||||||
|
"permission": "UPDATE_STRATEGY",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Delete strategies",
|
||||||
|
"permission": "DELETE_STRATEGY",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update context fields",
|
||||||
|
"permission": "UPDATE_CONTEXT_FIELD",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create context fields",
|
||||||
|
"permission": "CREATE_CONTEXT_FIELD",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Delete context fields",
|
||||||
|
"permission": "DELETE_CONTEXT_FIELD",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Read roles",
|
||||||
|
"permission": "READ_ROLE",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create activation strategies",
|
||||||
|
"permission": "CREATE_FEATURE_STRATEGY",
|
||||||
|
"type": "environment",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update activation strategies",
|
||||||
|
"permission": "UPDATE_FEATURE_STRATEGY",
|
||||||
|
"type": "environment",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Delete activation strategies",
|
||||||
|
"permission": "DELETE_FEATURE_STRATEGY",
|
||||||
|
"type": "environment",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create CLIENT API tokens",
|
||||||
|
"permission": "CREATE_CLIENT_API_TOKEN",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create/edit variants",
|
||||||
|
"permission": "UPDATE_FEATURE_VARIANTS",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Change feature toggle project",
|
||||||
|
"permission": "MOVE_FEATURE_TOGGLE",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create segments",
|
||||||
|
"permission": "CREATE_SEGMENT",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Edit segments",
|
||||||
|
"permission": "UPDATE_SEGMENT",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Delete segments",
|
||||||
|
"permission": "DELETE_SEGMENT",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Read api tokens for a specific project",
|
||||||
|
"permission": "READ_PROJECT_API_TOKEN",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create api tokens for a specific project",
|
||||||
|
"permission": "CREATE_PROJECT_API_TOKEN",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Delete api tokens for a specific project",
|
||||||
|
"permission": "DELETE_PROJECT_API_TOKEN",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update variants",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
|
||||||
|
"type": "environment",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Enable/disable toggles",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT",
|
||||||
|
"type": "environment",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Apply change requests",
|
||||||
|
"permission": "APPLY_CHANGE_REQUEST",
|
||||||
|
"type": "environment",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update CLIENT API tokens",
|
||||||
|
"permission": "UPDATE_CLIENT_API_TOKEN",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create/edit project segment",
|
||||||
|
"permission": "UPDATE_PROJECT_SEGMENT",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Skip change request process",
|
||||||
|
"permission": "SKIP_CHANGE_REQUEST",
|
||||||
|
"type": "environment",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Delete CLIENT API tokens",
|
||||||
|
"permission": "DELETE_CLIENT_API_TOKEN",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Read CLIENT API tokens",
|
||||||
|
"permission": "READ_CLIENT_API_TOKEN",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Approve/Reject change requests",
|
||||||
|
"permission": "APPROVE_CHANGE_REQUEST",
|
||||||
|
"type": "environment",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create FRONTEND API tokens",
|
||||||
|
"permission": "CREATE_FRONTEND_API_TOKEN",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update FRONTEND API tokens",
|
||||||
|
"permission": "UPDATE_FRONTEND_API_TOKEN",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Delete FRONTEND API tokens",
|
||||||
|
"permission": "DELETE_FRONTEND_API_TOKEN",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Read FRONTEND API tokens",
|
||||||
|
"permission": "READ_FRONTEND_API_TOKEN",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Update feature dependency",
|
||||||
|
"permission": "UPDATE_FEATURE_DEPENDENCY",
|
||||||
|
"type": "project",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"display_name": "Create tag types",
|
||||||
|
"permission": "CREATE_TAG_TYPE",
|
||||||
|
"type": "root",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`Favor permission name over id migration correctly assigns permissions by name 2`] = `
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "ADMIN",
|
||||||
|
"role_id": 6,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_FEATURE",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_ADDON",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_ADDON",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_ADDON",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_FEATURE",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_FEATURE",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_APPLICATION",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_TAG_TYPE",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_TAG_TYPE",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_PROJECT",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_PROJECT",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_PROJECT",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_CONTEXT_FIELD",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_CONTEXT_FIELD",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_CONTEXT_FIELD",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_FEATURE_VARIANTS",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "CREATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "CREATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "CREATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "UPDATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "UPDATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "UPDATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "DELETE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "DELETE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "DELETE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "MOVE_FEATURE_TOGGLE",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_SEGMENT",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_SEGMENT",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_SEGMENT",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "READ_PROJECT_API_TOKEN",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_PROJECT_API_TOKEN",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_PROJECT_API_TOKEN",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "READ_CLIENT_API_TOKEN",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "READ_FRONTEND_API_TOKEN",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_TAG_TYPE",
|
||||||
|
"role_id": 7,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_FEATURE",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_FEATURE",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_FEATURE",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_PROJECT",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_PROJECT",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_FEATURE_VARIANTS",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "CREATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "CREATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "CREATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "UPDATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "UPDATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "UPDATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "DELETE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "DELETE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "DELETE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "MOVE_FEATURE_TOGGLE",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "READ_PROJECT_API_TOKEN",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_PROJECT_API_TOKEN",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_PROJECT_API_TOKEN",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_FEATURE_DEPENDENCY",
|
||||||
|
"role_id": 9,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_FEATURE",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_FEATURE",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_FEATURE",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_FEATURE_VARIANTS",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "CREATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "CREATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "CREATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "UPDATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "UPDATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "UPDATE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "DELETE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "DELETE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "DELETE_FEATURE_STRATEGY",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "default",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "development",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": "production",
|
||||||
|
"permission": "UPDATE_FEATURE_ENVIRONMENT_VARIANTS",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "READ_PROJECT_API_TOKEN",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "CREATE_PROJECT_API_TOKEN",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "DELETE_PROJECT_API_TOKEN",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"environment": null,
|
||||||
|
"permission": "UPDATE_FEATURE_DEPENDENCY",
|
||||||
|
"role_id": 10,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
`;
|
@ -48,7 +48,7 @@ test('Dedupe permissions migration correctly dedupes permissions', async () => {
|
|||||||
const client = new Client(config.db);
|
const client = new Client(config.db);
|
||||||
await client.connect();
|
await client.connect();
|
||||||
await client.query(`
|
await client.query(`
|
||||||
DELETE FROM "dedupe_permissions_test"."role_permission";
|
DELETE FROM "dedupe_permissions_test"."roles";
|
||||||
|
|
||||||
INSERT INTO "dedupe_permissions_test"."roles" (id, name, description, type) VALUES (101, 'Role 1', 'A test role', 'custom');
|
INSERT INTO "dedupe_permissions_test"."roles" (id, name, description, type) VALUES (101, 'Role 1', 'A test role', 'custom');
|
||||||
INSERT INTO "dedupe_permissions_test"."roles" (id, name, description, type) VALUES (102, 'Role 2', 'A test role', 'custom');
|
INSERT INTO "dedupe_permissions_test"."roles" (id, name, description, type) VALUES (102, 'Role 2', 'A test role', 'custom');
|
||||||
|
158
src/test/e2e/favor-permission-name-over-id.e2e.test.ts
Normal file
158
src/test/e2e/favor-permission-name-over-id.e2e.test.ts
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
import { getDbConfig } from './helpers/database-config';
|
||||||
|
import { createTestConfig } from '../config/test-config';
|
||||||
|
import { getInstance } from 'db-migrate';
|
||||||
|
import { log } from 'db-migrate-shared';
|
||||||
|
import { Client } from 'pg';
|
||||||
|
import { IDBOption } from 'lib/types';
|
||||||
|
|
||||||
|
log.setLogLevel('error');
|
||||||
|
|
||||||
|
async function initSchema(db: IDBOption): Promise<void> {
|
||||||
|
const client = new Client(db);
|
||||||
|
await client.connect();
|
||||||
|
await client.query(`DROP SCHEMA IF EXISTS ${db.schema} CASCADE`);
|
||||||
|
await client.query(`CREATE SCHEMA IF NOT EXISTS ${db.schema}`);
|
||||||
|
await client.end();
|
||||||
|
}
|
||||||
|
|
||||||
|
test('Favor permission name over id migration correctly assigns permissions by name', async () => {
|
||||||
|
jest.setTimeout(15000);
|
||||||
|
const config = createTestConfig({
|
||||||
|
db: {
|
||||||
|
...getDbConfig(),
|
||||||
|
pool: { min: 1, max: 4 },
|
||||||
|
schema: 'favor_permission_name_over_id_test',
|
||||||
|
ssl: false,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
await initSchema(config.db);
|
||||||
|
|
||||||
|
const e2e = {
|
||||||
|
...config.db,
|
||||||
|
connectionTimeoutMillis: 2000,
|
||||||
|
};
|
||||||
|
|
||||||
|
// disable Intellij/WebStorm from setting verbose CLI argument to db-migrator
|
||||||
|
process.argv = process.argv.filter((it) => !it.includes('--verbose'));
|
||||||
|
const dbm = getInstance(true, {
|
||||||
|
cwd: `${__dirname}/../../`, // relative to src/test/e2e
|
||||||
|
config: { e2e },
|
||||||
|
env: 'e2e',
|
||||||
|
});
|
||||||
|
|
||||||
|
// Run all migrations up to, and including, this one, the last one before the favor permission name over id migration
|
||||||
|
await dbm.up('20231123100052-drop-last-seen-foreign-key.js');
|
||||||
|
|
||||||
|
// Set up the test data
|
||||||
|
const client = new Client(config.db);
|
||||||
|
await client.connect();
|
||||||
|
await client.query(`
|
||||||
|
DELETE FROM "favor_permission_name_over_id_test"."roles";
|
||||||
|
|
||||||
|
INSERT INTO "favor_permission_name_over_id_test"."roles" (id, name, description, type) VALUES (101, 'Role 1', 'A test role', 'custom');
|
||||||
|
`);
|
||||||
|
await client.query(`
|
||||||
|
DELETE FROM "favor_permission_name_over_id_test"."permissions";
|
||||||
|
|
||||||
|
INSERT INTO "favor_permission_name_over_id_test"."permissions" (id, permission, display_name, type) VALUES (101, 'TEST_PERMISSION_1', 'A test permission', 'root');
|
||||||
|
INSERT INTO "favor_permission_name_over_id_test"."permissions" (id, permission, display_name, type) VALUES (102, 'TEST_PERMISSION_2', 'A test permission', 'root');
|
||||||
|
INSERT INTO "favor_permission_name_over_id_test"."permissions" (id, permission, display_name, type) VALUES (103, 'TEST_PERMISSION_3', 'A test permission', 'root');
|
||||||
|
INSERT INTO "favor_permission_name_over_id_test"."permissions" (id, permission, display_name, type) VALUES (104, 'TEST_PERMISSION_4', 'A test permission', 'root');
|
||||||
|
`);
|
||||||
|
await client.query(`
|
||||||
|
DELETE FROM "favor_permission_name_over_id_test"."role_permission";
|
||||||
|
|
||||||
|
INSERT INTO "favor_permission_name_over_id_test"."role_permission" (role_id, permission_id) VALUES (101, 101);
|
||||||
|
INSERT INTO "favor_permission_name_over_id_test"."role_permission" (role_id, permission_id) VALUES (101, 102);
|
||||||
|
INSERT INTO "favor_permission_name_over_id_test"."role_permission" (role_id, permission_id) VALUES (101, 103);
|
||||||
|
INSERT INTO "favor_permission_name_over_id_test"."role_permission" (role_id, permission_id) VALUES (101, 104);
|
||||||
|
`);
|
||||||
|
|
||||||
|
// Run the drop permissions id migration
|
||||||
|
await dbm.up('20231123155649-favor-permission-name-over-id.js');
|
||||||
|
|
||||||
|
// Check the results
|
||||||
|
const { rows: resultsPermissions } = await client.query(`
|
||||||
|
SELECT * FROM "favor_permission_name_over_id_test"."permissions" ORDER BY created_at;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const { rows: resultsRolePermission } = await client.query(`
|
||||||
|
SELECT * FROM "favor_permission_name_over_id_test"."role_permission" WHERE role_id = 101 ORDER BY created_at;
|
||||||
|
`);
|
||||||
|
|
||||||
|
// We keep the id for now, but will remove it in the next major version
|
||||||
|
expect('id' in resultsPermissions[0]).toEqual(true);
|
||||||
|
expect(
|
||||||
|
resultsPermissions.map(({ permission, display_name, type }) => ({
|
||||||
|
permission,
|
||||||
|
display_name,
|
||||||
|
type,
|
||||||
|
})),
|
||||||
|
).toMatchSnapshot();
|
||||||
|
|
||||||
|
// We keep the permission_id for now, but will remove it in the next minor version
|
||||||
|
expect('permission_id' in resultsRolePermission[0]).toEqual(true);
|
||||||
|
expect('permission' in resultsRolePermission[0]).toEqual(true);
|
||||||
|
|
||||||
|
expect(resultsRolePermission.length).toEqual(4);
|
||||||
|
expect(
|
||||||
|
resultsRolePermission.map(({ role_id, permission }) => ({
|
||||||
|
role_id,
|
||||||
|
permission,
|
||||||
|
})),
|
||||||
|
).toEqual([
|
||||||
|
{ role_id: 101, permission: 'TEST_PERMISSION_1' },
|
||||||
|
{ role_id: 101, permission: 'TEST_PERMISSION_2' },
|
||||||
|
{ role_id: 101, permission: 'TEST_PERMISSION_3' },
|
||||||
|
{ role_id: 101, permission: 'TEST_PERMISSION_4' },
|
||||||
|
]);
|
||||||
|
|
||||||
|
// Check the results that ensure the default roles exist and have the correct permissions
|
||||||
|
const { rows: resultsRoles } = await client.query(`
|
||||||
|
SELECT name, description, type FROM "favor_permission_name_over_id_test"."roles" WHERE id != 101 ORDER BY created_at;
|
||||||
|
`);
|
||||||
|
|
||||||
|
const { rows: resultsDefaultRolePermissions } = await client.query(`
|
||||||
|
SELECT role_id, environment, permission FROM "favor_permission_name_over_id_test"."role_permission" WHERE role_id != 101 ORDER BY created_at;
|
||||||
|
`);
|
||||||
|
|
||||||
|
expect(resultsRoles.length).toEqual(5);
|
||||||
|
expect(resultsRoles).toEqual([
|
||||||
|
{
|
||||||
|
name: 'Admin',
|
||||||
|
description:
|
||||||
|
'Users with the root admin role have superuser access to Unleash and can perform any operation within the Unleash platform.',
|
||||||
|
type: 'root',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Editor',
|
||||||
|
description:
|
||||||
|
'Users with the root editor role have access to most features in Unleash, but can not manage users and roles in the root scope. Editors will be added as project owners when creating projects and get superuser rights within the context of these projects. Users with the editor role will also get access to most permissions on the default project by default.',
|
||||||
|
type: 'root',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Viewer',
|
||||||
|
description:
|
||||||
|
'Users with the root viewer role can only read root resources in Unleash. Viewers can be added to specific projects as project members. Users with the viewer role may not view API tokens.',
|
||||||
|
type: 'root',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Owner',
|
||||||
|
description:
|
||||||
|
'Users with the project owner role have full control over the project, and can add and manage other users within the project context, manage feature toggles within the project, and control advanced project features like archiving and deleting the project.',
|
||||||
|
type: 'project',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Member',
|
||||||
|
description:
|
||||||
|
"Users with the project member role are allowed to view, create, and update feature toggles within a project, but have limited permissions in regards to managing the project's user access and can not archive or delete the project.",
|
||||||
|
type: 'project',
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(resultsDefaultRolePermissions).toMatchSnapshot();
|
||||||
|
|
||||||
|
await client.end();
|
||||||
|
await dbm.reset();
|
||||||
|
});
|
Loading…
Reference in New Issue
Block a user