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:
parent
896769ee8f
commit
9dd7aff9c6
5 changed files with 84 additions and 73 deletions
|
@ -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) => {
|
||||
|
|
|
@ -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) => {
|
||||
|
|
|
@ -128,3 +128,4 @@ const api = MembersApi({
|
|||
|
||||
module.exports = api;
|
||||
module.exports.publicKey = publicKey;
|
||||
module.exports.paymentConfigured = !!membersConfig.paymentProcessors.length;
|
||||
|
|
|
@ -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);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -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: {
|
||||
|
|
Loading…
Reference in a new issue