Skip to content

Commit 00b1c60

Browse files
Rewrite LDAP draft
1 parent 97be61c commit 00b1c60

1 file changed

Lines changed: 82 additions & 32 deletions

File tree

docs/specs/ldap.md

Lines changed: 82 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,12 @@
22
* [Supported LDAP protocol](#supported-ldap-protocol)
33
* [Authenticate Authgear as a LDAP client](#authenticate-authgear-as-a-ldap-client)
44
* [Configuration of LDAP servers](#configuration-of-ldap-servers)
5+
* [Validation on the configuration](#validation-on-the-configuration)
6+
* [Testing on the configuration](#testing-on-the-configuration)
57
* [The database schema of a LDAP identity](#the-database-schema-of-a-ldap-identity)
68
* [Handling of a LDAP identity](#handling-of-a-ldap-identity)
79
* [The UX of LDAP in Auth UI](#the-ux-of-ldap-in-auth-ui)
10+
* [Errors](#errors)
811

912
# LDAP
1013

@@ -43,38 +46,29 @@ In `authgear.yaml`
4346
identity:
4447
ldap:
4548
servers:
46-
- name: ldap1
49+
- name: default
4750
url: "ldap://localhost:389"
48-
base_distinguished_name: "dc=localhost"
49-
relative_distinguished_name_attribute: "uid"
50-
- name: ldap2
51-
url: "ldap://mycompany.com:389"
52-
base_distinguished_name: "dc=mycompany,dc=com"
53-
relative_distinguished_name_attribute: "uid"
51+
base_dn: "dc=localhost"
52+
search_filter_template: |
53+
{{- if (hasSuffix $.Username "@mycompany.com") }}
54+
(&(objectCategory=person)(objectClass=user)(memberof=dc=mycompany,dc=com)(sAMAccountName={{ $.Username }}))
55+
{{- else }}
56+
(&(objectCategory=person)(objectClass=user)(memberof=dc=anothercompany,dc=com)(sAMAccountName={{ $.Username }}))
57+
{{- end }}
58+
user_id_attribute: "myUserID"
5459
```
5560
56-
- `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.
61+
- `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.
5762
- `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.
63+
- `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).
64+
- `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.
65+
- `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.
5866

5967
> Why does `identity.ldap.servers.url` allow scheme, host, and port?
6068
> 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).
6169
> In particular, a LDAP URL can contain multiple question mark characters.
6270
> 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).
6371

64-
- `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).
65-
- `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`.
66-
67-
> 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.
68-
> In the future, we can support a new configuration, distinguished_name_template, which is a Go template that MUST return a DN.
69-
> It looks like
70-
> ```
71-
> {{- if (hasSuffix $.Username "@mycompany.com") }}
72-
> {{- (ldapDN (ldapAttribute "uid" $.Username) (ldapParse "dc=mycompany,dc=com") ) }}
73-
> {{- else }}
74-
> {{- (ldapDN (ldapAttribute "uid" $.Username) (ldapParse "dc=anothercompany,dc=com") ) }}
75-
> {{- end }}
76-
> ```
77-
7872
> 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
7973
> what attributes map to which standard attributes.
8074

@@ -84,45 +78,92 @@ In `authgear.secrets.yaml`
8478
secrets:
8579
- data:
8680
items:
87-
- name: ldap1
81+
- name: default
8882
username: authgear
8983
password: secret1
90-
- name: ldap2
91-
username: authgear
92-
password: secret2
9384
key: ldap
9485
```
9586

9687
- `items.name`: To associate a LDAP server in `authgear.yaml`.
9788
- `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.
9889
- `items.password`: Optional. The password Authgear uses to authenticate itself to the LDAP server. If `username` is provided, then `password` is required.
9990

91+
## Validation on the configuration
92+
93+
Here is the JSON schema for the LDAP server configuration.
94+
95+
```
96+
{
97+
"type": "object",
98+
"additionalProperties": false,
99+
"required": ["name", "url", "base_dn", "search_filter_template", "user_id_attribute"],
100+
"properties": {
101+
"name": {
102+
"type": "string",
103+
"minLength": 1
104+
},
105+
"url": {
106+
"type": "string",
107+
"format": "ldap_url"
108+
},
109+
"base_dn": {
110+
"type": "string",
111+
"format": "ldap_dn"
112+
},
113+
"search_filter_template": {
114+
"type": "string",
115+
"format": "ldap_search_filter_template"
116+
},
117+
"user_id_attribute": {
118+
"type": "string",
119+
"format": "ldap_attribute_name"
120+
}
121+
}
122+
}
123+
```
124+
125+
- `format: ldap_url`: It is a JSON schema format that implements the rules of `identity.ldap.servers.url`.
126+
- `format: ldap_dn`: It is a JSON schema format that validates the value to be a valid DN.
127+
- `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.
128+
- `format: ldap_attribute_name`: It is a JSON schema format that validates the value to be a valid LDAP attribute name.
129+
130+
## Testing on the configuration
131+
132+
> TODO: Add a mutation in the Admin API to test LDAP connection.
133+
> It should take the whole server configuration, and optionally a end-user username.
134+
> It connects the LDAP server with the URL and the credentials.
135+
> If the optional end-user username is given, it performs a Search request, and validates the user exists and has user_id_attribute.
136+
> It returns detailed API errors so that the portal can display relevant information for the developer to debug the configuration.
137+
> Such detailed API errors ARE NOT returned in actual use. They are reported as internal errors.
138+
100139
## The database schema of a LDAP identity
101140
102141
```sql
103142
CREATE TABLE _auth_identity_ldap
104143
(
105144
id text PRIMARY KEY REFERENCES _auth_identity (id),
106145
app_id text NOT NULL,
107-
server_url text NOT NULL,
108-
distinguished_name text NOT NULL,
146+
server_name text NOT NULL,
147+
user_id_attribute text NOT NULL,
148+
user_id_value text NOT NULL,
109149
claims jsonb NOT NULL,
110150
raw_entry_json jsonb NOT NULL
111151
);
112152
113-
CREATE UNIQUE INDEX _auth_identity_ldap_unique ON _auth_identity_ldap (app_id, server_url, distinguished_name);
153+
CREATE UNIQUE INDEX _auth_identity_ldap_unique ON _auth_identity_ldap (app_id, server_name, user_id_attribute, user_id_value);
114154
```
115155

116156
- `id`: The primary key of this table. This is the same as other `_auth_identity_*` tables.
117157
- `app_id`: The app ID of this table for multi-tenant. This is the same as other `_auth_identity_*` tables.
118-
- `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.
119-
- `distinguished_name`: The distinguished name of this LDAP entry.
158+
- `server_name`: The `name` of the LDAP server.
159+
- `user_id_attribute`: The `user_id_attribute` of the LDAP server when this identity is created.
160+
- `user_id_value`: The value of the `user_id_attribute` of the user.
120161
- `claims`: The standard claims extracted from this LDAP entry.
121162
- `raw_entry_json`: The raw LDAP entry encoded in JSON. It looks like `{ "dn": "uid=johndoe,dc=example,dc=com", "attr1": ["value1"] }`.
122163

123164
## Handling of a LDAP identity
124165

125-
- To look up a LDAP identity in Authgear, we use the tuple `(app_id, server_url, distinguished_name)`.
166+
- To look up a LDAP identity in Authgear, we use the tuple `(app_id, server_name, user_id_attribute, user_id_value)`.
126167
- Similar to OAuth identity, we update an LDAP identity when it is used in login.
127168

128169
## 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
131172
Except that the enter-username-and-password page is hosted by Authgear as a integral part of Auth UI.
132173

133174
In the future, we may consider an option to make LDAP "replaces" Login ID in the UX.
175+
176+
## Errors
177+
178+
This section documents the expected errors.
179+
180+
|Description|Name|Reason|Info|
181+
|---|---|---|---|
182+
|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||
183+
|When the end-user cannot authenticate to the LDAP server|Unauthorized|InvalidCredentials||

0 commit comments

Comments
 (0)