Просмотр исходного кода

Merge branch 'master' of https://github.com/sharelatex/web-sharelatex

Henry Oswald 11 лет назад
Родитель
Сommit
c583903e04

+ 21 - 0
services/web/app/coffee/Features/Authentication/AuthenticationController.coffee

@@ -6,6 +6,7 @@ Metrics = require('../../infrastructure/Metrics')
 logger = require("logger-sharelatex")
 querystring = require('querystring')
 Url = require("url")
+Settings = require "settings-sharelatex"
 
 module.exports = AuthenticationController =
 	login: (req, res, next = (error) ->) ->
@@ -84,6 +85,26 @@ module.exports = AuthenticationController =
 
 		return doRequest
 
+	_globalLoginWhitelist: []
+	addEndpointToLoginWhitelist: (endpoint) ->
+		AuthenticationController._globalLoginWhitelist.push endpoint
+
+	requireGlobalLogin: (req, res, next) ->
+		if req.url in AuthenticationController._globalLoginWhitelist
+			return next()
+
+		if req.headers['authorization']?
+			return AuthenticationController.httpAuth(req, res, next)
+		else if req.session.user?
+			return next()
+		else
+			return res.redirect "/login"
+
+	httpAuth: require('express').basicAuth (user, pass)->
+		isValid = Settings.httpAuthUsers[user] == pass
+		if !isValid
+			logger.err user:user, pass:pass, "invalid login details"
+		return isValid
 
 	_redirectToLoginOrRegisterPage: (req, res)->
 		if req.query.zipUrl? or req.query.project_name?

+ 3 - 2
services/web/app/coffee/Features/Editor/EditorRouter.coffee

@@ -1,8 +1,9 @@
 EditorHttpController = require('./EditorHttpController')
 SecurityManager = require('../../managers/SecurityManager')
+AuthenticationController = require "../Authentication/AuthenticationController"
 
 module.exports =
-	apply: (app, httpAuth) ->
+	apply: (app) ->
 		app.post   '/project/:Project_id/doc', SecurityManager.requestCanModifyProject, EditorHttpController.addDoc
 		app.post   '/project/:Project_id/folder', SecurityManager.requestCanModifyProject, EditorHttpController.addFolder
 
@@ -18,5 +19,5 @@ module.exports =
 		# Called by the real-time API to load up the current project state.
 		# This is a post request because it's more than just a getting of data. We take actions
 		# whenever a user joins a project, like updating the deleted status.
-		app.post   '/project/:Project_id/join', httpAuth, EditorHttpController.joinProject
+		app.post   '/project/:Project_id/join', AuthenticationController.httpAuth, EditorHttpController.joinProject
 		app.ignoreCsrf('post', '/project/:Project_id/join')

+ 17 - 17
services/web/app/coffee/router.coffee

@@ -39,25 +39,25 @@ RealTimeProxyRouter = require('./Features/RealTimeProxy/RealTimeProxyRouter')
 logger = require("logger-sharelatex")
 _ = require("underscore")
 
-httpAuth = require('express').basicAuth (user, pass)->
-	isValid = Settings.httpAuthUsers[user] == pass
-	if !isValid
-		logger.err user:user, pass:pass, "invalid login details"
-	return isValid
-
 module.exports = class Router
 	constructor: (app)->
+		if !Settings.allowPublicAccess
+			app.all '*', AuthenticationController.requireGlobalLogin
+
 		app.use(app.router)
 		
 		app.get  '/login', UserPagesController.loginPage
+		AuthenticationController.addEndpointToLoginWhitelist '/login'
+
 		app.post '/login', AuthenticationController.login
 		app.get  '/logout', UserController.logout
 		app.get  '/restricted', SecurityManager.restricted
 
 		# Left as a placeholder for implementing a public register page
 		app.get  '/register', UserPagesController.registerPage
+		AuthenticationController.addEndpointToLoginWhitelist '/register'
 
-		EditorRouter.apply(app, httpAuth)
+		EditorRouter.apply(app)
 		CollaboratorsRouter.apply(app)
 		SubscriptionRouter.apply(app)
 		UploadsRouter.apply(app)
@@ -82,7 +82,7 @@ module.exports = class Router
 
 		app.get  '/user/auth_token', AuthenticationController.requireLogin(), AuthenticationController.getAuthToken
 		app.get  '/user/personal_info', AuthenticationController.requireLogin(allow_auth_token: true), UserInfoController.getLoggedInUsersPersonalInfo
-		app.get  '/user/:user_id/personal_info', httpAuth, UserInfoController.getPersonalInfo
+		app.get  '/user/:user_id/personal_info', AuthenticationController.httpAuth, UserInfoController.getPersonalInfo
 
 		app.get  '/project', AuthenticationController.requireLogin(), ProjectController.projectListPage
 		app.post '/project/new', AuthenticationController.requireLogin(), ProjectController.newProject
@@ -127,23 +127,23 @@ module.exports = class Router
 		app.get '/tag', AuthenticationController.requireLogin(), TagsController.getAllTags
 		app.post '/project/:project_id/tag', AuthenticationController.requireLogin(), TagsController.processTagsUpdate
 
-		app.get  '/project/:project_id/details', httpAuth, ProjectApiController.getProjectDetails
+		app.get  '/project/:project_id/details', AuthenticationController.httpAuth, ProjectApiController.getProjectDetails
 
