OPENDJ-2955 Use i18n in rest2ldap
10 files added
17 files modified
| | |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>org.forgerock.commons</groupId> |
| | | <artifactId>i18n-core</artifactId> |
| | | </dependency> |
| | | |
| | | <dependency> |
| | | <groupId>org.forgerock.openig</groupId> |
| | | <artifactId>openig-oauth2-resource-server-filter</artifactId> |
| | | <version>${openig.version}</version> |
| | |
| | | <build> |
| | | <plugins> |
| | | <plugin> |
| | | <groupId>org.forgerock.commons</groupId> |
| | | <artifactId>i18n-maven-plugin</artifactId> |
| | | <executions> |
| | | <execution> |
| | | <phase>generate-sources</phase> |
| | | <goals> |
| | | <goal>generate-messages</goal> |
| | | </goals> |
| | | <configuration> |
| | | <messageFiles> |
| | | <messageFile>org/forgerock/opendj/rest2ldap/rest2ldap.properties</messageFile> |
| | | </messageFiles> |
| | | </configuration> |
| | | </execution> |
| | | </executions> |
| | | </plugin> |
| | | |
| | | <plugin> |
| | | <groupId>org.apache.felix</groupId> |
| | | <artifactId>maven-bundle-plugin</artifactId> |
| | | <extensions>true</extensions> |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static java.util.Collections.emptyList; |
| | | import static java.util.Collections.singletonList; |
| | | import static org.forgerock.opendj.ldap.Attributes.emptyAttribute; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.i18n; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.isNullOrEmpty; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newNotSupportedException; |
| | | import static org.forgerock.opendj.rest2ldap.WritabilityPolicy.READ_WRITE; |
| | | |
| | | import java.util.ArrayList; |
| | |
| | | import org.forgerock.json.JsonPointer; |
| | | import org.forgerock.json.JsonValue; |
| | | import org.forgerock.json.resource.BadRequestException; |
| | | import org.forgerock.json.resource.NotSupportedException; |
| | | import org.forgerock.json.resource.PatchOperation; |
| | | import org.forgerock.json.resource.ResourceException; |
| | | import org.forgerock.opendj.ldap.Attribute; |
| | |
| | | public List<Attribute> apply(Attribute newLDAPAttribute) throws ResourceException { |
| | | if (!writabilityPolicy.canCreate(ldapAttributeName)) { |
| | | if (!newLDAPAttribute.isEmpty() && !writabilityPolicy.discardWrites()) { |
| | | throw new BadRequestException(i18n("The request cannot be processed because it attempts " |
| | | + "to create the read-only field '%s'", path)); |
| | | throw newBadRequestException(ERR_CREATION_READ_ONLY_FIELD.get(path)); |
| | | } |
| | | return Collections.emptyList(); |
| | | } else if (newLDAPAttribute.isEmpty()) { |
| | | if (isRequired) { |
| | | throw new BadRequestException(i18n("The request cannot be processed because it attempts " |
| | | + "to remove the required field '%s'", path)); |
| | | throw newBadRequestException(ERR_REMOVE_REQUIRED_FIELD.get("create", path)); |
| | | } |
| | | return Collections.emptyList(); |
| | | } |
| | |
| | | * if it is configured to discard writes. |
| | | */ |
| | | if (!writabilityPolicy.canWrite(ldapAttributeName)) { |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because it attempts to modify " |
| | | + "the read-only field '%s'", path)); |
| | | throw newBadRequestException(ERR_MODIFY_READ_ONLY_FIELD.get("patch", path)); |
| | | } |
| | | |
| | | switch (field.size()) { |
| | |
| | | if (attributeIsSingleValued()) { |
| | | if (v.isList()) { |
| | | // Single-valued field violation. |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because an array of values was " |
| | | + "provided for the single valued field '%s'", path)); |
| | | throw newBadRequestException(ERR_ARRAY_FOR_SINGLE_VALUED_FIELD.get(path)); |
| | | } |
| | | } else if (!v.isList() && !operation.isIncrement() |
| | | && !(v.isNull() && (operation.isReplace() || operation.isRemove()))) { |
| | | // Multi-valued field violation. |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because an array of values was " |
| | | + "not provided for the multi-valued field '%s'", path)); |
| | | throw newBadRequestException(ERR_NO_ARRAY_FOR_MULTI_VALUED_FIELD.get(path)); |
| | | } |
| | | break; |
| | | case 1: |
| | |
| | | if (fieldName.equals("-") && operation.isAdd()) { |
| | | // Append a single value. |
| | | if (attributeIsSingleValued()) { |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because it attempts to append a " |
| | | + "value to the single valued field '%s'", path)); |
| | | throw newBadRequestException(ERR_PATCH_APPEND_IN_SINGLE_VALUED_FIELD.get(path)); |
| | | } else if (v.isList()) { |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because it attempts to " |
| | | + "perform an indexed append of an array of values to " |
| | | + "the multi-valued field '%s'", path.child(fieldName))); |
| | | throw newBadRequestException( |
| | | ERR_PATCH_INDEXED_APPEND_TO_MULTI_VALUED_FIELD.get(path.child(fieldName))); |
| | | } |
| | | } else if (fieldName.matches("[0-9]+")) { |
| | | // Array index - not allowed. |
| | | throw new NotSupportedException(i18n( |
| | | "The request cannot be processed because it included " |
| | | + "an indexed patch operation '%s' which is not supported " |
| | | + "by this resource provider", path.child(fieldName))); |
| | | throw newNotSupportedException(ERR_PATCH_INDEXED_OPERATION.get(path.child(fieldName))); |
| | | } else { |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because it included " |
| | | + "an unrecognized field '%s'", path.child(fieldName))); |
| | | throw newBadRequestException(ERR_UNRECOGNIZED_FIELD.get(path.child(fieldName))); |
| | | } |
| | | break; |
| | | default: |
| | |
| | | * The patch operation targets the child of a sub-field. This is |
| | | * not possible for a LDAP attribute mapper. |
| | | */ |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because it included " |
| | | + "an unrecognized field '%s'", path.child(field.get(0)))); |
| | | throw newBadRequestException(ERR_UNRECOGNIZED_FIELD.get(path.child(field.get(0)))); |
| | | } |
| | | |
| | | // Check that the values are compatible with the type of patch operation. |
| | |
| | | modType = |
| | | attributeIsSingleValued() ? ModificationType.REPLACE : ModificationType.ADD; |
| | | if (newValues.isEmpty()) { |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because it included " |
| | | + "an add patch operation but no value(s) for field '%s'", path |
| | | .child(field.get(0)))); |
| | | throw newBadRequestException(ERR_PATCH_ADD_NO_VALUE_FOR_FIELD.get(path.child(field.get(0)))); |
| | | } |
| | | } else if (operation.isRemove()) { |
| | | modType = ModificationType.DELETE; |
| | |
| | | } else if (operation.isIncrement()) { |
| | | modType = ModificationType.INCREMENT; |
| | | } else { |
| | | throw new NotSupportedException(i18n( |
| | | "The request cannot be processed because it included " |
| | | + "an unsupported type of patch operation '%s'", operation |
| | | .getOperation())); |
| | | throw newNotSupportedException(ERR_PATCH_UNSUPPORTED_OPERATION.get(operation.getOperation())); |
| | | } |
| | | |
| | | // Create the modification. |
| | |
| | | // Deleting the attribute. |
| | | if (isRequired) { |
| | | return Promises.<List<Modification>, ResourceException> newExceptionPromise( |
| | | new BadRequestException(i18n( |
| | | "The request cannot be processed because it attempts to remove the required field '%s'", |
| | | path))); |
| | | newBadRequestException(ERR_REMOVE_REQUIRED_FIELD.get("update", path))); |
| | | } else { |
| | | return Promises.newResultPromise( |
| | | singletonList(new Modification(modType, emptyAttribute(ldapAttributeName)))); |
| | |
| | | // No change. |
| | | return Collections.emptyList(); |
| | | } |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because it attempts to modify the read-only field '%s'", |
| | | path)); |
| | | throw newBadRequestException(ERR_MODIFY_READ_ONLY_FIELD.get("update", path)); |
| | | } |
| | | |
| | | if (oldLDAPAttribute.isEmpty() && newLDAPAttribute.isEmpty()) { |
| | |
| | | } else if (newLDAPAttribute.isEmpty()) { |
| | | // The attribute is being deleted - this is not allowed if the attribute is required. |
| | | if (isRequired) { |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because it attempts to remove the required field '%s'", |
| | | path)); |
| | | throw newBadRequestException(ERR_REMOVE_REQUIRED_FIELD.get("update", path)); |
| | | } |
| | | return singletonList(new Modification(ModificationType.REPLACE, newLDAPAttribute)); |
| | | } else { |
| | |
| | | if (attributeIsSingleValued()) { |
| | | if (v != null && v.isList()) { |
| | | // Single-valued field violation. |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because an array of values was " |
| | | + "provided for the single valued field '%s'", path)); |
| | | throw newBadRequestException(ERR_ARRAY_FOR_SINGLE_VALUED_FIELD.get(path)); |
| | | } |
| | | } else if (v != null && !v.isList()) { |
| | | // Multi-valued field violation. |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because an array of values was " |
| | | + "not provided for the multi-valued field '%s'", path)); |
| | | throw newBadRequestException(ERR_NO_ARRAY_FOR_MULTI_VALUED_FIELD.get(path)); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static org.forgerock.opendj.ldap.Filter.alwaysFalse; |
| | | import static org.forgerock.opendj.ldap.Filter.alwaysTrue; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.i18n; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.isNullOrEmpty; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.toFilter; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase; |
| | | |
| | |
| | | |
| | | import org.forgerock.json.JsonPointer; |
| | | import org.forgerock.json.JsonValue; |
| | | import org.forgerock.json.resource.BadRequestException; |
| | | import org.forgerock.json.resource.PatchOperation; |
| | | import org.forgerock.json.resource.ResourceException; |
| | | import org.forgerock.opendj.ldap.Attribute; |
| | |
| | | Promise<List<Attribute>, ResourceException> create(final Connection connection, final JsonPointer path, |
| | | final JsonValue v) { |
| | | if (!isNullOrEmpty(v) && !v.getObject().equals(value.getObject())) { |
| | | return Promises.<List<Attribute>, ResourceException> newExceptionPromise(new BadRequestException(i18n( |
| | | "The request cannot be processed because it attempts to create the read-only field '%s'", path))); |
| | | return Promises.<List<Attribute>, ResourceException> newExceptionPromise( |
| | | newBadRequestException(ERR_CREATION_READ_ONLY_FIELD.get(path))); |
| | | } else { |
| | | return Promises.newResultPromise(Collections.<Attribute> emptyList()); |
| | | } |
| | |
| | | @Override |
| | | Promise<List<Modification>, ResourceException> patch(final Connection connection, final JsonPointer path, |
| | | final PatchOperation operation) { |
| | | return Promises.<List<Modification>, ResourceException> newExceptionPromise(new BadRequestException(i18n( |
| | | "The request cannot be processed because it attempts to patch the read-only field '%s'", path))); |
| | | return Promises.<List<Modification>, ResourceException> newExceptionPromise( |
| | | newBadRequestException(ERR_PATCH_READ_ONLY_FIELD.get(path))); |
| | | } |
| | | |
| | | @Override |
| | |
| | | Promise<List<Modification>, ResourceException> update( |
| | | final Connection connection, final JsonPointer path, final Entry e, final JsonValue v) { |
| | | if (!isNullOrEmpty(v) && !v.getObject().equals(value.getObject())) { |
| | | return Promises.<List<Modification>, ResourceException> newExceptionPromise(new BadRequestException(i18n( |
| | | "The request cannot be processed because it attempts to modify the read-only field '%s'", path))); |
| | | return Promises.<List<Modification>, ResourceException> newExceptionPromise( |
| | | newBadRequestException(ERR_MODIFY_READ_ONLY_FIELD.get("update", path))); |
| | | } else { |
| | | return Promises.newResultPromise(Collections.<Modification> emptyList()); |
| | | } |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static java.util.Arrays.asList; |
| | | import static org.forgerock.opendj.ldap.Filter.alwaysFalse; |
| | | import static org.forgerock.opendj.ldap.Filter.alwaysTrue; |
| | |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest; |
| | | import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.i18n; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newNotSupportedException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.toFilter; |
| | | |
| | | import java.nio.charset.StandardCharsets; |
| | |
| | | public Promise<ActionResponse, ResourceException> actionCollection( |
| | | final Context context, final ActionRequest request) { |
| | | return Promises.<ActionResponse, ResourceException> newExceptionPromise( |
| | | new NotSupportedException("Not yet implemented")); |
| | | newNotSupportedException(ERR_NOT_YET_IMPLEMENTED.get())); |
| | | } |
| | | |
| | | @Override |
| | |
| | | return passwordModify(context, resourceId, request); |
| | | } |
| | | return Promises.<ActionResponse, ResourceException> newExceptionPromise( |
| | | new NotSupportedException("The action '" + actionId + "' is not supported")); |
| | | newNotSupportedException(ERR_ACTION_NOT_SUPPORTED.get(actionId))); |
| | | } |
| | | |
| | | private Promise<ActionResponse, ResourceException> passwordModify( |
| | |
| | | if (!context.containsContext(ClientContext.class) |
| | | || !context.asContext(ClientContext.class).isSecure()) { |
| | | return Promises.newExceptionPromise(ResourceException.newResourceException( |
| | | ResourceException.FORBIDDEN, "Password modify requires a secure connection.")); |
| | | ResourceException.FORBIDDEN, ERR_PASSWORD_MODIFY_SECURE_CONNECTION.get().toString())); |
| | | } |
| | | if (!context.containsContext(SecurityContext.class) |
| | | || context.asContext(SecurityContext.class).getAuthenticationId() == null) { |
| | | return Promises.newExceptionPromise(ResourceException.newResourceException( |
| | | ResourceException.FORBIDDEN, "Password modify requires user to be authenticated.")); |
| | | ResourceException.FORBIDDEN, ERR_PASSWORD_MODIFY_USER_AUTHENTICATED.get().toString())); |
| | | } |
| | | |
| | | final JsonValue jsonContent = request.getContent(); |
| | |
| | | oldPassword = jsonContent.get("oldPassword").asString(); |
| | | newPassword = jsonContent.get("newPassword").asString(); |
| | | } catch (JsonValueException e) { |
| | | return Promises.newExceptionPromise( |
| | | ResourceException.newResourceException(ResourceException.BAD_REQUEST, e.getLocalizedMessage(), e)); |
| | | final ResourceException ex = newBadRequestException(ERR_PASSWORD_MODIFY_REQUEST_IS_INVALID.get(), e); |
| | | return Promises.newExceptionPromise(ex); |
| | | } |
| | | |
| | | final Connection connection = context.asContext(AuthenticatedConnectionContext.class).getConnection(); |
| | |
| | | |
| | | private void ensureMVCCSupported() throws NotSupportedException { |
| | | if (etagAttribute == null) { |
| | | throw new NotSupportedException( |
| | | i18n("Multi-version concurrency control is not supported by this resource")); |
| | | throw newNotSupportedException(ERR_MVCC_NOT_SUPPORTED.get()); |
| | | } |
| | | } |
| | | |
| | |
| | | ensureMVCCSupported(); |
| | | final String actualRevision = entry.parseAttribute(etagAttribute).asString(); |
| | | if (actualRevision == null) { |
| | | throw new PreconditionFailedException(i18n( |
| | | "The resource could not be accessed because it did not contain any " |
| | | + "version information, when the version '%s' was expected", expectedRevision)); |
| | | throw new PreconditionFailedException(ERR_MVCC_NO_VERSION_INFORMATION.get(expectedRevision).toString()); |
| | | } else if (!expectedRevision.equals(actualRevision)) { |
| | | throw new PreconditionFailedException(i18n( |
| | | "The resource could not be accessed because the expected version '%s' " |
| | | + "does not match the current version '%s'", expectedRevision, actualRevision)); |
| | | throw new PreconditionFailedException( |
| | | ERR_MVCC_VERSIONS_MISMATCH.get(expectedRevision, actualRevision).toString()); |
| | | } |
| | | } |
| | | } |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static org.forgerock.json.resource.PatchOperation.operation; |
| | | import static org.forgerock.opendj.ldap.Filter.alwaysFalse; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.i18n; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.toLowerCase; |
| | | |
| | | import java.util.AbstractMap.SimpleImmutableEntry; |
| | |
| | | |
| | | import org.forgerock.json.JsonPointer; |
| | | import org.forgerock.json.JsonValue; |
| | | import org.forgerock.json.resource.BadRequestException; |
| | | import org.forgerock.json.resource.PatchOperation; |
| | | import org.forgerock.json.resource.ResourceException; |
| | | import org.forgerock.opendj.ldap.Attribute; |
| | |
| | | final String fieldName = field.get(0); |
| | | final Mapping mapping = getMapping(fieldName); |
| | | if (mapping == null) { |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because it included " |
| | | + "an unrecognized field '%s'", path.child(fieldName))); |
| | | throw newBadRequestException(ERR_UNRECOGNIZED_FIELD.get(path.child(fieldName))); |
| | | } |
| | | final PatchOperation subOperation = |
| | | operation(operation.getOperation(), field.relativePointer(), v); |
| | |
| | | if (v.isMap()) { |
| | | for (final String attribute : v.asMap().keySet()) { |
| | | if (missingMappings.remove(toLowerCase(attribute)) == null) { |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because it included " |
| | | + "an unrecognized field '%s'", path.child(attribute))); |
| | | throw newBadRequestException(ERR_UNRECOGNIZED_FIELD.get(path.child(attribute))); |
| | | } |
| | | } |
| | | } else { |
| | | throw new BadRequestException(i18n( |
| | | "The request cannot be processed because it included " |
| | | + "the field '%s' whose value is the wrong type: " |
| | | + "an object is expected", path)); |
| | | throw newBadRequestException(ERR_FIELD_WRONG_TYPE.get(path)); |
| | | } |
| | | } |
| | | return missingMappings; |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static org.forgerock.opendj.ldap.LdapException.newLdapException; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSearchRequest; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.asResourceException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.i18n; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.LinkedHashSet; |
| | |
| | | |
| | | import org.forgerock.json.JsonPointer; |
| | | import org.forgerock.json.JsonValue; |
| | | import org.forgerock.json.resource.BadRequestException; |
| | | import org.forgerock.json.resource.ResourceException; |
| | | import org.forgerock.opendj.ldap.Attribute; |
| | | import org.forgerock.opendj.ldap.AttributeDescription; |
| | |
| | | } |
| | | |
| | | if (primaryKeyAttribute == null || primaryKeyAttribute.isEmpty()) { |
| | | promise.handleException(new BadRequestException( |
| | | i18n("The request cannot be processed because the reference field '%s' contains " |
| | | + "a value which does not contain a primary key", path))); |
| | | promise.handleException(newBadRequestException(ERR_REFERENCE_FIELD_NO_PRIMARY_KEY.get(path))); |
| | | } |
| | | |
| | | if (primaryKeyAttribute.size() > 1) { |
| | | promise.handleException(new BadRequestException( |
| | | i18n("The request cannot be processed because the reference field '%s' contains " |
| | | + "a value which contains multiple primary keys", path))); |
| | | promise.handleException( |
| | | newBadRequestException(ERR_REFERENCE_FIELD_MULTIPLE_PRIMARY_KEYS.get(path))); |
| | | } |
| | | |
| | | // Now search for the referenced entry in to get its DN. |
| | |
| | | try { |
| | | throw error; |
| | | } catch (final EntryNotFoundException e) { |
| | | re = new BadRequestException(i18n( |
| | | "The request cannot be processed because the resource " |
| | | + "'%s' referenced in field '%s' does not exist", |
| | | re = newBadRequestException(ERR_REFERENCE_FIELD_DOES_NOT_EXIST.get( |
| | | primaryKeyValue.toString(), path)); |
| | | } catch (final MultipleEntriesFoundException e) { |
| | | re = new BadRequestException(i18n( |
| | | "The request cannot be processed because the resource " |
| | | + "'%s' referenced in field '%s' is ambiguous", |
| | | primaryKeyValue.toString(), path)); |
| | | re = newBadRequestException( |
| | | ERR_REFERENCE_FIELD_AMBIGUOUS.get(primaryKeyValue.toString(), path)); |
| | | } catch (final LdapException e) { |
| | | re = asResourceException(e); |
| | | } |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static java.util.Arrays.asList; |
| | | import static org.forgerock.json.resource.ResourceException.newResourceException; |
| | | import static org.forgerock.opendj.ldap.Connections.newCachedConnectionPool; |
| | |
| | | import static org.forgerock.opendj.ldap.schema.CoreSchema.getEntryUUIDAttributeType; |
| | | import static org.forgerock.opendj.rest2ldap.ReadOnUpdatePolicy.CONTROLS; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.ensureNotNull; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newLocalizedIllegalArgumentException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newJsonValueException; |
| | | import static org.forgerock.util.time.Duration.*; |
| | | |
| | | import java.io.IOException; |
| | |
| | | import java.util.concurrent.TimeUnit; |
| | | |
| | | import org.forgerock.json.JsonValue; |
| | | import org.forgerock.json.JsonValueException; |
| | | import org.forgerock.json.resource.BadRequestException; |
| | | import org.forgerock.json.resource.CollectionResourceProvider; |
| | | import org.forgerock.json.resource.ResourceException; |
| | | import org.forgerock.opendj.ldap.AssertionFailureException; |
| | |
| | | public CollectionResourceProvider build() { |
| | | ensureNotNull(baseDN); |
| | | if (rootMapper == null) { |
| | | throw new IllegalStateException("No mappings provided"); |
| | | throw new IllegalStateException(ERR_CONFIG_NO_MAPPINGS_PROVIDED.get().toString()); |
| | | } |
| | | return new LDAPCollectionResourceProvider(baseDN, rootMapper, nameStrategy, etagAttribute, |
| | | new Config(readOnUpdatePolicy, useSubtreeDelete, usePermissiveModify, schema), |
| | |
| | | useServerNaming(namingStrategy.get("dnAttribute").required().asString(), |
| | | namingStrategy.get("idAttribute").required().asString()); |
| | | } else { |
| | | throw new IllegalArgumentException( |
| | | "Illegal naming strategy. Must be one of: clientDNNaming, clientNaming, or serverNaming"); |
| | | throw newLocalizedIllegalArgumentException(ERR_CONFIG_UNKNOWN_NAMING_CONFIGURATION.get( |
| | | namingStrategy.asString(), "clientDNNaming, clientNaming or serverNaming")); |
| | | } |
| | | } |
| | | |
| | |
| | | } else if (mapper.isDefined("object")) { |
| | | return configureObjectMapper(mapper.get("object")); |
| | | } else { |
| | | throw new JsonValueException(mapper, |
| | | "Illegal mapping: must contain constant, simple, or object"); |
| | | throw newJsonValueException(mapper, ERR_CONFIG_NO_MAPPING_IN_CONFIGURATION.get( |
| | | "constant, simple, reference or object")); |
| | | } |
| | | } |
| | | |
| | |
| | | } else if (writability.equalsIgnoreCase("readWrite")) { |
| | | return WritabilityPolicy.READ_WRITE; |
| | | } else { |
| | | throw new JsonValueException(mapper, |
| | | "Illegal writability: must be one of readOnly, readOnlyDiscardWrites, " |
| | | + "createOnly, createOnlyDiscardWrites, or readWrite"); |
| | | throw newJsonValueException(mapper, ERR_CONFIG_UNKNOWN_WRITABILITY.get(writability, |
| | | "readOnly, readOnlyDiscardWrites, createOnly, createOnlyDiscardWrites, or readWrite")); |
| | | } |
| | | } else { |
| | | return WritabilityPolicy.READ_WRITE; |
| | |
| | | final AttributeDescription idAttribute, final boolean isServerProvided) { |
| | | this.dnAttribute = AttributeDescription.create(dnAttribute); |
| | | if (this.dnAttribute.equals(idAttribute)) { |
| | | throw new IllegalArgumentException("DN and ID attributes must be different"); |
| | | throw newLocalizedIllegalArgumentException(ERR_CONFIG_NAMING_STRATEGY_DN_AND_ID_NOT_DIFFERENT.get()); |
| | | } |
| | | this.idAttribute = ensureNotNull(idAttribute); |
| | | this.isServerProvided = isServerProvided; |
| | |
| | | final Entry entry) throws ResourceException { |
| | | if (isServerProvided) { |
| | | if (resourceId != null) { |
| | | throw new BadRequestException("Resources cannot be created with a " |
| | | + "client provided resource ID"); |
| | | throw newBadRequestException(ERR_CLIENT_PROVIDER_RESOURCE_ID_MISSING.get()); |
| | | } |
| | | } else { |
| | | entry.addAttribute(new LinkedAttribute(idAttribute, ByteString.valueOfUtf8(resourceId))); |
| | |
| | | } else if (entry.getAttribute(attribute) != null) { |
| | | entry.setName(baseDN.child(rdn(entry.parseAttribute(attribute).asString()))); |
| | | } else { |
| | | throw new BadRequestException("Resources cannot be created without a " |
| | | + "client provided resource ID"); |
| | | throw newBadRequestException(ERR_CLIENT_PROVIDER_RESOURCE_ID_MISSING.get()); |
| | | } |
| | | } |
| | | |
| | |
| | | simple.get("bindPassword").required().asString().toCharArray()); |
| | | options.set(AUTHN_BIND_REQUEST, bindRequest); |
| | | } else { |
| | | throw new IllegalArgumentException("Only simple authentication is supported"); |
| | | throw newLocalizedIllegalArgumentException(ERR_CONFIG_INVALID_AUTHENTICATION.get()); |
| | | } |
| | | } |
| | | |
| | |
| | | secondary = parseLDAPServers(secondaryLDAPServers, connectionPoolSize, options); |
| | | } |
| | | } else if (!secondaryLDAPServers.isNull()) { |
| | | throw new IllegalArgumentException("Invalid secondaryLDAPServers configuration"); |
| | | throw newLocalizedIllegalArgumentException(ERR_CONFIG_INVALID_SECONDARY_LDAP_SERVER.get()); |
| | | } |
| | | |
| | | // Create fail-over. |
| | |
| | | final String name, final int depth) { |
| | | // Protect against infinite recursion in the configuration. |
| | | if (depth > 100) { |
| | | throw new IllegalArgumentException( |
| | | "The LDAP server configuration '" |
| | | + name |
| | | + "' could not be parsed because of potential circular inheritance dependencies"); |
| | | throw newLocalizedIllegalArgumentException(ERR_CONFIG_SERVER_CIRCULAR_DEPENDENCIES.get(name)); |
| | | } |
| | | |
| | | final JsonValue current = configuration.get(name).required(); |
| | |
| | | |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static org.forgerock.http.util.Json.readJsonLenient; |
| | | import static org.forgerock.json.JsonValueFunctions.duration; |
| | | import static org.forgerock.json.JsonValueFunctions.enumConstant; |
| | | import static org.forgerock.json.JsonValueFunctions.setOf; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2LDAP.configureConnectionFactory; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newLocalizedIllegalArgumentException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newJsonValueException; |
| | | import static org.forgerock.opendj.rest2ldap.authz.AuthenticationStrategies.*; |
| | | import static org.forgerock.opendj.rest2ldap.authz.Authorizations.*; |
| | | import static org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.*; |
| | | import static org.forgerock.opendj.rest2ldap.authz.CredentialExtractors.*; |
| | | import static org.forgerock.util.Reject.checkNotNull; |
| | | import static org.forgerock.util.Utils.closeSilently; |
| | | import static org.forgerock.util.Utils.joinAsString; |
| | | |
| | | import java.io.IOException; |
| | | import java.io.InputStream; |
| | |
| | | import org.forgerock.http.io.Buffer; |
| | | import org.forgerock.http.protocol.Headers; |
| | | import org.forgerock.json.JsonValue; |
| | | import org.forgerock.json.JsonValueException; |
| | | import org.forgerock.json.resource.RequestHandler; |
| | | import org.forgerock.json.resource.Router; |
| | | import org.forgerock.json.resource.http.CrestHttp; |
| | |
| | | |
| | | /** Define the method which should be used to resolve an OAuth2 access token. */ |
| | | private enum OAuth2ResolverType { |
| | | RFC7662, |
| | | OPENAM, |
| | | CTS, |
| | | FILE |
| | | RFC7662, OPENAM, CTS, FILE; |
| | | |
| | | private static String listValues() { |
| | | final List<String> values = new ArrayList<>(); |
| | | for (final OAuth2ResolverType value : OAuth2ResolverType.values()) { |
| | | values.add(value.name().toLowerCase()); |
| | | } |
| | | return joinAsString(",", values); |
| | | } |
| | | } |
| | | |
| | | @VisibleForTesting |
| | | enum Policy { OAUTH2, BASIC, ANONYMOUS } |
| | | |
| | | private enum BindStrategy { SIMPLE, SEARCH, SASL_PLAIN } |
| | | private enum BindStrategy { |
| | | SIMPLE("simple"), |
| | | SEARCH("search"), |
| | | SASL_PLAIN("sasl-plain"); |
| | | |
| | | private final String jsonField; |
| | | |
| | | BindStrategy(final String jsonField) { |
| | | this.jsonField = jsonField; |
| | | } |
| | | |
| | | private static String listValues() { |
| | | final List<String> values = new ArrayList<>(); |
| | | for (final BindStrategy mapping : BindStrategy.values()) { |
| | | values.add(mapping.jsonField); |
| | | } |
| | | return joinAsString(",", values); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Default constructor called by the HTTP Framework which will use the default configuration file location. |
| | |
| | | CrestHttp.newHttpHandler(configureRest2Ldap(configuration)), |
| | | buildAuthorizationFilter(configuration.get("authorization").required())); |
| | | } catch (final Exception e) { |
| | | // TODO i18n, once supported in opendj-rest2ldap |
| | | final String errorMsg = "Unable to start Rest2Ldap Http Application"; |
| | | final String errorMsg = ERR_FAIL_PARSE_CONFIGURATION.get(e.getLocalizedMessage()).toString(); |
| | | LOG.error(errorMsg, e); |
| | | stop(); |
| | | throw new HttpApplicationException(errorMsg, e); |
| | |
| | | case FILE: |
| | | return newFileAccessTokenResolver(configuration.get("file").get("folderPath").required().asString()); |
| | | default: |
| | | throw new JsonValueException(resolver, "is not a supported access token resolver"); |
| | | throw newJsonValueException(resolver, |
| | | ERR_CONFIG_OAUTH2_UNSUPPORTED_ACCESS_TOKEN_RESOLVER.get( |
| | | resolver.getObject(), OAuth2ResolverType.listValues())); |
| | | } |
| | | } |
| | | |
| | |
| | | rfc7662.get("clientId").required().asString(), |
| | | rfc7662.get("clientSecret").required().asString()); |
| | | } catch (final URISyntaxException e) { |
| | | throw new IllegalArgumentException("The token introspection endpoint '" |
| | | + introspectionEndPointURL + "' URL has an invalid syntax: " + e.getLocalizedMessage(), e); |
| | | throw new IllegalArgumentException(ERR_CONIFG_OAUTH2_INVALID_INTROSPECT_URL.get( |
| | | introspectionEndPointURL, e.getLocalizedMessage()).toString(), e); |
| | | } |
| | | } |
| | | |
| | |
| | | try { |
| | | final Duration expiration = expirationJson.as(duration()); |
| | | if (expiration.isZero() || expiration.isUnlimited()) { |
| | | throw new JsonValueException(expirationJson, "The cache expiration duration cannot be " |
| | | + (expiration.isZero() ? "zero" : "unlimited.")); |
| | | throw newJsonValueException(expirationJson, |
| | | expiration.isZero() ? ERR_CONIFG_OAUTH2_CACHE_ZERO_DURATION.get() |
| | | : ERR_CONIFG_OAUTH2_CACHE_UNLIMITED_DURATION.get()); |
| | | } |
| | | return expiration; |
| | | } catch (final Exception e) { |
| | | throw new JsonValueException(expirationJson, |
| | | "Malformed duration value '" + expirationJson.toString() + "' for cache expiration. " |
| | | + "The duration syntax supports all human readable notations from day ('days'', 'day'', 'd'') " |
| | | + "to nanosecond ('nanoseconds', 'nanosecond', 'nanosec', 'nanos', 'nano', 'ns')"); |
| | | throw newJsonValueException(expirationJson, |
| | | ERR_CONFIG_OAUTH2_CACHE_INVALID_DURATION.get(expirationJson.toString())); |
| | | } |
| | | } |
| | | |
| | |
| | | case SASL_PLAIN: |
| | | return buildSASLBindStrategy(config); |
| | | default: |
| | | throw new IllegalArgumentException("Unsupported strategy '" + strategy + "'"); |
| | | throw newLocalizedIllegalArgumentException(ERR_CONFIG_UNSUPPORTED_BIND_STRATEGY.get( |
| | | strategy, BindStrategy.listValues())); |
| | | } |
| | | } |
| | | |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.json.JsonPointer; |
| | | import org.forgerock.json.JsonValue; |
| | | import org.forgerock.json.resource.BadRequestException; |
| | | import org.forgerock.json.resource.ResourceException; |
| | | import org.forgerock.opendj.ldap.Attribute; |
| | | import org.forgerock.opendj.ldap.AttributeDescription; |
| | |
| | | return newResultPromise(toFilter(type, ldapAttributeName.toString(), va)); |
| | | } catch (final Exception e) { |
| | | // Invalid assertion value - bad request. |
| | | return newExceptionPromise((ResourceException) new BadRequestException(i18n( |
| | | "The request cannot be processed because it contained an " |
| | | + "illegal filter assertion value '%s' for field '%s'", |
| | | String.valueOf(valueAssertion), path), e)); |
| | | return newExceptionPromise((ResourceException) newBadRequestException( |
| | | ERR_ILLEGAL_FILTER_ASSERTION_VALUE.get(String.valueOf(valueAssertion), path), e)); |
| | | } |
| | | } else { |
| | | // This attribute mapper does not support partial filtering. |
| | |
| | | try { |
| | | return newResultPromise(jsonToAttribute(newValues, ldapAttributeName, encoder())); |
| | | } catch (final Exception ex) { |
| | | return newExceptionPromise((ResourceException) new BadRequestException(i18n( |
| | | "The request cannot be processed because an error occurred while " |
| | | + "encoding the values for the field '%s': %s", path, ex.getMessage()))); |
| | | return newExceptionPromise((ResourceException) newBadRequestException( |
| | | ERR_ENCODING_VALUES_FOR_FIELD.get(path, ex.getMessage()))); |
| | | } |
| | | } |
| | | |
| | |
| | | * Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | * information: "Portions Copyright [year] [name of copyright owner]". |
| | | * |
| | | * Copyright 2012-2015 ForgeRock AS. |
| | | * Copyright 2012-2016 ForgeRock AS. |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static javax.xml.bind.DatatypeConverter.parseDateTime; |
| | | import static javax.xml.bind.DatatypeConverter.printDateTime; |
| | | import static org.forgerock.opendj.ldap.Filter.alwaysFalse; |
| | |
| | | import java.util.List; |
| | | import java.util.Locale; |
| | | |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.i18n.LocalizedIllegalArgumentException; |
| | | import org.forgerock.json.JsonValue; |
| | | import org.forgerock.json.JsonValueException; |
| | | import org.forgerock.json.resource.BadRequestException; |
| | | import org.forgerock.json.resource.NotSupportedException; |
| | | import org.forgerock.opendj.ldap.Attribute; |
| | | import org.forgerock.opendj.ldap.AttributeDescription; |
| | | import org.forgerock.opendj.ldap.ByteString; |
| | |
| | | } |
| | | return a; |
| | | } else { |
| | | throw new IllegalArgumentException("Unrecognized type of JSON value: " |
| | | + value.getClass().getName()); |
| | | throw newLocalizedIllegalArgumentException(ERR_UNRECOGNIZED_JSON_VALUE.get(value.getClass().getName())); |
| | | } |
| | | } |
| | | |
| | |
| | | return ByteString.valueOfObject(value); |
| | | } |
| | | } else { |
| | | throw new IllegalArgumentException("Unrecognized type of JSON value: " |
| | | + value.getClass().getName()); |
| | | throw newLocalizedIllegalArgumentException( |
| | | ERR_UNRECOGNIZED_JSON_VALUE.get(value.getClass().getName())); |
| | | } |
| | | } |
| | | }; |
| | |
| | | return s != null ? s.toLowerCase(Locale.ENGLISH) : null; |
| | | } |
| | | |
| | | static NotSupportedException newNotSupportedException(final LocalizableMessage message) { |
| | | return new NotSupportedException(message.toString()); |
| | | } |
| | | |
| | | static JsonValueException newJsonValueException(final JsonValue value, final LocalizableMessage message) { |
| | | return new JsonValueException(value, message.toString()); |
| | | } |
| | | |
| | | static LocalizedIllegalArgumentException newLocalizedIllegalArgumentException(final LocalizableMessage message) { |
| | | return new LocalizedIllegalArgumentException(message); |
| | | } |
| | | |
| | | static BadRequestException newBadRequestException(final LocalizableMessage message) { |
| | | return newBadRequestException(message, null); |
| | | } |
| | | |
| | | static BadRequestException newBadRequestException(final LocalizableMessage message, final Throwable cause) { |
| | | return new BadRequestException(message.toString(), cause); |
| | | } |
| | | |
| | | private static <T> List<T> asList(final Collection<T> c) { |
| | | if (c instanceof List) { |
| | | return (List<T>) c; |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap.authz; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.asConditionalFilter; |
| | | import static org.forgerock.opendj.rest2ldap.authz.ConditionalFilters.newConditionalFilter; |
| | | import static org.forgerock.util.promise.Promises.newResultPromise; |
| | |
| | | try { |
| | | authz.put(template.getSecurityContextID(), template.formatAsAuthzId(token.asJsonValue())); |
| | | } catch (final IllegalArgumentException e) { |
| | | return newResultPromise( |
| | | new Response().setStatus(Status.FORBIDDEN).setCause(e).setEntity("Invalid configuration")); |
| | | return newResultPromise(new Response().setStatus(Status.FORBIDDEN) |
| | | .setCause(e) |
| | | .setEntity(ERR_AUTHZID_DECODER_RESPONSE.get().toString())); |
| | | } |
| | | final Context securityContext = new SecurityContext(context, token.getToken(), authz); |
| | | return next.handle(securityContext, request); |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap.authz; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static org.forgerock.opendj.rest2ldap.authz.Utils.newIllegalArgumentException; |
| | | import static org.forgerock.util.Utils.joinAsString; |
| | | |
| | | import java.util.ArrayList; |
| | |
| | | return type; |
| | | } |
| | | } |
| | | throw new IllegalArgumentException("Invalid authorization ID template: '" + template + "'. Templates must " |
| | | + "start with one of the following elements: " + joinAsString(",", getSupportedStartKeys())); |
| | | throw newIllegalArgumentException( |
| | | ERR_CONFIG_INVALID_AUTHZID_TEMPLATE.get(template, joinAsString(",", getSupportedStartKeys()))); |
| | | } |
| | | |
| | | private static List<String> getSupportedStartKeys() { |
| | |
| | | final String key = keys.get(i); |
| | | final JsonValue value = principals.get(new JsonPointer(key)); |
| | | if (value == null) { |
| | | throw new IllegalArgumentException(String.format( |
| | | "The request could not be authorized because the required " |
| | | + "security principal '%s' could not be determined", key)); |
| | | throw newIllegalArgumentException(ERR_AUTHZID_DECODER_PRINCIPAL_CANNOT_BE_DETERMINED.get(key)); |
| | | } |
| | | |
| | | final Object object = value.getObject(); |
| | | if (!isJSONPrimitive(object)) { |
| | | throw new IllegalArgumentException(String.format( |
| | | "The request could not be authorized because the required " |
| | | + "security principal '%s' had an invalid data type", key)); |
| | | throw newIllegalArgumentException(ERR_AUTHZID_DECODER_PRINCIPAL_INVALID_DATA_TYPE.get(key)); |
| | | } |
| | | values[i] = String.valueOf(object); |
| | | } |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap.authz; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static org.forgerock.opendj.ldap.requests.Requests.newSingleEntrySearchRequest; |
| | | import static org.forgerock.opendj.rest2ldap.authz.Utils.close; |
| | | import static org.forgerock.opendj.rest2ldap.authz.Utils.newAccessTokenException; |
| | | import static org.forgerock.util.Reject.checkNotNull; |
| | | |
| | | import java.io.IOException; |
| | |
| | | |
| | | final String tokenName = getRequiredFirstValue(accessToken.get("tokenName")); |
| | | if (!tokenName.equals("access_token")) { |
| | | throw new AccessTokenException( |
| | | "The token '" + token + "' must be an access token, but it is a \"" + tokenName + "\""); |
| | | throw newAccessTokenException(ERR_OAUTH2_CTS_INVALID_TOKEN_TYPE.get(token, tokenName)); |
| | | } |
| | | |
| | | return new AccessTokenInfo(accessToken, token, |
| | |
| | | }, new Function<LdapException, AccessTokenInfo, AccessTokenException>() { |
| | | @Override |
| | | public AccessTokenInfo apply(final LdapException e) throws AccessTokenException { |
| | | throw new AccessTokenException("Unable to find the token '" + token + "' in the CTS because: " |
| | | + e.getMessage(), e); |
| | | throw newAccessTokenException(ERR_OAUTH2_CTS_TOKEN_NOT_FOUND.get(token, e.getMessage()), e); |
| | | } |
| | | }).thenCatchRuntimeException(new Function<RuntimeException, AccessTokenInfo, AccessTokenException>() { |
| | | @Override |
| | | public AccessTokenInfo apply(final RuntimeException e) throws AccessTokenException { |
| | | throw new AccessTokenException("Unable to resolve access token '" + token |
| | | + "' due to the following reason: " + e.getMessage(), e); |
| | | throw newAccessTokenException(ERR_OAUTH2_CTS_TOKEN_RESOLUTION.get(token, e.getMessage()), e); |
| | | } |
| | | }).thenFinally(close(connectionHolder)); |
| | | } |
| | |
| | | try { |
| | | return new JsonValue(Json.readJson(accessTokenJson)); |
| | | } catch (final IOException e) { |
| | | throw new AccessTokenException("Json of token '" + token + "' is malformed"); |
| | | throw newAccessTokenException(ERR_OAUTH2_CTS_INVALID_JSON_TOKEN.get(token)); |
| | | } |
| | | } |
| | | } |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap.authz; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static org.forgerock.opendj.rest2ldap.authz.Utils.newAccessTokenException; |
| | | import static org.forgerock.util.Reject.checkNotNull; |
| | | import static org.forgerock.util.promise.Promises.newExceptionPromise; |
| | | import static org.forgerock.util.promise.Promises.newResultPromise; |
| | |
| | | try (final InputStream stream = new FileInputStream(new File(folderPath, token))) { |
| | | accessToken = new JsonValue(Json.readJsonLenient(stream)); |
| | | } catch (final IOException e) { |
| | | return newExceptionPromise(new AccessTokenException("Unable to find token file '" + token + "'", e)); |
| | | return newExceptionPromise(newAccessTokenException(ERR_OAUTH2_FILE_NO_TOKEN.get(token) , e)); |
| | | } |
| | | |
| | | try { |
| | |
| | | return newResultPromise(result); |
| | | } catch (final JsonValueException e) { |
| | | return newExceptionPromise( |
| | | new AccessTokenException("Malformed token file '" + token + "': '" + e.getMessage() + "'.", e)); |
| | | newAccessTokenException(ERR_OAUTH2_FILE_INVALID_JSON_TOKEN.get(token, e.getMessage()), e)); |
| | | } |
| | | } |
| | | } |
| | |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap.authz; |
| | | |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.*; |
| | | import static java.util.concurrent.TimeUnit.SECONDS; |
| | | import static org.forgerock.opendj.rest2ldap.authz.Utils.newAccessTokenException; |
| | | import static org.forgerock.util.Reject.checkNotNull; |
| | | |
| | | import java.io.IOException; |
| | |
| | | public AccessTokenInfo apply(final Response response) throws AccessTokenException { |
| | | final Status status = response.getStatus(); |
| | | if (!Status.OK.equals(status)) { |
| | | throw new AccessTokenException( |
| | | "Authorization server returned an error: " + status, response.getCause()); |
| | | throw newAccessTokenException( |
| | | ERR_OAUTH2_RFC7662_RETURNED_ERROR.get(status), response.getCause()); |
| | | } |
| | | |
| | | try (final Entity entity = response.getEntity()) { |
| | | final JsonValue jsonResponse = asJson(entity); |
| | | if (!jsonResponse.get(RFC_7662_RESPONSE_ACTIVE_FIELD).defaultTo(Boolean.FALSE).asBoolean()) { |
| | | throw new AccessTokenException( |
| | | "Access token returned by authorization server is not currently active"); |
| | | throw newAccessTokenException(ERR_OAUTH2_RFC7662_TOKEN_NOT_ACTIVE.get()); |
| | | } |
| | | return buildAccessTokenFromJson(jsonResponse, tokenSent); |
| | | } catch (final JsonValueException e) { |
| | | throw new AccessTokenException("Invalid or malformed access token: " + e.getMessage(), e); |
| | | throw newAccessTokenException(ERR_OAUTH2_RFC7662_INVALID_JSON_TOKEN.get(e.getMessage()), e); |
| | | } |
| | | } |
| | | }; |
| | |
| | | return new JsonValue(entity.getJson()); |
| | | } catch (final IOException e) { |
| | | // Do not use Entity.toString(), we probably don't want to fully output the content here |
| | | throw new AccessTokenException("Cannot read response content as JSON", e); |
| | | throw newAccessTokenException(ERR_OAUTH2_RFC7662_CANNOT_READ_RESPONSE.get(), e); |
| | | } |
| | | } |
| | | } |
| | |
| | | import java.io.Closeable; |
| | | import java.util.concurrent.atomic.AtomicReference; |
| | | |
| | | import org.forgerock.authz.modules.oauth2.AccessTokenException; |
| | | import org.forgerock.http.protocol.Response; |
| | | import org.forgerock.http.protocol.Status; |
| | | import org.forgerock.i18n.LocalizableMessage; |
| | | import org.forgerock.json.resource.ResourceException; |
| | | import org.forgerock.util.promise.NeverThrowsException; |
| | | import org.forgerock.util.promise.Promise; |
| | |
| | | |
| | | private Utils() { } |
| | | |
| | | static IllegalArgumentException newIllegalArgumentException(final LocalizableMessage message) { |
| | | return new IllegalArgumentException(message.toString()); |
| | | } |
| | | |
| | | static AccessTokenException newAccessTokenException(final LocalizableMessage message) { |
| | | return newAccessTokenException(message, null); |
| | | } |
| | | |
| | | static AccessTokenException newAccessTokenException(final LocalizableMessage message, final Exception cause) { |
| | | return new AccessTokenException(message.toString(), cause); |
| | | } |
| | | |
| | | static Runnable close(final AtomicReference<? extends Closeable> holder) { |
| | | return new Runnable() { |
| | | @Override |
| New file |
| | |
| | | # |
| | | # The contents of this file are subject to the terms of the Common Development and |
| | | # Distribution License (the License). You may not use this file except in compliance with the |
| | | # License. |
| | | # |
| | | # You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | # specific language governing permission and limitations under the License. |
| | | # |
| | | # When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | # the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | # Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | # information: "Portions Copyright [year] [name of copyright owner]". |
| | | # |
| | | # Copyright 2016 ForgeRock AS. |
| | | # |
| | | |
| | | # Configuration errors |
| | | ERR_FAIL_PARSE_CONFIGURATION_1=Unable to start Rest2Ldap Http Application due to the configuration error: '%s' |
| | | ERR_CONFIG_OAUTH2_UNSUPPORTED_ACCESS_TOKEN_RESOLVER_2= '%s'is not a supported access token resolver. Must be one of '%s' |
| | | ERR_CONIFG_OAUTH2_INVALID_INTROSPECT_URL_3=The token introspection endpoint '%s' is not a valid URL: '%s' |
| | | ERR_CONIFG_OAUTH2_CACHE_ZERO_DURATION_4=The cache expiration duration cannot be zero |
| | | ERR_CONIFG_OAUTH2_CACHE_UNLIMITED_DURATION_5=The cache expiration duration cannot be unlimited |
| | | ERR_CONFIG_OAUTH2_CACHE_INVALID_DURATION_6=Malformed duration value '%s' for cache expiration. \ |
| | | The duration syntax supports all human readable notations from day ('days'', 'day'', 'd'') to nanosecond \ |
| | | ('nanoseconds', 'nanosecond', 'nanosec', 'nanos', 'nano', 'ns') |
| | | ERR_CONFIG_UNSUPPORTED_BIND_STRATEGY_7=Unsupported bind strategy '%s'. Must be one of '%s' |
| | | ERR_CONFIG_INVALID_AUTHZID_TEMPLATE_8=Invalid authorization ID template: '%s'.\ |
| | | Templates must start with one of the following elements: '%s' |
| | | ERR_CONFIG_INVALID_AUTHENTICATION_9=Only simple authentication is supported |
| | | ERR_CONFIG_INVALID_SECONDARY_LDAP_SERVER_10=secondaryLDAPServers configuration must contain a json array |
| | | ERR_CONFIG_SERVER_CIRCULAR_DEPENDENCIES_11=The LDAP server configuration '%s' could not be parsed because of \ |
| | | potential circular inheritance dependencies |
| | | ERR_CONFIG_NAMING_STRATEGY_DN_AND_ID_NOT_DIFFERENT_12=Naming strategy DN and ID must be different |
| | | ERR_CONFIG_NO_MAPPINGS_PROVIDED_13=No mappings provided in configuration |
| | | ERR_CONFIG_NO_MAPPING_IN_CONFIGURATION_14=Unknown mapping. The configuration mapping must be one of '%s' |
| | | ERR_CONFIG_UNKNOWN_WRITABILITY_15=Unknown writability '%s'. Must be one of %s |
| | | ERR_CONFIG_UNKNOWN_NAMING_CONFIGURATION_16=Unknown naming strategy '%s'. Must be one of %s |
| | | |
| | | # Runtime errors |
| | | ERR_AUTHZID_DECODER_PRINCIPAL_CANNOT_BE_DETERMINED_17=The request could not be authorized because the required \ |
| | | security principal '%s' could not be determined |
| | | ERR_AUTHZID_DECODER_PRINCIPAL_INVALID_DATA_TYPE_18=The request could not be authorized because the required \ |
| | | security principal '%s' had an invalid data type |
| | | ERR_AUTHZID_DECODER_RESPONSE_19=Invalid configuration |
| | | ERR_RESOLVING_AUTHZID_TEMPLATE_20=Unable to resolve oauthzid template placeholders for access token '%s': '%s' |
| | | # Runtime errors > OAuth2 > CTS Resolver |
| | | ERR_OAUTH2_CTS_INVALID_TOKEN_TYPE_21=The token '%s' must be an access token, but it is a '%s' |
| | | ERR_OAUTH2_CTS_TOKEN_NOT_FOUND_22=Unable to find the token '%s' in the CTS because: '%s' |
| | | ERR_OAUTH2_CTS_TOKEN_RESOLUTION_23=Unable to resolve access token '%s' due to the following reason: '%s' |
| | | ERR_OAUTH2_CTS_INVALID_JSON_TOKEN_24=Json of token '%s' is malformed |
| | | # Runtime errors > OAuth2 > File Resolver |
| | | ERR_OAUTH2_FILE_NO_TOKEN_25=Unable to find token file '%s' |
| | | ERR_OAUTH2_FILE_INVALID_JSON_TOKEN_26=Malformed token file '%s'. Details: '%s' |
| | | # Runtime errors > OAuth2 > RFC-7662 Resolver |
| | | ERR_OAUTH2_RFC7662_RETURNED_ERROR_27=Authorization server returned an error: '%s' |
| | | ERR_OAUTH2_RFC7662_TOKEN_NOT_ACTIVE_28=Access token returned by authorization server is not currently active |
| | | ERR_OAUTH2_RFC7662_INVALID_JSON_TOKEN_29=Invalid or malformed access token: '%s' |
| | | ERR_OAUTH2_RFC7662_CANNOT_READ_RESPONSE_30=Cannot read response content as JSON |
| | | |
| | | # Runtime rest to ldap processing |
| | | ERR_CREATION_READ_ONLY_FIELD_31=The create request cannot be processed because it attempts to create the \ |
| | | read-only field '%s' |
| | | ERR_REMOVE_REQUIRED_FIELD_32=The %s request cannot be processed because it attempts to remove the required field '%s' |
| | | ERR_MODIFY_READ_ONLY_FIELD_33=The %s request cannot be processed because it attempts to modify the read-only field '%s' |
| | | ERR_PATCH_READ_ONLY_FIELD_34=The patch request cannot be processed because it attempts to patch the read-only field '%s' |
| | | ERR_ARRAY_FOR_SINGLE_VALUED_FIELD_35=The request cannot be processed because an array of values was \ |
| | | provided for the single valued field '%s' |
| | | ERR_NO_ARRAY_FOR_MULTI_VALUED_FIELD_36=The request cannot be processed because an array of values was \ |
| | | not provided for the multi-valued field '%s' |
| | | ERR_PATCH_APPEND_IN_SINGLE_VALUED_FIELD_37=The patch request cannot be processed because it attempts to append a \ |
| | | value to the single valued field '%s' |
| | | ERR_PATCH_INDEXED_APPEND_TO_MULTI_VALUED_FIELD_38=The patch request cannot be processed because it attempts to perform \ |
| | | an indexed append of an array of values to the multi-valued field '%s' |
| | | ERR_PATCH_INDEXED_OPERATION_39=The patch request cannot be processed because it included an indexed patch \ |
| | | operation '%s' which is not supported by this resource provider |
| | | ERR_UNRECOGNIZED_FIELD_40=The request cannot be processed because it included an unrecognized field '%s' |
| | | ERR_FIELD_WRONG_TYPE_41=The request cannot be processed because it included the field '%s' whose value \ |
| | | is the wrong type: an object is expected |
| | | ERR_PATCH_ADD_NO_VALUE_FOR_FIELD_42=The patch request cannot be processed because it included an add patch operation \ |
| | | but no value(s) for field '%s' |
| | | ERR_PATCH_UNSUPPORTED_OPERATION_43=The patch request cannot be processed because it included an unsupported \ |
| | | type of patch operation '%s' |
| | | ERR_MVCC_NOT_SUPPORTED_44=Multi-version concurrency control is not supported by this resource |
| | | ERR_MVCC_NO_VERSION_INFORMATION_45=The resource could not be accessed because it did not contain any version \ |
| | | information, when the version '%s' was expected |
| | | ERR_MVCC_VERSIONS_MISMATCH_46=The resource could not be accessed because the expected version '%s' does \ |
| | | not match the current version '%s' |
| | | ERR_REFERENCE_FIELD_NO_PRIMARY_KEY_47=The request cannot be processed because the reference field '%s' \ |
| | | contains a value which does not contain a primary key |
| | | ERR_REFERENCE_FIELD_MULTIPLE_PRIMARY_KEYS_48=The request cannot be processed because the reference field \ |
| | | '%s' contains a value which contains multiple primary keys |
| | | ERR_REFERENCE_FIELD_DOES_NOT_EXIST_49=The request cannot be processed because the resource '%s' referenced \ |
| | | in field '%s' does not exist |
| | | ERR_REFERENCE_FIELD_AMBIGUOUS_50=The request cannot be processed because the resource '%s' referenced in \ |
| | | field '%s' is ambiguous |
| | | ERR_ILLEGAL_FILTER_ASSERTION_VALUE_51=The request cannot be processed because it contained an illegal \ |
| | | filter assertion value '%s' for field '%s' |
| | | ERR_ENCODING_VALUES_FOR_FIELD_52=The request cannot be processed because an error occurred while encoding \ |
| | | the values for the field '%s': '%s' |
| | | ERR_UNRECOGNIZED_JSON_VALUE_53=Unrecognized type of JSON value: '%s' |
| | | ERR_CLIENT_PROVIDER_RESOURCE_ID_MISSING_54=Resources cannot be created without a client provided resource ID |
| | | ERR_NOT_YET_IMPLEMENTED_55=Not yet implemented |
| | | ERR_ACTION_NOT_SUPPORTED_56=The action '%s' is not supported |
| | | ERR_PASSWORD_MODIFY_SECURE_CONNECTION_57=Password modify requires a secure connection |
| | | ERR_PASSWORD_MODIFY_USER_AUTHENTICATED_58=Password modify requires user to be authenticated |
| | | ERR_DECODING_CONTROL_59=Unable to decode ldap control: '%s' |
| | | ERR_ERROR_RESPONSE_60=An error occurred while processing the request '%s': '%s' (details: '%s') |
| | | 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' |
| New file |
| | |
| | | # |
| | | # The contents of this file are subject to the terms of the Common Development and |
| | | # Distribution License (the License). You may not use this file except in compliance with the |
| | | # License. |
| | | # |
| | | # You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | # specific language governing permission and limitations under the License. |
| | | # |
| | | # When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | # the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | # Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | # information: "Portions Copyright [year] [name of copyright owner]". |
| | | # |
| | | # Copyright 2016 ForgeRock AS. |
| | | # |
| New file |
| | |
| | | # |
| | | # The contents of this file are subject to the terms of the Common Development and |
| | | # Distribution License (the License). You may not use this file except in compliance with the |
| | | # License. |
| | | # |
| | | # You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | # specific language governing permission and limitations under the License. |
| | | # |
| | | # When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | # the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | # Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | # information: "Portions Copyright [year] [name of copyright owner]". |
| | | # |
| | | # Copyright 2016 ForgeRock AS. |
| | | # |
| New file |
| | |
| | | # |
| | | # The contents of this file are subject to the terms of the Common Development and |
| | | # Distribution License (the License). You may not use this file except in compliance with the |
| | | # License. |
| | | # |
| | | # You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | # specific language governing permission and limitations under the License. |
| | | # |
| | | # When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | # the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | # Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | # information: "Portions Copyright [year] [name of copyright owner]". |
| | | # |
| | | # Copyright 2016 ForgeRock AS. |
| | | # |
| New file |
| | |
| | | # |
| | | # The contents of this file are subject to the terms of the Common Development and |
| | | # Distribution License (the License). You may not use this file except in compliance with the |
| | | # License. |
| | | # |
| | | # You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | # specific language governing permission and limitations under the License. |
| | | # |
| | | # When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | # the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | # Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | # information: "Portions Copyright [year] [name of copyright owner]". |
| | | # |
| | | # Copyright 2016 ForgeRock AS. |
| | | # |
| New file |
| | |
| | | # |
| | | # The contents of this file are subject to the terms of the Common Development and |
| | | # Distribution License (the License). You may not use this file except in compliance with the |
| | | # License. |
| | | # |
| | | # You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | # specific language governing permission and limitations under the License. |
| | | # |
| | | # When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | # the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | # Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | # information: "Portions Copyright [year] [name of copyright owner]". |
| | | # |
| | | # Copyright 2016 ForgeRock AS. |
| | | # |
| New file |
| | |
| | | # |
| | | # The contents of this file are subject to the terms of the Common Development and |
| | | # Distribution License (the License). You may not use this file except in compliance with the |
| | | # License. |
| | | # |
| | | # You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | # specific language governing permission and limitations under the License. |
| | | # |
| | | # When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | # the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | # Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | # information: "Portions Copyright [year] [name of copyright owner]". |
| | | # |
| | | # Copyright 2016 ForgeRock AS. |
| | | # |
| New file |
| | |
| | | # |
| | | # The contents of this file are subject to the terms of the Common Development and |
| | | # Distribution License (the License). You may not use this file except in compliance with the |
| | | # License. |
| | | # |
| | | # You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | # specific language governing permission and limitations under the License. |
| | | # |
| | | # When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | # the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | # Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | # information: "Portions Copyright [year] [name of copyright owner]". |
| | | # |
| | | # Copyright 2016 ForgeRock AS. |
| | | # |
| New file |
| | |
| | | # |
| | | # The contents of this file are subject to the terms of the Common Development and |
| | | # Distribution License (the License). You may not use this file except in compliance with the |
| | | # License. |
| | | # |
| | | # You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | # specific language governing permission and limitations under the License. |
| | | # |
| | | # When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | # the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | # Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | # information: "Portions Copyright [year] [name of copyright owner]". |
| | | # |
| | | # Copyright 2016 ForgeRock AS. |
| | | # |
| New file |
| | |
| | | # |
| | | # The contents of this file are subject to the terms of the Common Development and |
| | | # Distribution License (the License). You may not use this file except in compliance with the |
| | | # License. |
| | | # |
| | | # You can obtain a copy of the License at legal/CDDLv1.0.txt. See the License for the |
| | | # specific language governing permission and limitations under the License. |
| | | # |
| | | # When distributing Covered Software, include this CDDL Header Notice in each file and include |
| | | # the License file at legal/CDDLv1.0.txt. If applicable, add the following below the CDDL |
| | | # Header, with the fields enclosed by brackets [] replaced by your own identifying |
| | | # information: "Portions Copyright [year] [name of copyright owner]". |
| | | # |
| | | # Copyright 2016 ForgeRock AS. |
| | | # |
| | |
| | | } |
| | | |
| | | @Test(expectedExceptions = ExecutionException.class, expectedExceptionsMessageRegExp = |
| | | ".*The token 'test-token' must be an access token, but it is a \"refresh_token\"") |
| | | ".*The token 'test-token' must be an access token, but it is a 'refresh_token'") |
| | | public void testInvalidTokenType() throws Exception { |
| | | final SearchResultEntry entry = mock(SearchResultEntry.class); |
| | | final Attribute attribute = mock(Attribute.class); |