Java tutorial
/* * Copyright (c) Members of the EGEE Collaboration. 2006-2010. * See http://www.eu-egee.org/partners/ for details on the copyright holders. * * Licensed 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.glite.authz.pep.obligation.dfpmap; import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Arrays; import java.util.BitSet; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.security.auth.x500.X500Principal; import org.glite.authz.common.util.Strings; import org.glite.authz.pep.obligation.ObligationProcessingException; import org.glite.voms.PKIUtils; import org.apache.commons.httpclient.URIException; import org.apache.commons.httpclient.util.URIUtil; import org.jruby.ext.posix.FileStat; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A {@link PoolAccountManager} implementation that uses the filesystem as a * persistence mechanism. * * The mapping directory must be prepopulated with files whose names represent * every pool account to be managed. */ public class GridMapDirPoolAccountManager implements PoolAccountManager { /** Class logger. */ private Logger log = LoggerFactory.getLogger(GridMapDirPoolAccountManager.class); /** Directory containing the grid mappings. */ private final File gridMapDirectory_; /** * Determine the lease filename should contains the secondary group names or * not. * <p> * Bug fix: https://savannah.cern.ch/bugs/?83317 * * @see GridMapDirPoolAccountManager#buildSubjectIdentifier(X500Principal, * String, List) */ private boolean useSecondaryGroupNamesForMapping_ = true; /** * Regexp pattern used to identify pool account names. * <p> * Contains a single group match whose value is the pool account name * prefix. * <ul> * <li>Bug fix: https://savannah.cern.ch/bugs/?66574 * <li>Bug fix: https://savannah.cern.ch/bugs/?80526 * </ul> */ private final Pattern poolAccountNamePattern_ = Pattern.compile("^([a-zA-Z][a-zA-Z0-9._-]*?)[0-9]++$"); /** * Constructor. * * @param gridMapDir * existing, readable, and writable directory where grid mappings * will be recorded * @param useSecondaryGroupNamesForMapping * if the lease filename in the gridmapDir should contains * secondary group names or not */ public GridMapDirPoolAccountManager(File gridMapDir, boolean useSecondaryGroupNamesForMapping) { if (!gridMapDir.exists()) { throw new IllegalArgumentException( "Grid map directory " + gridMapDir.getAbsolutePath() + " does not exist"); } if (!gridMapDir.canRead()) { throw new IllegalArgumentException( "Grid map directory " + gridMapDir.getAbsolutePath() + " is not readable by this process"); } if (!gridMapDir.canWrite()) { throw new IllegalArgumentException( "Grid map directory " + gridMapDir.getAbsolutePath() + " is not writable by this process"); } gridMapDirectory_ = gridMapDir; useSecondaryGroupNamesForMapping_ = useSecondaryGroupNamesForMapping; } /** {@inheritDoc} */ public List<String> getPoolAccountNamePrefixes() { ArrayList<String> poolAccountNames = new ArrayList<String>(); Matcher nameMatcher; File[] files = gridMapDirectory_.listFiles(); for (File file : files) { if (file.isFile()) { nameMatcher = poolAccountNamePattern_.matcher(file.getName()); if (nameMatcher.matches() && !poolAccountNames.contains(nameMatcher.group(1))) { poolAccountNames.add(nameMatcher.group(1)); } } } return poolAccountNames; } /** {@inheritDoc} */ public List<String> getPoolAccountNames() { return Arrays.asList(getAccountFileNames(null)); } /** {@inheritDoc} */ public List<String> getPoolAccountNames(String prefix) { return Arrays.asList(getAccountFileNames(Strings.safeTrimOrNullString(prefix))); } /** {@inheritDoc} */ public boolean isPoolAccountPrefix(String accountIndicator) { return accountIndicator.startsWith("."); } /** {@inheritDoc} */ public String getPoolAccountPrefix(String accountIndicator) { if (isPoolAccountPrefix(accountIndicator)) { return accountIndicator.substring(1); } return null; } /** * {@inheritDoc} * <ul> * <li>BUG FIX: https://savannah.cern.ch/bugs/index.php?83281 * <li>BUG FIX: https://savannah.cern.ch/bugs/index.php?84846 * </ul> * */ public String mapToAccount(String accountNamePrefix, X500Principal subjectDN, String primaryGroup, List<String> secondaryGroups) throws ObligationProcessingException { String subjectIdentifier = buildSubjectIdentifier(subjectDN, primaryGroup, secondaryGroups); File subjectIdentifierFile = new File(buildSubjectIdentifierFilePath(subjectIdentifier)); log.debug( "Checking if there is an existing account mapping for subject {} with primary group {} and secondary groups {}", new Object[] { subjectDN.getName(), primaryGroup, secondaryGroups }); String accountName = getAccountNameByKey(accountNamePrefix, subjectIdentifier); if (accountName != null) { // BUG FIX: https://savannah.cern.ch/bugs/index.php?83281 // touch the subjectIdentifierFile every time a mapping is re-done. PosixUtil.touchFile(subjectIdentifierFile); log.debug( "An existing account mapping has mapped subject {} with primary group {} and secondary groups {} to pool account {}", new Object[] { subjectDN.getName(), primaryGroup, secondaryGroups, accountName }); return accountName; } accountName = createMapping(accountNamePrefix, subjectIdentifier); if (accountName != null) { // BUG FIX: https://savannah.cern.ch/bugs/index.php?84846 // touch the subjectIdentifierFile the first time a mapping is done. PosixUtil.touchFile(subjectIdentifierFile); log.debug( "A new account mapping has mapped subject {} with primary group {} and secondary groups {} to pool account {}", new Object[] { subjectDN.getName(), primaryGroup, secondaryGroups, accountName }); } else { log.debug( "No pool account was available to which subject {} with primary group {} and secondary groups {} could be mapped", new Object[] { subjectDN.getName(), primaryGroup, secondaryGroups }); } return accountName; } /** * Gets the user account to which a given subject had previously been * mapped. * * @param accountNamePrefix * prefix of the account to which the subject should be mapped * @param subjectIdentifier * key identifying the subject * * @return account to which the subject was mapped or null if not map * currently exists * * @throws ObligationProcessingException * thrown if the link count on the pool account name file or the * account key file is more than 2 */ private String getAccountNameByKey(String accountNamePrefix, String subjectIdentifier) throws ObligationProcessingException { File subjectIdentifierFile = new File(buildSubjectIdentifierFilePath(subjectIdentifier)); // the file doesn't exit yet!!! if (!subjectIdentifierFile.exists()) { return null; } FileStat subjectIdentifierFileStat = PosixUtil.getFileStat(subjectIdentifierFile.getAbsolutePath()); if (subjectIdentifierFileStat.nlink() != 2) { log.error("The subject identifier file " + subjectIdentifierFile.getAbsolutePath() + " has a link count greater than 2. This mapping is corrupted and can not be used."); throw new ObligationProcessingException("Unable to map subject to a POSIX account"); } FileStat accountFileStat; for (File accountFile : getAccountFiles(accountNamePrefix)) { accountFileStat = PosixUtil.getFileStat(accountFile.getAbsolutePath()); if (accountFileStat.ino() == subjectIdentifierFileStat.ino()) { if (accountFileStat.nlink() != 2) { log.error("The pool account file " + accountFile.getAbsolutePath() + " has a link count greater than 2. This mapping is corrupted and can not be used."); } return accountFile.getName(); } } return null; } /** * Creates a mapping between an account and a subject identified by the * account key. * * @param accountNamePrefix * prefix of the pool account names * @param subjectIdentifier * key identifying the subject mapped to the account * * @return the account to which the subject was mapped or null if not * account was available */ protected String createMapping(String accountNamePrefix, String subjectIdentifier) { FileStat accountFileStat; for (File accountFile : getAccountFiles(accountNamePrefix)) { log.debug("Checking if grid map account {} may be linked to subject identifier {}", accountFile.getName(), subjectIdentifier); String subjectIdentifierFilePath = buildSubjectIdentifierFilePath(subjectIdentifier); accountFileStat = PosixUtil.getFileStat(accountFile.getAbsolutePath()); if (accountFileStat.nlink() == 1) { PosixUtil.createHardlink(accountFile.getAbsolutePath(), subjectIdentifierFilePath); accountFileStat = PosixUtil.getFileStat(accountFile.getAbsolutePath()); if (accountFileStat.nlink() == 2) { log.debug("Linked subject identifier {} to pool account file {}", subjectIdentifier, accountFile.getName()); return accountFile.getName(); } new File(subjectIdentifierFilePath).delete(); } log.debug("Could not map to account {}", accountFile.getName()); } log.warn("{} pool account is full. Impossible to map {}", accountNamePrefix, subjectIdentifier); return null; } /** * Creates an identifier (lease filename) for the subject that is based on * the subject's DN and primary and secondary groups. The secondary groups * are only included in the identifier if the * {@link #useSecondaryGroupNamesForMapping_} is <code>true</code>. * <p> * Implements the legacy gLExec LCAS/LCMAP lease filename encoding. * <ul> * <li>BUG FIX: https://savannah.cern.ch/bugs/index.php?83419 * <li>Bug fix: https://savannah.cern.ch/bugs/?83317 * </ul> * * @param subjectDN * DN of the subject * @param primaryGroupName * primary group to which the subject was assigned, may be null * @param secondaryGroupNames * ordered list of secondary groups to which the subject * assigned, may be null * * @return the identifier for the subject */ protected String buildSubjectIdentifier(X500Principal subjectDN, String primaryGroupName, List<String> secondaryGroupNames) { StringBuilder identifier = new StringBuilder(); try { String openSSLPrincipal = PKIUtils.getOpenSSLFormatPrincipal(subjectDN, true); // BUG FIX: https://savannah.cern.ch/bugs/index.php?83419 // encode using the legacy gLExec LCAS/LCMAP algorithm String encodedId = encodeSubjectIdentifier(openSSLPrincipal); identifier.append(encodedId); } catch (URIException e) { throw new RuntimeException("Charset required to be supported by JVM but is not available", e); } if (primaryGroupName != null) { identifier.append(":").append(primaryGroupName); } // BUG FIX: https://savannah.cern.ch/bugs/?83317 // use or not secondary groups in lease filename if (useSecondaryGroupNamesForMapping_ && secondaryGroupNames != null && !secondaryGroupNames.isEmpty()) { for (String secondaryGroupName : secondaryGroupNames) { identifier.append(":").append(secondaryGroupName); } } return identifier.toString(); } /** * Alpha numeric characters set: <code>[0-9a-zA-Z]</code> */ protected static final BitSet ALPHANUM = new BitSet(256); // Static initializer for alphanum static { for (int i = 'a'; i <= 'z'; i++) { ALPHANUM.set(i); } for (int i = 'A'; i <= 'Z'; i++) { ALPHANUM.set(i); } for (int i = '0'; i <= '9'; i++) { ALPHANUM.set(i); } } /** * Encodes the unescaped subject identifier, typically the user DN. * <p> * Implements the legacy string encoding used by gLExec LCAS/LCMAP for the * lease file names: * <ul> * <li>URL encode all no alpha-numeric characters <code>[0-9a-zA-Z]</code> * <li>apply lower case * </ul> * * @param unescaped * The unescaped user DN * @return encoded, escaped, user DN, compatible with gLExec * @throws URIException */ protected String encodeSubjectIdentifier(String unescaped) throws URIException { String encoded = URIUtil.encode(unescaped, ALPHANUM); return encoded.toLowerCase(); } /** * Builds the absolute path to the subject identifier file. * * @param subjectIdentifier * the subject identifier * * @return the absolute path to the subject identifier file */ protected String buildSubjectIdentifierFilePath(String subjectIdentifier) { return gridMapDirectory_.getAbsolutePath() + File.separator + subjectIdentifier; } /** * Gets a list of account files where the file names begin with the given * prefix. * <ul> * <li>BUG FIX: https://savannah.cern.ch/bugs/?66574 * </ul> * * @param prefix * prefix with which the file names should begin, may be null to * signify all file names * * @return the selected account files */ private File[] getAccountFiles(final String prefix) { return gridMapDirectory_.listFiles(new FilenameFilter() { public boolean accept(File dir, String name) { Matcher nameMatcher = poolAccountNamePattern_.matcher(name); if (nameMatcher.matches()) { // BUG FIX: https://savannah.cern.ch/bugs/?66574 if (prefix == null || prefix.equals(nameMatcher.group(1))) { return true; } } return false; } }); } /** * Gets a list of account file names where the names begin with the given * prefix. * <ul> * <li>BUG FIX: https://savannah.cern.ch/bugs/?66574 * </ul> * * @param prefix * prefix with which the file names should begin, may be null to * signify all file names * * @return the selected account file names */ private String[] getAccountFileNames(final String prefix) { return gridMapDirectory_.list(new FilenameFilter() { public boolean accept(File dir, String name) { Matcher nameMatcher = poolAccountNamePattern_.matcher(name); if (nameMatcher.matches()) { // BUG FIX: https://savannah.cern.ch/bugs/?66574 if (prefix == null || prefix.equals(nameMatcher.group(1))) { return true; } } return false; } }); } /** * @param useSecondaryGroupNamesForMapping * the useSecondaryGroupNamesForMapping_ to set */ protected void setUseSecondaryGroupNamesForMapping(boolean useSecondaryGroupNamesForMapping) { this.useSecondaryGroupNamesForMapping_ = useSecondaryGroupNamesForMapping; } }