0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-02-03 23:00:14 -05:00

Added the Admin API and tests for Personal Tokens.

no issue
This commit is contained in:
Thibaut Patel 2020-10-26 17:27:12 +01:00
parent be4146e324
commit 4edccfd2f7
5 changed files with 134 additions and 1 deletions

View file

@ -8,6 +8,30 @@ const permissionsService = require('../../services/permissions');
const ALLOWED_INCLUDES = ['count.posts', 'permissions', 'roles', 'roles.permissions']; const ALLOWED_INCLUDES = ['count.posts', 'permissions', 'roles', 'roles.permissions'];
const UNSAFE_ATTRS = ['status', 'roles']; const UNSAFE_ATTRS = ['status', 'roles'];
function permissionOnlySelf(frame) {
const targetId = getTargetId(frame);
const userId = frame.user.id;
if (targetId !== userId) {
return Promise.reject(new errors.NoPermissionError({message: i18n.t('errors.permissions.noPermissionToAction')}));
}
return Promise.resolve();
}
function getTargetId(frame) {
return frame.options.id === 'me' ? frame.user.id : frame.options.id;
}
async function fetchOrCreatePersonalToken(userId) {
const token = await models.ApiKey.findOne({user_id: userId}, {});
if (!token) {
const newToken = await models.ApiKey.add({user_id: userId, type: 'admin'});
return newToken;
}
return token;
}
module.exports = { module.exports = {
docName: 'users', docName: 'users',
@ -176,5 +200,46 @@ module.exports = {
query(frame) { query(frame) {
return models.User.transferOwnership(frame.data.owner[0], frame.options); return models.User.transferOwnership(frame.data.owner[0], frame.options);
} }
},
readToken: {
options: [
'id'
],
validation: {
options: {
id: {
required: true
}
}
},
permissions: permissionOnlySelf,
query(frame) {
const targetId = getTargetId(frame);
return fetchOrCreatePersonalToken(targetId);
}
},
regenerateToken: {
headers: {
cacheInvalidate: true
},
options: [
'id'
],
validation: {
options: {
id: {
required: true
}
}
},
permissions: permissionOnlySelf,
query(frame) {
const targetId = getTargetId(frame);
return fetchOrCreatePersonalToken(targetId).then((model) => {
return models.ApiKey.refreshSecret(model.toJSON(), Object.assign({}, {id: model.id}));
});
}
} }
}; };

View file

@ -49,5 +49,22 @@ module.exports = {
frame.response = { frame.response = {
users: models.map(model => model.toJSON(frame.options)) users: models.map(model => model.toJSON(frame.options))
}; };
},
readToken(model, apiConfig, frame) {
debug('readToken');
frame.response = {
apiKey: model.toJSON(frame.options)
};
},
regenerateToken(model, apiConfig, frame) {
debug('regenerateToken');
frame.response = {
apiKey: model.toJSON(frame.options)
};
} }
}; };

View file

@ -71,10 +71,12 @@ module.exports = function apiRoutes() {
router.get('/users/slug/:slug', mw.authAdminApi, http(apiCanary.users.read)); router.get('/users/slug/:slug', mw.authAdminApi, http(apiCanary.users.read));
// NOTE: We don't expose any email addresses via the public api. // NOTE: We don't expose any email addresses via the public api.
router.get('/users/email/:email', mw.authAdminApi, http(apiCanary.users.read)); router.get('/users/email/:email', mw.authAdminApi, http(apiCanary.users.read));
router.get('/users/:id/token', mw.authAdminApi, http(apiCanary.users.readToken));
router.put('/users/password', mw.authAdminApi, http(apiCanary.users.changePassword)); router.put('/users/password', mw.authAdminApi, http(apiCanary.users.changePassword));
router.put('/users/owner', mw.authAdminApi, http(apiCanary.users.transferOwnership)); router.put('/users/owner', mw.authAdminApi, http(apiCanary.users.transferOwnership));
router.put('/users/:id', mw.authAdminApi, http(apiCanary.users.edit)); router.put('/users/:id', mw.authAdminApi, http(apiCanary.users.edit));
router.put('/users/:id/token', mw.authAdminApi, http(apiCanary.users.regenerateToken));
router.del('/users/:id', mw.authAdminApi, http(apiCanary.users.destroy)); router.del('/users/:id', mw.authAdminApi, http(apiCanary.users.destroy));
// ## Tags // ## Tags

View file

@ -334,4 +334,53 @@ describe('User API', function () {
.set('Origin', config.get('url')) .set('Origin', config.get('url'))
.expect(200); .expect(200);
}); });
it('Can read the user\'s Personal Token', async function () {
await request
.get(localUtils.API.getApiQuery('users/me/token/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
should.exist(res.body.apiKey);
should.exist(res.body.apiKey.id);
should.exist(res.body.apiKey.secret);
});
});
it('Can\'t read another user\'s Personal Token', async function () {
const userNotAdmin = testUtils.existingData.users.find(user => user.email === 'ghost-author@example.com');
await request
.get(localUtils.API.getApiQuery('users/' + userNotAdmin.id + '/token/'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403)
.then((res) => {
should.exist(res.body.errors);
});
});
it('Can re-generate the user\'s Personal Token', async function () {
const {body: {apiKey: {id, secret}}} = await request
.get(localUtils.API.getApiQuery('users/me/token/'))
.set('Origin', config.get('url'))
.expect(200);
await request
.put(localUtils.API.getApiQuery('users/me/token'))
.set('Origin', config.get('url'))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200)
.then((res) => {
should.exist(res.body.apiKey);
should.exist(res.body.apiKey.id);
should.exist(res.body.apiKey.secret);
should(res.body.id).not.be.equal(id);
should(res.body.secret).not.be.equal(secret);
});
});
}); });

View file

@ -32,7 +32,7 @@ const defaultSettings = require('../../../../core/server/data/schema/default-set
*/ */
describe('DB version integrity', function () { describe('DB version integrity', function () {
// Only these variables should need updating // Only these variables should need updating
const currentSchemaHash = 'cd8820283c865acf610f97252addfa99'; const currentSchemaHash = '6a5b00d987f045f364c6581082cd0b03';
const currentFixturesHash = 'd46d696c94d03e41a5903500547fea77'; const currentFixturesHash = 'd46d696c94d03e41a5903500547fea77';
const currentSettingsHash = 'b7c0c2c6a4c61561dfefe642470d30f8'; const currentSettingsHash = 'b7c0c2c6a4c61561dfefe642470d30f8';
const currentRoutesHash = '3d180d52c663d173a6be791ef411ed01'; const currentRoutesHash = '3d180d52c663d173a6be791ef411ed01';