Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow for prefix/suffix to SSO GroupNames #4902

Merged
merged 8 commits into from
Dec 13, 2024
Merged
Show file tree
Hide file tree
Changes from 6 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
6 changes: 6 additions & 0 deletions docs/admin/sso/ldap.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,12 @@ 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.

If group naming policy doesn't allow this exact format, a prefix and suffix can be applied and the length
of these additions can be added to the SSO configuration so they can be removed. e.g. with a prefix and
suffix length of 5 the following group name will work:

- `test_ff-development-owner_test`

hardillb marked this conversation as resolved.
Show resolved Hide resolved
## Managing Admin users

The SSO Configuration can be configured to manage the admin users of the platform by enabling the
Expand Down
6 changes: 6 additions & 0 deletions docs/admin/sso/saml.md
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ 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.

If group naming policy doesn't allow this exact format, a prefix and suffix can be applied and the length
of these additions can be added to the SSO configuration so they can be removed. e.g. with a prefix and
suffix length of 5 the following group name will work:

- `test_ff-development-owner_test`

hardillb marked this conversation as resolved.
Show resolved Hide resolved
## 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 Postfix Length
<template #description>The length of any prefix added to the FlowFuse Group Name format</template>
hardillb marked this conversation as resolved.
Show resolved Hide resolved
</FormRow>
<FormRow v-model="input.options.groupSuffixLength" :error="groupSuffixLengthError" type="number">
Group Name Postfix Length
hardillb marked this conversation as resolved.
Show resolved Hide resolved
<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_test',
'test_ff-bteam-owner_test',
'ff-ateam-admin_test'
]
}, app.user, {
groupAssertionName: 'ff-roles',
groupAllTeams: true,
groupPrefixLength: 5,
groupSuffixLength: 5
hardillb marked this conversation as resolved.
Show resolved Hide resolved
})
;(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)
})
})
})
Loading