From 83054a35151e6f9e705cdd42e051598e302be5cd Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Mon, 22 Jul 2024 22:22:18 +0800 Subject: [PATCH 01/10] Initial draft of LDAP --- docs/specs/account-linking.md | 2 + docs/specs/ldap.md | 133 ++++++++++++++++++++++++++++++++++ docs/specs/user-model.md | 24 ++++-- 3 files changed, 153 insertions(+), 6 deletions(-) create mode 100644 docs/specs/ldap.md diff --git a/docs/specs/account-linking.md b/docs/specs/account-linking.md index b589ec4f065..19c98ed076f 100644 --- a/docs/specs/account-linking.md +++ b/docs/specs/account-linking.md @@ -1,5 +1,7 @@ # Account Linking +TODO(ldap): Specify how an LDAP identity take part in Account Linking. + - [Introduction](#introduction) - [Configuration](#configuration) - [Defining how the linking occurs and the corresponding action](#defining-how-the-linking-occurs-and-the-corresponding-action) diff --git a/docs/specs/ldap.md b/docs/specs/ldap.md new file mode 100644 index 00000000000..5eebe2bc1cd --- /dev/null +++ b/docs/specs/ldap.md @@ -0,0 +1,133 @@ +- [LDAP](#ldap) + * [Supported LDAP protocol](#supported-ldap-protocol) + * [Authenticate Authgear as a LDAP client](#authenticate-authgear-as-a-ldap-client) + * [Configuration of LDAP servers](#configuration-of-ldap-servers) + * [The database schema of a LDAP identity](#the-database-schema-of-a-ldap-identity) + * [Handling of a LDAP identity](#handling-of-a-ldap-identity) + * [The UX of LDAP in Auth UI](#the-ux-of-ldap-in-auth-ui) + +# LDAP + +This document describes the LDAP support in Authgear. Authgear acts as a LDAP client, and connects to one or more LDAP servers. + +## Supported LDAP protocol + +The supported LDAP protocol is LDAPv3, which is specified in [RFC4511](https://datatracker.ietf.org/doc/html/rfc4511). + +Authgear MUST BE compatible with + +1. A LDAP server without TLS (i.e. ldap://) +2. The non-standard ldaps:// +3. The StartTLS operation, as defined in [Section 4.14 in RFC4511](https://datatracker.ietf.org/doc/html/rfc4511#section-4.14) + +When the connection URL starts with `ldap://`, StartTLS will be tried. +If the LDAP server responds protocolError, as defined in [Section 4.14.1 in RFC4511](https://datatracker.ietf.org/doc/html/rfc4511#section-4.14.1), +then Authgear will treat it as no TLS is required. + +When the connection URL starts with `ldaps://`, then Authgear will connect to the LDAP server with TLS directly, without using StartTLS. + +## Authenticate Authgear as a LDAP client + +It is very common that before a LDAP client can run any [Search Operation](https://datatracker.ietf.org/doc/html/rfc4511#section-4.5), +the LDAP client must perform [Bind Operation](https://datatracker.ietf.org/doc/html/rfc4511#section-4.2) first. + +Authgear supports Simple Bind with username and password. + +## Configuration of LDAP servers + +The following example demonstrates how the developer should configure Authgear to connect to their LDAP servers. + +In `authgear.yaml` + +```yaml +identity: + ldap: + servers: + - name: ldap1 + url: "ldap://localhost:389" + base_distinguished_name: "dc=localhost" + relative_distinguished_name_attribute: "uid" + - name: ldap2 + url: "ldap://mycompany.com:389" + base_distinguished_name: "dc=mycompany,dc=com" + relative_distinguished_name_attribute: "uid" +``` + +- `identity.ldap.servers.name`: A name that only exists in `authgear.yaml` and `authgear.secrets.yaml` for associating a LDAP server. It serves no other purpose. +- `identity.ldap.servers.url`: The connection URL to the LDAP server. The scheme MUST be `ldap:` or `ldaps:`. The URL MUST contain `host`, and optionally a port. If the port is omitted, the default port of the scheme is assumed. The default port of `ldap:` is `389`, while the default port of `ldaps:` is `636`. The URL MUST NOT contain other elements, such as path, nor query. + +> Why does `identity.ldap.servers.url` allow scheme, host, and port? +> The LDAP URL, defined in [Section 2 in RFC 4516](https://datatracker.ietf.org/doc/html/rfc4516#section-2), is syntactically different from the URL defined in [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986). +> In particular, a LDAP URL can contain multiple question mark characters. +> To ease implementation, we do not support the LDAP URL, and require a RFC3986 URL (which is implemented by the standard library net/url package). + +- `identity.ldap.servers.base_distinguished_name`: The base distinguished name to construct a Search Request, as defined in [Section 4.5.1 in RFC4511](https://datatracker.ietf.org/doc/html/rfc4511#section-4.5.1). +- `identity.ldap.servers.relative_distinguished_name_attribute`: The attribute name Authgear should use to construct the search request. For example, if the value is `uid`, and the end-user gives a username of `user1`, and the base distinguished name is `dc=example,dc=com`, then the relative distinguished name is `uid=user1`, and the distinguished name is `uid=user1,dc=example,dc=com`. + +> base_distinguished_name and relative_distinguished_name_attribute may not be sufficient if the developer needs to determine the DN in a more dynamic way. +> In the future, we can support a new configuration, distinguished_name_template, which is a Go template that MUST return a DN. +> It looks like +> ``` +> {{- if (hasSuffix $.Username "@mycompany.com") }} +> {{- (ldapDN (ldapAttribute "uid" $.Username) (ldapParse "dc=mycompany,dc=com") ) }} +> {{- else }} +> {{- (ldapDN (ldapAttribute "uid" $.Username) (ldapParse "dc=anothercompany,dc=com") ) }} +> {{- end }} +> ``` + +> TODO: The current configuration is missing an important feature. The feature is allow the developer to specify what attributes they want to retrieve from the LDAP server, and +> what attributes map to which standard attributes. + +In `authgear.secrets.yaml` + +```yaml +secrets: +- data: + items: + - name: ldap1 + username: authgear + password: secret1 + - name: ldap2 + username: authgear + password: secret2 + key: ldap +``` + +- `items.name`: To associate a LDAP server in `authgear.yaml`. +- `items.username`: Optional. The username Authgear uses to authenticate itself to the LDAP server. If it is not provided, then Authgear does not authenticates itself, and assumes the LDAP server allows anonymous requests. +- `items.password`: Optional. The password Authgear uses to authenticate itself to the LDAP server. If `username` is provided, then `password` is required. + +## The database schema of a LDAP identity + +```sql +CREATE TABLE _auth_identity_ldap +( + id text PRIMARY KEY REFERENCES _auth_identity (id), + app_id text NOT NULL, + server_url text NOT NULL, + distinguished_name text NOT NULL, + claims jsonb NOT NULL, + raw_entry_json jsonb NOT NULL +); + +CREATE UNIQUE INDEX _auth_identity_ldap_unique ON _auth_identity_ldap (app_id, server_url, distinguished_name); +``` + +- `id`: The primary key of this table. This is the same as other `_auth_identity_*` tables. +- `app_id`: The app ID of this table for multi-tenant. This is the same as other `_auth_identity_*` tables. +- `server_url`: The URL to the LDAP server when this identity was created. The value is taken from the configuration at that moment. It does not change even if the URL in `authgear.yaml` changes. +- `distinguished_name`: The distinguished name of this LDAP entry. +- `claims`: The standard claims extracted from this LDAP entry. +- `raw_entry_json`: The raw LDAP entry encoded in JSON. It looks like `{ "dn": "uid=johndoe,dc=example,dc=com", "attr1": ["value1"] }`. + +## Handling of a LDAP identity + +- To look up a LDAP identity in Authgear, we use the tuple `(app_id, server_url, distinguished_name)`. +- Similar to OAuth identity, we update an LDAP identity when it is used in login. + +## The UX of LDAP in Auth UI + +In the MVP phase (that is, now), sign in with LDAP is like sign in with an OAuth provider. +Except that the enter-username-and-password page is hosted by Authgear as a integral part of Auth UI. + +In the future, we may consider an option to make LDAP "replaces" Login ID in the UX. diff --git a/docs/specs/user-model.md b/docs/specs/user-model.md index f9fbe6b8097..9c6adfd62ce 100644 --- a/docs/specs/user-model.md +++ b/docs/specs/user-model.md @@ -3,6 +3,7 @@ * [Identity](#identity) + [Identity Claims](#identity-claims) + [OAuth Identity](#oauth-identity) + + [LDAP Identity](#ldap-identity) + [WebAuthn Identity](#webauthn-identity) + [Anonymous Identity](#anonymous-identity) - [Anonymous Identity JWT](#anonymous-identity-jwt) @@ -38,8 +39,7 @@ - [WebAuthn Authenticator](#webauthn-authenticator) - [TOTP Authenticator](#totp-authenticator) - [OOB-OTP Authenticator](#oob-otp-authenticator) - - [Login Link](#login-link) - + [Skip verification](#skip-verification) + * [Login Link](#login-link) + [Device Token](#device-token) + [Recovery Code](#recovery-code) * [Deleting a user](#deleting-a-user) @@ -50,6 +50,7 @@ + [Deactivated user](#deactivated-user) + [Scheduled account deletion or anonymization](#scheduled-account-deletion-or-anonymization) + [Sessions](#sessions) + + [Configuration](#configuration) # User Model @@ -61,10 +62,11 @@ A user has many identities. A user has many authenticators. An identity is used to look up a user. -5 types of identity are supported. +6 types of identity are supported. - Login ID - OAuth +- LDAP - WebAuthn - Anonymous - Biometric @@ -72,7 +74,7 @@ An identity is used to look up a user. A user either has no anonymous identity, or have exactly one anonymous identity. A user with anonymous identity is considered as anonymous user. -A user must have at least 1 Login ID identity or 1 OAuth identity. +A user must have at least 1 Login ID identity, or at least 1 OAuth identity, or at least 1 LDAP identity. ### Identity Claims @@ -84,9 +86,19 @@ The claims are used to detect duplicate identity. For example, an Email Login ID ### OAuth Identity -OAuth identity is external identity from supported OAuth 2 IdPs. Only authorization code flow is supported. If the provider supports OIDC, OIDC is preferred over provider-specific OAuth 2 protocol. +OAuth identity is an external identity from supported OAuth 2 IdPs. Only authorization code flow is supported. If the provider supports OIDC, OIDC is preferred over provider-specific OAuth 2 protocol. -OAuth identity does not require primary authentication. +An OAuth identity does not require primary authentication, nor secondary authentication. + +### LDAP Identity + +LDAP identity is an external identity from a LDAPv3 server. +A LDAP identity is internally identified in Authgear with the URL to the LDAP server, and the DN of the entry. + +A LDAP identity does not require primary authentication, the LDAP server is responsible for authenticating with the Bind operation. +If the project has configured secondary authentication, then a LDAP identity requires secondary authentication. + +For the details of LDAP, please see [./ldap.md](./ldap.md) ### WebAuthn Identity From 58c2ee3d865aaf419d398c3f210af308247ecf72 Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Sat, 27 Jul 2024 10:29:19 +0800 Subject: [PATCH 02/10] Rewrite LDAP draft --- docs/specs/ldap.md | 114 ++++++++++++++++++++++++++++++++------------- 1 file changed, 82 insertions(+), 32 deletions(-) diff --git a/docs/specs/ldap.md b/docs/specs/ldap.md index 5eebe2bc1cd..79f252df592 100644 --- a/docs/specs/ldap.md +++ b/docs/specs/ldap.md @@ -2,9 +2,12 @@ * [Supported LDAP protocol](#supported-ldap-protocol) * [Authenticate Authgear as a LDAP client](#authenticate-authgear-as-a-ldap-client) * [Configuration of LDAP servers](#configuration-of-ldap-servers) + * [Validation on the configuration](#validation-on-the-configuration) + * [Testing on the configuration](#testing-on-the-configuration) * [The database schema of a LDAP identity](#the-database-schema-of-a-ldap-identity) * [Handling of a LDAP identity](#handling-of-a-ldap-identity) * [The UX of LDAP in Auth UI](#the-ux-of-ldap-in-auth-ui) + * [Errors](#errors) # LDAP @@ -43,38 +46,29 @@ In `authgear.yaml` identity: ldap: servers: - - name: ldap1 + - name: default url: "ldap://localhost:389" - base_distinguished_name: "dc=localhost" - relative_distinguished_name_attribute: "uid" - - name: ldap2 - url: "ldap://mycompany.com:389" - base_distinguished_name: "dc=mycompany,dc=com" - relative_distinguished_name_attribute: "uid" + base_dn: "dc=localhost" + search_filter_template: | + {{- if (hasSuffix $.Username "@mycompany.com") }} + (&(objectCategory=person)(objectClass=user)(memberof=dc=mycompany,dc=com)(sAMAccountName={{ $.Username }})) + {{- else }} + (&(objectCategory=person)(objectClass=user)(memberof=dc=anothercompany,dc=com)(sAMAccountName={{ $.Username }})) + {{- end }} + user_id_attribute: "myUserID" ``` -- `identity.ldap.servers.name`: A name that only exists in `authgear.yaml` and `authgear.secrets.yaml` for associating a LDAP server. It serves no other purpose. +- `identity.ldap.servers.name`: A unique name to identify this LDAP server. Once set, it cannot be changed. It is stored in the database as part of the unique key to identify a LDAP identity. See [The database schema of a LDAP identity](#the-database-schema-of-a-ldap-identity) for details. - `identity.ldap.servers.url`: The connection URL to the LDAP server. The scheme MUST be `ldap:` or `ldaps:`. The URL MUST contain `host`, and optionally a port. If the port is omitted, the default port of the scheme is assumed. The default port of `ldap:` is `389`, while the default port of `ldaps:` is `636`. The URL MUST NOT contain other elements, such as path, nor query. +- `identity.ldap.servers.base_dn`: The base DN to construct a Search Request, as defined in [Section 4.5.1 in RFC4511](https://datatracker.ietf.org/doc/html/rfc4511#section-4.5.1). +- `identity.ldap.servers.search_filter_template`: A Go template that renders to a filter to be used in the Search Request. This template can use the variable `$.Username` to render the username entered by the end-user. `$.Username` is pre-processed so that it is an escaped LDAP string. The strings function from [https://masterminds.github.io/sprig/](https://masterminds.github.io/sprig/) can be used in the template. +- `identity.ldap.servers.user_id_attribute`: The attribute that is guaranteed to be unique and never change for a given user in the LDAP server. It is used to identify a user from the LDAP server. Warning: Changing this value will cause Authgear not able to look up any previous LDAP identities. > Why does `identity.ldap.servers.url` allow scheme, host, and port? > The LDAP URL, defined in [Section 2 in RFC 4516](https://datatracker.ietf.org/doc/html/rfc4516#section-2), is syntactically different from the URL defined in [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986). > In particular, a LDAP URL can contain multiple question mark characters. > To ease implementation, we do not support the LDAP URL, and require a RFC3986 URL (which is implemented by the standard library net/url package). -- `identity.ldap.servers.base_distinguished_name`: The base distinguished name to construct a Search Request, as defined in [Section 4.5.1 in RFC4511](https://datatracker.ietf.org/doc/html/rfc4511#section-4.5.1). -- `identity.ldap.servers.relative_distinguished_name_attribute`: The attribute name Authgear should use to construct the search request. For example, if the value is `uid`, and the end-user gives a username of `user1`, and the base distinguished name is `dc=example,dc=com`, then the relative distinguished name is `uid=user1`, and the distinguished name is `uid=user1,dc=example,dc=com`. - -> base_distinguished_name and relative_distinguished_name_attribute may not be sufficient if the developer needs to determine the DN in a more dynamic way. -> In the future, we can support a new configuration, distinguished_name_template, which is a Go template that MUST return a DN. -> It looks like -> ``` -> {{- if (hasSuffix $.Username "@mycompany.com") }} -> {{- (ldapDN (ldapAttribute "uid" $.Username) (ldapParse "dc=mycompany,dc=com") ) }} -> {{- else }} -> {{- (ldapDN (ldapAttribute "uid" $.Username) (ldapParse "dc=anothercompany,dc=com") ) }} -> {{- end }} -> ``` - > TODO: The current configuration is missing an important feature. The feature is allow the developer to specify what attributes they want to retrieve from the LDAP server, and > what attributes map to which standard attributes. @@ -84,12 +78,9 @@ In `authgear.secrets.yaml` secrets: - data: items: - - name: ldap1 + - name: default username: authgear password: secret1 - - name: ldap2 - username: authgear - password: secret2 key: ldap ``` @@ -97,6 +88,54 @@ secrets: - `items.username`: Optional. The username Authgear uses to authenticate itself to the LDAP server. If it is not provided, then Authgear does not authenticates itself, and assumes the LDAP server allows anonymous requests. - `items.password`: Optional. The password Authgear uses to authenticate itself to the LDAP server. If `username` is provided, then `password` is required. +## Validation on the configuration + +Here is the JSON schema for the LDAP server configuration. + +``` +{ + "type": "object", + "additionalProperties": false, + "required": ["name", "url", "base_dn", "search_filter_template", "user_id_attribute"], + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "url": { + "type": "string", + "format": "ldap_url" + }, + "base_dn": { + "type": "string", + "format": "ldap_dn" + }, + "search_filter_template": { + "type": "string", + "format": "ldap_search_filter_template" + }, + "user_id_attribute": { + "type": "string", + "format": "ldap_attribute_name" + } + } +} +``` + +- `format: ldap_url`: It is a JSON schema format that implements the rules of `identity.ldap.servers.url`. +- `format: ldap_dn`: It is a JSON schema format that validates the value to be a valid DN. +- `format: ldap_search_filter_template`: It is a JSON schema format that validates the rendered string to be a valid Search Filter. It does the validation by running the template with `Username=user`, `Username=user@example.com`, and `Username=+85298765432`, and then parse the resulting Search Filter as a Search Filter. +- `format: ldap_attribute_name`: It is a JSON schema format that validates the value to be a valid LDAP attribute name. + +## Testing on the configuration + +> TODO: Add a mutation in the Admin API to test LDAP connection. +> It should take the whole server configuration, and optionally a end-user username. +> It connects the LDAP server with the URL and the credentials. +> If the optional end-user username is given, it performs a Search request, and validates the user exists and has user_id_attribute. +> It returns detailed API errors so that the portal can display relevant information for the developer to debug the configuration. +> Such detailed API errors ARE NOT returned in actual use. They are reported as internal errors. + ## The database schema of a LDAP identity ```sql @@ -104,25 +143,27 @@ CREATE TABLE _auth_identity_ldap ( id text PRIMARY KEY REFERENCES _auth_identity (id), app_id text NOT NULL, - server_url text NOT NULL, - distinguished_name text NOT NULL, + server_name text NOT NULL, + user_id_attribute text NOT NULL, + user_id_value text NOT NULL, claims jsonb NOT NULL, raw_entry_json jsonb NOT NULL ); -CREATE UNIQUE INDEX _auth_identity_ldap_unique ON _auth_identity_ldap (app_id, server_url, distinguished_name); +CREATE UNIQUE INDEX _auth_identity_ldap_unique ON _auth_identity_ldap (app_id, server_name, user_id_attribute, user_id_value); ``` - `id`: The primary key of this table. This is the same as other `_auth_identity_*` tables. - `app_id`: The app ID of this table for multi-tenant. This is the same as other `_auth_identity_*` tables. -- `server_url`: The URL to the LDAP server when this identity was created. The value is taken from the configuration at that moment. It does not change even if the URL in `authgear.yaml` changes. -- `distinguished_name`: The distinguished name of this LDAP entry. +- `server_name`: The `name` of the LDAP server. +- `user_id_attribute`: The `user_id_attribute` of the LDAP server when this identity is created. +- `user_id_value`: The value of the `user_id_attribute` of the user. - `claims`: The standard claims extracted from this LDAP entry. - `raw_entry_json`: The raw LDAP entry encoded in JSON. It looks like `{ "dn": "uid=johndoe,dc=example,dc=com", "attr1": ["value1"] }`. ## Handling of a LDAP identity -- To look up a LDAP identity in Authgear, we use the tuple `(app_id, server_url, distinguished_name)`. +- To look up a LDAP identity in Authgear, we use the tuple `(app_id, server_name, user_id_attribute, user_id_value)`. - Similar to OAuth identity, we update an LDAP identity when it is used in login. ## The UX of LDAP in Auth UI @@ -131,3 +172,12 @@ In the MVP phase (that is, now), sign in with LDAP is like sign in with an OAuth Except that the enter-username-and-password page is hosted by Authgear as a integral part of Auth UI. In the future, we may consider an option to make LDAP "replaces" Login ID in the UX. + +## Errors + +This section documents the expected errors. + +|Description|Name|Reason|Info| +|---|---|---|---| +|When the LDAP server is service unavailable, `user_id_attribute` not found in a user, search filter turns out to be invalid, etc|InternalError|UnexpectedError|| +|When the end-user cannot authenticate to the LDAP server|Unauthorized|InvalidCredentials|| From 73fc6f66ddd62eb71cd9b2dd8aea713742ee2bc9 Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Wed, 31 Jul 2024 18:51:20 +0800 Subject: [PATCH 03/10] Replace user_id_attribute to user_id_attribute_oid --- docs/specs/ldap.md | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/docs/specs/ldap.md b/docs/specs/ldap.md index 79f252df592..0e7d450b62d 100644 --- a/docs/specs/ldap.md +++ b/docs/specs/ldap.md @@ -55,14 +55,17 @@ identity: {{- else }} (&(objectCategory=person)(objectClass=user)(memberof=dc=anothercompany,dc=com)(sAMAccountName={{ $.Username }})) {{- end }} - user_id_attribute: "myUserID" + # According to https://datatracker.ietf.org/doc/html/rfc4512#section-2.5 + # The unique way to identify an attribute in LDAP is oid. + # For those who are curious, "0.9.2342.19200300.100.1.1" is "uid", according to https://datatracker.ietf.org/doc/html/rfc4519#section-2.39 + user_id_attribute_oid: "0.9.2342.19200300.100.1.1" ``` - `identity.ldap.servers.name`: A unique name to identify this LDAP server. Once set, it cannot be changed. It is stored in the database as part of the unique key to identify a LDAP identity. See [The database schema of a LDAP identity](#the-database-schema-of-a-ldap-identity) for details. - `identity.ldap.servers.url`: The connection URL to the LDAP server. The scheme MUST be `ldap:` or `ldaps:`. The URL MUST contain `host`, and optionally a port. If the port is omitted, the default port of the scheme is assumed. The default port of `ldap:` is `389`, while the default port of `ldaps:` is `636`. The URL MUST NOT contain other elements, such as path, nor query. - `identity.ldap.servers.base_dn`: The base DN to construct a Search Request, as defined in [Section 4.5.1 in RFC4511](https://datatracker.ietf.org/doc/html/rfc4511#section-4.5.1). - `identity.ldap.servers.search_filter_template`: A Go template that renders to a filter to be used in the Search Request. This template can use the variable `$.Username` to render the username entered by the end-user. `$.Username` is pre-processed so that it is an escaped LDAP string. The strings function from [https://masterminds.github.io/sprig/](https://masterminds.github.io/sprig/) can be used in the template. -- `identity.ldap.servers.user_id_attribute`: The attribute that is guaranteed to be unique and never change for a given user in the LDAP server. It is used to identify a user from the LDAP server. Warning: Changing this value will cause Authgear not able to look up any previous LDAP identities. +- `identity.ldap.servers.user_id_attribute_oid`: The attribute that is guaranteed to be unique and never change for a given user in the LDAP server. It is used to identify a user from the LDAP server. Warning: Changing this value will cause Authgear not able to look up any previous LDAP identities. > Why does `identity.ldap.servers.url` allow scheme, host, and port? > The LDAP URL, defined in [Section 2 in RFC 4516](https://datatracker.ietf.org/doc/html/rfc4516#section-2), is syntactically different from the URL defined in [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986). @@ -96,7 +99,7 @@ Here is the JSON schema for the LDAP server configuration. { "type": "object", "additionalProperties": false, - "required": ["name", "url", "base_dn", "search_filter_template", "user_id_attribute"], + "required": ["name", "url", "base_dn", "search_filter_template", "user_id_attribute_oid"], "properties": { "name": { "type": "string", @@ -114,9 +117,9 @@ Here is the JSON schema for the LDAP server configuration. "type": "string", "format": "ldap_search_filter_template" }, - "user_id_attribute": { + "user_id_attribute_oid": { "type": "string", - "format": "ldap_attribute_name" + "format": "ldap_oid" } } } @@ -125,14 +128,14 @@ Here is the JSON schema for the LDAP server configuration. - `format: ldap_url`: It is a JSON schema format that implements the rules of `identity.ldap.servers.url`. - `format: ldap_dn`: It is a JSON schema format that validates the value to be a valid DN. - `format: ldap_search_filter_template`: It is a JSON schema format that validates the rendered string to be a valid Search Filter. It does the validation by running the template with `Username=user`, `Username=user@example.com`, and `Username=+85298765432`, and then parse the resulting Search Filter as a Search Filter. -- `format: ldap_attribute_name`: It is a JSON schema format that validates the value to be a valid LDAP attribute name. +- `format: ldap_oid`: It is a JSON schema format that validates the value to be a valid LDAP oid. ## Testing on the configuration > TODO: Add a mutation in the Admin API to test LDAP connection. > It should take the whole server configuration, and optionally a end-user username. > It connects the LDAP server with the URL and the credentials. -> If the optional end-user username is given, it performs a Search request, and validates the user exists and has user_id_attribute. +> If the optional end-user username is given, it performs a Search request, and validates the user exists and has user_id_attribute_oid. > It returns detailed API errors so that the portal can display relevant information for the developer to debug the configuration. > Such detailed API errors ARE NOT returned in actual use. They are reported as internal errors. @@ -141,29 +144,29 @@ Here is the JSON schema for the LDAP server configuration. ```sql CREATE TABLE _auth_identity_ldap ( - id text PRIMARY KEY REFERENCES _auth_identity (id), - app_id text NOT NULL, - server_name text NOT NULL, - user_id_attribute text NOT NULL, - user_id_value text NOT NULL, - claims jsonb NOT NULL, - raw_entry_json jsonb NOT NULL + id text PRIMARY KEY REFERENCES _auth_identity (id), + app_id text NOT NULL, + server_name text NOT NULL, + user_id_attribute_oid text NOT NULL, + user_id_attribute_value text NOT NULL, + claims jsonb NOT NULL, + raw_entry_json jsonb NOT NULL ); -CREATE UNIQUE INDEX _auth_identity_ldap_unique ON _auth_identity_ldap (app_id, server_name, user_id_attribute, user_id_value); +CREATE UNIQUE INDEX _auth_identity_ldap_unique ON _auth_identity_ldap (app_id, server_name, user_id_attribute_oid, user_id_attribute_value); ``` - `id`: The primary key of this table. This is the same as other `_auth_identity_*` tables. - `app_id`: The app ID of this table for multi-tenant. This is the same as other `_auth_identity_*` tables. - `server_name`: The `name` of the LDAP server. -- `user_id_attribute`: The `user_id_attribute` of the LDAP server when this identity is created. -- `user_id_value`: The value of the `user_id_attribute` of the user. +- `user_id_attribute_oid`: The `user_id_attribute_oid` of the LDAP server when this identity is created. +- `user_id_attribute_value`: The value of the `user_id_attribute_oid` of the user. - `claims`: The standard claims extracted from this LDAP entry. - `raw_entry_json`: The raw LDAP entry encoded in JSON. It looks like `{ "dn": "uid=johndoe,dc=example,dc=com", "attr1": ["value1"] }`. ## Handling of a LDAP identity -- To look up a LDAP identity in Authgear, we use the tuple `(app_id, server_name, user_id_attribute, user_id_value)`. +- To look up a LDAP identity in Authgear, we use the tuple `(app_id, server_name, user_id_attribute_oid, user_id_attribute_value)`. - Similar to OAuth identity, we update an LDAP identity when it is used in login. ## The UX of LDAP in Auth UI @@ -179,5 +182,5 @@ This section documents the expected errors. |Description|Name|Reason|Info| |---|---|---|---| -|When the LDAP server is service unavailable, `user_id_attribute` not found in a user, search filter turns out to be invalid, etc|InternalError|UnexpectedError|| +|When the LDAP server is service unavailable, `user_id_attribute_oid` not found in a user, search filter turns out to be invalid, etc|InternalError|UnexpectedError|| |When the end-user cannot authenticate to the LDAP server|Unauthorized|InvalidCredentials|| From a3f279d9ceeec6693a4e6b7296e20f9f96c829c9 Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Wed, 31 Jul 2024 19:17:23 +0800 Subject: [PATCH 04/10] Explain the design choice of not having base_dn as a Go template --- docs/specs/ldap.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/specs/ldap.md b/docs/specs/ldap.md index 0e7d450b62d..9b38edf8b00 100644 --- a/docs/specs/ldap.md +++ b/docs/specs/ldap.md @@ -67,6 +67,10 @@ identity: - `identity.ldap.servers.search_filter_template`: A Go template that renders to a filter to be used in the Search Request. This template can use the variable `$.Username` to render the username entered by the end-user. `$.Username` is pre-processed so that it is an escaped LDAP string. The strings function from [https://masterminds.github.io/sprig/](https://masterminds.github.io/sprig/) can be used in the template. - `identity.ldap.servers.user_id_attribute_oid`: The attribute that is guaranteed to be unique and never change for a given user in the LDAP server. It is used to identify a user from the LDAP server. Warning: Changing this value will cause Authgear not able to look up any previous LDAP identities. +> What if I want to use a different base_dn depend on the username? +> In this case, you need to specify a very generic base_dn like "dc=com", and then +> you write your own search_filter_template to filter entries. + > Why does `identity.ldap.servers.url` allow scheme, host, and port? > The LDAP URL, defined in [Section 2 in RFC 4516](https://datatracker.ietf.org/doc/html/rfc4516#section-2), is syntactically different from the URL defined in [RFC3986](https://datatracker.ietf.org/doc/html/rfc3986). > In particular, a LDAP URL can contain multiple question mark characters. From d7ff36d88e4b6d42154aa45d9317cde3b2bda1f6 Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Thu, 1 Aug 2024 12:54:54 +0800 Subject: [PATCH 05/10] Use dn instead of username --- docs/specs/ldap.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/docs/specs/ldap.md b/docs/specs/ldap.md index 9b38edf8b00..3986415e49c 100644 --- a/docs/specs/ldap.md +++ b/docs/specs/ldap.md @@ -34,7 +34,7 @@ When the connection URL starts with `ldaps://`, then Authgear will connect to th It is very common that before a LDAP client can run any [Search Operation](https://datatracker.ietf.org/doc/html/rfc4511#section-4.5), the LDAP client must perform [Bind Operation](https://datatracker.ietf.org/doc/html/rfc4511#section-4.2) first. -Authgear supports Simple Bind with username and password. +Authgear supports Simple Bind with DN and password, according to https://datatracker.ietf.org/doc/html/rfc4513#section-5.1.3 ## Configuration of LDAP servers @@ -86,14 +86,16 @@ secrets: - data: items: - name: default - username: authgear + # According to https://datatracker.ietf.org/doc/html/rfc4513#section-5.1.3, + # Simple Bind takes a DN and a password. + dn: cn=authgear,dc=example,dc=com password: secret1 key: ldap ``` - `items.name`: To associate a LDAP server in `authgear.yaml`. -- `items.username`: Optional. The username Authgear uses to authenticate itself to the LDAP server. If it is not provided, then Authgear does not authenticates itself, and assumes the LDAP server allows anonymous requests. -- `items.password`: Optional. The password Authgear uses to authenticate itself to the LDAP server. If `username` is provided, then `password` is required. +- `items.dn`: Optional. The DN of the LDAP entry Authgear uses to authenticate itself to the LDAP server. If it is not provided, then Authgear does not authenticates itself, and assumes the LDAP server allows anonymous requests. +- `items.password`: Optional. The password of the LDAP entry Authgear uses to authenticate itself to the LDAP server. If `dn` is provided, then `password` is required. ## Validation on the configuration From d83455d4728f824247fe54ffa3b5eae88d2e2b3e Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Tue, 6 Aug 2024 13:11:17 +0800 Subject: [PATCH 06/10] Specify testLDAPConnection --- docs/specs/ldap.md | 45 +++++++++++++++++++++++++++++++++++++-------- 1 file changed, 37 insertions(+), 8 deletions(-) diff --git a/docs/specs/ldap.md b/docs/specs/ldap.md index 3986415e49c..c0c2a5ff0e7 100644 --- a/docs/specs/ldap.md +++ b/docs/specs/ldap.md @@ -138,12 +138,34 @@ Here is the JSON schema for the LDAP server configuration. ## Testing on the configuration -> TODO: Add a mutation in the Admin API to test LDAP connection. -> It should take the whole server configuration, and optionally a end-user username. -> It connects the LDAP server with the URL and the credentials. -> If the optional end-user username is given, it performs a Search request, and validates the user exists and has user_id_attribute_oid. -> It returns detailed API errors so that the portal can display relevant information for the developer to debug the configuration. -> Such detailed API errors ARE NOT returned in actual use. They are reported as internal errors. +A new mutation in the Admin API is added to allow the developer to test the LDAP connection. + +```graphql +type Mutation { + # other root fields... + + testLDAPConnection(input: TestLDAPConnectionInput!): TestLDAPConnectionPayload! +} + +input TestLDAPConnectionInput { + url: String! + searchUserDN: String! + searchUserPassword: String! + baseDN: String! + searchFilterTemplate: String! + userIDAttributeOID: String! + # This is optional. If this is given, then a search request is performed, + # the user must exist and has the user_id_attribute_oid. + endUserUsername: String +} + +type TestLDAPConnectionPayload { + # This is always true. If any error occurs, error is returned instead. + ok: Boolean! +} +``` + +See [Errors](#errors) for what error is required to return in this mutation. ## The database schema of a LDAP identity @@ -188,5 +210,12 @@ This section documents the expected errors. |Description|Name|Reason|Info| |---|---|---|---| -|When the LDAP server is service unavailable, `user_id_attribute_oid` not found in a user, search filter turns out to be invalid, etc|InternalError|UnexpectedError|| -|When the end-user cannot authenticate to the LDAP server|Unauthorized|InvalidCredentials|| +|In testLDAPConnection, if connection cannot be established with the LDAP server|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "FailedToConnect"`| +|In testLDAPConnection, if search user credentials are invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "FailedToBindSearchUser"`| +|In testLDAPConnection, if base DN is invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "InvalidBaseDN"`| +|In testLDAPConnection, if search filter template is invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "InvalidSearchFilterTemplate"`| +|In testLDAPConnection, if `user_id_attribute_oid` is invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "InvalidUserIDAttributeOID"`| +|In testLDAPConnection, if endUserUsername is given, but the user is not found|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "TestingEndUserNotFound"`| +|In testLDAPConnection, if endUserUsername is given, but `user_id_attribute_oid` is not found|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "TestingEndUserMissingUserIDAttribute"`| +|When the LDAP server is service unavailable, search filter turns out to be invalid, etc|InternalError|UnexpectedError|| +|When the end-user cannot authenticate to the LDAP server, or `user_id_attribute_oid` not found|Unauthorized|InvalidCredentials|| From 1b99e0805619c7a4068c4b2f7bbaa443641d58b4 Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Tue, 6 Aug 2024 13:15:51 +0800 Subject: [PATCH 07/10] Take attribute name instead of oid It turns out the LDAP server does not return oid in the search request. --- docs/specs/ldap.md | 41 +++++++++++++++++++++++------------------ 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/docs/specs/ldap.md b/docs/specs/ldap.md index c0c2a5ff0e7..107a925a9a9 100644 --- a/docs/specs/ldap.md +++ b/docs/specs/ldap.md @@ -56,16 +56,21 @@ identity: (&(objectCategory=person)(objectClass=user)(memberof=dc=anothercompany,dc=com)(sAMAccountName={{ $.Username }})) {{- end }} # According to https://datatracker.ietf.org/doc/html/rfc4512#section-2.5 - # The unique way to identify an attribute in LDAP is oid. - # For those who are curious, "0.9.2342.19200300.100.1.1" is "uid", according to https://datatracker.ietf.org/doc/html/rfc4519#section-2.39 - user_id_attribute_oid: "0.9.2342.19200300.100.1.1" + # the production rule of attribute name is + # attributename = 1*keychar + # keychar = ALPHA / DIGIT / HYPHEN + # ALPHA = %x41-5A / %x61-7A ; "A"-"Z" / "a"-"z" + # DIGIT = %x30 / LDIGIT ; "0"-"9" + # LDIGIT = %x31-39 ; "1"-"9" + # HYPHEN = %x2D ; hyphen ("-") + user_id_attribute_name: "name" ``` - `identity.ldap.servers.name`: A unique name to identify this LDAP server. Once set, it cannot be changed. It is stored in the database as part of the unique key to identify a LDAP identity. See [The database schema of a LDAP identity](#the-database-schema-of-a-ldap-identity) for details. - `identity.ldap.servers.url`: The connection URL to the LDAP server. The scheme MUST be `ldap:` or `ldaps:`. The URL MUST contain `host`, and optionally a port. If the port is omitted, the default port of the scheme is assumed. The default port of `ldap:` is `389`, while the default port of `ldaps:` is `636`. The URL MUST NOT contain other elements, such as path, nor query. - `identity.ldap.servers.base_dn`: The base DN to construct a Search Request, as defined in [Section 4.5.1 in RFC4511](https://datatracker.ietf.org/doc/html/rfc4511#section-4.5.1). - `identity.ldap.servers.search_filter_template`: A Go template that renders to a filter to be used in the Search Request. This template can use the variable `$.Username` to render the username entered by the end-user. `$.Username` is pre-processed so that it is an escaped LDAP string. The strings function from [https://masterminds.github.io/sprig/](https://masterminds.github.io/sprig/) can be used in the template. -- `identity.ldap.servers.user_id_attribute_oid`: The attribute that is guaranteed to be unique and never change for a given user in the LDAP server. It is used to identify a user from the LDAP server. Warning: Changing this value will cause Authgear not able to look up any previous LDAP identities. +- `identity.ldap.servers.user_id_attribute_name`: The attribute that is guaranteed to be unique and never change for a given user in the LDAP server. It is used to identify a user from the LDAP server. Warning: Changing this value will cause Authgear not able to look up any previous LDAP identities. > What if I want to use a different base_dn depend on the username? > In this case, you need to specify a very generic base_dn like "dc=com", and then @@ -105,7 +110,7 @@ Here is the JSON schema for the LDAP server configuration. { "type": "object", "additionalProperties": false, - "required": ["name", "url", "base_dn", "search_filter_template", "user_id_attribute_oid"], + "required": ["name", "url", "base_dn", "search_filter_template", "user_id_attribute_name"], "properties": { "name": { "type": "string", @@ -123,9 +128,9 @@ Here is the JSON schema for the LDAP server configuration. "type": "string", "format": "ldap_search_filter_template" }, - "user_id_attribute_oid": { + "user_id_attribute_name": { "type": "string", - "format": "ldap_oid" + "format": "ldap_attribute_name" } } } @@ -134,7 +139,7 @@ Here is the JSON schema for the LDAP server configuration. - `format: ldap_url`: It is a JSON schema format that implements the rules of `identity.ldap.servers.url`. - `format: ldap_dn`: It is a JSON schema format that validates the value to be a valid DN. - `format: ldap_search_filter_template`: It is a JSON schema format that validates the rendered string to be a valid Search Filter. It does the validation by running the template with `Username=user`, `Username=user@example.com`, and `Username=+85298765432`, and then parse the resulting Search Filter as a Search Filter. -- `format: ldap_oid`: It is a JSON schema format that validates the value to be a valid LDAP oid. +- `format: ldap_attribute_name`: It is a JSON schema format that validates the value to be a valid LDAP attribute name. ## Testing on the configuration @@ -153,9 +158,9 @@ input TestLDAPConnectionInput { searchUserPassword: String! baseDN: String! searchFilterTemplate: String! - userIDAttributeOID: String! + userIDAttributeName: String! # This is optional. If this is given, then a search request is performed, - # the user must exist and has the user_id_attribute_oid. + # the user must exist and has the user_id_attribute_name. endUserUsername: String } @@ -175,26 +180,26 @@ CREATE TABLE _auth_identity_ldap id text PRIMARY KEY REFERENCES _auth_identity (id), app_id text NOT NULL, server_name text NOT NULL, - user_id_attribute_oid text NOT NULL, + user_id_attribute_name text NOT NULL, user_id_attribute_value text NOT NULL, claims jsonb NOT NULL, raw_entry_json jsonb NOT NULL ); -CREATE UNIQUE INDEX _auth_identity_ldap_unique ON _auth_identity_ldap (app_id, server_name, user_id_attribute_oid, user_id_attribute_value); +CREATE UNIQUE INDEX _auth_identity_ldap_unique ON _auth_identity_ldap (app_id, server_name, user_id_attribute_name, user_id_attribute_value); ``` - `id`: The primary key of this table. This is the same as other `_auth_identity_*` tables. - `app_id`: The app ID of this table for multi-tenant. This is the same as other `_auth_identity_*` tables. - `server_name`: The `name` of the LDAP server. -- `user_id_attribute_oid`: The `user_id_attribute_oid` of the LDAP server when this identity is created. -- `user_id_attribute_value`: The value of the `user_id_attribute_oid` of the user. +- `user_id_attribute_name`: The `user_id_attribute_name` of the LDAP server when this identity is created. +- `user_id_attribute_value`: The value of the `user_id_attribute_name` of the user. - `claims`: The standard claims extracted from this LDAP entry. - `raw_entry_json`: The raw LDAP entry encoded in JSON. It looks like `{ "dn": "uid=johndoe,dc=example,dc=com", "attr1": ["value1"] }`. ## Handling of a LDAP identity -- To look up a LDAP identity in Authgear, we use the tuple `(app_id, server_name, user_id_attribute_oid, user_id_attribute_value)`. +- To look up a LDAP identity in Authgear, we use the tuple `(app_id, server_name, user_id_attribute_name, user_id_attribute_value)`. - Similar to OAuth identity, we update an LDAP identity when it is used in login. ## The UX of LDAP in Auth UI @@ -214,8 +219,8 @@ This section documents the expected errors. |In testLDAPConnection, if search user credentials are invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "FailedToBindSearchUser"`| |In testLDAPConnection, if base DN is invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "InvalidBaseDN"`| |In testLDAPConnection, if search filter template is invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "InvalidSearchFilterTemplate"`| -|In testLDAPConnection, if `user_id_attribute_oid` is invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "InvalidUserIDAttributeOID"`| +|In testLDAPConnection, if `user_id_attribute_name` is invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "InvalidUserIDAttributeName"`| |In testLDAPConnection, if endUserUsername is given, but the user is not found|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "TestingEndUserNotFound"`| -|In testLDAPConnection, if endUserUsername is given, but `user_id_attribute_oid` is not found|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "TestingEndUserMissingUserIDAttribute"`| +|In testLDAPConnection, if endUserUsername is given, but `user_id_attribute_name` is not found|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "TestingEndUserMissingUserIDAttribute"`| |When the LDAP server is service unavailable, search filter turns out to be invalid, etc|InternalError|UnexpectedError|| -|When the end-user cannot authenticate to the LDAP server, or `user_id_attribute_oid` not found|Unauthorized|InvalidCredentials|| +|When the end-user cannot authenticate to the LDAP server, or `user_id_attribute_name` not found|Unauthorized|InvalidCredentials|| From 5926eccc1b5ae5e8f69e95acc48ce6e88b22021b Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Tue, 6 Aug 2024 18:52:54 +0800 Subject: [PATCH 08/10] Remove validation errors and add one more possible error --- docs/specs/ldap.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/docs/specs/ldap.md b/docs/specs/ldap.md index 107a925a9a9..0ccf1fa0a7b 100644 --- a/docs/specs/ldap.md +++ b/docs/specs/ldap.md @@ -217,10 +217,8 @@ This section documents the expected errors. |---|---|---|---| |In testLDAPConnection, if connection cannot be established with the LDAP server|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "FailedToConnect"`| |In testLDAPConnection, if search user credentials are invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "FailedToBindSearchUser"`| -|In testLDAPConnection, if base DN is invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "InvalidBaseDN"`| -|In testLDAPConnection, if search filter template is invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "InvalidSearchFilterTemplate"`| -|In testLDAPConnection, if `user_id_attribute_name` is invalid|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "InvalidUserIDAttributeName"`| |In testLDAPConnection, if endUserUsername is given, but the user is not found|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "TestingEndUserNotFound"`| +|In testLDAPConnection, if endUserUsername is given, but the search request results in more than 1 entry|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "MoreThanOneEntryInSearchResult"`| |In testLDAPConnection, if endUserUsername is given, but `user_id_attribute_name` is not found|ServiceUnavailable|LDAPConnectionTestFailed|`"cause": "TestingEndUserMissingUserIDAttribute"`| |When the LDAP server is service unavailable, search filter turns out to be invalid, etc|InternalError|UnexpectedError|| |When the end-user cannot authenticate to the LDAP server, or `user_id_attribute_name` not found|Unauthorized|InvalidCredentials|| From 1c6df0e509a0a398ce5f9c6a3e0866d3d1bd98a1 Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Tue, 13 Aug 2024 19:35:57 +0800 Subject: [PATCH 09/10] Change user_id_attribute_value and raw_entry_json to bytea --- docs/specs/ldap.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/docs/specs/ldap.md b/docs/specs/ldap.md index 0ccf1fa0a7b..544a7ca9e9d 100644 --- a/docs/specs/ldap.md +++ b/docs/specs/ldap.md @@ -181,7 +181,7 @@ CREATE TABLE _auth_identity_ldap app_id text NOT NULL, server_name text NOT NULL, user_id_attribute_name text NOT NULL, - user_id_attribute_value text NOT NULL, + user_id_attribute_value bytea NOT NULL, claims jsonb NOT NULL, raw_entry_json jsonb NOT NULL ); @@ -195,7 +195,16 @@ CREATE UNIQUE INDEX _auth_identity_ldap_unique ON _auth_identity_ldap (app_id, s - `user_id_attribute_name`: The `user_id_attribute_name` of the LDAP server when this identity is created. - `user_id_attribute_value`: The value of the `user_id_attribute_name` of the user. - `claims`: The standard claims extracted from this LDAP entry. -- `raw_entry_json`: The raw LDAP entry encoded in JSON. It looks like `{ "dn": "uid=johndoe,dc=example,dc=com", "attr1": ["value1"] }`. +- `raw_entry_json`: The raw LDAP entry encoded in JSON. The shape is as follows + +``` +{ + "dn": "uid=johndoe,dc=example,dc=com", + "attr1": ["BASE64_STRING"] +} +``` + +where BASE64_STRING is base64 with padding. ## Handling of a LDAP identity From 82e474a8b888b5ecbe9ec185818aa453a8f12ae5 Mon Sep 17 00:00:00 2001 From: Louis Chan Date: Tue, 13 Aug 2024 19:38:23 +0800 Subject: [PATCH 10/10] Remember the last used username --- docs/specs/ldap.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/docs/specs/ldap.md b/docs/specs/ldap.md index 544a7ca9e9d..d9538a84708 100644 --- a/docs/specs/ldap.md +++ b/docs/specs/ldap.md @@ -182,11 +182,13 @@ CREATE TABLE _auth_identity_ldap server_name text NOT NULL, user_id_attribute_name text NOT NULL, user_id_attribute_value bytea NOT NULL, + last_used_username text NOT NULL, claims jsonb NOT NULL, raw_entry_json jsonb NOT NULL ); CREATE UNIQUE INDEX _auth_identity_ldap_unique ON _auth_identity_ldap (app_id, server_name, user_id_attribute_name, user_id_attribute_value); +CREATE INDEX _auth_identity_ldap_last_user_username ON _auth_identity_ldap (app_id, server_name, last_used_username); ``` - `id`: The primary key of this table. This is the same as other `_auth_identity_*` tables.