/*
* JBoss, Home of Professional Open Source.
* Copyright 2014 Red Hat, Inc., and individual contributors
* as indicated by the @author tags.
*
* 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.wildfly.security.auth.client;
import static org.wildfly.security._private.ElytronMessages.log;
import java.net.URI;
import java.util.Arrays;
/**
* @author <a href="mailto:david.lloyd@redhat.com">David M. Lloyd</a>
*/
public abstract class MatchRule {
private final MatchRule parent;
/**
* The root rule which matches all URIs.
*/
public static final MatchRule ALL = new MatchRule(null) {
MatchRule reparent(final MatchRule newParent) {
return this;
}
public boolean isPurposeMatched() {
return false;
}
String[] getMatchPurposesRaw() {
return null;
}
public boolean isProtocolMatched() {
return false;
}
public boolean isTypeMatched() {
return false;
}
public boolean isTypeAuthorityMatched() {
return false;
}
public String getMatchProtocol() {
return null;
}
public String getMatchAbstractType() {
return null;
}
public String getMatchAbstractTypeAuthority() {
return null;
}
public boolean isHostMatched() {
return false;
}
public String getMatchHost() {
return null;
}
public boolean isPathMatched() {
return false;
}
public String getMatchPath() {
return null;
}
public boolean isPortMatched() {
return false;
}
public int getMatchPort() {
return 0;
}
public boolean isUserMatched() {
return true;
}
public String getMatchUser() {
return null;
}
public boolean isUrnNameMatched() {
return false;
}
public String getMatchUrnName() {
return null;
}
public boolean matches(final URI uri, final String abstractType, final String abstractTypeAuthority, final String purpose) {
return true;
}
MatchRule without(final Class<? extends MatchRule> clazz) {
return this;
}
boolean halfEqual(final MatchRule other) {
return true;
}
public int hashCode() {
return System.identityHashCode(this);
}
StringBuilder asString(final StringBuilder b) {
return b;
}
};
MatchRule(final MatchRule parent) {
this.parent = parent;
}
abstract MatchRule reparent(MatchRule newParent);
/**
* Determine whether this rule is equal to another object. Two rules are equal if they match the same conditions.
*
* @param other the other object
* @return {@code true} if they are equal, {@code false} otherwise
*/
public final boolean equals(final Object other) {
return other instanceof MatchRule && equals((MatchRule) other);
}
/**
* Determine whether this rule is equal to another. Two rules are equal if they match the same conditions.
*
* @param other the other object
* @return {@code true} if they are equal, {@code false} otherwise
*/
public final boolean equals(MatchRule other) {
return hashCode() == other.hashCode() && halfEqual(other) && other.halfEqual(this);
}
abstract boolean halfEqual(MatchRule other);
final boolean parentHalfEqual(MatchRule other) {
return parent.halfEqual(other);
}
/**
* Get the hash code of this rule.
*
* @return the hash code
*/
public abstract int hashCode();
final int parentHashCode() {
return parent.hashCode();
}
MatchRule without(Class<? extends MatchRule> clazz) {
if (clazz.isInstance(this)) return parent;
MatchRule newParent = parent.without(clazz);
if (parent == newParent) return this;
return reparent(newParent);
}
/**
* Determine if this rule matches the given URI.
*
* @param uri the URI to test
* @return {@code true} if the rule matches, {@code false} otherwise
*/
public final boolean matches(URI uri) {
return matches(uri, null, null, null);
}
/**
* Determine if this rule matches the given URI, type, and purpose.
*
* @param uri the URI to test
* @param abstractType the abstract type of the connection (may be {@code null})
* @param abstractTypeAuthority the authority name of the abstract type (may be {@code null})
* @return {@code true} if the rule matches, {@code false} otherwise
*/
public final boolean matches(URI uri, final String abstractType, final String abstractTypeAuthority) {
return matches(uri, abstractType, abstractTypeAuthority, null);
}
/**
* Determine if this rule matches the given URI, type, and purpose.
*
* @param uri the URI to test
* @param abstractType the abstract type of the connection (may be {@code null})
* @param abstractTypeAuthority the authority name of the abstract type (may be {@code null})
* @param purpose the authentication purpose name (may be {@code null})
* @return {@code true} if the rule matches, {@code false} otherwise
*/
public boolean matches(URI uri, final String abstractType, final String abstractTypeAuthority, final String purpose) {
return parent.matches(uri, abstractType, abstractTypeAuthority, purpose);
}
// purpose
/**
* Determine whether this rule matches based on match purpose.
*
* @return {@code true} if the rule matches based on match purpose, {@code false} otherwise
*/
public boolean isPurposeMatched() {
return parent.isPurposeMatched();
}
/**
* Get the purposes that this rule matches by.
*
* @return the purposes that this rule matches by, or {@code null} if none
*/
public String[] getMatchPurposes() {
final String[] array = getMatchPurposesRaw();
return array == null ? null : array.length == 0 ? array : array.clone();
}
String[] getMatchPurposesRaw() {
return parent.getMatchPurposesRaw();
}
/**
* Create a new rule which is the same as this rule, but also matches the given purpose name.
*
* @param purpose the purpose name
* @return the new rule
*/
public MatchRule matchPurpose(String purpose) {
if (purpose == null || purpose.equals("*")) {
return without(MatchPurposeRule.class);
}
return new MatchPurposeRule(this, new String[] { purpose });
}
/**
* Create a new rule which is the same as this rule, but also matches the given purposes name.
*
* @param purposes the purposes
* @return the new rule
*/
public MatchRule matchPurposes(String... purposes) {
if (purposes == null) {
return without(MatchPurposeRule.class);
}
purposes = purposes.clone();
int validCnt = 0;
for (String purpose : purposes) {
if (purpose != null && ! purpose.isEmpty()) {
validCnt++;
}
}
if (validCnt == 0) {
return without(MatchPurposeRule.class);
}
if (validCnt < purposes.length) {
String[] valid = new String[validCnt];
int j = 0;
for (String purpose : purposes) {
if (purpose != null && ! purpose.isEmpty()) {
valid[j++] = purpose;
}
}
purposes = valid;
}
if (validCnt > 1) Arrays.sort(purposes);
return new MatchPurposeRule(this, purposes);
}
// protocol (scheme)
/**
* Determine whether this rule matches based on URI protocol (scheme).
*
* @return {@code true} if the rule matches based on URI protocol, {@code false} otherwise
*/
public boolean isProtocolMatched() {
return parent.isProtocolMatched();
}
/**
* Get the protocol (scheme) that this rule matches, or {@code null} if this rule does not match by protocol.
*
* @return the protocol, or {@code null} if there is none
*/
public String getMatchProtocol() {
return parent.getMatchProtocol();
}
/**
* Determine whether this rule matches based on abstract type.
*
* @return {@code true} if the rule matches based on type, {@code false} otherwise
*/
public boolean isTypeMatched() {
return parent.isTypeMatched();
}
/**
* Determine whether this rule matches based on abstract type.
*
* @return {@code true} if the rule matches based on type, {@code false} otherwise
*/
public boolean isTypeAuthorityMatched() {
return parent.isTypeAuthorityMatched();
}
/**
* Get the abstract type that this rule matches, or {@code null} if this rule does not match by abstract type.
*
* @return the abstract type, or {@code null} if there is none
*/
public String getMatchAbstractType() {
return parent.getMatchAbstractType();
}
/**
* Get the abstract type authority that this rule matches, or {@code null} if this rule does not match by abstract type authority.
*
* @return the abstract type, or {@code null} if there is none
*/
public String getMatchAbstractTypeAuthority() {
return parent.getMatchAbstractTypeAuthority();
}
/**
* Create a new rule which is the same as this rule, but also matches the given protocol (scheme) name.
*
* @param protoName the protocol name to match
* @return the new rule
*/
public final MatchRule matchProtocol(String protoName) {
if (protoName == null || protoName.equals("*")) {
return without(MatchSchemeRule.class);
}
return new MatchSchemeRule(this, protoName);
}
/**
* Create a new rule which is the same as this rule, but also matches the given abstract type and type authority.
*
* @param typeName the type to match
* @param authorityName the type authority name to match
* @return the new rule
*/
public final MatchRule matchAbstractType(String typeName, String authorityName) {
MatchRule baseRule;
if (typeName == null || typeName.equals("*")) {
baseRule = without(MatchAbstractTypeRule.class);
} else {
baseRule = new MatchAbstractTypeRule(this, typeName);
}
if (authorityName == null || authorityName.equals("*")) {
return baseRule.without(MatchAbstractTypeAuthorityRule.class);
} else {
return new MatchAbstractTypeAuthorityRule(baseRule, authorityName);
}
}
// host
/**
* Determine whether this rule matches based on host name.
*
* @return {@code true} if the rule matches based on host name, {@code false} otherwise
*/
public boolean isHostMatched() {
return parent.isHostMatched();
}
/**
* Get the host name that this rule matches, or {@code null} if this rule does not match by host.
*
* @return the host name, or {@code null} if there is none
*/
public String getMatchHost() {
return parent.getMatchHost();
}
/**
* Create a new rule which is the same as this rule, but also matches the given host name.
*
* @param hostSpec the host name to match
* @return the new rule
*/
public final MatchRule matchHost(String hostSpec) {
if (hostSpec == null || hostSpec.equals("*")) {
return without(MatchHostRule.class);
}
return new MatchHostRule(this, hostSpec);
}
// path
/**
* Determine whether this rule matches based on path name.
*
* @return {@code true} if the rule matches based on path name, {@code false} otherwise
*/
public boolean isPathMatched() {
return parent.isPathMatched();
}
/**
* Get the path name that this rule matches, or {@code null} if this rule does not match by path.
*
* @return the path name, or {@code null} if there is none
*/
public String getMatchPath() {
return parent.getMatchPath();
}
/**
* Create a new rule which is the same as this rule, but also matches the given path name.
*
* @param pathSpec the path name to match
* @return the new rule
*/
public final MatchRule matchPath(String pathSpec) {
if (pathSpec == null || pathSpec.equals("**") || pathSpec.equals("/**")) {
return without(MatchPathRule.class);
}
return new MatchPathRule(this, pathSpec);
}
// port
/**
* Determine whether this rule matches based on port.
*
* @return {@code true} if the rule matches based on port, {@code false} otherwise
*/
public boolean isPortMatched() {
return parent.isPortMatched();
}
/**
* Get the port number that this rule matches, or 0 if this rule does not match by port.
*
* @return the port number, or 0 if there is none
*/
public int getMatchPort() {
return parent.getMatchPort();
}
/**
* Create a new rule which is the same as this rule, but also matches the given port number. The port number must
* be between 1 and 65535 (inclusive).
*
* @param port the port to match
* @return the new rule
*/
public final MatchRule matchPort(int port) {
if (port <= 0 || port > 65535) {
throw log.invalidPortNumber(port);
}
return new MatchPortRule(this, port);
}
// user
// internal builder operations
/**
* Determine whether this rule matches based on non-empty URI user info.
*
* @return {@code true} if the rule matches based on non-empty user info, {@code false} otherwise
*/
public boolean isUserMatched() {
return parent.isUserMatched();
}
/**
* Get the URI user info that this rule matches, or {@code null} if this rule only matches empty URI user info.
*
* @return the user info, or {@code null} if there is none
*/
public String getMatchUser() {
return parent.getMatchUser();
}
/**
* Create a new rule which is the same as this rule, but also matches the given URI user info.
*
* @param userSpec the user info to match
* @return the new rule
*/
public final MatchRule matchUser(String userSpec) {
return userSpec == null ? matchNoUser() : new MatchUserRule(this, userSpec);
}
/**
* Create a new rule which is the same as this rule, but only matches URIs with no user info.
*
* @return the new rule
*/
public final MatchRule matchNoUser() {
return new MatchNoUserRule(this);
}
/**
* Create a new rule which is the same as this rule, but matches URIs with or without user info.
*
* @return the new rule
*/
public final MatchRule matchAnyUser() {
return without(MatchUserRule.class).without(MatchNoUserRule.class);
}
// URN
/**
* Determine whether this rule matches based on URN name.
*
* @return {@code true} if the rule matches based on URN name, {@code false} otherwise
*/
public boolean isUrnNameMatched() {
return parent.isUrnNameMatched();
}
/**
* Get the URN name that this rule matches, or {@code null} if this rule does not match by URN.
*
* @return the URN name, or {@code null} if there is none
*/
public String getMatchUrnName() {
return parent.getMatchUrnName();
}
/**
* Create a new rule which is the same as this rule, but also matches the given URN name.
*
* @param name the URN name to match
* @return the new rule
*/
public final MatchRule matchUrnName(String name) {
return name == null ? without(MatchSchemeSpecificPartRule.class) : new MatchSchemeSpecificPartRule(this, name);
}
/**
* Create a new rule which is the same as this rule, but also matches the given security domain.
*
* @param name the security domain name to match
* @return the new rule
*/
public final MatchRule matchLocalSecurityDomain(String name) {
return name == null ? matchProtocol(null).matchUrnName(null) : matchProtocol("domain").matchUrnName(name);
}
// string
/**
* Get the string representation of this rule.
*
* @return the string representation of this rule
*/
public final String toString() {
final StringBuilder b = new StringBuilder();
asString(b);
if (b.length() > 1) {
b.setLength(b.length() - 1);
}
return b.toString();
}
final StringBuilder parentAsString(StringBuilder b) {
return parent.asString(b);
}
abstract StringBuilder asString(StringBuilder b);
}