Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 17 additions & 13 deletions forge/ee/routes/sso/social/google.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,20 +49,24 @@ module.exports = fp(async function (app, opts) {
})
const googleUserInfo = userinfo.data
await googleJWTCache.set(request.query.code, {})
if (googleUserInfo.email_verified !== true) {
reply.code(403).send({ code: 'unverified_email', error: 'Google account email is not verified' })
return
}
// Any email on a domain with an SSO provider must go through that provider,
// whether or not Google reports a Workspace (hd) account
const providerId = await app.sso.getProviderForEmail(googleUserInfo.email)
if (providerId) {
reply.send({
url: `/ee/sso/login?u=${encodeURIComponent(googleUserInfo.email)}`
})
return
}
if (googleUserInfo.hd) {
// This is a Google Workspace account - check if there is an SSO provider configured for this domain
const providerId = await app.sso.getProviderForEmail(googleUserInfo.email)
if (!providerId) {
// No SSO provider configured for this domain - redirect to home
reply.send({
error: 'SSO not available for this Google Workspace domain'
})
} else {
// There is an SSO provider configured for this domain - use it
reply.send({
url: `/ee/sso/login?u=${encodeURIComponent(googleUserInfo.email)}`
})
}
// Workspace account on a domain with no SSO provider configured
reply.send({
error: 'SSO not available for this Google Workspace domain'
})
return
}
const user = await app.db.models.User.byUsernameOrEmail(googleUserInfo.email)
Expand Down
88 changes: 88 additions & 0 deletions test/unit/forge/ee/routes/sso/google_spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
const { OAuth2Client } = require('google-auth-library')
const should = require('should') // eslint-disable-line
const sinon = require('sinon')

const setup = require('../../setup')

describe('Google SSO callback', function () {
let app
let userinfo
let codeCounter = 0

before(async function () {
app = await setup({ billing: undefined })
await app.settings.set('platform:sso:google', true)
await app.settings.set('platform:sso:google:clientId', 'test-client-id')

await app.db.models.User.create({ username: 'carol', name: 'Carol', email: 'carol@corp.com', email_verified: true, password: 'ccPassword1!' })
await app.db.models.User.create({ username: 'dave', name: 'Dave', email: 'dave@plain.com', email_verified: true, password: 'ddPassword1!' })
await app.db.models.SAMLProvider.create({
name: 'corp.com',
domainFilter: '@corp.com',
active: true,
options: {
cert: '-----BEGIN CERTIFICATE-----abcde-----END CERTIFICATE-----',
entryPoint: 'https://sso.corp.com/entry'
}
})

sinon.stub(OAuth2Client.prototype, 'getTokenInfo').resolves({ aud: 'test-client-id' })
sinon.stub(OAuth2Client.prototype, 'request').callsFake(async () => ({ data: userinfo }))
})

after(async function () {
sinon.restore()
await app.close()
})

async function googleCallback (info) {
userinfo = info
return app.inject({
method: 'POST',
url: `/ee/sso/login/callback/google?code=code-${codeCounter++}`
})
}

it('redirects a consumer account on an SSO enforced domain to the provider', async function () {
const response = await googleCallback({ email: 'carol@corp.com', email_verified: true })
response.statusCode.should.equal(200)
response.json().should.have.property('url', `/ee/sso/login?u=${encodeURIComponent('carol@corp.com')}`)
response.cookies.should.have.length(0)
})

it('redirects a Workspace account on an SSO enforced domain to the provider', async function () {
const response = await googleCallback({ email: 'carol@corp.com', email_verified: true, hd: 'corp.com' })
response.statusCode.should.equal(200)
response.json().should.have.property('url', `/ee/sso/login?u=${encodeURIComponent('carol@corp.com')}`)
response.cookies.should.have.length(0)
})

it('rejects an account whose email Google reports as unverified', async function () {
const response = await googleCallback({ email: 'dave@plain.com', email_verified: false })
response.statusCode.should.equal(403)
response.json().should.have.property('code', 'unverified_email')
response.cookies.should.have.length(0)
})

it('rejects an account with no email_verified claim', async function () {
const response = await googleCallback({ email: 'dave@plain.com' })
response.statusCode.should.equal(403)
response.json().should.have.property('code', 'unverified_email')
response.cookies.should.have.length(0)
})

it('returns an error for a Workspace account on a domain with no SSO provider', async function () {
const response = await googleCallback({ email: 'dave@plain.com', email_verified: true, hd: 'plain.com' })
response.statusCode.should.equal(200)
response.json().should.have.property('error', 'SSO not available for this Google Workspace domain')
response.cookies.should.have.length(0)
})

it('signs in an existing user on a domain with no SSO provider', async function () {
const response = await googleCallback({ email: 'dave@plain.com', email_verified: true })
response.statusCode.should.equal(200)
response.json().should.have.property('url', '/')
response.cookies.should.have.length(1)
response.cookies[0].name.should.equal('sid')
})
})
Loading