org.apache.hadoop.fs.azurebfs.utils.TextFileBasedIdentityHandler Maven / Gradle / Ivy
/**
* 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.hadoop.fs.azurebfs.utils;
import org.apache.hadoop.thirdparty.com.google.common.base.Preconditions;
import org.apache.hadoop.thirdparty.com.google.common.base.Strings;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.LineIterator;
import org.apache.hadoop.io.IOUtils;
import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.COLON;
import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.EMPTY_STRING;
import static org.apache.hadoop.fs.azurebfs.constants.AbfsHttpConstants.HASH;
/**
* {@code TextFileBasedIdentityHandler} is a {@link IdentityHandler} implements
* translation operation which returns identity mapped to AAD identity by
* loading the mapping file from the configured location. Location of the
* mapping file should be configured in {@code core-site.xml}.
*
* User identity file should be delimited by colon in below format.
*
* # OBJ_ID:USER_NAME:USER_ID:GROUP_ID:SPI_NAME:APP_ID
*
*
* Example:
*
* a2b27aec-77bd-46dd-8c8c-39611a333331:user1:11000:21000:spi-user1:abcf86e9-5a5b-49e2-a253-f5c9e2afd4ec
*
*
* Group identity file should be delimited by colon in below format.
*
* # OBJ_ID:GROUP_NAME:GROUP_ID:SGP_NAME
*
*
* Example:
*
* 1d23024d-957c-4456-aac1-a57f9e2de914:group1:21000:sgp-group1
*
*/
public class TextFileBasedIdentityHandler implements IdentityHandler {
private static final Logger LOG = LoggerFactory.getLogger(TextFileBasedIdentityHandler.class);
/**
* Expected no of fields in the user mapping file.
*/
private static final int NO_OF_FIELDS_USER_MAPPING = 6;
/**
* Expected no of fields in the group mapping file.
*/
private static final int NO_OF_FIELDS_GROUP_MAPPING = 4;
/**
* Array index for the local username.
* Example:
* a2b27aec-77bd-46dd-8c8c-39611a333331:user1:11000:21000:spi-user1:abcf86e9-5a5b-49e2-a253-f5c9e2afd4ec
*/
private static final int ARRAY_INDEX_FOR_LOCAL_USER_NAME = 1;
/**
* Array index for the security group name.
* Example:
* 1d23024d-957c-4456-aac1-a57f9e2de914:group1:21000:sgp-group1
*/
private static final int ARRAY_INDEX_FOR_LOCAL_GROUP_NAME = 1;
/**
* Array index for the AAD Service Principal's Object ID.
*/
private static final int ARRAY_INDEX_FOR_AAD_SP_OBJECT_ID = 0;
/**
* Array index for the AAD Security Group's Object ID.
*/
private static final int ARRAY_INDEX_FOR_AAD_SG_OBJECT_ID = 0;
private String userMappingFileLocation;
private String groupMappingFileLocation;
private HashMap userMap;
private HashMap groupMap;
public TextFileBasedIdentityHandler(String userMappingFilePath, String groupMappingFilePath) {
Preconditions.checkArgument(!Strings.isNullOrEmpty(userMappingFilePath),
"Local User to Service Principal mapping filePath cannot by Null or Empty");
Preconditions.checkArgument(!Strings.isNullOrEmpty(groupMappingFilePath),
"Local Group to Security Group mapping filePath cannot by Null or Empty");
this.userMappingFileLocation = userMappingFilePath;
this.groupMappingFileLocation = groupMappingFilePath;
//Lazy Loading
this.userMap = new HashMap<>();
this.groupMap = new HashMap<>();
}
/**
* Perform lookup from Service Principal's Object ID to Local Username.
* @param originalIdentity AAD object ID.
* @return Local User name, if no name found or on exception, returns empty string.
* */
public synchronized String lookupForLocalUserIdentity(String originalIdentity) throws IOException {
if(Strings.isNullOrEmpty(originalIdentity)) {
return EMPTY_STRING;
}
if (userMap.size() == 0) {
loadMap(userMap, userMappingFileLocation, NO_OF_FIELDS_USER_MAPPING, ARRAY_INDEX_FOR_AAD_SP_OBJECT_ID);
}
try {
String username = !Strings.isNullOrEmpty(userMap.get(originalIdentity))
? userMap.get(originalIdentity).split(COLON)[ARRAY_INDEX_FOR_LOCAL_USER_NAME] : EMPTY_STRING;
return username;
} catch (ArrayIndexOutOfBoundsException e) {
LOG.error("Error while parsing the line, returning empty string", e);
return EMPTY_STRING;
}
}
/**
* Perform lookup from Security Group's Object ID to Local Security Group name.
* @param originalIdentity AAD object ID.
* @return Local Security group name, if no name found or on exception, returns empty string.
* */
public synchronized String lookupForLocalGroupIdentity(String originalIdentity) throws IOException {
if(Strings.isNullOrEmpty(originalIdentity)) {
return EMPTY_STRING;
}
if (groupMap.size() == 0) {
loadMap(groupMap, groupMappingFileLocation, NO_OF_FIELDS_GROUP_MAPPING,
ARRAY_INDEX_FOR_AAD_SG_OBJECT_ID);
}
try {
String groupname =
!Strings.isNullOrEmpty(groupMap.get(originalIdentity))
? groupMap.get(originalIdentity).split(COLON)[ARRAY_INDEX_FOR_LOCAL_GROUP_NAME] : EMPTY_STRING;
return groupname;
} catch (ArrayIndexOutOfBoundsException e) {
LOG.error("Error while parsing the line, returning empty string", e);
return EMPTY_STRING;
}
}
/**
* Creates the map from the file using the key index.
* @param cache Instance of cache object to store the data.
* @param fileLocation Location of the file to be loaded.
* @param keyIndex Index of the key from the data loaded from the key.
*/
private static void loadMap(HashMap cache, String fileLocation, int noOfFields, int keyIndex)
throws IOException {
LOG.debug("Loading identity map from file {}", fileLocation);
int errorRecord = 0;
File file = new File(fileLocation);
LineIterator it = null;
try {
it = FileUtils.lineIterator(file, "UTF-8");
while (it.hasNext()) {
String line = it.nextLine();
if (!Strings.isNullOrEmpty(line.trim()) && !line.startsWith(HASH)) {
if (line.split(COLON).length != noOfFields) {
errorRecord += 1;
continue;
}
cache.put(line.split(COLON)[keyIndex], line);
}
}
LOG.debug("Loaded map stats - File: {}, Loaded: {}, Error: {} ", fileLocation, cache.size(), errorRecord);
} catch (ArrayIndexOutOfBoundsException e) {
LOG.error("Error while parsing mapping file", e);
} finally {
IOUtils.cleanupWithLogger(LOG, it);
}
}
}