/* * The Kuali Financial System, a comprehensive financial management system for higher education. * * Copyright 2005-2014 The Kuali Foundation * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.kuali.kfs.sys.identity; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.apache.commons.lang.StringUtils; import org.kuali.rice.kim.api.KimConstants; import org.kuali.rice.kim.api.permission.Permission; import org.kuali.rice.krad.kim.NamespaceWildcardAllowedAndOrStringExactMatchPermissionTypeServiceImpl; /** * This is a permission type service with attributes for wildcard matching on the namespace and/or * wildcard matching on another attribute (set in the spring config). * * The permissions returned are the most exact matches found and namespace exact matching takes * priority over string attribute exact matching. There is no difference in priority between two attributes * which both match partially (i.e. staging/* and staging/sys/*) * * Priority Namespace String attribute * 1. Exact Exact * 2. Exact Partial * 3. Exact Blank * 4. Partial Exact * 5. Partial Partial * 6. Partial Blank * 7. Blank Exact * 8. Blank Partial */ public class NamespaceWildcardAllowedAndOrStringWildcardAllowedPermissionTypeServiceImpl extends NamespaceWildcardAllowedAndOrStringExactMatchPermissionTypeServiceImpl { org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(NamespaceWildcardAllowedAndOrStringWildcardAllowedPermissionTypeServiceImpl.class); protected static final String NAMESPACE_CODE = KimConstants.AttributeConstants.NAMESPACE_CODE; protected boolean namespaceRequiredOnStoredAttributeSet; /** * Check for entries that match the namespace or the string attribute identified in 'wildcardMatchStringAttributeName'. * Return the ones which are the most specific. * * I.e., matches best. KFS-SYS will have priority over KFS-* * * @see org.kuali.rice.kim.service.support.impl.KimPermissionTypeServiceBase#performPermissionMatches(org.kuali.rice.kim.bo.types.dto.AttributeSet, java.util.List) */ @Override protected List<Permission> performPermissionMatches(Map<String, String> requestedDetails, List<Permission> permissionsList) { if (LOG.isDebugEnabled()) { LOG.debug("requested details = "+prettyPrintAttributeSet(requestedDetails)); LOG.debug("number of permissions to check against: "+permissionsList.size()); } List<Permission> matchingPermissions = new ArrayList<Permission>(); List<Permission> exactNamespacePermissions = new ArrayList<Permission>(); List<Permission> partialNamespacePermissions = new ArrayList<Permission>(); List<Permission> blankNamespacePermissions = new ArrayList<Permission>(); String requestedNamespaceAttributeValue = requestedDetails.get(NAMESPACE_CODE); for ( Permission kpi : permissionsList ) { String permissionNamespaceAttributeValue = kpi.getAttributes().get(NAMESPACE_CODE); if ( matchExact(requestedNamespaceAttributeValue, permissionNamespaceAttributeValue) ) { exactNamespacePermissions.add(kpi); } else if ( matchPartial(requestedNamespaceAttributeValue, permissionNamespaceAttributeValue) ) { partialNamespacePermissions.add(kpi); } else if ( matchBlank(permissionNamespaceAttributeValue) ) { blankNamespacePermissions.add(kpi); } } // if the exact match worked, use those when checking the string attribute if ( !exactNamespacePermissions.isEmpty() ) { matchingPermissions = performStringAttributeMatching(requestedDetails, getWildcardMatchStringAttributeName(), exactNamespacePermissions); //found exact namespace match and exact, partial, or blank string attribute match if (!matchingPermissions.isEmpty()) { if (LOG.isDebugEnabled()) { LOG.info("found exact namespace match and exact, partial, or blank string attribute match"); } return matchingPermissions; } } // if the partial match worked, use those when checking the string attribute if ( !partialNamespacePermissions.isEmpty() ) { matchingPermissions = performStringAttributeMatching(requestedDetails, getWildcardMatchStringAttributeName(), partialNamespacePermissions); //found partial namespace match and exact, partial, or blank string attribute match if (!matchingPermissions.isEmpty()) { if (LOG.isDebugEnabled()) { LOG.debug("found partial namespace match and exact, partial, or blank string attribute match"); } return matchingPermissions; } } //don't match on a null namespace when it is required if (!getNamespaceRequiredOnStoredAttributeSet()) { // if the blank match worked, use those when checking the string attribute if ( !blankNamespacePermissions.isEmpty() ) { matchingPermissions = performStringAttributeMatching(requestedDetails, getWildcardMatchStringAttributeName(), blankNamespacePermissions); //found blank namespace match and exact, partial, or blank string attribute match if (!matchingPermissions.isEmpty()) { if (LOG.isDebugEnabled()) { LOG.debug("found blank namespace match and exact, partial, or blank string attribute match"); } return matchingPermissions; } } } if (LOG.isDebugEnabled()) { LOG.debug("found no matching permissions"); } return matchingPermissions; // empty list } protected String prettyPrintAttributeSet(Map<String, String> attributeSet) { StringBuilder sb = new StringBuilder(); for (String key: attributeSet.keySet()) { sb.append(key); sb.append(" => "); sb.append(attributeSet.get(key)); sb.append("; "); } return sb.toString(); } /** * Determines which existing permissions apply in the case which best matches the requested details * @param requestedDetails the details to find the best permission matches for * @param attributeNameForMatching the attribute name of the wildcard string to match against * @param permissionsList the list of permissions for this permission type * @return the list of matching permissions */ private List<Permission> performStringAttributeMatching(Map<String, String> requestedDetails, String attributeNameForMatching, List<Permission> permissionsList) { String requestedAttributeValue = requestedDetails.get(attributeNameForMatching); List<Permission> exactMatchingPermissions = new ArrayList<Permission>(); List<Permission> partialMatchingPermissions = new ArrayList<Permission>(); List<Permission> blankMatchingPermissions = new ArrayList<Permission>(); //check attribute matching for ( Permission kpi : permissionsList ) { String permissionAttributeValue = kpi.getAttributes().get(attributeNameForMatching); if ( matchExact(requestedAttributeValue, permissionAttributeValue) ) { exactMatchingPermissions.add(kpi); } else if ( matchPartial(requestedAttributeValue, permissionAttributeValue) ) { partialMatchingPermissions.add(kpi); } else if ( matchBlank(permissionAttributeValue) ) { blankMatchingPermissions.add(kpi); } } if (!exactMatchingPermissions.isEmpty()) { return exactMatchingPermissions; } if (!partialMatchingPermissions.isEmpty()) { return partialMatchingPermissions; } if (!blankMatchingPermissions.isEmpty()) { return blankMatchingPermissions; } return exactMatchingPermissions; // empty list } /** * Returns true if the attributes match exactly * * @param requestedAttributeValue * @param permissionAttributeValue * @return */ protected boolean matchExact(String requestedAttributeValue, String permissionAttributeValue) { return StringUtils.equals(requestedAttributeValue, permissionAttributeValue); } /** * Returns true if the permissionAttribute matches part of the requestedAttribute * * @param requestedAttributeValue * @param permissionAttributeValue * @return */ protected boolean matchPartial(String requestedAttributeValue, String permissionAttributeValue) { if (requestedAttributeValue != null && permissionAttributeValue != null) { //replace any '*' with '.*' for regex matching permissionAttributeValue = permissionAttributeValue.replaceAll("\\*", ".*"); return requestedAttributeValue.matches(permissionAttributeValue); } return false; } /** * Returns true if the permissionAttribute is blank * * @param permissionAttributeValue * @return */ protected boolean matchBlank(String permissionAttributeValue) { return StringUtils.isBlank(permissionAttributeValue); } /** * @return the attribute to match wildcards against */ protected String getWildcardMatchStringAttributeName() { return this.exactMatchStringAttributeName; } /** * @return true if the namespace is required in saved attribute sets using this permission type, false otherwise */ protected boolean getNamespaceRequiredOnStoredAttributeSet() { return this.namespaceRequiredOnStoredAttributeSet; } public void setWildcardMatchStringAttributeName(String wildcardMatchStringAttributeName) { super.setExactMatchStringAttributeName(wildcardMatchStringAttributeName); } public void setNamespaceRequiredOnStoredAttributeSet(boolean namespaceRequiredOnStoredAttributeSet) { this.namespaceRequiredOnStoredAttributeSet = namespaceRequiredOnStoredAttributeSet; } /** * Exact match helper method that isn't applicable in wildcard match * @see org.kuali.rice.krad.kim.NamespaceWildcardAllowedAndOrStringExactMatchPermissionTypeServiceImpl#setExactMatchStringAttributeName(java.lang.String) */ @Override public void setExactMatchStringAttributeName(String exactMatchStringAttributeName) { throw new UnsupportedOperationException(); } }