blob: 3c9208c09a0aa05ff355e1d0fc4305ed59805633 [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.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.regex.Matcher;
import java.util.regex.Pattern;
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) {
final List<IdentityMapping> mappings = new ArrayList<>();
// go through each property
for (String propertyName : properties.getPropertyKeys()) {
if (StringUtils.startsWith(propertyName, NiFiRegistryProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX)) {
final String key = StringUtils.substringAfter(propertyName, NiFiRegistryProperties.SECURITY_IDENTITY_MAPPING_PATTERN_PREFIX);
final String identityPattern = properties.getProperty(propertyName);
if (StringUtils.isBlank(identityPattern)) {
LOGGER.warn("Identity Mapping property {} was found, but was empty", new Object[]{propertyName});
continue;
}
final String identityValueProperty = NiFiRegistryProperties.SECURITY_IDENTITY_MAPPING_VALUE_PREFIX + key;
final String identityValue = properties.getProperty(identityValueProperty);
if (StringUtils.isBlank(identityValue)) {
LOGGER.warn("Identity Mapping property {} was found, but corresponding value {} was not found",
new Object[]{propertyName, identityValueProperty});
continue;
}
final IdentityMapping identityMapping = new IdentityMapping(key, Pattern.compile(identityPattern), identityValue);
mappings.add(identityMapping);
LOGGER.debug("Found Identity Mapping with key = {}, pattern = {}, value = {}",
new Object[] {key, identityPattern, identityValue});
}
}
// 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());
return identity.replaceAll(pattern, replacementValue);
}
}
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;
}
}