Skip to content

Commit

Permalink
Merge pull request #4902 from FlowFuse/sso-group-prefix
Browse files Browse the repository at this point in the history
Allow for prefix/suffix to SSO GroupNames
  • Loading branch information
knolleary authored Dec 13, 2024
2 parents 308da63 + 9d3306b commit 529c85c
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 5 deletions.
3 changes: 3 additions & 0 deletions docs/admin/sso/ldap.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ the groups in the LDAP Provider - rather than using the team's id. However, a te
by a team owner. Doing so will break the link between the group and the team membership - so should only
be done with care.

An optional prefix and suffix can be include in the group name to support LDAP providers that have existing naming policies. The SSO configuration can be configured with the lengths of these values so they will be stripped off before the group name is validated.

For example, if an organisation requires all groups to begin with `acme-org-`, a prefix length of `9` can be set and the group `acme-org-ff-development-owner` will be handled as `ff-development-owner`.
## Managing Admin users

The SSO Configuration can be configured to manage the admin users of the platform by enabling the
Expand Down
3 changes: 3 additions & 0 deletions docs/admin/sso/saml.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ the groups in the SAML Provider - rather than using the team's id. However, a te
by a team owner. Doing so will break the link between the group and the team membership - so should only
be done with care.

An optional prefix and suffix can be include in the group name to support SAML providers that have existing naming policies. The SSO configuration can be configured with the lengths of these values so they will be stripped off before the group name is validated.

For example, if an organisation requires all groups to begin with `acme-org-`, a prefix length of `9` can be set and the group `acme-org-ff-development-owner` will be handled as `ff-development-owner`.
## Managing Admin users

