View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.core.persistence.jpa.entity;
20  
21  import java.time.OffsetDateTime;
22  import java.util.Base64;
23  import java.util.Optional;
24  import java.util.regex.Pattern;
25  import javax.persistence.Lob;
26  import javax.persistence.MappedSuperclass;
27  import org.apache.commons.lang3.ArrayUtils;
28  import org.apache.commons.lang3.StringUtils;
29  import org.apache.commons.lang3.builder.ToStringBuilder;
30  import org.apache.syncope.common.lib.SyncopeConstants;
31  import org.apache.syncope.common.lib.types.AttrSchemaType;
32  import org.apache.syncope.core.persistence.api.attrvalue.validation.ParsingValidationException;
33  import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
34  import org.apache.syncope.core.persistence.api.entity.PlainSchema;
35  import org.apache.syncope.core.persistence.jpa.validation.entity.PlainAttrValueCheck;
36  import org.apache.syncope.core.provisioning.api.utils.FormatUtils;
37  import org.apache.syncope.core.spring.ApplicationContextProvider;
38  import org.apache.syncope.core.spring.security.Encryptor;
39  
40  @MappedSuperclass
41  @PlainAttrValueCheck
42  public abstract class AbstractPlainAttrValue extends AbstractGeneratedKeyEntity implements PlainAttrValue {
43  
44      private static final long serialVersionUID = -9141923816611244785L;
45  
46      private static final Pattern SPRING_ENV_PROPERTY = Pattern.compile("^\\$\\{.*\\}$");
47  
48      private String stringValue;
49  
50      private OffsetDateTime dateValue;
51  
52      private Boolean booleanValue;
53  
54      private Long longValue;
55  
56      private Double doubleValue;
57  
58      @Lob
59      private byte[] binaryValue;
60  
61      @Override
62      public Boolean getBooleanValue() {
63          return booleanValue;
64      }
65  
66      @Override
67      public void setBooleanValue(final Boolean booleanValue) {
68          this.booleanValue = booleanValue;
69      }
70  
71      @Override
72      public OffsetDateTime getDateValue() {
73          return dateValue;
74      }
75  
76      @Override
77      public void setDateValue(final OffsetDateTime dateValue) {
78          this.dateValue = dateValue;
79      }
80  
81      @Override
82      public Double getDoubleValue() {
83          return doubleValue;
84      }
85  
86      @Override
87      public void setDoubleValue(final Double doubleValue) {
88          this.doubleValue = doubleValue;
89      }
90  
91      @Override
92      public Long getLongValue() {
93          return longValue;
94      }
95  
96      @Override
97      public void setLongValue(final Long longValue) {
98          this.longValue = longValue;
99      }
100 
101     @Override
102     public String getStringValue() {
103         // workaround for Oracle DB considering empty string values as NULL (SYNCOPE-664)
104         return dateValue == null
105                 && booleanValue == null
106                 && longValue == null
107                 && doubleValue == null
108                 && binaryValue == null
109                 && stringValue == null
110                         ? StringUtils.EMPTY
111                         : stringValue;
112     }
113 
114     @Override
115     public void setStringValue(final String stringValue) {
116         this.stringValue = stringValue;
117     }
118 
119     @Override
120     public byte[] getBinaryValue() {
121         return binaryValue;
122     }
123 
124     @Override
125     public void setBinaryValue(final byte[] binaryValue) {
126         this.binaryValue = ArrayUtils.clone(binaryValue);
127     }
128 
129     protected String getSecretKey(final PlainSchema schema) {
130         return SPRING_ENV_PROPERTY.matcher(schema.getSecretKey()).matches()
131                 ? ApplicationContextProvider.getApplicationContext().getEnvironment().
132                         getProperty(StringUtils.substringBetween(schema.getSecretKey(), "${", "}"))
133                 : schema.getSecretKey();
134     }
135 
136     @Override
137     public void parseValue(final PlainSchema schema, final String value) {
138         Exception exception = null;
139 
140         switch (schema.getType()) {
141 
142             case Boolean:
143                 this.setBooleanValue(Boolean.valueOf(value));
144                 break;
145 
146             case Long:
147                 try {
148                 this.setLongValue(schema.getConversionPattern() == null
149                         ? Long.valueOf(value)
150                         : FormatUtils.parseNumber(value, schema.getConversionPattern()).longValue());
151             } catch (Exception pe) {
152                 exception = pe;
153             }
154             break;
155 
156             case Double:
157                 try {
158                 this.setDoubleValue(schema.getConversionPattern() == null
159                         ? Double.valueOf(value)
160                         : FormatUtils.parseNumber(value, schema.getConversionPattern()).doubleValue());
161             } catch (Exception pe) {
162                 exception = pe;
163             }
164             break;
165 
166             case Date:
167                 try {
168                 this.setDateValue(schema.getConversionPattern() == null
169                         ? FormatUtils.parseDate(value)
170                         : FormatUtils.parseDate(value, schema.getConversionPattern()));
171             } catch (Exception pe) {
172                 exception = pe;
173             }
174             break;
175 
176             case Encrypted:
177                 try {
178                 this.setStringValue(Encryptor.getInstance(getSecretKey(schema)).
179                         encode(value, schema.getCipherAlgorithm()));
180             } catch (Exception pe) {
181                 exception = pe;
182             }
183             break;
184 
185             case Binary:
186                 this.setBinaryValue(Base64.getDecoder().decode(value));
187                 break;
188 
189             case String:
190             case Enum:
191             default:
192                 this.setStringValue(value);
193         }
194 
195         if (exception != null) {
196             throw new ParsingValidationException(
197                     "While trying to parse '" + value + "' as " + schema.getKey(), exception);
198         }
199     }
200 
201     @SuppressWarnings("unchecked")
202     @Override
203     public <T> T getValue() {
204         return (T) (booleanValue != null
205                 ? getBooleanValue()
206                 : dateValue != null
207                         ? getDateValue()
208                         : doubleValue != null
209                                 ? getDoubleValue()
210                                 : longValue != null
211                                         ? getLongValue()
212                                         : binaryValue != null
213                                                 ? getBinaryValue()
214                                                 : getStringValue());
215     }
216 
217     private Object getValue(final AttrSchemaType type) {
218         Object value;
219         switch (type) {
220 
221             case Boolean:
222                 value = getBooleanValue();
223                 break;
224 
225             case Long:
226                 value = getLongValue();
227                 break;
228 
229             case Double:
230                 value = getDoubleValue();
231                 break;
232 
233             case Date:
234                 value = getDateValue();
235                 break;
236 
237             case Binary:
238                 value = getBinaryValue();
239                 break;
240 
241             case String:
242             case Enum:
243             case Encrypted:
244                 value = getStringValue();
245                 break;
246 
247             default:
248                 value = null;
249         }
250 
251         return value;
252     }
253 
254     private String getValueAsString(final AttrSchemaType type, final PlainSchema schema) {
255         if (getValue(type) == null) {
256             LOG.warn("Could not find expected value for type {} in {}, reverting to getValue().toString()", type, this);
257 
258             Object value = getValue();
259             return Optional.ofNullable(value).map(Object::toString).orElse(null);
260         }
261 
262         String result;
263         switch (type) {
264 
265             case Boolean:
266                 result = getBooleanValue().toString();
267                 break;
268 
269             case Long:
270                 result = schema == null || schema.getConversionPattern() == null
271                         ? getLongValue().toString()
272                         : FormatUtils.format(getLongValue(), schema.getConversionPattern());
273                 break;
274 
275             case Double:
276                 result = schema == null || schema.getConversionPattern() == null
277                         ? getDoubleValue().toString()
278                         : FormatUtils.format(getDoubleValue(), schema.getConversionPattern());
279                 break;
280 
281             case Date:
282                 result = schema == null || schema.getConversionPattern() == null
283                         ? FormatUtils.format(getDateValue())
284                         : FormatUtils.format(getDateValue(), schema.getConversionPattern());
285                 break;
286 
287             case Binary:
288                 result = Base64.getEncoder().encodeToString(getBinaryValue());
289                 break;
290 
291             case Encrypted:
292                 if (schema == null
293                         || !SyncopeConstants.ENCRYPTED_DECODE_CONVERSION_PATTERN.equals(schema.getConversionPattern())
294                         || !schema.getCipherAlgorithm().isInvertible()) {
295 
296                     result = getStringValue();
297                 } else {
298                     try {
299                         result = Encryptor.getInstance(getSecretKey(schema)).
300                                 decode(getStringValue(), schema.getCipherAlgorithm());
301                     } catch (Exception e) {
302                         LOG.error("Could not decode encrypted value {} for schema {}", getStringValue(), schema, e);
303                         result = getStringValue();
304                     }
305                 }
306                 break;
307 
308             case String:
309             case Enum:
310             default:
311                 result = getStringValue();
312         }
313 
314         return result;
315     }
316 
317     @Override
318     public String getValueAsString() {
319         PlainSchema schema = getAttr() == null || getAttr().getSchema() == null
320                 ? null
321                 : getAttr().getSchema();
322         AttrSchemaType type = schema == null || schema.getType() == null
323                 ? AttrSchemaType.String
324                 : getAttr().getSchema().getType();
325 
326         return getValueAsString(type, schema);
327     }
328 
329     @Override
330     public String getValueAsString(final AttrSchemaType type) {
331         return getValueAsString(
332                 type,
333                 getAttr() == null || getAttr().getSchema() == null ? null : getAttr().getSchema());
334     }
335 
336     @Override
337     public String getValueAsString(final PlainSchema schema) {
338         return getValueAsString(schema.getType(), schema);
339     }
340 
341     @Override
342     public String toString() {
343         return new ToStringBuilder(this).
344                 append(getKey()).
345                 append(stringValue).
346                 append(dateValue).
347                 append(booleanValue).
348                 append(longValue).
349                 append(doubleValue).
350                 append(binaryValue).
351                 build();
352     }
353 }