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

Blocked requests from integrations when integration limit is in place

https://github.com/TryGhost/Team/issues/599

- When custom integration limit is enabled all requests from existing integrations should not be accepted. With the exception of internal integrations like backup and scheduler
This commit is contained in:
Naz 2021-04-10 00:45:26 +12:00
parent edd0e26a78
commit cb0807d07a
4 changed files with 89 additions and 4 deletions

View file

@ -2,6 +2,7 @@ const jwt = require('jsonwebtoken');
const url = require('url');
const models = require('../../../models');
const errors = require('@tryghost/errors');
const limitService = require('../../../services/limits');
const {i18n} = require('../../../lib/common');
const _ = require('lodash');
@ -98,7 +99,7 @@ const authenticateWithToken = async (req, res, next, {token, JWT_OPTIONS}) => {
}
try {
const apiKey = await models.ApiKey.findOne({id: apiKeyId});
const apiKey = await models.ApiKey.findOne({id: apiKeyId}, {withRelated: ['integration']});
if (!apiKey) {
return next(new errors.UnauthorizedError({
@ -114,6 +115,14 @@ const authenticateWithToken = async (req, res, next, {token, JWT_OPTIONS}) => {
}));
}
// CASE: blocking all non-internal: "custom" and "builtin" integration requests when the limit is reached
if (limitService.isLimited('customIntegrations')
&& (apiKey.relations.integration && !['internal'].includes(apiKey.relations.integration.get('type')))) {
// NOTE: using "checkWouldGoOverLimit" instead of "checkIsOverLimit" here because flag limits don't have
// a concept of measuring if the limit has been surpassed
await limitService.errorIfWouldGoOverLimit('customIntegrations');
}
// Decoding from hex and transforming into bytes is here to
// keep comparison of the bytes that are stored in the secret.
// Useful context:
@ -162,7 +171,11 @@ const authenticateWithToken = async (req, res, next, {token, JWT_OPTIONS}) => {
next();
} catch (err) {
next(new errors.InternalServerError({err}));
if (err instanceof errors.HostLimitError) {
next(err);
} else {
next(new errors.InternalServerError({err}));
}
}
};

View file

@ -1,5 +1,6 @@
const models = require('../../../models');
const errors = require('@tryghost/errors');
const limitService = require('../../../services/limits');
const {i18n} = require('../../../lib/common');
const authenticateContentApiKey = async function authenticateContentApiKey(req, res, next) {
@ -18,7 +19,7 @@ const authenticateContentApiKey = async function authenticateContentApiKey(req,
let key = req.query.key;
try {
const apiKey = await models.ApiKey.findOne({secret: key});
const apiKey = await models.ApiKey.findOne({secret: key}, {withRelated: ['integration']});
if (!apiKey) {
return next(new errors.UnauthorizedError({
@ -34,12 +35,24 @@ const authenticateContentApiKey = async function authenticateContentApiKey(req,
}));
}
// CASE: blocking all non-internal: "custom" and "builtin" integration requests when the limit is reached
if (limitService.isLimited('customIntegrations')
&& (apiKey.relations.integration && !['internal'].includes(apiKey.relations.integration.get('type')))) {
// NOTE: using "checkWouldGoOverLimit" instead of "checkIsOverLimit" here because flag limits don't have
// a concept of measuring if the limit has been surpassed
await limitService.errorIfWouldGoOverLimit('customIntegrations');
}
// authenticated OK, store the api key on the request for later checks and logging
req.api_key = apiKey;
next();
} catch (err) {
next(new errors.InternalServerError({err}));
if (err instanceof errors.HostLimitError) {
next(err);
} else {
next(new errors.InternalServerError({err}));
}
}
};

View file

@ -3,6 +3,7 @@ const supertest = require('supertest');
const testUtils = require('../../utils');
const config = require('../../../core/shared/config');
const localUtils = require('./utils');
const configUtils = require('../../utils/configUtils');
describe('Admin API key authentication', function () {
let request;
@ -68,4 +69,32 @@ describe('Admin API key authentication', function () {
localUtils.API.checkResponse(res.body.users[0], 'user');
});
describe('Host Settings: custom integration limits', function () {
afterEach(function () {
configUtils.set('hostSettings:limits', undefined);
});
it('Blocks the request when host limit is in place for custom integrations', async function () {
configUtils.set('hostSettings:limits', {
customIntegrations: {
disabled: true,
error: 'Custom limit error message'
}
});
// NOTE: need to do a full reboot to reinitialize hostSettings
await testUtils.startGhost();
await testUtils.initFixtures('api_keys');
const response = await request.get(localUtils.API.getApiQuery('posts/'))
.set('Authorization', `Ghost ${localUtils.getValidAdminToken('/canary/admin/')}`)
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403);
response.body.errors[0].type.should.equal('HostLimitError');
response.body.errors[0].message.should.equal('Custom limit error message');
});
});
});

View file

@ -3,6 +3,7 @@ const supertest = require('supertest');
const testUtils = require('../../utils');
const localUtils = require('./utils');
const config = require('../../../core/shared/config');
const configUtils = require('../../utils/configUtils');
describe('Content API key authentication', function () {
let request;
@ -28,4 +29,33 @@ describe('Content API key authentication', function () {
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(200);
});
describe('Host Settings: custom integration limits', function () {
afterEach(function () {
configUtils.set('hostSettings:limits', undefined);
});
it('Blocks the request when host limit is in place for custom integrations', async function () {
configUtils.set('hostSettings:limits', {
customIntegrations: {
disabled: true,
error: 'Custom limit error message'
}
});
// NOTE: need to do a full reboot to reinitialize hostSettings
await testUtils.startGhost();
await testUtils.initFixtures('api_keys');
const key = localUtils.getValidKey();
const response = await request.get(localUtils.API.getApiQuery(`posts/?key=${key}`))
.expect('Content-Type', /json/)
.expect('Cache-Control', testUtils.cacheRules.private)
.expect(403);
response.body.errors[0].errorType.should.equal('HostLimitError');
response.body.errors[0].message.should.equal('Custom limit error message');
});
});
});