blob: b0d513f559b14336e4f5a8fb53c9538ee0ceac93 [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.annotations.VisibleForTesting;
import com.google.common.base.Objects;
import com.google.common.base.Preconditions;
import com.google.common.base.Predicate;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.ImmutableSetMultimap;
import com.google.common.collect.Iterables;
import com.google.common.collect.Maps;
import com.google.common.collect.SetMultimap;
import com.google.enterprise.secmgr.common.Chain;
import com.google.enterprise.secmgr.common.CookieStore;
import com.google.enterprise.secmgr.common.GCookie;
import com.google.enterprise.secmgr.common.SecurityManagerUtil;
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.AbstractCredential;
import com.google.enterprise.secmgr.identity.Credential;
import com.google.enterprise.secmgr.identity.Verification;
import com.google.enterprise.secmgr.json.ProxyTypeAdapter;
import com.google.enterprise.secmgr.json.TypeProxy;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonDeserializationContext;
import com.google.gson.JsonDeserializer;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSerializationContext;
import com.google.gson.JsonSerializer;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;
import javax.annotation.CheckReturnValue;
import javax.annotation.Nonnull;
import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.concurrent.Immutable;
import javax.annotation.concurrent.NotThreadSafe;
/**
* A representation of the state stored in an authentication session. This is
* not the AuthnState that's used to keep track of the what the controller is
* doing; this is the credentials gathered by the controller.
*/
@Immutable
@ParametersAreNonnullByDefault
public final class AuthnSessionState {
/**
* The possible instruction operations supported here.
*/
public static enum Operation {
ADD_COOKIE(GCookie.class),
ADD_CREDENTIAL(Credential.class),
ADD_VERIFICATION(Verification.class),
REMOVE_COOKIE(GCookie.class),
REMOVE_CREDENTIAL(Credential.class),
REMOVE_VERIFICATION(Verification.class);
@Nonnull private final Class<?> operandClass;
private Operation(Class<?> operandClass) {
Preconditions.checkNotNull(operandClass);
this.operandClass = operandClass;
}
@CheckReturnValue
@Nonnull
public Class<?> getOperandClass() {
return operandClass;
}
}
private static final AuthnSessionState EMPTY = new AuthnSessionState(Chain.<Instruction>empty());
@Nonnull private final Chain<Instruction> instructions;
private AuthnSessionState(Chain<Instruction> instructions) {
Preconditions.checkNotNull(instructions);
this.instructions = instructions;
}
/**
* Gets an empty session state.
*/
@CheckReturnValue
@Nonnull
public static AuthnSessionState empty() {
return EMPTY;
}
/**
* Gets a new session state with only the given verification.
*
* @param authority The authority that performed the verification process.
* @param verification The verification that the state will hold.
* @return A new state with that verification.
*/
@CheckReturnValue
@Nonnull
public static AuthnSessionState of(AuthnAuthority authority, Verification verification) {
return empty().addVerification(authority, verification);
}
@VisibleForTesting
static AuthnSessionState of(Iterable<Instruction> instructions) {
return new AuthnSessionState(Chain.copyOf(instructions));
}
@VisibleForTesting
List<Instruction> getInstructions() {
return instructions.toList();
}
/**
* Is this state object empty?
*
* @return True only if there are no elements in this state.
*/
@CheckReturnValue
public boolean isEmpty() {
return instructions.isEmpty();
}
/**
* Unit testing relies on this; it's not used in production.
*/
@Override
public boolean equals(Object object) {
if (object == this) { return true; }
if (!(object instanceof AuthnSessionState)) { return false; }
AuthnSessionState other = (AuthnSessionState) object;
return Objects.equal(instructions, other.instructions);
}
/**
* Unit testing relies on this; it's not used in production.
*/
@Override
public int hashCode() {
return Objects.hashCode(instructions);
}
/**
* Unit testing uses this for failure reporting; it's not used in production.
*/
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
for (Instruction instruction : getInstructions()) {
builder.append(" ");
builder.append(instruction);
builder.append("\n");
}
return builder.toString();
}
/**
* Appends some state to this state.
*
* @param delta The state to append
* @return A new state with that state appended.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState add(AuthnSessionState delta) {
return new AuthnSessionState(instructions.addAll(delta.getInstructions()));
}
/**
* Gets a session state that represents the delta between this state and a
* given one. For a given ancestor state {@code A}, it is always true that
* {@code this.equals(A.add(this.getDelta(A)))}.
*
* @param ancestor The ancestor state to use as a reference.
* @return A new state with instructions that have been added since the
* ancestor.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState getDelta(AuthnSessionState ancestor) {
return AuthnSessionState.of(instructions.toList(ancestor.instructions));
}
@CheckReturnValue
private AuthnSessionState addInstruction(Instruction instruction) {
Preconditions.checkNotNull(instruction);
return new AuthnSessionState(instructions.add(instruction));
}
/**
* Adds a cookie to this state.
*
* @param authority The authority from which the cookie was received.
* @param cookie The cookie to be added.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState addCookie(AuthnAuthority authority, GCookie cookie) {
return addInstruction(Instruction.make(Operation.ADD_COOKIE, authority, cookie));
}
/**
* Adds some cookies to this state.
*
* @param authority The authority from which the cookie was received.
* @param cookies The cookies to be added.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState addCookies(AuthnAuthority authority, Iterable<GCookie> cookies) {
AuthnSessionState state = this;
for (GCookie cookie : cookies) {
state = state.addCookie(authority, cookie);
}
return state;
}
/**
* Removes a cookie from this state.
*
* @param authority The authority from which the cookie was originally received.
* @param cookie The cookie to be removed.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState removeCookie(AuthnAuthority authority, GCookie cookie) {
return addInstruction(Instruction.make(Operation.REMOVE_COOKIE, authority, cookie));
}
/**
* Removes some cookies from this state.
*
* @param authority The authority from which the cookie was received.
* @param cookies The cookies to be removed.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState removeCookies(AuthnAuthority authority, Iterable<GCookie> cookies) {
AuthnSessionState state = this;
for (GCookie cookie : cookies) {
state = state.removeCookie(authority, cookie);
}
return state;
}
/**
* Adds a credential to this state.
*
* @param authority The authority from which the credential was received.
* @param credential The credential to be added.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState addCredential(AuthnAuthority authority, Credential credential) {
return addInstruction(Instruction.make(Operation.ADD_CREDENTIAL, authority, credential));
}
/**
* Adds some credentials to this state.
*
* @param authority The authority from which the cookie was received.
* @param credentials The credentials to be added.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState addCredentials(AuthnAuthority authority,
Iterable<Credential> credentials) {
AuthnSessionState state = this;
for (Credential credential : credentials) {
state = state.addCredential(authority, credential);
}
return state;
}
/**
* Removes a credential from this state.
*
* @param authority The authority from which the credential was originally received.
* @param credential The credential to be removed.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState removeCredential(AuthnAuthority authority,
Credential credential) {
return addInstruction(Instruction.make(Operation.REMOVE_CREDENTIAL, authority, credential));
}
/**
* Adds a verification to this state.
*
* @param authority The authority that performed the verification process.
* @param verification The verification to be added.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState addVerification(AuthnAuthority authority, Verification verification) {
return addInstruction(Instruction.make(Operation.ADD_VERIFICATION, authority, verification));
}
/**
* Adds some verifications to this state.
*
* @param authority The authority that performed the verification process.
* @param verifications The verifications to be added.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState addVerifications(AuthnAuthority authority,
Iterable<Verification> verifications) {
AuthnSessionState state = this;
for (Verification verification : verifications) {
state = state.addVerification(authority, verification);
}
return state;
}
/**
* Removes a verification from this state.
*
* @param authority The authority that performed the verification process.
* @param verification The verification to be removed.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState removeVerification(AuthnAuthority authority, Verification verification) {
return addInstruction(Instruction.make(Operation.REMOVE_VERIFICATION, authority, verification));
}
/**
* Removes some verifications from this state.
*
* @param authority The authority that performed the verification process.
* @param verifications The verifications to be removed.
*/
@CheckReturnValue
@Nonnull
public AuthnSessionState removeVerifications(AuthnAuthority authority,
Iterable<Verification> verifications) {
AuthnSessionState state = this;
for (Verification verification : verifications) {
state = state.removeVerification(authority, verification);
}
return state;
}
/**
* Computes a summary of this state's contents.
*
* @param credentialGroups The credential groups associated with this session.
* @return An immutable summary of the state's contents.
*/
@CheckReturnValue
@Nonnull
public Summary computeSummary(Iterable<CredentialGroup> credentialGroups) {
return evolveSummary(new Evolver(credentialGroups));
}
/**
* Computes a summary by extending a given summary with this state's contents.
*
* @param summary The summary to be extended.
* @return An immutable extended summary.
*/
@CheckReturnValue
@Nonnull
public Summary evolveSummary(Summary summary) {
return evolveSummary(summary.evolve());
}
private Summary evolveSummary(Evolver evolver) {
for (Instruction instruction : getInstructions()) {
AuthnAuthority authority = instruction.getAuthority();
Object operand = instruction.getOperand();
switch (instruction.getOperation()) {
case ADD_COOKIE:
evolver.addCookie(authority, (GCookie) operand);
break;
case REMOVE_COOKIE:
evolver.removeCookie(authority, (GCookie) operand);
break;
case ADD_CREDENTIAL:
evolver.addCredential(authority, (Credential) operand);
break;
case REMOVE_CREDENTIAL:
evolver.removeCredential(authority, (Credential) operand);
break;
case ADD_VERIFICATION:
evolver.addVerification(authority, (Verification) operand);
break;
case REMOVE_VERIFICATION:
evolver.removeVerification(authority, (Verification) operand);
break;
default:
throw new IllegalStateException("Unknown instruction operation: "
+ instruction.getOperation());
}
}
return evolver.getSummary();
}
@NotThreadSafe
@ParametersAreNonnullByDefault
private static final class Evolver {
@Nonnull final ImmutableList<CredentialGroup> credentialGroups;
@Nonnull final Map<AuthnAuthority, CookieStore> cookiesMap;
@Nonnull final SetMultimap<AuthnAuthority, Credential> credentialsMap;
@Nonnull final Map<AuthnAuthority, Verification> verificationsMap;
Evolver(Iterable<CredentialGroup> credentialGroups) {
this.credentialGroups = ImmutableList.copyOf(credentialGroups);
cookiesMap = Maps.<AuthnAuthority, CookieStore>newHashMap();
credentialsMap = HashMultimap.<AuthnAuthority, Credential>create();
verificationsMap = Maps.<AuthnAuthority, Verification>newHashMap();
}
Evolver(ImmutableList<CredentialGroup> credentialGroups,
ImmutableMap<AuthnAuthority, ImmutableSet<GCookie>> cookiesMap,
ImmutableSetMultimap<AuthnAuthority, Credential> credentialsMap,
ImmutableMap<AuthnAuthority, Verification> verificationsMap) {
this.credentialGroups = credentialGroups;
this.cookiesMap = thawCookiesMap(cookiesMap);
this.credentialsMap = HashMultimap.create(credentialsMap);
this.verificationsMap = Maps.newHashMap(verificationsMap);
}
static Map<AuthnAuthority, CookieStore> thawCookiesMap(
ImmutableMap<AuthnAuthority, ImmutableSet<GCookie>> cookiesMap) {
Map<AuthnAuthority, CookieStore> result = Maps.newHashMap();
for (Map.Entry<AuthnAuthority, ImmutableSet<GCookie>> entry : cookiesMap.entrySet()) {
CookieStore cookies = GCookie.makeStore();
cookies.addAll(entry.getValue());
result.put(entry.getKey(), cookies);
}
return result;
}
Summary getSummary() {
return new Summary(credentialGroups, freezeCookiesMap(),
ImmutableSetMultimap.copyOf(credentialsMap), ImmutableMap.copyOf(verificationsMap));
}
ImmutableMap<AuthnAuthority, ImmutableSet<GCookie>> freezeCookiesMap() {
ImmutableMap.Builder<AuthnAuthority, ImmutableSet<GCookie>> builder = ImmutableMap.builder();
for (Map.Entry<AuthnAuthority, CookieStore> entry : cookiesMap.entrySet()) {
builder.put(entry.getKey(), ImmutableSet.copyOf(entry.getValue()));
}
return builder.build();
}
void addCookie(AuthnAuthority authority, GCookie cookie) {
getCookies(authority).add(cookie);
}
void removeCookie(AuthnAuthority authority, GCookie cookie) {
getCookies(authority).remove(cookie);
}
CookieStore getCookies(AuthnAuthority authority) {
CookieStore cookies = cookiesMap.get(authority);
if (cookies == null) {
cookies = GCookie.makeStore();
cookiesMap.put(authority, cookies);
}
return cookies;
}
void addCredential(AuthnAuthority authority, Credential credential) {
AuthnAuthority cgAuthority = getCredGroupAuthority(authority);
if (!credentialsMap.containsEntry(cgAuthority, credential)) {
Iterable<Credential> removed
= SecurityManagerUtil.removeInPlace(credentialsMap.get(cgAuthority),
AbstractCredential.getTypePredicate(credential.getClass()));
for (AuthnAuthority mechAuthority : getMechAuthorities(cgAuthority)) {
Verification verification = verificationsMap.get(mechAuthority);
if (verification != null && verification.containsAnyCredential(removed)) {
verificationsMap.remove(mechAuthority);
}
}
credentialsMap.put(cgAuthority, credential);
}
}
void removeCredential(AuthnAuthority authority, Credential credential) {
AuthnAuthority cgAuthority = getCredGroupAuthority(authority);
if (credentialsMap.containsEntry(cgAuthority, credential)) {
for (AuthnAuthority mechAuthority : getMechAuthorities(cgAuthority)) {
Verification verification = verificationsMap.get(mechAuthority);
if (verification != null && verification.containsCredential(credential)) {
verificationsMap.remove(mechAuthority);
}
}
credentialsMap.remove(cgAuthority, credential);
}
}
void addVerification(AuthnAuthority authority, Verification verification) {
verificationsMap.put(authority, verification);
for (Credential credential : verification.getCredentials()) {
addCredential(authority, credential);
}
}
void removeVerification(AuthnAuthority authority, Verification verification) {
if (verification.equals(verificationsMap.get(authority))) {
verificationsMap.remove(authority);
}
}
AuthnAuthority getCredGroupAuthority(AuthnAuthority authority) {
for (CredentialGroup credentialGroup : credentialGroups) {
for (AuthnMechanism mechanism : credentialGroup.getMechanisms()) {
if (authority.equals(mechanism.getAuthority())) {
return credentialGroup.getAuthority();
}
}
}
return authority;
}
Iterable<AuthnAuthority> getMechAuthorities(AuthnAuthority authority) {
for (CredentialGroup credentialGroup : credentialGroups) {
if (authority.equals(credentialGroup.getAuthority())) {
return Iterables.transform(credentialGroup.getMechanisms(),
AuthnMechanism.getAuthorityFunction());
}
}
return ImmutableList.of(authority);
}
}
/**
* The result of {@link #computeSummary}, this structure contains a summary of
* the session state as computed by executing the session-state's
* instructions.
*/
@Immutable
@ParametersAreNonnullByDefault
public static final class Summary {
@Nonnull private final ImmutableList<CredentialGroup> credentialGroups;
@Nonnull private final ImmutableMap<AuthnAuthority, ImmutableSet<GCookie>> cookiesMap;
@Nonnull private final ImmutableSetMultimap<AuthnAuthority, Credential> credentialsMap;
@Nonnull private final ImmutableMap<AuthnAuthority, Verification> verificationsMap;
private Summary(ImmutableList<CredentialGroup> credentialGroups,
ImmutableMap<AuthnAuthority, ImmutableSet<GCookie>> cookiesMap,
ImmutableSetMultimap<AuthnAuthority, Credential> credentialsMap,
ImmutableMap<AuthnAuthority, Verification> verificationsMap) {
this.credentialGroups = credentialGroups;
this.cookiesMap = cookiesMap;
this.credentialsMap = credentialsMap;
this.verificationsMap = verificationsMap;
}
@CheckReturnValue
@Nonnull
private Evolver evolve() {
return new Evolver(credentialGroups, cookiesMap, credentialsMap, verificationsMap);
}
/**
* Gets the credential groups that were used to generate this summary.
*/
@CheckReturnValue
@Nonnull
public ImmutableList<CredentialGroup> getCredentialGroups() {
return credentialGroups;
}
/**
* Gets all the cookies in this summary.
*
* @return An immutable map of the cookies for each authority.
*/
@CheckReturnValue
@Nonnull
public ImmutableMap<AuthnAuthority, ImmutableSet<GCookie>> getCookiesMap() {
return cookiesMap;
}
/**
* Gets all the credentials in this summary.
*
* @return An immutable map of the credentials for each authority.
*/
public ImmutableSetMultimap<AuthnAuthority, Credential> getCredentialsMap() {
return credentialsMap;
}
/**
* Gets all the verifications in this summary.
*
* @return An immutable map of the verifications for each authority.
*/
@CheckReturnValue
@Nonnull
public ImmutableMap<AuthnAuthority, Verification> getVerificationsMap() {
return verificationsMap;
}
/**
* Gets the cookies in this summary for some specified authorities.
*
* @param predicate A predicate specifying the authorities to get cookies for.
* @return An immutable set of the cookies for the matching authorities.
*/
@CheckReturnValue
@Nonnull
public ImmutableSet<GCookie> getCookies(Predicate<AuthnAuthority> predicate) {
ImmutableSet.Builder<GCookie> builder = ImmutableSet.builder();
for (Map.Entry<AuthnAuthority, ImmutableSet<GCookie>> entry : cookiesMap.entrySet()) {
if (predicate.apply(entry.getKey())) {
builder.addAll(entry.getValue());
}
}
return builder.build();
}
/**
* Gets the unexpired cookies in this summary for some specified authorities.
*
* @param predicate A predicate specifying the authorities to get cookies for.
* @param timeStamp A reference time to use for expiring cookies.
* @return An immutable set of the unexpired cookies for the matching authorities.
*/
@CheckReturnValue
@Nonnull
public ImmutableSet<GCookie> getCookies(Predicate<AuthnAuthority> predicate, long timeStamp) {
ImmutableSet.Builder<GCookie> builder = ImmutableSet.builder();
for (Map.Entry<AuthnAuthority, ImmutableSet<GCookie>> entry : cookiesMap.entrySet()) {
if (predicate.apply(entry.getKey())) {
for (GCookie cookie : entry.getValue()) {
if (!cookie.isExpired(timeStamp)) {
builder.add(cookie);
}
}
}
}
return builder.build();
}
/**
* Gets the credentials in this summary for some specified authorities.
*
* @param predicate A predicate specifying the authorities to get credentials for.
* @return An immutable set of the credentials for the matching authorities.
*/
@CheckReturnValue
@Nonnull
public ImmutableSet<Credential> getCredentials(Predicate<AuthnAuthority> predicate) {
ImmutableSet.Builder<Credential> builder = ImmutableSet.builder();
for (AuthnAuthority authority : credentialsMap.keySet()) {
if (predicate.apply(authority)) {
builder.addAll(credentialsMap.get(authority));
}
}
return builder.build();
}
/**
* Gets the verifications in this summary for some specified authorities.
*
* @param predicate A predicate specifying the authorities to get verifications for.
* @return An immutable set of the verifications for the matching authorities.
*/
@CheckReturnValue
@Nonnull
public ImmutableSet<Verification> getVerifications(Predicate<AuthnAuthority> predicate) {
ImmutableSet.Builder<Verification> builder = ImmutableSet.builder();
for (Map.Entry<AuthnAuthority, Verification> entry : verificationsMap.entrySet()) {
if (predicate.apply(entry.getKey())) {
builder.add(entry.getValue());
}
}
return builder.build();
}
/**
* Gets the unexpired verifications in this summary for some specified authorities.
*
* @param predicate A predicate specifying the authorities to get verifications for.
* @param timeStamp A reference time to use for expiring verifications.
* @return An immutable set of the unexpired verifications for the matching authorities.
*/
@CheckReturnValue
@Nonnull
public ImmutableSet<Verification> getVerifications(Predicate<AuthnAuthority> predicate,
long timeStamp) {
ImmutableSet.Builder<Verification> builder = ImmutableSet.builder();
for (Map.Entry<AuthnAuthority, Verification> entry : verificationsMap.entrySet()) {
if (predicate.apply(entry.getKey())) {
Verification verification = entry.getValue();
if (!verification.hasExpired(timeStamp)) {
builder.add(verification);
}
}
}
return builder.build();
}
}
static void registerTypeAdapters(GsonBuilder builder) {
builder.registerTypeAdapter(AuthnSessionState.class,
ProxyTypeAdapter.make(AuthnSessionState.class, LocalProxy.class));
builder.registerTypeAdapter(Instruction.class,
new Instruction.LocalTypeAdapter());
}
private static final class LocalProxy implements TypeProxy<AuthnSessionState> {
List<Instruction> instructions;
@SuppressWarnings("unused")
LocalProxy() {
}
@SuppressWarnings("unused")
LocalProxy(AuthnSessionState state) {
instructions = state.getInstructions();
}
@Override
public AuthnSessionState build() {
return AuthnSessionState.of(instructions);
}
}
/**
* An instruction, consisting of an operation, an authority, and an operand.
*/
@Immutable
@ParametersAreNonnullByDefault
public static final class Instruction {
@Nonnull private final Operation operation;
@Nonnull private final AuthnAuthority authority;
@Nonnull private final Object operand;
private Instruction(Operation operation, AuthnAuthority authority, Object operand) {
this.operation = operation;
this.authority = authority;
this.operand = operand;
}
@CheckReturnValue
@Nonnull
public static Instruction make(Operation operation, AuthnAuthority authority, Object operand) {
Preconditions.checkNotNull(authority);
Preconditions.checkArgument(operation.getOperandClass().isInstance(operand));
return new Instruction(operation, authority, operand);
}
@CheckReturnValue
@Nonnull
public Operation getOperation() {
return operation;
}
@CheckReturnValue
@Nonnull
public AuthnAuthority getAuthority() {
return authority;
}
@CheckReturnValue
@Nonnull
public Object getOperand() {
return operand;
}
@Override
public boolean equals(Object object) {
if (object == this) { return true; }
if (!(object instanceof Instruction)) { return false; }
Instruction other = (Instruction) object;
return Objects.equal(getOperation(), other.getOperation())
&& Objects.equal(getAuthority(), other.getAuthority())
&& Objects.equal(getOperand(), other.getOperand());
}
@Override
public int hashCode() {
return Objects.hashCode(getOperation(), getAuthority(), getOperand());
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
switch (operation) {
case ADD_COOKIE:
case ADD_CREDENTIAL:
case ADD_VERIFICATION:
builder.append("add to ");
break;
case REMOVE_COOKIE:
case REMOVE_CREDENTIAL:
case REMOVE_VERIFICATION:
builder.append("remove from ");
break;
default:
throw new IllegalStateException("Unknown operation: " + operation);
}
builder.append(authority);
builder.append(": ");
builder.append(operand);
return builder.toString();
}
private static final class LocalTypeAdapter
implements JsonSerializer<Instruction>, JsonDeserializer<Instruction> {
static final String KEY_OPERATION = "operation";
static final String KEY_AUTHORITY = "authority";
static final String KEY_OPERAND = "operand";
LocalTypeAdapter() {
}
@Override
public JsonElement serialize(Instruction instruction, Type type,
JsonSerializationContext context) {
JsonObject jo = new JsonObject();
Operation operation = instruction.getOperation();
jo.addProperty(KEY_OPERATION, operation.toString());
jo.add(KEY_AUTHORITY, context.serialize(instruction.getAuthority(), AuthnAuthority.class));
jo.add(KEY_OPERAND,
context.serialize(instruction.getOperand(), operation.getOperandClass()));
return jo;
}
@Override
public Instruction deserialize(JsonElement src, Type type,
JsonDeserializationContext context) {
JsonObject jo = src.getAsJsonObject();
Operation operation = Operation.valueOf(jo.getAsJsonPrimitive(KEY_OPERATION).getAsString());
AuthnAuthority authority = context.deserialize(jo.get(KEY_AUTHORITY), AuthnAuthority.class);
return Instruction.make(operation, authority,
context.deserialize(jo.get(KEY_OPERAND), operation.getOperandClass()));
}
}
}
}