Merge pull request #1047 from sharelatex/ew-oauth-authorization

add oauth middlewear

GitOrigin-RevId: b68360763e1060fdbcbb4348d3d691a803fbfa41
This commit is contained in:
Ersun Warncke 2018-10-30 14:18:42 -04:00 committed by sharelatex
parent 0cfb765501
commit 365158f283
5 changed files with 120 additions and 0 deletions

View file

@ -13,6 +13,8 @@ Analytics = require "../Analytics/AnalyticsManager"
passport = require 'passport' passport = require 'passport'
NotificationsBuilder = require("../Notifications/NotificationsBuilder") NotificationsBuilder = require("../Notifications/NotificationsBuilder")
SudoModeHandler = require '../SudoMode/SudoModeHandler' SudoModeHandler = require '../SudoMode/SudoModeHandler'
V1Api = require "../V1/V1Api"
{User} = require "../../models/User"
module.exports = AuthenticationController = module.exports = AuthenticationController =
@ -169,6 +171,24 @@ module.exports = AuthenticationController =
return doRequest return doRequest
requireOauth: () ->
return (req, res, next = (error) ->) ->
return res.status(401).send() unless req.token?
options =
expectedStatusCodes: [401]
json: token: req.token
method: "POST"
uri: "/api/v1/sharelatex/oauth_authorize"
V1Api.request options, (error, response, body) ->
return next(error) if error?
return res.status(401).json({error: "invalid_token"}) unless body?.user_profile?.id
User.findOne { "overleaf.id": body.user_profile.id }, (error, user) ->
return next(error) if error?
return res.status(401).send() unless user?
req.oauth = access_token: body.access_token
req.oauth_user = user
next()
_globalLoginWhitelist: [] _globalLoginWhitelist: []
addEndpointToLoginWhitelist: (endpoint) -> addEndpointToLoginWhitelist: (endpoint) ->
AuthenticationController._globalLoginWhitelist.push endpoint AuthenticationController._globalLoginWhitelist.push endpoint

View file

@ -20,6 +20,7 @@ methodOverride = require('method-override')
csrf = require('csurf') csrf = require('csurf')
csrfProtection = csrf() csrfProtection = csrf()
cookieParser = require('cookie-parser') cookieParser = require('cookie-parser')
bearerToken = require('express-bearer-token')
# Init the session store # Init the session store
sessionStore = new RedisStore(client:sessionsRedisClient) sessionStore = new RedisStore(client:sessionsRedisClient)
@ -71,6 +72,7 @@ app.use bodyParser.urlencoded({ extended: true, limit: "2mb"})
app.use bodyParser.json({limit: Settings.max_doc_length + 64 * 1024}) # 64kb overhead app.use bodyParser.json({limit: Settings.max_doc_length + 64 * 1024}) # 64kb overhead
app.use multer(dest: Settings.path.uploadFolder) app.use multer(dest: Settings.path.uploadFolder)
app.use methodOverride() app.use methodOverride()
app.use bearerToken()
app.use metrics.http.monitor(logger) app.use metrics.http.monitor(logger)
RedirectManager.apply(webRouter) RedirectManager.apply(webRouter)

View file

