Revert "Revert "add oauth2-server""

This reverts commit 946a7c2494d39fd7581cb8a068af7df647fb3bda.

GitOrigin-RevId: 2f02e9d9e2d0348e4ea1d447e0291fae72c0008a
This commit is contained in:
Ersun Warncke 2019-05-14 05:27:21 -04:00 committed by sharelatex
parent 9b24ed6daa
commit 7883554d73
7 changed files with 280 additions and 74 deletions

View file

@ -187,25 +187,41 @@ module.exports = AuthenticationController =
return doRequest
requireOauth: () ->
# require this here because module may not be included in some versions
Oauth2Server = require "../../../../modules/oauth2-server/app/js/Oauth2Server"
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) ->
request = new Oauth2Server.Request(req)
response = new Oauth2Server.Response(res)
Oauth2Server.server.authenticate request, response, {}, (err, token) ->
if err?
# fall back to v1 on invalid token
return AuthenticationController._requireOauthV1Fallback req, res, next if err.code == 401
# bubble up all other errors
return next(err)
req.oauth =
access_token: token.accessToken
req.oauth_token = token
req.oauth_user = token.user
return next()
_requireOauthV1Fallback: (req, res, next) ->
return res.sendStatus 401 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).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({error: "invalid_token"}) unless user?
req.oauth =
access_token: body.access_token
collabratec_customer_id: body.collabratec_customer_id
user_profile: body.user_profile
req.oauth_user = user
next()
return res.status(401).json({error: "invalid_token"}) unless user?
req.oauth =
access_token: body.access_token
user.collabratec_id = body.collabratec_customer_id unless user.collabratec_id?
req.oauth_user = user
next()
_globalLoginWhitelist: []
addEndpointToLoginWhitelist: (endpoint) ->

View file

@ -0,0 +1,31 @@
mongoose = require 'mongoose'
Settings = require 'settings-sharelatex'
Schema = mongoose.Schema
ObjectId = Schema.ObjectId
OauthAccessTokenSchema = new Schema(
{
accessToken: String
accessTokenExpiresAt: Date
oauthApplication_id: { type: ObjectId, ref: 'OauthApplication' }
refreshToken: String
refreshTokenExpiresAt: Date
scope: String
user_id: { type: ObjectId, ref: 'User' }
},
{
collection: 'oauthAccessTokens'
}
)
conn = mongoose.createConnection(Settings.mongo.url, {
server: {poolSize: Settings.mongo.poolSize || 10},
config: {autoIndex: false}
})
OauthAccessToken = conn.model('OauthAccessToken', OauthAccessTokenSchema)
mongoose.model 'OauthAccessToken', OauthAccessTokenSchema
exports.OauthAccessToken = OauthAccessToken
exports.OauthAccessTokenSchema = OauthAccessTokenSchema

View file

@ -0,0 +1,30 @@
mongoose = require 'mongoose'
Settings = require 'settings-sharelatex'
Schema = mongoose.Schema
ObjectId = Schema.ObjectId
OauthApplicationSchema = new Schema(
{
id: String
clientSecret: String
grants: [ String ]
name: String
redirectUris: [ String ]
scopes: [ String ]
},
{
collection: 'oauthApplications'
}
)
conn = mongoose.createConnection(Settings.mongo.url, {
server: {poolSize: Settings.mongo.poolSize || 10},
config: {autoIndex: false}
})
OauthApplication = conn.model('OauthApplication', OauthApplicationSchema)
mongoose.model 'OauthApplication', OauthApplicationSchema
exports.OauthApplication = OauthApplication
exports.OauthApplicationSchema = OauthApplicationSchema

View file

@ -0,0 +1,30 @@
mongoose = require 'mongoose'
Settings = require 'settings-sharelatex'
Schema = mongoose.Schema
ObjectId = Schema.ObjectId
OauthAuthorizationCodeSchema = new Schema(
{
authorizationCode: String
expiresAt: Date
oauthApplication_id: { type: ObjectId, ref: 'OauthApplication' }
redirectUri: String
scope: String
user_id: { type: ObjectId, ref: 'User' }
},
{
collection: 'oauthAuthorizationCodes'
}
)
conn = mongoose.createConnection(Settings.mongo.url, {
server: {poolSize: Settings.mongo.poolSize || 10},
config: {autoIndex: false}
})
OauthAuthorizationCode = conn.model('OauthAuthorizationCode', OauthAuthorizationCodeSchema)
mongoose.model 'OauthAuthorizationCode', OauthAuthorizationCodeSchema
exports.OauthAuthorizationCode = OauthAuthorizationCode
exports.OauthAuthorizationCodeSchema = OauthAuthorizationCodeSchema

View file

