This is an automated email from the ASF dual-hosted git repository. rombert pushed a commit to annotated tag org.apache.sling.jcr.jackrabbit.usermanager-2.0.2-incubator in repository https://gitbox.apache.org/repos/asf/sling-org-apache-sling-jcr-jackrabbit-usermanager.git
commit 8b60a665ec27a968f81036556048065dae9a63cb Author: Felix Meschberger <[email protected]> AuthorDate: Fri Feb 27 09:19:41 2009 +0000 SLING-875 add new jackrabbit-usermanager module (thanks Eric for providing) git-svn-id: https://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/jackrabbit-usermanager@748454 13f79535-47bb-0310-9956-ffa450edef68 --- LICENSE | 202 +++++++ NOTICE | 5 + README.txt | 37 ++ pom.xml | 128 +++++ .../post/AbstractAuthorizableOperation.java | 634 +++++++++++++++++++++ .../usermanager/post/ChangePasswordOperation.java | 103 ++++ .../usermanager/post/CreateGroupOperation.java | 97 ++++ .../usermanager/post/CreateUserOperation.java | 174 ++++++ .../post/DeleteAuthorizableOperation.java | 73 +++ .../post/UpdateAuthorizableOperation.java | 81 +++ .../usermanager/post/impl/DateParser.java | 138 +++++ .../usermanager/post/impl/RequestProperty.java | 257 +++++++++ .../usermanager/resource/AuthorizableResource.java | 119 ++++ .../resource/AuthorizableResourceProvider.java | 220 +++++++ .../usermanager/resource/AuthorizableValueMap.java | 319 +++++++++++ src/main/resources/META-INF/LICENSE | 202 +++++++ src/main/resources/META-INF/NOTICE | 5 + .../OSGI-INF/metatype/metatype.properties | 36 ++ 18 files changed, 2830 insertions(+) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..75b5248 --- /dev/null +++ b/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..9f4ac5f --- /dev/null +++ b/NOTICE @@ -0,0 +1,5 @@ +Apache Sling Jackrabbit UserManager Support +Copyright 2008-2009 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/README.txt b/README.txt new file mode 100644 index 0000000..046bab8 --- /dev/null +++ b/README.txt @@ -0,0 +1,37 @@ +Apache Sling Jackrabbit UserManager Support + +Provides ResourceProvider and SlingPostOperations for the Jackrabbit UserManager. + +Disclaimer +========== +Apache Sling is an effort undergoing incubation at The Apache Software Foundation (ASF), +sponsored by the Apache Jackrabbit PMC. Incubation is required of all newly accepted +projects until a further review indicates that the infrastructure, communications, +and decision making process have stabilized in a manner consistent with other +successful ASF projects. While incubation status is not necessarily a reflection of +the completeness or stability of the code, it does indicate that the project has yet +to be fully endorsed by the ASF. + +Getting Started +=============== + +This component uses a Maven 2 (http://maven.apache.org/) build +environment. It requires a Java 5 JDK (or higher) and Maven (http://maven.apache.org/) +2.0.7 or later. We recommend to use the latest Maven version. + +If you have Maven 2 installed, you can compile and +package the jar using the following command: + + mvn package + +See the Maven 2 documentation for other build features. + +The latest source code for this component is available in the +Subversion (http://subversion.tigris.org/) source repository of +the Apache Software Foundation. If you have Subversion installed, +you can checkout the latest source using the following command: + + svn checkout http://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/jackrabbit-usermanager + +See the Subversion documentation for other source control features. + diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..a39f834 --- /dev/null +++ b/pom.xml @@ -0,0 +1,128 @@ +<?xml version="1.0" encoding="ISO-8859-1"?> +<!-- + 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. +--> +<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> + + <modelVersion>4.0.0</modelVersion> + <parent> + <groupId>org.apache.sling</groupId> + <artifactId>sling</artifactId> + <version>5-incubator-SNAPSHOT</version> + <relativePath>../../../parent/pom.xml</relativePath> + </parent> + + <artifactId>org.apache.sling.jcr.jackrabbit.usermanager</artifactId> + <packaging>bundle</packaging> + <version>2.0.0-incubator-SNAPSHOT</version> + <name>Apache Sling Jackrabbit UserManager Support</name> + <description> + Provides ResourceProvider and SlingPostOperations for the Jackrabbit + UserManager. + </description> + + <scm> + <connection> + scm:svn:http://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/jackrabbit-usermanager + </connection> + <developerConnection> + scm:svn:https://svn.apache.org/repos/asf/incubator/sling/trunk/bundles/jcr/jackrabbit-usermanager + </developerConnection> + <url> + http://svn.apache.org/viewvc/incubator/sling/trunk/bundles/jcr/jackrabbit-usermanager + </url> + </scm> + + <build> + <plugins> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-scr-plugin</artifactId> + </plugin> + <plugin> + <groupId>org.apache.felix</groupId> + <artifactId>maven-bundle-plugin</artifactId> + <extensions>true</extensions> + <configuration> + <instructions> + <Private-Package> + org.apache.sling.jackrabbit.usermanager.* + </Private-Package> + <Sling-Initial-Content></Sling-Initial-Content> + <Sling-Nodetypes></Sling-Nodetypes> + </instructions> + </configuration> + </plugin> + </plugins> + </build> + + <reporting> + <plugins> + <plugin> + <groupId>org.apache.maven.plugins</groupId> + <artifactId>maven-javadoc-plugin</artifactId> + <configuration> + <!-- No javadocs --> + <excludePackageNames> + org.apache.sling.jackrabbit.usermanager.post.impl + </excludePackageNames> + </configuration> + </plugin> + </plugins> + </reporting> + + <dependencies> + <dependency> + <groupId>javax.servlet</groupId> + <artifactId>servlet-api</artifactId> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.api</artifactId> + <version>2.0.3-incubator-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.servlets.post</artifactId> + <version>2.0.3-incubator-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.jcr.jackrabbit.api</artifactId> + <version>2.0.3-incubator-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>org.apache.sling</groupId> + <artifactId>org.apache.sling.jcr.base</artifactId> + <version>2.0.3-incubator-SNAPSHOT</version> + </dependency> + <dependency> + <groupId>org.apache.jackrabbit</groupId> + <artifactId>jackrabbit-api</artifactId> + <version>1.5.0</version> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.osgi.core</artifactId> + </dependency> + <dependency> + <groupId>org.apache.felix</groupId> + <artifactId>org.osgi.compendium</artifactId> + </dependency> + </dependencies> +</project> \ No newline at end of file diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/post/AbstractAuthorizableOperation.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/AbstractAuthorizableOperation.java new file mode 100644 index 0000000..06f6577 --- /dev/null +++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/AbstractAuthorizableOperation.java @@ -0,0 +1,634 @@ +/* + * 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.sling.jackrabbit.usermanager.post; + +import java.io.UnsupportedEncodingException; +import java.security.NoSuchAlgorithmException; +import java.util.Calendar; +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import javax.jcr.PropertyType; +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; +import javax.jcr.ValueFactory; +import javax.servlet.ServletException; + +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.jackrabbit.api.security.user.Group; +import org.apache.jackrabbit.util.Text; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.request.RequestParameter; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.servlets.HtmlResponse; +import org.apache.sling.commons.osgi.OsgiUtil; +import org.apache.sling.jackrabbit.usermanager.post.impl.DateParser; +import org.apache.sling.jackrabbit.usermanager.post.impl.RequestProperty; +import org.apache.sling.jackrabbit.usermanager.resource.AuthorizableResourceProvider; +import org.apache.sling.servlets.post.AbstractSlingPostOperation; +import org.apache.sling.servlets.post.Modification; +import org.apache.sling.servlets.post.SlingPostConstants; +import org.osgi.service.component.ComponentContext; + + +/** + * Base class for operations that do work on authorizable resources + */ +public abstract class AbstractAuthorizableOperation extends AbstractSlingPostOperation { + + /** + * @scr.property values.0="EEE MMM dd yyyy HH:mm:ss 'GMT'Z" + * values.1="yyyy-MM-dd'T'HH:mm:ss.SSSZ" + * values.2="yyyy-MM-dd'T'HH:mm:ss" values.3="yyyy-MM-dd" + * values.4="dd.MM.yyyy HH:mm:ss" values.5="dd.MM.yyyy" + */ + private static final String PROP_DATE_FORMAT = "servlet.post.dateFormats"; + + private DateParser dateParser; + + + /** + * To be used for the encryption. E.g. for passwords in + * {@link javax.jcr.SimpleCredentials#getPassword()} SimpleCredentials} + * @scr.property valueRef="DEFAULT_PASSWORD_DIGEST_ALGORITHM" + */ + private static final String PROP_PASSWORD_DIGEST_ALGORITHM = "password.digest.algorithm"; + private static final String DEFAULT_PASSWORD_DIGEST_ALGORITHM = "sha1"; + private String passwordDigestAlgoritm = null; + + // ---------- SCR Integration ---------------------------------------------- + + protected void activate(ComponentContext context) { + Dictionary<?, ?> props = context.getProperties(); + + dateParser = new DateParser(); + String[] dateFormats = OsgiUtil.toStringArray(props.get(PROP_DATE_FORMAT)); + for (String dateFormat : dateFormats) { + dateParser.register(dateFormat); + } + Object propValue = props.get(PROP_PASSWORD_DIGEST_ALGORITHM); + if (propValue instanceof String) { + passwordDigestAlgoritm = (String)propValue; + } else { + passwordDigestAlgoritm = DEFAULT_PASSWORD_DIGEST_ALGORITHM; + } + } + + protected void deactivate(ComponentContext context) { + dateParser = null; + passwordDigestAlgoritm = null; + } + + protected String digestPassword(String pwd) throws IllegalArgumentException { + try { + StringBuffer password = new StringBuffer(); + password.append("{").append(passwordDigestAlgoritm).append("}"); + password.append(Text.digest(passwordDigestAlgoritm, pwd.getBytes("UTF-8"))); + return password.toString(); + } catch (NoSuchAlgorithmException e) { + throw new IllegalArgumentException(e.toString()); + } catch (UnsupportedEncodingException e) { + throw new IllegalArgumentException(e.toString()); + } + } + + + /** + * Update the group membership based on the ":member" request + * parameters. If the ":member" value ends with @Delete it is removed + * from the group membership, otherwise it is added to the group membership. + * + * @param request + * @param authorizable + * @throws RepositoryException + */ + protected void updateGroupMembership(SlingHttpServletRequest request, + Authorizable authorizable, List<Modification> changes) throws RepositoryException { + if (authorizable.isGroup()) { + Group group = ((Group)authorizable); + String groupPath = AuthorizableResourceProvider.SYSTEM_USER_MANAGER_GROUP_PREFIX + group.getID(); + + ResourceResolver resolver = request.getResourceResolver(); + Resource baseResource = request.getResource(); + boolean changed = false; + + //first remove any members posted as ":member@Delete" + String[] membersToDelete = request.getParameterValues(SlingPostConstants.RP_PREFIX + "member" + SlingPostConstants.SUFFIX_DELETE); + if (membersToDelete != null) { + for (String member : membersToDelete) { + Resource res = resolver.getResource(baseResource, member); + if (res != null) { + Authorizable memberAuthorizable = res.adaptTo(Authorizable.class); + if (memberAuthorizable != null) { + group.removeMember(memberAuthorizable); + changed = true; + } + } + + } + } + + //second add any members posted as ":member" + String[] membersToAdd = request.getParameterValues(SlingPostConstants.RP_PREFIX + "member"); + if (membersToAdd != null) { + for (String member : membersToAdd) { + Resource res = resolver.getResource(baseResource, member); + if (res != null) { + Authorizable memberAuthorizable = res.adaptTo(Authorizable.class); + if (memberAuthorizable != null) { + group.addMember(memberAuthorizable); + changed = true; + } + } + } + } + + if (changed) { + //add an entry to the changes list to record the membership change + changes.add(Modification.onModified(groupPath + "/members")); + } + } + } + + + + // ------ The methods below are based on the private methods from the ModifyOperation class ----- + + /** + * Collects the properties that form the content to be written back to the + * repository. + * + * NOTE: In the returned map, the key is the property name not a path. + * + * @throws RepositoryException if a repository error occurs + * @throws ServletException if an internal error occurs + */ + protected Map<String, RequestProperty> collectContent( + SlingHttpServletRequest request, HtmlResponse response) { + + boolean requireItemPrefix = requireItemPathPrefix(request); + + // walk the request parameters and collect the properties + Map<String, RequestProperty> reqProperties = new HashMap<String, RequestProperty>(); + for (Map.Entry<String, RequestParameter[]> e : request.getRequestParameterMap().entrySet()) { + final String paramName = e.getKey(); + + // do not store parameters with names starting with sling:post + if (paramName.startsWith(SlingPostConstants.RP_PREFIX)) { + continue; + } + // SLING-298: skip form encoding parameter + if (paramName.equals("_charset_")) { + continue; + } + // skip parameters that do not start with the save prefix + if (requireItemPrefix && !hasItemPathPrefix(paramName)) { + continue; + } + + // ensure the paramName is an absolute property name + String propPath; + if (paramName.startsWith("./")) { + propPath = paramName.substring(2); + } else { + propPath = paramName; + } + if (propPath.indexOf('/') != -1) { + //only one path segment is valid here, so this paramter can't be used. + continue; //skip it. + } + + // @TypeHint example + // <input type="text" name="./age" /> + // <input type="hidden" name="./age@TypeHint" value="long" /> + // causes the setProperty using the 'long' property type + if (propPath.endsWith(SlingPostConstants.TYPE_HINT_SUFFIX)) { + RequestProperty prop = getOrCreateRequestProperty( + reqProperties, propPath, + SlingPostConstants.TYPE_HINT_SUFFIX); + + final RequestParameter[] rp = e.getValue(); + if (rp.length > 0) { + prop.setTypeHintValue(rp[0].getString()); + } + + continue; + } + + // @DefaultValue + if (propPath.endsWith(SlingPostConstants.DEFAULT_VALUE_SUFFIX)) { + RequestProperty prop = getOrCreateRequestProperty( + reqProperties, propPath, + SlingPostConstants.DEFAULT_VALUE_SUFFIX); + + prop.setDefaultValues(e.getValue()); + + continue; + } + + // SLING-130: VALUE_FROM_SUFFIX means take the value of this + // property from a different field + // @ValueFrom example: + // <input name="./Text@ValueFrom" type="hidden" value="fulltext" /> + // causes the JCR Text property to be set to the value of the + // fulltext form field. + if (propPath.endsWith(SlingPostConstants.VALUE_FROM_SUFFIX)) { + RequestProperty prop = getOrCreateRequestProperty( + reqProperties, propPath, + SlingPostConstants.VALUE_FROM_SUFFIX); + + // @ValueFrom params must have exactly one value, else ignored + if (e.getValue().length == 1) { + String refName = e.getValue()[0].getString(); + RequestParameter[] refValues = request.getRequestParameters(refName); + if (refValues != null) { + prop.setValues(refValues); + } + } + + continue; + } + + // SLING-458: Allow Removal of properties prior to update + // @Delete example: + // <input name="./Text@Delete" type="hidden" /> + // causes the JCR Text property to be deleted before update + if (propPath.endsWith(SlingPostConstants.SUFFIX_DELETE)) { + RequestProperty prop = getOrCreateRequestProperty( + reqProperties, propPath, SlingPostConstants.SUFFIX_DELETE); + + prop.setDelete(true); + + continue; + } + + // SLING-455: @MoveFrom means moving content to another location + // @MoveFrom example: + // <input name="./Text@MoveFrom" type="hidden" value="/tmp/path" /> + // causes the JCR Text property to be set by moving the /tmp/path + // property to Text. + if (propPath.endsWith(SlingPostConstants.SUFFIX_MOVE_FROM)) { + //don't support @MoveFrom here + continue; + } + + // SLING-455: @CopyFrom means moving content to another location + // @CopyFrom example: + // <input name="./Text@CopyFrom" type="hidden" value="/tmp/path" /> + // causes the JCR Text property to be set by copying the /tmp/path + // property to Text. + if (propPath.endsWith(SlingPostConstants.SUFFIX_COPY_FROM)) { + //don't support @CopyFrom here + continue; + } + + // plain property, create from values + RequestProperty prop = getOrCreateRequestProperty(reqProperties, + propPath, null); + prop.setValues(e.getValue()); + } + + return reqProperties; + } + + + /** + * Returns the request property for the given property path. If such a + * request property does not exist yet it is created and stored in the + * <code>props</code>. + * + * @param props The map of already seen request properties. + * @param paramName The absolute path of the property including the + * <code>suffix</code> to be looked up. + * @param suffix The (optional) suffix to remove from the + * <code>paramName</code> before looking it up. + * @return The {@link RequestProperty} for the <code>paramName</code>. + */ + private RequestProperty getOrCreateRequestProperty( + Map<String, RequestProperty> props, String paramName, String suffix) { + if (suffix != null && paramName.endsWith(suffix)) { + paramName = paramName.substring(0, paramName.length() + - suffix.length()); + } + + RequestProperty prop = props.get(paramName); + if (prop == null) { + prop = new RequestProperty(paramName); + props.put(paramName, prop); + } + + return prop; + } + + + /** + * Removes all properties listed as {@link RequestProperty#isDelete()} from + * the authorizable. + * + * @param authorizable The <code>org.apache.jackrabbit.api.security.user.Authorizable</code> + * that should have properties deleted. + * @param reqProperties The map of request properties to check for + * properties to be removed. + * @param response The <code>HtmlResponse</code> to be updated with + * information on deleted properties. + * @throws RepositoryException Is thrown if an error occurrs checking or + * removing properties. + */ + protected void processDeletes(Authorizable resource, + Map<String, RequestProperty> reqProperties, + List<Modification> changes) throws RepositoryException { + + for (RequestProperty property : reqProperties.values()) { + if (property.isDelete()) { + if (resource.hasProperty(property.getName())) { + resource.removeProperty(property.getName()); + changes.add(Modification.onDeleted(property.getPath())); + } + } + } + } + + + /** + * Writes back the content + * + * @throws RepositoryException if a repository error occurs + * @throws ServletException if an internal error occurs + */ + protected void writeContent(Session session, Authorizable authorizable, + Map<String, RequestProperty> reqProperties, List<Modification> changes) + throws RepositoryException { + + for (RequestProperty prop : reqProperties.values()) { + if (prop.hasValues()) { + // skip jcr special properties + if (prop.getName().equals("jcr:primaryType") + || prop.getName().equals("jcr:mixinTypes")) { + continue; + } + if (authorizable.isGroup()) { + if (prop.getName().equals("groupId")) { + //skip these + continue; + } + } else { + if (prop.getName().equals("userId") || + prop.getName().equals("pwd") || + prop.getName().equals("pwdConfirm")) { + //skip these + continue; + } + } + if (prop.isFileUpload()) { + //don't handle files for user properties for now. + continue; + //uploadHandler.setFile(parent, prop, changes); + } else { + setPropertyAsIs(session, authorizable, prop, changes); + } + } + } + } + + /** + * set property without processing, except for type hints + * + * @param parent the parent node + * @param prop the request property + * @throws RepositoryException if a repository error occurs. + */ + private void setPropertyAsIs(Session session, Authorizable parent, RequestProperty prop, List<Modification> changes) + throws RepositoryException { + + String parentPath; + if (parent.isGroup()) { + parentPath = AuthorizableResourceProvider.SYSTEM_USER_MANAGER_GROUP_PREFIX + parent.getID(); + } else { + parentPath = AuthorizableResourceProvider.SYSTEM_USER_MANAGER_USER_PREFIX + parent.getID(); + } + + + // no explicit typehint + int type = PropertyType.UNDEFINED; + if (prop.getTypeHint() != null) { + try { + type = PropertyType.valueFromName(prop.getTypeHint()); + } catch (Exception e) { + // ignore + } + } + + String[] values = prop.getStringValues(); + if (values == null) { + // remove property + boolean removedProp = removePropertyIfExists(parent, prop.getName()); + if (removedProp) { + changes.add(Modification.onDeleted( + parentPath + "/" + prop.getName() + )); + } + } else if (values.length == 0) { + // do not create new prop here, but clear existing + if (parent.hasProperty(prop.getName())) { + Value val = session.getValueFactory().createValue(""); + parent.setProperty(prop.getName(), val); + changes.add(Modification.onModified( + parentPath + "/" + prop.getName() + )); + } + } else if (values.length == 1) { + boolean removedProp = removePropertyIfExists(parent, prop.getName()); + // if the provided value is the empty string, we don't have to do anything. + if ( values[0].length() == 0 ) { + if ( removedProp ) { + changes.add(Modification.onDeleted(parentPath + "/" + prop.getName())); + } + } else { + // modify property + if (type == PropertyType.DATE) { + // try conversion + Calendar c = dateParser.parse(values[0]); + if (c != null) { + if ( prop.hasMultiValueTypeHint() ) { + final Value[] array = new Value[1]; + array[0] = session.getValueFactory().createValue(c); + parent.setProperty(prop.getName(), array); + changes.add(Modification.onModified( + parentPath + "/" + prop.getName() + )); + } else { + Value cVal = session.getValueFactory().createValue(c); + parent.setProperty(prop.getName(), cVal); + changes.add(Modification.onModified( + parentPath + "/" + prop.getName() + )); + } + return; + } + // fall back to default behaviour + } + if ( type == PropertyType.UNDEFINED ) { + Value val = session.getValueFactory().createValue(values[0], PropertyType.STRING); + parent.setProperty(prop.getName(), val); + } else { + if ( prop.hasMultiValueTypeHint() ) { + final Value[] array = new Value[1]; + array[0] = session.getValueFactory().createValue(values[0], type); + parent.setProperty(prop.getName(), array); + } else { + Value val = session.getValueFactory().createValue(values[0], type); + parent.setProperty(prop.getName(), val); + } + } + changes.add(Modification.onModified(parentPath + "/" + prop.getName())); + } + } else { + removePropertyIfExists(parent, prop.getName()); + if (type == PropertyType.DATE) { + // try conversion + ValueFactory valFac = session.getValueFactory(); + Value[] c = dateParser.parse(values, valFac); + if (c != null) { + parent.setProperty(prop.getName(), c); + changes.add(Modification.onModified( + parentPath + "/" + prop.getName() + )); + return; + } + // fall back to default behaviour + } + + Value [] vals = new Value[values.length]; + if ( type == PropertyType.UNDEFINED ) { + for(int i=0; i < values.length; i++) { + vals[i] = session.getValueFactory().createValue(values[i]); + } + } else { + for(int i=0; i < values.length; i++) { + vals[i] = session.getValueFactory().createValue(values[i], type); + } + } + parent.setProperty(prop.getName(), vals); + changes.add(Modification.onModified(parentPath + "/" + prop.getName())); + } + + } + + /** + * Removes the property with the given name from the parent resource if it + * exists. + * + * @param parent the parent resource + * @param name the name of the property to remove + * @return path of the property that was removed or <code>null</code> if + * it was not removed + * @throws RepositoryException if a repository error occurs. + */ + private boolean removePropertyIfExists(Authorizable resource, String name) throws RepositoryException { + if (resource.getProperty(name) != null) { + resource.removeProperty(name); + return true; + } + return false; + } + + + /** + * Returns an iterator on <code>Resource</code> instances addressed in the + * {@link SlingPostConstants#RP_APPLY_TO} request parameter. If the request + * parameter is not set, <code>null</code> is returned. If the parameter + * is set with valid resources an empty iterator is returned. Any resources + * addressed in the {@link SlingPostConstants#RP_APPLY_TO} parameter is + * ignored. + * + * @param request The <code>SlingHttpServletRequest</code> object used to + * get the {@link SlingPostConstants#RP_APPLY_TO} parameter. + * @return The iterator of resources listed in the parameter or + * <code>null</code> if the parameter is not set in the request. + */ + protected Iterator<Resource> getApplyToResources( + SlingHttpServletRequest request) { + + String[] applyTo = request.getParameterValues(SlingPostConstants.RP_APPLY_TO); + if (applyTo == null) { + return null; + } + + return new ApplyToIterator(request, applyTo); + } + + private static class ApplyToIterator implements Iterator<Resource> { + + private final ResourceResolver resolver; + private final Resource baseResource; + private final String[] paths; + + private int pathIndex; + + private Resource nextResource; + + ApplyToIterator(SlingHttpServletRequest request, String[] paths) { + this.resolver = request.getResourceResolver(); + this.baseResource = request.getResource(); + this.paths = paths; + this.pathIndex = 0; + + nextResource = seek(); + } + + public boolean hasNext() { + return nextResource != null; + } + + public Resource next() { + if (!hasNext()) { + throw new NoSuchElementException(); + } + + Resource result = nextResource; + nextResource = seek(); + + return result; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + + private Resource seek() { + while (pathIndex < paths.length) { + String path = paths[pathIndex]; + pathIndex++; + + Resource res = resolver.getResource(baseResource, path); + if (res != null) { + return res; + } + } + + // no more elements in the array + return null; + } + } + +} diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/post/ChangePasswordOperation.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/ChangePasswordOperation.java new file mode 100644 index 0000000..9972ede --- /dev/null +++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/ChangePasswordOperation.java @@ -0,0 +1,103 @@ +/* + * 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.sling.jackrabbit.usermanager.post; + +import java.util.List; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.jcr.Value; + +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.jackrabbit.api.security.user.User; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.servlets.HtmlResponse; +import org.apache.sling.servlets.post.Modification; + +/** + * Sling Post Operation implementation for updating the password of + * a user in the jackrabbit UserManager. + * + * @scr.component metatype="no" immediate="true" + * @scr.service interface="org.apache.sling.servlets.post.SlingPostOperation" + * @scr.property name="sling.post.operation" value="changePassword" + */ +public class ChangePasswordOperation extends AbstractAuthorizableOperation { + + /* (non-Javadoc) + * @see org.apache.sling.servlets.post.AbstractSlingPostOperation#doRun(org.apache.sling.api.SlingHttpServletRequest, org.apache.sling.api.servlets.HtmlResponse, java.util.List) + */ + @Override + protected void doRun(SlingHttpServletRequest request, + HtmlResponse response, List<Modification> changes) + throws RepositoryException { + Authorizable authorizable = null; + Resource resource = request.getResource(); + if (resource != null) { + authorizable = resource.adaptTo(Authorizable.class); + } + + //check that the user was located. + if (authorizable == null || authorizable.isGroup()) { + throw new RepositoryException("User to update could not be determined."); + } + + if ("anonymous".equals(authorizable.getID())) { + throw new RepositoryException("Can not change the password of the anonymous user."); + } + + Session session = request.getResourceResolver().adaptTo(Session.class); + if (session == null) { + throw new RepositoryException("JCR Session not found"); + } + + //check that the submitted parameter values have valid values. + String oldPwd = request.getParameter("oldPwd"); + if (oldPwd == null || oldPwd.length() == 0) { + throw new RepositoryException("Old Password was not submitted"); + } + String newPwd = request.getParameter("newPwd"); + if (newPwd == null || newPwd.length() == 0) { + throw new RepositoryException("New Password was not submitted"); + } + String newPwdConfirm = request.getParameter("newPwdConfirm"); + if (!newPwd.equals(newPwdConfirm)) { + throw new RepositoryException("New Password does not match the confirmation password"); + } + + try { + String digestedOldPwd = digestPassword(oldPwd); + Value[] pwdProperty = ((User)authorizable).getProperty("rep:password"); + if (pwdProperty != null && pwdProperty.length > 0) { + String repPasswordValue = pwdProperty[0].getString(); + if (!digestedOldPwd.equals(repPasswordValue)) { + //submitted oldPwd value is not correct. + throw new RepositoryException("Old Password does not match"); + } + } + + ((User)authorizable).changePassword(digestPassword(newPwd)); + + changes.add(Modification.onModified( + resource.getPath() + "/rep:password" + )); + } catch (RepositoryException re) { + throw new RepositoryException("Failed to change user password.", re); + } + } +} diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/post/CreateGroupOperation.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/CreateGroupOperation.java new file mode 100644 index 0000000..08e2456 --- /dev/null +++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/CreateGroupOperation.java @@ -0,0 +1,97 @@ +/* + * 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.sling.jackrabbit.usermanager.post; + +import java.security.Principal; +import java.util.List; +import java.util.Map; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.jackrabbit.api.security.user.Group; +import org.apache.jackrabbit.api.security.user.UserManager; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.servlets.HtmlResponse; +import org.apache.sling.jackrabbit.usermanager.post.impl.RequestProperty; +import org.apache.sling.jackrabbit.usermanager.resource.AuthorizableResourceProvider; +import org.apache.sling.jcr.base.util.AccessControlUtil; +import org.apache.sling.servlets.post.Modification; +import org.apache.sling.servlets.post.SlingPostConstants; + +/** + * Sling Post Operation implementation for creating a group in the jackrabbit + * UserManager. + * + * @scr.component metatype="no" immediate="true" + * @scr.service interface="org.apache.sling.servlets.post.SlingPostOperation" + * @scr.property name="sling.post.operation" value="createGroup" + */ +public class CreateGroupOperation extends AbstractAuthorizableOperation { + + /* (non-Javadoc) + * @see org.apache.sling.servlets.post.AbstractSlingPostOperation#doRun(org.apache.sling.api.SlingHttpServletRequest, org.apache.sling.api.servlets.HtmlResponse, java.util.List) + */ + @Override + protected void doRun(SlingHttpServletRequest request, + HtmlResponse response, List<Modification> changes) + throws RepositoryException { + Session session = request.getResourceResolver().adaptTo(Session.class); + if (session == null) { + throw new RepositoryException("JCR Session not found"); + } + + //check that the submitted parameter values have valid values. + final String principalName = request.getParameter(SlingPostConstants.RP_NODE_NAME); + if (principalName == null) { + throw new RepositoryException("Group name was not submitted"); + } + + try { + UserManager userManager = AccessControlUtil.getUserManager(session); + Authorizable authorizable = userManager.getAuthorizable(principalName); + + if (authorizable != null) { + //principal already exists! + throw new RepositoryException("A principal already exists with the requested name: " + principalName); + } else { + Map<String, RequestProperty> reqProperties = collectContent(request, response); + + Group group = userManager.createGroup(new Principal() { + public String getName() { + return principalName; + } + }); + + String groupPath = AuthorizableResourceProvider.SYSTEM_USER_MANAGER_GROUP_PREFIX + group.getID(); + response.setPath(groupPath); + response.setLocation(externalizePath(request, groupPath)); + response.setParentLocation(externalizePath(request, AuthorizableResourceProvider.SYSTEM_USER_MANAGER_GROUP_PATH)); + changes.add(Modification.onCreated(groupPath)); + + // write content from form + writeContent(session, group, reqProperties, changes); + + //update the group memberships + updateGroupMembership(request, group, changes); + } + } catch (RepositoryException re) { + throw new RepositoryException("Failed to create new group.", re); + } + } +} diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/post/CreateUserOperation.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/CreateUserOperation.java new file mode 100644 index 0000000..c0e385f --- /dev/null +++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/CreateUserOperation.java @@ -0,0 +1,174 @@ +/* + * 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.sling.jackrabbit.usermanager.post; + +import java.util.Dictionary; +import java.util.List; +import java.util.Map; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.jackrabbit.api.security.user.User; +import org.apache.jackrabbit.api.security.user.UserManager; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.servlets.HtmlResponse; +import org.apache.sling.jackrabbit.usermanager.post.impl.RequestProperty; +import org.apache.sling.jackrabbit.usermanager.resource.AuthorizableResourceProvider; +import org.apache.sling.jcr.api.SlingRepository; +import org.apache.sling.jcr.base.util.AccessControlUtil; +import org.apache.sling.servlets.post.Modification; +import org.apache.sling.servlets.post.SlingPostConstants; +import org.osgi.service.component.ComponentContext; + +/** + * Sling Post Operation implementation for creating a user in the jackrabbit + * UserManager. + * + * @scr.component immediate="true" label="%createUser.post.operation.name" + * description="%createUser.post.operation.description" + * @scr.service interface="org.apache.sling.servlets.post.SlingPostOperation" + * @scr.property name="sling.post.operation" value="createUser" + */ +public class CreateUserOperation extends AbstractAuthorizableOperation { + + /** @scr.property label="%self.registration.enabled.name" + * description="%self.registration.enabled.description" + * valueRef="DEFAULT_SELF_REGISTRATION_ENABLED" + */ + private static final String PROP_SELF_REGISTRATION_ENABLED = "self.registration.enabled"; + private static final Boolean DEFAULT_SELF_REGISTRATION_ENABLED = Boolean.TRUE; + + private Boolean selfRegistrationEnabled = DEFAULT_SELF_REGISTRATION_ENABLED; + + /** + * The JCR Repository we access to resolve resources + * + * @scr.reference + */ + private SlingRepository repository; + + /** Returns the JCR repository used by this service. */ + protected SlingRepository getRepository() { + return repository; + } + + /** + * Returns an administrative session to the default workspace. + */ + private Session getSession() throws RepositoryException { + return getRepository().loginAdministrative(null); + } + + /** + * Return the administrative session and close it. + */ + private void ungetSession(final Session session) { + if ( session != null ) { + try { + session.logout(); + } catch (Throwable t) { + log.error("Unable to log out of session: " + t.getMessage(), t); + } + } + } + + + // ---------- SCR integration --------------------------------------------- + + /** + * Activates this component. + * + * @param componentContext The OSGi <code>ComponentContext</code> of this + * component. + */ + protected void activate(ComponentContext componentContext) { + super.activate(componentContext); + Dictionary<?, ?> props = componentContext.getProperties(); + Object propValue = props.get(PROP_SELF_REGISTRATION_ENABLED); + if (propValue instanceof String) { + selfRegistrationEnabled = Boolean.parseBoolean((String)propValue); + } else { + selfRegistrationEnabled = DEFAULT_SELF_REGISTRATION_ENABLED; + } + } + + /* (non-Javadoc) + * @see org.apache.sling.servlets.post.AbstractSlingPostOperation#doRun(org.apache.sling.api.SlingHttpServletRequest, org.apache.sling.api.servlets.HtmlResponse, java.util.List) + */ + @Override + protected void doRun(SlingHttpServletRequest request, + HtmlResponse response, List<Modification> changes) + throws RepositoryException { + + //make sure user self-registration is enabled + if (!selfRegistrationEnabled) { + throw new RepositoryException("Sorry, registration of new users is not currently enabled. Please try again later."); + } + + Session session = request.getResourceResolver().adaptTo(Session.class); + if (session == null) { + throw new RepositoryException("JCR Session not found"); + } + + //check that the submitted parameter values have valid values. + String principalName = request.getParameter(SlingPostConstants.RP_NODE_NAME); + if (principalName == null) { + throw new RepositoryException("User name was not submitted"); + } + String pwd = request.getParameter("pwd"); + if (pwd == null) { + throw new RepositoryException("Password was not submitted"); + } + String pwdConfirm = request.getParameter("pwdConfirm"); + if (!pwd.equals(pwdConfirm)) { + throw new RepositoryException("Password value does not match the confirmation password"); + } + + Session selfRegSession = null; + try { + selfRegSession = getSession(); + + UserManager userManager = AccessControlUtil.getUserManager(selfRegSession); + Authorizable authorizable = userManager.getAuthorizable(principalName); + + if (authorizable != null) { + //user already exists! + throw new RepositoryException("A principal already exists with the requested name: " + principalName); + } else { + Map<String, RequestProperty> reqProperties = collectContent(request, response); + + User user = userManager.createUser(principalName, digestPassword(pwd)); + String userPath = AuthorizableResourceProvider.SYSTEM_USER_MANAGER_USER_PREFIX + user.getID(); + response.setPath(userPath); + response.setLocation(externalizePath(request, userPath)); + response.setParentLocation(externalizePath(request, AuthorizableResourceProvider.SYSTEM_USER_MANAGER_USER_PATH)); + changes.add(Modification.onCreated(userPath)); + + // write content from form + writeContent(selfRegSession, user, reqProperties, changes); + + if (selfRegSession.hasPendingChanges()) { + selfRegSession.save(); + } + } + } finally { + ungetSession(selfRegSession); + } + } +} diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/post/DeleteAuthorizableOperation.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/DeleteAuthorizableOperation.java new file mode 100644 index 0000000..991cee0 --- /dev/null +++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/DeleteAuthorizableOperation.java @@ -0,0 +1,73 @@ +/* + * 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.sling.jackrabbit.usermanager.post; + +import java.util.Iterator; +import java.util.List; + +import javax.jcr.RepositoryException; +import javax.servlet.http.HttpServletResponse; + +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceNotFoundException; +import org.apache.sling.api.servlets.HtmlResponse; +import org.apache.sling.servlets.post.Modification; + +/** + * Sling Post Operation implementation for deleting users and/or groups from the + * jackrabbit UserManager. + * + * @scr.component metatype="no" immediate="true" + * @scr.service interface="org.apache.sling.servlets.post.SlingPostOperation" + * @scr.property name="sling.post.operation" value="deleteAuthorizable" + */ +public class DeleteAuthorizableOperation extends AbstractAuthorizableOperation { + + /* (non-Javadoc) + * @see org.apache.sling.servlets.post.AbstractSlingPostOperation#doRun(org.apache.sling.api.SlingHttpServletRequest, org.apache.sling.api.servlets.HtmlResponse, java.util.List) + */ + @Override + protected void doRun(SlingHttpServletRequest request, + HtmlResponse response, List<Modification> changes) + throws RepositoryException { + + Iterator<Resource> res = getApplyToResources(request); + if (res == null) { + Resource resource = request.getResource(); + Authorizable item = resource.adaptTo(Authorizable.class); + if (item == null) { + String msg = "Missing source " + resource.getPath() + " for delete"; + response.setStatus(HttpServletResponse.SC_NOT_FOUND, msg); + throw new ResourceNotFoundException(msg); + } + + item.remove(); + changes.add(Modification.onDeleted(resource.getPath())); + } else { + while (res.hasNext()) { + Resource resource = res.next(); + Authorizable item = resource.adaptTo(Authorizable.class); + if (item != null) { + item.remove(); + changes.add(Modification.onDeleted(resource.getPath())); + } + } + } + } +} diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/post/UpdateAuthorizableOperation.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/UpdateAuthorizableOperation.java new file mode 100644 index 0000000..c88be1c --- /dev/null +++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/UpdateAuthorizableOperation.java @@ -0,0 +1,81 @@ +/* + * 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.sling.jackrabbit.usermanager.post; + +import java.util.List; +import java.util.Map; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; + +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.sling.api.SlingHttpServletRequest; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.servlets.HtmlResponse; +import org.apache.sling.jackrabbit.usermanager.post.impl.RequestProperty; +import org.apache.sling.servlets.post.Modification; + +/** + * Sling Post Operation implementation for updating a user or group in the + * jackrabbit UserManager. + * + * @scr.component metatype="no" immediate="true" + * @scr.service interface="org.apache.sling.servlets.post.SlingPostOperation" + * @scr.property name="sling.post.operation" value="updateAuthorizable" + */ +public class UpdateAuthorizableOperation extends AbstractAuthorizableOperation { + + /* (non-Javadoc) + * @see org.apache.sling.servlets.post.AbstractSlingPostOperation#doRun(org.apache.sling.api.SlingHttpServletRequest, org.apache.sling.api.servlets.HtmlResponse, java.util.List) + */ + @Override + protected void doRun(SlingHttpServletRequest request, + HtmlResponse response, List<Modification> changes) + throws RepositoryException { + Authorizable authorizable = null; + Resource resource = request.getResource(); + if (resource != null) { + authorizable = resource.adaptTo(Authorizable.class); + } + + //check that the group was located. + if (authorizable == null) { + throw new RepositoryException("Authorizable to update could not be determined"); + } + + Session session = request.getResourceResolver().adaptTo(Session.class); + if (session == null) { + throw new RepositoryException("JCR Session not found"); + } + + Map<String, RequestProperty> reqProperties = collectContent(request, response); + try { + // cleanup any old content (@Delete parameters) + processDeletes(authorizable, reqProperties, changes); + + // write content from form + writeContent(session, authorizable, reqProperties, changes); + + //update the group memberships + if (authorizable.isGroup()) { + updateGroupMembership(request, authorizable, changes); + } + } catch (RepositoryException re) { + throw new RepositoryException("Failed to update authorizable.", re); + } + } +} diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/post/impl/DateParser.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/impl/DateParser.java new file mode 100644 index 0000000..227ea4c --- /dev/null +++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/impl/DateParser.java @@ -0,0 +1,138 @@ +/* + * 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.sling.jackrabbit.usermanager.post.impl; + +import java.text.DateFormat; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; + +import javax.jcr.Value; +import javax.jcr.ValueFactory; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Takes a string representation of a time-date string and tries for parse it + * using different formats. + */ +public class DateParser { + + /** + * default log + */ + private static final Logger log = LoggerFactory.getLogger(DateParser.class); + + /** + * lits of formats + */ + private final List<DateFormat> formats = new LinkedList<DateFormat>(); + + /** + * Registers a format string to the list of internally checked ones. + * Uses the {@link SimpleDateFormat}. + * @param format format as in {@link SimpleDateFormat} + * @throws IllegalArgumentException if the format is not valid. + */ + public void register(String format) { + register(new SimpleDateFormat(format, Locale.US)); + } + + /** + * Registers a date format to the list of internally checked ones. + * @param format date format + */ + public void register(DateFormat format) { + formats.add(format); + } + + /** + * Parses the given source string and returns the respective calendar + * instance. If no format matches returns <code>null</code>. + * <p/> + * Note: method is synchronized because SimpleDateFormat is not. + * + * @param source date time source string + * @return calendar representation of the source or <code>null</code> + */ + public synchronized Calendar parse(String source) { + for (DateFormat fmt: formats) { + try { + Date d = fmt.parse(source); + if (log.isDebugEnabled()) { + log.debug("Parsed " + source + " using " + fmt + " into " + d); + } + Calendar c = Calendar.getInstance(); + c.setTime(d); + return c; + } catch (ParseException e) { + if (log.isDebugEnabled()) { + log.debug("Failed parsing " + source + " using " + fmt); + } + } + } + return null; + } + + /** + * Parses the given source strings and returns the respective calendar + * instances. If no format matches for any of the sources + * returns <code>null</code>. + * <p/> + * Note: method is synchronized because SimpleDateFormat is not. + * + * @param sources date time source strings + * @return calendar representations of the source or <code>null</code> + */ + public synchronized Calendar[] parse(String sources[]) { + Calendar ret[] = new Calendar[sources.length]; + for (int i=0; i< sources.length; i++) { + if ((ret[i] = parse(sources[i])) == null) { + return null; + } + } + return ret; + } + + /** + * Parses the given source strings and returns the respective jcr date value + * instances. If no format matches for any of the sources + * returns <code>null</code>. + * <p/> + * Note: method is synchronized because SimpleDateFormat is not. + * + * @param sources date time source strings + * @param factory the value factory + * @return jcr date value representations of the source or <code>null</code> + */ + public synchronized Value[] parse(String sources[], ValueFactory factory) { + Value ret[] = new Value[sources.length]; + for (int i=0; i< sources.length; i++) { + Calendar c = parse(sources[i]); + if (c == null) { + return null; + } + ret[i] = factory.createValue(c); + } + return ret; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/post/impl/RequestProperty.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/impl/RequestProperty.java new file mode 100644 index 0000000..cca1323 --- /dev/null +++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/post/impl/RequestProperty.java @@ -0,0 +1,257 @@ +/* + * 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.sling.jackrabbit.usermanager.post.impl; + +import org.apache.sling.api.request.RequestParameter; +import org.apache.sling.api.resource.ResourceUtil; +import org.apache.sling.servlets.post.SlingPostConstants; + +/** + * This is a copy of the class from 'org.apache.sling.servlets.post.impl.helper' which is not exported. + * + * Encapsulates all infos from the respective request parameters that are needed + * to create the repository property + */ +public class RequestProperty { + + private static final RequestParameter[] EMPTY_PARAM_ARRAY = new RequestParameter[0]; + + public static final String DEFAULT_IGNORE = SlingPostConstants.RP_PREFIX + + "ignore"; + + public static final String DEFAULT_NULL = SlingPostConstants.RP_PREFIX + + "null"; + + private final String path; + + private final String name; + + private final String parentPath; + + private RequestParameter[] values; + + private String[] stringValues; + + private String typeHint; + + private boolean hasMultiValueTypeHint; + + private RequestParameter[] defaultValues = EMPTY_PARAM_ARRAY; + + private boolean isDelete; + + private String repositoryResourcePath; + + private boolean isRepositoryResourceMove; + + public RequestProperty(String path) { + assert path.startsWith("/"); + this.path = ResourceUtil.normalize(path); + this.parentPath = ResourceUtil.getParent(path); + this.name = ResourceUtil.getName(path); + } + + public String getTypeHint() { + return typeHint; + } + + public boolean hasMultiValueTypeHint() { + return this.hasMultiValueTypeHint; + } + + public void setTypeHintValue(String typeHint) { + if ( typeHint != null && typeHint.endsWith("[]") ) { + this.typeHint = typeHint.substring(0, typeHint.length() - 2); + this.hasMultiValueTypeHint = true; + } else { + this.typeHint = typeHint; + this.hasMultiValueTypeHint = false; + } + } + + public String getPath() { + return path; + } + + public String getName() { + return name; + } + + public String getParentPath() { + return parentPath; + } + + public boolean hasValues() { + return values != null; + } + + public RequestParameter[] getValues() { + return values; + } + + public void setValues(RequestParameter[] values) { + this.values = values; + } + + public RequestParameter[] getDefaultValues() { + return defaultValues; + } + + public void setDefaultValues(RequestParameter[] defaultValues) { + if (defaultValues == null) { + this.defaultValues = EMPTY_PARAM_ARRAY; + } else { + this.defaultValues = defaultValues; + } + } + + public boolean isFileUpload() { + return !values[0].isFormField(); + } + + /** + * Checks if this property provides any values. this is the case if one of + * the values is not empty or if the default handling is not 'ignore' + * + * @return <code>true</code> if this property provides values + */ + public boolean providesValue() { + // should void double creation of string values + String[] sv = getStringValues(); + if (sv == null) { + // is missleading return type. but means that property should not + // get auto-create values + return true; + } + for (String s : sv) { + if (!s.equals("")) { + return true; + } + } + return false; + } + + /** + * Returns the assembled string array out of the provided request values and + * default values. + * + * @return a String array or <code>null</code> if the property needs to be + * removed. + */ + public String[] getStringValues() { + if (stringValues == null) { + if (values.length > 1) { + // TODO: how the default values work for MV props is not very + // clear + stringValues = new String[values.length]; + for (int i = 0; i < stringValues.length; i++) { + stringValues[i] = values[i].getString(); + } + } else { + String value = values[0].getString(); + if (value.equals("")) { + if (defaultValues.length == 1) { + String defValue = defaultValues[0].getString(); + if (defValue.equals(DEFAULT_IGNORE)) { + // ignore means, do not create empty values + return new String[0]; + } else if (defValue.equals(DEFAULT_NULL)) { + // null means, remove property if exist + return null; + } + value = defValue; + } + } + stringValues = new String[] { value }; + } + } + return stringValues; + } + + /** + * Specifies whether this property should be deleted before any new content + * is to be set according to the values stored. + * + * @param isDelete <code>true</code> if the repository item described by + * this is to be deleted before any other operation. + */ + public void setDelete(boolean isDelete) { + this.isDelete = isDelete; + } + + /** + * Returns <code>true</code> if the repository item described by this is + * to be deleted before setting new content to it. + */ + public boolean isDelete() { + return isDelete; + } + + /** + * Sets the path of the repository item from which the content for this + * property is to be copied or moved. The path may be relative in which case + * it will be resolved relative to the absolute path of this property. + * + * @param sourcePath The path of the repository item to get the content from + * @param isMove <code>true</code> if the source content is to be moved, + * otherwise the source content is copied from the repository + * item. + */ + public void setRepositorySource(String sourcePath, boolean isMove) { + + // make source path absolute + if (!sourcePath.startsWith("/")) { + sourcePath = getParentPath() + "/" + sourcePath; + sourcePath = ResourceUtil.normalize(sourcePath); + } + + this.repositoryResourcePath = sourcePath; + this.isRepositoryResourceMove = isMove; + } + + /** + * Returns <code>true</code> if the content of this property is to be set + * by moving content from another repository item. + * + * @see #getRepositorySource() + */ + public boolean hasRepositoryMoveSource() { + return isRepositoryResourceMove; + } + + /** + * Returns <code>true</code> if the content of this property is to be set + * by copying content from another repository item. + * + * @see #getRepositorySource() + */ + public boolean hasRepositoryCopySource() { + return getRepositorySource() != null && !hasRepositoryMoveSource(); + } + + /** + * Returns the absolute path of the repository item from which the content + * for this property is to be copied or moved. + * + * @see #hasRepositoryCopySource() + * @see #hasRepositoryMoveSource() + * @see #setRepositorySource(String, boolean) + */ + public String getRepositorySource() { + return repositoryResourcePath; + } +} \ No newline at end of file diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/resource/AuthorizableResource.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/resource/AuthorizableResource.java new file mode 100644 index 0000000..c158d46 --- /dev/null +++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/resource/AuthorizableResource.java @@ -0,0 +1,119 @@ +/* + * 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.sling.jackrabbit.usermanager.resource; + + +import java.util.Map; + +import javax.jcr.RepositoryException; + +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.sling.adapter.SlingAdaptable; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceMetadata; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.ValueMap; + +/** + * Resource implementation for Authorizable + */ +public class AuthorizableResource extends SlingAdaptable implements Resource { + private Authorizable authorizable = null; + private ResourceResolver resourceResolver = null; + private final String path; + private final String resourceType; + private final ResourceMetadata metadata; + + public AuthorizableResource(Authorizable authorizable, + ResourceResolver resourceResolver, String path) { + super(); + + this.resourceResolver = resourceResolver; + this.authorizable = authorizable; + this.path = path; + if (authorizable.isGroup()) { + this.resourceType = "sling:group"; + } else { + this.resourceType = "sling:user"; + } + + this.metadata = new ResourceMetadata(); + metadata.setResolutionPath(path); + } + + /* (non-Javadoc) + * @see org.apache.sling.api.resource.Resource#getPath() + */ + public String getPath() { + return path; + } + + /* (non-Javadoc) + * @see org.apache.sling.api.resource.Resource#getResourceMetadata() + */ + public ResourceMetadata getResourceMetadata() { + return metadata; + } + + /* (non-Javadoc) + * @see org.apache.sling.api.resource.Resource#getResourceResolver() + */ + public ResourceResolver getResourceResolver() { + return resourceResolver; + } + + /* (non-Javadoc) + * @see org.apache.sling.api.resource.Resource#getResourceSuperType() + */ + public String getResourceSuperType() { + return null; + } + + /* (non-Javadoc) + * @see org.apache.sling.api.resource.Resource#getResourceType() + */ + public String getResourceType() { + return resourceType; + } + + /* (non-Javadoc) + * @see org.apache.sling.api.adapter.Adaptable#adaptTo(java.lang.Class) + */ + @SuppressWarnings("unchecked") + public <AdapterType> AdapterType adaptTo(Class<AdapterType> type) { + if (type == Map.class || type == ValueMap.class) { + return (AdapterType) new AuthorizableValueMap(authorizable); // unchecked cast + } else if (type == Authorizable.class) { + return (AdapterType)authorizable; + } + + return super.adaptTo(type); + } + + public String toString() { + String id = null; + if (authorizable != null) { + try { + id = authorizable.getID(); + } catch (RepositoryException e) { + //ignore it. + } + } + return getClass().getSimpleName() + ", id=" + id + + ", path=" + getPath(); + } +} diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/resource/AuthorizableResourceProvider.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/resource/AuthorizableResourceProvider.java new file mode 100644 index 0000000..af864b6 --- /dev/null +++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/resource/AuthorizableResourceProvider.java @@ -0,0 +1,220 @@ +/* + * 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.sling.jackrabbit.usermanager.resource; + +import java.security.Principal; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; + +import javax.jcr.RepositoryException; +import javax.jcr.Session; +import javax.servlet.http.HttpServletRequest; + +import org.apache.jackrabbit.api.security.principal.PrincipalIterator; +import org.apache.jackrabbit.api.security.principal.PrincipalManager; +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.jackrabbit.api.security.user.UserManager; +import org.apache.sling.api.SlingException; +import org.apache.sling.api.resource.Resource; +import org.apache.sling.api.resource.ResourceProvider; +import org.apache.sling.api.resource.ResourceResolver; +import org.apache.sling.api.resource.SyntheticResource; +import org.apache.sling.jcr.base.util.AccessControlUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Resource Provider implementation for jackrabbit UserManager resources. + * + * @scr.component immediate="true" label="%authorizable.resourceprovider.name" + * description="authorizable.resourceprovider.description" + * @scr.property name="service.description" + * value="Resource provider implementation for UserManager resources" + * @scr.property name="service.vendor" value="The Apache Software Foundation" + * @scr.property name="provider.roots" value="/system/userManager/" + * @scr.service interface="org.apache.sling.api.resource.ResourceProvider" + */ +public class AuthorizableResourceProvider implements ResourceProvider { + + /** + * default log + */ + private final Logger log = LoggerFactory.getLogger(getClass()); + + public static final String SYSTEM_USER_MANAGER_PATH = "/system/userManager"; + + public static final String SYSTEM_USER_MANAGER_USER_PATH = SYSTEM_USER_MANAGER_PATH + "/user"; + public static final String SYSTEM_USER_MANAGER_GROUP_PATH = SYSTEM_USER_MANAGER_PATH + "/group"; + + public static final String SYSTEM_USER_MANAGER_USER_PREFIX = SYSTEM_USER_MANAGER_USER_PATH + "/"; + public static final String SYSTEM_USER_MANAGER_GROUP_PREFIX = SYSTEM_USER_MANAGER_GROUP_PATH + "/"; + + /* (non-Javadoc) + * @see org.apache.sling.api.resource.ResourceProvider#getResource(org.apache.sling.api.resource.ResourceResolver, javax.servlet.http.HttpServletRequest, java.lang.String) + */ + public Resource getResource(ResourceResolver resourceResolver, + HttpServletRequest request, String path) { + return getResource(resourceResolver, path); + } + + + /* (non-Javadoc) + * @see org.apache.sling.api.resource.ResourceProvider#getResource(org.apache.sling.api.resource.ResourceResolver, java.lang.String) + */ + public Resource getResource(ResourceResolver resourceResolver, String path) { + + //handle resources for the virtual container resources + if (path.equals(SYSTEM_USER_MANAGER_PATH)) { + return new SyntheticResource(resourceResolver, path, "sling:userManager"); + } else if (path.equals(SYSTEM_USER_MANAGER_USER_PATH)) { + return new SyntheticResource(resourceResolver, path, "sling:users"); + } else if (path.equals(SYSTEM_USER_MANAGER_GROUP_PATH)) { + return new SyntheticResource(resourceResolver, path, "sling:groups"); + } + + // the principalId should be the first segment after the prefix + String pid = null; + if (path.startsWith(SYSTEM_USER_MANAGER_USER_PREFIX)) { + pid = path.substring(SYSTEM_USER_MANAGER_USER_PREFIX.length()); + } else if (path.startsWith(SYSTEM_USER_MANAGER_GROUP_PREFIX)) { + pid = path.substring(SYSTEM_USER_MANAGER_GROUP_PREFIX.length()); + } + + if (pid != null) { + if (pid.indexOf('/') != -1) { + return null; //something bogus on the end of the path so bail out now. + } + try { + Session session = resourceResolver.adaptTo(Session.class); + if (session != null) { + UserManager userManager = AccessControlUtil.getUserManager(session); + if (userManager != null) { + Authorizable authorizable = userManager.getAuthorizable(pid); + if (authorizable != null) { + //found the Authorizable, so return the resource that wraps it. + return new AuthorizableResource(authorizable, resourceResolver, path); + } + } + } + } catch (RepositoryException re) { + throw new SlingException("Error looking up Authorizable for principal: " + pid, re); + } + } + return null; + } + + + /* (non-Javadoc) + * @see org.apache.sling.api.resource.ResourceProvider#listChildren(org.apache.sling.api.resource.Resource) + */ + public Iterator<Resource> listChildren(Resource parent) { + if (parent == null) { + throw new NullPointerException("parent is null"); + } + try { + String path = parent.getPath(); + ResourceResolver resourceResolver = parent.getResourceResolver(); + + //handle children of /system/userManager + if (SYSTEM_USER_MANAGER_PATH.equals(path)) { + List<Resource> resources = new ArrayList<Resource>(); + if (resourceResolver != null) { + resources.add(getResource(resourceResolver, SYSTEM_USER_MANAGER_USER_PATH)); + resources.add(getResource(resourceResolver, SYSTEM_USER_MANAGER_GROUP_PATH)); + } + return resources.iterator(); + } + + int searchType = -1; + if (SYSTEM_USER_MANAGER_USER_PATH.equals(path)) { + searchType = PrincipalManager.SEARCH_TYPE_NOT_GROUP; + } else if (SYSTEM_USER_MANAGER_GROUP_PATH.equals(path)) { + searchType = PrincipalManager.SEARCH_TYPE_GROUP; + } + if (searchType != -1) { + PrincipalIterator principals = null; + + //TODO: this actually does not work correctly since the jackrabbit findPrincipals API + // currently does an exact match of the search filter so it won't match a wildcard + Session session = resourceResolver.adaptTo(Session.class); + if (session != null) { + PrincipalManager principalManager = AccessControlUtil.getPrincipalManager(session); + principals = principalManager.findPrincipals(".*", PrincipalManager.SEARCH_TYPE_NOT_GROUP); + } + + + if (principals != null) { + return new ChildrenIterator(parent, principals); + } + } + } catch (RepositoryException re) { + throw new SlingException("Error listing children of resource: " + parent.getPath(), re); + } + + return null; + } + + + + private final class ChildrenIterator implements Iterator<Resource> { + private PrincipalIterator principals; + private Resource parent; + + public ChildrenIterator(Resource parent, PrincipalIterator principals) { + this.parent = parent; + this.principals = principals; + } + + public boolean hasNext() { + return principals.hasNext(); + } + + public Resource next() { + Principal nextPrincipal = principals.nextPrincipal(); + try { + ResourceResolver resourceResolver = parent.getResourceResolver(); + if (resourceResolver != null) { + Session session = resourceResolver.adaptTo(Session.class); + if (session != null) { + UserManager userManager = AccessControlUtil.getUserManager(session); + if (userManager != null) { + Authorizable authorizable = userManager.getAuthorizable(nextPrincipal.getName()); + if (authorizable != null) { + String path; + if (authorizable.isGroup()) { + path = SYSTEM_USER_MANAGER_GROUP_PREFIX + nextPrincipal.getName(); + } else { + path = SYSTEM_USER_MANAGER_USER_PREFIX + nextPrincipal.getName(); + } + return new AuthorizableResource(authorizable, resourceResolver, path); + } + } + } + } + } catch (RepositoryException re) { + log.error("Exception while looking up authorizable resource.", re); + } + return null; + } + + public void remove() { + throw new UnsupportedOperationException(); + } + } + +} diff --git a/src/main/java/org/apache/sling/jackrabbit/usermanager/resource/AuthorizableValueMap.java b/src/main/java/org/apache/sling/jackrabbit/usermanager/resource/AuthorizableValueMap.java new file mode 100644 index 0000000..3dafa19 --- /dev/null +++ b/src/main/java/org/apache/sling/jackrabbit/usermanager/resource/AuthorizableValueMap.java @@ -0,0 +1,319 @@ +/* + * 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.sling.jackrabbit.usermanager.resource; + +import java.lang.reflect.Array; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Calendar; +import java.util.Collection; +import java.util.Date; +import java.util.HashSet; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.jcr.Property; +import javax.jcr.RepositoryException; +import javax.jcr.Value; +import javax.jcr.ValueFormatException; + +import org.apache.jackrabbit.api.security.user.Authorizable; +import org.apache.sling.api.resource.ValueMap; +import org.apache.sling.jcr.resource.JcrResourceUtil; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * ValueMap implementation for Authorizable Resources + */ +public class AuthorizableValueMap implements ValueMap { + private Logger logger = LoggerFactory.getLogger(AuthorizableValueMap.class); + private Set<String> hiddenProperties = new HashSet<String>(Arrays.asList(new String[]{"rep:password", "jcr:uuid"})); + private boolean fullyRead; + private final Map<String, Object> cache; + private Authorizable authorizable; + + public AuthorizableValueMap(Authorizable authorizable) { + this.authorizable = authorizable; + this.cache = new LinkedHashMap<String, Object>(); + this.fullyRead = false; + } + + @SuppressWarnings("unchecked") + public <T> T get(String name, Class<T> type) { + if (type == null) { + return (T) get(name); + } + + return convertToType(name, type); + } + + @SuppressWarnings("unchecked") + public <T> T get(String name, T defaultValue) { + if (defaultValue == null) { + return (T) get(name); + } + + // special handling in case the default value implements one + // of the interface types supported by the convertToType method + Class<T> type = (Class<T>) normalizeClass(defaultValue.getClass()); + + T value = get(name, type); + if (value == null) { + value = defaultValue; + } + + return value; + } + + public boolean containsKey(Object key) { + return get(key) != null; + } + + public boolean containsValue(Object value) { + readFully(); + return cache.containsValue(value); + } + + public Set<java.util.Map.Entry<String, Object>> entrySet() { + readFully(); + return cache.entrySet(); + } + + public Object get(Object key) { + Object value = cache.get(key); + if (value == null) { + value = read((String) key); + } + + return value; + } + + + public Set<String> keySet() { + readFully(); + return cache.keySet(); + } + + public int size() { + readFully(); + return cache.size(); + } + + public boolean isEmpty() { + return size() == 0; + } + + public Collection<Object> values() { + readFully(); + return cache.values(); + } + + protected Object read(String key) { + + // if the item has been completely read, we need not check + // again, as we certainly will not find the key + if (fullyRead) { + return null; + } + + if (hiddenProperties.contains(key)) { + return null; + } + + try { + if (authorizable.hasProperty(key)) { + Value[] property = authorizable.getProperty(key); + Object value = valuesToJavaObject(property); + cache.put(key, value); + return value; + } + } catch (RepositoryException re) { + // TODO: log !! + } + + // property not found or some error accessing it + return null; + } + + protected Object valuesToJavaObject(Value [] values) throws RepositoryException { + if (values == null) { + return null; + } else if (values.length == 1) { + return JcrResourceUtil.toJavaObject(values[0]); + } else { + Object [] valuesObjs = new Object[values.length]; + for (int i=0; i < values.length; i++) { + valuesObjs[i] = JcrResourceUtil.toJavaObject(values[i]); + } + return valuesObjs; + } + } + + @SuppressWarnings("unchecked") + protected void readFully() { + if (!fullyRead) { + try { + Iterator pi = authorizable.getPropertyNames(); + while (pi.hasNext()) { + String key = (String)pi.next(); + + if (hiddenProperties.contains(key)) { + continue; //skip it. + } + + if (!cache.containsKey(key)) { + Value[] property = authorizable.getProperty(key); + Object value = valuesToJavaObject(property); + cache.put(key, value); + } + } + fullyRead = true; + } catch (RepositoryException re) { + // TODO: log !! + } + } + } + + // ---------- Unsupported Modification methods + + public Object remove(Object arg0) { + throw new UnsupportedOperationException(); + } + public void clear() { + throw new UnsupportedOperationException(); + } + public Object put(String arg0, Object arg1) { + throw new UnsupportedOperationException(); + } + + public void putAll(Map<? extends String, ? extends Object> arg0) { + throw new UnsupportedOperationException(); + } + + + // ---------- Implementation helper + + @SuppressWarnings("unchecked") + private <T> T convertToType(String name, Class<T> type) { + T result = null; + + try { + if (authorizable.hasProperty(name)) { + Value[] values = authorizable.getProperty(name); + + if (values == null) { + return null; + } + + boolean multiValue = values.length > 1; + boolean array = type.isArray(); + + if (multiValue) { + if (array) { + result = (T) convertToArray(values, + type.getComponentType()); + } else if (values.length > 0) { + result = convertToType(-1, values[0], type); + } + } else { + Value value = values[0]; + if (array) { + result = (T) convertToArray( + new Value[] { value }, type.getComponentType()); + } else { + result = convertToType(-1, value, type); + } + } + } + + } catch (ValueFormatException vfe) { + logger.info("converToType: Cannot convert value of " + name + + " to " + type, vfe); + } catch (RepositoryException re) { + logger.info("converToType: Cannot get value of " + name, re); + } + + // fall back to nothing + return result; + } + + private <T> T[] convertToArray(Value[] jcrValues, Class<T> type) + throws ValueFormatException, RepositoryException { + List<T> values = new ArrayList<T>(); + for (int i = 0; i < jcrValues.length; i++) { + T value = convertToType(i, jcrValues[i], type); + if (value != null) { + values.add(value); + } + } + + @SuppressWarnings("unchecked") + T[] result = (T[]) Array.newInstance(type, values.size()); + + return values.toArray(result); + } + + @SuppressWarnings("unchecked") + private <T> T convertToType(int index, Value jcrValue, + Class<T> type) throws ValueFormatException, RepositoryException { + + if (String.class == type) { + return (T) jcrValue.getString(); + } else if (Byte.class == type) { + return (T) new Byte((byte) jcrValue.getLong()); + } else if (Short.class == type) { + return (T) new Short((short) jcrValue.getLong()); + } else if (Integer.class == type) { + return (T) new Integer((int) jcrValue.getLong()); + } else if (Long.class == type) { + return (T) new Long(jcrValue.getLong()); + } else if (Float.class == type) { + return (T) new Float(jcrValue.getDouble()); + } else if (Double.class == type) { + return (T) new Double(jcrValue.getDouble()); + } else if (Boolean.class == type) { + return (T) Boolean.valueOf(jcrValue.getBoolean()); + } else if (Date.class == type) { + return (T) jcrValue.getDate().getTime(); + } else if (Calendar.class == type) { + return (T) jcrValue.getDate(); + } else if (Value.class == type) { + return (T) jcrValue; + } + + // fallback in case of unsupported type + return null; + } + + private Class<?> normalizeClass(Class<?> type) { + if (Calendar.class.isAssignableFrom(type)) { + type = Calendar.class; + } else if (Date.class.isAssignableFrom(type)) { + type = Date.class; + } else if (Value.class.isAssignableFrom(type)) { + type = Value.class; + } else if (Property.class.isAssignableFrom(type)) { + type = Property.class; + } + return type; + } + +} \ No newline at end of file diff --git a/src/main/resources/META-INF/LICENSE b/src/main/resources/META-INF/LICENSE new file mode 100644 index 0000000..75b5248 --- /dev/null +++ b/src/main/resources/META-INF/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed 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. diff --git a/src/main/resources/META-INF/NOTICE b/src/main/resources/META-INF/NOTICE new file mode 100644 index 0000000..9f4ac5f --- /dev/null +++ b/src/main/resources/META-INF/NOTICE @@ -0,0 +1,5 @@ +Apache Sling Jackrabbit UserManager Support +Copyright 2008-2009 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (http://www.apache.org/). diff --git a/src/main/resources/OSGI-INF/metatype/metatype.properties b/src/main/resources/OSGI-INF/metatype/metatype.properties new file mode 100644 index 0000000..597ece1 --- /dev/null +++ b/src/main/resources/OSGI-INF/metatype/metatype.properties @@ -0,0 +1,36 @@ +# +# 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. +# + + +# +# This file contains localization strings for configuration labels and +# descriptions as used in the metatype.xml descriptor generated by the +# the Sling SCR plugin + +authorizable.resourceprovider.name = Resolver for UserManager resources +authorizable.resourceprovider.description = Handles resolving resources for the \ + jackrabbit UserManager. + +createUser.post.operation.name = Create User Sling Post Operation +createUser.post.operation.description = The Sling POST Operation to handle create user \ + requests in Sling. + +self.registration.enabled.name = Self-Registration Enabled +self.registration.enabled.description = When selected, the anonymous user is allowed to \ + register a new user with the system. -- To stop receiving notification emails like this one, please contact "[email protected]" <[email protected]>.
