Skip to content

Commit 3f561ee

Browse files
rolodatokyle-ssg
andauthored
feat: Copy ACS URL for SAML configurations to clipboard. Disable editing SAML configuration names (#4494)
Co-authored-by: kyle-ssg <[email protected]>
1 parent 5cfdaba commit 3f561ee

File tree

4 files changed

+83
-34
lines changed

4 files changed

+83
-34
lines changed

docs/docs/system-administration/authentication/01-SAML/index.md

+13-11
Original file line numberDiff line numberDiff line change
@@ -15,19 +15,22 @@ SAML tab, you'll be able to configure it.
1515

1616
In the UI, you will be able to configure the following fields.
1717

18-
**Name:** (**Required**) A short name for the organisation, used as the input when clicking "Single Sign-on" at login
19-
(note this is unique across all tenants and will form part of the URL so should only be alphanumeric + '-,\_').
18+
**Name:** (**Required**) A short name for the organisation, used as the input when clicking "Single Sign-On" at login.
19+
This name must be unique across all Flagsmith organisations and forms part of the URL that your identity provider will
20+
post SAML messages to during authentication.
2021

21-
**Frontend URL**: (**Required**) This should be the base URL of the Flagsmith dashboard.
22+
**Frontend URL**: (**Required**) This should be the base URL of the Flagsmith dashboard. Users will be redirected here
23+
after authenticating successfully.
2224

23-
**Allow IdP initiated**: This field determines whether logins can be initiated from the IdP.
25+
**Allow IdP-initiated**: If enabled, users will be able to log in directly from your identity provider without needing
26+
to visit the Flagsmith login page.
2427

25-
**IdP metadata xml**: The metadata from the IdP.
28+
**IdP metadata XML**: The metadata from your identity provider.
2629

2730
Once you have configured your identity provider, you can download the service provider metadata XML document with the
2831
button "Download Service Provider Metadata".
2932

30-
### Assertion Consumer Service URL
33+
### Assertion consumer service URL
3134

3235
The assertion consumer service (ACS) URL, also known as single sign-on URL, for this SAML configuration will be at the
3336
following path, replacing `flagsmith.example.com` with your Flagsmith API's domain:
@@ -66,12 +69,11 @@ Flagsmith also maps user attributes from the following claims in the SAML assert
6669

6770
| Flagsmith attribute | IdP claims |
6871
| ------------------- | ---------------------------------------------------- |
69-
| `email` | `mail`, `email` or `emailAddress` |
70-
| `first_name` | `gn`, `givenName` or the first part of `displayName` |
71-
| `last_name` | `sn`, `surname` or the second part of `displayName` |
72+
| Email | `mail`, `email` or `emailAddress` |
73+
| First name | `gn`, `givenName` or the first part of `displayName` |
74+
| Last name | `sn`, `surname` or the second part of `displayName` |
7275

73-
You can override these mappings by adding the corresponding IdP attribute names to your SAML configuration from the
74-
Django admin interface.
76+
To add custom attribute mappings, edit your SAML configuration and open the Attribute Mappings tab.
7577

7678
## Permissions for SAML users
7779

frontend/web/components/SAMLAttributeMappingTable.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -37,11 +37,11 @@ const SAMLAttributeMappingTable: FC<SAMLAttributeMappingTableType> = ({
3737
header={
3838
<Row className='table-header'>
3939
<Flex className='table-column px-3'>
40-
<div className='font-weight-medium'>SAML Attribute Name</div>
40+
<div className='font-weight-medium'>SAML attribute name</div>
4141
</Flex>
4242
<Flex className='table-column px-3'>
4343
<div className='table-column' style={{ width: '375px' }}>
44-
IDP Attribute Name
44+
IdP attribute name
4545
</div>
4646
</Flex>
4747
</Row>

frontend/web/components/SamlTab.tsx

+20-10
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import CreateSAML from './modals/CreateSAML'
1313
import Switch from './Switch'
1414
import { SAMLConfiguration } from 'common/types/responses'
1515
import PlanBasedBanner from './PlanBasedAccess'
16-
16+
1717
export type SamlTabType = {
1818
organisationId: number
1919
}
20+
2021
const SamlTab: FC<SamlTabType> = ({ organisationId }) => {
2122
const { data } = useGetSamlConfigurationsQuery({
2223
organisation_id: organisationId,
@@ -37,15 +38,15 @@ const SamlTab: FC<SamlTabType> = ({ organisationId }) => {
3738
return (
3839
<PlanBasedBanner feature={'SAML'} theme={'page'} className='mt-3'>
3940
<PageTitle
40-
title={'SAML Configuration'}
41+
title={'SAML Configurations'}
4142
cta={
4243
<Button
4344
className='text-right'
4445
onClick={() => {
4546
openCreateSAML('Create SAML configuration', organisationId)
4647
}}
4748
>
48-
{'Create a SAML Configuration'}
49+
{'Create a SAML configuration'}
4950
</Button>
5051
}
5152
/>
@@ -62,11 +63,17 @@ const SamlTab: FC<SamlTabType> = ({ organisationId }) => {
6263
}
6364
header={
6465
<Row className='table-header'>
65-
<Flex className='table-column px-3'>
66-
<div className='font-weight-medium'>SAML Name</div>
66+
<Flex className='table-column'>
67+
<div className='font-weight-medium'>Configuration name</div>
6768
</Flex>
68-
<div className='table-column' style={{ width: '205px' }}>
69-
Allow IDP Initiated
69+
<div
70+
className='table-column d-none d-md-block'
71+
style={{ width: '150px' }}
72+
>
73+
Allow IdP-initiated
74+
</div>
75+
<div style={{ width: 90 }} className='table-column'>
76+
Action
7077
</div>
7178
</Row>
7279
}
@@ -81,16 +88,19 @@ const SamlTab: FC<SamlTabType> = ({ organisationId }) => {
8188
)
8289
}}
8390
space
84-
className='list-item clickable cursor-pointer'
91+
className='list-item py-2 py-md-0 clickable cursor-pointer'
8592
key={samlConf.name}
8693
>
8794
<Flex className='table-column px-3'>
8895
<div className='font-weight-medium mb-1'>{samlConf.name}</div>
8996
</Flex>
90-
<div className='table-column' style={{ width: '95px' }}>
97+
<div
98+
className='table-column d-none d-md-flex gap-4 align-items-center'
99+
style={{ width: '150px' }}
100+
>
91101
<Switch checked={samlConf.allow_idp_initiated} />
92102
</div>
93-
<div className='table-column'>
103+
<div className='table-column' style={{ width: 90 }}>
94104
<Button
95105
id='delete-invite'
96106
type='button'

frontend/web/components/modals/CreateSAML.tsx

+48-11
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import Tabs from 'components/base/forms/Tabs'
2121
import TabItem from 'components/base/forms/TabItem'
2222
import { AttributeName } from 'common/types/responses'
2323
import SAMLAttributeMappingTable from 'components/SAMLAttributeMappingTable'
24+
import Input from 'components/base/forms/Input'
25+
import Icon from 'components/Icon'
26+
import Project from 'common/project'
2427

2528
type CreateSAML = {
2629
organisationId: number
@@ -52,6 +55,12 @@ const CreateSAML: FC<CreateSAML> = ({ organisationId, samlName }) => {
5255
{ skip: !samlName },
5356
)
5457

58+
const acsUrl = new URL(`/auth/saml/${name}/response/`, Project.api).href
59+
const copyAcsUrl = async () => {
60+
await navigator.clipboard.writeText(acsUrl)
61+
toast('Copied to clipboard')
62+
}
63+
5564
useEffect(() => {
5665
if (isSuccess && data) {
5766
setPreviousName(data.name)
@@ -101,9 +110,10 @@ const CreateSAML: FC<CreateSAML> = ({ organisationId, samlName }) => {
101110
className='mt-2'
102111
title='Name*'
103112
data-test='saml-name'
104-
tooltip='A short name for the organization, used as the input when clicking "Single Sign-on" at login, should only consist of alphanumeric characters, plus (+), underscore (_), and hyphen (-).'
113+
tooltip='An URL-friendly name for this configuration, used as the input when selecting "Single Sign-On" at login. It determines the Assertion Consumer Service (ACS) URL that your identity provider must post SAML responses to. This cannot be changed after the SAML configuration is created.'
105114
tooltipPlace='right'
106115
value={name}
116+
disabled={isEdit}
107117
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
108118
const newName = Utils.safeParseEventValue(event).replace(/ /g, '_')
109119
if (validateName(newName)) {
@@ -121,7 +131,7 @@ const CreateSAML: FC<CreateSAML> = ({ organisationId, samlName }) => {
121131
className='mt-2 mb-4'
122132
title='Frontend URL*'
123133
data-test='frontend-url'
124-
tooltip='The base URL of the Flagsmith dashboard'
134+
tooltip='The base URL of the Flagsmith dashboard. Users will be redirected here after authenticating successfully.'
125135
tooltipPlace='right'
126136
value={data?.frontend_url || frontendUrl}
127137
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {
@@ -136,8 +146,8 @@ const CreateSAML: FC<CreateSAML> = ({ organisationId, samlName }) => {
136146
/>
137147
<InputGroup
138148
className='mt-2 mb-4'
139-
title='Allow IDP initiated'
140-
tooltip='Determines whether logins can be initiated from the IDP'
149+
title='Allow IdP-initiated logins'
150+
tooltip="Enable this to allow logins initiated by your identity provider. If disabled, users can only log in from Flagsmith's login page."
141151
tooltipPlace='right'
142152
component={
143153
<Switch
@@ -151,7 +161,7 @@ const CreateSAML: FC<CreateSAML> = ({ organisationId, samlName }) => {
151161
<FormGroup className='mb-1'>
152162
<div className='mt-2 p-0'>
153163
<Row>
154-
<label className='form-label'>IDP Metadata XML</label>
164+
<label className='form-label'>IdP metadata XML</label>
155165
{data?.idp_metadata_xml && (
156166
<div className='ml-2 clickable' onClick={downloadIDPMetadata}>
157167
<Tooltip
@@ -203,6 +213,35 @@ const CreateSAML: FC<CreateSAML> = ({ organisationId, samlName }) => {
203213
<ErrorMessage error={createError || updateError} />
204214
</div>
205215
)}
216+
{isEdit && (
217+
<div className='mt-12'>
218+
<Tooltip
219+
title={
220+
<label>
221+
Assertion Consumer Service (ACS) URL
222+
<Icon name='info-outlined' />
223+
</label>
224+
}
225+
>
226+
Also known as sign-on URL. Your identity provider needs to know this
227+
URL to send SAML responses to it.
228+
</Tooltip>
229+
<div
230+
onClick={(e) => e.stopPropagation()}
231+
className='flex flex-row gap-2'
232+
>
233+
<Input className='w-full flex-1' value={acsUrl} readOnly />
234+
<Button
235+
onClick={() => {
236+
copyAcsUrl()
237+
}}
238+
className='me-2 btn-with-icon'
239+
>
240+
<Icon name='copy' width={20} fill='#656D7B' />
241+
</Button>
242+
</div>
243+
</div>
244+
)}
206245
<div className='text-right py-2'>
207246
{isEdit && (
208247
<Button
@@ -273,13 +312,11 @@ const CreateSAML: FC<CreateSAML> = ({ organisationId, samlName }) => {
273312
return (
274313
<div className='create-feature-tab mt-3'>
275314
<InputGroup
276-
title={'SAML Attribute Name*'}
277-
tooltip='This is the attribute name where you want to store the information received from the SAML identity provider'
278-
tooltipPlace='right'
315+
title={'Flagsmith user attribute'}
279316
component={
280317
<Select
281318
value={djangoAttributeName}
282-
placeholder='Select a SAML attribute name'
319+
placeholder='Select a Flagsmith user attribute'
283320
options={samlAttributes}
284321
onChange={(m: samlAttributeType) => {
285322
setDjangoAttributeName(m)
@@ -290,9 +327,9 @@ const CreateSAML: FC<CreateSAML> = ({ organisationId, samlName }) => {
290327
/>
291328
<InputGroup
292329
className='mt-2'
293-
title='IDP Attribute Name*'
330+
title='IdP attribute name*'
294331
data-test='attribute-name'
295-
tooltip='This is the specific value of the attribute sent by the SAML identity provider'
332+
tooltip='The value(s) of this SAML attribute from your identity provider will be saved to the selected Flagsmith user attribute.'
296333
tooltipPlace='right'
297334
value={ipdAttributeName}
298335
onChange={(event: React.ChangeEvent<HTMLInputElement>) => {

0 commit comments

Comments
 (0)