| 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. |
| | | */ |
| | | package org.forgerock.opendj.rest2ldap; |
| | | |
| | | import static java.util.Collections.emptyList; |
| | | import static java.util.Collections.singletonList; |
| | | import static org.forgerock.json.JsonValue.field; |
| | | import static org.forgerock.json.JsonValue.object; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2Ldap.asResourceException; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_ENCODING_VALUES_FOR_FIELD; |
| | | import static org.forgerock.opendj.rest2ldap.Rest2ldapMessages.ERR_PATCH_JSON_INTERNAL_PROPERTY; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.jsonToAttribute; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newBadRequestException; |
| | | import static org.forgerock.opendj.rest2ldap.Utils.newNotSupportedException; |
| | | import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.byteStringToJson; |
| | | import static org.forgerock.opendj.rest2ldap.schema.JsonSchema.jsonToByteString; |
| | | import static org.forgerock.util.promise.Promises.newResultPromise; |
| | | |
| | | import java.util.ArrayList; |
| | | import java.util.Collection; |
| | | import java.util.List; |
| | | import java.util.Set; |
| | | |
| | | import org.forgerock.json.JsonPointer; |
| | | import org.forgerock.json.JsonValue; |
| | | import org.forgerock.json.resource.PatchOperation; |
| | | import org.forgerock.json.resource.ResourceException; |
| | | import org.forgerock.opendj.ldap.Attribute; |
| | | import org.forgerock.opendj.ldap.AttributeDescription; |
| | | import org.forgerock.opendj.ldap.Entry; |
| | | import org.forgerock.opendj.ldap.Filter; |
| | | import org.forgerock.opendj.ldap.Modification; |
| | | import org.forgerock.services.context.Context; |
| | | import org.forgerock.util.promise.Promise; |
| | | import org.forgerock.util.query.QueryFilter; |
| | | |
| | | /** A property mapper which provides a mapping from a JSON value to an LDAP attribute having the JSON syntax. */ |
| | | public final class JsonPropertyMapper extends AbstractLdapPropertyMapper<JsonPropertyMapper> { |
| | | /** |
| | | * The default JSON schema for this property. According to json-schema.org the {} schema allows anything. |
| | | * However, the OpenAPI transformer seems to expect at least a "type" field to be present. |
| | | */ |
| | | private static final JsonValue ANY_SCHEMA = new JsonValue(object(field("type", "object"))); |
| | | private JsonValue jsonSchema = ANY_SCHEMA; |
| | | |
| | | JsonPropertyMapper(final AttributeDescription ldapAttributeName) { |
| | | super(ldapAttributeName); |
| | | } |
| | | |
| | | /** |
| | | * Sets the default JSON value which should be substituted when the LDAP attribute is not found in the LDAP entry. |
| | | * |
| | | * @param defaultValue |
| | | * The default JSON value. |
| | | * @return This property mapper. |
| | | */ |
| | | public JsonPropertyMapper defaultJsonValue(final Object defaultValue) { |
| | | this.defaultJsonValues = defaultValue != null ? singletonList(defaultValue) : emptyList(); |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Sets the default JSON values which should be substituted when the LDAP attribute is not found in the LDAP entry. |
| | | * |
| | | * @param defaultValues |
| | | * The default JSON values. |
| | | * @return This property mapper. |
| | | */ |
| | | public JsonPropertyMapper defaultJsonValues(final Collection<?> defaultValues) { |
| | | this.defaultJsonValues = defaultValues != null ? new ArrayList<>(defaultValues) : emptyList(); |
| | | return this; |
| | | } |
| | | |
| | | /** |
| | | * Sets the JSON schema corresponding to this simple property mapper. If not {@code null}, |
| | | * it will be returned by {@link #toJsonSchema()}, otherwise a default JSON schema will be |
| | | * automatically generated with the information available in this property mapper. |
| | | * |
| | | * @param jsonSchema |
| | | * the JSON schema corresponding to this simple property mapper. Can be {@code null} |
| | | * @return This property mapper. |
| | | */ |
| | | public JsonPropertyMapper jsonSchema(JsonValue jsonSchema) { |
| | | this.jsonSchema = jsonSchema != null ? jsonSchema : ANY_SCHEMA; |
| | | return this; |
| | | } |
| | | |
| | | @Override |
| | | public String toString() { |
| | | return "json(" + ldapAttributeName + ")"; |
| | | } |
| | | |
| | | @Override |
| | | Promise<Filter, ResourceException> getLdapFilter(final Context context, final Resource resource, |
| | | final JsonPointer path, final JsonPointer subPath, |
| | | final FilterType type, final String operator, |
| | | final Object valueAssertion) { |
| | | final QueryFilter<JsonPointer> queryFilter = toQueryFilter(type, subPath, operator, valueAssertion); |
| | | return newResultPromise(Filter.equality(ldapAttributeName.toString(), queryFilter)); |
| | | } |
| | | |
| | | private QueryFilter<JsonPointer> toQueryFilter(final FilterType type, final JsonPointer subPath, |
| | | final String operator, final Object valueAssertion) { |
| | | switch (type) { |
| | | case CONTAINS: |
| | | return QueryFilter.contains(subPath, valueAssertion); |
| | | case STARTS_WITH: |
| | | return QueryFilter.startsWith(subPath, valueAssertion); |
| | | case EQUAL_TO: |
| | | return QueryFilter.equalTo(subPath, valueAssertion); |
| | | case GREATER_THAN: |
| | | return QueryFilter.greaterThan(subPath, valueAssertion); |
| | | case GREATER_THAN_OR_EQUAL_TO: |
| | | return QueryFilter.greaterThanOrEqualTo(subPath, valueAssertion); |
| | | case LESS_THAN: |
| | | return QueryFilter.lessThan(subPath, valueAssertion); |
| | | case LESS_THAN_OR_EQUAL_TO: |
| | | return QueryFilter.lessThanOrEqualTo(subPath, valueAssertion); |
| | | case PRESENT: |
| | | return QueryFilter.present(subPath); |
| | | case EXTENDED: |
| | | return QueryFilter.extendedMatch(subPath, operator, valueAssertion); |
| | | default: |
| | | return QueryFilter.alwaysFalse(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | Promise<Attribute, ResourceException> getNewLdapAttributes(final Context context, final Resource resource, |
| | | final JsonPointer path, final List<Object> newValues) { |
| | | try { |
| | | return newResultPromise(jsonToAttribute(newValues, ldapAttributeName, jsonToByteString())); |
| | | } catch (final Exception e) { |
| | | return newBadRequestException(ERR_ENCODING_VALUES_FOR_FIELD.get(path, e.getMessage())).asPromise(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | JsonPropertyMapper getThis() { |
| | | return this; |
| | | } |
| | | |
| | | /** Intercept attempts to patch internal fields and reject these as unsupported rather than unrecognized. */ |
| | | @Override |
| | | Promise<List<Modification>, ResourceException> patch(final Context context, final Resource resource, |
| | | final JsonPointer path, final PatchOperation operation) { |
| | | final JsonPointer field = operation.getField(); |
| | | if (field.isEmpty() || field.size() == 1 && field.get(0).equals("-")) { |
| | | return super.patch(context, resource, path, operation); |
| | | } |
| | | return newNotSupportedException(ERR_PATCH_JSON_INTERNAL_PROPERTY.get(field, path, path)).asPromise(); |
| | | } |
| | | |
| | | @SuppressWarnings("fallthrough") |
| | | @Override |
| | | Promise<JsonValue, ResourceException> read(final Context context, final Resource resource, final JsonPointer path, |
| | | final Entry e) { |
| | | try { |
| | | final Set<Object> s = e.parseAttribute(ldapAttributeName).asSetOf(byteStringToJson(), defaultJsonValues); |
| | | switch (s.size()) { |
| | | case 0: |
| | | return newResultPromise(null); |
| | | case 1: |
| | | if (attributeIsSingleValued()) { |
| | | return newResultPromise(new JsonValue(s.iterator().next())); |
| | | } |
| | | // Fall-though: unexpectedly got multiple values. It's probably best to just return them. |
| | | default: |
| | | return newResultPromise(new JsonValue(new ArrayList<>(s))); |
| | | } |
| | | } catch (final Exception ex) { |
| | | // The LDAP attribute could not be decoded. |
| | | return asResourceException(ex).asPromise(); |
| | | } |
| | | } |
| | | |
| | | @Override |
| | | JsonValue toJsonSchema() { |
| | | return jsonSchema; |
| | | } |
| | | } |