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

Yannick Lecaillez
16.24.2016 f098b470ee98ecd478845cd0442a141e6a9b3a54
OPENDJ-3015: Oauth2 HTTP client must support HTTPS

* Add support for password file.
* Set JVM as default for TrustManager rather than trustAll.
* Use a common TrustManager & KeyManager
5 files modified
855 ■■■■■ changed files
opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/opendj-rest2ldap-config.json 669 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java 70 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAPHttpApplication.java 105 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java 9 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap.properties 2 ●●●●● patch | view | raw | blame | history
opendj-rest2ldap-servlet/src/main/webapp/WEB-INF/classes/opendj-rest2ldap-config.json
@@ -1,9 +1,47 @@
{
    "security": {
        // Specifies the policy for trusting server certificates exchanged
        // during SSL/StartTLS negotiation. This setting and the following
        // trust policy settings will be ignored if there is no connection
        // security. Acceptable values are:
        //
        // "trustAll" - blindly trust all server certificates
        // "jvm"      - only certificates signed by the authorities
        //              associated with the host JVM will be accepted (default)
        // "file"     - use a file-based trust store for validating
        //              certificates. This option requires the following
        //              "fileBasedTrustManager*" settings to be configured.
        //
        "trustManager": "jvm",
        // File based trust manager configuration (see above).
        "fileBasedTrustManagerType": "JKS",
        "fileBasedTrustManagerFile": "/path/to/truststore",
        "fileBasedTrustManagerPasswordFile": "/path/to/pinfile",
        // Specifies the key-manager for authenticating the http client performing
        // the request against the access-token resolver endpoint. Acceptable values are:
        //
        // "jvm"      - use the JVM's default keystore for retrieving certificates. (default)
        // "keystore" - use the named key store file for retrieving certificates.
        // "pkcs11"   - use a PKCS#11 token for retrieving certificates.
        "keyManager": "jvm",
        // Keystore based key manager configuration (see above).
        "keyStoreFile": "/path/to/keystore",
        "keyStorePasswordFile": "/path/to/pinfile",
        "keyStoreFormat": "JKS",
        "keyStoreProvider": "",
        // PKCS11 based key manager configuration
        "pkcs11PasswordFile": "/path/to/pinfile"
    },
    // The array of connection factories which will be used by the Rest2LDAP
    // Servlet and authentication filter.
    "ldapConnectionFactories" : {
    "ldapConnectionFactories": {
        // Unauthenticated connections used for performing bind requests.
        "bind" : {
        "bind": {
            // Indicates whether LDAP connections should be secured using
            // SSL or StartTLS. Acceptable values are:
            //
@@ -11,44 +49,27 @@
            // "ssl"      - secure connection using LDAPS
            // "startTLS" - secure connection using LDAP+StartTLS
            //
            "connectionSecurity"       : "none",
            "connectionSecurity": "none",
            // Specifies the policy for trusting server certificates exchanged
            // during SSL/StartTLS negotiation. This setting and the following
            // trust policy settings will be ignored if there is no connection
            // security. Acceptable values are:
            //
            // "trustAll" - blindly trust all server certificates (default)
            // "jvm"      - only certificates signed by the authorities
            //              associated with the host JVM will be accepted
            // "file"     - use a file-based trust store for validating
            //              certificates. This option requires the following
            //              "fileBasedTrustManager*" settings to be configured.
            //
            "trustManager"             : "trustAll",
            // File based trust manager configuration (see above).
            "fileBasedTrustManagerType"     : "JKS",
            "fileBasedTrustManagerFile"     : "/path/to/truststore",
            "fileBasedTrustManagerPassword" : "password",
            // This alias points at an existing certificate that is used for SSL authentication for secure
            // communication between this gateway and the remote LDAP server.
            "sslCertAlias": "client-cert",
            // Re-usable pool of 24 connections per server.
            "connectionPoolSize"       : 24,
            "connectionPoolSize": 24,
            // Check pooled connections are alive every 30 seconds with a 500ms
            // heart beat timeout.
            "heartBeatIntervalSeconds"    : 30,
            "heartBeatTimeoutMilliSeconds" : 500,
            "heartBeatIntervalSeconds": 30,
            "heartBeatTimeoutMilliSeconds": 500,
            // The preferred load-balancing pool.
            "primaryLDAPServers"       : [
                {
                    "hostname" : "localhost",
                    "port"     : 1389
                }
            ],
            "primaryLDAPServers": [{
                "hostname": "localhost",
                "port": 1389
            }],
            // The fail-over load-balancing pool (optional).
            "secondaryLDAPServers"     : [
            "secondaryLDAPServers": [
                // Empty.
            ]
        },
@@ -56,25 +77,25 @@
        // Authenticated connections which will be used for searches during
        // authentication and proxied operations (if enabled). This factory
        // will re-use the server "bind" configuration.
        "root" : {
            "inheritFrom"    : "bind",
        "root": {
            "inheritFrom": "bind",
            // Defines how authentication should be performed. Only "simple"
            // authentication is supported at the moment.
            // If the OAuth 2.0 authorization policy is configured below,
            // then the directory service must be configured
            // to allow the user configured here to perform proxied authorization.
            "authentication" : {
                "simple" : {
                    "bindDN"       : "cn=directory manager",
                    "bindPassword" : "password"
            "authentication": {
                "simple": {
                    "bindDN": "cn=directory manager",
                    "bindPassword": "password"
                }
            }
        }
    },
    "authorization": {
        // The authorization policies to use. Supported policies are "anonymous", "basic" and "oauth2".
        // The authorization policies to use. Supported policies are "anonymous", "basic" and "oauth2".
        "policies": [ "basic" ],
        // Perform all operations using a pre-authorization connection. 
@@ -84,77 +105,77 @@
            "ldapConnectionFactory": "root"
        },
        // Use HTTP Basic authentication's information to bind to the LDAP server.
        "basic": {
            // Indicates whether the filter should allow alternative authentication
            // and, if so, which HTTP headers it should obtain the username and
            // password from.
            "supportAltAuthentication"        : true,
            "altAuthenticationUsernameHeader" : "X-OpenIDM-Username",
            "altAuthenticationPasswordHeader" : "X-OpenIDM-Password",
        // Use HTTP Basic authentication's information to bind to the LDAP server.
        "basic": {
            // Indicates whether the filter should allow alternative authentication
            // and, if so, which HTTP headers it should obtain the username and
            // password from.
            "supportAltAuthentication": true,
            "altAuthenticationUsernameHeader": "X-OpenIDM-Username",
            "altAuthenticationPasswordHeader": "X-OpenIDM-Password",
            // Define which LDAP bind mechanism to use
            // Supported mechanisms are "simple", "sasl-plain", "search"
            "bind": "search",
            // Define which LDAP bind mechanism to use
            // Supported mechanisms are "simple", "sasl-plain", "search"
            "bind": "search",
            // Bind to the LDAP server using the DN built from the HTTP Basic's username
            "simple": {
                // Connection factory used to perform the bind operation.
                // If missing, "bind" factory will be used.
                "ldapConnectionFactory": "bind",
            // Bind to the LDAP server using the DN built from the HTTP Basic's username
            "simple": {
                // Connection factory used to perform the bind operation.
                // If missing, "bind" factory will be used.
                "ldapConnectionFactory": "bind",
                // The Bind DN Template containing a single {username} which will be replaced by the authenticating
                // user's name. (i.e: uid={username},ou=People,dc=example,dc=com)
                // If missing, "{username}" is used.
                "bindDNTemplate": "uid={username},ou=People,dc=example,dc=com"
            },
                // The Bind DN Template containing a single {username} which will be replaced by the authenticating
                // user's name. (i.e: uid={username},ou=People,dc=example,dc=com)
                // If missing, "{username}" is used.
                "bindDNTemplate": "uid={username},ou=People,dc=example,dc=com"
            },
            // Bind to the LDAP server using a SASL Plain request
            "sasl-plain": {
                // Connection factory used to perform the bind operation.
                // If missing, "bind" factory will be used.
                "ldapConnectionFactory": "bind",
            // Bind to the LDAP server using a SASL Plain request
            "sasl-plain": {
                // Connection factory used to perform the bind operation.
                // If missing, "bind" factory will be used.
                "ldapConnectionFactory": "bind",
                // Authentication identity template containing a single {username} which will be replaced by the authenticating
                // Authentication identity template containing a single {username} which will be replaced by the authenticating
                // user's name. (i.e: u:{username})
                "authzIdTemplate": "u:{username}"
            },
            // Bind to the LDAP server using the resulting DN of a search request.
            "search": {
                // Connection factory used to perform the search operation.
                // If missing, "root" factory will be used.
                "searchLDAPConnectionFactory": "root",
                // Connection factory used to perform the bind operation.
                // If missing, "bind" factory will be used.
                "bindLDAPConnectionFactory": "bind",
                "authzIdTemplate": "u:{username}"
            },
            // Bind to the LDAP server using the resulting DN of a search request.
            "search": {
                // Connection factory used to perform the search operation.
                // If missing, "root" factory will be used.
                "searchLDAPConnectionFactory": "root",
                // Connection factory used to perform the bind operation.
                // If missing, "bind" factory will be used.
                "bindLDAPConnectionFactory": "bind",
                // The {username} filter format parameters will be substituted with the client-provided username,
                // using LDAP filter string character escaping.
                "baseDN"         : "ou=people,dc=example,dc=com",
                "scope"          : "sub", // Or "one".
                "filterTemplate" : "(&(uid={username})(objectClass=inetOrgPerson))"
            }
            // TODO: support for HTTP sessions?
        },
                "baseDN": "ou=people,dc=example,dc=com",
                "scope": "sub", // Or "one".
                "filterTemplate": "(&(uid={username})(objectClass=inetOrgPerson))"
            }
            // TODO: support for HTTP sessions?
        },
        // Use OAuth2 authorization method. If used, LDAP requests will be performed with proxied authorization control.
        // This field is optional.
        "oauth2": {
            // Access tokens associated realm.
            // This attribute is optional and has a string syntax.
            "realm": "myrealm",
            // Access tokens associated realm.
            // This attribute is optional and has a string syntax.
            "realm": "myrealm",
            // Defines the list of required scopes required to access the service.
            // This field is required and cannot be empty.
            "requiredScopes": [ "read", "write", "uid" ],
            // Specify the resolver to use to resolve OAuth2 access token.
            // This attribute is required and its value must be one of "openam", "rfc7662", "cts".
            // Note that the JSON object corresponding to this attribute value must be present
            // and well formed in the "oauth2" JSON attribute.
            "resolver": "openam",
            // Specify the resolver to use to resolve OAuth2 access token.
            // This attribute is required and its value must be one of "openam", "rfc7662", "cts".
            // Note that the JSON object corresponding to this attribute value must be present
            // and well formed in the "oauth2" JSON attribute.
            "resolver": "openam",
            // Configures caching of access token introspection results.
            // This attribute is optional, if it is not present, no token caching will be performed.
@@ -180,17 +201,21 @@
                // This attribute is required and must have a string syntax.
                "endpointURL": "http://openam.example.com:8080/openam/oauth2/tokeninfo",
                // The default authzIdTemplate demonstrates how an authorization DN may be constructed
                // from the "uid" field in the following example OpenAM tokeninfo response:
                // {
                //     "scope":["uid"],
                //     "realm":"/",
                //     "expires_in":45,
                //     "uid" : "bjensen",
                // }
                // This attribute is required and has a string syntax.
                // It must start with either 'dn:' or 'u:'.
                "authzIdTemplate": "dn:uid={uid},ou=People,dc=example,dc=com"
                // This alias points at an existing certificate that is used for SSL authentication for secure
                // communication between this gateway and the OpenAM access-token resolver.
                "sslCertAlias": "client-cert",
                // The default authzIdTemplate demonstrates how an authorization DN may be constructed
                // from the "uid" field in the following example OpenAM tokeninfo response:
                // {
                //     "scope":["uid"],
                //     "realm":"/",
                //     "expires_in":45,
                //     "uid" : "bjensen",
                // }
                // This attribute is required and has a string syntax.
                // It must start with either 'dn:' or 'u:'.
                "authzIdTemplate": "dn:uid={uid},ou=People,dc=example,dc=com"
            },
            // The RFC-7662 (see https://tools.ietf.org/html/rfc7662) access token resolver configuration.
@@ -201,52 +226,56 @@
                // This attribute is required and must have a string syntax.
                "endpointURL": "http://openam.example.com:8080/openam/oauth2/myrealm/introspect",
                // Token introspect endpoint requires authentication.
                // It should support HTTP basic authorization (a base64-encoded string of clientId:clientSecret)
                // These attributes are mandatory.
                "clientId": "client_id",
                "clientSecret": "client_secret",
                // This alias points at an existing certificate that is used for SSL authentication for secure
                // communication between this gateway and the introspection access-token resolver.
                "sslCertAlias": "client-cert",
                // The default authzIdTemplate demonstrates how an authorization DN may be constructed
                // from the "username" field in the following example introspect response:
                // {
                //     "active": true,
                //     "token_type": "access_token",
                //     "exp": 3524,
                //     "username" : "bjensen",
                // }
                // This attribute is required and has a string syntax.
                // It must start with either 'dn:' or 'u:'.
                "authzIdTemplate": "dn:uid={username},ou=People,dc=example,dc=com"
                // Token introspect endpoint requires authentication.
                // It should support HTTP basic authorization (a base64-encoded string of clientId:clientSecret)
                // These attributes are mandatory.
                "clientId": "client_id",
                "clientSecret": "client_secret",
                // The default authzIdTemplate demonstrates how an authorization DN may be constructed
                // from the "username" field in the following example introspect response:
                // {
                //     "active": true,
                //     "token_type": "access_token",
                //     "exp": 3524,
                //     "username" : "bjensen",
                // }
                // This attribute is required and has a string syntax.
                // It must start with either 'dn:' or 'u:'.
                "authzIdTemplate": "dn:uid={username},ou=People,dc=example,dc=com"
            },
            // The CTS access token resolver.
            // This attribute must be present if the "oauth2/resolver" is equal to "cts".
            // If "oauth2/resolver" is set to another resolver, this attribute will be ignored.
            // Note: You can use {userName/0} in authzIdTemplate configuration to access
            //       user id from the default CTS access token content config.
            // Note: You can use {userName/0} in authzIdTemplate configuration to access
            // user id from the default CTS access token content config.
            "cts": {
                // The connection factory to use to access CTS.
                // This value is only used in gateway mode.
                // This attribute must reference a connection factory defined in the "ldapConnectionFactories" section.
                // Default value: "root" (i.e the root connection factory will be used to access the CTS).
                "ldapConnectionFactory": "root",
                // The connection factory to use to access CTS.
                // This value is only used in gateway mode.
                // This attribute must reference a connection factory defined in the "ldapConnectionFactories" section.
                // Default value: "root" (i.e the root connection factory will be used to access the CTS).
                "ldapConnectionFactory": "root",
                // The access token base DN.
                // This attribute is required and must have a string syntax.
                "baseDN": "ou=famrecords,ou=openam-session,ou=tokens,dc=example,dc=com",
                // The default authzIdTemplate demonstrates how an authorization DN may be constructed
                // from the "userName" field in the following example CTS access token entry:
                // {
                //     "active": true,
                //     "tokenName": ["access_token"],
                //     "exp": [3524],
                //     "userName" : ["bjensen"],
                // }
                // This attribute is required and has a string syntax.
                // It must start with either 'dn:' or 'u:'.
                "authzIdTemplate": "dn:uid={userName/0},ou=People,dc=example,dc=com"
                // The default authzIdTemplate demonstrates how an authorization DN may be constructed
                // from the "userName" field in the following example CTS access token entry:
                // {
                //     "active": true,
                //     "tokenName": ["access_token"],
                //     "exp": [3524],
                //     "userName" : ["bjensen"],
                // }
                // This attribute is required and has a string syntax.
                // It must start with either 'dn:' or 'u:'.
                "authzIdTemplate": "dn:uid={userName/0},ou=People,dc=example,dc=com"
            },
            // ONLY FOR TEST PURPOSE: A File based access token resolver
@@ -257,125 +286,263 @@
                // You can test the rest2ldap OAuth2 authorization support by providing some json token files under
                // the directory set in the configuration below.
                // File names must be equal to the token strings.
                // The file content must a JSON object with the following attributes:
                // 'scope', 'expireTime' and all the field(s) needed to resolve the authzIdTemplate.
                // The file content must a JSON object with the following attributes:
                // 'scope', 'expireTime' and all the field(s) needed to resolve the authzIdTemplate.
                "folderPath": "/path/to/test/folder",
                // The default authzIdTemplate demonstrates how an authorization DN may be constructed
                // from the "uid" field extracted from a fake token file:
                // {
                //      "scope": ["read", "uid", "write"],
                //      "expireTime": 1961336698000,
                //      "uid": "bjensen"
                // }
                // This attribute is required and has a string syntax.
                // It must start with either 'dn:' or 'u:'.
                "authzIdTemplate": "dn:uid={uid},ou=People,dc=example,dc=com"
                // The default authzIdTemplate demonstrates how an authorization DN may be constructed
                // from the "uid" field extracted from a fake token file:
                // {
                //     "scope": ["read", "uid", "write"],
                //     "expireTime": 1961336698000,
                //     "uid": "bjensen"
                // }
                // This attribute is required and has a string syntax.
                // It must start with either 'dn:' or 'u:'.
                "authzIdTemplate": "dn:uid={uid},ou=People,dc=example,dc=com"
            }
        }
    },
    // The REST APIs and their LDAP attribute mappings.
    "mappings" : {
        "/users" : {
            "baseDN"              : "ou=people,dc=example,dc=com",
            "readOnUpdatePolicy"  : "controls",
            "useSubtreeDelete"    : false,
            "usePermissiveModify" : true,
            "etagAttribute"       : "etag",
            "namingStrategy"      : {
                "strategy"    : "clientDNNaming",
                "dnAttribute" : "uid"
            },
            "additionalLDAPAttributes" : [
                {
                    "type" : "objectClass",
                    "values" : [
                        "top",
                        "person",
                        "organizationalPerson",
                        "inetOrgPerson"
                    ]
                }
            ],
            "attributes" : {
                "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
                "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
                "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
                "userName"    : { "simple"   : { "ldapAttribute" : "mail", "isSingleValued" : true, "writability" : "readOnly" } },
                "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true } },
                "name"        : { "object"   : {
                    "givenName"  : { "simple" : { "ldapAttribute" : "givenName", "isSingleValued" : true } },
                    "familyName" : { "simple" : { "ldapAttribute" : "sn", "isSingleValued" : true, "isRequired" : true } }
                } },
                "manager"     : { "reference" : {
                    "ldapAttribute" : "manager",
                    "baseDN"        : "ou=people,dc=example,dc=com",
                    "primaryKey"    : "uid",
                    "mapper"         : { "object" : {
                        "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
                        "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
                    } }
                } },
                "groups"     : { "reference" : {
                    "ldapAttribute" : "isMemberOf",
                    "baseDN"        : "ou=groups,dc=example,dc=com",
                    "writability"   : "readOnly",
                    "primaryKey"    : "cn",
                    "mapper"        : { "object" : {
                        "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true } }
                    } }
                } },
                "contactInformation" : { "object" : {
                    "telephoneNumber" : { "simple" : { "ldapAttribute" : "telephoneNumber", "isSingleValued" : true } },
                    "emailAddress"    : { "simple" : { "ldapAttribute" : "mail", "isSingleValued" : true } }
                } },
                "meta"        : { "object" : {
                    "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
                    "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
                } }
            }
        },
        "/groups" : {
            "baseDN"              : "ou=groups,dc=example,dc=com",
            "readOnUpdatePolicy"  : "controls",
            "useSubtreeDelete"    : false,
            "usePermissiveModify" : true,
            "etagAttribute"       : "etag",
            "namingStrategy"      : {
                "strategy"    : "clientDNNaming",
                "dnAttribute" : "cn"
            },
            "additionalLDAPAttributes" : [
                {
                    "type" : "objectClass",
                    "values" : [
                        "top",
                        "groupOfUniqueNames"
                    ]
                }
            ],
            "attributes" : {
                "schemas"     : { "constant" : [ "urn:scim:schemas:core:1.0" ] },
                "_id"         : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "createOnly" } },
                "_rev"        : { "simple"   : { "ldapAttribute" : "etag", "isSingleValued" : true, "writability" : "readOnly" } },
                "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "isRequired" : true, "writability" : "readOnly" } },
                "members"    : { "reference" : {
                    "ldapAttribute" : "uniqueMember",
                    "baseDN"        : "dc=example,dc=com",
                    "primaryKey"    : "uid",
                    "mapper"        : { "object" : {
                        "_id"         : { "simple"   : { "ldapAttribute" : "uid", "isSingleValued" : true, "isRequired" : true } },
                        "displayName" : { "simple"   : { "ldapAttribute" : "cn", "isSingleValued" : true, "writability" : "readOnlyDiscardWrites" } }
                    } }
                } },
                "meta"        : { "object" : {
                    "created"      : { "simple" : { "ldapAttribute" : "createTimestamp", "isSingleValued" : true, "writability" : "readOnly" } },
                    "lastModified" : { "simple" : { "ldapAttribute" : "modifyTimestamp", "isSingleValued" : true, "writability" : "readOnly" } }
                } }
            }
        }
    }
    // The REST APIs and their LDAP attribute mappings.
    "mappings": {
        "/users": {
            "baseDN": "ou=people,dc=example,dc=com",
            "readOnUpdatePolicy": "controls",
            "useSubtreeDelete": false,
            "usePermissiveModify": true,
            "etagAttribute": "etag",
            "namingStrategy": {
                "strategy": "clientDNNaming",
                "dnAttribute": "uid"
            },
            "additionalLDAPAttributes": [{
                "type": "objectClass",
                "values": [
                    "top",
                    "person",
                    "organizationalPerson",
                    "inetOrgPerson"
                ]
            }],
            "attributes": {
                "schemas": {
                    "constant": ["urn:scim:schemas:core:1.0"]
                },
                "_id": {
                    "simple": {
                        "ldapAttribute": "uid",
                        "isSingleValued": true,
                        "isRequired": true,
                        "writability": "createOnly"
                    }
                },
                "_rev": {
                    "simple": {
                        "ldapAttribute": "etag",
                        "isSingleValued": true,
                        "writability": "readOnly"
                    }
                },
                "userName": {
                    "simple": {
                        "ldapAttribute": "mail",
                        "isSingleValued": true,
                        "writability": "readOnly"
                    }
                },
                "displayName": {
                    "simple": {
                        "ldapAttribute": "cn",
                        "isSingleValued": true,
                        "isRequired": true
                    }
                },
                "name": {
                    "object": {
                        "givenName": {
                            "simple": {
                                "ldapAttribute": "givenName",
                                "isSingleValued": true
                            }
                        },
                        "familyName": {
                            "simple": {
                                "ldapAttribute": "sn",
                                "isSingleValued": true,
                                "isRequired": true
                            }
                        }
                    }
                },
                "manager": {
                    "reference": {
                        "ldapAttribute": "manager",
                        "baseDN": "ou=people,dc=example,dc=com",
                        "primaryKey": "uid",
                        "mapper": {
                            "object": {
                                "_id": {
                                    "simple": {
                                        "ldapAttribute": "uid",
                                        "isSingleValued": true,
                                        "isRequired": true
                                    }
                                },
                                "displayName": {
                                    "simple": {
                                        "ldapAttribute": "cn",
                                        "isSingleValued": true,
                                        "writability": "readOnlyDiscardWrites"
                                    }
                                }
                            }
                        }
                    }
                },
                "groups": {
                    "reference": {
                        "ldapAttribute": "isMemberOf",
                        "baseDN": "ou=groups,dc=example,dc=com",
                        "writability": "readOnly",
                        "primaryKey": "cn",
                        "mapper": {
                            "object": {
                                "_id": {
                                    "simple": {
                                        "ldapAttribute": "cn",
                                        "isSingleValued": true
                                    }
                                }
                            }
                        }
                    }
                },
                "contactInformation": {
                    "object": {
                        "telephoneNumber": {
                            "simple": {
                                "ldapAttribute": "telephoneNumber",
                                "isSingleValued": true
                            }
                        },
                        "emailAddress": {
                            "simple": {
                                "ldapAttribute": "mail",
                                "isSingleValued": true
                            }
                        }
                    }
                },
                "meta": {
                    "object": {
                        "created": {
                            "simple": {
                                "ldapAttribute": "createTimestamp",
                                "isSingleValued": true,
                                "writability": "readOnly"
                            }
                        },
                        "lastModified": {
                            "simple": {
                                "ldapAttribute": "modifyTimestamp",
                                "isSingleValued": true,
                                "writability": "readOnly"
                            }
                        }
                    }
                }
            }
        },
        "/groups": {
            "baseDN": "ou=groups,dc=example,dc=com",
            "readOnUpdatePolicy": "controls",
            "useSubtreeDelete": false,
            "usePermissiveModify": true,
            "etagAttribute": "etag",
            "namingStrategy": {
                "strategy": "clientDNNaming",
                "dnAttribute": "cn"
            },
            "additionalLDAPAttributes": [{
                "type": "objectClass",
                "values": [
                    "top",
                    "groupOfUniqueNames"
                ]
            }],
            "attributes": {
                "schemas": {
                    "constant": ["urn:scim:schemas:core:1.0"]
                },
                "_id": {
                    "simple": {
                        "ldapAttribute": "cn",
                        "isSingleValued": true,
                        "isRequired": true,
                        "writability": "createOnly"
                    }
                },
                "_rev": {
                    "simple": {
                        "ldapAttribute": "etag",
                        "isSingleValued": true,
                        "writability": "readOnly"
                    }
                },
                "displayName": {
                    "simple": {
                        "ldapAttribute": "cn",
                        "isSingleValued": true,
                        "isRequired": true,
                        "writability": "readOnly"
                    }
                },
                "members": {
                    "reference": {
                        "ldapAttribute": "uniqueMember",
                        "baseDN": "dc=example,dc=com",
                        "primaryKey": "uid",
                        "mapper": {
                            "object": {
                                "_id": {
                                    "simple": {
                                        "ldapAttribute": "uid",
                                        "isSingleValued": true,
                                        "isRequired": true
                                    }
                                },
                                "displayName": {
                                    "simple": {
                                        "ldapAttribute": "cn",
                                        "isSingleValued": true,
                                        "writability": "readOnlyDiscardWrites"
                                    }
                                }
                            }
                        }
                    }
                },
                "meta": {
                    "object": {
                        "created": {
                            "simple": {
                                "ldapAttribute": "createTimestamp",
                                "isSingleValued": true,
                                "writability": "readOnly"
                            }
                        },
                        "lastModified": {
                            "simple": {
                                "ldapAttribute": "modifyTimestamp",
                                "isSingleValued": true,
                                "writability": "readOnly"
                            }
                        }
                    }
                }
            }
        }
    }
}
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAP.java
@@ -31,8 +31,8 @@
import static org.forgerock.opendj.rest2ldap.Utils.newLocalizedIllegalArgumentException;
import static org.forgerock.opendj.rest2ldap.Utils.newJsonValueException;
import static org.forgerock.util.time.Duration.*;
import static org.forgerock.opendj.ldap.KeyManagers.useSingleCertificate;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
@@ -42,6 +42,9 @@
import java.util.Set;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509KeyManager;
import org.forgerock.json.JsonValue;
import org.forgerock.json.resource.CollectionResourceProvider;
import org.forgerock.json.resource.ResourceException;
@@ -68,7 +71,6 @@
import org.forgerock.opendj.ldap.SSLContextBuilder;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.TimeoutResultException;
import org.forgerock.opendj.ldap.TrustManagers;
import org.forgerock.opendj.ldap.requests.BindRequest;
import org.forgerock.opendj.ldap.requests.Requests;
import org.forgerock.opendj.ldap.requests.SearchRequest;
@@ -88,10 +90,18 @@
     * Specifies the mechanism which should be used for trusting certificates
     * presented by the LDAP server.
     */
    private enum TrustManagerType {
    enum TrustManagerType {
        TRUSTALL, JVM, FILE
    }
    /**
     * Specifies the mechanism which manage which X509 certificate-based key pairs should be used to authenticate the
     * local side of a secure socket.
     */
    enum KeyManagerType {
        JVM, KEYSTORE, PKCS11
    }
    /** A builder for incrementally constructing LDAP resource collections. */
    public static final class Builder {
        private final List<Attribute> additionalLDAPAttributes = new LinkedList<>();
@@ -784,6 +794,12 @@
     *            The JSON configuration.
     * @param name
     *            The name of the connection factory configuration to be parsed.
     * @param trustManager
     *            The trust manager to use for secure connection. Can be {@code null}
     *            to use the default JVM trust manager.
     * @param keyManager
     *            The key manager to use for secure connection. Can be {@code null}
     *            to use the default JVM key manager.
     * @param providerClassLoader
     *            The {@link ClassLoader} used to fetch the
     *            {@link org.forgerock.opendj.ldap.spi.TransportProvider}.
@@ -794,10 +810,12 @@
     */
    public static ConnectionFactory configureConnectionFactory(final JsonValue configuration,
                                                               final String name,
                                                               final TrustManager trustManager,
                                                               final X509KeyManager keyManager,
                                                               final ClassLoader providerClassLoader) {
        final JsonValue normalizedConfiguration =
                normalizeConnectionFactory(configuration, name, 0);
        return configureConnectionFactory(normalizedConfiguration, providerClassLoader);
        return configureConnectionFactory(normalizedConfiguration, trustManager, keyManager, providerClassLoader);
    }
    /**
@@ -809,13 +827,19 @@
     *            The JSON configuration.
     * @param name
     *            The name of the connection factory configuration to be parsed.
     * @param trustManager
     *            The trust manager to use for secure connection. Can be {@code null}
     *            to use the default JVM trust manager.
     * @param keyManager
     *            The key manager to use for secure connection. Can be {@code null}
     *            to use the default JVM key manager.
     * @return A new connection factory using the provided JSON configuration.
     * @throws IllegalArgumentException
     *             If the configuration is invalid.
     */
    public static ConnectionFactory configureConnectionFactory(final JsonValue configuration,
            final String name) {
        return configureConnectionFactory(configuration, name, null);
            final String name, final TrustManager trustManager, final X509KeyManager keyManager) {
        return configureConnectionFactory(configuration, name, trustManager, keyManager, null);
    }
    /**
@@ -908,6 +932,8 @@
    }
    private static ConnectionFactory configureConnectionFactory(final JsonValue configuration,
                                                                final TrustManager trustManager,
                                                                final X509KeyManager keyManager,
                                                                final ClassLoader providerClassLoader) {
        final long heartBeatIntervalSeconds = configuration.get("heartBeatIntervalSeconds").defaultTo(30L).asLong();
        final Duration heartBeatInterval = duration(Math.max(heartBeatIntervalSeconds, 1L), TimeUnit.SECONDS);
@@ -948,32 +974,14 @@
            try {
                // Configure SSL.
                final SSLContextBuilder builder = new SSLContextBuilder();
                // Parse trust store configuration.
                final TrustManagerType trustManagerType =
                        configuration.get("trustManager").defaultTo(TrustManagerType.TRUSTALL)
                                .asEnum(TrustManagerType.class);
                switch (trustManagerType) {
                case TRUSTALL:
                    builder.setTrustManager(TrustManagers.trustAll());
                    break;
                case JVM:
                    // Do nothing: JVM trust manager is the default.
                    break;
                case FILE:
                    final String fileName =
                            configuration.get("fileBasedTrustManagerFile").required().asString();
                    final String password =
                            configuration.get("fileBasedTrustManagerPassword").asString();
                    final String type = configuration.get("fileBasedTrustManagerType").asString();
                    builder.setTrustManager(TrustManagers.checkUsingTrustStore(fileName,
                            password != null ? password.toCharArray() : null, type));
                    break;
                }
                builder.setTrustManager(trustManager);
                final String sslCertAlias = configuration.get("sslCertAlias").asString();
                builder.setKeyManager(sslCertAlias != null
                        ? useSingleCertificate(sslCertAlias, keyManager)
                        : keyManager);
                options.set(SSL_CONTEXT, builder.getSSLContext());
                options.set(SSL_USE_STARTTLS,
                            connectionSecurity == ConnectionSecurity.STARTTLS);
            } catch (GeneralSecurityException | IOException e) {
                options.set(SSL_USE_STARTTLS, connectionSecurity == ConnectionSecurity.STARTTLS);
            } catch (GeneralSecurityException e) {
                // Rethrow as unchecked exception.
                throw new IllegalArgumentException(e);
            }
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Rest2LDAPHttpApplication.java
@@ -17,6 +17,10 @@
package org.forgerock.opendj.rest2ldap;
import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*;
import static org.forgerock.http.handler.HttpClientHandler.*;
import static org.forgerock.opendj.ldap.KeyManagers.*;
import static org.forgerock.opendj.ldap.TrustManagers.checkUsingTrustStore;
import static org.forgerock.opendj.ldap.TrustManagers.trustAll;
import static org.forgerock.http.util.Json.readJsonLenient;
import static org.forgerock.json.JsonValueFunctions.duration;
import static org.forgerock.json.JsonValueFunctions.enumConstant;
@@ -31,12 +35,14 @@
import static org.forgerock.util.Reject.checkNotNull;
import static org.forgerock.util.Utils.closeSilently;
import static org.forgerock.util.Utils.joinAsString;
import static org.forgerock.opendj.rest2ldap.Utils.readPasswordFromFile;
import java.io.IOException;
import java.io.InputStream;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@@ -46,6 +52,10 @@
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import javax.net.ssl.KeyManager;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509KeyManager;
import org.forgerock.openig.oauth2.AccessTokenInfo;
import org.forgerock.openig.oauth2.AccessTokenException;
import org.forgerock.openig.oauth2.AccessTokenResolver;
@@ -71,11 +81,14 @@
import org.forgerock.opendj.ldap.DN;
import org.forgerock.opendj.ldap.SearchScope;
import org.forgerock.opendj.ldap.schema.Schema;
import org.forgerock.opendj.rest2ldap.Rest2LDAP.KeyManagerType;
import org.forgerock.opendj.rest2ldap.Rest2LDAP.TrustManagerType;
import org.forgerock.opendj.rest2ldap.authz.AuthenticationStrategy;
import org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.ConditionalFilter;
import org.forgerock.services.context.SecurityContext;
import org.forgerock.util.Factory;
import org.forgerock.util.Function;
import org.forgerock.util.Options;
import org.forgerock.util.Pair;
import org.forgerock.util.PerItemEvictionStrategyCache;
import org.forgerock.util.annotations.VisibleForTesting;
@@ -113,6 +126,9 @@
    /** Used for token caching. */
    private ScheduledExecutorService executorService;
    private TrustManager trustManager;
    private X509KeyManager keyManager;
    /** Define the method which should be used to resolve an OAuth2 access token. */
    private enum OAuth2ResolverType {
        RFC7662, OPENAM, CTS, FILE;
@@ -175,6 +191,7 @@
        try {
            final JsonValue configuration = readJson(configurationUrl);
            executorService = Executors.newSingleThreadScheduledExecutor();
            configureSecurity(configuration.get("security"));
            configureConnectionFactories(configuration.get("ldapConnectionFactories"));
            return Handlers.chainOf(
                    CrestHttp.newHttpHandler(configureRest2Ldap(configuration)),
@@ -204,10 +221,76 @@
        return router;
    }
    private void configureSecurity(final JsonValue configuration) {
        try {
            trustManager = configureTrustManager(configuration, TrustManagerType.JVM);
        } catch (GeneralSecurityException | IOException e) {
            throw new IllegalArgumentException(ERR_CONFIG_INVALID_TRUST_MANAGER
                    .get(configuration.getPointer(), e.getLocalizedMessage()).toString(), e);
        }
        try {
            keyManager = configureKeyManager(configuration, KeyManagerType.JVM);
        } catch (GeneralSecurityException | IOException e) {
            throw new IllegalArgumentException(ERR_CONFIG_INVALID_KEY_MANAGER
                    .get(configuration.getPointer(), e.getLocalizedMessage()).toString(), e);
        }
    }
    private TrustManager configureTrustManager(JsonValue config, TrustManagerType defaultIfMissing)
            throws GeneralSecurityException, IOException {
        // Parse trust store configuration.
        final TrustManagerType trustManagerType =
                config.get("trustManager").defaultTo(defaultIfMissing).as(enumConstant(TrustManagerType.class));
        switch (trustManagerType) {
        case TRUSTALL:
            return trustAll();
        case JVM:
            return null;
        case FILE:
            final String fileName = config.get("fileBasedTrustManagerFile").required().asString();
            final String passwordFile = config.get("fileBasedTrustManagerPasswordFile").asString();
            final String password = passwordFile != null
                    ? readPasswordFromFile(passwordFile)
                    : config.get("fileBasedTrustManagerPassword").asString();
            final String type = config.get("fileBasedTrustManagerType").asString();
            return checkUsingTrustStore(fileName, password != null ? password.toCharArray() : null, type);
        default:
            throw new IllegalArgumentException("Unsupported trust-manager type: " + trustManagerType);
        }
    }
    private X509KeyManager configureKeyManager(JsonValue config, KeyManagerType defaultIfMissing)
            throws GeneralSecurityException, IOException {
        // Parse trust store configuration.
        final KeyManagerType keyManagerType = config.get("keyManager").defaultTo(defaultIfMissing)
                .as(enumConstant(KeyManagerType.class));
        switch (keyManagerType) {
        case JVM:
            return useJvmDefaultKeyStore();
        case KEYSTORE:
            final String fileName = config.get("keyStoreFile").required().asString();
            final String passwordFile = config.get("keyStorePasswordFile").asString();
            final String password = passwordFile != null
                    ? readPasswordFromFile(passwordFile)
                    : config.get("keyStorePassword").asString();
            final String format = config.get("keyStoreFormat").asString();
            final String provider = config.get("keyStoreProvider").asString();
            return useKeyStoreFile(fileName, password != null ? password.toCharArray() : null, format, provider);
        case PKCS11:
            final String pkcs11PasswordFile = config.get("pkcs11PasswordFile").asString();
            return usePKCS11Token(pkcs11PasswordFile != null
                    ? readPasswordFromFile(pkcs11PasswordFile).toCharArray()
                    : null);
        default:
            throw new IllegalArgumentException("Unsupported key-manager type: " + keyManagerType);
        }
    }
    private void configureConnectionFactories(final JsonValue config) {
        connectionFactories.clear();
        for (String name : config.keys()) {
            connectionFactories.put(name, configureConnectionFactory(config, name));
            connectionFactories.put(name, configureConnectionFactory(config, name, trustManager, keyManager));
        }
    }
@@ -282,9 +365,10 @@
        case RFC7662:
            return parseRfc7662Resolver(configuration);
        case OPENAM:
            return new OpenAmAccessTokenResolver(new HttpClientHandler(),
            final JsonValue openAm = configuration.get("openam");
            return new OpenAmAccessTokenResolver(newHttpClientHandler(openAm),
                                                 TimeService.SYSTEM,
                                                 configuration.get("openam").get("endpointURL").required().asString());
                                                 openAm.get("endpointURL").required().asString());
        case CTS:
            final JsonValue cts = configuration.get("cts").required();
            return newCtsAccessTokenResolver(
@@ -303,7 +387,7 @@
        final JsonValue rfc7662 = configuration.get("rfc7662").required();
        final String introspectionEndPointURL = rfc7662.get("endpointURL").required().asString();
        try {
            return newRfc7662AccessTokenResolver(new HttpClientHandler(),
            return newRfc7662AccessTokenResolver(newHttpClientHandler(rfc7662),
                                                 new URI(introspectionEndPointURL),
                                                 rfc7662.get("clientId").required().asString(),
                                                 rfc7662.get("clientSecret").required().asString());
@@ -313,6 +397,19 @@
        }
    }
    private HttpClientHandler newHttpClientHandler(final JsonValue config) throws HttpApplicationException {
        final Options httpOptions = Options.defaultOptions();
        if (trustManager != null) {
            httpOptions.set(OPTION_TRUST_MANAGERS, new TrustManager[] { trustManager });
        }
        if (keyManager != null) {
            final String keyAlias = config.get("sslCertAlias").asString();
            httpOptions.set(OPTION_KEY_MANAGERS,
                    new KeyManager[] { keyAlias != null ? useSingleCertificate(keyAlias, keyManager) : keyManager });
        }
        return new HttpClientHandler(httpOptions);
    }
    private Duration parseCacheExpiration(final JsonValue expirationJson) {
        try {
            final Duration expiration = expirationJson.as(duration());
opendj-rest2ldap/src/main/java/org/forgerock/opendj/rest2ldap/Utils.java
@@ -27,6 +27,10 @@
import static org.forgerock.opendj.ldap.schema.CoreSchema.getGeneralizedTimeSyntax;
import static org.forgerock.opendj.ldap.schema.CoreSchema.getIntegerSyntax;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@@ -70,6 +74,11 @@
                }
            };
    static String readPasswordFromFile(String fileName) throws IOException {
        try (final BufferedReader reader = new BufferedReader(new FileReader(new File(fileName)))) {
            return reader.readLine();
        }
    }
    static Object attributeToJson(final Attribute a) {
        final Function<ByteString, Object, NeverThrowsException> f = byteStringToJson(a.getAttributeDescription());
opendj-rest2ldap/src/main/resources/org/forgerock/opendj/rest2ldap/rest2ldap.properties
@@ -107,3 +107,5 @@
ERR_RUNTIME_EXCEPTION_61=A runtime exception occurred wile processing the request '%s': '%s'
ERR_PASSWORD_MODIFY_REQUEST_IS_INVALID_62=The password modify request has been rejected because it is invalid. \
 A password modify request may contain two string valued fields 'oldPassword' and 'newPassword'
ERR_CONFIG_INVALID_TRUST_MANAGER_63=The trust-manager defined in '%s' is invalid: %s
ERR_CONFIG_INVALID_KEY_MANAGER_64=The key-manager defined in '%s' is invalid: %s