[SYNCOPE-1064] improved security to avoid JS hacking and exploitation, added relative tests and moved form customization server side
Project: http://git-wip-us.apache.org/repos/asf/syncope/repo Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/b292d3c0 Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/b292d3c0 Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/b292d3c0 Branch: refs/heads/SYNCOPE-808 Commit: b292d3c08680046a859ab0a3a7e1ff21c7a5f0e6 Parents: dcc1266 Author: Andrea Patricelli <[email protected]> Authored: Wed Apr 12 11:21:56 2017 +0200 Committer: Andrea Patricelli <[email protected]> Committed: Wed Apr 12 12:12:57 2017 +0200 ---------------------------------------------------------------------- archetype/pom.xml | 2 + .../archetype-resources/enduser/pom.xml | 4 + client/enduser/pom.xml | 8 + .../enduser/SyncopeEnduserApplication.java | 101 ++++++++++ .../client/enduser/SyncopeEnduserConstants.java | 2 + .../client/enduser/SyncopeEnduserSession.java | 15 ++ .../enduser/adapters/PlatformInfoAdapter.java | 6 +- .../client/enduser/model/CustomAttribute.java | 62 ++++++ .../enduser/model/CustomAttributesInfo.java | 8 +- .../enduser/model/PlatformInfoRequest.java | 11 + .../enduser/resources/BaseUserSelfResource.java | 14 ++ .../client/enduser/resources/InfoResource.java | 12 +- .../enduser/resources/SchemaResource.java | 14 +- .../resources/UserSelfCreateResource.java | 200 ++++++++++--------- .../enduser/resources/UserSelfReadResource.java | 40 +++- .../resources/UserSelfUpdateResource.java | 188 +++++++++-------- .../enduser/util/UserRequestValidator.java | 86 ++++++++ .../resources/app/configuration/customForm.json | 1 - .../resources/META-INF/resources/app/js/app.js | 15 +- .../app/js/controllers/UserController.js | 2 +- .../app/js/services/configurationService.js | 41 ---- .../resources/app/js/services/schemaService.js | 12 +- .../enduser/src/main/resources/customForm.json | 1 + .../enduser/util/UserRequestValidatorTest.java | 85 ++++++++ .../enduser/src/test/resources/customForm.json | 47 +++++ deb/enduser/pom.xml | 1 + deb/enduser/src/deb/control/conffiles | 1 + .../src/main/resources/customForm.json | 1 + .../src/test/resources/customForm.json | 1 + 29 files changed, 711 insertions(+), 270 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/archetype/pom.xml ---------------------------------------------------------------------- diff --git a/archetype/pom.xml b/archetype/pom.xml index 1e0088e..8ecc078 100644 --- a/archetype/pom.xml +++ b/archetype/pom.xml @@ -258,6 +258,7 @@ under the License. <targetPath>${project.build.outputDirectory}/archetype-resources/enduser/src/main/resources</targetPath> <includes> <include>enduser.properties</include> + <include>customForm.json</include> </includes> </resource> <resource> @@ -284,6 +285,7 @@ under the License. <includes> <include>enduser.properties</include> <include>saml2sp-agent.properties</include> + <include>customForm.json</include> </includes> </resource> <resource> http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/archetype/src/main/resources/archetype-resources/enduser/pom.xml ---------------------------------------------------------------------- diff --git a/archetype/src/main/resources/archetype-resources/enduser/pom.xml b/archetype/src/main/resources/archetype-resources/enduser/pom.xml index 803a4ea..4a4edab 100644 --- a/archetype/src/main/resources/archetype-resources/enduser/pom.xml +++ b/archetype/src/main/resources/archetype-resources/enduser/pom.xml @@ -246,6 +246,10 @@ under the License. <copy file="${project.build.directory}/test-classes/enduser.properties" todir="${project.build.directory}/${project.build.finalName}/WEB-INF/classes" overwrite="true"/> + + <copy file="${project.build.directory}/test-classes/customForm" + todir="${project.build.directory}/${project.build.finalName}/WEB-INF/classes" + overwrite="true"/> </target> </configuration> <goals> http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/pom.xml ---------------------------------------------------------------------- diff --git a/client/enduser/pom.xml b/client/enduser/pom.xml index d544cd3..48f4f04 100644 --- a/client/enduser/pom.xml +++ b/client/enduser/pom.xml @@ -182,6 +182,14 @@ under the License. <groupId>org.apache.logging.log4j</groupId> <artifactId>log4j-core</artifactId> </dependency> + + <!-- TEST --> + <dependency> + <groupId>junit</groupId> + <artifactId>junit</artifactId> + <scope>test</scope> + </dependency> + </dependencies> <build> http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java index cfed2c5..fc0d730 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserApplication.java @@ -18,16 +18,28 @@ */ package org.apache.syncope.client.enduser; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; import java.io.File; +import java.io.FileFilter; +import java.io.IOException; import java.io.InputStream; import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; import org.apache.syncope.client.enduser.pages.HomePage; import java.util.Properties; import org.apache.commons.io.FileUtils; +import org.apache.commons.io.monitor.FileAlterationListener; +import org.apache.commons.io.monitor.FileAlterationListenerAdaptor; +import org.apache.commons.io.monitor.FileAlterationMonitor; +import org.apache.commons.io.monitor.FileAlterationObserver; import org.apache.commons.lang3.BooleanUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.syncope.client.enduser.annotations.Resource; import org.apache.syncope.client.enduser.init.ClassPathScanImplementationLookup; import org.apache.syncope.client.enduser.init.EnduserInitializer; +import org.apache.syncope.client.enduser.model.CustomAttributesInfo; import org.apache.syncope.client.enduser.resources.CaptchaResource; import org.apache.syncope.client.lib.SyncopeClientFactoryBean; import org.apache.syncope.common.lib.SyncopeConstants; @@ -52,6 +64,8 @@ public class SyncopeEnduserApplication extends WebApplication implements Seriali private static final String ENDUSER_PROPERTIES = "enduser.properties"; + private static final String CUSTOM_FORM_FILE = "customForm.json"; + public static SyncopeEnduserApplication get() { return (SyncopeEnduserApplication) WebApplication.get(); } @@ -76,6 +90,10 @@ public class SyncopeEnduserApplication extends WebApplication implements Seriali private SyncopeClientFactoryBean clientFactory; + private Map<String, CustomAttributesInfo> customForm; + + private static final ObjectMapper MAPPER = new ObjectMapper(); + @Override protected void init() { super.init(); @@ -131,6 +149,80 @@ public class SyncopeEnduserApplication extends WebApplication implements Seriali setContentType(SyncopeClientFactoryBean.ContentType.JSON). setUseCompression(BooleanUtils.toBoolean(useGZIPCompression)); + // read customForm.json + try (InputStream is = getClass().getResourceAsStream("/" + CUSTOM_FORM_FILE)) { + customForm = MAPPER.readValue(is, + new TypeReference<HashMap<String, CustomAttributesInfo>>() { + }); + File enduserDir = new File(props.getProperty("enduser.directory")); + boolean existsEnduserDir = enduserDir.exists() && enduserDir.canRead() && enduserDir.isDirectory(); + if (existsEnduserDir) { + File customFormFile = FileUtils.getFile(enduserDir, CUSTOM_FORM_FILE); + if (customFormFile.exists() && customFormFile.canRead() && customFormFile.isFile()) { + customForm = MAPPER.readValue(FileUtils.openInputStream(customFormFile), + new TypeReference<HashMap<String, CustomAttributesInfo>>() { + }); + } + } + FileAlterationObserver observer = existsEnduserDir + ? new FileAlterationObserver(enduserDir, new FileFilter() { + + @Override + public boolean accept(final File pathname) { + return StringUtils.contains(pathname.getPath(), CUSTOM_FORM_FILE); + } + }) + : new FileAlterationObserver(getClass().getResource("/" + CUSTOM_FORM_FILE).getFile(), + new FileFilter() { + + @Override + public boolean accept(final File pathname) { + return StringUtils.contains(pathname.getPath(), CUSTOM_FORM_FILE); + } + }); + + FileAlterationMonitor monitor = new FileAlterationMonitor(5000); + + FileAlterationListener listener = new FileAlterationListenerAdaptor() { + + @Override + public void onFileChange(final File file) { + try { + LOG.trace("{} has changed. Reloading form customization configuration.", CUSTOM_FORM_FILE); + customForm = MAPPER.readValue(FileUtils.openInputStream(file), + new TypeReference<HashMap<String, CustomAttributesInfo>>() { + }); + } catch (IOException e) { + e.printStackTrace(System.err); + } + } + + @Override + public void onFileCreate(final File file) { + try { + LOG.trace("{} has been created. Loading form customization configuration.", CUSTOM_FORM_FILE); + customForm = MAPPER.readValue(FileUtils.openInputStream(file), + new TypeReference<HashMap<String, CustomAttributesInfo>>() { + }); + } catch (IOException e) { + e.printStackTrace(System.err); + } + } + + @Override + public void onFileDelete(final File file) { + LOG.trace("{} has been deleted. Resetting form customization configuration.", CUSTOM_FORM_FILE); + customForm = null; + } + }; + + observer.addListener(listener); + monitor.addObserver(observer); + monitor.start(); + } catch (Exception e) { + throw new WicketRuntimeException("Could not read " + CUSTOM_FORM_FILE, e); + } + // mount resources ClassPathScanImplementationLookup classPathScanImplementationLookup = (ClassPathScanImplementationLookup) getServletContext(). @@ -221,4 +313,13 @@ public class SyncopeEnduserApplication extends WebApplication implements Seriali return xsrfEnabled; } + public Map<String, CustomAttributesInfo> getCustomForm() { + return customForm; + } + + public void setCustomForm(final Map<String, CustomAttributesInfo> customForm) { + this.customForm.clear(); + this.customForm.putAll(customForm); + } + } http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java index 4600166..0a683a9 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserConstants.java @@ -26,6 +26,8 @@ public final class SyncopeEnduserConstants { public static final String XSRF_HEADER_NAME = "X-XSRF-TOKEN"; + public static final String MEMBERSHIP_ATTR_SEPARATOR = "#"; + private SyncopeEnduserConstants() { // private constructor for utility class } http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java index 168656c..8268ef5 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/SyncopeEnduserSession.java @@ -19,6 +19,7 @@ package org.apache.syncope.client.enduser; import java.security.AccessControlException; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; @@ -28,6 +29,7 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.Predicate; import org.apache.commons.lang3.tuple.Pair; import org.apache.cxf.jaxrs.client.WebClient; +import org.apache.syncope.client.enduser.model.CustomAttributesInfo; import org.apache.syncope.client.lib.AnonymousAuthenticationHandler; import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.common.lib.info.PlatformInfo; @@ -66,6 +68,8 @@ public class SyncopeEnduserSession extends WebSession { private final CookieUtils cookieUtils; + private final Map<String, CustomAttributesInfo> customForm; + private boolean xsrfTokenGenerated = false; public static SyncopeEnduserSession get() { @@ -92,6 +96,7 @@ public class SyncopeEnduserSession extends WebSession { return object.getType() == AttrSchemaType.Date; } }); + customForm = new HashMap<>(); } private void afterAuthentication() { @@ -197,4 +202,14 @@ public class SyncopeEnduserSession extends WebSession { public void setXsrfTokenGenerated(final boolean xsrfTokenGenerated) { this.xsrfTokenGenerated = xsrfTokenGenerated; } + + public Map<String, CustomAttributesInfo> getCustomForm() { + return customForm; + } + + public void setCustomForm(final Map<String, CustomAttributesInfo> customForm) { + this.customForm.clear(); + this.customForm.putAll(customForm); + } + } http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/PlatformInfoAdapter.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/PlatformInfoAdapter.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/PlatformInfoAdapter.java index 5ed11be..a961576 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/PlatformInfoAdapter.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/adapters/PlatformInfoAdapter.java @@ -18,19 +18,23 @@ */ package org.apache.syncope.client.enduser.adapters; +import java.util.Map; import org.apache.syncope.client.enduser.SyncopeEnduserApplication; +import org.apache.syncope.client.enduser.model.CustomAttributesInfo; import org.apache.syncope.client.enduser.model.PlatformInfoRequest; import org.apache.syncope.common.lib.info.PlatformInfo; public final class PlatformInfoAdapter { - public static PlatformInfoRequest toPlatformInfoRequest(final PlatformInfo platformInfo) { + public static PlatformInfoRequest toPlatformInfoRequest(final PlatformInfo platformInfo, + final Map<String, CustomAttributesInfo> customForm) { PlatformInfoRequest request = new PlatformInfoRequest(); request.setPwdResetAllowed(platformInfo.isPwdResetAllowed()); request.setSelfRegAllowed(platformInfo.isSelfRegAllowed()); request.setPwdResetRequiringSecurityQuestions(platformInfo.isPwdResetRequiringSecurityQuestions()); request.setVersion(platformInfo.getVersion()); request.setCaptchaEnabled(SyncopeEnduserApplication.get().isCaptchaEnabled()); + request.setCustomForm(customForm); return request; } http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttribute.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttribute.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttribute.java new file mode 100644 index 0000000..80d65f6 --- /dev/null +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttribute.java @@ -0,0 +1,62 @@ +/* + * 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.client.enduser.model; + +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; + +public class CustomAttribute implements Serializable { + + private static final long serialVersionUID = 4910266842123376686L; + + private Boolean readonly; + + private List<String> defaultValues = new ArrayList<>(); + + public CustomAttribute() { + } + + public Boolean getReadonly() { + return readonly; + } + + public void setReadonly(final Boolean readonly) { + this.readonly = readonly; + } + + public List<String> getDefaultValues() { + return defaultValues; + } + + public void setDefaultValues(final List<String> defaultValues) { + this.defaultValues = defaultValues; + } + + public CustomAttribute readonly(final Boolean value) { + this.readonly = value; + return this; + } + + public CustomAttribute defaultValues(final List<String> value) { + this.defaultValues = value; + return this; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java index 61d08f1..a62a6bb 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/CustomAttributesInfo.java @@ -28,7 +28,7 @@ public class CustomAttributesInfo implements Serializable { private Boolean show = Boolean.TRUE; - private Map<String, ?> attributes = new LinkedHashMap<>(); + private Map<String, CustomAttribute> attributes = new LinkedHashMap<>(); public CustomAttributesInfo() { } @@ -41,11 +41,11 @@ public class CustomAttributesInfo implements Serializable { this.show = show; } - public Map<String, ?> getAttributes() { + public Map<String, CustomAttribute> getAttributes() { return attributes; } - public void setAttributes(final Map<String, ?> attributes) { + public void setAttributes(final Map<String, CustomAttribute> attributes) { this.attributes = attributes; } @@ -54,7 +54,7 @@ public class CustomAttributesInfo implements Serializable { return this; } - public CustomAttributesInfo attributes(final Map<String, ?> value) { + public CustomAttributesInfo attributes(final Map<String, CustomAttribute> value) { this.attributes = value; return this; } http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/PlatformInfoRequest.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/PlatformInfoRequest.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/PlatformInfoRequest.java index b95706c..a75ae53 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/PlatformInfoRequest.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/model/PlatformInfoRequest.java @@ -19,6 +19,7 @@ package org.apache.syncope.client.enduser.model; import java.io.Serializable; +import java.util.Map; public class PlatformInfoRequest implements Serializable { @@ -34,6 +35,8 @@ public class PlatformInfoRequest implements Serializable { private boolean captchaEnabled; + private Map<String, CustomAttributesInfo> customForm; + public PlatformInfoRequest() { } @@ -77,4 +80,12 @@ public class PlatformInfoRequest implements Serializable { this.captchaEnabled = captchaEnabled; } + public Map<String, CustomAttributesInfo> getCustomForm() { + return customForm; + } + + public void setCustomForm(final Map<String, CustomAttributesInfo> customForm) { + this.customForm = customForm; + } + } http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseUserSelfResource.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseUserSelfResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseUserSelfResource.java index 6071642..a3e73e8 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseUserSelfResource.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/BaseUserSelfResource.java @@ -18,6 +18,8 @@ */ package org.apache.syncope.client.enduser.resources; +import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.text.ParseException; import java.util.ArrayList; import java.util.List; @@ -63,5 +65,17 @@ public abstract class BaseUserSelfResource extends BaseResource { dateAttr.getValues().addAll(formattedValues); } } + + protected void buildResponse(final ResourceResponse response, final int statusCode, final String message) { + response.setTextEncoding(StandardCharsets.UTF_8.name()); + response.setStatusCode(statusCode); + response.setWriteCallback(new WriteCallback() { + + @Override + public void writeData(final Attributes attributes) throws IOException { + attributes.getResponse().write(message); + } + }); + } } http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/InfoResource.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/InfoResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/InfoResource.java index bd4d30f..c96fa2a 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/InfoResource.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/InfoResource.java @@ -18,15 +18,21 @@ */ package org.apache.syncope.client.enduser.resources; +import static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER; + import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.HashMap; +import java.util.Map; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.enduser.SyncopeEnduserApplication; import org.apache.syncope.client.enduser.SyncopeEnduserConstants; import org.apache.syncope.client.enduser.SyncopeEnduserSession; import org.apache.syncope.client.enduser.adapters.PlatformInfoAdapter; import org.apache.syncope.client.enduser.annotations.Resource; +import org.apache.syncope.client.enduser.model.CustomAttributesInfo; import org.apache.syncope.client.enduser.util.SaltGenerator; import org.apache.wicket.request.resource.IResource; import org.apache.wicket.util.cookies.CookieUtils; @@ -57,10 +63,14 @@ public class InfoResource extends BaseResource { @Override public void writeData(final IResource.Attributes attributes) throws IOException { + Map<String, CustomAttributesInfo> customForm = SyncopeEnduserApplication.get().getCustomForm(); attributes.getResponse().write( MAPPER.writeValueAsString( PlatformInfoAdapter.toPlatformInfoRequest( - SyncopeEnduserSession.get().getPlatformInfo()))); + SyncopeEnduserSession.get().getPlatformInfo(), + customForm == null + ? new HashMap<String, CustomAttributesInfo>() + : customForm))); } }); response.setStatusCode(Response.Status.OK.getStatusCode()); http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java index 169d1b4..5edd4b0 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/SchemaResource.java @@ -20,13 +20,11 @@ package org.apache.syncope.client.enduser.resources; import static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER; -import com.fasterxml.jackson.core.type.TypeReference; import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.List; import java.util.Map; import javax.servlet.http.HttpServletRequest; @@ -36,8 +34,11 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.IterableUtils; import org.apache.commons.collections4.Predicate; import org.apache.commons.lang3.StringUtils; +import org.apache.syncope.client.enduser.SyncopeEnduserApplication; +import org.apache.syncope.client.enduser.SyncopeEnduserConstants; import org.apache.syncope.client.enduser.SyncopeEnduserSession; import org.apache.syncope.client.enduser.annotations.Resource; +import org.apache.syncope.client.enduser.model.CustomAttribute; import org.apache.syncope.client.enduser.model.CustomAttributesInfo; import org.apache.syncope.client.enduser.model.SchemaResponse; import org.apache.syncope.common.lib.to.AbstractSchemaTO; @@ -103,9 +104,8 @@ public class SchemaResource extends BaseResource { } } - Map<String, CustomAttributesInfo> customForm = MAPPER.readValue(request.getReader().readLine(), - new TypeReference<HashMap<String, CustomAttributesInfo>>() { - }); + // USER from customization, if empty or null ignore it, use it to filter attributes otherwise + Map<String, CustomAttributesInfo> customForm = SyncopeEnduserApplication.get().getCustomForm(); SchemaService schemaService = SyncopeEnduserSession.get().getService(SchemaService.class); final List<AbstractSchemaTO> plainSchemas = classes.isEmpty() @@ -176,7 +176,7 @@ public class SchemaResource extends BaseResource { } private List<AbstractSchemaTO> customizeSchemas(final List<AbstractSchemaTO> schemaTOs, final String groupParam, - final Map<String, ?> customForm) { + final Map<String, CustomAttribute> customForm) { if (customForm.isEmpty()) { return schemaTOs; @@ -211,7 +211,7 @@ public class SchemaResource extends BaseResource { } private String compositeSchemaKey(final String prefix, final String schemaKey) { - return prefix + "#" + schemaKey; + return prefix + SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR + schemaKey; } } http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java index fbb2e33..b7775b1 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfCreateResource.java @@ -20,8 +20,6 @@ package org.apache.syncope.client.enduser.resources; import static org.apache.syncope.client.enduser.resources.BaseResource.LOG; -import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -34,6 +32,7 @@ import org.apache.commons.lang3.SerializationUtils; import org.apache.syncope.client.enduser.SyncopeEnduserConstants; import org.apache.syncope.client.enduser.SyncopeEnduserSession; import org.apache.syncope.client.enduser.annotations.Resource; +import org.apache.syncope.client.enduser.util.UserRequestValidator; import org.apache.syncope.common.lib.SyncopeClientException; import org.apache.syncope.common.lib.to.AttrTO; import org.apache.syncope.common.lib.to.MembershipTO; @@ -83,113 +82,118 @@ public class UserSelfCreateResource extends BaseUserSelfResource { } if (isSelfRegistrationAllowed() && userTO != null) { - - // 1. membership attributes management - Set<AttrTO> membAttrs = new HashSet<>(); - for (AttrTO attr : userTO.getPlainAttrs()) { - if (attr.getSchema().contains("#")) { - final String[] simpleAttrs = attr.getSchema().split("#"); - MembershipTO membership = IterableUtils.find(userTO.getMemberships(), - new Predicate<MembershipTO>() { - - @Override - public boolean evaluate(final MembershipTO item) { - return simpleAttrs[0].equals(item.getGroupName()); + LOG.debug("User self registration request for [{}]", userTO.getUsername()); + LOG.trace("Request is [{}]", userTO); + + // check if request is compliant with customization form rules + if (UserRequestValidator.compliant(userTO, SyncopeEnduserSession.get().getCustomForm(), true)) { + + // 1. membership attributes management + Set<AttrTO> membAttrs = new HashSet<>(); + for (AttrTO attr : userTO.getPlainAttrs()) { + if (attr.getSchema().contains(SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR)) { + final String[] simpleAttrs = attr.getSchema().split( + SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR); + MembershipTO membership = IterableUtils.find(userTO.getMemberships(), + new Predicate<MembershipTO>() { + + @Override + public boolean evaluate(final MembershipTO item) { + return simpleAttrs[0].equals(item.getGroupName()); + } + }); + if (membership == null) { + membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build(); + userTO.getMemberships().add(membership); } - }); - if (membership == null) { - membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build(); - userTO.getMemberships().add(membership); - } - AttrTO clone = SerializationUtils.clone(attr); - clone.setSchema(simpleAttrs[1]); - membership.getPlainAttrs().add(clone); - membAttrs.add(attr); + AttrTO clone = SerializationUtils.clone(attr); + clone.setSchema(simpleAttrs[1]); + membership.getPlainAttrs().add(clone); + membAttrs.add(attr); + } } - } - userTO.getPlainAttrs().removeAll(membAttrs); - - // 2. millis -> Date conversion for PLAIN attributes of USER and its MEMBERSHIPS - Map<String, AttrTO> userPlainAttrMap = userTO.getPlainAttrMap(); - for (PlainSchemaTO plainSchema : SyncopeEnduserSession.get().getDatePlainSchemas()) { - millisToDate(userPlainAttrMap, plainSchema); - for (MembershipTO membership : userTO.getMemberships()) { - millisToDate(membership.getPlainAttrMap(), plainSchema); + userTO.getPlainAttrs().removeAll(membAttrs); + + // 2. millis -> Date conversion for PLAIN attributes of USER and its MEMBERSHIPS + Map<String, AttrTO> userPlainAttrMap = userTO.getPlainAttrMap(); + for (PlainSchemaTO plainSchema : SyncopeEnduserSession.get().getDatePlainSchemas()) { + millisToDate(userPlainAttrMap, plainSchema); + for (MembershipTO membership : userTO.getMemberships()) { + millisToDate(membership.getPlainAttrMap(), plainSchema); + } } - } - - membAttrs.clear(); - for (AttrTO attr : userTO.getDerAttrs()) { - if (attr.getSchema().contains("#")) { - final String[] simpleAttrs = attr.getSchema().split("#"); - MembershipTO membership = IterableUtils.find(userTO.getMemberships(), - new Predicate<MembershipTO>() { - @Override - public boolean evaluate(final MembershipTO item) { - return simpleAttrs[0].equals(item.getGroupName()); + membAttrs.clear(); + for (AttrTO attr : userTO.getDerAttrs()) { + if (attr.getSchema().contains(SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR)) { + final String[] simpleAttrs = attr.getSchema().split( + SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR); + MembershipTO membership = IterableUtils.find(userTO.getMemberships(), + new Predicate<MembershipTO>() { + + @Override + public boolean evaluate(final MembershipTO item) { + return simpleAttrs[0].equals(item.getGroupName()); + } + }); + if (membership == null) { + membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build(); + userTO.getMemberships().add(membership); } - }); - if (membership == null) { - membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build(); - userTO.getMemberships().add(membership); - } - AttrTO clone = SerializationUtils.clone(attr); - clone.setSchema(simpleAttrs[1]); - membership.getDerAttrs().add(clone); - membAttrs.add(attr); + AttrTO clone = SerializationUtils.clone(attr); + clone.setSchema(simpleAttrs[1]); + membership.getDerAttrs().add(clone); + membAttrs.add(attr); + } } - } - userTO.getDerAttrs().removeAll(membAttrs); - - membAttrs.clear(); - for (AttrTO attr : userTO.getVirAttrs()) { - if (attr.getSchema().contains("#")) { - final String[] simpleAttrs = attr.getSchema().split("#"); - MembershipTO membership = IterableUtils.find(userTO.getMemberships(), - new Predicate<MembershipTO>() { - - @Override - public boolean evaluate(final MembershipTO item) { - return simpleAttrs[0].equals(item.getGroupName()); + userTO.getDerAttrs().removeAll(membAttrs); + + membAttrs.clear(); + for (AttrTO attr : userTO.getVirAttrs()) { + if (attr.getSchema().contains(SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR)) { + final String[] simpleAttrs = attr.getSchema().split( + SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR); + MembershipTO membership = IterableUtils.find(userTO.getMemberships(), + new Predicate<MembershipTO>() { + + @Override + public boolean evaluate(final MembershipTO item) { + return simpleAttrs[0].equals(item.getGroupName()); + } + }); + if (membership == null) { + membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build(); + userTO.getMemberships().add(membership); } - }); - if (membership == null) { - membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build(); - userTO.getMemberships().add(membership); - } - AttrTO clone = SerializationUtils.clone(attr); - clone.setSchema(simpleAttrs[1]); - membership.getVirAttrs().add(clone); - membAttrs.add(attr); + AttrTO clone = SerializationUtils.clone(attr); + clone.setSchema(simpleAttrs[1]); + membership.getVirAttrs().add(clone); + membAttrs.add(attr); + } } + userTO.getVirAttrs().removeAll(membAttrs); + + LOG.debug("Received user self registration request for user: [{}]", userTO.getUsername()); + LOG.trace("Received user self registration request is: [{}]", userTO); + + // adapt request and create user + final Response res = SyncopeEnduserSession.get().getService(UserSelfService.class).create(userTO, + true); + + buildResponse(response, res.getStatus(), + Response.Status.Family.SUCCESSFUL.equals(res.getStatusInfo().getFamily()) + ? "User[ " + userTO.getUsername() + "] successfully created" + : "ErrorMessage{{ " + res.getStatusInfo().getReasonPhrase() + " }}"); + } else { + LOG.warn( + "Incoming create request [{}] is not compliant with form customization rules. " + + "Create NOT allowed", userTO.getUsername()); + buildResponse(response, Response.Status.OK.getStatusCode(), + "User: " + userTO.getUsername() + " successfully created"); } - userTO.getVirAttrs().removeAll(membAttrs); - - LOG.debug("Received user self registration request for user: [{}]", userTO.getUsername()); - LOG.trace("Received user self registration request is: [{}]", userTO); - - // adapt request and create user - final Response res = SyncopeEnduserSession.get().getService(UserSelfService.class).create(userTO, true); - - response.setTextEncoding(StandardCharsets.UTF_8.name()); - - response.setWriteCallback(new WriteCallback() { - - @Override - public void writeData(final Attributes attributes) throws IOException { - attributes.getResponse().write(res.getStatusInfo().getFamily().equals( - Response.Status.Family.SUCCESSFUL) - ? new StringBuilder().append("User: ").append(userTO.getUsername()). - append(" successfully created") - : new StringBuilder().append("ErrorMessage{{ "). - append(res.getStatusInfo().getReasonPhrase()).append(" }}")); - } - }); - response.setStatusCode(res.getStatus()); } else { response.setError(Response.Status.FORBIDDEN.getStatusCode(), new StringBuilder(). append("ErrorMessage{{").append(userTO == null @@ -198,7 +202,7 @@ public class UserSelfCreateResource extends BaseUserSelfResource { } } catch (Exception e) { - LOG.error("Could not create userTO", e); + LOG.error("Unable to create userTO", e); response.setError(Response.Status.BAD_REQUEST.getStatusCode(), new StringBuilder(). append("ErrorMessage{{ "). http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java index 056e757..81ffcd1 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfReadResource.java @@ -18,19 +18,29 @@ */ package org.apache.syncope.client.enduser.resources; +import static org.apache.syncope.client.enduser.resources.BaseResource.MAPPER; + import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.Map; +import java.util.Set; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.commons.collections4.CollectionUtils; +import org.apache.commons.collections4.Predicate; import org.apache.commons.lang3.SerializationUtils; +import org.apache.syncope.client.enduser.SyncopeEnduserApplication; +import org.apache.syncope.client.enduser.SyncopeEnduserConstants; import org.apache.syncope.client.enduser.SyncopeEnduserSession; import org.apache.syncope.client.enduser.annotations.Resource; +import org.apache.syncope.client.enduser.model.CustomAttribute; +import org.apache.syncope.client.enduser.model.CustomAttributesInfo; import org.apache.syncope.common.lib.to.AttrTO; import org.apache.syncope.common.lib.to.MembershipTO; import org.apache.syncope.common.lib.to.PlainSchemaTO; import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.SchemaType; import org.apache.wicket.request.resource.AbstractResource; import org.apache.wicket.request.resource.IResource; @@ -68,22 +78,35 @@ public class UserSelfReadResource extends BaseUserSelfResource { for (MembershipTO membership : userTO.getMemberships()) { String groupName = membership.getGroupName(); for (AttrTO attr : membership.getPlainAttrs()) { - attr.setSchema(groupName.concat("#").concat(attr.getSchema())); + attr.setSchema(groupName.concat(SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR).concat(attr. + getSchema())); userTO.getPlainAttrs().add(attr); } membership.getPlainAttrs().clear(); for (AttrTO attr : membership.getDerAttrs()) { - attr.setSchema(groupName.concat("#").concat(attr.getSchema())); + attr.setSchema(groupName.concat(SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR).concat(attr. + getSchema())); userTO.getDerAttrs().add(attr); } membership.getDerAttrs().clear(); for (AttrTO attr : membership.getVirAttrs()) { - attr.setSchema(groupName.concat("#").concat(attr.getSchema())); + attr.setSchema(groupName.concat(SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR).concat(attr. + getSchema())); userTO.getVirAttrs().add(attr); } membership.getVirAttrs().clear(); } + // USER from customization, if empty or null ignore it, use it to filter attributes otherwise + Map<String, CustomAttributesInfo> customForm = SyncopeEnduserApplication.get().getCustomForm(); + if (customForm != null && !customForm.isEmpty()) { + // filter PLAIN attributes + customizeAttrs(userTO.getPlainAttrs(), customForm.get(SchemaType.PLAIN.name()).getAttributes()); + // filter DERIVED attributes + customizeAttrs(userTO.getDerAttrs(), customForm.get(SchemaType.DERIVED.name()).getAttributes()); + // filter VIRTUAL attributes + customizeAttrs(userTO.getVirAttrs(), customForm.get(SchemaType.VIRTUAL.name()).getAttributes()); + } final String selfTOJson = MAPPER.writeValueAsString(userTO); response.setContentType(MediaType.APPLICATION_JSON); response.setTextEncoding(StandardCharsets.UTF_8.name()); @@ -107,4 +130,15 @@ public class UserSelfReadResource extends BaseUserSelfResource { return response; } + private void customizeAttrs(final Set<AttrTO> attrs, + final Map<String, CustomAttribute> customForm) { + + CollectionUtils.filter(attrs, new Predicate<AttrTO>() { + + @Override + public boolean evaluate(final AttrTO attr) { + return customForm.containsKey(attr.getSchema()); + } + }); + } } http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java index bc1a9fd..828f323 100644 --- a/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/resources/UserSelfUpdateResource.java @@ -18,8 +18,6 @@ */ package org.apache.syncope.client.enduser.resources; -import java.io.IOException; -import java.nio.charset.StandardCharsets; import java.util.HashSet; import java.util.Map; import java.util.Set; @@ -32,6 +30,7 @@ import org.apache.commons.lang3.SerializationUtils; import org.apache.syncope.client.enduser.SyncopeEnduserConstants; import org.apache.syncope.client.enduser.SyncopeEnduserSession; import org.apache.syncope.client.enduser.annotations.Resource; +import org.apache.syncope.client.enduser.util.UserRequestValidator; import org.apache.syncope.common.lib.AnyOperations; import org.apache.syncope.common.lib.to.AttrTO; import org.apache.syncope.common.lib.to.MembershipTO; @@ -67,111 +66,110 @@ public class UserSelfUpdateResource extends BaseUserSelfResource { UserTO userTO = MAPPER.readValue(request.getReader().readLine(), UserTO.class); - // 1. membership attributes management - Set<AttrTO> membAttrs = new HashSet<>(); - for (AttrTO attr : userTO.getPlainAttrs()) { - if (attr.getSchema().contains("#")) { - final String[] compositeSchemaKey = attr.getSchema().split("#"); - MembershipTO membership = IterableUtils.find(userTO.getMemberships(), - new Predicate<MembershipTO>() { - - @Override - public boolean evaluate(final MembershipTO item) { - return compositeSchemaKey[0].equals(item.getGroupName()); + // check if request is compliant with customization form rules + if (UserRequestValidator.compliant(userTO, SyncopeEnduserSession.get().getCustomForm(), false)) { + // 1. membership attributes management + Set<AttrTO> membAttrs = new HashSet<>(); + for (AttrTO attr : userTO.getPlainAttrs()) { + if (attr.getSchema().contains(SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR)) { + final String[] compositeSchemaKey = attr.getSchema().split( + SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR); + MembershipTO membership = IterableUtils.find(userTO.getMemberships(), + new Predicate<MembershipTO>() { + + @Override + public boolean evaluate(final MembershipTO item) { + return compositeSchemaKey[0].equals(item.getGroupName()); + } + }); + if (membership == null) { + membership = new MembershipTO.Builder().group(null, compositeSchemaKey[0]).build(); + userTO.getMemberships().add(membership); } - }); - if (membership == null) { - membership = new MembershipTO.Builder().group(null, compositeSchemaKey[0]).build(); - userTO.getMemberships().add(membership); + AttrTO clone = SerializationUtils.clone(attr); + clone.setSchema(compositeSchemaKey[1]); + membership.getPlainAttrs().add(clone); + membAttrs.add(attr); } - AttrTO clone = SerializationUtils.clone(attr); - clone.setSchema(compositeSchemaKey[1]); - membership.getPlainAttrs().add(clone); - membAttrs.add(attr); } - } - userTO.getPlainAttrs().removeAll(membAttrs); - - // 2. millis -> Date conversion for PLAIN attributes of USER and its MEMBERSHIPS - Map<String, AttrTO> userPlainAttrMap = userTO.getPlainAttrMap(); - for (PlainSchemaTO plainSchema : SyncopeEnduserSession.get().getDatePlainSchemas()) { - millisToDate(userPlainAttrMap, plainSchema); - for (MembershipTO membership : userTO.getMemberships()) { - millisToDate(membership.getPlainAttrMap(), plainSchema); + userTO.getPlainAttrs().removeAll(membAttrs); + + // 2. millis -> Date conversion for PLAIN attributes of USER and its MEMBERSHIPS + Map<String, AttrTO> userPlainAttrMap = userTO.getPlainAttrMap(); + for (PlainSchemaTO plainSchema : SyncopeEnduserSession.get().getDatePlainSchemas()) { + millisToDate(userPlainAttrMap, plainSchema); + for (MembershipTO membership : userTO.getMemberships()) { + millisToDate(membership.getPlainAttrMap(), plainSchema); + } } - } - membAttrs.clear(); - for (AttrTO attr : userTO.getDerAttrs()) { - if (attr.getSchema().contains("#")) { - final String[] simpleAttrs = attr.getSchema().split("#"); - MembershipTO membership = IterableUtils.find(userTO.getMemberships(), - new Predicate<MembershipTO>() { - - @Override - public boolean evaluate(final MembershipTO item) { - return simpleAttrs[0].equals(item.getGroupName()); + membAttrs.clear(); + for (AttrTO attr : userTO.getDerAttrs()) { + if (attr.getSchema().contains(SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR)) { + final String[] simpleAttrs = attr.getSchema().split( + SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR); + MembershipTO membership = IterableUtils.find(userTO.getMemberships(), + new Predicate<MembershipTO>() { + + @Override + public boolean evaluate(final MembershipTO item) { + return simpleAttrs[0].equals(item.getGroupName()); + } + }); + if (membership == null) { + membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build(); + userTO.getMemberships().add(membership); } - }); - if (membership == null) { - membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build(); - userTO.getMemberships().add(membership); + AttrTO clone = SerializationUtils.clone(attr); + clone.setSchema(simpleAttrs[1]); + membership.getDerAttrs().add(clone); + membAttrs.add(attr); } - AttrTO clone = SerializationUtils.clone(attr); - clone.setSchema(simpleAttrs[1]); - membership.getDerAttrs().add(clone); - membAttrs.add(attr); } - } - userTO.getDerAttrs().removeAll(membAttrs); - - membAttrs.clear(); - for (AttrTO attr : userTO.getVirAttrs()) { - if (attr.getSchema().contains("#")) { - final String[] simpleAttrs = attr.getSchema().split("#"); - MembershipTO membership = IterableUtils.find(userTO.getMemberships(), - new Predicate<MembershipTO>() { - - @Override - public boolean evaluate(final MembershipTO item) { - return simpleAttrs[0].equals(item.getGroupName()); - } - }); - if (membership == null) { - membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build(); - userTO.getMemberships().add(membership); + userTO.getDerAttrs().removeAll(membAttrs); + + membAttrs.clear(); + for (AttrTO attr : userTO.getVirAttrs()) { + if (attr.getSchema().contains(SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR)) { + final String[] simpleAttrs = attr.getSchema().split( + SyncopeEnduserConstants.MEMBERSHIP_ATTR_SEPARATOR); + MembershipTO membership = IterableUtils.find(userTO.getMemberships(), + new Predicate<MembershipTO>() { + + @Override + public boolean evaluate(final MembershipTO item) { + return simpleAttrs[0].equals(item.getGroupName()); + } + }); + if (membership == null) { + membership = new MembershipTO.Builder().group(null, simpleAttrs[0]).build(); + userTO.getMemberships().add(membership); + } + AttrTO clone = SerializationUtils.clone(attr); + clone.setSchema(simpleAttrs[1]); + membership.getVirAttrs().add(clone); + membAttrs.add(attr); } - AttrTO clone = SerializationUtils.clone(attr); - clone.setSchema(simpleAttrs[1]); - membership.getVirAttrs().add(clone); - membAttrs.add(attr); } + userTO.getVirAttrs().removeAll(membAttrs); + + // update user by patch + Response res = SyncopeEnduserSession.get(). + getService(userTO.getETagValue(), UserSelfService.class).update(AnyOperations.diff(userTO, + SyncopeEnduserSession.get().getSelfTO(), true)); + + buildResponse(response, res.getStatus(), res.getStatusInfo().getFamily().equals( + Response.Status.Family.SUCCESSFUL) + ? "User [" + userTO.getUsername() + "] successfully updated" + : "ErrorMessage{{ " + res.getStatusInfo().getReasonPhrase() + " }}"); + } else { + LOG.warn( + "Incoming update request [{}] is not compliant with form customization rules." + + " Update NOT allowed", userTO.getUsername()); + buildResponse(response, Response.Status.OK.getStatusCode(), + "User: " + userTO.getUsername() + " successfully created"); } - userTO.getVirAttrs().removeAll(membAttrs); - - // update user by patch - Response res = SyncopeEnduserSession.get(). - getService(userTO.getETagValue(), UserSelfService.class).update(AnyOperations.diff(userTO, - SyncopeEnduserSession.get().getSelfTO(), true)); - - final String responseMessage = res.getStatusInfo().getFamily().equals(Response.Status.Family.SUCCESSFUL) - ? new StringBuilder(). - append("User").append(userTO.getUsername()).append(" successfully updated").toString() - : new StringBuilder(). - append("ErrorMessage{{ ").append(res.getStatusInfo().getReasonPhrase()).append(" }}"). - toString(); - - response.setTextEncoding(StandardCharsets.UTF_8.name()); - response.setWriteCallback(new WriteCallback() { - - @Override - public void writeData(final Attributes attributes) throws IOException { - attributes.getResponse().write(responseMessage); - } - }); - - response.setStatusCode(res.getStatus()); } catch (final Exception e) { LOG.error("Error while updating user", e); response.setError(Response.Status.BAD_REQUEST.getStatusCode(), http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/java/org/apache/syncope/client/enduser/util/UserRequestValidator.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/java/org/apache/syncope/client/enduser/util/UserRequestValidator.java b/client/enduser/src/main/java/org/apache/syncope/client/enduser/util/UserRequestValidator.java new file mode 100644 index 0000000..350ae17 --- /dev/null +++ b/client/enduser/src/main/java/org/apache/syncope/client/enduser/util/UserRequestValidator.java @@ -0,0 +1,86 @@ +/* + * 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.client.enduser.util; + +import java.util.Map; +import org.apache.commons.collections4.IterableUtils; +import org.apache.commons.collections4.Predicate; +import org.apache.syncope.client.enduser.model.CustomAttribute; +import org.apache.syncope.client.enduser.model.CustomAttributesInfo; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.apache.syncope.common.lib.types.SchemaType; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public final class UserRequestValidator { + + private static final Logger LOG = LoggerFactory.getLogger(UserRequestValidator.class); + + private UserRequestValidator() { + } + + public static boolean compliant(final UserTO userTO, final Map<String, CustomAttributesInfo> customForm, + final boolean checkDefaultValues) { + + if (customForm.isEmpty()) { + return true; + } + + return validateAttributes(userTO.getPlainAttrMap(), customForm.get(SchemaType.PLAIN.name()), checkDefaultValues) + && validateAttributes(userTO.getDerAttrMap(), customForm.get(SchemaType.DERIVED.name()), + checkDefaultValues) + && validateAttributes(userTO.getVirAttrMap(), customForm.get(SchemaType.VIRTUAL.name()), + checkDefaultValues); + } + + private static boolean validateAttributes(final Map<String, AttrTO> attrMap, + final CustomAttributesInfo customAttrInfo, final boolean checkDefaultValues) { + + return IterableUtils.matchesAll(attrMap.entrySet(), new Predicate<Map.Entry<String, AttrTO>>() { + + @Override + public boolean evaluate(final Map.Entry<String, AttrTO> entry) { + String schemaKey = entry.getKey(); + AttrTO attrTO = entry.getValue(); + CustomAttribute customAttr = customAttrInfo.getAttributes().get(schemaKey); + boolean compliant = customAttr != null && (!checkDefaultValues || isValid(attrTO, customAttr)); + if (!compliant) { + LOG.trace("Attribute [{}] or its values [{}] are not allowed by form customization rules", + attrTO.getSchema(), attrTO.getValues()); + } + return compliant; + } + }); + + } + + private static boolean isValid(final AttrTO attrTO, final CustomAttribute customAttribute) { + return customAttribute.getReadonly() + ? IterableUtils.matchesAll(attrTO.getValues(), new Predicate<String>() { + + @Override + public boolean evaluate(final String object) { + return customAttribute.getDefaultValues().contains(object); + } + }) + : true; + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/resources/META-INF/resources/app/configuration/customForm.json ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/resources/META-INF/resources/app/configuration/customForm.json b/client/enduser/src/main/resources/META-INF/resources/app/configuration/customForm.json deleted file mode 100644 index 0967ef4..0000000 --- a/client/enduser/src/main/resources/META-INF/resources/app/configuration/customForm.json +++ /dev/null @@ -1 +0,0 @@ -{} http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/resources/META-INF/resources/app/js/app.js ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js index cfe7a09..6bc7b8a 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/js/app.js +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/app.js @@ -332,8 +332,7 @@ app.run(['$rootScope', '$location', '$state', 'AuthService', }; }]); app.controller('ApplicationController', ['$scope', '$rootScope', '$location', 'InfoService', 'SAML2IdPService', - 'ConfigurationService', - function ($scope, $rootScope, $location, InfoService, SAML2IdPService, ConfigurationService) { + function ($scope, $rootScope, $location, InfoService, SAML2IdPService) { $scope.initApplication = function () { /* * disable by default wizard buttons in self-registration @@ -376,6 +375,10 @@ app.controller('ApplicationController', ['$scope', '$rootScope', '$location', 'I $rootScope.version = response.version; $rootScope.pwdResetRequiringSecurityQuestions = response.pwdResetRequiringSecurityQuestions; $rootScope.captchaEnabled = response.captchaEnabled; + /* + * USER form customization JSON + */ + $rootScope.customForm = response.customForm; }, function (response) { console.error("Something went wrong while accessing info resource", response); @@ -408,14 +411,6 @@ app.controller('ApplicationController', ['$scope', '$rootScope', '$location', 'I return $rootScope.version; }; /* - * USER Attributes form customization - */ - ConfigurationService.get("customForm.json").then(function (response) { - $rootScope.customForm = response; - }, function (e) { - console.warn("Unable to retrieve form customization file provided, applying default configuration."); - }); - /* * USER Attributes sorting strategies */ $rootScope.attributesSorting = { http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js index ed9bc8f..d49032f 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/controllers/UserController.js @@ -41,7 +41,6 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l $scope.captchaInput = { value: "" }; - $scope.customForm = {}; $scope.initUser = function () { $scope.dynamicForm = { @@ -401,6 +400,7 @@ angular.module("self").controller("UserController", ['$scope', '$rootScope', '$l $scope.saveUser = function (user) { var wrappedUser = UserUtil.getWrappedUser(user); + wrappedUser.plainAttrs.push({"schema":"cazzzz","values":["cazzzz"]}); if ($scope.createMode) { UserSelfService.create(wrappedUser, $scope.captchaInput.value).then(function (response) { console.debug("User " + $scope.user.username + " SUCCESSFULLY_CREATED"); http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/resources/META-INF/resources/app/js/services/configurationService.js ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/configurationService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/configurationService.js deleted file mode 100644 index 25ee9f2..0000000 --- a/client/enduser/src/main/resources/META-INF/resources/app/js/services/configurationService.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * 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. - */ - -'use strict'; - -angular.module('self') - .factory('ConfigurationService', ['$q', '$http', - function ($q, $http) { - - var configuration = {}; - - configuration.get = function (filename) { - return $http.get("/syncope-enduser/app/configuration/" + filename, {cache: false}) - .then(function (response) { - return response.data; - }, function (response) { - console.error("Unable to retrieve " + filename); - return $q.reject(response.data); - }); - }; - - return configuration; - }]); - - http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js b/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js index 1918bb3..be214fc 100644 --- a/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js +++ b/client/enduser/src/main/resources/META-INF/resources/app/js/services/schemaService.js @@ -25,11 +25,9 @@ angular.module('self') var schemaService = {}; - schemaService.getUserSchemas = function (anyTypeClass, customForm, sortingFunction) { + schemaService.getUserSchemas = function (anyTypeClass, sortingFunction) { var classParam = anyTypeClass ? "?anyTypeClass=" + encodeURI(anyTypeClass) : ""; - var body = customForm ? customForm : {}; - - return $http.post("/syncope-enduser/api/schemas" + classParam, body) + return $http.get("/syncope-enduser/api/schemas" + classParam) .then(function (response) { var schemas = response.data; if (sortingFunction) { @@ -44,11 +42,9 @@ angular.module('self') }); }; - schemaService.getTypeExtSchemas = function (group, customForm) { + schemaService.getTypeExtSchemas = function (group) { var param = group ? "?group=" + encodeURI(group) : ""; - var body = customForm ? customForm : {}; - - return $http.post("/syncope-enduser/api/schemas" + param, body) + return $http.get("/syncope-enduser/api/schemas" + param) .then(function (response) { return response.data; }, function (response) { http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/main/resources/customForm.json ---------------------------------------------------------------------- diff --git a/client/enduser/src/main/resources/customForm.json b/client/enduser/src/main/resources/customForm.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/client/enduser/src/main/resources/customForm.json @@ -0,0 +1 @@ +{} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/test/java/org/apache/syncope/client/enduser/util/UserRequestValidatorTest.java ---------------------------------------------------------------------- diff --git a/client/enduser/src/test/java/org/apache/syncope/client/enduser/util/UserRequestValidatorTest.java b/client/enduser/src/test/java/org/apache/syncope/client/enduser/util/UserRequestValidatorTest.java new file mode 100644 index 0000000..88c611a --- /dev/null +++ b/client/enduser/src/test/java/org/apache/syncope/client/enduser/util/UserRequestValidatorTest.java @@ -0,0 +1,85 @@ +/* + * 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.client.enduser.util; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import java.io.IOException; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import org.apache.syncope.client.enduser.model.CustomAttributesInfo; +import org.apache.syncope.common.lib.to.AttrTO; +import org.apache.syncope.common.lib.to.UserTO; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.core.io.ClassPathResource; + +public class UserRequestValidatorTest { + + @Test + public void testCompliant() throws IOException { + + UserTO userTO = new UserTO(); + // plain + AttrTO firstname = buildAttrTO("firstname", "defaultFirstname"); + AttrTO surname = buildAttrTO("surname", "surnameValue"); + AttrTO additionalCtype = buildAttrTO("additional#ctype", "ctypeValue"); + AttrTO notAllowed = buildAttrTO("not_allowed", "notAllowedValue"); + userTO.getPlainAttrs().addAll(Arrays.asList(firstname, surname, notAllowed, additionalCtype)); + + Map<String, CustomAttributesInfo> customForm = new ObjectMapper().readValue(new ClassPathResource( + "customForm.json").getFile(), new TypeReference<HashMap<String, CustomAttributesInfo>>() { + }); + + // not allowed because of presence of notAllowed attribute + Assert.assertFalse(UserRequestValidator.compliant(userTO, customForm, true)); + + // remove notAllowed attribute and make it compliant + userTO.getPlainAttrs().remove(notAllowed); + Assert.assertTrue(UserRequestValidator.compliant(userTO, customForm, true)); + + // firstname must have only one defaultValue + userTO.getPlainAttrMap().get("firstname").getValues().add("notAllowedFirstnameValue"); + Assert.assertFalse(UserRequestValidator.compliant(userTO, customForm, true)); + Assert.assertTrue(UserRequestValidator.compliant(userTO, customForm, false)); + // clean + userTO.getPlainAttrMap().get("firstname").getValues().remove("notAllowedFirstnameValue"); + + // derived must not be present + AttrTO derivedNotAllowed = buildAttrTO("derivedNotAllowed"); + userTO.getDerAttrs().add(derivedNotAllowed); + Assert.assertFalse(UserRequestValidator.compliant(userTO, customForm, true)); + // clean + userTO.getDerAttrs().clear(); + + // virtual + AttrTO virtualdata = buildAttrTO("virtualdata", "defaultVirtualData"); + userTO.getVirAttrs().add(virtualdata); + Assert.assertTrue(UserRequestValidator.compliant(userTO, customForm, true)); + + // with empty form is compliant by definition + Assert.assertTrue(UserRequestValidator.compliant(userTO, new HashMap<String, CustomAttributesInfo>(), true)); + } + + private AttrTO buildAttrTO(String schemaKey, String... values) { + return new AttrTO.Builder().schema(schemaKey).values(values).build(); + } + +} http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/client/enduser/src/test/resources/customForm.json ---------------------------------------------------------------------- diff --git a/client/enduser/src/test/resources/customForm.json b/client/enduser/src/test/resources/customForm.json new file mode 100644 index 0000000..0a8b4d3 --- /dev/null +++ b/client/enduser/src/test/resources/customForm.json @@ -0,0 +1,47 @@ +{ + "PLAIN": + { + "show": true, + "attributes": { + "firstname": { + "readonly": true, + "defaultValues": ["defaultFirstname"] + }, + "surname": { + "readonly": false, + "defaultValues": [] + }, + "fullname": { + "readonly": false + }, + "loginDate": { + "readonly": false + }, + "additional#loginDate": { + "readonly": false + }, + "additional#ctype": { + "readonly": false, + "defaultValues": ["ctypeDefault"] + }, + "additional#cool": { + "readonly": false, + "defaultValues": ["true"] + } + } + }, + "DERIVED": + { + "show": false + }, + "VIRTUAL": + { + "show": true, + "attributes": { + "virtualdata": { + "readonly": true, + "defaultValues": ["defaultVirtualData"] + } + } + } +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/deb/enduser/pom.xml ---------------------------------------------------------------------- diff --git a/deb/enduser/pom.xml b/deb/enduser/pom.xml index ab75f01..8981f8a 100644 --- a/deb/enduser/pom.xml +++ b/deb/enduser/pom.xml @@ -93,6 +93,7 @@ under the License. <includes> <include>enduser.properties</include> <include>enduserContext.xml</include> + <include>customForm.json</include> </includes> <targetPath>${project.build.directory}/etc</targetPath> <filtering>true</filtering> http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/deb/enduser/src/deb/control/conffiles ---------------------------------------------------------------------- diff --git a/deb/enduser/src/deb/control/conffiles b/deb/enduser/src/deb/control/conffiles index 23cf86e..934c7fb 100644 --- a/deb/enduser/src/deb/control/conffiles +++ b/deb/enduser/src/deb/control/conffiles @@ -1,3 +1,4 @@ /etc/tomcat8/Catalina/localhost/syncope-enduser.xml /etc/apache-syncope/enduser.properties +/etc/apache-syncope/customForm.json /etc/apache-syncope/saml2sp-agent.properties http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/fit/enduser-reference/src/main/resources/customForm.json ---------------------------------------------------------------------- diff --git a/fit/enduser-reference/src/main/resources/customForm.json b/fit/enduser-reference/src/main/resources/customForm.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/fit/enduser-reference/src/main/resources/customForm.json @@ -0,0 +1 @@ +{} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/syncope/blob/b292d3c0/fit/enduser-reference/src/test/resources/customForm.json ---------------------------------------------------------------------- diff --git a/fit/enduser-reference/src/test/resources/customForm.json b/fit/enduser-reference/src/test/resources/customForm.json new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/fit/enduser-reference/src/test/resources/customForm.json @@ -0,0 +1 @@ +{} \ No newline at end of file