@ -3332,6 +3332,11 @@
} }
} }
}, },
"express-bearer-token": {
"version": "2.2.0",
"from": "express-bearer-token@>=2.2.0 <3.0.0",
"resolved": "https://registry.npmjs.org/express-bearer-token/-/express-bearer-token-2.2.0.tgz"
},
"express-http-proxy": { "express-http-proxy": {
"version": "1.1.0", "version": "1.1.0",
"from": "express-http-proxy@>=1.1.0 <2.0.0", "from": "express-http-proxy@>=1.1.0 <2.0.0",

View file

@ -44,6 +44,7 @@
"dateformat": "1.0.4-1.2.3", "dateformat": "1.0.4-1.2.3",
"daterangepicker": "^2.1.27", "daterangepicker": "^2.1.27",
"express": "4.13.0", "express": "4.13.0",
"express-bearer-token": "^2.2.0",
"express-http-proxy": "^1.1.0", "express-http-proxy": "^1.1.0",
"express-session": "^1.14.2", "express-session": "^1.14.2",
"fs-extra": "^4.0.2", "fs-extra": "^4.0.2",

View file

@ -1,5 +1,7 @@
sinon = require('sinon') sinon = require('sinon')
chai = require('chai') chai = require('chai')
sinonChai = require "sinon-chai"
chai.use sinonChai
should = chai.should() should = chai.should()
expect = chai.expect expect = chai.expect
modulePath = "../../../../app/js/Features/Authentication/AuthenticationController.js" modulePath = "../../../../app/js/Features/Authentication/AuthenticationController.js"
@ -13,6 +15,7 @@ ObjectId = require("mongojs").ObjectId
describe "AuthenticationController", -> describe "AuthenticationController", ->
beforeEach -> beforeEach ->
tk.freeze(Date.now()) tk.freeze(Date.now())
@UserModel = findOne: sinon.stub()
@AuthenticationController = SandboxedModule.require modulePath, requires: @AuthenticationController = SandboxedModule.require modulePath, requires:
"./AuthenticationManager": @AuthenticationManager = {} "./AuthenticationManager": @AuthenticationManager = {}
"../User/UserUpdater" : @UserUpdater = {updateUser:sinon.stub()} "../User/UserUpdater" : @UserUpdater = {updateUser:sinon.stub()}
@ -32,6 +35,8 @@ describe "AuthenticationController", ->
"../SudoMode/SudoModeHandler": @SudoModeHandler = {activateSudoMode: sinon.stub().callsArgWith(1, null)} "../SudoMode/SudoModeHandler": @SudoModeHandler = {activateSudoMode: sinon.stub().callsArgWith(1, null)}
"../Notifications/NotificationsBuilder": @NotificationsBuilder = "../Notifications/NotificationsBuilder": @NotificationsBuilder =
ipMatcherAffiliation: sinon.stub() ipMatcherAffiliation: sinon.stub()
"../V1/V1Api": @V1Api = request: sinon.stub()
"../../models/User": { User: @UserModel }
@user = @user =
_id: ObjectId() _id: ObjectId()
email: @email = "USER@example.com" email: @email = "USER@example.com"
@ -395,6 +400,93 @@ describe "AuthenticationController", ->
it "should redirect to the register or login page", -> it "should redirect to the register or login page", ->
@AuthenticationController._redirectToLoginOrRegisterPage.calledWith(@req, @res).should.equal true @AuthenticationController._redirectToLoginOrRegisterPage.calledWith(@req, @res).should.equal true
describe "requireOauth", ->
beforeEach ->
@res.send = sinon.stub()
@res.status = sinon.stub().returns(@res)
@middleware = @AuthenticationController.requireOauth()
describe "when token not provided", ->
beforeEach ->
@middleware(@req, @res, @next)
it "should return 401 error", ->
@res.status.should.have.been.calledWith 401
describe "when token provided", ->
beforeEach ->
@V1Api.request = sinon.stub().yields("error", {}, {})
@req.token = "foo"
@middleware(@req, @res, @next)
it "should make request to v1 api with token", ->
@V1Api.request.should.have.been.calledWith {
expectedStatusCodes: [401]
json: token: "foo"
method: "POST"
uri: "/api/v1/sharelatex/oauth_authorize"
}
describe "when v1 api returns error", ->
beforeEach ->
@V1Api.request = sinon.stub().yields("error", {}, {})
@req.token = "foo"
@middleware(@req, @res, @next)
it "should return status", ->
@next.should.have.been.calledWith "error"
describe "when v1 api status code is not 200", ->
beforeEach ->
@V1Api.request = sinon.stub().yields(null, {statusCode: 401}, {})
@req.token = "foo"
@middleware(@req, @res, @next)
it "should return status", ->
@res.status.should.have.been.calledWith 401
describe "when v1 api returns authorized profile and access token", ->
beforeEach ->
@oauth_authorize =
access_token: "access_token"
user_profile: id: "overleaf-id"
@V1Api.request = sinon.stub().yields(null, {statusCode: 200}, @oauth_authorize)
@req.token = "foo"
describe "in all cases", ->
beforeEach ->
@middleware(@req, @res, @next)
it "should find user", ->
@UserModel.findOne.should.have.been.calledWithMatch { "overleaf.id": "overleaf-id" }
describe "when user find returns error", ->
beforeEach ->
@UserModel.findOne = sinon.stub().yields("error")
@middleware(@req, @res, @next)
it "should return error", ->
@next.should.have.been.calledWith "error"
describe "when user is not found", ->
beforeEach ->
@UserModel.findOne = sinon.stub().yields(null, null)
@middleware(@req, @res, @next)
it "should return unauthorized", ->
@res.status.should.have.been.calledWith 401
describe "when user is found", ->
beforeEach ->
@UserModel.findOne = sinon.stub().yields(null, "user")
@middleware(@req, @res, @next)
it "should add user to request", ->
@req.oauth_user.should.equal "user"
it "should add access_token to request", ->
@req.oauth.access_token.should.equal "access_token"
describe "requireGlobalLogin", -> describe "requireGlobalLogin", ->
beforeEach -> beforeEach ->
@req.headers = {} @req.headers = {}