The SSO Configuration can be configured to managed the admin users of the platform by enabling the
Expand Down
19 changes: 17 additions & 2 deletions forge/ee/lib/sso/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,9 +310,17 @@ module.exports.init = async function (app) {
const desiredTeamMemberships = {}
app.log.debug(`SAML Group Assertions for ${user.username} ${JSON.stringify(groupAssertions)}`)
groupAssertions.forEach(ga => {
// Trim prefix/postfix from group name
let shortGA = ga
if (providerOpts.groupPrefixLength || providerOpts.groupSuffixLength) {
const start = providerOpts.groupPrefixLength || 0
const end = providerOpts.groupSuffixLength || 0
shortGA = ga.slice(start, (end * -1))
app.log.debug(`Converting Group name ${ga} to ${shortGA}`)
}
// Parse the group name - format: 'ff-SLUG-ROLE'
// Generate a slug->role object (desiredTeamMemberships)
const match = /^ff-(.+)-([^-]+)$/.exec(ga)
const match = /^ff-(.+)-([^-]+)$/.exec(shortGA)
if (match) {
const teamSlug = match[1]
const teamRoleName = match[2]
Expand Down Expand Up @@ -444,7 +452,14 @@ module.exports.init = async function (app) {
const desiredTeamMemberships = {}
const groupRegEx = /^ff-(.+)-([^-]+)$/
for (const i in searchEntries) {
const match = groupRegEx.exec(searchEntries[i].cn)
let shortCN = searchEntries[i].cn
if (providerOpts.groupPrefixLength || providerOpts.groupSuffixLength) {
// Trim prefix and postfix
const start = providerOpts.groupPrefixLength || 0
const end = providerOpts.groupSuffixLength || 0
shortCN = searchEntries[i].cn.slice(start, (end * -1))
}
const match = groupRegEx.exec(shortCN)
if (match) {
app.log.debug(`Found group ${searchEntries[i].cn} for user ${user.username}`)
const teamSlug = match[1]
Expand Down
36 changes: 33 additions & 3 deletions frontend/src/pages/admin/Settings/SSO/createEditProvider.vue
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,14 @@
<template #description>The name of the base object to search for groups</template>
</FormRow>
</div>
<FormRow v-model="input.options.groupPrefixLength" :error="groupPrefixLengthError" type="number">
Group Name Prefix Length
<template #description>The length of any prefix added to the FlowFuse Group Name format</template>
</FormRow>
<FormRow v-model="input.options.groupSuffixLength" :error="groupSuffixLengthError" type="number">
Group Name Suffix Length
<template #description>The length of any suffix added to the FlowFuse Group Name format</template>
</FormRow>
<FormRow v-model="input.options.groupAllTeams" :options="[{ value:true, label: 'Apply to all teams' }, { value:false, label: 'Apply to selected teams' }]">
Team Scope
<template #description>Should this apply to all teams on the platform, or just a restricted list of teams</template>
Expand Down Expand Up @@ -164,7 +172,9 @@ export default {
groupAssertionName: '',
groupsDN: '',
groupMapping: false,
groupAdminName: ''
groupAdminName: '',
groupPrefixLength: 0,
groupSuffixLength: 0
}
},
errors: {},
Expand All @@ -182,7 +192,7 @@ export default {
isGroupOptionsValid () {
return !this.input.options.groupMapping || (
(this.input.type === 'saml' ? this.isGroupAssertionNameValid : this.isGroupsDNValid) &&
this.isGroupAdminNameValid
this.isGroupAdminNameValid && this.isGroupPrefixValid && this.isGroupSuffixValid
)
},
isGroupAssertionNameValid () {
Expand All @@ -197,6 +207,18 @@ export default {
groupsDNError () {
return !this.isGroupsDNValid ? 'Group DN is required' : ''
},
groupPrefixLengthError () {
return this.input.options.groupPrefixLength < 0 ? 'Must be a greater or equal to 0' : ''
},
isGroupPrefixValid () {
return this.input.options.groupPrefixLength >= 0
},
groupSuffixLengthError () {
return this.input.options.groupSuffixLength < 0 ? 'Must be a greater or equal to 0' : ''
},
isGroupSuffixValid () {
return this.input.options.groupSuffixLength >= 0
},
isGroupAdminNameValid () {
return !this.input.options.groupAdmin || (this.input.options.groupAdminName && this.input.options.groupAdminName.length > 0)
},
Expand Down Expand Up @@ -303,7 +325,9 @@ export default {
groupOtherTeams: false,
groupAdmin: false,
groupAdminName: 'ff-admins',
groupAssertionName: 'ff-roles'
groupAssertionName: 'ff-roles',
groupPrefixLength: 0,
groupSuffixLength: 0
}
} else {
this.loading = true
Expand Down Expand Up @@ -343,6 +367,12 @@ export default {
this.input.options.tlsVerifyServer = true
}
}
if (this.provider.options.groupPrefixLength === undefined) {
this.input.options.groupPrefixLength = 0
}
if (this.provider.options.groupSuffixLength === undefined) {
this.input.options.groupSuffixLength = 0
}
this.originalValues = JSON.stringify(this.input)
},
async testProvider () {
Expand Down
24 changes: 24 additions & 0 deletions test/unit/forge/ee/lib/sso/index_spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -365,5 +365,29 @@ d
})
;(await app.db.models.TeamMember.getTeamMembership(app.user.id, teams.ATeam.id)).should.have.property('role', Roles.Owner)
})
it('strip prefix and suffix from SAML groups', async function () {
// This should remove ownership from Alice in Team A

// Starting state:
// Alice owner ATeam

// Expected result:
// Alice owner ATeam - unchanged
await app.sso.updateTeamMembership({
'ff-roles': [
'test_ff-ateam-magician_err',
'test_ff-ateam-member_test2',
'test_ff-bteam-owner_test2',
'ff-ateam-admin_test2'
]
}, app.user, {
groupAssertionName: 'ff-roles',
groupAllTeams: true,
groupPrefixLength: 5,
groupSuffixLength: 6
})
;(await app.db.models.TeamMember.getTeamMembership(app.user.id, teams.ATeam.id)).should.have.property('role', Roles.Member)
;(await app.db.models.TeamMember.getTeamMembership(app.user.id, teams.BTeam.id)).should.have.property('role', Roles.Owner)
})
})
})

0 comments on commit 529c85c

Please sign in to comment.