| 1 | package diskCacheV111.util; |
| 2 | |
| 3 | import java.io.BufferedReader; |
| 4 | import java.io.File; |
| 5 | import java.io.FileReader; |
| 6 | import java.io.IOException; |
| 7 | import java.util.ArrayList; |
| 8 | import java.util.List; |
| 9 | import java.util.Collection; |
| 10 | import java.util.NoSuchElementException; |
| 11 | import java.util.concurrent.locks.Lock; |
| 12 | import java.util.concurrent.locks.ReadWriteLock; |
| 13 | import java.util.concurrent.locks.ReentrantReadWriteLock; |
| 14 | import java.util.regex.Matcher; |
| 15 | import java.util.regex.Pattern; |
| 16 | import java.util.regex.PatternSyntaxException; |
| 17 | |
| 18 | import javax.security.auth.Subject; |
| 19 | import org.dcache.auth.Subjects; |
| 20 | |
| 21 | import diskCacheV111.vehicles.StorageInfo; |
| 22 | |
| 23 | public class CheckStagePermission { |
| 24 | private File _stageConfigFile; |
| 25 | private long _lastTimeReadingStageConfigFile = 0L; |
| 26 | private List<Pattern[]> _regexList; |
| 27 | private final boolean _isEnabled; |
| 28 | |
| 29 | private final ReadWriteLock _fileReadWriteLock = new ReentrantReadWriteLock(); |
| 30 | private final Lock _fileReadLock = _fileReadWriteLock.readLock(); |
| 31 | private final Lock _fileWriteLock = _fileReadWriteLock.writeLock(); |
| 32 | |
| 33 | public CheckStagePermission(String stageConfigurationFilePath) { |
| 34 | if ( stageConfigurationFilePath == null || stageConfigurationFilePath.length() == 0 ) { |
| 35 | _isEnabled = false; |
| 36 | return; |
| 37 | } |
| 38 | |
| 39 | _stageConfigFile = new File(stageConfigurationFilePath); |
| 40 | _isEnabled = true; |
| 41 | } |
| 42 | |
| 43 | /** |
| 44 | * Check whether staging is allowed for a particular subject on a particular object. |
| 45 | * |
| 46 | * @param subject The subject |
| 47 | * @param storageInfo The storage info of the object |
| 48 | * @return true if and only if the subject is allowed to perform |
| 49 | * staging |
| 50 | */ |
| 51 | public boolean canPerformStaging(Subject subject, StorageInfo storageInfo) |
| 52 | throws PatternSyntaxException, IOException |
| 53 | { |
| 54 | if (!_isEnabled || Subjects.isRoot(subject)) |
| 55 | return true; |
| 56 | |
| 57 | try { |
| 58 | String dn = Subjects.getDn(subject); |
| 59 | Collection<String> fqans = Subjects.getFqans(subject); |
| 60 | |
| 61 | String storageClass = storageInfo.getStorageClass(); |
| 62 | String hsm = storageInfo.getHsm(); |
| 63 | |
| 64 | String storeUnit = ""; |
| 65 | if (storageClass != null && hsm != null) { |
| 66 | storeUnit = storageClass+"@"+hsm; |
| 67 | } |
| 68 | |
| 69 | if (dn == null) { |
| 70 | dn = ""; |
| 71 | } |
| 72 | |
| 73 | if (fqans.isEmpty()) { |
| 74 | return canPerformStaging(dn, "", storeUnit); |
| 75 | } else { |
| 76 | for (String fqan: fqans) { |
| 77 | if (canPerformStaging(dn, fqan, storeUnit)) |
| 78 | return true; |
| 79 | } |
| 80 | return false; |
| 81 | } |
| 82 | } catch (NoSuchElementException e) { |
| 83 | throw new IllegalArgumentException("Subject has multiple DNs"); |
| 84 | } |
| 85 | } |
| 86 | |
| 87 | /** |
| 88 | * Check whether staging is allowed for the user with given DN and FQAN |
| 89 | * for the object in the given storage group. |
| 90 | * |
| 91 | * @param dn user's Distinguished Name |
| 92 | * @param fqan user's Fully Qualified Attribute Name |
| 93 | * @param storeUnit object's store unit |
| 94 | * @return true if the user is allowed to perform staging of the object |
| 95 | * @throws PatternSyntaxException |
| 96 | * @throws IOException |
| 97 | */ |
| 98 | public boolean canPerformStaging(String dn, String fqan, String storeUnit) throws PatternSyntaxException, IOException { |
| 99 | |
| 100 | if ( !_isEnabled ) |
| 101 | return true; |
| 102 | |
| 103 | if ( !_stageConfigFile.exists() ) { |
| 104 | //if file does not exist, staging is denied for all users |
| 105 | return false; |
| 106 | } |
| 107 | |
| 108 | if ( fileNeedsRereading() ) |
| 109 | rereadConfig(); |
| 110 | |
| 111 | if (fqan==null) fqan=""; |
| 112 | |
| 113 | return userMatchesPredicates(dn, fqan, storeUnit); |
| 114 | } |
| 115 | |
| 116 | /** |
| 117 | * Reread the contents of the configuration file. |
| 118 | * @throws IOException |
| 119 | * @throws PatternSyntaxException |
| 120 | */ |
| 121 | void rereadConfig() throws PatternSyntaxException, IOException { |
| 122 | try { |
| 123 | _fileWriteLock.lock(); |
| 124 | if ( fileNeedsRereading() ) { |
| 125 | BufferedReader reader = new BufferedReader(new FileReader(_stageConfigFile)); |
| 126 | try { |
| 127 | _regexList = readStageConfigFile(reader); |
| 128 | _lastTimeReadingStageConfigFile = System.currentTimeMillis(); |
| 129 | } finally { |
| 130 | reader.close(); |
| 131 | } |
| 132 | } |
| 133 | } finally { |
| 134 | _fileWriteLock.unlock(); |
| 135 | } |
| 136 | } |
| 137 | |
| 138 | /** |
| 139 | * Check whether the stageConfigFile needs rereading. |
| 140 | * |
| 141 | * @return true if the file should be reread. |
| 142 | */ |
| 143 | boolean fileNeedsRereading() { |
| 144 | long modificationTimeStageConfigFile; |
| 145 | modificationTimeStageConfigFile = _stageConfigFile.lastModified(); |
| 146 | |
| 147 | return modificationTimeStageConfigFile > _lastTimeReadingStageConfigFile; |
| 148 | } |
| 149 | |
| 150 | /** |
| 151 | * Check whether the user matches predicates, that is, whether the user is in the |
| 152 | * list of authorized users that are allowed to perform staging of the object in the |
| 153 | * given storage group. |
| 154 | * |
| 155 | * @param dn user's Distinguished Name |
| 156 | * @param fqan user's FQAN as a String |
| 157 | * @param storeUnit object's storage unit |
| 158 | * @return true if the user and object match predicates |
| 159 | */ |
| 160 | boolean userMatchesPredicates(String dn, String fqanStr, String storeUnit) { |
| 161 | try { |
| 162 | _fileReadLock.lock(); |
| 163 | for (Pattern[] regexLine : _regexList) { |
| 164 | if ( regexLine[0].matcher(dn).matches() ) { |
| 165 | |
| 166 | if ( regexLine[1] == null ) { |
| 167 | return true; // line contains only DN; DN match -> STAGE allowed |
| 168 | } else if ( regexLine[1].matcher(fqanStr).matches() && (regexLine[2] == null || regexLine[2].matcher(storeUnit).matches()) ) { |
| 169 | return true; |
| 170 | //two cases covered here: |
| 171 | //line contains DN and FQAN; DN and FQAN match -> STAGE allowed |
| 172 | //line contains DN, FQAN, storeUnit; DN, FQAN, storeUnit match -> STAGE allowed |
| 173 | } |
| 174 | } |
| 175 | } |
| 176 | } finally { |
| 177 | _fileReadLock.unlock(); |
| 178 | } |
| 179 | return false; |
| 180 | } |
| 181 | |
| 182 | /** |
| 183 | * Read configuration file and create list of compiled patterns, containing DNs and FQANs(optionally) |
| 184 | * of the users that are allowed to perform staging, |
| 185 | * as well as storage group of the object to be staged (optionally). |
| 186 | * |
| 187 | * @param reader |
| 188 | * @return list of compiled patterns |
| 189 | * @throws IOException |
| 190 | * @throws PatternSyntaxException |
| 191 | */ |
| 192 | List<Pattern[]> readStageConfigFile(BufferedReader reader) throws IOException, PatternSyntaxException { |
| 193 | |
| 194 | String line = null; |
| 195 | Pattern linePattern = Pattern.compile("\"([^\"]*)\"([ \t]+\"([^\"]*)\"([ \t]+\"([^\"]*)\")?)?"); |
| 196 | Matcher matcherLine; |
| 197 | |
| 198 | List<Pattern[]> regexList = new ArrayList<Pattern[]>(); |
| 199 | |
| 200 | while ((line = reader.readLine()) != null) { |
| 201 | |
| 202 | line = line.trim(); |
| 203 | matcherLine = linePattern.matcher(line); |
| 204 | if ( line.startsWith("#") || line.isEmpty() ) { //commented or empty line |
| 205 | continue; |
| 206 | } |
| 207 | |
| 208 | if ( !matcherLine.matches() ) |
| 209 | continue; |
| 210 | |
| 211 | Pattern[] arrayPattern = new Pattern[3]; |
| 212 | |
| 213 | String matchDN = matcherLine.group(1); |
| 214 | String matchFQAN = matcherLine.group(3); |
| 215 | String matchStoreUnit = matcherLine.group(5); |
| 216 | |
| 217 | if ( matchFQAN != null ) { |
| 218 | if (matchStoreUnit != null) { //line: DN, FQAN, StoreUnit |
| 219 | arrayPattern[0] = Pattern.compile(matchDN); |
| 220 | arrayPattern[1] = Pattern.compile(matchFQAN); |
| 221 | arrayPattern[2] = Pattern.compile(matchStoreUnit); |
| 222 | regexList.add(arrayPattern); |
| 223 | } else { //line: DN, FQAN |
| 224 | arrayPattern[0] = Pattern.compile(matchDN); |
| 225 | arrayPattern[1] = Pattern.compile(matchFQAN); |
| 226 | arrayPattern[2] = null; |
| 227 | regexList.add(arrayPattern); |
| 228 | } |
| 229 | } else { //line: DN |
| 230 | arrayPattern[0] = Pattern.compile(matchDN); |
| 231 | arrayPattern[1] = null; |
| 232 | arrayPattern[2] = null; |
| 233 | regexList.add(arrayPattern); |
| 234 | } |
| 235 | } |
| 236 | return regexList; |
| 237 | } |
| 238 | |
| 239 | } |