0
Fork 0
mirror of https://github.com/TryGhost/Ghost.git synced 2025-01-06 22:40:14 -05:00

Updated Content API to use members plans to determine permission (#10483)

no-issue


* Refactored hideMembersOnlyContent to 3 "stages"
* Exported paymentConfigured flag from members service
* Updated Content-API to check members service for paymentConfigured
* Updated members content output serializer to remove content if plan required and no plan
* Updated isContentAPI method
* Moved api util test
This commit is contained in:
Fabien O'Carroll 2019-02-14 18:17:02 +01:00 committed by GitHub
parent 896769ee8f
commit 9dd7aff9c6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 84 additions and 73 deletions

View file

@ -16,10 +16,13 @@ module.exports = {
* the whole context object currently: https://github.com/TryGhost/Ghost/issues/10099
*/
isContentAPI: (frame) => {
return !!(Object.keys(frame.options.context).length === 0 ||
(!frame.options.context.user && frame.options.context.api_key && (frame.options.context.api_key.type === 'content')) ||
frame.options.context.public
);
const context = frame.options && frame.options.context || {};
// CASE: An empty context is considered public by the core/server/services/permissions/parse-context.js
// module, replicated here because the context is unparsed until after the permissions layer of the pipeline
const isPublic = context.public || Object.keys(context).length === 0;
// CASE: apiType = 'content' for HTTP Content API
return frame.apiType === 'content' || isPublic;
},
isAdminAPIKey: (frame) => {

View file

@ -1,15 +1,36 @@
const labs = require('../../../../../../services/labs');
const membersService = require('../../../../../../services/members');
const MEMBER_TAG = '#members';
const PERMIT_CONTENT = false;
const BLOCK_CONTENT = true;
// Checks if request should hide memnbers only content
function hideMembersOnlyContent(attrs, frame) {
let hasMemberTag = false;
if (labs.isSet('members') && !frame.original.context.member && attrs.tags) {
hasMemberTag = attrs.tags.find((tag) => {
return (tag.name === MEMBER_TAG);
});
const membersEnabled = labs.isSet('members');
if (!membersEnabled) {
return PERMIT_CONTENT;
}
return hasMemberTag;
const postHasMemberTag = attrs.tags && attrs.tags.find((tag) => {
return (tag.name === MEMBER_TAG);
});
const requestFromMember = frame.original.context.member;
if (!postHasMemberTag) {
return PERMIT_CONTENT;
}
if (!requestFromMember) {
return BLOCK_CONTENT;
}
const planRequired = membersService.api.paymentConfigured;
const memberHasPlan = !!(frame.original.context.member.plans || []).length;
if (!planRequired) {
return PERMIT_CONTENT;
}
if (memberHasPlan) {
return PERMIT_CONTENT;
}
return BLOCK_CONTENT;
}
const forPost = (attrs, frame) => {

View file

@ -128,3 +128,4 @@ const api = MembersApi({
module.exports = api;
module.exports.publicKey = publicKey;
module.exports.paymentConfigured = !!membersConfig.paymentProcessors.length;

View file

@ -1,6 +1,6 @@
const should = require('should');
const sinon = require('sinon');
const utils = require('../../../../server/api/v2/utils');
const utils = require('../../../../../server/api/v2/utils');
describe('Unit: v2/utils/index', function () {
afterEach(function () {
@ -8,46 +8,14 @@ describe('Unit: v2/utils/index', function () {
});
describe('isContentAPI', function () {
it('is truthy when having api key of content type', function () {
it('is true when apiType is "content"', function () {
const frame = {
options: {
context: {
api_key: {
id: 'keyId',
type: 'content'
}
}
}
apiType: 'content'
};
should(utils.isContentAPI(frame)).equal(true);
});
it('is falsy when having api key and a user', function () {
const frame = {
options: {
context: {
user: {},
api_key: {
id: 'keyId',
type: 'content'
}
}
}
};
should(utils.isContentAPI(frame)).equal(false);
});
it('is truthy when context is empty', function () {
const frame = {
options: {
context: {
}
}
};
should(utils.isContentAPI(frame)).equal(true);
});
it('is truthy when context is public', function () {
it('is true when options.context.public is true', function () {
const frame = {
options: {
context: {
@ -57,5 +25,26 @@ describe('Unit: v2/utils/index', function () {
};
should(utils.isContentAPI(frame)).equal(true);
});
it('is true when options.context is empty', function () {
const frame = {
options: {
context: {}
}
};
should(utils.isContentAPI(frame)).equal(true);
});
it('is false when options.context has no public value and apiType is not content', function () {
const frame = {
apiType: 'admin',
options: {
context: {
no: 'public'
}
}
};
should(utils.isContentAPI(frame)).equal(false);
});
});
});

View file

@ -7,6 +7,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
it('default', function () {
const apiConfig = {};
const frame = {
apiType: 'content',
options: {
context: {
user: 0,
@ -25,6 +26,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
it('should not work for non public context', function () {
const apiConfig = {};
const frame = {
apiType: 'admin',
options: {
context: {
user: 1
@ -39,6 +41,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
it('combine filters', function () {
const apiConfig = {};
const frame = {
apiType: 'content',
options: {
context: {
user: 0,
@ -58,6 +61,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
it('combine filters', function () {
const apiConfig = {};
const frame = {
apiType: 'content',
options: {
context: {
user: 0,
@ -77,6 +81,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
it('combine filters', function () {
const apiConfig = {};
const frame = {
apiType: 'content',
options: {
context: {
user: 0,
@ -96,6 +101,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
it('combine filters', function () {
const apiConfig = {};
const frame = {
apiType: 'content',
options: {
context: {
user: 0,
@ -129,18 +135,11 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
});
describe('read', function () {
it('with api_key_id', function () {
it('with apiType of "content" it sets data.page to false', function () {
const apiConfig = {};
const frame = {
options: {
context: {
user: 0,
api_key: {
id: 1,
type: 'content'
},
}
},
apiType: 'content',
options: {},
data: {}
};
@ -148,18 +147,11 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
frame.data.page.should.eql(false);
});
it('with api_key_id: overrides page', function () {
it('with apiType of "content" it overrides data.page to be false', function () {
const apiConfig = {};
const frame = {
options: {
context: {
user: 0,
api_key: {
id: 1,
type: 'content'
},
}
},
apiType: 'content',
options: {},
data: {
status: 'all',
page: true
@ -167,17 +159,19 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
};
serializers.input.posts.read(apiConfig, frame);
frame.data.status.should.eql('all');
frame.data.page.should.eql(false);
});
it('with user', function () {
it('with apiType of "admin" it does not set data.page', function () {
const apiConfig = {};
const frame = {
apiType: 'admin',
options: {
context: {
user: 1,
api_key_id: 0
api_key: {
id: 1,
type: 'admin'
}
}
},
data: {}
@ -187,13 +181,16 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
should.not.exist(frame.data.page);
});
it('with user', function () {
it('with non public request it does not override data.page', function () {
const apiConfig = {};
const frame = {
apiType: 'admin',
options: {
context: {
user: 1,
api_key_id: 0
api_key: {
id: 1,
type: 'admin'
}
}
},
data: {