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
|
* the whole context object currently: https://github.com/TryGhost/Ghost/issues/10099
|
||||||
*/
|
*/
|
||||||
isContentAPI: (frame) => {
|
isContentAPI: (frame) => {
|
||||||
return !!(Object.keys(frame.options.context).length === 0 ||
|
const context = frame.options && frame.options.context || {};
|
||||||
(!frame.options.context.user && frame.options.context.api_key && (frame.options.context.api_key.type === 'content')) ||
|
// CASE: An empty context is considered public by the core/server/services/permissions/parse-context.js
|
||||||
frame.options.context.public
|
// 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) => {
|
isAdminAPIKey: (frame) => {
|
||||||
|
|
|
@ -1,15 +1,36 @@
|
||||||
const labs = require('../../../../../../services/labs');
|
const labs = require('../../../../../../services/labs');
|
||||||
|
const membersService = require('../../../../../../services/members');
|
||||||
const MEMBER_TAG = '#members';
|
const MEMBER_TAG = '#members';
|
||||||
|
const PERMIT_CONTENT = false;
|
||||||
|
const BLOCK_CONTENT = true;
|
||||||
|
|
||||||
// Checks if request should hide memnbers only content
|
// Checks if request should hide memnbers only content
|
||||||
function hideMembersOnlyContent(attrs, frame) {
|
function hideMembersOnlyContent(attrs, frame) {
|
||||||
let hasMemberTag = false;
|
const membersEnabled = labs.isSet('members');
|
||||||
if (labs.isSet('members') && !frame.original.context.member && attrs.tags) {
|
if (!membersEnabled) {
|
||||||
hasMemberTag = attrs.tags.find((tag) => {
|
return PERMIT_CONTENT;
|
||||||
return (tag.name === MEMBER_TAG);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
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) => {
|
const forPost = (attrs, frame) => {
|
||||||
|
|
|
@ -128,3 +128,4 @@ const api = MembersApi({
|
||||||
|
|
||||||
module.exports = api;
|
module.exports = api;
|
||||||
module.exports.publicKey = publicKey;
|
module.exports.publicKey = publicKey;
|
||||||
|
module.exports.paymentConfigured = !!membersConfig.paymentProcessors.length;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
const should = require('should');
|
const should = require('should');
|
||||||
const sinon = require('sinon');
|
const sinon = require('sinon');
|
||||||
const utils = require('../../../../server/api/v2/utils');
|
const utils = require('../../../../../server/api/v2/utils');
|
||||||
|
|
||||||
describe('Unit: v2/utils/index', function () {
|
describe('Unit: v2/utils/index', function () {
|
||||||
afterEach(function () {
|
afterEach(function () {
|
||||||
|
@ -8,46 +8,14 @@ describe('Unit: v2/utils/index', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('isContentAPI', 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 = {
|
const frame = {
|
||||||
options: {
|
apiType: 'content'
|
||||||
context: {
|
|
||||||
api_key: {
|
|
||||||
id: 'keyId',
|
|
||||||
type: 'content'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
should(utils.isContentAPI(frame)).equal(true);
|
should(utils.isContentAPI(frame)).equal(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('is falsy when having api key and a user', function () {
|
it('is true when options.context.public is true', 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 () {
|
|
||||||
const frame = {
|
const frame = {
|
||||||
options: {
|
options: {
|
||||||
context: {
|
context: {
|
||||||
|
@ -57,5 +25,26 @@ describe('Unit: v2/utils/index', function () {
|
||||||
};
|
};
|
||||||
should(utils.isContentAPI(frame)).equal(true);
|
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 () {
|
it('default', function () {
|
||||||
const apiConfig = {};
|
const apiConfig = {};
|
||||||
const frame = {
|
const frame = {
|
||||||
|
apiType: 'content',
|
||||||
options: {
|
options: {
|
||||||
context: {
|
context: {
|
||||||
user: 0,
|
user: 0,
|
||||||
|
@ -25,6 +26,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
||||||
it('should not work for non public context', function () {
|
it('should not work for non public context', function () {
|
||||||
const apiConfig = {};
|
const apiConfig = {};
|
||||||
const frame = {
|
const frame = {
|
||||||
|
apiType: 'admin',
|
||||||
options: {
|
options: {
|
||||||
context: {
|
context: {
|
||||||
user: 1
|
user: 1
|
||||||
|
@ -39,6 +41,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
||||||
it('combine filters', function () {
|
it('combine filters', function () {
|
||||||
const apiConfig = {};
|
const apiConfig = {};
|
||||||
const frame = {
|
const frame = {
|
||||||
|
apiType: 'content',
|
||||||
options: {
|
options: {
|
||||||
context: {
|
context: {
|
||||||
user: 0,
|
user: 0,
|
||||||
|
@ -58,6 +61,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
||||||
it('combine filters', function () {
|
it('combine filters', function () {
|
||||||
const apiConfig = {};
|
const apiConfig = {};
|
||||||
const frame = {
|
const frame = {
|
||||||
|
apiType: 'content',
|
||||||
options: {
|
options: {
|
||||||
context: {
|
context: {
|
||||||
user: 0,
|
user: 0,
|
||||||
|
@ -77,6 +81,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
||||||
it('combine filters', function () {
|
it('combine filters', function () {
|
||||||
const apiConfig = {};
|
const apiConfig = {};
|
||||||
const frame = {
|
const frame = {
|
||||||
|
apiType: 'content',
|
||||||
options: {
|
options: {
|
||||||
context: {
|
context: {
|
||||||
user: 0,
|
user: 0,
|
||||||
|
@ -96,6 +101,7 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
||||||
it('combine filters', function () {
|
it('combine filters', function () {
|
||||||
const apiConfig = {};
|
const apiConfig = {};
|
||||||
const frame = {
|
const frame = {
|
||||||
|
apiType: 'content',
|
||||||
options: {
|
options: {
|
||||||
context: {
|
context: {
|
||||||
user: 0,
|
user: 0,
|
||||||
|
@ -129,18 +135,11 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('read', 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 apiConfig = {};
|
||||||
const frame = {
|
const frame = {
|
||||||
options: {
|
apiType: 'content',
|
||||||
context: {
|
options: {},
|
||||||
user: 0,
|
|
||||||
api_key: {
|
|
||||||
id: 1,
|
|
||||||
type: 'content'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: {}
|
data: {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -148,18 +147,11 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
||||||
frame.data.page.should.eql(false);
|
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 apiConfig = {};
|
||||||
const frame = {
|
const frame = {
|
||||||
options: {
|
apiType: 'content',
|
||||||
context: {
|
options: {},
|
||||||
user: 0,
|
|
||||||
api_key: {
|
|
||||||
id: 1,
|
|
||||||
type: 'content'
|
|
||||||
},
|
|
||||||
}
|
|
||||||
},
|
|
||||||
data: {
|
data: {
|
||||||
status: 'all',
|
status: 'all',
|
||||||
page: true
|
page: true
|
||||||
|
@ -167,17 +159,19 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
serializers.input.posts.read(apiConfig, frame);
|
serializers.input.posts.read(apiConfig, frame);
|
||||||
frame.data.status.should.eql('all');
|
|
||||||
frame.data.page.should.eql(false);
|
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 apiConfig = {};
|
||||||
const frame = {
|
const frame = {
|
||||||
|
apiType: 'admin',
|
||||||
options: {
|
options: {
|
||||||
context: {
|
context: {
|
||||||
user: 1,
|
api_key: {
|
||||||
api_key_id: 0
|
id: 1,
|
||||||
|
type: 'admin'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data: {}
|
data: {}
|
||||||
|
@ -187,13 +181,16 @@ describe('Unit: v2/utils/serializers/input/posts', function () {
|
||||||
should.not.exist(frame.data.page);
|
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 apiConfig = {};
|
||||||
const frame = {
|
const frame = {
|
||||||
|
apiType: 'admin',
|
||||||
options: {
|
options: {
|
||||||
context: {
|
context: {
|
||||||
user: 1,
|
api_key: {
|
||||||
api_key_id: 0
|
id: 1,
|
||||||
|
type: 'admin'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
data: {
|
data: {
|
||||||
|
|
Loading…
Reference in a new issue