diff --git a/core/client/app/authenticators/oauth2.js b/core/client/app/authenticators/oauth2.js index ada73d8eda..49bf6bf77d 100644 --- a/core/client/app/authenticators/oauth2.js +++ b/core/client/app/authenticators/oauth2.js @@ -1,8 +1,11 @@ +import Ember from 'ember'; import Authenticator from 'simple-auth-oauth2/authenticators/oauth2'; export default Authenticator.extend({ + config: Ember.inject.service(), makeRequest: function (url, data) { - data.client_id = 'ghost-admin'; + data.client_id = this.get('config.clientId'); + data.client_secret = this.get('config.clientSecret'); return this._super(url, data); } }); diff --git a/core/server/api/clients.js b/core/server/api/clients.js new file mode 100644 index 0000000000..6bce698130 --- /dev/null +++ b/core/server/api/clients.js @@ -0,0 +1,60 @@ +// # Client API +// RESTful API for the Client resource +var Promise = require('bluebird'), + _ = require('lodash'), + dataProvider = require('../models'), + errors = require('../errors'), + utils = require('./utils'), + pipeline = require('../utils/pipeline'), + + docName = 'clients', + clients; + +/** + * ### Clients API Methods + * + * **See:** [API Methods](index.js.html#api%20methods) + */ +clients = { + + /** + * ## Read + * @param {{id}} options + * @return {Promise} Client + */ + read: function read(options) { + var attrs = ['id', 'slug'], + tasks; + + /** + * ### Model Query + * Make the call to the Model layer + * @param {Object} options + * @returns {Object} options + */ + function doQuery(options) { + // only User Agent (type = `ua`) clients are available at the moment. + options.data = _.extend(options.data, {type: 'ua'}); + return dataProvider.Client.findOne(options.data, _.omit(options, ['data'])); + } + + // Push all of our tasks into a `tasks` array in the correct order + tasks = [ + utils.validate(docName, {attrs: attrs}), + // TODO: add permissions + // utils.handlePublicPermissions(docName, 'read'), + doQuery + ]; + + // Pipeline calls each task passing the result of one to be the arguments for the next + return pipeline(tasks, options).then(function formatResponse(result) { + if (result) { + return {clients: [result.toJSON(options)]}; + } + + return Promise.reject(new errors.NotFoundError('Client not found.')); + }); + } +}; + +module.exports = clients; diff --git a/core/server/api/index.js b/core/server/api/index.js index 3d3ec2c6ee..d117d01c75 100644 --- a/core/server/api/index.js +++ b/core/server/api/index.js @@ -15,6 +15,7 @@ var _ = require('lodash'), roles = require('./roles'), settings = require('./settings'), tags = require('./tags'), + clients = require('./clients'), themes = require('./themes'), users = require('./users'), slugs = require('./slugs'), @@ -233,6 +234,7 @@ module.exports = { roles: roles, settings: settings, tags: tags, + clients: clients, themes: themes, users: users, slugs: slugs, diff --git a/core/server/api/tags.js b/core/server/api/tags.js index 15dfbbe0c1..43dd38067e 100644 --- a/core/server/api/tags.js +++ b/core/server/api/tags.js @@ -5,7 +5,7 @@ var Promise = require('bluebird'), dataProvider = require('../models'), errors = require('../errors'), utils = require('./utils'), - pipeline = require('../utils/pipeline'), + pipeline = require('../utils/pipeline'), docName = 'tags', allowedIncludes = ['post_count'], diff --git a/core/server/controllers/admin.js b/core/server/controllers/admin.js index 825dd8ae3c..c9ba330669 100644 --- a/core/server/controllers/admin.js +++ b/core/server/controllers/admin.js @@ -13,8 +13,16 @@ adminControllers = { /*jslint unparam:true*/ function renderIndex() { + var configuration; return api.configuration.browse().then(function then(data) { - var apiConfig = _.omit(data.configuration, function omit(value) { + configuration = data.configuration; + }).then(function getAPIClient() { + return api.clients.read({slug: 'ghost-admin'}); + }).then(function renderIndex(adminClient) { + configuration.push({key: 'clientId', value: adminClient.clients[0].slug}); + configuration.push({key: 'clientSecret', value: adminClient.clients[0].secret}); + + var apiConfig = _.omit(configuration, function omit(value) { return _.contains(['environment', 'database', 'mail', 'version'], value.key); }); diff --git a/core/server/data/fixtures/index.js b/core/server/data/fixtures/index.js index c7b28d9277..8b58f67c8f 100644 --- a/core/server/data/fixtures/index.js +++ b/core/server/data/fixtures/index.js @@ -6,6 +6,7 @@ // making them. var Promise = require('bluebird'), + crypto = require('crypto'), sequence = require('../../utils/sequence'), _ = require('lodash'), errors = require('../../errors'), @@ -224,7 +225,9 @@ to004 = function to004() { upgradeOp = models.Client.findOne({slug: fixtures.clients[0].slug}).then(function (client) { if (client) { logInfo('Update ghost-admin client fixture'); - return models.Client.edit(fixtures.clients[0], _.extend({}, options, {id: client.id})); + var adminClient = fixtures.clients[0]; + adminClient.secret = crypto.randomBytes(6).toString('hex'); + return models.Client.edit(adminClient, _.extend({}, options, {id: client.id})); } return Promise.resolve(); }); @@ -234,7 +237,9 @@ to004 = function to004() { upgradeOp = models.Client.findOne({slug: fixtures.clients[1].slug}).then(function (client) { if (!client) { logInfo('Add ghost-frontend client fixture'); - return models.Client.add(fixtures.clients[1], options); + var frontendClient = fixtures.clients[1]; + frontendClient.secret = crypto.randomBytes(6).toString('hex'); + return models.Client.add(frontendClient, options); } return Promise.resolve(); }); diff --git a/core/server/middleware/client-auth.js b/core/server/middleware/client-auth.js index 71f1686d09..2ff7303a80 100644 --- a/core/server/middleware/client-auth.js +++ b/core/server/middleware/client-auth.js @@ -1,5 +1,4 @@ var passport = require('passport'), - _ = require('lodash'), oauthServer, clientAuth; @@ -9,15 +8,6 @@ function cacheOauthServer(server) { } clientAuth = { - // work around to handle missing client_secret - // oauth2orize needs it, but untrusted clients don't have it - addClientSecret: function addClientSecret(req, res, next) { - if (_.isEmpty(req.body.client_secret)) { - req.body.client_secret = 'not_available'; - } - next(); - }, - // ### Authenticate Client Middleware // authenticate client that is asking for an access token authenticateClient: function authenticateClient(req, res, next) { diff --git a/core/server/middleware/index.js b/core/server/middleware/index.js index b98d935980..6deac4825f 100644 --- a/core/server/middleware/index.js +++ b/core/server/middleware/index.js @@ -44,7 +44,6 @@ middleware = { spamPrevention: spamPrevention, privateBlogging: privateBlogging, api: { - addClientSecret: clientAuth.addClientSecret, cacheOauthServer: clientAuth.cacheOauthServer, authenticateClient: clientAuth.authenticateClient, generateAccessToken: clientAuth.generateAccessToken, diff --git a/core/server/routes/api.js b/core/server/routes/api.js index b157a8f405..5c57da6137 100644 --- a/core/server/routes/api.js +++ b/core/server/routes/api.js @@ -47,6 +47,9 @@ apiRoutes = function apiRoutes(middleware) { // ## Roles router.get('/roles/', api.http(api.roles.browse)); + // ## Clients + router.get('/clients/slug/:slug', api.http(api.clients.read)); + // ## Slugs router.get('/slugs/:type/:name', api.http(api.slugs.generate)); @@ -81,7 +84,6 @@ apiRoutes = function apiRoutes(middleware) { router.get('/authentication/setup', api.http(api.authentication.isSetup)); router.post('/authentication/token', middleware.spamPrevention.signin, - middleware.api.addClientSecret, middleware.api.authenticateClient, middleware.api.generateAccessToken ); diff --git a/core/test/functional/routes/api/authentication_spec.js b/core/test/functional/routes/api/authentication_spec.js index 13b775acc3..185794ec54 100644 --- a/core/test/functional/routes/api/authentication_spec.js +++ b/core/test/functional/routes/api/authentication_spec.js @@ -31,7 +31,7 @@ describe('Authentication API', function () { it('can authenticate', function (done) { request.post(testUtils.API.getApiQuery('authentication/token')) - .send({grant_type: 'password', username: user.email, password: user.password, client_id: 'ghost-admin'}) + .send({grant_type: 'password', username: user.email, password: user.password, client_id: 'ghost-admin', client_secret: 'not_available'}) .expect('Content-Type', /json/) // TODO: make it possible to override oauth2orize's header so that this is consistent .expect('Cache-Control', 'no-store') @@ -52,7 +52,7 @@ describe('Authentication API', function () { it('can\'t authenticate unknown user', function (done) { request.post(testUtils.API.getApiQuery('authentication/token')) - .send({grant_type: 'password', username: 'invalid@email.com', password: user.password, client_id: 'ghost-admin'}) + .send({grant_type: 'password', username: 'invalid@email.com', password: user.password, client_id: 'ghost-admin', client_secret: 'not_available'}) .expect('Content-Type', /json/) .expect('Cache-Control', testUtils.cacheRules['private']) .expect(404) @@ -69,7 +69,7 @@ describe('Authentication API', function () { it('can\'t authenticate invalid password user', function (done) { request.post(testUtils.API.getApiQuery('authentication/token')) - .send({grant_type: 'password', username: user.email, password: 'invalid', client_id: 'ghost-admin'}) + .send({grant_type: 'password', username: user.email, password: 'invalid', client_id: 'ghost-admin', client_secret: 'not_available'}) .expect('Content-Type', /json/) .expect('Cache-Control', testUtils.cacheRules['private']) .expect(401) @@ -86,7 +86,7 @@ describe('Authentication API', function () { it('can request new access token', function (done) { request.post(testUtils.API.getApiQuery('authentication/token')) - .send({grant_type: 'password', username: user.email, password: user.password, client_id: 'ghost-admin'}) + .send({grant_type: 'password', username: user.email, password: user.password, client_id: 'ghost-admin', client_secret: 'not_available'}) .expect('Content-Type', /json/) // TODO: make it possible to override oauth2orize's header so that this is consistent .expect('Cache-Control', 'no-store') @@ -97,7 +97,7 @@ describe('Authentication API', function () { } var refreshToken = res.body.refresh_token; request.post(testUtils.API.getApiQuery('authentication/token')) - .send({grant_type: 'refresh_token', refresh_token: refreshToken, client_id: 'ghost-admin'}) + .send({grant_type: 'refresh_token', refresh_token: refreshToken, client_id: 'ghost-admin', client_secret: 'not_available'}) .expect('Content-Type', /json/) // TODO: make it possible to override oauth2orize's header so that this is consistent .expect('Cache-Control', 'no-store') @@ -116,7 +116,7 @@ describe('Authentication API', function () { it('can\'t request new access token with invalid refresh token', function (done) { request.post(testUtils.API.getApiQuery('authentication/token')) - .send({grant_type: 'refresh_token', refresh_token: 'invalid', client_id: 'ghost-admin'}) + .send({grant_type: 'refresh_token', refresh_token: 'invalid', client_id: 'ghost-admin', client_secret: 'not_available'}) .expect('Content-Type', /json/) .expect('Cache-Control', testUtils.cacheRules['private']) .expect(403) diff --git a/core/test/unit/middleware/client-auth_spec.js b/core/test/unit/middleware/client-auth_spec.js deleted file mode 100644 index 9804b23fb4..0000000000 --- a/core/test/unit/middleware/client-auth_spec.js +++ /dev/null @@ -1,44 +0,0 @@ -/*globals describe, beforeEach, it*/ -/*jshint expr:true*/ -var should = require('should'), - sinon = require('sinon'), - - middleware = require('../../../server/middleware').middleware; - -describe('Middleware: Client Auth', function () { - var req, res, next; - - beforeEach(function () { - req = {}; - res = {}; - next = sinon.spy(); - }); - - describe('addClientSecret', function () { - it('sets a `client_secret` if not part of body', function () { - var requestBody = {}; - - req.body = requestBody; - - middleware.api.addClientSecret(req, res, next); - - next.called.should.be.true; - should(req.body).have.property('client_secret'); - req.body.client_secret.should.not.be.empty; - }); - - it('does not tamper with `client_secret` if already present', function () { - var requestBody = { - client_secret: 'keep-it-safe-keep-it-secret' - }; - - req.body = requestBody; - - middleware.api.addClientSecret(req, res, next); - - next.called.should.be.true; - should(req.body).have.property('client_secret'); - req.body.client_secret.should.equal('keep-it-safe-keep-it-secret'); - }); - }); -}); diff --git a/core/test/utils/index.js b/core/test/utils/index.js index 26acea53fb..03a1ef6b33 100644 --- a/core/test/utils/index.js +++ b/core/test/utils/index.js @@ -525,7 +525,7 @@ login = function login(request) { return new Promise(function (resolve, reject) { request.post('/ghost/api/v0.1/authentication/token/') - .send({grant_type: 'password', username: user.email, password: user.password, client_id: 'ghost-admin'}) + .send({grant_type: 'password', username: user.email, password: user.password, client_id: 'ghost-admin', client_secret: 'not_available'}) .end(function (err, res) { if (err) { return reject(err);