blob: d7c27091834f93f31f892f8d8f3aea1efc06dc8e [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.nifi.registry.properties.util;
import org.apache.commons.lang3.StringUtils;
import org.apache.nifi.registry.properties.util.IdentityMapping.Transform;
import org.apache.nifi.registry.properties.NiFiRegistryProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.apache.nifi.registry.properties.NiFiRegistryProperties.SECURITY_GROUP_MAPPING_PATTERN_PREFIX;
import static org.apache.nifi.registry.properties.NiFiRegistryProperties.SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX;
import static org.apache.nifi.registry.properties.NiFiRegistryProperties.SECURITY_GROUP_MAPPING_VALUE_PREFIX;
import static org.apache.nifi.registry.properties.NiFiRegistryProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX;
import static org.apache.nifi.registry.properties.NiFiRegistryProperties.SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX;
import static org.apache.nifi.registry.properties.NiFiRegistryProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX;
public class IdentityMappingUtil {
private static final Logger LOGGER = LoggerFactory.getLogger(IdentityMappingUtil.class);
private static final Pattern backReferencePattern = Pattern.compile("\\$(\\d+)");
/**
* Builds the identity mappings from NiFiRegistryProperties.
*
* @param properties the NiFiRegistryProperties instance
* @return a list of identity mappings
*/
public static List<IdentityMapping> getIdentityMappings(final NiFiRegistryProperties properties) {
return getMappings(
properties,
SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX,
SECURITY_IDENTITY_MAPPING_VALUE_PREFIX,
SECURITY_IDENTITY_MAPPING_TRANSFORM_PREFIX,
() -> "Identity");
}
/**
* Buils the group mappings from NiFiProperties.
*
* @param properties the NiFiProperties instance
* @return a list of group mappings
*/
public static List<IdentityMapping> getGroupMappings(final NiFiRegistryProperties properties) {
return getMappings(
properties,
SECURITY_GROUP_MAPPING_PATTERN_PREFIX,
SECURITY_GROUP_MAPPING_VALUE_PREFIX,
SECURITY_GROUP_MAPPING_TRANSFORM_PREFIX,
() -> "Group");
}
/**
* Builds the identity mappings from NiFiRegistryProperties.
*
* @param properties the NiFiRegistryProperties instance
* @return a list of identity mappings
*/
private static List<IdentityMapping> getMappings(final NiFiRegistryProperties properties, final String patternPrefix,
final String valuePrefix, final String transformPrefix, final Supplier<String> getSubject) {
final List<IdentityMapping> mappings = new ArrayList<>();
// go through each property
for (String propertyName : properties.getPropertyKeys()) {
if (StringUtils.startsWith(propertyName, patternPrefix)) {
final String key = StringUtils.substringAfter(propertyName, patternPrefix);
final String identityPattern = properties.getProperty(propertyName);
if (StringUtils.isBlank(identityPattern)) {
LOGGER.warn("{} Mapping property {} was found, but was empty", new Object[] {getSubject.get(), propertyName});
continue;
}
final String identityValueProperty = valuePrefix + key;
final String identityValue = properties.getProperty(identityValueProperty);
if (StringUtils.isBlank(identityValue)) {
LOGGER.warn("{} Mapping property {} was found, but corresponding value {} was not found",
new Object[]{propertyName, identityValueProperty});
continue;
}
final String identityTransformProperty = transformPrefix + key;
String rawIdentityTransform = properties.getProperty(identityTransformProperty);
if (StringUtils.isBlank(rawIdentityTransform)) {
LOGGER.debug("{} Mapping property {} was found, but no transform was present. Using NONE.", new Object[] {getSubject.get(), propertyName});
rawIdentityTransform = IdentityMapping.Transform.NONE.name();
}
final Transform identityTransform;
try {
identityTransform = Transform.valueOf(rawIdentityTransform);
} catch (final IllegalArgumentException iae) {
LOGGER.warn("{} Mapping property {} was found, but corresponding transform {} was not valid. Allowed values {}",
new Object[] {getSubject.get(), propertyName, rawIdentityTransform, StringUtils.join(Transform.values(), ", ")});
continue;
}
final IdentityMapping identityMapping = new IdentityMapping(key, Pattern.compile(identityPattern), identityValue, identityTransform);
mappings.add(identityMapping);
LOGGER.debug("Found {} Mapping with key = {}, pattern = {}, value = {}, transform = {}",
new Object[] {getSubject.get(), key, identityPattern, identityValue, rawIdentityTransform});
}
}
// sort the list by the key so users can control the ordering in nifi-registry.properties
Collections.sort(mappings, new Comparator<IdentityMapping>() {
@Override
public int compare(IdentityMapping m1, IdentityMapping m2) {
return m1.getKey().compareTo(m2.getKey());
}
});
return mappings;
}
/**
* Checks the given identity against each provided mapping and performs the mapping using the first one that matches.
* If none match then the identity is returned as is.
*
* @param identity the identity to map
* @param mappings the mappings
* @return the mapped identity, or the same identity if no mappings matched
*/
public static String mapIdentity(final String identity, List<IdentityMapping> mappings) {
for (IdentityMapping mapping : mappings) {
Matcher m = mapping.getPattern().matcher(identity);
if (m.matches()) {
final String pattern = mapping.getPattern().pattern();
final String replacementValue = escapeLiteralBackReferences(mapping.getReplacementValue(), m.groupCount());
final String replacement = identity.replaceAll(pattern, replacementValue);
if (Transform.UPPER.equals(mapping.getTransform())) {
return replacement.toUpperCase();
} else if (Transform.LOWER.equals(mapping.getTransform())) {
return replacement.toLowerCase();
} else {
return replacement;
}
}
}
return identity;
}
// If we find a back reference that is not valid, then we will treat it as a literal string. For example, if we have 3 capturing
// groups and the Replacement Value has the value is "I owe $8 to him", then we want to treat the $8 as a literal "$8", rather
// than attempting to use it as a back reference.
private static String escapeLiteralBackReferences(final String unescaped, final int numCapturingGroups) {
if (numCapturingGroups == 0) {
return unescaped;
}
String value = unescaped;
final Matcher backRefMatcher = backReferencePattern.matcher(value);
while (backRefMatcher.find()) {
final String backRefNum = backRefMatcher.group(1);
if (backRefNum.startsWith("0")) {
continue;
}
final int originalBackRefIndex = Integer.parseInt(backRefNum);
int backRefIndex = originalBackRefIndex;
// if we have a replacement value like $123, and we have less than 123 capturing groups, then
// we want to truncate the 3 and use capturing group 12; if we have less than 12 capturing groups,
// then we want to truncate the 2 and use capturing group 1; if we don't have a capturing group then
// we want to truncate the 1 and get 0.
while (backRefIndex > numCapturingGroups && backRefIndex >= 10) {
backRefIndex /= 10;
}
if (backRefIndex > numCapturingGroups) {
final StringBuilder sb = new StringBuilder(value.length() + 1);
final int groupStart = backRefMatcher.start(1);
sb.append(value.substring(0, groupStart - 1));
sb.append("\\");
sb.append(value.substring(groupStart - 1));
value = sb.toString();
}
}
return value;
}
}