diff --git a/forge/ee/lib/sso/index.js b/forge/ee/lib/sso/index.js index bbb8d38b1f..bea0ff1ac1 100644 --- a/forge/ee/lib/sso/index.js +++ b/forge/ee/lib/sso/index.js @@ -454,6 +454,11 @@ module.exports.init = async function (app) { // This is an array of groups the user belongs to. We expect them to be // of the form 'ff-SLUG-ROLE' - anything else is ignored let groupAssertions = samlUser[providerOpts.groupAssertionName] + if (!groupAssertions && providerOpts.groupOtherTeams) { + // Some IdPs (e.g. Azure) omit the claim when the user is in no groups + app.log.info(`SAML response missing ${providerOpts.groupAssertionName} assertion for ${user.username}, treating as no groups`) + groupAssertions = [] + } if (groupAssertions) { const promises = [] if (!Array.isArray(groupAssertions)) { diff --git a/test/unit/forge/ee/lib/sso/index_spec.js b/test/unit/forge/ee/lib/sso/index_spec.js index 90777bd491..6a91df7009 100644 --- a/test/unit/forge/ee/lib/sso/index_spec.js +++ b/test/unit/forge/ee/lib/sso/index_spec.js @@ -291,6 +291,37 @@ NOY6Z1oJnpttQ9gwyV8euQ3C0Wcjf3+OVQ== }) }) + it('treats missing group assertions as no groups when other teams are allowed', async function () { + // An IdP can omit the claim when the user is in no groups (seen with Azure) + await app.sso.updateTeamMembership({}, app.user, { + groupAssertionName: 'ff-roles', + groupTeams: ['ateam', 'bteam'], + groupOtherTeams: true + }) + should.not.exist(await app.db.models.TeamMember.getTeamMembership(app.user.id, teams.ATeam.id)) + should.not.exist(await app.db.models.TeamMember.getTeamMembership(app.user.id, teams.BTeam.id)) + ;(await app.db.models.TeamMember.getTeamMembership(app.user.id, teams.DTeam.id)).should.have.property('role', Roles.Viewer) + }) + + it('removes admin when group assertions are missing and groupAdmin is set', async function () { + // a second admin, so Alice is not the last one + const otherAdmin = await app.factory.createUser({ admin: true, username: 'sso-admin2', name: 'Other Admin', email: 'sso-admin2@example.com', password: 'ccPassword' }) + try { + await app.sso.updateTeamMembership({}, app.user, { + groupAssertionName: 'ff-roles', + groupTeams: ['ateam', 'bteam'], + groupOtherTeams: true, + groupAdmin: true, + groupAdminName: 'ff-admins' + }) + ;(await app.db.models.User.byId(app.user.id)).should.have.property('admin', false) + } finally { + app.user.admin = true + await app.user.save() + await otherAdmin.destroy() + } + }) + it('applies team membership changes - all teams', async function () { // Apply changes to all teams // Covers: