blob: 1fd6157e1d23810189179ec4797c82ba0b42bbf3 [file] [log] [blame]
// Copyright 2011 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.authncontroller;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.collect.ImmutableCollection;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.enterprise.secmgr.common.GCookie;
import com.google.enterprise.secmgr.config.AuthnAuthority;
import com.google.enterprise.secmgr.config.AuthnMechanism;
import com.google.enterprise.secmgr.config.CredentialGroup;
import com.google.enterprise.secmgr.identity.AuthnPrincipal;
import com.google.enterprise.secmgr.identity.CredPassword;
import com.google.enterprise.secmgr.identity.Credential;
import com.google.enterprise.secmgr.identity.GroupMemberships;
import com.google.enterprise.secmgr.identity.Verification;
import com.google.enterprise.secmgr.identity.VerificationStatus;
import java.net.URL;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnegative;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.GuardedBy;
import javax.annotation.concurrent.Immutable;
/**
* A view of a session snapshot that may be specialized or unspecialized.
*/
@Immutable
@ParametersAreNonnullByDefault
public abstract class SessionView {
@Nonnull protected final SessionSnapshot snapshot;
@GuardedBy("this") @Nonnull protected AuthnSessionState.Summary summary;
protected SessionView(SessionSnapshot snapshot) {
Preconditions.checkNotNull(snapshot);
this.snapshot = snapshot;
summary = null;
}
/**
* Creates a session view specialized for a given authentication mechanism.
*
* @param snapshot The session snapshot the view is a specialization of.
* @param mechanism The authentication mechanism to specialize for.
* @return A view of the snapshot specialized for the mechanism.
*/
@CheckReturnValue
@Nonnull
static SessionView create(SessionSnapshot snapshot, AuthnMechanism mechanism) {
return new SessionViewForMechanism(snapshot, mechanism);
}
/**
* Creates a session view specialized for a given credential group.
*
* @param snapshot The session snapshot the view is a specialization of.
* @param credentialGroup The credential group to specialize for.
* @return A view of the snapshot specialized for the credential group.
*/
@CheckReturnValue
@Nonnull
static SessionView create(SessionSnapshot snapshot, CredentialGroup credentialGroup) {
return new SessionViewForCredentialGroup(snapshot, credentialGroup);
}
/**
* Creates an unspecialized session view.
*
* @param snapshot The session snapshot the view is a specialization of.
* @return An unspecialized view of the snapshot.
*/
@CheckReturnValue
@Nonnull
static SessionView create(SessionSnapshot snapshot) {
return new SessionViewUnspecialized(snapshot);
}
/**
* Gets a new view identical to this one except that it contains the given
* user-agent cookies. Used to update a view after returning from a
* credentials gatherer.
*
* @param userAgentCookies The new user-agent cookies.
* @return A new view as specified.
*/
@CheckReturnValue
@Nonnull
SessionView withNewUserAgentCookies(ImmutableCollection<GCookie> userAgentCookies) {
return withNewSnapshot(snapshot.withNewUserAgentCookies(userAgentCookies));
}
@CheckReturnValue
@Nonnull
protected abstract SessionView withNewSnapshot(SessionSnapshot snapshot);
/**
* Is this a mechanism view?
*
* @return True only if this view is specialized for an authentication mechanism.
*/
@CheckReturnValue
public boolean isSpecializedForMechanism() {
return false;
}
/**
* Is this a credential-group view?
*
* @return True only if this view is specialized for a credential group.
*/
@CheckReturnValue
public boolean isSpecializedForCredentialGroup() {
return false;
}
/**
* Is this an unspecialized view?
*
* @return True only if this is an unspecialized view.
*/
@CheckReturnValue
public boolean isUnspecialized() {
return false;
}
/**
* Gets a summary of the view's session state.
*/
@CheckReturnValue
@Nonnull
public synchronized AuthnSessionState.Summary getSummary() {
if (summary == null) {
summary = snapshot.getState().computeSummary(snapshot.getConfig().getCredentialGroups());
}
return summary;
}
/**
* Gets the session ID string.
*
* @return The session ID string.
*/
@CheckReturnValue
@Nonnull
public String getSessionId() {
return snapshot.getSessionId();
}
/**
* Gets the URL that initiated the current authentication request.
*
* @return The authentication request URL, or null if we're not processing an
* authentication request.
*/
@CheckReturnValue
@Nullable
public URL getAuthnEntryUrl() {
return snapshot.getAuthnEntryUrl();
}
/**
* Gets the URL that initiated the current authentication request.
*
* @return The authentication request URL as a string, or null if we're not
* processing an authentication request.
*/
@CheckReturnValue
@Nullable
public String getAuthnEntryUrlString() {
return getAuthnEntryUrl().toString();
}
/**
* Gets the time at which this view's snapshot was taken.
*
* @return The snapshot time in milliseconds since the epoch.
*/
@CheckReturnValue
@Nonnegative
public long getTimeStamp() {
return snapshot.getTimeStamp();
}
/**
* Gets this view's authority, if it is a specialized view.
*
* @return The authority.
* @throws UnsupportedOperationException if this is not a specialized view.
*/
@CheckReturnValue
@Nonnull
public abstract AuthnAuthority getAuthority();
/**
* Gets this view's credential group, if this is a specialized view.
*
* @return The credential group.
* @throws UnsupportedOperationException if this is not a specialized view.
*/
@CheckReturnValue
@Nonnull
public abstract CredentialGroup getCredentialGroup();
/**
* Gets this view's mechanism, if this is a mechanism view.
*
* @return The mechanism associated with this view.
* @throws UnsupportedOperationException if this is not a mechanism view.
*/
@CheckReturnValue
@Nonnull
public abstract AuthnMechanism getMechanism();
/**
* Is this view's credential group required to have a principal?
*
* @return True if a principal is required.
* @throws UnsupportedOperationException if this is not a specialized view.
*/
@CheckReturnValue
public boolean getRequiresPrincipal() {
return getCredentialGroup().getRequiresUsername();
}
/**
* Gets the configured expiration time.
*
* @return The expiration time.
* @throws UnsupportedOperationException if this is not a mechanism view.
*/
@CheckReturnValue
public long getConfiguredExpirationTime() {
long trustDuration = getMechanism().getTrustDuration();
return (trustDuration > 0)
? snapshot.getTimeStamp() + trustDuration
: Verification.EXPIRES_AFTER_REQUEST;
}
protected abstract Predicate<AuthnAuthority> getCookieFilter();
protected abstract Predicate<AuthnAuthority> getCredentialFilter();
/**
* Is this view satisfied?
*/
@CheckReturnValue
public abstract boolean isSatisfied();
// **************** Cookies ****************
/**
* Gets the cookies that were received from the user agent in the most recent
* HTTP request.
*
* @return The cookies.
*/
@CheckReturnValue
@Nonnull
public Iterable<GCookie> getUserAgentCookies() {
return snapshot.getUserAgentCookies();
}
/**
* Gets a user-agent cookie by name. Note that there can be multiple such
* cookies; in that case one is chosen arbitrarily.
*
* @param name The name of the cookie to get.
* @return A cookie with that name, or {@code null} if none.
*/
@CheckReturnValue
@Nullable
public GCookie getUserAgentCookie(String name) {
Preconditions.checkNotNull(name);
for (GCookie cookie : getUserAgentCookies()) {
if (name.equals(cookie.getName())) {
return cookie;
}
}
return null;
}
/**
* Gets the cookies that have been received from this view's authorities.
*
* @return An immutable set of the authority cookies.
*/
@CheckReturnValue
@Nonnull
public ImmutableSet<GCookie> getAuthorityCookies() {
return getSummary().getCookies(getCookieFilter(), getTimeStamp());
}
// **************** Credentials ****************
/**
* Gets the credentials that have been gathered for this view.
*
* @return An immutable set of the credentials.
*/
public ImmutableSet<Credential> getCredentials() {
return getSummary().getCredentials(getCredentialFilter());
}
/**
* Gets this view's principal.
*
* @return The principal, or {@code null} if there isn't one.
*/
@CheckReturnValue
@Nullable
public AuthnPrincipal getPrincipal() {
return getUniqueCredential(AuthnPrincipal.class);
}
/**
* Gets this view's password credential.
*
* @return The password credential, or {@code null} if there isn't one.
*/
@CheckReturnValue
@Nullable
public CredPassword getPasswordCredential() {
return getUniqueCredential(CredPassword.class);
}
/**
* Gets this view's group-memberships credential.
*
* @return The group-memberships credential, or {@code null} if there isn't one.
*/
@CheckReturnValue
@Nullable
public GroupMemberships getGroupMemberships() {
return getUniqueCredential(GroupMemberships.class);
}
private <T extends Credential> T getUniqueCredential(Class<T> clazz) {
Credential uniqueCredential = null;
for (Credential credential : getCredentials()) {
if (clazz.isInstance(credential)) {
if (uniqueCredential == null) {
uniqueCredential = credential;
} else if (!uniqueCredential.equals(credential)) {
return null;
}
}
}
return clazz.cast(uniqueCredential);
}
/**
* Does this view have a principal and a password?
*
* @return True if the view has exactly one principal and one password.
*/
@CheckReturnValue
public boolean hasPrincipalAndPassword() {
return hasPrincipalAndPassword(getPrincipal(), getPasswordCredential());
}
private boolean hasPrincipalAndPassword(AuthnPrincipal principal, CredPassword password) {
return principal != null
&& !principal.getName().isEmpty()
&& password != null
&& !password.getText().isEmpty();
}
/**
* Gets this view's principal and password.
*
* @return An immutable collection containing those credentials.
* @throws IllegalStateException unless both are available.
*/
@CheckReturnValue
@Nonnull
public ImmutableCollection<Credential> getPrincipalAndPassword() {
AuthnPrincipal principal = getPrincipal();
CredPassword password = getPasswordCredential();
Preconditions.checkState(hasPrincipalAndPassword(principal, password));
return ImmutableList.<Credential>of(principal, password);
}
/**
* Gets this view's username.
*
* @return The username, or {@code null} if there isn't one.
*/
@CheckReturnValue
@Nullable
public String getUsername() {
AuthnPrincipal principal = getPrincipal();
return (principal != null) ? principal.getName() : null;
}
/**
* Gets this view's domain.
*
* @return The domain, or {@code null} if there isn't one.
*/
@CheckReturnValue
@Nullable
public String getDomain() {
AuthnPrincipal principal = getPrincipal();
return (principal != null) ? principal.getActiveDirectoryDomain() : null;
}
/**
* Gets this view's password.
*
* @return The password, or {@code null} if there isn't one.
*/
@CheckReturnValue
@Nullable
public String getPassword() {
CredPassword password = getPasswordCredential();
return (password != null) ? password.getText() : null;
}
/**
* Gets this view's groups.
*
* @return The groups, as an immutable set.
*/
@CheckReturnValue
@Nullable
public ImmutableSet<String> getGroups() {
GroupMemberships groups = getGroupMemberships();
return (groups != null) ? groups.getGroups() : ImmutableSet.<String>of();
}
// **************** Verifications ****************
/**
* Gets the verifications for this view.
*
* @return An immutable set of this view's verifications.
*/
@CheckReturnValue
@Nonnull
public ImmutableSet<Verification> getVerifications() {
return getSummary().getVerifications(getCredentialFilter(), getTimeStamp());
}
/**
* Does this view have a verified principal?
*
* @return True if this view has at least one verification containing a
* principal, and if no other verification contains a different principal.
*/
@CheckReturnValue
public boolean hasVerifiedPrincipal() {
return null != getUniqueVerifiedCredential(AuthnPrincipal.class);
}
/**
* Does this view have a verified password?
*
* @return True if this view has at least one verification containing a
* password, and if no other verification contains a different password.
*/
@CheckReturnValue
protected boolean hasVerifiedPassword() {
return null != getUniqueVerifiedCredential(AuthnPrincipal.class);
}
/**
* Does this view have a jointly verified principal and password?
*
* @return True if this view has at least one verification containing both a
* principal and password, and if no other verification contains a
* different principal or password.
*/
@CheckReturnValue
public boolean hasVerifiedPrincipalAndPassword() {
AuthnPrincipal principal = getUniqueVerifiedCredential(AuthnPrincipal.class);
CredPassword password = getUniqueVerifiedCredential(CredPassword.class);
return principal != null
&& password != null
&& areJointlyVerified(principal, password);
}
/**
* Gets a verified principal.
*
* @return The verified principal. Returns {@code null} if
* {@code hasVerifiedPrincipal()} is false.
*/
@CheckReturnValue
@Nullable
public AuthnPrincipal getVerifiedPrincipal() {
return getUniqueVerifiedCredential(AuthnPrincipal.class);
}
/**
* Gets the verified groups for this view.
*
* @return The verified groups, may be empty.
*/
@CheckReturnValue
@Nonnull
public ImmutableSet<String> getVerifiedGroups() {
ImmutableSet.Builder<String> builder = ImmutableSet.builder();
for (GroupMemberships credential : getVerifiedCredentials(GroupMemberships.class)) {
builder.addAll(credential.getGroups());
}
return builder.build();
}
/**
* Gets the expiration time of a credential.
*
* @param credential A credential to get the expiration time for.
* @return The credential's expiration time, or {@code 0} if the credential
* isn't verified.
*/
@CheckReturnValue
public long getCredentialExpirationTime(Credential credential) {
Preconditions.checkNotNull(credential);
return Verification.maximumExpirationTime(getJointVerifications(credential));
}
private <T extends Credential> T getUniqueVerifiedCredential(Class<T> clazz) {
Credential uniqueCredential = null;
for (Verification verification : getVerifications()) {
if (verification.isVerified()) {
for (Credential credential : verification.getCredentials()) {
if (clazz.isInstance(credential)) {
if (uniqueCredential == null) {
uniqueCredential = credential;
} else if (!uniqueCredential.equals(credential)) {
return null;
}
}
}
}
}
return clazz.cast(uniqueCredential);
}
private <T extends Credential> Iterable<T> getVerifiedCredentials(Class<T> clazz) {
ImmutableList.Builder<T> builder = ImmutableList.builder();
for (Verification verification : getVerifications()) {
if (verification.isVerified()) {
for (Credential credential : verification.getCredentials()) {
if (clazz.isInstance(credential)) {
builder.add(clazz.cast(credential));
}
}
}
}
return builder.build();
}
private boolean areJointlyVerified(Credential... credentials) {
return Iterables.any(getVerifications(),
Predicates.and(
Verification.isVerifiedPredicate(),
Verification.getContainsAllCredentialsPredicate(credentials)));
}
private Iterable<Verification> getJointVerifications(Credential... credentials) {
return Iterables.filter(getVerifications(),
Predicates.and(
Verification.isVerifiedPredicate(),
Verification.getContainsAllCredentialsPredicate(credentials)));
}
/**
* Gets this view's verification status.
*
* @return The verification status.
*/
@CheckReturnValue
@Nonnull
public VerificationStatus getVerificationStatus() {
return Verification.getStatus(getVerifications());
}
/**
* Does this view have no refutations and at least one verification?
*
* @return True if this view is verified.
*/
@CheckReturnValue
public boolean isVerified() {
return getVerificationStatus() == VerificationStatus.VERIFIED;
}
/**
* Does this view have at least one refutation?
*
* @return True if this view is refuted.
*/
@CheckReturnValue
public boolean isRefuted() {
return getVerificationStatus() == VerificationStatus.REFUTED;
}
/**
* Does this view have no refutations and no verifications?
*
* @return True if this view is indeterminate.
*/
@CheckReturnValue
public boolean isIndeterminate() {
return getVerificationStatus() == VerificationStatus.INDETERMINATE;
}
// **************** Logging ****************
/**
* Annotates a log message with this view's session ID.
*
* @param format The format string to pass to {@link String#format}.
* @param args The arguments to pass to {@link String#format}.
* @return The annotated log message.
*/
@CheckReturnValue
@Nonnull
public String logMessage(String format, Object... args) {
return snapshot.logMessage(format, args);
}
}