private final MultiFieldPanel<String> enumerationKeys;
+ private final AjaxDropDownChoicePanel<String> validator;
+
public PlainSchemaDetails(
final String id,
final PageReference pageReference,
binaryParams, mimeType);
target.add(conversionParams);
target.add(typeParams);
+ target.add(validator);
}
}
);
map(EntityTO::getKey).sorted().collect(Collectors.toList());
}
};
- final AjaxDropDownChoicePanel<String> validator = new AjaxDropDownChoicePanel<>("validator",
+ validator = new AjaxDropDownChoicePanel<>("validator",
getString("validator"), new PropertyModel<>(schemaTO, "validator"));
+ validator.setOutputMarkupId(true);
((DropDownChoice) validator.getField()).setNullValid(true);
validator.setChoices(validators.getObject());
schemaForm.add(validator);
binaryParams.setVisible(false);
mimeType.setModelObject(null);
mimeType.setChoices(null);
+
+ PlainSchemaTO.class.cast(schema).setValidator(null);
} else if (AttrSchemaType.Enum.ordinal() == typeOrdinal) {
conversionParams.setVisible(false);
conversionPattern.setModelObject(null);
binaryParams.setVisible(false);
mimeType.setModelObject(null);
mimeType.setChoices(null);
+
+ PlainSchemaTO.class.cast(schema).setValidator(null);
} else if (AttrSchemaType.Encrypted.ordinal() == typeOrdinal) {
conversionParams.setVisible(false);
conversionPattern.setModelObject(null);
binaryParams.setVisible(false);
mimeType.setModelObject(null);
mimeType.setChoices(null);
+
+ PlainSchemaTO.class.cast(schema).setValidator(null);
} else if (AttrSchemaType.Binary.ordinal() == typeOrdinal) {
conversionParams.setVisible(false);
conversionPattern.setModelObject(null);
binaryParams.setVisible(true);
mimeType.setChoices(MIME_TYPES_LOADER.getMimeTypes());
+
+ PlainSchemaTO.class.cast(schema).setValidator("BinaryValidator");
} else {
conversionParams.setVisible(false);
conversionPattern.setModelObject(null);
binaryParams.setVisible(false);
mimeType.setModelObject(null);
mimeType.setChoices(null);
+
+ PlainSchemaTO.class.cast(schema).setValidator(null);
}
}
}
<artifactId>syncope-core-spring</artifactId>
<version>${project.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>org.apache.tika</groupId>
+ <artifactId>tika-core</artifactId>
+ </dependency>
<!-- TEST -->
<dependency>
--- /dev/null
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.syncope.core.persistence.jpa.attrvalue.validation;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import java.io.IOException;
+import org.apache.syncope.core.persistence.api.attrvalue.validation.InvalidPlainAttrValueException;
+import org.apache.syncope.core.persistence.api.entity.PlainAttrValue;
+import org.apache.syncope.core.persistence.api.entity.PlainSchema;
+import org.apache.tika.Tika;
+
+public class BinaryValidator extends AbstractValidator {
+
+ private static final long serialVersionUID = 1344152444666540361L;
+
+ private static final ObjectMapper MAPPER = new ObjectMapper();
+
+ @Override
+ protected void doValidate(final PlainAttrValue attrValue) {
+ // check Binary schemas MIME Type mismatches
+ if (attrValue.getBinaryValue() != null) {
+ PlainSchema currentSchema = attrValue.getAttr().getSchema();
+ byte[] binaryValue = attrValue.getBinaryValue();
+ String mimeType = detectSchemaMimeType(binaryValue);
+ boolean valid = true;
+ if (!mimeType.equals(currentSchema.getMimeType())) {
+ if (mimeType.equals("text/plain")
+ && currentSchema.getMimeType().equals("application/json")) {
+ String decoded = new String(binaryValue).trim();
+ valid = (decoded.startsWith("{") && decoded.endsWith("}"))
+ || (decoded.startsWith("[") && decoded.endsWith("]"))
+ && isValidJSON(decoded);
+ } else {
+ valid = false;
+ }
+ }
+ if (!valid) {
+ throw new InvalidPlainAttrValueException(
+ "Found MIME type: '"
+ + mimeType
+ + "', expecting: '"
+ + currentSchema.getMimeType()
+ + "'");
+ }
+ }
+ }
+
+ private String detectSchemaMimeType(final byte[] value) {
+ Tika tika = new Tika();
+ tika.setMaxStringLength(-1);
+ return tika.detect(value);
+ }
+
+ private boolean isValidJSON(final String value) {
+ try {
+ MAPPER.readTree(value);
+ return true;
+ } catch (IOException e) {
+ return false;
+ }
+ }
+}
<PlainSchema id="email" type="String" anyTypeClass_id="BaseUser"
mandatoryCondition="false" multivalue="0" uniqueConstraint="0" readonly="0"
validator_id="EmailAddressValidator"/>
+
+ <Implementation id="BinaryValidator" type="VALIDATOR" engine="JAVA"
+ body="org.apache.syncope.core.persistence.jpa.attrvalue.validation.BinaryValidator"/>
<Implementation id="PullJobDelegate" type="TASKJOB_DELEGATE" engine="JAVA"
body="org.apache.syncope.core.provisioning.java.pushpull.PullJobDelegate"/>
List<Implementation> implementations = implementationDAO.findAll();
assertFalse(implementations.isEmpty());
- assertEquals(16, implementations.size());
+ assertEquals(17, implementations.size());
implementations = implementationDAO.find(ImplementationType.PULL_ACTIONS);
assertEquals(1, implementations.size());
assertEquals(3, implementations.size());
implementations = implementationDAO.find(ImplementationType.VALIDATOR);
- assertEquals(1, implementations.size());
+ assertEquals(2, implementations.size());
implementations = implementationDAO.find(ImplementationType.PULL_CORRELATION_RULE);
assertEquals(1, implementations.size());
<PlainSchema id="photo" type="Binary" anyTypeClass_id="other"
mandatoryCondition="false" multivalue="0" uniqueConstraint="0" readonly="0"
mimeType="image/jpeg"/>
+
+ <Implementation id="BinaryValidator" type="VALIDATOR" engine="JAVA"
+ body="org.apache.syncope.core.persistence.jpa.attrvalue.validation.BinaryValidator"/>
<SyncopeSchema id="csvuserid"/>
<DerSchema id="csvuserid" expression="firstname + ',' + surname" anyTypeClass_id="csv"/>
try {
attr.add(value, anyUtils);
} catch (InvalidPlainAttrValueException e) {
- LOG.warn("Invalid value for attribute " + schema.getKey() + ": " + value, e);
+ String valueToPrint = value.length() > 40
+ ? value.substring(0, 20) + "..."
+ : value;
+ LOG.warn("Invalid value for attribute " + schema.getKey() + ": " + valueToPrint, e);
- invalidValues.getElements().add(schema.getKey() + ": " + value + " - " + e.getMessage());
+ invalidValues.getElements().add(schema.getKey() + ": " + valueToPrint + " - " + e.getMessage());
}
}
});
import org.apache.syncope.core.persistence.api.entity.Implementation;
import org.apache.syncope.core.persistence.jpa.attrvalue.validation.AlwaysTrueValidator;
import org.apache.syncope.core.persistence.jpa.attrvalue.validation.BasicValidator;
+import org.apache.syncope.core.persistence.jpa.attrvalue.validation.BinaryValidator;
import org.apache.syncope.core.persistence.jpa.attrvalue.validation.EmailAddressValidator;
import org.apache.syncope.core.persistence.jpa.dao.DefaultAccountRule;
import org.apache.syncope.core.persistence.jpa.dao.DefaultPasswordRule;
classNames.add(BasicValidator.class.getName());
classNames.add(EmailAddressValidator.class.getName());
classNames.add(AlwaysTrueValidator.class.getName());
+ classNames.add(BinaryValidator.class.getName());
put(ImplementationType.VALIDATOR, classNames);
classNames = new HashSet<>();
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
+import java.io.IOException;
+import java.util.Base64;
import java.util.List;
import javax.ws.rs.core.GenericType;
import javax.ws.rs.core.Response;
import org.apache.commons.lang3.SerializationUtils;
import org.apache.syncope.common.lib.SyncopeClientException;
+import org.apache.syncope.common.lib.patch.AttrPatch;
+import org.apache.syncope.common.lib.patch.UserPatch;
import org.apache.syncope.common.lib.to.AnyTypeClassTO;
import org.apache.syncope.common.lib.to.MembershipTO;
import org.apache.syncope.common.lib.to.PlainSchemaTO;
import org.apache.syncope.common.lib.types.CipherAlgorithm;
import org.apache.syncope.common.lib.types.ClientExceptionType;
import org.apache.syncope.common.lib.types.EntityViolationType;
+import org.apache.syncope.common.lib.types.PatchOperation;
import org.apache.syncope.common.lib.types.SchemaType;
import org.apache.syncope.common.rest.api.beans.SchemaQuery;
import org.apache.syncope.fit.AbstractITCase;
import org.junit.jupiter.api.Test;
+import org.apache.cxf.helpers.IOUtils;
public class PlainSchemaITCase extends AbstractITCase {
}
@Test
+ public void testBinaryValidation() throws IOException {
+ // pdf - with validator
+ PlainSchemaTO schemaTOpdf = new PlainSchemaTO();
+ schemaTOpdf.setKey("BinaryPDF");
+ schemaTOpdf.setType(AttrSchemaType.Binary);
+ schemaTOpdf.setMimeType("application/pdf");
+ schemaTOpdf.setValidator("BinaryValidator");
+ schemaTOpdf.setAnyTypeClass("minimal user");
+
+ createSchema(SchemaType.PLAIN, schemaTOpdf);
+
+ // json - with validator
+ PlainSchemaTO schemaTOjson = new PlainSchemaTO();
+ schemaTOjson.setKey("BinaryJSON");
+ schemaTOjson.setType(AttrSchemaType.Binary);
+ schemaTOjson.setMimeType("application/json");
+ schemaTOjson.setValidator("BinaryValidator");
+ schemaTOjson.setAnyTypeClass("minimal user");
+
+ createSchema(SchemaType.PLAIN, schemaTOjson);
+
+ // json - no validator
+ PlainSchemaTO schemaTOjson2 = new PlainSchemaTO();
+ schemaTOjson2.setKey("BinaryJSON2");
+ schemaTOjson2.setType(AttrSchemaType.Binary);
+ schemaTOjson2.setMimeType("application/json");
+ schemaTOjson2.setAnyTypeClass("minimal user");
+
+ createSchema(SchemaType.PLAIN, schemaTOjson2);
+
+ UserTO userTO = UserITCase.getUniqueSampleTO("test@syncope.apache.org");
+
+ userTO = createUser(userTO).getEntity();
+ assertNotNull(userTO);
+
+ UserPatch userPatch = new UserPatch();
+ userPatch.setKey(userTO.getKey());
+ // validation OK - application/pdf -> application/pdf
+ userPatch.getPlainAttrs().add(new AttrPatch.Builder().operation(PatchOperation.ADD_REPLACE).
+ attrTO(attrTO("BinaryPDF",
+ Base64.getEncoder().encodeToString(
+ IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.pdf"))))).
+ build());
+
+ updateUser(userPatch);
+ assertNotNull(userService.read(userTO.getKey()).getPlainAttr("BinaryPDF"));
+
+ userPatch = new UserPatch();
+ userPatch.setKey(userTO.getKey());
+ // validation KO - text/html -> application/pdf
+ try {
+ userPatch.getPlainAttrs().add(new AttrPatch.Builder().operation(PatchOperation.ADD_REPLACE).
+ attrTO(attrTO("BinaryPDF",
+ Base64.getEncoder().encodeToString(
+ IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.html"))))).
+ build());
+
+ updateUser(userPatch);
+ fail("This should not be reacheable");
+ } catch (SyncopeClientException e) {
+ assertEquals(ClientExceptionType.InvalidValues, e.getType());
+ }
+
+ userPatch = new UserPatch();
+ userPatch.setKey(userTO.getKey());
+ // validation ok - application/json -> application/json
+ userPatch.getPlainAttrs().add(new AttrPatch.Builder().operation(PatchOperation.ADD_REPLACE).
+ attrTO(attrTO("BinaryJSON",
+ Base64.getEncoder().encodeToString(
+ IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.json"))))).
+ build());
+
+ updateUser(userPatch);
+ assertNotNull(userService.read(userTO.getKey()).getPlainAttr("BinaryJSON"));
+
+ userPatch = new UserPatch();
+ userPatch.setKey(userTO.getKey());
+ // no validation - application/xml -> application/json
+ userPatch.getPlainAttrs().add(new AttrPatch.Builder().operation(PatchOperation.ADD_REPLACE).
+ attrTO(attrTO("BinaryJSON2",
+ Base64.getEncoder().encodeToString(
+ IOUtils.readBytesFromStream(getClass().getResourceAsStream("/test.xml"))))).
+ build());
+
+ updateUser(userPatch);
+ assertNotNull(userService.read(userTO.getKey()).getPlainAttr("BinaryJSON2"));
+ }
+
+ @Test
public void delete() {
PlainSchemaTO schemaTO = buildPlainSchemaTO("todelete", AttrSchemaType.String);
schemaTO.setMandatoryCondition("false");
--- /dev/null
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<!doctype html>
+
+<head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
+ <meta name="description" content="">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+</head>
+<body>
+ <p> Syncope is Magic </p>
+</body>
+</html>
--- /dev/null
+{
+ "message": "Syncope is Magic"
+}
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements. See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership. The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License. You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied. See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+<Tests xmlns="http://www.test.com">
+ <Test>
+ <Name>Syncope is Magic</Name>
+ </Test>
+</Tests>
\ No newline at end of file
<commons-lang.version>3.7</commons-lang.version>
<commons-text.version>1.2</commons-text.version>
<commons-logging.version>1.1.3</commons-logging.version>
+
+ <tika.version>1.17</tika.version>
<joda.version>2.9.9</joda.version>
<artifactId>httpmime</artifactId>
<version>${httpclient.version}</version>
</dependency>
+
+ <dependency>
+ <groupId>org.apache.tika</groupId>
+ <artifactId>tika-core</artifactId>
+ <version>${tika.version}</version>
+ </dependency>
<dependency>
<groupId>org.netbeans.api</groupId>