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

Guy Paddock
26.53.2017 ce283e9af9ec1c0cdc76e4fe7b3ec2b7d8973c75
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
/*
 * 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;
    }
}