diff --git a/services/web/app/coffee/Features/Institutions/InstitutionsController.coffee b/services/web/app/coffee/Features/Institutions/InstitutionsController.coffee new file mode 100644 index 0000000000..af8c7c5644 --- /dev/null +++ b/services/web/app/coffee/Features/Institutions/InstitutionsController.coffee @@ -0,0 +1,24 @@ +logger = require("logger-sharelatex") +UserGetter = require("../User/UserGetter") +{ addAffiliation } = require("../Institutions/InstitutionsAPI") +async = require('async') + +module.exports = InstitutionsController = + confirmDomain: (req, res, next) -> + hostname = req.body.hostname + reversedHostname = hostname.trim().split('').reverse().join('') + UserGetter.getUsersByHostname hostname, {_id:1, emails:1}, (error, users) -> + if error? + logger.err error: error, 'problem fetching users by hostname' + return next(error) + async.map users, ((user) -> + matchingEmails = user.emails.filter (email) -> email.reversedHostname == reversedHostname + for email in matchingEmails + addAffiliation user._id, email.email, {confirmedAt: email.confirmedAt}, (error) => + if error? + logger.err error: error, 'problem adding affiliation while confirming hostname' + return next(error) + ), (error) -> + if error? + return next(error) + res.sendStatus 200 diff --git a/services/web/app/coffee/Features/User/UserGetter.coffee b/services/web/app/coffee/Features/User/UserGetter.coffee index e22ea204fd..7eb1e4ba20 100644 --- a/services/web/app/coffee/Features/User/UserGetter.coffee +++ b/services/web/app/coffee/Features/User/UserGetter.coffee @@ -58,6 +58,11 @@ module.exports = UserGetter = # well @getUserByMainEmail email, projection, callback + getUsersByHostname: (hostname, projection, callback = (error, users) ->) -> + reversedHostname = hostname.trim().split('').reverse().join('') + query = emails: { $exists: true }, 'emails.reversedHostname': reversedHostname + db.users.find query, projection, callback + getUsers: (user_ids, projection, callback = (error, users) ->) -> try user_ids = user_ids.map (u) -> ObjectId(u.toString()) diff --git a/services/web/app/coffee/Features/User/UserUpdater.coffee b/services/web/app/coffee/Features/User/UserUpdater.coffee index c8f35eba10..b640aae3c3 100644 --- a/services/web/app/coffee/Features/User/UserUpdater.coffee +++ b/services/web/app/coffee/Features/User/UserUpdater.coffee @@ -66,7 +66,8 @@ module.exports = UserUpdater = logger.err error: error, 'problem adding affiliation while adding email' return callback(error) - update = $push: emails: email: newEmail, createdAt: new Date() + reversedHostname = newEmail.split('@')[1].split('').reverse().join('') + update = $push: emails: email: newEmail, createdAt: new Date(), reversedHostname: reversedHostname @updateUser userId, update, (error) -> if error? logger.err error: error, 'problem updating users emails' diff --git a/services/web/app/coffee/models/User.coffee b/services/web/app/coffee/models/User.coffee index ef9a93b2f2..8a29a800b0 100644 --- a/services/web/app/coffee/models/User.coffee +++ b/services/web/app/coffee/models/User.coffee @@ -10,6 +10,7 @@ UserSchema = new Schema email : {type : String, default : ''} emails: [{ email: { type : String, default : '' }, + reversedHostname: { type : String, default : '' }, createdAt: { type : Date, default: () -> new Date() }, confirmedAt: { type: Date } }], diff --git a/services/web/app/coffee/router.coffee b/services/web/app/coffee/router.coffee index 8c44f505cb..592ddaa061 100644 --- a/services/web/app/coffee/router.coffee +++ b/services/web/app/coffee/router.coffee @@ -50,6 +50,7 @@ TokenAccessController = require('./Features/TokenAccess/TokenAccessController') Features = require('./infrastructure/Features') LinkedFilesRouter = require './Features/LinkedFiles/LinkedFilesRouter' TemplatesRouter = require './Features/Templates/TemplatesRouter' +InstitutionsController = require './Features/Institutions/InstitutionsController' UserMembershipRouter = require './Features/UserMembership/UserMembershipRouter' logger = require("logger-sharelatex") @@ -332,6 +333,7 @@ module.exports = class Router ), AuthenticationController.httpAuth, CompileController.getFileFromClsiWithoutUser + publicApiRouter.post '/api/institutions/confirm_university_domain', AuthenticationController.httpAuth, InstitutionsController.confirmDomain webRouter.get '/teams', (req, res, next) -> # Match v1 behaviour - if the user is signed in, show their teams list diff --git a/services/web/test/unit/coffee/Institutions/InstitutionsControllerTests.coffee b/services/web/test/unit/coffee/Institutions/InstitutionsControllerTests.coffee new file mode 100644 index 0000000000..4e269b1f6f --- /dev/null +++ b/services/web/test/unit/coffee/Institutions/InstitutionsControllerTests.coffee @@ -0,0 +1,57 @@ +should = require('chai').should() +SandboxedModule = require('sandboxed-module') +assert = require('assert') +path = require('path') +sinon = require('sinon') +modulePath = path.join __dirname, "../../../../app/js/Features/Institutions/InstitutionsController" +expect = require("chai").expect + +describe "InstitutionsController", -> + + beforeEach -> + @logger = err: sinon.stub(), log: -> + @host = "mit.edu".split('').reverse().join('') + @stubbedUser1 = + _id: "3131231" + name:"bob" + email:"hello@world.com" + emails: [ + {"email":"stubb1@mit.edu","reversedHostname":@host}, + {"email":"test@test.com","reversedHostname":"test.com"}, + {"email":"another@mit.edu","reversedHostname":@host} + ] + @stubbedUser2 = + _id: "3131232" + name:"test" + email:"hello2@world.com" + emails: [ + {"email":"subb2@mit.edu","reversedHostname":@host} + ] + + @getUsersByHostname = sinon.stub().callsArgWith(2, null, [ @stubbedUser1, @stubbedUser2 ]) + @addAffiliation = sinon.stub().callsArgWith(3, null) + @InstitutionsController = SandboxedModule.require modulePath, requires: + 'logger-sharelatex': @logger + '../User/UserGetter': + getUsersByHostname: @getUsersByHostname + '../Institutions/InstitutionsAPI': + addAffiliation: @addAffiliation + + @req = + body:{} + + @res = + send: sinon.stub() + json: sinon.stub() + @next = sinon.stub() + + describe 'confirmDomain', -> + it 'should add affiliations for matching users', (done)-> + @req.body.hostname = "mit.edu" + @res.sendStatus = (code) => + @getUsersByHostname.calledOnce.should.equal true + @addAffiliation.calledThrice.should.equal true + @addAffiliation.calledWith(@stubbedUser1._id, @stubbedUser1.emails[0].email).should.equal true + @addAffiliation.calledWith(@stubbedUser1._id, @stubbedUser1.emails[2].email).should.equal true + done() + @InstitutionsController.confirmDomain @req, @res, @next diff --git a/services/web/test/unit/coffee/User/UserGetterTests.coffee b/services/web/test/unit/coffee/User/UserGetterTests.coffee index 7d4641c404..0691ed0cdb 100644 --- a/services/web/test/unit/coffee/User/UserGetterTests.coffee +++ b/services/web/test/unit/coffee/User/UserGetterTests.coffee @@ -14,12 +14,15 @@ describe "UserGetter", -> _id: '12390i' email: 'email2@foo.bar' emails: [ - { email: 'email1@foo.bar' } - { email: 'email2@foo.bar' } + { email: 'email1@foo.bar', reversedHostname: 'rab.oof' } + { email: 'email2@foo.bar', reversedHostname: 'rab.oof' } ] @findOne = sinon.stub().callsArgWith(2, null, @fakeUser) + @find = sinon.stub().callsArgWith(2, null, [ @fakeUser ]) @Mongo = - db: users: findOne: @findOne + db: users: + findOne: @findOne + find: @find ObjectId: (id) -> return id settings = apis: { v1: { url: 'v1.url', user: '', pass: '' } } @getUserAffiliations = sinon.stub().callsArgWith(1, null, []) @@ -66,8 +69,8 @@ describe "UserGetter", -> @UserGetter.getUser = sinon.stub().callsArgWith(2, null, @fakeUser) @UserGetter.getUserFullEmails @fakeUser._id, (error, fullEmails) => assert.deepEqual fullEmails, [ - { email: 'email1@foo.bar', default: false } - { email: 'email2@foo.bar', default: true } + { email: 'email1@foo.bar', reversedHostname: 'rab.oof', default: false } + { email: 'email2@foo.bar', reversedHostname: 'rab.oof', default: true } ] done() @@ -86,7 +89,8 @@ describe "UserGetter", -> @UserGetter.getUserFullEmails @fakeUser._id, (error, fullEmails) => assert.deepEqual fullEmails, [ { - email: 'email1@foo.bar' + email: 'email1@foo.bar', + reversedHostname: 'rab.oof' default: false affiliation: institution: affiliationsData[0].institution @@ -94,7 +98,7 @@ describe "UserGetter", -> department: affiliationsData[0].department role: affiliationsData[0].role } - { email: 'email2@foo.bar', default: true } + { email: 'email2@foo.bar', reversedHostname: 'rab.oof', default: true } ] done() @@ -161,6 +165,18 @@ describe "UserGetter", -> @findOne.calledWith(email: email, projection).should.equal true done() + describe "getUsersByHostname", -> + it "should find user by hostname", (done)-> + hostname = "bar.foo" + expectedQuery = + emails: {$exists: true }, + 'emails.reversedHostname': hostname.split('').reverse().join('') + projection = emails: 1 + @UserGetter.getUsersByHostname hostname, projection, (error, users) => + @find.calledOnce.should.equal true + @find.calledWith(expectedQuery, projection).should.equal true + done() + describe 'ensureUniqueEmailAddress', -> beforeEach -> @UserGetter.getUserByAnyEmail = sinon.stub() diff --git a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee index 3be7733153..034c8b68ab 100644 --- a/services/web/test/unit/coffee/User/UserUpdaterTests.coffee +++ b/services/web/test/unit/coffee/User/UserUpdaterTests.coffee @@ -90,9 +90,10 @@ describe "UserUpdater", -> @UserUpdater.addEmailAddress @stubbedUser._id, @newEmail, (err)=> @UserGetter.ensureUniqueEmailAddress.called.should.equal true should.not.exist(err) + reversedHostname = @newEmail.split('@')[1].split('').reverse().join('') @UserUpdater.updateUser.calledWith( @stubbedUser._id, - $push: { emails: { email: @newEmail, createdAt: sinon.match.date } } + $push: { emails: { email: @newEmail, createdAt: sinon.match.date, reversedHostname: reversedHostname } } ).should.equal true done()