/*
* 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.common.fqan;
import java.text.ParseException;
import org.glite.authz.common.util.Strings;
/** Represents an FQAN. */
public class FQAN {
/** Role component identifier, {@value} . */
public static final String ROLE= "Role";
/** Capability component identifier, {@value} . */
public static final String CAPABILITY= "Capability";
/**
* The value "NULL", used in the canonical form to represent the absence of
* a Role or Capability.
*/
public static final String NULL= "NULL";
/** The wildcard <code>*</code> used for pattern matching */
public static final String WILDCARD= "*";
/** Group name component of the FQAN. */
private String groupName;
/** Role component of the FQAN. */
private String role;
/** Capability component of the FQAN. */
private String capability;
/**
* Constructor.
*
* @param fqanGroupName
* group name of the FQAN, may not be null
* @param fqanRole
* role value of the FQAN, may be null
* @param fqanCapability
* capability value of the FQAN, may be null
*/
public FQAN(String fqanGroupName, String fqanRole, String fqanCapability) {
groupName= Strings.safeTrimOrNullString(fqanGroupName);
if (groupName == null) {
throw new IllegalArgumentException("Group name may not be null");
}
// remove trailing / in group name
if (groupName.endsWith("/")) {
groupName= groupName.substring(0, groupName.length() - 1);
}
role= Strings.safeTrimOrNullString(fqanRole);
if (role == null || role.equalsIgnoreCase(NULL)) {
role= NULL;
}
capability= Strings.safeTrimOrNullString(fqanCapability);
if (capability == null || capability.equalsIgnoreCase(NULL)) {
capability= NULL;
}
}
/**
* Constructor with a FQAN group name and a Role value.
*
* @param fqanGroupName
* group name of the FQAN, may not be null
* @param fqanRole
* role value of the FQAN, may be null
*/
public FQAN(String fqanGroupName, String fqanRole) {
this(fqanGroupName, fqanRole, null);
}
/**
* Constructor with a FQAN group name, but without Role (NULL).
*
* @param fqanGroupName
* group name of the FQAN, may not be null
*/
public FQAN(String fqanGroupName) {
this(fqanGroupName, null, null);
}
/**
* Gets the group name component of the FQAN.
*
* @return group name component of the FQAN
*/
public String getGroupName() {
return groupName;
}
/**
* Gets the Role value of the FQAN i.e. <code>/Role=value</code>.
*
* @return Role component of the FQAN
*/
public String getRole() {
return role;
}
/**
* Gets the Capability value of the FQAN i.e <code>/Capability=value</code>.
*
* @return Capability component of the FQAN
*/
public String getCapability() {
return capability;
}
/**
* Parses an FQAN, in string form, in to an {@link FQAN}.
*
* @param fqan
* FQAN in string form
*
* @return FQAN object
*
* @throws ParseException
* thrown if the FQAN string is invalid
*/
public static FQAN parseFQAN(String fqan) throws ParseException {
String trimmed= Strings.safeTrimOrNullString(fqan);
if (trimmed == null) {
return null;
}
if (!trimmed.startsWith("/")) {
throw new ParseException("FQANs must start with a /", 0);
}
String[] components= trimmed.split("/");
String[] subComponents;
StringBuilder groupName= new StringBuilder();
String role= null;
String capability= null;
for (String component : components) {
if (component.contains("=")) {
subComponents= component.split("=");
if (subComponents.length == 1) {
continue;
}
if (subComponents.length > 2) {
throw new ParseException("Non group name components may not contain an = in their value: "
+ component,
fqan.indexOf(component));
}
if (subComponents[0].equalsIgnoreCase(ROLE)) {
if (role != null) {
throw new ParseException("Role may not appear more than once in an FQAN",
fqan.indexOf(component));
}
role= subComponents[1];
}
else if (subComponents[0].equalsIgnoreCase(CAPABILITY)) {
if (capability != null) {
throw new ParseException("Capability may not appear more than once in an FQAN",
fqan.indexOf(component));
}
capability= subComponents[1];
}
else {
throw new ParseException("FQAN contains an unknown, non-group-name component: "
+ component,
fqan.indexOf(component));
}
}
else {
if (!Strings.isEmpty(component)) {
groupName.append("/").append(component);
}
}
}
if (groupName.length() == 0) {
throw new ParseException("FQAN did not contain a group name", 0);
}
return new FQAN(groupName.toString(), role, capability);
}
/** {@inheritDoc} */
public String toString() {
StringBuffer sb= new StringBuffer();
sb.append(groupName);
if (!role.equalsIgnoreCase(NULL)) {
sb.append('/').append(ROLE).append('=').append(role);
}
if (!capability.equalsIgnoreCase(NULL)) {
sb.append('/').append(CAPABILITY).append('=').append(capability);
}
return sb.toString();
}
/** {@inheritDoc} */
public int hashCode() {
final int prime= 31;
int result= 1;
result= prime * result + groupName.hashCode();
result= prime * result + role.hashCode();
result= prime * result + capability.hashCode();
return result;
}
/** {@inheritDoc} */
public boolean equals(Object obj) {
if (obj == null) {
return false;
}
if (this == obj) {
return true;
}
FQAN otherFQAN= (FQAN) obj;
return getGroupName().equals(otherFQAN.getGroupName())
&& getRole().equals(otherFQAN.getRole())
&& getCapability().equals(otherFQAN.getCapability());
}
/**
* Checks whether this FQAN matches the given FQAN regular expression.
*
* @param regexp
* the FQAN regular expression
*
* @return true if the FQAN matches the regular expression.
*
* @throws ParseException
* thrown if the given expression is not a valid FQAN regular
* expression
*
* @see <a
* href="https://edms.cern.ch/file/975443/1/EGEE-III-JRA1_FQAN_wildcard_v1.0.pdf">FQAN
* matching specification</a>
*/
public boolean matches(String regexp) throws ParseException {
FQAN regexpFQAN= FQAN.parseFQAN(regexp);
return matches(regexpFQAN);
}
/**
* Checks if this FQAN matches the given regular expression FQAN
*
* @param regexpFQAN
* The regular expression FQAN
* @return true if the FQAN matches the regular expression.
*
* @throws ParseException
* thrown if the given expression is not a valid FQAN regular
* expression
*/
public boolean matches(FQAN regexpFQAN) throws ParseException {
return matchesGroupName(regexpFQAN) && matchesRole(regexpFQAN);
}
/**
* Checks if the group name of this FQAN matches a group name regular
* expression. In the event that the expression does not contain the
* wildcard '*' character, exact equality matching is performed
*
* @param regexpFQAN
* the group name regular expression
*
* @return true if the given group name matches the given regular expression
*
* @throws ParseException
* thrown if the regular expression is not valid
*/
protected boolean matchesGroupName(FQAN regexpFQAN) throws ParseException {
String regexpGroup= regexpFQAN.getGroupName();
if (regexpGroup.contains(WILDCARD)) {
// group name contains a regular expression
String groupNameBase= regexpGroup.substring(0,
regexpGroup.length() - 1);
if (!groupNameBase.endsWith("/")) {
throw new ParseException("Invalid regular expression within FQAN group name, name does not end with a '/*'",
regexpGroup.length());
}
if (groupNameBase.contains(WILDCARD)) {
throw new ParseException("Invalid regular expression within FQAN group name, name contains more than one '*'",
regexpGroup.indexOf(WILDCARD));
}
if (groupNameBase.equals("/")) {
throw new ParseException("Invalid regular expression within FQAN group name, VO not specified",
0);
}
// we explicitly terminate this FQAN's group name so that we can
// easily compare
// against the regexp strings, which are terminated, the alternative
// would have
// been to strip off the terminator from the regexp string but that
// leads to problems
// ie. '/foo/* would match '/foobar'
String terminatedGroupName= groupName + "/";
return terminatedGroupName.startsWith(groupNameBase);
}
// group name does not contain a regular expression
return regexpGroup.equals(groupName);
}
/**
* Checks if the role of this FQAN matches a role regular expression. In the
* event that the expression does not contain the wildcard '*' character,
* exact equality matching is performed
*
* @param regexpFQAN
* the role regular expression
*
* @return true if the given role matches the given regular expression
*
* @throws ParseException
* thrown if the regular expression is not valid
*/
protected boolean matchesRole(FQAN regexpFQAN) throws ParseException {
String regexpRole= regexpFQAN.getRole();
if (regexpRole.contains(WILDCARD)) {
// role contains a regular expression
if (!regexpRole.equals(WILDCARD)) {
throw new ParseException("Invalid regular expression within FQAN role, role is not '*'",
regexpFQAN.toString().indexOf(regexpRole));
}
return true;
}
// role doesn't contain a regular expression
// since values are normalized
return regexpRole.equals(role);
}
}