mirror of
https://github.com/strapi/strapi.git
synced 2025-12-03 02:23:44 +00:00
Merge branch 'features/api-token-v2' into api-token-v2/change-primary-button-size-on-api-token-list
This commit is contained in:
commit
1cb18d767e
@ -38,6 +38,21 @@ module.exports = {
|
||||
ctx.created({ data: apiToken });
|
||||
},
|
||||
|
||||
async regenerate(ctx) {
|
||||
const { id } = ctx.params;
|
||||
const apiTokenService = getService('api-token');
|
||||
|
||||
const apiTokenExists = await apiTokenService.getById(id);
|
||||
if (!apiTokenExists) {
|
||||
ctx.notFound('API Token not found');
|
||||
return;
|
||||
}
|
||||
|
||||
const accessToken = await apiTokenService.regenerate(id);
|
||||
|
||||
ctx.created({ data: accessToken });
|
||||
},
|
||||
|
||||
async list(ctx) {
|
||||
const apiTokenService = getService('api-token');
|
||||
const apiTokens = await apiTokenService.list();
|
||||
@ -60,7 +75,6 @@ module.exports = {
|
||||
|
||||
if (!apiToken) {
|
||||
ctx.notFound('API Token not found');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@ -56,4 +56,15 @@ module.exports = [
|
||||
],
|
||||
},
|
||||
},
|
||||
{
|
||||
method: 'POST',
|
||||
path: '/api-tokens/:id/regenerate',
|
||||
handler: 'api-token.regenerate',
|
||||
config: {
|
||||
policies: [
|
||||
'admin::isAuthenticatedAdmin',
|
||||
{ name: 'admin::hasPermissions', config: { actions: ['admin::api-tokens.update'] } },
|
||||
],
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const { NotFoundError } = require('@strapi/utils/lib/errors');
|
||||
const crypto = require('crypto');
|
||||
const { omit } = require('lodash/fp');
|
||||
const apiTokenService = require('../api-token');
|
||||
@ -250,6 +251,59 @@ describe('API Token', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('regenerate', () => {
|
||||
test('It regenerates the accessKey', async () => {
|
||||
const update = jest.fn(({ data }) => Promise.resolve(data));
|
||||
|
||||
global.strapi = {
|
||||
query() {
|
||||
return { update };
|
||||
},
|
||||
config: {
|
||||
get: jest.fn(() => ''),
|
||||
},
|
||||
};
|
||||
|
||||
const id = 1;
|
||||
const res = await apiTokenService.regenerate(id);
|
||||
|
||||
expect(update).toHaveBeenCalledWith({
|
||||
where: { id },
|
||||
select: ['id', 'accessKey'],
|
||||
data: {
|
||||
accessKey: apiTokenService.hash(mockedApiToken.hexedString),
|
||||
},
|
||||
});
|
||||
expect(res).toEqual({ accessKey: mockedApiToken.hexedString });
|
||||
});
|
||||
|
||||
test('It throws a NotFound if the id is not found', async () => {
|
||||
const update = jest.fn(() => Promise.resolve(null));
|
||||
|
||||
global.strapi = {
|
||||
query() {
|
||||
return { update };
|
||||
},
|
||||
config: {
|
||||
get: jest.fn(() => ''),
|
||||
},
|
||||
};
|
||||
|
||||
const id = 1;
|
||||
await expect(async () => {
|
||||
await apiTokenService.regenerate(id);
|
||||
}).rejects.toThrowError(NotFoundError);
|
||||
|
||||
expect(update).toHaveBeenCalledWith({
|
||||
where: { id },
|
||||
select: ['id', 'accessKey'],
|
||||
data: {
|
||||
accessKey: apiTokenService.hash(mockedApiToken.hexedString),
|
||||
},
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('update', () => {
|
||||
test('Updates a non-custom token', async () => {
|
||||
const token = {
|
||||
|
||||
@ -181,6 +181,32 @@ const create = async (attributes) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {string|number} id
|
||||
*
|
||||
* @returns {Promise<ApiToken>}
|
||||
*/
|
||||
const regenerate = async (id) => {
|
||||
const accessKey = crypto.randomBytes(128).toString('hex');
|
||||
|
||||
const apiToken = await strapi.query('admin::api-token').update({
|
||||
select: ['id', 'accessKey'],
|
||||
where: { id },
|
||||
data: {
|
||||
accessKey: hash(accessKey),
|
||||
},
|
||||
});
|
||||
|
||||
if (!apiToken) {
|
||||
throw new NotFoundError('The provided token id does not exist');
|
||||
}
|
||||
|
||||
return {
|
||||
...apiToken,
|
||||
accessKey,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* @returns {void}
|
||||
*/
|
||||
@ -371,6 +397,7 @@ const update = async (id, attributes) => {
|
||||
|
||||
module.exports = {
|
||||
create,
|
||||
regenerate,
|
||||
exists,
|
||||
checkSaltIsDefined,
|
||||
hash,
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
'use strict';
|
||||
|
||||
const { omit, map, orderBy } = require('lodash');
|
||||
const { omit } = require('lodash');
|
||||
const { createStrapiInstance } = require('../../../../../test/helpers/strapi');
|
||||
const { createAuthRequest } = require('../../../../../test/helpers/request');
|
||||
|
||||
@ -36,6 +36,7 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
const createValidToken = async (token = {}) => {
|
||||
const body = {
|
||||
type: 'read-only',
|
||||
// eslint-disable-next-line no-plusplus
|
||||
name: `token_${String(currentTokens++)}`,
|
||||
description: 'generic description',
|
||||
...token,
|
||||
@ -130,7 +131,7 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
expect(res.body.data).toStrictEqual({
|
||||
expect(res.body.data).toMatchObject({
|
||||
accessKey: expect.any(String),
|
||||
name: body.name,
|
||||
permissions: [],
|
||||
@ -189,7 +190,7 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
expect(res.body.data).toStrictEqual({
|
||||
expect(res.body.data).toMatchObject({
|
||||
accessKey: expect.any(String),
|
||||
name: body.name,
|
||||
permissions: [],
|
||||
@ -217,7 +218,7 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
expect(res.body.data).toStrictEqual({
|
||||
expect(res.body.data).toMatchObject({
|
||||
accessKey: expect.any(String),
|
||||
name: body.name,
|
||||
permissions: body.permissions,
|
||||
@ -269,7 +270,7 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
expect(res.body.data).toStrictEqual({
|
||||
expect(res.body.data).toMatchObject({
|
||||
accessKey: expect.any(String),
|
||||
name: body.name,
|
||||
permissions: [],
|
||||
@ -296,7 +297,7 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
expect(res.body.data).toStrictEqual({
|
||||
expect(res.body.data).toMatchObject({
|
||||
accessKey: expect.any(String),
|
||||
name: 'api-token_tests-spaces-at-the-end',
|
||||
permissions: [],
|
||||
@ -331,9 +332,16 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.data.length).toBe(4);
|
||||
expect(orderBy(res.body.data, ['id'])).toStrictEqual(
|
||||
map(orderBy(tokens, ['id']), (t) => omit(t, ['accessKey']))
|
||||
);
|
||||
// check that each token exists in data
|
||||
tokens.forEach((token) => {
|
||||
const t = res.body.data.find((t) => t.id === token.id);
|
||||
if (t.permissions) {
|
||||
t.permissions = t.permissions.sort();
|
||||
// eslint-disable-next-line no-param-reassign
|
||||
token.permissions = token.permissions.sort();
|
||||
}
|
||||
expect(t).toMatchObject(omit(token, ['accessKey']));
|
||||
});
|
||||
});
|
||||
|
||||
test('Deletes a token (successfully)', async () => {
|
||||
@ -345,7 +353,7 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.data).toStrictEqual({
|
||||
expect(res.body.data).toMatchObject({
|
||||
name: token.name,
|
||||
permissions: token.permissions,
|
||||
description: token.description,
|
||||
@ -378,7 +386,7 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.data).toStrictEqual({
|
||||
expect(res.body.data).toMatchObject({
|
||||
name: token.name,
|
||||
permissions: token.permissions,
|
||||
description: token.description,
|
||||
@ -402,7 +410,7 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(200);
|
||||
expect(res.body.data).toStrictEqual({
|
||||
expect(res.body.data).toMatchObject({
|
||||
name: token.name,
|
||||
permissions: token.permissions,
|
||||
description: token.description,
|
||||
@ -460,7 +468,7 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
});
|
||||
|
||||
expect(updatedRes.statusCode).toBe(200);
|
||||
expect(updatedRes.body.data).toStrictEqual({
|
||||
expect(updatedRes.body.data).toMatchObject({
|
||||
name: updatedBody.name,
|
||||
permissions: [],
|
||||
description: updatedBody.description,
|
||||
@ -608,4 +616,36 @@ describe('Admin API Token v2 CRUD (e2e)', () => {
|
||||
updatedAt: expect.any(String),
|
||||
});
|
||||
});
|
||||
|
||||
test('Regenerates an api token access key', async () => {
|
||||
const token = await createValidToken();
|
||||
|
||||
const res = await rq({
|
||||
url: `/admin/api-tokens/${token.id}/regenerate`,
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(201);
|
||||
expect(res.body.data).toMatchObject({
|
||||
accessKey: expect.any(String),
|
||||
});
|
||||
expect(res.body.data.accessKey).not.toEqual(token.accessKey);
|
||||
});
|
||||
|
||||
test('Regenerate throws a NotFound if provided an invalid id', async () => {
|
||||
const res = await rq({
|
||||
url: `/admin/api-tokens/999999/regenerate`,
|
||||
method: 'POST',
|
||||
});
|
||||
|
||||
expect(res.statusCode).toBe(404);
|
||||
expect(res.body.error).toMatchObject({
|
||||
name: 'NotFoundError',
|
||||
status: 404,
|
||||
});
|
||||
});
|
||||
|
||||
test.todo('Regenerated access key works');
|
||||
test.todo('Tokens access content for which they are authorized');
|
||||
test.todo('Tokens fail to access content for which they are not authorized');
|
||||
});
|
||||
|
||||
@ -454,7 +454,7 @@ class Strapi {
|
||||
await this.runLifecyclesFunctions(LIFECYCLES.BOOTSTRAP);
|
||||
|
||||
// TODO: is this the best place for this?
|
||||
await this.contentAPI.permissions.syncActions();
|
||||
await this.contentAPI.permissions.registerActions();
|
||||
|
||||
this.cron.start();
|
||||
|
||||
|
||||
@ -0,0 +1,291 @@
|
||||
'use strict';
|
||||
|
||||
const createContentAPI = require('../content-api');
|
||||
|
||||
describe('Content API - Permissions', () => {
|
||||
const bindToContentAPI = (action) => {
|
||||
Object.assign(action, { [Symbol.for('__type__')]: ['content-api'] });
|
||||
return action;
|
||||
};
|
||||
|
||||
describe('Get Actions Map', () => {
|
||||
test('When no API are defined, it should return an empty object', () => {
|
||||
global.strapi = {};
|
||||
|
||||
const contentAPI = createContentAPI(global.strapi);
|
||||
const actionsMap = contentAPI.permissions.getActionsMap();
|
||||
|
||||
expect(actionsMap).toEqual({});
|
||||
});
|
||||
|
||||
test('When no controller are defined for an API, it should ignore the API', () => {
|
||||
global.strapi = {
|
||||
api: {
|
||||
foo: {},
|
||||
bar: {},
|
||||
},
|
||||
};
|
||||
|
||||
const contentAPI = createContentAPI(global.strapi);
|
||||
const actionsMap = contentAPI.permissions.getActionsMap();
|
||||
|
||||
expect(actionsMap).toEqual({});
|
||||
});
|
||||
|
||||
test(`Do not register controller if they're not bound to the content API`, () => {
|
||||
const actionC = () => {};
|
||||
Object.assign(actionC, { [Symbol.for('__type__')]: ['admin-api'] });
|
||||
|
||||
global.strapi = {
|
||||
api: {
|
||||
foo: {
|
||||
controllers: {
|
||||
controllerA: {
|
||||
actionA: bindToContentAPI(() => {}),
|
||||
actionB() {},
|
||||
actionC,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const contentAPI = createContentAPI(global.strapi);
|
||||
const actionsMap = contentAPI.permissions.getActionsMap();
|
||||
|
||||
expect(actionsMap).toEqual({
|
||||
'api::foo': { controllers: { controllerA: ['actionA'] } },
|
||||
});
|
||||
});
|
||||
|
||||
test('Creates and populate a map of actions from APIs and plugins', () => {
|
||||
global.strapi = {
|
||||
api: {
|
||||
foo: {
|
||||
controllers: {
|
||||
controllerA: {
|
||||
actionA: bindToContentAPI(() => {}),
|
||||
actionB: bindToContentAPI(() => {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
bar: {
|
||||
controllers: {
|
||||
controllerA: {
|
||||
actionA: bindToContentAPI(() => {}),
|
||||
actionB: bindToContentAPI(() => {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
foobar: {
|
||||
controllers: {
|
||||
controllerA: {
|
||||
actionA: bindToContentAPI(() => {}),
|
||||
actionB: bindToContentAPI(() => {}),
|
||||
},
|
||||
controllerB: {
|
||||
actionC: bindToContentAPI(() => {}),
|
||||
actionD: bindToContentAPI(() => {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
foo: {
|
||||
controllers: {
|
||||
controllerA: {
|
||||
actionA: bindToContentAPI(() => {}),
|
||||
actionB: bindToContentAPI(() => {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
bar: {
|
||||
controllers: {
|
||||
controllerA: {
|
||||
actionA: bindToContentAPI(() => {}),
|
||||
actionB: bindToContentAPI(() => {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const contentAPI = createContentAPI(global.strapi);
|
||||
const actionsMap = contentAPI.permissions.getActionsMap();
|
||||
|
||||
expect(actionsMap).toEqual({
|
||||
'api::foo': { controllers: { controllerA: ['actionA', 'actionB'] } },
|
||||
'api::bar': { controllers: { controllerA: ['actionA', 'actionB'] } },
|
||||
'api::foobar': {
|
||||
controllers: {
|
||||
controllerA: ['actionA', 'actionB'],
|
||||
controllerB: ['actionC', 'actionD'],
|
||||
},
|
||||
},
|
||||
'plugin::foo': { controllers: { controllerA: ['actionA', 'actionB'] } },
|
||||
'plugin::bar': { controllers: { controllerA: ['actionA', 'actionB'] } },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('Register Actions', () => {
|
||||
beforeEach(() => {
|
||||
global.strapi = {
|
||||
api: {
|
||||
foo: {
|
||||
controllers: {
|
||||
controllerA: {
|
||||
actionA: bindToContentAPI(() => {}),
|
||||
actionB: bindToContentAPI(() => {}),
|
||||
},
|
||||
controllerB: {
|
||||
actionC: bindToContentAPI(() => {}),
|
||||
actionD: bindToContentAPI(() => {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
foo: {
|
||||
controllers: {
|
||||
controllerA: {
|
||||
actionA: bindToContentAPI(() => {}),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
||||
test('The action provider should holds every action from APIs and plugins', async () => {
|
||||
const contentAPI = createContentAPI(global.strapi);
|
||||
|
||||
await contentAPI.permissions.registerActions();
|
||||
|
||||
const values = contentAPI.permissions.providers.action.values();
|
||||
|
||||
expect(values).toEqual([
|
||||
{
|
||||
uid: 'api::foo.controllerA.actionA',
|
||||
api: 'api::foo',
|
||||
controller: 'controllerA',
|
||||
action: 'actionA',
|
||||
},
|
||||
{
|
||||
uid: 'api::foo.controllerA.actionB',
|
||||
api: 'api::foo',
|
||||
controller: 'controllerA',
|
||||
action: 'actionB',
|
||||
},
|
||||
{
|
||||
uid: 'api::foo.controllerB.actionC',
|
||||
api: 'api::foo',
|
||||
controller: 'controllerB',
|
||||
action: 'actionC',
|
||||
},
|
||||
{
|
||||
uid: 'api::foo.controllerB.actionD',
|
||||
api: 'api::foo',
|
||||
controller: 'controllerB',
|
||||
action: 'actionD',
|
||||
},
|
||||
{
|
||||
uid: 'plugin::foo.controllerA.actionA',
|
||||
api: 'plugin::foo',
|
||||
controller: 'controllerA',
|
||||
action: 'actionA',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('Call registerActions twice should throw a duplicate error', async () => {
|
||||
const contentAPI = createContentAPI(global.strapi);
|
||||
|
||||
await contentAPI.permissions.registerActions();
|
||||
|
||||
expect(() => contentAPI.permissions.registerActions()).rejects.toThrowError(
|
||||
'Duplicated item key: api::foo.controllerA.actionA'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Providers', () => {
|
||||
test('You should not be able to register action once strapi is loaded', () => {
|
||||
global.strapi.isLoaded = true;
|
||||
|
||||
const contentAPI = createContentAPI(global.strapi);
|
||||
|
||||
// Actions
|
||||
expect(() =>
|
||||
contentAPI.permissions.providers.action.register('foo', {})
|
||||
).rejects.toThrowError(`You can't register new actions outside the bootstrap function.`);
|
||||
|
||||
// Conditions
|
||||
expect(() =>
|
||||
contentAPI.permissions.providers.condition.register({ name: 'myCondition' })
|
||||
).rejects.toThrowError(`You can't register new conditions outside the bootstrap function.`);
|
||||
|
||||
// Register Actions
|
||||
expect(() => contentAPI.permissions.registerActions()).rejects.toThrowError(
|
||||
`You can't register new actions outside the bootstrap function.`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Engine', () => {
|
||||
test('Engine warns when registering an unknown action', async () => {
|
||||
global.strapi = {
|
||||
log: {
|
||||
debug: jest.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
const contentAPI = createContentAPI();
|
||||
|
||||
const ability = await contentAPI.permissions.engine.generateAbility([{ action: 'foo' }]);
|
||||
|
||||
expect(ability.rules).toHaveLength(0);
|
||||
expect(global.strapi.log.debug).toHaveBeenCalledWith(
|
||||
`Unknown action "foo" supplied when registering a new permission`
|
||||
);
|
||||
});
|
||||
|
||||
test('Engine filter out invalid action when generating an ability', async () => {
|
||||
global.strapi = {
|
||||
log: {
|
||||
debug: jest.fn(),
|
||||
},
|
||||
|
||||
api: {
|
||||
foo: {
|
||||
controllers: {
|
||||
bar: { foobar: bindToContentAPI(() => {}) },
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const contentAPI = createContentAPI(global.strapi);
|
||||
|
||||
await contentAPI.permissions.registerActions();
|
||||
|
||||
const ability = await contentAPI.permissions.engine.generateAbility([
|
||||
{ action: 'foo' },
|
||||
{ action: 'api::foo.bar.foobar' },
|
||||
]);
|
||||
|
||||
expect(ability.rules).toHaveLength(1);
|
||||
expect(ability.rules).toEqual([
|
||||
{
|
||||
action: 'api::foo.bar.foobar',
|
||||
subject: 'all',
|
||||
},
|
||||
]);
|
||||
|
||||
expect(global.strapi.log.debug).toHaveBeenCalledTimes(1);
|
||||
expect(global.strapi.log.debug).toHaveBeenCalledWith(
|
||||
`Unknown action "foo" supplied when registering a new permission`
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -1,65 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const { uniq } = require('lodash');
|
||||
const _ = require('lodash');
|
||||
const permissions = require('./permissions');
|
||||
/**
|
||||
* Create a content API container that holds logic, tools and utils. (eg: permissions, ...)
|
||||
*/
|
||||
const createContentAPI = (/* strapi */) => {
|
||||
const syncActions = async () => {
|
||||
/**
|
||||
* NOTE: For some reason, this doesn't seem to be necessary because all the routes exist
|
||||
* createActionProvider uses a providerFactory, which seems to already include everything, and when we try
|
||||
* to register our actions we get an error that the keys already exist
|
||||
* Could providerFactory not be providing a new provider, and instead sharing the registry with everything that uses it?
|
||||
*
|
||||
* If this isn't an issue to fix and is expected, we don't need the route registration code below and it should be removed
|
||||
* */
|
||||
// Start of route registration
|
||||
const apiRoutesName = Object.values(strapi.api)
|
||||
.map((api) => api.routes)
|
||||
.reduce((acc, routesMap) => {
|
||||
const routes = Object.values(routesMap)
|
||||
// Only content api routes
|
||||
.filter((p) => p.type === 'content-api')
|
||||
// Resolve every handler name for each route
|
||||
.reduce((a, p) => a.concat(p.routes.map((i) => i.handler)), []);
|
||||
return acc.concat(routes);
|
||||
}, []);
|
||||
const pluginsRoutesname = Object.values(strapi.plugins)
|
||||
.map((plugin) => plugin.routes['content-api'] || {})
|
||||
.map((p) => (p.routes || []).map((i) => i.handler))
|
||||
.flat();
|
||||
const actions = apiRoutesName.concat(pluginsRoutesname);
|
||||
Promise.all(
|
||||
uniq(actions).map((action) =>
|
||||
providers.action.register(action).catch(() => {
|
||||
// console.log('Key already exists', action);
|
||||
})
|
||||
)
|
||||
);
|
||||
};
|
||||
// End of route registration
|
||||
|
||||
// Add providers
|
||||
const providers = {
|
||||
action: permissions.providers.createActionProvider(),
|
||||
condition: permissions.providers.createConditionProvider(),
|
||||
};
|
||||
|
||||
// create permission engine
|
||||
const engine = permissions
|
||||
.createPermissionEngine({ providers })
|
||||
.on('before-format::validate.permission', createValidatePermissionHandler(providers.action));
|
||||
|
||||
return {
|
||||
permissions: {
|
||||
engine,
|
||||
providers,
|
||||
syncActions,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates an handler which check that the permission's action exists in the action registry
|
||||
@ -78,4 +20,96 @@ const createValidatePermissionHandler =
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a content API container that holds logic, tools and utils. (eg: permissions, ...)
|
||||
*/
|
||||
const createContentAPI = (strapi) => {
|
||||
// Add providers
|
||||
const providers = {
|
||||
action: permissions.providers.createActionProvider(),
|
||||
condition: permissions.providers.createConditionProvider(),
|
||||
};
|
||||
|
||||
const getActionsMap = () => {
|
||||
const actionMap = {};
|
||||
|
||||
const isContentApi = (action) => {
|
||||
if (!_.has(action, Symbol.for('__type__'))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return action[Symbol.for('__type__')].includes('content-api');
|
||||
};
|
||||
|
||||
const registerAPIsActions = (apis, source) => {
|
||||
_.forEach(apis, (api, apiName) => {
|
||||
const controllers = _.reduce(
|
||||
api.controllers,
|
||||
(acc, controller, controllerName) => {
|
||||
const contentApiActions = _.pickBy(controller, isContentApi);
|
||||
|
||||
if (_.isEmpty(contentApiActions)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc[controllerName] = Object.keys(contentApiActions);
|
||||
|
||||
return acc;
|
||||
},
|
||||
{}
|
||||
);
|
||||
|
||||
if (!_.isEmpty(controllers)) {
|
||||
actionMap[`${source}::${apiName}`] = { controllers };
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
registerAPIsActions(strapi.api, 'api');
|
||||
registerAPIsActions(strapi.plugins, 'plugin');
|
||||
|
||||
return actionMap;
|
||||
};
|
||||
|
||||
const registerActions = async () => {
|
||||
const actionsMap = getActionsMap();
|
||||
|
||||
// For each API
|
||||
for (const [api, value] of Object.entries(actionsMap)) {
|
||||
const { controllers } = value;
|
||||
|
||||
// Register controllers methods as actions
|
||||
for (const [controller, actions] of Object.entries(controllers)) {
|
||||
// Register each action individually
|
||||
await Promise.all(
|
||||
actions.map((action) => {
|
||||
const actionUID = `${api}.${controller}.${action}`;
|
||||
|
||||
return providers.action.register(actionUID, {
|
||||
api,
|
||||
controller,
|
||||
action,
|
||||
uid: actionUID,
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// create permission engine
|
||||
const engine = permissions
|
||||
.createPermissionEngine({ providers })
|
||||
.on('before-format::validate.permission', createValidatePermissionHandler(providers.action));
|
||||
|
||||
return {
|
||||
permissions: {
|
||||
engine,
|
||||
providers,
|
||||
registerActions,
|
||||
getActionsMap,
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
module.exports = createContentAPI;
|
||||
|
||||
@ -8,12 +8,12 @@ module.exports = (options = {}) => {
|
||||
return {
|
||||
...provider,
|
||||
|
||||
async register(action) {
|
||||
async register(action, payload) {
|
||||
if (strapi.isLoaded) {
|
||||
throw new Error(`You can't register new actions outside the bootstrap function.`);
|
||||
}
|
||||
|
||||
return provider.register(action, { name: action });
|
||||
return provider.register(action, payload);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@ -171,10 +171,6 @@ module.exports = ({ strapi }) => ({
|
||||
|
||||
const toDelete = _.difference(permissionsFoundInDB, allActions);
|
||||
|
||||
// Register actions into the content API action provider
|
||||
// TODO: do this in the content API bootstrap phase instead
|
||||
allActions.forEach((action) => strapi.contentAPI.permissions.providers.action.register(action));
|
||||
|
||||
await Promise.all(
|
||||
toDelete.map((action) => {
|
||||
return strapi.query('plugin::users-permissions.permission').delete({ where: { action } });
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user