blob: c97939b9c11b5eabc4f380ae21354715542283e9 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more contributor license
* agreements. See the NOTICE file distributed with this work for additional information regarding
* copyright ownership. The ASF licenses this file to You 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.apache.geode.security.generator;
import java.security.Principal;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.logging.log4j.Logger;
import org.apache.geode.cache.operations.OperationContext.OperationCode;
import org.apache.geode.internal.logging.LogService;
import org.apache.geode.security.AccessControl;
import org.apache.geode.security.templates.DummyAuthorization;
import org.apache.geode.security.templates.XmlAuthorization;
/**
* Encapsulates obtaining authorized and unauthorized credentials for a given operation in a region.
* Implementations will be for different kinds of authorization scheme and authentication scheme
* combos.
*
* @since GemFire 5.5
*/
public abstract class AuthzCredentialGenerator {
private static final Logger logger = LogService.getLogger();
/**
* The {@link CredentialGenerator} being used.
*/
protected CredentialGenerator generator;
/**
* A set of system properties that should be added to the gemfire system properties before using
* the authorization module.
*/
private Properties systemProperties;
/**
* A factory method to create a new instance of an {@link AuthzCredentialGenerator} for the given
* {@link ClassCode}. Caller is supposed to invoke {@link AuthzCredentialGenerator#init}
* immediately after obtaining the instance.
*
* @param classCode the {@code ClassCode} of the {@code AuthzCredentialGenerator} implementation
*
* @return an instance of {@code AuthzCredentialGenerator} for the given class code
*/
public static AuthzCredentialGenerator create(final ClassCode classCode) {
switch (classCode.classType) {
case ClassCode.ID_DUMMY:
return new DummyAuthzCredentialGenerator();
case ClassCode.ID_XML:
return new XmlAuthzCredentialGenerator();
default:
return null;
}
}
/**
* Initialize the authorized credential generator.
*
* @param generator an instance of {@link CredentialGenerator} of the credential implementation
* for which to obtain authorized/unauthorized credentials.
*
* @return false when the given {@link CredentialGenerator} is incompatible with this
* authorization module.
*/
public boolean init(final CredentialGenerator generator) {
this.generator = generator;
try {
this.systemProperties = init();
} catch (IllegalArgumentException ex) {
return false;
}
return true;
}
/**
*
* @return A set of extra properties that should be added to Gemfire system properties when not
* null.
*/
public Properties getSystemProperties() {
return this.systemProperties;
}
/**
* Get the {@link CredentialGenerator} being used by this instance.
*/
public CredentialGenerator getCredentialGenerator() {
return this.generator;
}
/**
* Initialize the authorized credential generator.
*
* Required to be implemented by concrete classes that implement this abstract class.
*
* @return A set of extra properties that should be added to Gemfire system properties when not
* null.
*
* @throws IllegalArgumentException when the {@link CredentialGenerator} is incompatible with this
* authorization module.
*/
protected abstract Properties init() throws IllegalArgumentException;
/**
* The {@link ClassCode} of the particular implementation.
*
* @return the {@code ClassCode}
*/
public abstract ClassCode classCode();
/**
* The name of the {@link AccessControl} factory function that should be used as the authorization
* module on the server side.
*
* @return name of the {@code AccessControl} factory function
*/
public abstract String getAuthorizationCallback();
/**
* Get a set of credentials generated using the given index allowed to perform the given
* {@link OperationCode}s for the given regions.
*
* @param opCodes the list of {@link OperationCode}s of the operations requiring authorization;
* should not be null
* @param regionNames list of the region names requiring authorization; a value of null indicates
* all regions
* @param index used to generate multiple such credentials by passing different values for this
*
* @return the set of credentials authorized to perform the given operation in the given regions
*/
public Properties getAllowedCredentials(final OperationCode[] opCodes, final String[] regionNames,
final int index) {
int numTries = getNumPrincipalTries(opCodes, regionNames);
if (numTries <= 0) {
numTries = 1;
}
for (int tries = 0; tries < numTries; tries++) {
final Principal principal =
getAllowedPrincipal(opCodes, regionNames, (index + tries) % numTries);
try {
return this.generator.getValidCredentials(principal);
} catch (IllegalArgumentException ex) {
}
}
return null;
}
/**
* Get a set of credentials generated using the given index not allowed to perform the given
* {@link OperationCode}s for the given regions. The credentials are required to be valid for
* authentication.
*
* @param opCodes the {@link OperationCode}s of the operations requiring authorization failure;
* should not be null
* @param regionNames list of the region names requiring authorization failure; a value of null
* indicates all regions
* @param index used to generate multiple such credentials by passing different values for this
*
* @return the set of credentials that are not authorized to perform the given operation in the
* given region
*/
public Properties getDisallowedCredentials(final OperationCode[] opCodes,
final String[] regionNames, final int index) {
// This may not be very correct since we use the value of
// getNumPrincipalTries() but is used to avoid adding another method.
// Also something like getNumDisallowedPrincipals() will be normally always
// infinite, and the number here is just to perform some number of tries
// before giving up.
int numTries = getNumPrincipalTries(opCodes, regionNames);
if (numTries <= 0) {
numTries = 1;
}
for (int tries = 0; tries < numTries; tries++) {
final Principal principal =
getDisallowedPrincipal(opCodes, regionNames, (index + tries) % numTries);
try {
return this.generator.getValidCredentials(principal);
} catch (IllegalArgumentException ex) {
}
}
return null;
}
/**
* Get the number of tries to be done for obtaining valid credentials for the given operations in
* the given region. It is required that {@link #getAllowedPrincipal} method returns valid
* principals for values of {@code index} from 0 through (n-1) where {@code n} is the value
* returned by this method. It is recommended that the principals so returned be unique for
* efficiency.
*
* This will be used by {@link #getAllowedCredentials} to step through different principals and
* obtain a set of valid credentials.
*
* Required to be implemented by concrete classes that implement this abstract class.
*
* @param opCodes the {@link OperationCode}s of the operations requiring authorization
* @param regionNames list of the region names requiring authorization; a value of null indicates
* all regions
*
* @return the number of principals allowed to perform the given operation in the given region
*/
protected abstract int getNumPrincipalTries(final OperationCode[] opCodes,
final String[] regionNames);
/**
* Get a {@link Principal} generated using the given index allowed to perform the given
* {@link OperationCode}s for the given region.
*
* Required to be implemented by concrete classes that implement this abstract class.
*
* @param opCodes the {@link OperationCode}s of the operations requiring authorization
* @param regionNames list of the region names requiring authorization; a value of null indicates
* all regions
* @param index used to generate multiple such principals by passing different values for this
*
* @return the {@link Principal} authorized to perform the given operation in the given region
*/
protected abstract Principal getAllowedPrincipal(final OperationCode[] opCodes,
final String[] regionNames, final int index);
/**
* Get a {@link Principal} generated using the given index not allowed to perform the given
* {@link OperationCode}s for the given region.
*
* Required to be implemented by concrete classes that implement this abstract class.
*
* @param opCodes the {@link OperationCode}s of the operations requiring authorization failure
* @param regionNames list of the region names requiring authorization failure; a value of null
* indicates all regions
* @param index used to generate multiple such principals by passing different values for this
*
* @return a {@link Principal} not authorized to perform the given operation in the given region
*/
protected abstract Principal getDisallowedPrincipal(final OperationCode[] opCodes,
final String[] regionNames, final int index);
/**
* Enumeration for various {@link AuthzCredentialGenerator} implementations.
*
* <p>
* The following schemes are supported as of now:
* <ul>
* <li>{@code DummyAuthorization} with {@code DummyAuthenticator}</li>
* <li>{@code XMLAuthorization} with {@code DummyAuthenticator}</li>
* <li>{@code XMLAuthorization} with {@code LDAPAuthenticator}</li>
* <li>{@code XMLAuthorization} with {@code PKCSAuthenticator}</li>
* <li>{@code XMLAuthorization} when using SSL sockets</li>
* </ul>
*
* <p>
* To add a new authorization scheme the following needs to be done:
* <ul>
* <li>Add implementation for {@link AccessControl}.</li>
* <li>Choose the authentication schemes that it shall work with from
* {@link CredentialGenerator.ClassCode}</li>
* <li>Add a new enumeration value for the scheme in this class. Notice the size of {@code VALUES}
* array and increase that if it is getting overflowed. Note the methods and fields for existing
* schemes and add for the new one in a similar manner.</li>
* <li>Add an implementation for {@link AuthzCredentialGenerator}. Note the
* {@link AuthzCredentialGenerator#init} method where different authentication schemes can be
* passed and initialize differently for the authentication schemes that shall be handled.</li>
* <li>Modify the {@link AuthzCredentialGenerator#create} method to add creation of an instance of
* the new implementation for the {@code ClassCode} enumeration value.</li>
* </ul>
*
* <p>
* All dunit tests will automagically start testing the new implementation after this.
*
* @since GemFire 5.5
*/
public static class ClassCode {
private static byte nextOrdinal = 0;
private static final byte ID_DUMMY = 1;
private static final byte ID_XML = 2;
private static final ClassCode[] VALUES = new ClassCode[10];
private static final Map CODE_NAME_MAP = new HashMap();
public static final ClassCode DUMMY =
new ClassCode(DummyAuthorization.class.getName() + ".create", ID_DUMMY);
public static final ClassCode XML =
new ClassCode(XmlAuthorization.class.getName() + ".create", ID_XML);
/** The name of this class. */
private final String name;
/** byte used as ordinal to represent this class */
private final byte ordinal;
/**
* One of the following: ID_DUMMY, ID_LDAP, ID_PKI
*/
private final byte classType;
/** Creates a new instance of class code. */
private ClassCode(final String name, final byte classType) {
this.name = name;
this.classType = classType;
this.ordinal = nextOrdinal++;
VALUES[this.ordinal] = this;
CODE_NAME_MAP.put(name, this);
}
public boolean isDummy() {
return this.classType == ID_DUMMY;
}
public boolean isXml() {
return this.classType == ID_XML;
}
/**
* Returns the {@code ClassCode} represented by specified ordinal.
*/
public static ClassCode fromOrdinal(final byte ordinal) {
return VALUES[ordinal];
}
/**
* Returns the {@code ClassCode} represented by specified string.
*/
public static ClassCode parse(final String operationName) {
return (ClassCode) CODE_NAME_MAP.get(operationName);
}
/**
* Returns all the possible values.
*/
public static List getAll() {
final List codes = new ArrayList();
for (Iterator iter = CODE_NAME_MAP.values().iterator(); iter.hasNext();) {
codes.add(iter.next());
}
return codes;
}
/**
* Returns the ordinal for this class code.
*
* @return the ordinal of this class code.
*/
public byte toOrdinal() {
return this.ordinal;
}
/**
* Returns a string representation for this class code.
*
* @return the name of this class code.
*/
@Override
public String toString() {
return this.name;
}
/**
* Indicates whether other object is same as this one.
*
* @return true if other object is same as this one.
*/
@Override
public boolean equals(final Object obj) {
if (obj == this) {
return true;
}
if (!(obj instanceof ClassCode)) {
return false;
}
final ClassCode other = (ClassCode) obj;
return other.ordinal == this.ordinal;
}
/**
* Indicates whether other {@code ClassCode} is same as this one.
*
* @return true if other {@code ClassCode} is same as this one.
*/
public boolean equals(final ClassCode opCode) {
return opCode != null && opCode.ordinal == this.ordinal;
}
/**
* Returns a hash code value for this {@code ClassCode} which is the same as its ordinal.
*
* @return the ordinal of this {@code ClassCode}.
*/
@Override
public int hashCode() {
return this.ordinal;
}
}
}