blob: cf8e955c63c73a83a56fd16a3d611726dc2593fc [file] [log] [blame]
/*
* 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.impl.post;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.jackrabbit.oak.spi.security.user.AuthorizableType;
import org.apache.sling.api.request.RequestParameter;
import org.apache.sling.jackrabbit.usermanager.PrincipalNameFilter;
import org.apache.sling.jackrabbit.usermanager.PrincipalNameGenerator;
import org.apache.sling.servlets.post.SlingPostConstants;
import org.jetbrains.annotations.NotNull;
import org.osgi.service.component.annotations.Activate;
import org.osgi.service.component.annotations.Component;
import org.osgi.service.metatype.annotations.AttributeDefinition;
import org.osgi.service.metatype.annotations.Designate;
import org.osgi.service.metatype.annotations.ObjectClassDefinition;
/**
* Default implementation that generates a principal name based on a set of
* well-known request parameters
*
* <p>
* The value is resolved by the locating the first request parameter that is a
* match of one of the choices in the following order:
* </p>
* <ol>
* <li>":name" - value is the exact name to use</li>
* <li>":name@ValueFrom" - value is the name of another submitted parameter whose value is the exact name to use</li>
* <li>":nameHint" - value is filtered, trimmed and made unique</li>
* <li>":nameHint@ValueFrom" - value is the name of another submitted parameter whose value is filtered, trimmed and made unique</li>
* <li>otherwise, try the value of any configured "principalNameHints" parameters to treat as a hint that is filtered, trimmed and made unique</li>
* </ol>
*/
@Component(
configurationPid = "org.apache.sling.jackrabbit.usermanager.PrincipalNameGenerator",
service = {PrincipalNameGenerator.class})
@Designate(ocd = PrincipalNameGeneratorImpl.Config.class)
public class PrincipalNameGeneratorImpl implements PrincipalNameGenerator {
@ObjectClassDefinition(name = "Apache Sling Principal Name Generator",
description = "The Sling helper to generate a principal name from a hint")
public @interface Config {
@AttributeDefinition(name = "Maximum Principal Name Length",
description = "Maximum number of characters to "+
"use for automatically generated principal names. The default value is 20. Note, "+
"that actual principal names may be generated with at most 4 more characters if "+
"numeric suffixes must be appended to make the name unique.")
int principalNameMaxLength() default DEFAULT_MAX_NAME_LENGTH;
@AttributeDefinition(name = "Principal Name Hint Properties",
description = "The list of properties whose values "+
"may be used to derive a name for newly created principal. When handling a request "+
"to create a new principal, the name is automatically generated from this set if "+
"no \":name\" or \":nameHint\" property is provided. In this case the request "+
"parameters listed in this configuration value may be used as a hint to create the name.")
String[] principalNameHints();
}
private String[] parameterNames;
public static final int DEFAULT_MAX_NAME_LENGTH = 20;
private int maxLength = DEFAULT_MAX_NAME_LENGTH;
public PrincipalNameGeneratorImpl() {
this(null, -1);
}
public PrincipalNameGeneratorImpl(String[] parameterNames, int maxNameLength) {
if (parameterNames == null) {
this.parameterNames = new String[0];
} else {
this.parameterNames = parameterNames;
}
this.maxLength = (maxNameLength > 0)
? maxNameLength
: DEFAULT_MAX_NAME_LENGTH;
}
@Activate
protected void activate(Config config) {
this.maxLength = config.principalNameMaxLength();
this.parameterNames = config.principalNameHints();
}
/**
* Convert the value to a list of strings
*/
protected @NotNull List<String> valueToList(Object value) {
final List<String> valuesList;
if (value instanceof String[]) {
valuesList = Arrays.asList((String[])value);
} else if (value instanceof String) {
valuesList = Collections.singletonList((String)value);
} else if (value instanceof RequestParameter[]) {
valuesList = new ArrayList<>();
for (RequestParameter rp : (RequestParameter[])value) {
valuesList.add(rp.getString());
}
} else {
valuesList = Collections.emptyList();
}
return valuesList;
}
/**
* Determine the value to use for the specified parameter. This also
* considers the parameter with a {@link SlingPostConstants#VALUE_FROM_SUFFIX}
*
* @param parameters the map of request parameters
* @param paramName the parameter to get the value for
* @return the value to use for the parameter or null if it could not be determined
*/
protected String getValueToUse(Map<String, ?> parameters, String paramName) {
String valueToUse = null;
List<String> values = valueToList(parameters.get(paramName));
if (!values.isEmpty()) {
for (String specialParam : values) {
if (specialParam != null && !specialParam.isEmpty()) {
valueToUse = specialParam;
}
if (valueToUse != null) {
if (valueToUse.isEmpty()) {
// empty value is not usable
valueToUse = null;
} else {
// found value, so stop looping
break;
}
}
}
} else {
// check for a paramName@ValueFrom param
// SLING-130: VALUE_FROM_SUFFIX means take the value of this
// property from a different field
values = valueToList(parameters.get(String.format("%s%s", paramName, SlingPostConstants.VALUE_FROM_SUFFIX)));
if (!values.isEmpty()) {
for (String specialParam : values) {
if (specialParam != null && !specialParam.isEmpty()) {
// retrieve the reference parameter value
List<String> refValues = valueToList(parameters.get(specialParam));
// @ValueFrom params must have exactly one value, else ignored
if (refValues.size() == 1) {
specialParam = refValues.get(0);
if (specialParam != null && !specialParam.isEmpty()) {
valueToUse = specialParam;
}
}
}
if (valueToUse != null) {
if (valueToUse.isEmpty()) {
// empty value is not usable
valueToUse = null;
} else {
// found value, so stop looping
break;
}
}
}
}
}
return valueToUse;
}
/**
* Get a "nice" principal name, if possible, based on given request
*
* @param parameters the properties to consider when generating a name
* @param type the type of principal
* @param principalNameFilter the filter to make a value work as a principal name
* @param defaultPrincipalNameGenerator the default principal name generator
* @return the principal name to be created or null if other PrincipalNameGenerators should be consulted
*/
@Override
public NameInfo getPrincipalName(Map<String, ?> parameters, AuthorizableType type,
PrincipalNameFilter principalNameFilter, PrincipalNameGenerator defaultPrincipalNameGenerator) {
String valueToUse = null;
boolean doFilter = true;
// find the first request parameter that matches one of
// our parameterNames, in order, and has a value
// we first check for the special sling parameters
valueToUse = getValueToUse(parameters, SlingPostConstants.RP_NODE_NAME);
if (valueToUse != null) {
doFilter = false;
}
if ( valueToUse == null ) {
valueToUse = getValueToUse(parameters, SlingPostConstants.RP_NODE_NAME_HINT);
if (valueToUse == null && parameterNames != null) {
for (String param : parameterNames) {
valueToUse = getValueToUse(parameters, param);
if (valueToUse != null) {
break;
}
}
}
}
String result = valueToUse;
// should we filter?
if (doFilter && result != null && principalNameFilter != null) {
// filter value so that it works as a principal name
result = principalNameFilter.filter(result);
}
// max length
if (doFilter && result != null && result.length() > maxLength) {
result = result.substring(0, maxLength);
}
if (result != null) {
return new NameInfo(result, doFilter);
} else {
return null;
}
}
}