mirror of
https://github.com/directus/directus.git
synced 2026-04-25 03:00:53 -04:00
Add ability to map oidc groups to Directus roles (#24157)
* add oidc group mapping * add changeset and contributors.yml * add back upstream changes * Run formatter * Fix capitalization in docs description * Re-add lost docs after merge conflict resolution * Make groups claim name configurable * Add OIDC to dictionary * Improve groups check Co-authored-by: Aiden Foxx <aiden.foxx.mail@gmail.com> * Add log message if OIDC group claim is empty * Make groups claim optional --------- Co-authored-by: Rijk van Zanten <rijkvanzanten@me.com> Co-authored-by: Aiden Foxx <aiden.foxx.mail@gmail.com>
This commit is contained in:
6
.changeset/swift-grapes-pump.md
Normal file
6
.changeset/swift-grapes-pump.md
Normal file
@@ -0,0 +1,6 @@
|
||||
---
|
||||
'@directus/api': minor
|
||||
'docs': minor
|
||||
---
|
||||
|
||||
Added ability to map oidc groups to oidc roles
|
||||
@@ -26,6 +26,7 @@ import { createDefaultAccountability } from '../../permissions/utils/create-defa
|
||||
import { AuthenticationService } from '../../services/authentication.js';
|
||||
import { UsersService } from '../../services/users.js';
|
||||
import type { AuthData, AuthDriverOptions, User } from '../../types/index.js';
|
||||
import type { RoleMap } from '../../types/rolemap.js';
|
||||
import asyncHandler from '../../utils/async-handler.js';
|
||||
import { getConfigFromEnv } from '../../utils/get-config-from-env.js';
|
||||
import { getIPFromReq } from '../../utils/get-ip-from-req.js';
|
||||
@@ -39,6 +40,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
||||
redirectUrl: string;
|
||||
usersService: UsersService;
|
||||
config: Record<string, any>;
|
||||
roleMap: RoleMap;
|
||||
|
||||
constructor(options: AuthDriverOptions, config: Record<string, any>) {
|
||||
super(options, config);
|
||||
@@ -69,6 +71,23 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
||||
this.redirectUrl = redirectUrl.toString();
|
||||
this.usersService = new UsersService({ knex: this.knex, schema: this.schema });
|
||||
this.config = additionalConfig;
|
||||
this.roleMap = {};
|
||||
|
||||
const roleMapping = this.config['roleMapping'];
|
||||
|
||||
if (roleMapping) {
|
||||
this.roleMap = roleMapping;
|
||||
}
|
||||
|
||||
// role mapping will fail on login if AUTH_<provider>_ROLE_MAPPING is an array instead of an object.
|
||||
// This happens if the 'json:' prefix is missing from the variable declaration. To save the user from exhaustive debugging, we'll try to fail early here.
|
||||
if (roleMapping instanceof Array) {
|
||||
logger.error(
|
||||
"[OpenID] Expected a JSON-Object as role mapping, got an Array instead. Make sure you declare the variable with 'json:' prefix.",
|
||||
);
|
||||
|
||||
throw new InvalidProviderError();
|
||||
}
|
||||
|
||||
this.client = new Promise((resolve, reject) => {
|
||||
Issuer.discover(issuerUrl)
|
||||
@@ -178,6 +197,22 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
||||
throw handleError(e);
|
||||
}
|
||||
|
||||
let role = this.config['defaultRoleId'];
|
||||
const groupClaimName: string = this.config['groupClaimName'] ?? 'groups';
|
||||
const groups = userInfo[groupClaimName];
|
||||
|
||||
if (Array.isArray(groups)) {
|
||||
for (const key in this.roleMap) {
|
||||
if (groups.includes(key)) {
|
||||
// Overwrite default role if user is member of a group specified in roleMap
|
||||
role = this.roleMap[key];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.debug(`[OpenID] Configured group claim with name "${groupClaimName}" does not exist or is empty.`);
|
||||
}
|
||||
|
||||
// Flatten response to support dot indexes
|
||||
userInfo = flatten(userInfo) as Record<string, unknown>;
|
||||
|
||||
@@ -198,7 +233,7 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
||||
last_name: userInfo['family_name'],
|
||||
email: email,
|
||||
external_identifier: identifier,
|
||||
role: this.config['defaultRoleId'],
|
||||
role: role,
|
||||
auth_data: tokenSet.refresh_token && JSON.stringify({ refreshToken: tokenSet.refresh_token }),
|
||||
};
|
||||
|
||||
@@ -209,6 +244,8 @@ export class OpenIDAuthDriver extends LocalAuthDriver {
|
||||
// user that is about to be updated
|
||||
let emitPayload: Record<string, unknown> = {
|
||||
auth_data: userPayload.auth_data,
|
||||
// Make sure a user's role gets updated if his openid group or role mapping changes
|
||||
role: role,
|
||||
};
|
||||
|
||||
if (syncUserInfo) {
|
||||
|
||||
3
api/src/types/rolemap.ts
Normal file
3
api/src/types/rolemap.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export type RoleMap = {
|
||||
[key: string]: string;
|
||||
};
|
||||
@@ -188,5 +188,6 @@
|
||||
- Zyles
|
||||
- eremannisto
|
||||
- m3Lith
|
||||
- Audiotape-2
|
||||
- vst-name
|
||||
- clonefetch
|
||||
|
||||
@@ -68,6 +68,7 @@
|
||||
(S|s)ubheader
|
||||
(S|s)upabase
|
||||
(T|t)extarea
|
||||
(T|t)heming
|
||||
(T|t)imeseries
|
||||
(T|t)ooltips?
|
||||
(T|t)riage
|
||||
@@ -173,10 +174,10 @@ customizable
|
||||
customizations
|
||||
DateTime
|
||||
DDoS
|
||||
destructure
|
||||
destructured
|
||||
devs
|
||||
Devtools
|
||||
destructure
|
||||
DigitalOcean
|
||||
DigitalOcean's
|
||||
directus
|
||||
@@ -201,8 +202,8 @@ entrypoint
|
||||
Entrypoint
|
||||
env
|
||||
ENV
|
||||
ESM
|
||||
esm
|
||||
ESM
|
||||
Exif
|
||||
fallbacks
|
||||
falsy
|
||||
@@ -309,10 +310,11 @@ O2M
|
||||
O2Ms
|
||||
O2O
|
||||
OAuth2?
|
||||
OpenAI
|
||||
OIDC
|
||||
Okta
|
||||
omnichannel
|
||||
on-prem
|
||||
OpenAI
|
||||
OpenAPI
|
||||
OpenID
|
||||
OracleDB
|
||||
@@ -414,7 +416,6 @@ SVGs?
|
||||
TablePlus
|
||||
tfa
|
||||
TFA
|
||||
(T|t)heming
|
||||
TileJSON
|
||||
TinyMCE
|
||||
TLS
|
||||
|
||||
@@ -859,22 +859,23 @@ Directus users "External Identifier".
|
||||
|
||||
OpenID is an authentication protocol built on OAuth 2.0, and should be preferred over standard OAuth 2.0 where possible.
|
||||
|
||||
| Variable | Description | Default Value |
|
||||
| ------------------------------------------- | --------------------------------------------------------------------------------------------------------- | ---------------------- |
|
||||
| `AUTH_<PROVIDER>_CLIENT_ID` | Client identifier for the external service. | -- |
|
||||
| `AUTH_<PROVIDER>_CLIENT_SECRET` | Client secret for the external service. | -- |
|
||||
| `AUTH_<PROVIDER>_SCOPE` | A white-space separated list of permissions to request. | `openid profile email` |
|
||||
| `AUTH_<PROVIDER>_ISSUER_URL` | OpenID `.well-known` discovery document URL of the external service. | -- |
|
||||
| `AUTH_<PROVIDER>_IDENTIFIER_KEY` | User profile identifier key <sup>[1]</sup>. | `sub`<sup>[2]</sup> |
|
||||
| `AUTH_<PROVIDER>_ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` |
|
||||
| `AUTH_<PROVIDER>_REQUIRE_VERIFIED_EMAIL` | Require created users to have a verified email address. | `false` |
|
||||
| `AUTH_<PROVIDER>_DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- |
|
||||
| `AUTH_<PROVIDER>_SYNC_USER_INFO` | Set user's first name, last name and email from provider's user info on each login. | `false` |
|
||||
| `AUTH_<PROVIDER>_ICON` | SVG icon to display with the login link. [See options here](/user-guide/overview/glossary#icons). | `account_circle` |
|
||||
| `AUTH_<PROVIDER>_LABEL` | Text to be presented on SSO button within App. | `<PROVIDER>` |
|
||||
| `AUTH_<PROVIDER>_PARAMS` | Custom query parameters applied to the authorization URL. | -- |
|
||||
| `AUTH_<PROVIDER>_REDIRECT_ALLOW_LIST` | A comma-separated list of external URLs (including paths) allowed for redirecting after successful login. | -- |
|
||||
| `AUTH_<PROVIDER>_LOGIN_TIMEOUT` | Time-Out for successful login in SSO. | `5m` |
|
||||
| Variable | Description | Default Value |
|
||||
| ------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------- |
|
||||
| `AUTH_<PROVIDER>_CLIENT_ID` | Client identifier for the external service. | -- |
|
||||
| `AUTH_<PROVIDER>_CLIENT_SECRET` | Client secret for the external service. | -- |
|
||||
| `AUTH_<PROVIDER>_SCOPE` | A white-space separated list of permissions to request. | `openid profile email` |
|
||||
| `AUTH_<PROVIDER>_ISSUER_URL` | OpenID `.well-known` discovery document URL of the external service. | -- |
|
||||
| `AUTH_<PROVIDER>_IDENTIFIER_KEY` | User profile identifier key <sup>[1]</sup>. | `sub`<sup>[2]</sup> |
|
||||
| `AUTH_<PROVIDER>_ALLOW_PUBLIC_REGISTRATION` | Automatically create accounts for authenticating users. | `false` |
|
||||
| `AUTH_<PROVIDER>_REQUIRE_VERIFIED_EMAIL` | Require created users to have a verified email address. | `false` |
|
||||
| `AUTH_<PROVIDER>_DEFAULT_ROLE_ID` | A Directus role ID to assign created users. | -- |
|
||||
| `AUTH_<PROVIDER>_SYNC_USER_INFO` | Set user's first name, last name and email from provider's user info on each login. | `false` |
|
||||
| `AUTH_<PROVIDER>_ICON` | SVG icon to display with the login link. [See options here](/user-guide/overview/glossary#icons). | `account_circle` |
|
||||
| `AUTH_<PROVIDER>_LABEL` | Text to be presented on SSO button within App. | `<PROVIDER>` |
|
||||
| `AUTH_<PROVIDER>_PARAMS` | Custom query parameters applied to the authorization URL. | -- |
|
||||
| `AUTH_<PROVIDER>_REDIRECT_ALLOW_LIST` | A comma-separated list of external URLs (including paths) allowed for redirecting after successful login. | -- |
|
||||
| `AUTH_<PROVIDER>_ROLE_MAPPING` | A JSON object in the form of `{ "openid_group_name": "directus_role_id" }` that you can use to map OpenID groups to Directus roles <sup>[3]</sup>. If not specified, falls back to `AUTH_<PROVIDER>_DEFAULT_ROLE_ID` | -- |
|
||||
| `AUTH_<PROVIDER>_GROUP_CLAIM_NAME` | The name of the OIDC claim that contains your user's groups. | `groups` |
|
||||
|
||||
<sup>[1]</sup> When authenticating, Directus will match the identifier value from the external user profile to a
|
||||
Directus users "External Identifier".
|
||||
@@ -882,6 +883,20 @@ Directus users "External Identifier".
|
||||
<sup>[2]</sup> `sub` represents a unique user identifier defined by the OpenID provider. For users not relying on
|
||||
`PUBLIC_REGISTRATION` it is recommended to use a human-readable identifier, such as `email`.
|
||||
|
||||
<sup>[3]</sup> As directus only allows one role per user, evaluating stops after the first match. An OpenID user that is
|
||||
member of both e.g. developer and admin groups may be assigned different roles depending on the order that you specify
|
||||
your role-mapping in: In the following example said OpenID user will be assigned the role `directus_developer_role_id`
|
||||
|
||||
```
|
||||
AUTH_<PROVIDER>_ROLE_MAPPING: json:{ "developer": "directus_developer_role_id", "admin": "directus_admin_role_id" }"
|
||||
```
|
||||
|
||||
Whereas in the following example the OpenID user will be assigned the role `directus_admin_role_id`
|
||||
|
||||
```
|
||||
AUTH_<PROVIDER>_ROLE_MAPPING: json:{ "admin": "directus_admin_role_id", "developer": "directus_developer_role_id" }"
|
||||
```
|
||||
|
||||
### LDAP (`ldap`)
|
||||
|
||||
LDAP allows Active Directory users to authenticate and use Directus without having to be manually configured. User
|
||||
|
||||
Reference in New Issue
Block a user