-		app.get '/internal/project/:Project_id/zip', httpAuth, ProjectDownloadsController.downloadProject
-		app.get '/internal/project/:project_id/compile/pdf', httpAuth, CompileController.compileAndDownloadPdf
+		app.get '/internal/project/:Project_id/zip', AuthenticationController.httpAuth, ProjectDownloadsController.downloadProject
+		app.get '/internal/project/:project_id/compile/pdf', AuthenticationController.httpAuth, CompileController.compileAndDownloadPdf
 
 
-		app.get  '/project/:Project_id/doc/:doc_id', httpAuth, DocumentController.getDocument
-		app.post '/project/:Project_id/doc/:doc_id', httpAuth, DocumentController.setDocument
+		app.get  '/project/:Project_id/doc/:doc_id', AuthenticationController.httpAuth, DocumentController.getDocument
+		app.post '/project/:Project_id/doc/:doc_id', AuthenticationController.httpAuth, DocumentController.setDocument
 		app.ignoreCsrf('post', '/project/:Project_id/doc/:doc_id')
 
-		app.post '/user/:user_id/update/*', httpAuth, TpdsController.mergeUpdate
-		app.del  '/user/:user_id/update/*', httpAuth, TpdsController.deleteUpdate
+		app.post '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.mergeUpdate
+		app.del  '/user/:user_id/update/*', AuthenticationController.httpAuth, TpdsController.deleteUpdate
 		app.ignoreCsrf('post', '/user/:user_id/update/*')
 		app.ignoreCsrf('delete', '/user/:user_id/update/*')
 		
-		app.post '/project/:project_id/contents/*', httpAuth, TpdsController.updateProjectContents
-		app.del  '/project/:project_id/contents/*', httpAuth, TpdsController.deleteProjectContents
+		app.post '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.updateProjectContents
+		app.del  '/project/:project_id/contents/*', AuthenticationController.httpAuth, TpdsController.deleteProjectContents
 		app.ignoreCsrf('post', '/project/:project_id/contents/*')
 		app.ignoreCsrf('delete', '/project/:project_id/contents/*')
 

+ 4 - 0
services/web/config/settings.defaults.coffee

@@ -232,6 +232,10 @@ module.exports =
 	
 	# Cookie max age (in milliseconds). Set to false for a browser session.
 	cookieSessionLength: 5 * 24 * 60 * 60 * 1000 # 5 days
+	
+	# Should we allow access to any page without logging in? This includes
+	# public projects, /learn, /templates, about pages, etc.
+	allowPublicAccess: false
 
 	# Internal configs
 	# ----------------

+ 41 - 0
services/web/test/UnitTests/coffee/Authentication/AuthenticationControllerTests.coffee

@@ -19,6 +19,7 @@ describe "AuthenticationController", ->
 			"../../infrastructure/Metrics": @Metrics = { inc: sinon.stub() }
 			"../Security/LoginRateLimiter": @LoginRateLimiter = { processLoginRequest:sinon.stub(), recordSuccessfulLogin:sinon.stub() }
 			"logger-sharelatex": @logger = { log: sinon.stub(), error: sinon.stub() }
+			"settings-sharelatex": {}
 		@user =
 			_id: ObjectId()
 			email: @email = "USER@example.com"
@@ -275,6 +276,46 @@ describe "AuthenticationController", ->
 					.calledWith(@req, {allow_auth_token: true})
 					.should.equal true
 
+	describe "requireGlobalLogin", ->
+		beforeEach ->
+			@req.headers = {}
+			@AuthenticationController.httpAuth = sinon.stub()
+		
+		describe "with white listed url", ->
+			beforeEach ->
+				@AuthenticationController.addEndpointToLoginWhitelist "/login"
+				@req.url = "/login"
+				@AuthenticationController.requireGlobalLogin @req, @res, @next
+			
+			it "should call next() directly", ->
+				@next.called.should.equal true
+			
+		describe "with http auth", ->
+			beforeEach ->
+				@req.headers["authorization"] = "Mock Basic Auth"
+				@AuthenticationController.requireGlobalLogin @req, @res, @next
+
+			it "should pass the request onto httpAuth", ->
+				@AuthenticationController.httpAuth
+					.calledWith(@req, @res, @next)
+					.should.equal true
+			
+		describe "with a user session", ->
+			beforeEach ->
+				@req.session =
+					user: {"mock": "user"}
+				@AuthenticationController.requireGlobalLogin @req, @res, @next
+			
+			it "should call next() directly", ->
+				@next.called.should.equal true
+			
+		describe "with no login credentials", ->
+			beforeEach ->
+				@AuthenticationController.requireGlobalLogin @req, @res, @next
+				
+			it "should redirect to the /login page", ->
+				@res.redirectedTo.should.equal "/login"
+
 	describe "_redirectToLoginOrRegisterPage", ->
 		beforeEach ->
 			@middleware = @AuthenticationController.requireLogin(@options = { load_from_db: false })

+ 1 - 1
services/web/test/UnitTests/coffee/helpers/MockRequest.coffee

@@ -7,6 +7,6 @@ class MockRequest
 	query: {}
 	i18n:
 		translate:->
-	
+
 module.exports = MockRequest