@ -2139,6 +2139,18 @@
"resolved": "https://registry.npmjs.org/base64url/-/base64url-2.0.0.tgz",
"integrity": "sha1-6sFuA+oUOO/5Qj1puqNiYu0fcLs="
},
"basic-auth": {
"version": "2.0.1",
"from": "basic-auth@>=2.0.0 <3.0.0",
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
"dependencies": {
"safe-buffer": {
"version": "5.1.2",
"from": "safe-buffer@5.1.2",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz"
}
}
},
"basic-auth-connect": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/basic-auth-connect/-/basic-auth-connect-1.0.0.tgz",
@ -3550,6 +3562,16 @@
"resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz",
"integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ="
},
"co-bluebird": {
"version": "1.1.0",
"from": "co-bluebird@>=1.1.0 <2.0.0",
"resolved": "https://registry.npmjs.org/co-bluebird/-/co-bluebird-1.1.0.tgz"
},
"co-use": {
"version": "1.1.0",
"from": "co-use@>=1.1.0 <2.0.0",
"resolved": "https://registry.npmjs.org/co-use/-/co-use-1.1.0.tgz"
},
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
@ -8431,6 +8453,11 @@
"number-is-nan": "^1.0.0"
}
},
"is-generator": {
"version": "1.0.3",
"from": "is-generator@>=1.0.2 <2.0.0",
"resolved": "https://registry.npmjs.org/is-generator/-/is-generator-1.0.3.tgz"
},
"is-glob": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz",
@ -12576,6 +12603,43 @@
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz",
"integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM="
},
"oauth2-server": {
"version": "3.0.1",
"from": "oauth2-server@latest",
"resolved": "https://registry.npmjs.org/oauth2-server/-/oauth2-server-3.0.1.tgz",
"dependencies": {
"bluebird": {
"version": "3.5.3",
"from": "bluebird@>=3.5.1 <4.0.0",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz"
},
"lodash": {
"version": "4.17.11",
"from": "lodash@>=4.17.10 <5.0.0",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz"
},
"mime-db": {
"version": "1.38.0",
"from": "mime-db@>=1.38.0 <1.39.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.38.0.tgz"
},
"mime-types": {
"version": "2.1.22",
"from": "mime-types@>=2.1.18 <2.2.0",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.22.tgz"
},
"statuses": {
"version": "1.5.0",
"from": "statuses@>=1.5.0 <2.0.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz"
},
"type-is": {
"version": "1.6.16",
"from": "type-is@>=1.6.16 <2.0.0",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz"
}
}
},
"object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
@ -14074,6 +14138,11 @@
"is-promise": "~1"
}
},
"promisify-any": {
"version": "2.0.1",
"from": "promisify-any@>=2.0.1 <3.0.0",
"resolved": "https://registry.npmjs.org/promisify-any/-/promisify-any-2.0.1.tgz"
},
"promisify-call": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/promisify-call/-/promisify-call-2.0.4.tgz",

View file

@ -79,6 +79,7 @@
"nodemailer-sendgrid-transport": "^0.2.0",
"nodemailer-ses-transport": "^1.3.0",
"nvd3": "^1.8.6",
"oauth2-server": "^3.0.1",
"optimist": "0.6.1",
"overleaf-error-type": "git+https://github.com/overleaf/overleaf-error-type.git",
"passport": "^0.3.2",

View file

@ -37,6 +37,10 @@ describe "AuthenticationController", ->
ipMatcherAffiliation: sinon.stub()
"../V1/V1Api": @V1Api = request: sinon.stub()
"../../models/User": { User: @UserModel }
"../../../../modules/oauth2-server/app/js/Oauth2Server": @Oauth2Server =
Request: sinon.stub()
Response: sinon.stub()
server: authenticate: sinon.stub()
@user =
_id: ObjectId()
email: @email = "USER@example.com"
@ -402,90 +406,115 @@ describe "AuthenticationController", ->
describe "requireOauth", ->
beforeEach ->
@res.sendStatus = sinon.stub()
@res.send = sinon.stub()
@res.status = sinon.stub().returns(@res)
@middleware = @AuthenticationController.requireOauth()
describe "when token not provided", ->
describe "when Oauth2Server authenticates", ->
beforeEach ->
@token =
accessToken: "token"
user: "user"
@Oauth2Server.server.authenticate.yields null, @token
@middleware(@req, @res, @next)
it "should return 401 error", ->
@res.status.should.have.been.calledWith 401
it "should set oauth_token on request", ->
@req.oauth_token.should.equal @token
describe "when token provided", ->
it "should set oauth on request", ->
@req.oauth.access_token.should.equal @token.accessToken
it "should set oauth_user on request", ->
@req.oauth_user.should.equal "user"
it "should call next", ->
@next.should.have.been.calledOnce
describe "when Oauth2Server does not authenticate", ->
beforeEach ->
@V1Api.request = sinon.stub().yields("error", {}, {})
@req.token = "foo"
@middleware(@req, @res, @next)
@Oauth2Server.server.authenticate.yields code: 401
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", ->
describe "when token not provided", ->
beforeEach ->
@middleware(@req, @res, @next)
it "should find user", ->
@UserModel.findOne.should.have.been.calledWithMatch { "overleaf.id": "overleaf-id" }
it "should return 401 error", ->
@res.sendStatus.should.have.been.calledWith 401
describe "when user find returns error", ->
describe "when token provided", ->
beforeEach ->
@UserModel.findOne = sinon.stub().yields("error")
@V1Api.request = sinon.stub().yields("error", {}, {})
@req.token = "foo"
@middleware(@req, @res, @next)
it "should return error", ->
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 user is not found", ->
describe "when v1 api status code is not 200", ->
beforeEach ->
@UserModel.findOne = sinon.stub().yields(null, null)
@V1Api.request = sinon.stub().yields(null, {statusCode: 401}, {})
@req.token = "foo"
@middleware(@req, @res, @next)
it "should return unauthorized", ->
it "should return status", ->
@res.status.should.have.been.calledWith 401
describe "when user is found", ->
describe "when v1 api returns authorized profile and access token", ->
beforeEach ->
@UserModel.findOne = sinon.stub().yields(null, "user")
@middleware(@req, @res, @next)
@oauth_authorize =
access_token: "access_token"
user_profile: id: "overleaf-id"
@V1Api.request = sinon.stub().yields(null, {statusCode: 200}, @oauth_authorize)
@req.token = "foo"
it "should add user to request", ->
@req.oauth_user.should.equal "user"
describe "in all cases", ->
beforeEach ->
@middleware(@req, @res, @next)
it "should add access_token to request", ->
@req.oauth.access_token.should.equal "access_token"
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", ->
beforeEach ->