package diskCacheV111.util;
import javax.security.auth.Subject;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.regex.PatternSyntaxException;
import diskCacheV111.vehicles.ProtocolInfo;
import org.dcache.auth.FQAN;
import org.dcache.auth.Subjects;
import org.dcache.vehicles.FileAttributes;
public class CheckStagePermission {
private static final Pattern LINE_PATTERN = Pattern.compile("\"(?<dn>[^\"]*)\"([ \t]+\"(?<fqan>[^\"]*)\"([ \t]+\"(?<su>[^\"]*)\"([ \t]+\"(?<protocol>[^\"]*)\")?)?)?");
private File _stageConfigFile;
private long _lastTimeReadingStageConfigFile;
private List<Pattern[]> _regexList;
private final boolean _isEnabled;
private final ReadWriteLock _fileReadWriteLock = new ReentrantReadWriteLock();
private final Lock _fileReadLock = _fileReadWriteLock.readLock();
private final Lock _fileWriteLock = _fileReadWriteLock.writeLock();
public CheckStagePermission(String stageConfigurationFilePath) {
if ( stageConfigurationFilePath == null || stageConfigurationFilePath.isEmpty()) {
_isEnabled = false;
return;
}
_stageConfigFile = new File(stageConfigurationFilePath);
_isEnabled = true;
}
/**
* Check whether staging is allowed for a particular subject on a particular object.
*
* @param subject The subject
* @param fileAttributes The attributes of the file
* @return true if and only if the subject is allowed to perform
* staging
*/
public boolean canPerformStaging(Subject subject,
FileAttributes fileAttributes,
ProtocolInfo protocolInfo)
throws PatternSyntaxException, IOException
{
if (!_isEnabled || Subjects.isRoot(subject)) {
return true;
}
try {
String dn = Subjects.getDn(subject);
Collection<FQAN> fqans = Subjects.getFqans(subject);
String storageClass = fileAttributes.getStorageClass();
String hsm = fileAttributes.getHsm();
String storeUnit = "";
if (storageClass != null && hsm != null) {
storeUnit = storageClass+"@"+hsm;
}
if (dn == null) {
dn = "";
}
String protocol = protocolInfo.getProtocol()+"/"+protocolInfo.getMajorVersion();
if (fqans.isEmpty()) {
return canPerformStaging(dn, null, storeUnit, protocol);
} else {
for (FQAN fqan: fqans) {
if (canPerformStaging(dn, fqan, storeUnit, protocol)) {
return true;
}
}
return false;
}
} catch (NoSuchElementException e) {
throw new IllegalArgumentException("Subject has multiple DNs");
}
}
/**
* Check whether staging is allowed for the user with given DN and FQAN
* for the object in the given storage group.
*
* @param dn user's Distinguished Name
* @param fqan user's Fully Qualified Attribute Name
* @param storeUnit object's store unit
* @return true if the user is allowed to perform staging of the object
* @throws PatternSyntaxException
* @throws IOException
*/
private boolean canPerformStaging(String dn,
FQAN fqan,
String storeUnit,
String protocol) throws PatternSyntaxException, IOException {
if ( !_isEnabled ) {
return true;
}
if ( !_stageConfigFile.exists() ) {
//if file does not exist, staging is denied for all users
return false;
}
if ( fileNeedsRereading() ) {
rereadConfig();
}
return userMatchesPredicates(dn,
Objects.toString(fqan, ""),
storeUnit,
protocol);
}
/**
* Reread the contents of the configuration file.
* @throws IOException
* @throws PatternSyntaxException
*/
private void rereadConfig() throws PatternSyntaxException, IOException {
_fileWriteLock.lock();
try {
if ( fileNeedsRereading() ) {
try (BufferedReader reader = new BufferedReader(new FileReader(_stageConfigFile))) {
_regexList = readStageConfigFile(reader);
_lastTimeReadingStageConfigFile = System
.currentTimeMillis();
}
}
} finally {
_fileWriteLock.unlock();
}
}
/**
* Check whether the stageConfigFile needs rereading.
*
* @return true if the file should be reread.
*/
private boolean fileNeedsRereading() {
long modificationTimeStageConfigFile;
modificationTimeStageConfigFile = _stageConfigFile.lastModified();
return modificationTimeStageConfigFile > _lastTimeReadingStageConfigFile;
}
/**
* Check whether the user matches predicates, that is, whether the user is in the
* list of authorized users that are allowed to perform staging of the object in the
* given storage group.
*
* @param dn user's Distinguished Name
* @param fqanStr user's FQAN as a String
* @param storeUnit object's storage unit
* @return true if the user and object match predicates
*/
private boolean userMatchesPredicates(String dn,
String fqanStr,
String storeUnit,
String protocol) {
try {
_fileReadLock.lock();
for (Pattern[] regexLine : _regexList) {
if ( regexLine[0].matcher(dn).matches() ) {
if ( regexLine[1] == null ) {
return true; // line contains only DN; DN match -> STAGE allowed
} else if ( regexLine[1].matcher(fqanStr).matches() &&
(regexLine[2] == null || regexLine[2].matcher(storeUnit).matches()) &&
(regexLine[3] == null || regexLine[3].matcher(protocol).matches())) {
//three cases covered here:
//line contains DN and FQAN; DN and FQAN match -> STAGE allowed
//line contains DN, FQAN, storeUnit; DN, FQAN, storeUnit match -> STAGE allowed
//line contains DN, FQAN, storeUnit, protocol; all match -> STAGE allowed
return true;
}
}
}
} finally {
_fileReadLock.unlock();
}
return false;
}
/**
* Read configuration file and create list of compiled patterns, containing DNs and FQANs(optionally)
* of the users that are allowed to perform staging,
* as well as storage group of the object to be staged (optionally).
*
* @param reader
* @return list of compiled patterns
* @throws IOException
* @throws PatternSyntaxException
*/
List<Pattern[]> readStageConfigFile(BufferedReader reader) throws IOException, PatternSyntaxException {
String line;
Matcher matcherLine;
List<Pattern[]> regexList = new ArrayList<>();
while ((line = reader.readLine()) != null) {
line = line.trim();
if ( line.startsWith("#") || line.isEmpty() ) { //commented or empty line
continue;
}
matcherLine = LINE_PATTERN.matcher(line);
if ( !matcherLine.matches() ) {
continue;
}
Pattern[] arrayPattern = new Pattern[4];
int i = 0;
for (String match : new String[] { matcherLine.group("dn"),
matcherLine.group("fqan"),
matcherLine.group("su"),
matcherLine.group("protocol")} ) {
if ( match != null ) {
if ( match.startsWith("!") ) {
arrayPattern[i] = Pattern.compile("(?!"+match.substring(1)+").*");
} else {
arrayPattern[i] = Pattern.compile(match);
}
}
++i;
}
regexList.add(arrayPattern);
}
return regexList;
}
}