Skip to content

Commit f0f2d92

Browse files
kushalshit27mgyarmathy
authored andcommitted
feat: Add support for Connection Profiles and Express Configuration on Clients (auth0#1204)
1 parent 95c6e5d commit f0f2d92

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

41 files changed

+9022
-4264
lines changed

docs/resource-specific-documentation.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -575,3 +575,82 @@ phoneProviders:
575575
}
576576
]
577577
```
578+
579+
## Connection Profiles
580+
581+
Application specific configuration for use with the OIN Express Configuration feature
582+
583+
### YAML Example
584+
585+
```yaml
586+
# Contents of ./tenant.yaml
587+
connectionProfiles:
588+
- name: 'Enterprise SSO Profile'
589+
organization:
590+
show_as_button: 'required'
591+
assign_membership_on_login: 'required'
592+
connection_name_prefix_template: 'org-{organization_name}'
593+
enabled_features:
594+
- scim
595+
- universal_logout
596+
strategy_overrides:
597+
samlp:
598+
enabled_features:
599+
- universal_logout
600+
oidc:
601+
enabled_features:
602+
- scim
603+
- universal_logout
604+
- name: 'Basic Connection Profile'
605+
organization:
606+
show_as_button: 'optional'
607+
assign_membership_on_login: 'optional'
608+
enabled_features:
609+
- scim
610+
```
611+
612+
### Directory Example
613+
614+
File: `./connection-profiles/Enterprise SSO Profile.json`
615+
616+
```json
617+
{
618+
"name": "Enterprise SSO Profile",
619+
"organization": {
620+
"show_as_button": "required",
621+
"assign_membership_on_login": "required"
622+
},
623+
"connection_name_prefix_template": "org-{organization_name}",
624+
"enabled_features": ["scim", "universal_logout"],
625+
"strategy_overrides": {
626+
"samlp": {
627+
"enabled_features": ["universal_logout"]
628+
},
629+
"oidc": {
630+
"enabled_features": ["scim", "universal_logout"]
631+
}
632+
}
633+
}
634+
```
635+
636+
### Express Configuration on Clients
637+
638+
Connection profiles are used in conjunction with the `express_configuration` property on client applications: (In order to use express_configuration app_type should not be 'express_configuration')
639+
640+
```yaml
641+
clients:
642+
- name: 'My Enterprise App'
643+
app_type: 'regular_web'
644+
express_configuration:
645+
initiate_login_uri_template: 'https://myapp.com/sso/start?org={organization_name}&conn={connection_name}'
646+
user_attribute_profile_id: 'My User Attribute Profile'
647+
connection_profile_id: 'Enterprise SSO Profile' # Reference to connection profile
648+
enable_client: true
649+
enable_organization: true
650+
okta_oin_client_id: 'My Okta OIN Client'
651+
admin_login_domain: 'login.myapp.com'
652+
linked_clients:
653+
- client_id: 'client_id_of_mobile_app'
654+
```
655+
656+
For more details, see the [Management API documentation](https://auth0.com/docs/api/management/v2).
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"name": "My App with Express Config",
3+
"app_type": "regular_web",
4+
"express_configuration": {
5+
"initiate_login_uri_template": "https://myapp.com/sso/start?org={organization_name}&conn={connection_name}",
6+
"user_attribute_profile_id": "My User Attribute Profile",
7+
"connection_profile_id": "Enterprise SSO Profile",
8+
"enable_client": true,
9+
"enable_organization": true,
10+
"okta_oin_client_id": "My Okta OIN Client",
11+
"admin_login_domain": "login.myapp.com"
12+
}
13+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"name": "Basic Connection Profile",
3+
"organization": {
4+
"show_as_button": "optional",
5+
"assign_membership_on_login": "optional"
6+
},
7+
"enabled_features": [
8+
"scim"
9+
]
10+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
{
2+
"name": "Enterprise SSO Profile",
3+
"organization": {
4+
"show_as_button": "required",
5+
"assign_membership_on_login": "required"
6+
},
7+
"connection_name_prefix_template": "org-{org_name}",
8+
"enabled_features": [
9+
"scim",
10+
"universal_logout"
11+
],
12+
"strategy_overrides": {
13+
"samlp": {
14+
"enabled_features": [
15+
"universal_logout"
16+
],
17+
"connection_config": {}
18+
},
19+
"oidc": {
20+
"enabled_features": [
21+
"scim",
22+
"universal_logout"
23+
],
24+
"connection_config": {}
25+
}
26+
}
27+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "My User Attribute Profile",
3+
"description": "My User Attribute Profile Description",
4+
"user_attributes": [
5+
{
6+
"name": "email",
7+
"description": "Email",
8+
"type": "email",
9+
"required": true
10+
}
11+
]
12+
}

examples/yaml/tenant.yaml

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,20 @@ clients:
4242
name: "My Resource Server Client"
4343
app_type: "resource_server"
4444
resource_server_identifier: "https://##ENV##.myapp.com/api/v1"
45+
-
46+
name: "My Okta OIN Client"
47+
app_type: "regular_web"
48+
-
49+
name: "My Express App"
50+
app_type: "regular_web"
51+
express_configuration:
52+
initiate_login_uri_template: "https://myapp.com/sso/start?org={organization_name}&conn={connection_name}"
53+
user_attribute_profile_id: "My User Attribute Profile"
54+
connection_profile_id: "Enterprise SSO Profile"
55+
enable_client: true
56+
enable_organization: true
57+
okta_oin_client_id: "My Okta OIN Client"
58+
admin_login_domain: "login.myapp.com"
4559

4660
databases:
4761
- name: "users"
@@ -372,3 +386,36 @@ attackProtection:
372386
max_attempts: 50
373387
rate: 1200
374388

389+
connectionProfiles:
390+
- name: "Enterprise SSO Profile"
391+
organization:
392+
show_as_button: "required"
393+
assign_membership_on_login: "required"
394+
connection_name_prefix_template: "org-{org_name}"
395+
enabled_features:
396+
- scim
397+
- universal_logout
398+
strategy_overrides:
399+
samlp:
400+
enabled_features:
401+
- universal_logout
402+
oidc:
403+
enabled_features:
404+
- scim
405+
- universal_logout
406+
- name: "Basic Connection Profile"
407+
organization:
408+
show_as_button: "optional"
409+
assign_membership_on_login: "optional"
410+
enabled_features:
411+
- scim
412+
413+
userAttributeProfiles:
414+
- name: "My User Attribute Profile"
415+
description: "My User Attribute Profile Description"
416+
user_attributes:
417+
- name: "email"
418+
description: "Email"
419+
type: "email"
420+
required: true
421+

package-lock.json

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
"homepage": "https://github.com/auth0/auth0-deploy-cli#readme",
3434
"dependencies": {
3535
"ajv": "^6.12.6",
36-
"auth0": "^4.36.0",
36+
"auth0": "^4.37.0",
3737
"dot-prop": "^5.3.0",
3838
"fs-extra": "^10.1.0",
3939
"js-yaml": "^4.1.1",

src/context/directory/handlers/clients.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@ function parse(context: DirectoryContext): ParsedClients {
5454

5555
async function dump(context: DirectoryContext): Promise<void> {
5656
const { clients } = context.assets;
57+
const { userAttributeProfiles, connectionProfiles } = context.assets;
5758

5859
if (!clients) return; // Skip, nothing to dump
5960

@@ -73,6 +74,40 @@ async function dump(context: DirectoryContext): Promise<void> {
7374

7475
client.custom_login_page = `./${clientName}_custom_login_page.html`;
7576
}
77+
78+
if (client.express_configuration) {
79+
// map ids to names for user attribute profiles
80+
const userAttributeProfileId = client?.express_configuration?.user_attribute_profile_id;
81+
if (client.express_configuration && userAttributeProfileId) {
82+
const p = userAttributeProfiles?.find((uap) => uap.id === userAttributeProfileId);
83+
client.express_configuration.user_attribute_profile_id = p?.name || userAttributeProfileId;
84+
}
85+
86+
// map ids to names for connection profiles
87+
const connectionProfilesProfileId = client?.express_configuration?.connection_profile_id;
88+
if (client.express_configuration && connectionProfilesProfileId) {
89+
const c = connectionProfiles?.find((uap) => uap.id === connectionProfilesProfileId);
90+
client.express_configuration.connection_profile_id = c?.name || connectionProfilesProfileId;
91+
}
92+
93+
// map ids to names for okta oin clients
94+
const oktaOinClientId = client?.express_configuration?.okta_oin_client_id;
95+
if (client.express_configuration && oktaOinClientId) {
96+
const o = clients?.find((uap) => uap.client_id === oktaOinClientId);
97+
client.express_configuration.okta_oin_client_id = o?.name || oktaOinClientId;
98+
}
99+
}
100+
101+
if (client.app_type === 'express_configuration') {
102+
// only keep relevant fields for express configuration
103+
client = {
104+
name: client.name,
105+
app_type: client.app_type,
106+
client_authentication_methods: client.client_authentication_methods,
107+
organization_require_behavior: client.organization_require_behavior,
108+
} as Client;
109+
}
110+
76111
dumpJSON(clientFile, clearClientArrays(client));
77112
});
78113
}
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
import path from 'path';
2+
import fs from 'fs-extra';
3+
import { ConnectionProfile } from 'auth0';
4+
import { constants } from '../../../tools';
5+
import log from '../../../logger';
6+
7+
import { dumpJSON, existsMustBeDir, getFiles, loadJSON, sanitize } from '../../../utils';
8+
import DirectoryContext from '..';
9+
import { ParsedAsset } from '../../../types';
10+
11+
type ParsedConnectionProfiles = ParsedAsset<'connectionProfiles', Partial<ConnectionProfile>[]>;
12+
13+
function parse(context: DirectoryContext): ParsedConnectionProfiles {
14+
const connectionProfilesFolder = path.join(
15+
context.filePath,
16+
constants.CONNECTION_PROFILES_DIRECTORY
17+
);
18+
if (!existsMustBeDir(connectionProfilesFolder)) return { connectionProfiles: null }; // Skip
19+
20+
const files = getFiles(connectionProfilesFolder, ['.json']);
21+
22+
const connectionProfiles = files.map((f) => {
23+
const profile = {
24+
...loadJSON(f, {
25+
mappings: context.mappings,
26+
disableKeywordReplacement: context.disableKeywordReplacement,
27+
}),
28+
};
29+
return profile;
30+
});
31+
32+
return {
33+
connectionProfiles,
34+
};
35+
}
36+
37+
async function dump(context: DirectoryContext): Promise<void> {
38+
const { connectionProfiles } = context.assets;
39+
if (!connectionProfiles) return;
40+
41+
const connectionProfilesFolder = path.join(
42+
context.filePath,
43+
constants.CONNECTION_PROFILES_DIRECTORY
44+
);
45+
fs.ensureDirSync(connectionProfilesFolder);
46+
47+
connectionProfiles.forEach((profile) => {
48+
const profileFile = path.join(connectionProfilesFolder, sanitize(`${profile.name}.json`));
49+
log.info(`Writing ${profileFile}`);
50+
51+
// Remove read-only fields
52+
if ('id' in profile) {
53+
delete profile.id;
54+
}
55+
56+
dumpJSON(profileFile, profile);
57+
});
58+
}
59+
60+
const connectionProfilesHandler = {
61+
parse,
62+
dump,
63+
};
64+
65+
export default connectionProfilesHandler;

0 commit comments

Comments
 (0)