blob: 5754e81e3a5a0dbd5ea08b5ccee4577b3bc7057a [file] [log] [blame]
// Copyright 2009 Google Inc.
//
// 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 com.google.enterprise.secmgr.config;
import com.google.common.base.Function;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.enterprise.secmgr.json.TypeAdapters;
import com.google.enterprise.secmgr.json.TypeProxy;
import com.google.gson.GsonBuilder;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.List;
import java.util.Locale;
import java.util.regex.Pattern;
import javax.annotation.concurrent.ThreadSafe;
/**
* The configuration of an authentication mechanism. This is the base class for
* all mechanisms; individual mechanisms inherit from here.
*/
@ThreadSafe
public abstract class AuthnMechanism {
private static final int CONFIG_NAME_MAX_LENGTH = 200;
private static final Pattern CONFIG_NAME_PATTERN
= Pattern.compile("^[a-zA-Z0-9_][a-zA-Z0-9_-]*$");
private final String name;
private final AuthnAuthority authority;
protected AuthnMechanism() {
name = null;
authority = AuthnAuthority.make();
}
protected AuthnMechanism(String name) {
// TODO(cph): eliminate name == null once all callers supply real names.
// Right now the only callers with name == null are unit tests.
this.name = name;
if (name == null) {
authority = AuthnAuthority.make();
} else {
Preconditions.checkArgument(isValidConfigName(name), "Invalid mechanism name: %s", name);
authority = AuthnAuthority.make(name);
}
}
/**
* Is a given string a valid name for a mechanism or credential group? A
* valid name is between 1 and 200 characters and consists only of ASCII
* alphanumerics, the underscore character, and the hyphen character. The
* first character may not be a hyphen.
*
* @param name The string to test for validity.
* @return True only if the string is valid.
*/
public static boolean isValidConfigName(String name) {
return name != null
&& !name.isEmpty()
&& name.length() <= CONFIG_NAME_MAX_LENGTH
&& CONFIG_NAME_PATTERN.matcher(name).matches();
}
/**
* Check a string argument that's allowed to be null. Disallows empty
* strings. May return a modified string; e.g. may run .trim() on the
* argument.
*
* @param string The string to check.
* @return A checked string that is possibly different from the argument.
* @throws IllegalArgumentException if the input string is empty.
*/
protected static String checkStringOrNull(String string) {
Preconditions.checkArgument(string == null || !string.isEmpty());
return string;
}
/**
* Check a string argument. Disallows null and empty strings. May return a
* modified string; e.g. may run .trim() on the argument.
*
* @param string The string to check.
* @return A checked string that is possibly different from the argument.
* @throws IllegalArgumentException if the input string is null or empty.
*/
protected static String checkString(String string) {
Preconditions.checkArgument(string != null && !string.isEmpty());
return string;
}
/**
* Check a URL string argument. Disallows null and non-well-formed strings.
* May return a modified string; e.g. may run .trim() on the argument.
*
* @param string The URL string to check.
* @return A checked URL string that is possibly different from the argument.
* @throws IllegalArgumentException if the input string is null or not well formed.
*/
protected static String checkUrlString(String string) {
try {
return (new URL(checkString(string))).toString();
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
}
/**
* Check a trust-duration argument.
*
* @param trustDuration The trust-duration argument to check.
* @return A checked value.
* @throws IllegalArgumentException if the argument is invalid.
*/
protected static long checkTrustDuration(long trustDuration) {
Preconditions.checkArgument(trustDuration >= 0);
return trustDuration;
}
/**
* Get the type name of the mechanism.
*
* @return The type name, never null or empty.
*/
public abstract String getTypeName();
/**
* @return An immutable list of the transformations this mechanism implements.
*/
public abstract List<CredentialTransform> getCredentialTransforms();
/**
* Can this mechanism use ULF credentials?
*/
public boolean canUseUlfCredentials() {
return Iterables.any(getCredentialTransforms(),
new Predicate<CredentialTransform>() {
public boolean apply(CredentialTransform credentialTransform) {
CredentialTypeSet inputs = credentialTransform.getInputs();
return !inputs.getAreVerified()
&& inputs.getElements().containsAll(
CredentialTypeSet.PRINCIPAL_AND_PASSWORD.getElements());
}
});
}
/**
* Return a copy of the current mechanism, with a given name.
*
* @param name The new name.
* @return A copy of the current mechanism with the new name.
*/
public abstract AuthnMechanism copyWithNewName(String name);
/**
* @return This mechanism's name.
*/
public String getName() {
return name;
}
/**
* @return This mechanism's authority.
*/
public AuthnAuthority getAuthority() {
return authority;
}
public static Function<AuthnMechanism, AuthnAuthority> getAuthorityFunction() {
return AUTHORITY_FUNCTION;
}
private static final Function<AuthnMechanism, AuthnAuthority> AUTHORITY_FUNCTION
= new Function<AuthnMechanism, AuthnAuthority>() {
@Override
public AuthnAuthority apply(AuthnMechanism mechanism) {
return mechanism.getAuthority();
}
};
/**
* A sample URL to GET for credential verification.
*
* @return The mechanism's sample URL as a string, or null if none.
*/
public String getSampleUrl() {
throw new UnsupportedOperationException();
}
/**
* For how many milliseconds does this mechanism trust newly-verified
* credentials?
*
* @return The number of milliseconds, always a non-negative number.
*/
public long getTrustDuration() {
throw new UnsupportedOperationException();
}
protected boolean equals(AuthnMechanism other) {
return getName() == null
|| other.getName() == null
|| getName().equalsIgnoreCase(other.getName());
}
protected int hashCode(Object... objects) {
Object[] copy = new Object[objects.length + 1];
copy[0] = (getName() != null)
? getName().toLowerCase(Locale.US)
: null;
for (int i = 0; i < objects.length; i += 1) {
copy[i + 1] = objects[i];
}
return Objects.hashCode(copy);
}
@Override
public String toString() {
return ConfigSingleton.getGson().toJson(this);
}
static void registerTypeAdapters(GsonBuilder builder) {
builder.registerTypeAdapter(AuthnMechanism.class,
TypeAdapters.dispatch(
ImmutableList.of(
AuthnMechBasic.class,
AuthnMechClient.class,
AuthnMechConnector.class,
AuthnMechForm.class,
AuthnMechKerberos.class,
AuthnMechLdap.class,
AuthnMechNtlm.class,
AuthnMechSaml.class,
AuthnMechSampleUrl.class)));
}
/** A base class for type proxies of sub-classes. */
protected abstract static class MechanismProxy<T extends AuthnMechanism>
implements TypeProxy<T> {
public String name;
protected MechanismProxy() {
}
protected MechanismProxy(AuthnMechanism mechanism) {
name = mechanism.getName();
}
}
}