mirror of https://github.com/OpenIdentityPlatform/OpenDJ.git

Valery Kharseko
yesterday 81bc6cd81b347f178a1a2b85e33b7101d1241c41
refs
author Valery Kharseko <vharseko@3a-systems.ru>
Monday, October 5, 2026 14:38 +0200
committer GitHub <noreply@github.com>
Monday, October 5, 2026 14:38 +0200
commit81bc6cd81b347f178a1a2b85e33b7101d1241c41
tree b1f1ba09c188f21e438a02f2727b4ff19fb9b2bc tree | zip | gz
parent 92ea60ad34783ab90ab7e2574f2169fc8ef03900 view | diff
[#1155] Make the rest2ldap bind templates and HTTP Basic credentials work as documented (#1165)

## Problem

rest2ldap could not build an LDAP name from HTTP Basic credentials in
several configurations the reference documentation recommends. See
#1155.

- `sasl-plain` with an `authzIdTemplate` starting with `dn:` failed
every bind with `INVALID_DN_SYNTAX`, because the whole `dn:...` string
went to `DN.format()`.
- The `simple` bind with its default `bindDnTemplate`, `{username}`,
never worked. `DN.format()` escaped the whole DN user name as one
attribute value. The parse error was thrown from `authenticate()`
instead of failing the returned promise.
- An HTTP Basic password that contains `:` was dropped: the credentials
were split with `split(":")` and anything but two parts meant "no
credentials". A bare `Basic` header threw
`StringIndexOutOfBoundsException`.
- A `%` in the fixed part of a template was read by `String.format()` as
a format specifier (`o=100%,dc=example` →
`MissingFormatArgumentException: '%,d'`). `DnTemplate` also stripped a
trailing `,..` after an escaped comma.

## Change

- **Bind DN templates** (`authz/Utils.formatBindDn`, used by
`SimpleBindStrategy` and the `dn:` branch of `SaslPlainStrategy`): a
template which is just `{username}` takes the user name as the DN
(`DN.valueOf`); any other template keeps `DN.format()`, which escapes
the user name as one attribute value. A user name which does not give a
DN fails the returned promise with `INVALID_CREDENTIALS` (HTTP 401)
before a connection is taken.
- **`sasl-plain`**: only the part after `dn:` is formatted, and the
authentication ID sent is `dn:<DN>`. Spaces after `dn:` are ignored, as
for the OAuth2 template, so `dn: {username}` reads as `dn:{username}`.
- **OAuth2 `authzIdTemplate`** (`AuthzIdTemplate`): a `dn:` template
which is just one placeholder, such as `dn:{sub}`, takes the field value
as the DN as well. Without this, it failed in the same way as the
default `simple` template.
- **HTTP Basic** (`CredentialExtractors`): the credentials are split at
the first `:`, as [RFC 7617
§2](https://www.rfc-editor.org/rfc/rfc7617#section-2) requires, and the
scheme must be followed by a space. The case-insensitive match no longer
depends on the default locale. Credentials which do not decode as
base64, such as `Basic abc` or unpadded base64, are "no credentials"
instead of a `NullPointerException`.
- **Empty password** (`HttpBasicAuthenticationFilter`): refused with 401
before any bind. A simple bind with a DN and an empty password is an
unauthenticated bind ([RFC 4513
§5.1.2](https://www.rfc-editor.org/rfc/rfc4513#section-5.1.2)). A server
which accepts it would let the request run as the named user through
proxied authorization without checking any password. Before this change,
`user:` from the Basic header was "no credentials" and fell through to
the next authorization mechanism; an empty alternative password header
already reached the bind strategy. The server's own HTTP Basic mechanism
(`HttpBasicAuthorizationMechanism`) uses the same filter and extractor,
so it gets both changes.
- **`%` in templates**: `DnTemplate`, `AuthzIdTemplate` and the
`{username}` templates of the `basic` configuration (`bindDnTemplate`,
`authzIdTemplate`, `filterTemplate`) escape `%` in their fixed parts.
The configuration defaults are now `{username}` and `u:{username}`
instead of the raw format strings `%s` and `u:%s`. A literal `%s`
written in `config.json`, which was never documented, is now kept as
text.
- **`DnTemplate`**: a trailing `,..` is stripped only when its comma is
not escaped (an even number of backslashes before it).
- The reference (`appendix-rest2ldap.adoc`) says how a template which is
just `{username}` or one field is read, and gives the `sasl-plain`
default.

Departures from the issue text: an empty password is parsed but refused
(see above), and a user name which does not give a DN is reported as
`INVALID_CREDENTIALS` (401) rather than `INVALID_DN_SYNTAX`, which maps
to 400.

## Test

- `SimpleBindStrategyTest` (new, in-memory backend): the default
template binds with a DN user name. A template escapes the user name, so
`bjensen,ou=People` cannot add RDNs. A user name which is not a DN fails
the promise with `INVALID_CREDENTIALS` and takes no connection.
- `SaslPlainStrategyTest` (new): the authentication ID sent for `dn:%s`,
`dn: %s`, `dn:uid=%s,...` (escaped) and `u:%s`. A user name which is not
a DN fails without taking a connection or sending a bind.
- `BasicJsonConfigurationTestCase` (new): the `basic` configuration
defaults for `simple` and `sasl-plain`, `dn:{username}`, and `%` in all
three templates, read from the request sent to the server.
- `CredentialExtractorsTest`: `bjensen:se:cret`, `bjensen::`, a DN user
name and `bjensen:`. `testBasicReturnNullOnInvalidCredentials` used to
fill `headers` and then pass `new Headers()`; it now checks the headers
it builds, including a bare `Basic`, `Basic abc` and unpadded base64.
- `HttpBasicAuthenticationFilterTest`: an empty password gives 401 and
the strategy is never called.
- `AuthzIdTemplateTest`, `DnTemplateTest`: `%` in the fixed part,
`dn:{dn}` and `dn: {dn}` with a DN value, `\,..` and `\\,..`.

Results:

- Against master, 27 of the new checks fail, each for the reason the
issue describes.
- With the fix, the whole `opendj-rest2ldap` suite passes (571 tests).
Javadoc doclint passes, and a broken `{@link}` added as a negative
control fails it.
- Nine mutants each turn at least one test red:
`DN.valueOf(String.format(...))` for every template, the empty password
let through, any backslash escaping the comma, the old `%s` default, a
split at the last colon, no `null` check after `Base64.decode`, no trim
after `dn:`, and `getConnectionAsync()` called before the user name is
checked, in `SimpleBindStrategy` and in `SaslPlainStrategy`.

Fixes #1155
14 files modified
3 files added
638 ■■■■■ changed files
opendj-doc-generated-ref/src/main/asciidoc/reference/appendix-rest2ldap.adoc 17 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/DnTemplate.java 26 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LdapHttpApplication.java 9 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthenticationStrategies.java 6 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplate.java 16 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/CredentialExtractors.java 22 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilter.java 5 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SaslPlainStrategy.java 28 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/SimpleBindStrategy.java 14 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/authz/Utils.java 30 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/BasicJsonConfigurationTestCase.java 135 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/DnTemplateTest.java 7 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/AuthzIdTemplateTest.java 23 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/CredentialExtractorsTest.java 47 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/HttpBasicAuthenticationFilterTest.java 21 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/SaslPlainStrategyTest.java 108 ●●●●● diff | view | raw | blame | history
opendj-rest2ldap/src/test/java/org/forgerock/opendj/rest2ldap/authz/SimpleBindStrategyTest.java 124 ●●●●● diff | view | raw | blame | history