diff --git a/api/v1/controllers/EndpointController.js b/api/v1/controllers/EndpointController.js new file mode 100644 index 0000000..9cd6255 --- /dev/null +++ b/api/v1/controllers/EndpointController.js @@ -0,0 +1,43 @@ +const bodyParser = require('body-parser'); +const router = require('express').Router(); + +const EndpointAccessRequest = require('../requests/EndpointAccessRequest'); +const middleware = require('../middleware'); +const roles = require('../utils/roles'); + +const EndpointService = require('../services/EndpointService'); + +function modifyEndpointAccess(req, res, next) { + EndpointService.modifyEndpointAccess(req.body.endpoint, req.body.enabled); + + res.body = req.body; + + return next(); +} + +function getEndpointAccess(req, res, next) { + res.body = {}; + res.body.endpoint = req.query.endpoint; + EndpointService.getEndpointAccess(req.query.endpoint, + () => { + res.body.enabled = true; + next(); + }, + () => { + res.body.enabled = false; + next(); + } + ) +} + + +router.use(bodyParser.json()); +router.use(middleware.auth); + +router.post('/', middleware.request(EndpointAccessRequest), middleware.permission(roles.ADMIN), modifyEndpointAccess); +router.get('/', middleware.permission(roles.ADMIN), getEndpointAccess); + +router.use(middleware.response); +router.use(middleware.errors); + +module.exports.router = router; diff --git a/api/v1/controllers/index.js b/api/v1/controllers/index.js index 3d641fa..3795bd8 100644 --- a/api/v1/controllers/index.js +++ b/api/v1/controllers/index.js @@ -2,6 +2,7 @@ module.exports = { AnnouncementController: require('./AnnouncementController.js'), AuthController: require('./AuthController.js'), EcosystemController: require('./EcosystemController.js'), + EndpointController: require('./EndpointController.js'), EventController: require('./EventController.js'), UploadController: require('./UploadController.js'), UserController: require('./UserController.js'), diff --git a/api/v1/errors/EndpointNotAvailableError.js b/api/v1/errors/EndpointNotAvailableError.js new file mode 100644 index 0000000..edbaeb2 --- /dev/null +++ b/api/v1/errors/EndpointNotAvailableError.js @@ -0,0 +1,22 @@ +const ApiError = require('./ApiError'); + +const ERROR_TYPE = 'EndpointNotAvailableError'; +const ERROR_TITLE = 'Endpoint Not Available'; +const STATUS_CODE = 400; + +const DEFAULT_MESSAGE = 'This endpoint has been disabled'; + +function EndpointNotAvailableError(message, source) { + ApiError.call(this, message, source); + + this.type = ERROR_TYPE; + this.status = STATUS_CODE; + this.title = ERROR_TITLE; + this.message = (message) ? message : DEFAULT_MESSAGE; + this.source = (source) ? source : null; +} + +EndpointNotAvailableError.prototype = Object.create(ApiError.prototype); +EndpointNotAvailableError.prototype.constructor = EndpointNotAvailableError; + +module.exports = EndpointNotAvailableError; diff --git a/api/v1/errors/index.js b/api/v1/errors/index.js index 06b3977..048dd65 100644 --- a/api/v1/errors/index.js +++ b/api/v1/errors/index.js @@ -14,5 +14,6 @@ module.exports = { TokenExpirationError: require('./TokenExpirationError.js'), InvalidTrackingStateError: require('./InvalidTrackingStateError'), UnauthorizedError: require('./UnauthorizedError.js'), - UnprocessableRequestError: require('./UnprocessableRequestError.js') + UnprocessableRequestError: require('./UnprocessableRequestError.js'), + EndpointNotAvailableError: require('./EndpointNotAvailableError.js') }; diff --git a/api/v1/index.js b/api/v1/index.js index dc90dd4..a9ce88e 100644 --- a/api/v1/index.js +++ b/api/v1/index.js @@ -12,6 +12,7 @@ v1.use((req, res, next) => { }); v1.use(middleware.ratelimiting); +v1.use(middleware.enabled); // set up CORS to allow for usage from different origins // we may remove this in the future @@ -37,6 +38,7 @@ v1.use('/stats', controllers.StatsController.router); v1.use('/tracking', controllers.TrackingController.router); v1.use('/mail', controllers.MailController.router); v1.use('/event', controllers.EventController.router); +v1.use('/endpointaccess', controllers.EndpointController.router); // logs resolved requests (the request once processed by various middleware) and outgoing responses v1.use((req, res, next) => { diff --git a/api/v1/middleware/enabled.js b/api/v1/middleware/enabled.js new file mode 100644 index 0000000..fe9d91e --- /dev/null +++ b/api/v1/middleware/enabled.js @@ -0,0 +1,23 @@ +const errors = require('../errors'); +const cache = require('../../cache').instance(); + +const Endpoint = require('../models/Endpoint'); + +const url = require('url'); + +const EndpointService = require('../services/EndpointService'); + +module.exports = (req, res, next) => { + const endpointPath = url.parse(req.url).pathname; + + console.log('endpt is ' + endpointPath); + + EndpointService.getEndpointAccess(endpointPath, + () => { + return next(); + }, + () => { + return next(new errors.EndpointNotAvailableError()); + } + ); +}; \ No newline at end of file diff --git a/api/v1/middleware/index.js b/api/v1/middleware/index.js index 651490e..f50ab0f 100644 --- a/api/v1/middleware/index.js +++ b/api/v1/middleware/index.js @@ -5,5 +5,6 @@ module.exports = { response: require('./response.js'), request: require('./request.js'), upload: require('./upload.js'), - ratelimiting: require('./ratelimiting.js') + ratelimiting: require('./ratelimiting.js'), + enabled: require('./enabled.js') }; diff --git a/api/v1/models/Endpoint.js b/api/v1/models/Endpoint.js new file mode 100644 index 0000000..6ff873f --- /dev/null +++ b/api/v1/models/Endpoint.js @@ -0,0 +1,12 @@ +const Model = require('./Model'); + +const Endpoint = Model.extend({ + tableName: 'endpoints', + idAttribute: 'endpoint', + validations: { + endpoint: ['required', 'string', 'maxLength:100'], + enabled: ['required', 'boolean'] + } +}); + +module.exports = Endpoint; \ No newline at end of file diff --git a/api/v1/models/index.js b/api/v1/models/index.js index 7ac36c5..3ce1d25 100644 --- a/api/v1/models/index.js +++ b/api/v1/models/index.js @@ -19,5 +19,6 @@ module.exports = { Mentor: require('./Mentor'), MentorProjectIdea: require('./MentorProjectIdea'), UniversalTrackingItem: require('./TrackingEvent'), - NetworkCredential: require('./NetworkCredential') + NetworkCredential: require('./NetworkCredential'), + Endpoint: require('./Endpoint') }; diff --git a/api/v1/requests/EndpointAccessRequest.js b/api/v1/requests/EndpointAccessRequest.js new file mode 100644 index 0000000..500c54e --- /dev/null +++ b/api/v1/requests/EndpointAccessRequest.js @@ -0,0 +1,21 @@ +const Request = require('./Request'); + +const bodyRequired = [ 'endpoint' ]; +const bodyAllowed = [ 'enabled' ]; +const bodyValidations = { + 'endpoint': ['required', 'string'], + 'enabled': [ 'boolean' ] +}; + +function EndpointAccessRequest(headers, body) { + Request.call(this, headers, body); + + this.bodyRequired = bodyRequired; + this.bodyAllowed = bodyAllowed; + this.bodyValidations = bodyValidations; +} + +EndpointAccessRequest.prototype = Object.create(Request.prototype); +EndpointAccessRequest.prototype.constructor = EndpointAccessRequest; + +module.exports = EndpointAccessRequest; diff --git a/api/v1/requests/index.js b/api/v1/requests/index.js index 1423bb5..ca93ef4 100644 --- a/api/v1/requests/index.js +++ b/api/v1/requests/index.js @@ -5,6 +5,7 @@ module.exports = { AccreditedUserCreationRequest: require('./AccreditedUserCreationRequest'), BasicAuthRequest: require('./BasicAuthRequest'), EcosystemCreationRequest: require('./EcosystemCreationRequest'), + EndpointAccessRequest: require('./EndpointAccessRequest'), EventCreationRequest: require('./EventCreationRequest'), LocationCreationRequest: require('./LocationCreationRequest'), MentorRequest: require('./MentorRequest'), diff --git a/api/v1/services/EndpointService.js b/api/v1/services/EndpointService.js new file mode 100644 index 0000000..c979a06 --- /dev/null +++ b/api/v1/services/EndpointService.js @@ -0,0 +1,51 @@ +const Endpoint = require('../models/Endpoint'); + +const cache = require('../../cache').instance(); + +const logger = require('../../logging'); + +function modifyEndpointAccess(reqEndpoint, reqEnabled) { + // Write enabled / disabled state to database + Endpoint.query({where: {endpoint: reqEndpoint}}).fetch().then((endpointModel) => { + const methodType = ((endpointModel == null) ? 'insert' : 'update'); + Endpoint.forge({ + endpoint: reqEndpoint, + enabled: reqEnabled + }).save(null, {method: methodType}); + // Write enabled / disabled state to cache + cache.set(reqEndpoint, reqEnabled); + }); + + // Log the endpoint access change here + logger.debug('EndpointStateChanged: %s is %s.', + reqEndpoint, + reqEnabled ? 'enabled' : 'disabled' + ); +} + +function getEndpointAccess(reqEndpoint, onEnabled, onDisabled) { + return cache.get(reqEndpoint, (err, reply) => { + console.log('reply is ' + reply); + if (reply == 'true') { + onEnabled(); + return; + } else if (reply == 'false') { + console.log('good'); + onDisabled(); + return; + } + Endpoint.query({where: {endpoint: reqEndpoint}}).fetch().then((endpointModel) => { + if (endpointModel == null || endpointModel.attributes.enabled[0] == 1) { + cache.set(reqEndpoint, 'true'); + onEnabled(); + return; + } + cache.set(reqEndpoint, 'false'); + onDisabled(); + return; + }); + }); +} + +module.exports.modifyEndpointAccess = modifyEndpointAccess; +module.exports.getEndpointAccess = getEndpointAccess; diff --git a/api/v1/services/index.js b/api/v1/services/index.js index a61a316..e2fa42d 100644 --- a/api/v1/services/index.js +++ b/api/v1/services/index.js @@ -1,6 +1,7 @@ module.exports = { AuthService: require('./AuthService'), EcosystemService: require('./EcosystemService'), + EndpointService: require('./EndpointService'), EventService: require('./EventService'), MailService: require('./MailService'), PermissionService: require('./PermissionService'), diff --git a/database/migration/V20171121_1645__addEndpointToggle.sql b/database/migration/V20171121_1645__addEndpointToggle.sql new file mode 100644 index 0000000..047aca3 --- /dev/null +++ b/database/migration/V20171121_1645__addEndpointToggle.sql @@ -0,0 +1,5 @@ +CREATE TABLE `endpoints` ( + `endpoint` VARCHAR(50) NOT NULL, + `enabled` BIT(1), + PRIMARY KEY (`endpoint`) +); diff --git a/database/revert/V20171121_1645__addEndpointToggle.revert.sql b/database/revert/V20171121_1645__addEndpointToggle.revert.sql new file mode 100644 index 0000000..d97c9f0 --- /dev/null +++ b/database/revert/V20171121_1645__addEndpointToggle.revert.sql @@ -0,0 +1 @@ +DROP TABLE `endpoints`;