From e63b03f3d7e4851e008e4bb4d184982c2c0bd229 Mon Sep 17 00:00:00 2001 From: WuKong Date: Tue, 24 May 2016 17:13:17 +0200 Subject: odl/aaa clone Change-Id: I2b72c16aa3245e02d985a2c6189aacee7caad36e Signed-off-by: WuKong --- odl-aaa-moon/aaa-authn/pom.xml | 103 ++++++++ .../main/java/org/opendaylight/aaa/Activator.java | 51 ++++ .../opendaylight/aaa/AuthenticationBuilder.java | 122 ++++++++++ .../opendaylight/aaa/AuthenticationManager.java | 77 ++++++ .../java/org/opendaylight/aaa/ClaimBuilder.java | 160 +++++++++++++ .../java/org/opendaylight/aaa/ClientManager.java | 88 +++++++ .../main/java/org/opendaylight/aaa/EqualUtil.java | 42 ++++ .../java/org/opendaylight/aaa/HashCodeUtil.java | 104 +++++++++ .../aaa/PasswordCredentialBuilder.java | 87 +++++++ .../org/opendaylight/aaa/SecureBlockingQueue.java | 258 +++++++++++++++++++++ .../OSGI-INF/metatype/metatype.properties | 12 + .../main/resources/OSGI-INF/metatype/metatype.xml | 16 ++ .../aaa-authn/src/main/resources/authn.cfg | 2 + .../aaa/AuthenticationBuilderTest.java | 129 +++++++++++ .../aaa/AuthenticationManagerTest.java | 133 +++++++++++ .../org/opendaylight/aaa/ClaimBuilderTest.java | 208 +++++++++++++++++ .../org/opendaylight/aaa/ClientManagerTest.java | 70 ++++++ .../opendaylight/aaa/PasswordCredentialTest.java | 39 ++++ .../opendaylight/aaa/SecureBlockingQueueTest.java | 191 +++++++++++++++ 19 files changed, 1892 insertions(+) create mode 100644 odl-aaa-moon/aaa-authn/pom.xml create mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/Activator.java create mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationBuilder.java create mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationManager.java create mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClaimBuilder.java create mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClientManager.java create mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/EqualUtil.java create mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/HashCodeUtil.java create mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/PasswordCredentialBuilder.java create mode 100644 odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/SecureBlockingQueue.java create mode 100644 odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.properties create mode 100644 odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.xml create mode 100644 odl-aaa-moon/aaa-authn/src/main/resources/authn.cfg create mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationBuilderTest.java create mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationManagerTest.java create mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClaimBuilderTest.java create mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClientManagerTest.java create mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/PasswordCredentialTest.java create mode 100644 odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/SecureBlockingQueueTest.java (limited to 'odl-aaa-moon/aaa-authn') diff --git a/odl-aaa-moon/aaa-authn/pom.xml b/odl-aaa-moon/aaa-authn/pom.xml new file mode 100644 index 00000000..06027a60 --- /dev/null +++ b/odl-aaa-moon/aaa-authn/pom.xml @@ -0,0 +1,103 @@ + + + + 4.0.0 + + org.opendaylight.aaa + aaa-parent + 0.3.1-Beryllium-SR1 + ../parent + + + aaa-authn + bundle + + + + org.opendaylight.aaa + aaa-authn-api + + + com.google.guava + guava + + + org.slf4j + slf4j-api + + + com.sun.jersey + jersey-server + provided + + + org.osgi + org.osgi.core + provided + + + org.osgi + org.osgi.compendium + provided + + + org.apache.felix + org.apache.felix.dependencymanager + provided + + + + junit + junit + test + + + org.slf4j + slf4j-simple + test + + + + + + + org.apache.felix + maven-bundle-plugin + true + + + org.opendaylight.aaa.Activator + + ${project.basedir}/META-INF + + + + org.codehaus.mojo + build-helper-maven-plugin + + + attach-artifacts + package + + attach-artifact + + + + + ${project.build.directory}/classes/authn.cfg + cfg + config + + + + + + + + + + diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/Activator.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/Activator.java new file mode 100644 index 00000000..cfe27ef0 --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/Activator.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.aaa; + +import java.util.Dictionary; +import org.apache.felix.dm.DependencyActivatorBase; +import org.apache.felix.dm.DependencyManager; +import org.opendaylight.aaa.api.AuthenticationService; +import org.opendaylight.aaa.api.ClientService; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.service.cm.ManagedService; + +/** + * Activator to register {@link AuthenticationService} with OSGi. + * + * @author liemmn + * + */ +public class Activator extends DependencyActivatorBase { + + private static final String AUTHN_PID = "org.opendaylight.aaa.authn"; + + @Override + public void init(BundleContext context, DependencyManager manager) throws Exception { + manager.add(createComponent().setInterface( + new String[] { AuthenticationService.class.getName() }, null).setImplementation( + AuthenticationManager.instance())); + + ClientManager cm = new ClientManager(); + manager.add(createComponent().setInterface(new String[] { ClientService.class.getName() }, + null).setImplementation(cm)); + context.registerService(ManagedService.class.getName(), cm, addPid(ClientManager.defaults)); + context.registerService(ManagedService.class.getName(), AuthenticationManager.instance(), + addPid(AuthenticationManager.defaults)); + } + + @Override + public void destroy(BundleContext context, DependencyManager manager) throws Exception { + } + + private Dictionary addPid(Dictionary dict) { + dict.put(Constants.SERVICE_PID, AUTHN_PID); + return dict; + } +} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationBuilder.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationBuilder.java new file mode 100644 index 00000000..948cbac6 --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationBuilder.java @@ -0,0 +1,122 @@ +/* + * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.aaa; + +import static org.opendaylight.aaa.EqualUtil.areEqual; +import static org.opendaylight.aaa.HashCodeUtil.hash; + +import java.io.Serializable; +import java.util.Set; +import org.opendaylight.aaa.api.Authentication; +import org.opendaylight.aaa.api.Claim; + +/** + * A builder for the authentication context. + * + * The expiration defaults to 0. + * + * @author liemmn + * + */ +public class AuthenticationBuilder { + + private long expiration = 0L; + private Claim claim; + + public AuthenticationBuilder(Claim claim) { + this.claim = claim; + } + + public AuthenticationBuilder setExpiration(long expiration) { + this.expiration = expiration; + return this; + } + + public Authentication build() { + return new ImmutableAuthentication(this); + } + + private static final class ImmutableAuthentication implements Authentication, Serializable { + private static final long serialVersionUID = 4919078164955609987L; + private int hashCode = 0; + long expiration = 0L; + Claim claim; + + private ImmutableAuthentication(AuthenticationBuilder base) { + if (base.claim == null) { + throw new IllegalStateException("The Claim is null."); + } + claim = new ClaimBuilder(base.claim).build(); + expiration = base.expiration; + + if (base.expiration < 0) { + throw new IllegalStateException("The expiration is less than 0."); + } + } + + @Override + public long expiration() { + return expiration; + } + + @Override + public String clientId() { + return claim.clientId(); + } + + @Override + public String userId() { + return claim.userId(); + } + + @Override + public String user() { + return claim.user(); + } + + @Override + public String domain() { + return claim.domain(); + } + + @Override + public Set roles() { + return claim.roles(); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Authentication)) { + return false; + } + Authentication a = (Authentication) o; + return areEqual(expiration, a.expiration()) && areEqual(claim.roles(), a.roles()) + && areEqual(claim.domain(), a.domain()) && areEqual(claim.userId(), a.userId()) + && areEqual(claim.user(), a.user()) && areEqual(claim.clientId(), a.clientId()); + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = HashCodeUtil.SEED; + result = hash(result, expiration); + result = hash(result, claim.hashCode()); + hashCode = result; + } + return hashCode; + } + + @Override + public String toString() { + return "expiration:" + expiration + "," + claim.toString(); + } + } +} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationManager.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationManager.java new file mode 100644 index 00000000..5f6420a3 --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/AuthenticationManager.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.aaa; + +import java.util.Dictionary; +import java.util.Hashtable; +import org.opendaylight.aaa.api.Authentication; +import org.opendaylight.aaa.api.AuthenticationService; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedService; + +/** + * An {@link InheritableThreadLocal}-based {@link AuthenticationService}. + * + * @author liemmn + */ +public class AuthenticationManager implements AuthenticationService, ManagedService { + private static final String AUTH_ENABLED_ERR = "Error setting authEnabled"; + + static final String AUTH_ENABLED = "authEnabled"; + static final Dictionary defaults = new Hashtable<>(); + static { + defaults.put(AUTH_ENABLED, Boolean.FALSE.toString()); + } + + // In non-Karaf environments, authEnabled is set to false by default + private static volatile boolean authEnabled = false; + + private final static AuthenticationManager am = new AuthenticationManager(); + private final ThreadLocal auth = new InheritableThreadLocal<>(); + + private AuthenticationManager() { + } + + static AuthenticationManager instance() { + return am; + } + + @Override + public Authentication get() { + return auth.get(); + } + + @Override + public void set(Authentication a) { + auth.set(a); + } + + @Override + public void clear() { + auth.remove(); + } + + @Override + public boolean isAuthEnabled() { + return authEnabled; + } + + @Override + public void updated(Dictionary properties) throws ConfigurationException { + if (properties == null) { + return; + } + + String propertyValue = (String) properties.get(AUTH_ENABLED); + boolean isTrueString = Boolean.parseBoolean(propertyValue); + if (!isTrueString && !"false".equalsIgnoreCase(propertyValue)) { + throw new ConfigurationException(AUTH_ENABLED, AUTH_ENABLED_ERR); + } + authEnabled = isTrueString; + } +} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClaimBuilder.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClaimBuilder.java new file mode 100644 index 00000000..4e4a8ef3 --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClaimBuilder.java @@ -0,0 +1,160 @@ +/* + * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.aaa; + +import static org.opendaylight.aaa.EqualUtil.areEqual; +import static org.opendaylight.aaa.HashCodeUtil.hash; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableSet; +import java.io.Serializable; +import java.util.LinkedHashSet; +import java.util.Set; +import org.opendaylight.aaa.api.Claim; + +/** + * Builder for a {@link Claim}. The userId, user, and roles information is + * mandatory. + * + * @author liemmn + * + */ +public class ClaimBuilder { + private String userId = ""; + private String user = ""; + private Set roles = new LinkedHashSet<>(); + private String clientId = ""; + private String domain = ""; + + public ClaimBuilder() { + } + + public ClaimBuilder(Claim claim) { + clientId = claim.clientId(); + userId = claim.userId(); + user = claim.user(); + domain = claim.domain(); + roles.addAll(claim.roles()); + } + + public ClaimBuilder setClientId(String clientId) { + this.clientId = Strings.nullToEmpty(clientId).trim(); + return this; + } + + public ClaimBuilder setUserId(String userId) { + this.userId = Strings.nullToEmpty(userId).trim(); + return this; + } + + public ClaimBuilder setUser(String userName) { + user = Strings.nullToEmpty(userName).trim(); + return this; + } + + public ClaimBuilder setDomain(String domain) { + this.domain = Strings.nullToEmpty(domain).trim(); + return this; + } + + public ClaimBuilder addRoles(Set roles) { + for (String role : roles) { + addRole(role); + } + return this; + } + + public ClaimBuilder addRole(String role) { + roles.add(Strings.nullToEmpty(role).trim()); + return this; + } + + public Claim build() { + return new ImmutableClaim(this); + } + + protected static class ImmutableClaim implements Claim, Serializable { + private static final long serialVersionUID = -8115027645190209129L; + private int hashCode = 0; + protected String clientId; + protected String userId; + protected String user; + protected String domain; + protected ImmutableSet roles; + + protected ImmutableClaim(ClaimBuilder base) { + clientId = base.clientId; + userId = base.userId; + user = base.user; + domain = base.domain; + roles = ImmutableSet. builder().addAll(base.roles).build(); + + if (userId.isEmpty() || user.isEmpty() || roles.isEmpty() || roles.contains("")) { + throw new IllegalStateException( + "The Claim is missing one or more of the required fields."); + } + } + + @Override + public String clientId() { + return clientId; + } + + @Override + public String userId() { + return userId; + } + + @Override + public String user() { + return user; + } + + @Override + public String domain() { + return domain; + } + + @Override + public Set roles() { + return roles; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (!(o instanceof Claim)) + return false; + Claim a = (Claim) o; + return areEqual(roles, a.roles()) && areEqual(domain, a.domain()) + && areEqual(userId, a.userId()) && areEqual(user, a.user()) + && areEqual(clientId, a.clientId()); + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = HashCodeUtil.SEED; + result = hash(result, clientId); + result = hash(result, userId); + result = hash(result, user); + result = hash(result, domain); + result = hash(result, roles); + hashCode = result; + } + return hashCode; + } + + @Override + public String toString() { + return "clientId:" + clientId + "," + "userId:" + userId + "," + "userName:" + user + + "," + "domain:" + domain + "," + "roles:" + roles; + } + } +} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClientManager.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClientManager.java new file mode 100644 index 00000000..e7e51424 --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/ClientManager.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.aaa; + +import java.util.Dictionary; +import java.util.HashMap; +import java.util.Hashtable; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import org.apache.felix.dm.Component; +import org.opendaylight.aaa.api.AuthenticationException; +import org.opendaylight.aaa.api.ClientService; +import org.osgi.service.cm.ConfigurationException; +import org.osgi.service.cm.ManagedService; + +/** + * A configuration-based client manager. + * + * @author liemmn + * + */ +public class ClientManager implements ClientService, ManagedService { + static final String CLIENTS = "authorizedClients"; + private static final String CLIENTS_FORMAT_ERR = "Clients are space-delimited in the form of :"; + private static final String UNAUTHORIZED_CLIENT_ERR = "Unauthorized client"; + + // Defaults (needed only for non-Karaf deployments) + static final Dictionary defaults = new Hashtable<>(); + static { + defaults.put(CLIENTS, "dlux:secrete"); + } + + private final Map clients = new ConcurrentHashMap<>(); + + // This should be a singleton + ClientManager() { + } + + // Called by DM when all required dependencies are satisfied. + void init(Component c) throws ConfigurationException { + reconfig(defaults); + } + + @Override + public void validate(String clientId, String clientSecret) throws AuthenticationException { + // TODO: Post-Helium, we will support a CRUD API + if (!clients.containsKey(clientId)) { + throw new AuthenticationException(UNAUTHORIZED_CLIENT_ERR); + } + if (!clients.get(clientId).equals(clientSecret)) { + throw new AuthenticationException(UNAUTHORIZED_CLIENT_ERR); + } + } + + @Override + public void updated(Dictionary props) throws ConfigurationException { + if (props == null) { + props = defaults; + } + reconfig(props); + } + + // Reconfigure the client map... + private void reconfig(@SuppressWarnings("rawtypes") Dictionary props) + throws ConfigurationException { + try { + String authorizedClients = (String) props.get(CLIENTS); + Map newClients = new HashMap<>(); + if (authorizedClients != null) { + for (String client : authorizedClients.split(" ")) { + String[] aClient = client.split(":"); + newClients.put(aClient[0], aClient[1]); + } + } + clients.clear(); + clients.putAll(newClients); + } catch (Throwable t) { + throw new ConfigurationException(null, CLIENTS_FORMAT_ERR); + } + } + +} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/EqualUtil.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/EqualUtil.java new file mode 100644 index 00000000..17204d0e --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/EqualUtil.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.aaa; + +/** + * Simple class to aide in implementing equals. + *

+ * + * Arrays are not handled by this class. This is because the + * Arrays.equals methods should be used for array fields. + */ +public final class EqualUtil { + static public boolean areEqual(boolean aThis, boolean aThat) { + return aThis == aThat; + } + + static public boolean areEqual(char aThis, char aThat) { + return aThis == aThat; + } + + static public boolean areEqual(long aThis, long aThat) { + return aThis == aThat; + } + + static public boolean areEqual(float aThis, float aThat) { + return Float.floatToIntBits(aThis) == Float.floatToIntBits(aThat); + } + + static public boolean areEqual(double aThis, double aThat) { + return Double.doubleToLongBits(aThis) == Double.doubleToLongBits(aThat); + } + + static public boolean areEqual(Object aThis, Object aThat) { + return aThis == null ? aThat == null : aThis.equals(aThat); + } +} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/HashCodeUtil.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/HashCodeUtil.java new file mode 100644 index 00000000..c295b3ed --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/HashCodeUtil.java @@ -0,0 +1,104 @@ +/***************************************************************************** + * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + *****************************************************************************/ + +package org.opendaylight.aaa; + +import java.lang.reflect.Array; + +/** + * Collected methods which allow easy implementation of hashCode. + * + * Example use case: + * + *

+ * public int hashCode() {
+ *     int result = HashCodeUtil.SEED;
+ *     // collect the contributions of various fields
+ *     result = HashCodeUtil.hash(result, fPrimitive);
+ *     result = HashCodeUtil.hash(result, fObject);
+ *     result = HashCodeUtil.hash(result, fArray);
+ *     return result;
+ * }
+ * 
+ */ +public final class HashCodeUtil { + + /** + * An initial value for a hashCode, to which is added contributions + * from fields. Using a non-zero value decreases collisions of + * hashCode values. + */ + public static final int SEED = 23; + + /** booleans. */ + public static int hash(int aSeed, boolean aBoolean) { + return firstTerm(aSeed) + (aBoolean ? 1 : 0); + } + + /*** chars. */ + public static int hash(int aSeed, char aChar) { + return firstTerm(aSeed) + aChar; + } + + /** ints. */ + public static int hash(int aSeed, int aInt) { + return firstTerm(aSeed) + aInt; + } + + /** longs. */ + public static int hash(int aSeed, long aLong) { + return firstTerm(aSeed) + (int) (aLong ^ (aLong >>> 32)); + } + + /** floats. */ + public static int hash(int aSeed, float aFloat) { + return hash(aSeed, Float.floatToIntBits(aFloat)); + } + + /** doubles. */ + public static int hash(int aSeed, double aDouble) { + return hash(aSeed, Double.doubleToLongBits(aDouble)); + } + + /** + * aObject is a possibly-null object field, and possibly an array. + * + * If aObject is an array, then each element may be a primitive or + * a possibly-null object. + */ + public static int hash(int aSeed, Object aObject) { + int result = aSeed; + if (aObject == null) { + result = hash(result, 0); + } else if (!isArray(aObject)) { + result = hash(result, aObject.hashCode()); + } else { + int length = Array.getLength(aObject); + for (int idx = 0; idx < length; ++idx) { + Object item = Array.get(aObject, idx); + // if an item in the array references the array itself, prevent + // infinite looping + if (!(item == aObject)) { + result = hash(result, item); + } + } + } + return result; + } + + // PRIVATE + private static final int fODD_PRIME_NUMBER = 37; + + private static int firstTerm(int aSeed) { + return fODD_PRIME_NUMBER * aSeed; + } + + private static boolean isArray(Object aObject) { + return aObject.getClass().isArray(); + } +} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/PasswordCredentialBuilder.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/PasswordCredentialBuilder.java new file mode 100644 index 00000000..d8a2e87a --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/PasswordCredentialBuilder.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.aaa; + +import static org.opendaylight.aaa.EqualUtil.areEqual; +import static org.opendaylight.aaa.HashCodeUtil.hash; + +import org.opendaylight.aaa.api.PasswordCredentials; + +/** + * {@link PasswordCredentials} builder. + * + * @author liemmn + * + */ +public class PasswordCredentialBuilder { + private final MutablePasswordCredentials pc = new MutablePasswordCredentials(); + + public PasswordCredentialBuilder setUserName(String username) { + pc.username = username; + return this; + } + + public PasswordCredentialBuilder setPassword(String password) { + pc.password = password; + return this; + } + + public PasswordCredentialBuilder setDomain(String domain) { + pc.domain = domain; + return this; + } + + public PasswordCredentials build() { + return pc; + } + + private static class MutablePasswordCredentials implements PasswordCredentials { + private int hashCode = 0; + private String username; + private String password; + private String domain; + + @Override + public String username() { + return username; + } + + @Override + public String password() { + return password; + } + + @Override + public String domain() { + return domain; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof PasswordCredentials)) { + return false; + } + PasswordCredentials p = (PasswordCredentials) o; + return areEqual(username, p.username()) && areEqual(password, p.password()); + } + + @Override + public int hashCode() { + if (hashCode == 0) { + int result = HashCodeUtil.SEED; + result = hash(result, username); + result = hash(result, password); + hashCode = result; + } + return hashCode; + } + } +} diff --git a/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/SecureBlockingQueue.java b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/SecureBlockingQueue.java new file mode 100644 index 00000000..3ded52da --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/java/org/opendaylight/aaa/SecureBlockingQueue.java @@ -0,0 +1,258 @@ +/* + * Copyright (c) 2014 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ +package org.opendaylight.aaa; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Iterator; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.TimeUnit; +import org.opendaylight.aaa.api.Authentication; + +/** + * A {@link BlockingQueue} decorator with injected security context. + * + * @author liemmn + * + * @param + * queue element type + */ +public class SecureBlockingQueue implements BlockingQueue { + private final BlockingQueue> queue; + + /** + * Constructor. + * + * @param queue + * blocking queue implementation to use + */ + public SecureBlockingQueue(BlockingQueue> queue) { + this.queue = queue; + } + + @Override + public T remove() { + return setAuth(queue.remove()); + } + + @Override + public T poll() { + return setAuth(queue.poll()); + } + + @Override + public T element() { + return setAuth(queue.element()); + } + + @Override + public T peek() { + return setAuth(queue.peek()); + } + + @Override + public int size() { + return queue.size(); + } + + @Override + public boolean isEmpty() { + return queue.isEmpty(); + } + + @Override + public Iterator iterator() { + return new Iterator() { + Iterator> it = queue.iterator(); + + @Override + public boolean hasNext() { + return it.hasNext(); + } + + @Override + public T next() { + return it.next().data; + } + + @Override + public void remove() { + it.remove(); + } + }; + } + + @Override + public Object[] toArray() { + return toData().toArray(); + } + + @SuppressWarnings("hiding") + @Override + public T[] toArray(T[] a) { + return toData().toArray(a); + } + + @Override + public boolean containsAll(Collection c) { + return toData().containsAll(c); + } + + @Override + public boolean addAll(Collection c) { + return queue.addAll(fromData(c)); + } + + @Override + public boolean removeAll(Collection c) { + return queue.removeAll(fromData(c)); + } + + @Override + public boolean retainAll(Collection c) { + return queue.retainAll(fromData(c)); + } + + @Override + public void clear() { + queue.clear(); + } + + @Override + public boolean add(T e) { + return queue.add(new SecureData<>(e)); + } + + @Override + public boolean offer(T e) { + return queue.offer(new SecureData<>(e)); + } + + @Override + public void put(T e) throws InterruptedException { + queue.put(new SecureData(e)); + } + + @Override + public boolean offer(T e, long timeout, TimeUnit unit) throws InterruptedException { + return queue.offer(new SecureData<>(e), timeout, unit); + } + + @Override + public T take() throws InterruptedException { + return setAuth(queue.take()); + } + + @Override + public T poll(long timeout, TimeUnit unit) throws InterruptedException { + return setAuth(queue.poll(timeout, unit)); + } + + @Override + public int remainingCapacity() { + return queue.remainingCapacity(); + } + + @Override + public boolean remove(Object o) { + Iterator> it = queue.iterator(); + while (it.hasNext()) { + SecureData sd = it.next(); + if (sd.data.equals(o)) { + return queue.remove(sd); + } + } + return false; + } + + @Override + public boolean contains(Object o) { + Iterator> it = queue.iterator(); + while (it.hasNext()) { + SecureData sd = it.next(); + if (sd.data.equals(o)) { + return true; + } + } + return false; + } + + @Override + public int drainTo(Collection c) { + Collection> sd = new ArrayList<>(); + int n = queue.drainTo(sd); + c.addAll(toData(sd)); + return n; + } + + @Override + public int drainTo(Collection c, int maxElements) { + Collection> sd = new ArrayList<>(); + int n = queue.drainTo(sd, maxElements); + c.addAll(toData(sd)); + return n; + } + + // Rehydrate security context + private T setAuth(SecureData i) { + AuthenticationManager.instance().set(i.auth); + return i.data; + } + + // Construct secure data collection from a plain old data collection + @SuppressWarnings("unchecked") + private Collection> fromData(Collection c) { + Collection> sd = new ArrayList<>(c.size()); + for (Object d : c) { + sd.add((SecureData) new SecureData<>(d)); + } + return sd; + } + + // Extract the data portion out from the secure data + @SuppressWarnings("unchecked") + private Collection toData() { + return toData(Arrays.> asList(queue.toArray(new SecureData[0]))); + } + + // Extract the data portion out from the secure data + private Collection toData(Collection> secureData) { + Collection data = new ArrayList<>(secureData.size()); + Iterator> it = secureData.iterator(); + while (it.hasNext()) { + data.add(it.next().data); + } + return data; + } + + // Inject security context + public static final class SecureData { + private final T data; + private final Authentication auth; + + private SecureData(T data) { + this.data = data; + this.auth = AuthenticationManager.instance().get(); + } + + @SuppressWarnings("rawtypes") + @Override + public boolean equals(Object o) { + if (o == null) { + return false; + } + return (o instanceof SecureData) ? data.equals(((SecureData) o).data) : false; + } + + @Override + public int hashCode() { + return data.hashCode(); + } + } +} diff --git a/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.properties b/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.properties new file mode 100644 index 00000000..75537f6b --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.properties @@ -0,0 +1,12 @@ +org.opendaylight.aaa.authn.name = Opendaylight AAA Authentication Configuration +org.opendaylight.aaa.authn.description = Configuration for AAA authorized clients +org.opendaylight.aaa.authn.authorizedClients.name = Authorized Clients +org.opendaylight.aaa.authn.authorizedClients.description = Space-delimited list of authorized \ + clients, with client id and client password separated by a ':'. \ + Example: dlux:secrete +org.opendaylight.aaa.authn.authEnabled.name = Enable authentication +org.opendaylight.aaa.authn.authEnabled.description = Enable authentication by setting it \ +to the value 'true', or 'false' if bypassing authentication. \ +Note that bypassing authentication may result in your controller being more \ +vulnerable to unauthorized accesses. Authorization, if enabled, will not work if \ +authentication is disabled. \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.xml b/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.xml new file mode 100644 index 00000000..10150587 --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/resources/OSGI-INF/metatype/metatype.xml @@ -0,0 +1,16 @@ + + + + + + + + + + \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/main/resources/authn.cfg b/odl-aaa-moon/aaa-authn/src/main/resources/authn.cfg new file mode 100644 index 00000000..e7326f86 --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/main/resources/authn.cfg @@ -0,0 +1,2 @@ +authorizedClients=dlux:secrete +authEnabled=true \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationBuilderTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationBuilderTest.java new file mode 100644 index 00000000..2f69fe5b --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationBuilderTest.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.aaa; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.LinkedHashSet; +import java.util.Set; +import org.junit.Test; +import org.opendaylight.aaa.api.Authentication; +import org.opendaylight.aaa.api.Claim; + +public class AuthenticationBuilderTest { + private Set roles = new LinkedHashSet<>(Arrays.asList("role1", "role2")); + private Claim validClaim = new ClaimBuilder().setDomain("aName").setUserId("1") + .setClientId("2222").setUser("bob").addRole("foo").addRoles(roles).build(); + + @Test + public void testBuildWithExpiration() { + Authentication a1 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); + assertEquals(1, a1.expiration()); + assertEquals("aName", a1.domain()); + assertEquals("1", a1.userId()); + assertEquals("2222", a1.clientId()); + assertEquals("bob", a1.user()); + assertTrue(a1.roles().contains("foo")); + assertTrue(a1.roles().containsAll(roles)); + assertEquals(3, a1.roles().size()); + Authentication a2 = new AuthenticationBuilder(a1).build(); + assertNotEquals(a1, a2); + Authentication a3 = new AuthenticationBuilder(a1).setExpiration(1).build(); + assertEquals(a1, a3); + } + + @Test + public void testBuildWithoutExpiration() { + Authentication a1 = new AuthenticationBuilder(validClaim).build(); + assertEquals(0, a1.expiration()); + assertEquals("aName", a1.domain()); + assertEquals("1", a1.userId()); + assertEquals("2222", a1.clientId()); + assertEquals("bob", a1.user()); + assertTrue(a1.roles().contains("foo")); + assertTrue(a1.roles().containsAll(roles)); + assertEquals(3, a1.roles().size()); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithNegativeExpiration() { + AuthenticationBuilder a1 = new AuthenticationBuilder(validClaim).setExpiration(-1); + a1.build(); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithNullClaim() { + AuthenticationBuilder a1 = new AuthenticationBuilder(null); + a1.build(); + } + + @Test + public void testToString() { + Authentication a1 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); + assertEquals( + "expiration:1,clientId:2222,userId:1,userName:bob,domain:aName,roles:[foo, role1, role2]", + a1.toString()); + } + + @Test + public void testEquals() { + Authentication a1 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); + assertTrue(a1.equals(a1)); + Authentication a2 = new AuthenticationBuilder(a1).setExpiration(1).build(); + assertTrue(a1.equals(a2)); + assertTrue(a2.equals(a1)); + Authentication a3 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); + assertTrue(a1.equals(a3)); + assertTrue(a3.equals(a2)); + assertTrue(a1.equals(a2)); + } + + @Test + public void testNotEquals() { + Authentication a1 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); + assertFalse(a1.equals(null)); + assertFalse(a1.equals("wrong object")); + Authentication a2 = new AuthenticationBuilder(a1).build(); + assertFalse(a1.equals(a2)); + assertFalse(a2.equals(a1)); + Authentication a3 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); + assertFalse(a1.equals(a2)); + assertTrue(a1.equals(a3)); + assertFalse(a2.equals(a3)); + Authentication a4 = new AuthenticationBuilder(validClaim).setExpiration(9).build(); + assertFalse(a1.equals(a4)); + assertFalse(a4.equals(a1)); + Authentication a5 = new AuthenticationBuilder(a1).setExpiration(9).build(); + assertFalse(a1.equals(a5)); + assertFalse(a5.equals(a1)); + } + + @Test + public void testHashCode() { + Authentication a1 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); + assertEquals(a1.hashCode(), a1.hashCode()); + Authentication a2 = new AuthenticationBuilder(a1).setExpiration(1).build(); + assertTrue(a1.equals(a2)); + assertEquals(a1.hashCode(), a2.hashCode()); + Authentication a3 = new AuthenticationBuilder(validClaim).setExpiration(1).build(); + assertTrue(a1.equals(a3)); + assertEquals(a1.hashCode(), a3.hashCode()); + assertEquals(a2.hashCode(), a3.hashCode()); + Authentication a4 = new AuthenticationBuilder(a1).setExpiration(9).build(); + assertFalse(a1.equals(a4)); + assertNotEquals(a1.hashCode(), a4.hashCode()); + Authentication a5 = new AuthenticationBuilder(a1).build(); + assertFalse(a1.equals(a5)); + assertNotEquals(a1.hashCode(), a5.hashCode()); + } +} diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationManagerTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationManagerTest.java new file mode 100644 index 00000000..540df287 --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/AuthenticationManagerTest.java @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.aaa; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Dictionary; +import java.util.Hashtable; +import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import org.junit.Test; +import org.opendaylight.aaa.api.Authentication; +import org.opendaylight.aaa.api.AuthenticationService; +import org.osgi.service.cm.ConfigurationException; + +public class AuthenticationManagerTest { + @Test + public void testAuthenticationCrudSameThread() { + Authentication auth = new AuthenticationBuilder(new ClaimBuilder().setUser("Bob") + .setUserId("1234").addRole("admin").addRole("guest").build()).build(); + AuthenticationService as = AuthenticationManager.instance(); + + assertNotNull(as); + + as.set(auth); + assertEquals(auth, as.get()); + + as.clear(); + assertNull(as.get()); + } + + @Test + public void testAuthenticationCrudSpawnedThread() throws InterruptedException, + ExecutionException { + AuthenticationService as = AuthenticationManager.instance(); + Authentication auth = new AuthenticationBuilder(new ClaimBuilder().setUser("Bob") + .setUserId("1234").addRole("admin").addRole("guest").build()).build(); + + as.set(auth); + Future f = Executors.newSingleThreadExecutor().submit(new Worker()); + assertEquals(auth, f.get()); + + as.clear(); + f = Executors.newSingleThreadExecutor().submit(new Worker()); + assertNull(f.get()); + } + + @Test + public void testAuthenticationCrudSpawnedThreadPool() throws InterruptedException, + ExecutionException { + AuthenticationService as = AuthenticationManager.instance(); + Authentication auth = new AuthenticationBuilder(new ClaimBuilder().setUser("Bob") + .setUserId("1234").addRole("admin").addRole("guest").build()).build(); + + as.set(auth); + List> fs = Executors.newFixedThreadPool(2).invokeAll( + Arrays.asList(new Worker(), new Worker())); + for (Future f : fs) { + assertEquals(auth, f.get()); + } + + as.clear(); + fs = Executors.newFixedThreadPool(2).invokeAll(Arrays.asList(new Worker(), new Worker())); + for (Future f : fs) { + assertNull(f.get()); + } + } + + @Test + public void testUpdatedValid() throws ConfigurationException { + Dictionary props = new Hashtable<>(); + AuthenticationManager as = AuthenticationManager.instance(); + + assertFalse(as.isAuthEnabled()); + + props.put(AuthenticationManager.AUTH_ENABLED, "TrUe"); + as.updated(props); + assertTrue(as.isAuthEnabled()); + + props.put(AuthenticationManager.AUTH_ENABLED, "FaLsE"); + as.updated(props); + assertFalse(as.isAuthEnabled()); + } + + @Test + public void testUpdatedNullProperty() throws ConfigurationException { + AuthenticationManager as = AuthenticationManager.instance(); + + assertFalse(as.isAuthEnabled()); + as.updated(null); + assertFalse(as.isAuthEnabled()); + } + + @Test(expected = ConfigurationException.class) + public void testUpdatedInvalidValue() throws ConfigurationException { + AuthenticationManager as = AuthenticationManager.instance(); + Dictionary props = new Hashtable<>(); + + props.put(AuthenticationManager.AUTH_ENABLED, "yes"); + as.updated(props); + } + + @Test(expected = ConfigurationException.class) + public void testUpdatedInvalidKey() throws ConfigurationException { + AuthenticationManager as = AuthenticationManager.instance(); + Dictionary props = new Hashtable<>(); + + props.put("Invalid Key", "true"); + as.updated(props); + } + + private class Worker implements Callable { + @Override + public Authentication call() throws Exception { + AuthenticationService as = AuthenticationManager.instance(); + return as.get(); + } + } +} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClaimBuilderTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClaimBuilderTest.java new file mode 100644 index 00000000..372eb6d2 --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClaimBuilderTest.java @@ -0,0 +1,208 @@ +/* + * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.aaa; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.HashSet; +import org.junit.Test; +import org.opendaylight.aaa.api.Claim; + +/** + * + * @author liemmn + * + */ +public class ClaimBuilderTest { + @Test + public void testBuildWithAll() { + Claim c1 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") + .setUserId("1234").addRole("foo").addRole("foo2") + .addRoles(new HashSet<>(Arrays.asList("foo", "bar"))).build(); + assertEquals("dlux", c1.clientId()); + assertEquals("pepsi", c1.domain()); + assertEquals("john", c1.user()); + assertEquals("1234", c1.userId()); + assertTrue(c1.roles().contains("foo")); + assertTrue(c1.roles().contains("foo2")); + assertTrue(c1.roles().contains("bar")); + assertEquals(3, c1.roles().size()); + Claim c2 = new ClaimBuilder(c1).build(); + assertEquals(c1, c2); + } + + @Test + public void testBuildWithRequired() { + Claim c1 = new ClaimBuilder().setUser("john").setUserId("1234").addRole("foo").build(); + assertEquals("john", c1.user()); + assertEquals("1234", c1.userId()); + assertTrue(c1.roles().contains("foo")); + assertEquals(1, c1.roles().size()); + assertEquals("", c1.domain()); + assertEquals("", c1.clientId()); + } + + @Test + public void testBuildWithEmptyOptional() { + Claim c1 = new ClaimBuilder().setDomain(" ").setClientId(" ").setUser("john") + .setUserId("1234").addRole("foo").build(); + assertEquals("", c1.domain()); + assertEquals("", c1.clientId()); + assertEquals("john", c1.user()); + assertEquals("1234", c1.userId()); + assertTrue(c1.roles().contains("foo")); + assertEquals(1, c1.roles().size()); + } + + @Test + public void testBuildWithNullOptional() { + Claim c1 = new ClaimBuilder().setDomain(null).setClientId(null).setUser("john") + .setUserId("1234").addRole("foo").build(); + assertEquals("", c1.domain()); + assertEquals("", c1.clientId()); + assertEquals("john", c1.user()); + assertEquals("1234", c1.userId()); + assertTrue(c1.roles().contains("foo")); + assertEquals(1, c1.roles().size()); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithDefault() { + ClaimBuilder c1 = new ClaimBuilder(); + c1.build(); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithoutUser() { + ClaimBuilder c1 = new ClaimBuilder().setUserId("1234").addRole("foo"); + c1.build(); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithNullUser() { + ClaimBuilder c1 = new ClaimBuilder().setUser(null).setUserId("1234").addRole("foo"); + c1.build(); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithEmptyUser() { + ClaimBuilder c1 = new ClaimBuilder().setUser(" ").setUserId("1234").addRole("foo"); + c1.build(); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithoutUserId() { + ClaimBuilder c1 = new ClaimBuilder().setUser("john").addRole("foo"); + c1.build(); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithNullUserId() { + ClaimBuilder c1 = new ClaimBuilder().setUser("john").setUserId(null).addRole("foo"); + c1.build(); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithEmptyUserId() { + ClaimBuilder c1 = new ClaimBuilder().setUser("john").setUserId(" ").addRole("foo"); + c1.build(); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithoutRole() { + ClaimBuilder c1 = new ClaimBuilder().setUser("john").setUserId("1234"); + c1.build(); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithNullRole() { + ClaimBuilder c1 = new ClaimBuilder().setUser("john").setUserId("1234").addRole(null); + c1.build(); + } + + @Test(expected = IllegalStateException.class) + public void testBuildWithEmptyRole() { + ClaimBuilder c1 = new ClaimBuilder().setUser("john").setUserId("1234").addRole(" "); + c1.build(); + } + + @Test + public void testEquals() { + Claim c1 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") + .setUserId("1234").addRole("foo").build(); + assertTrue(c1.equals(c1)); + Claim c2 = new ClaimBuilder(c1).addRole("foo").build(); + assertTrue(c1.equals(c2)); + assertTrue(c2.equals(c1)); + Claim c3 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") + .setUserId("1234").addRole("foo").build(); + assertTrue(c1.equals(c3)); + assertTrue(c3.equals(c2)); + assertTrue(c1.equals(c2)); + } + + @Test + public void testNotEquals() { + Claim c1 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") + .setUserId("1234").addRole("foo").build(); + assertFalse(c1.equals(null)); + assertFalse(c1.equals("wrong object")); + Claim c2 = new ClaimBuilder(c1).addRoles(new HashSet<>(Arrays.asList("foo", "bar"))) + .build(); + assertEquals(2, c2.roles().size()); + assertFalse(c1.equals(c2)); + assertFalse(c2.equals(c1)); + Claim c3 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") + .setUserId("1234").addRole("foo").build(); + assertFalse(c1.equals(c2)); + assertTrue(c1.equals(c3)); + assertFalse(c2.equals(c3)); + Claim c5 = new ClaimBuilder().setUser("john").setUserId("1234").addRole("foo").build(); + assertFalse(c1.equals(c5)); + assertFalse(c5.equals(c1)); + } + + @Test + public void testHash() { + Claim c1 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") + .setUserId("1234").addRole("foo").build(); + assertEquals(c1.hashCode(), c1.hashCode()); + Claim c2 = new ClaimBuilder(c1).addRole("foo").build(); + assertTrue(c1.equals(c2)); + assertEquals(c1.hashCode(), c2.hashCode()); + Claim c3 = new ClaimBuilder(c1).addRoles(new HashSet<>(Arrays.asList("foo", "bar"))) + .build(); + assertFalse(c1.equals(c3)); + assertNotEquals(c1.hashCode(), c3.hashCode()); + Claim c4 = new ClaimBuilder().setClientId("dlux").setDomain("pepsi").setUser("john") + .setUserId("1234").addRole("foo").build(); + assertTrue(c1.equals(c4)); + assertEquals(c1.hashCode(), c4.hashCode()); + assertEquals(c2.hashCode(), c4.hashCode()); + Claim c5 = new ClaimBuilder().setUser("john").setUserId("1234").addRole("foo").build(); + assertFalse(c1.equals(c5)); + assertNotEquals(c1.hashCode(), c5.hashCode()); + } + + @Test + public void testToString() { + Claim c1 = new ClaimBuilder().setUser("john").setUserId("1234").addRole("foo").build(); + assertEquals("clientId:,userId:1234,userName:john,domain:,roles:[foo]", c1.toString()); + c1 = new ClaimBuilder(c1).setClientId("dlux").setDomain("pepsi").build(); + assertEquals("clientId:dlux,userId:1234,userName:john,domain:pepsi,roles:[foo]", + c1.toString()); + c1 = new ClaimBuilder(c1).addRole("bar").build(); + assertEquals("clientId:dlux,userId:1234,userName:john,domain:pepsi,roles:[foo, bar]", + c1.toString()); + } +} \ No newline at end of file diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClientManagerTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClientManagerTest.java new file mode 100644 index 00000000..059ba9a3 --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/ClientManagerTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.aaa; + +import static org.junit.Assert.fail; + +import java.util.Dictionary; +import java.util.Hashtable; +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.aaa.api.AuthenticationException; +import org.osgi.service.cm.ConfigurationException; + +/** + * + * @author liemmn + * + */ +public class ClientManagerTest { + private static final ClientManager cm = new ClientManager(); + + @Before + public void setup() throws ConfigurationException { + cm.init(null); + } + + @Test + public void testValidate() { + cm.validate("dlux", "secrete"); + } + + @Test(expected = AuthenticationException.class) + public void testFailValidate() { + cm.validate("dlux", "what?"); + } + + @Test + public void testUpdate() throws ConfigurationException { + Dictionary configs = new Hashtable<>(); + configs.put(ClientManager.CLIENTS, "aws:amazon dlux:xxx"); + cm.updated(configs); + cm.validate("aws", "amazon"); + cm.validate("dlux", "xxx"); + } + + @Test + public void testFailUpdate() { + Dictionary configs = new Hashtable<>(); + configs.put(ClientManager.CLIENTS, "aws:amazon dlux"); + try { + cm.updated(configs); + fail("Shoulda failed updating bad configuration"); + } catch (ConfigurationException ce) { + // Expected + } + cm.validate("dlux", "secrete"); + try { + cm.validate("aws", "amazon"); + fail("Shoulda failed updating bad configuration"); + } catch (AuthenticationException ae) { + // Expected + } + } +} diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/PasswordCredentialTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/PasswordCredentialTest.java new file mode 100644 index 00000000..2dabb77b --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/PasswordCredentialTest.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.aaa; + +import static org.junit.Assert.assertEquals; + +import java.util.HashSet; +import org.junit.Test; +import org.opendaylight.aaa.api.PasswordCredentials; + +public class PasswordCredentialTest { + + @Test + public void testBuilder() { + PasswordCredentials pc1 = new PasswordCredentialBuilder().setUserName("bob") + .setPassword("secrete").build(); + assertEquals("bob", pc1.username()); + assertEquals("secrete", pc1.password()); + + PasswordCredentials pc2 = new PasswordCredentialBuilder().setUserName("bob") + .setPassword("secrete").build(); + assertEquals(pc1, pc2); + + PasswordCredentials pc3 = new PasswordCredentialBuilder().setUserName("bob") + .setPassword("secret").build(); + HashSet pcs = new HashSet<>(); + pcs.add(pc1); + pcs.add(pc2); + pcs.add(pc3); + assertEquals(2, pcs.size()); + } + +} diff --git a/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/SecureBlockingQueueTest.java b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/SecureBlockingQueueTest.java new file mode 100644 index 00000000..16627d9f --- /dev/null +++ b/odl-aaa-moon/aaa-authn/src/test/java/org/opendaylight/aaa/SecureBlockingQueueTest.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2014, 2015 Hewlett-Packard Development Company, L.P. and others. All rights reserved. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v1.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v10.html + */ + +package org.opendaylight.aaa; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import java.util.Arrays; +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.BlockingQueue; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.ThreadPoolExecutor; +import java.util.concurrent.TimeUnit; +import org.junit.Before; +import org.junit.Test; +import org.opendaylight.aaa.SecureBlockingQueue.SecureData; +import org.opendaylight.aaa.api.Authentication; + +public class SecureBlockingQueueTest { + private final int MAX_TASKS = 100; + + @Before + public void setup() { + AuthenticationManager.instance().clear(); + } + + @Test + public void testSecureThreadPoolExecutor() throws InterruptedException, ExecutionException { + BlockingQueue queue = new SecureBlockingQueue<>( + new ArrayBlockingQueue>(10)); + ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 500, TimeUnit.MILLISECONDS, + queue); + executor.prestartAllCoreThreads(); + for (int cnt = 1; cnt <= MAX_TASKS; cnt++) { + assertEquals(Integer.toString(cnt), + executor.submit(new Task(Integer.toString(cnt), "1111", "user")).get().user()); + } + executor.shutdown(); + } + + @Test + public void testNormalThreadPoolExecutor() throws InterruptedException, ExecutionException { + BlockingQueue queue = new ArrayBlockingQueue(10); + ThreadPoolExecutor executor = new ThreadPoolExecutor(5, 10, 500, TimeUnit.MILLISECONDS, + queue); + executor.prestartAllCoreThreads(); + for (int cnt = 1; cnt <= MAX_TASKS; cnt++) { + assertNull(executor.submit(new Task(Integer.toString(cnt), "1111", "user")).get()); + } + executor.shutdown(); + } + + @Test + public void testQueueOps() throws InterruptedException, ExecutionException { + BlockingQueue queue = new SecureBlockingQueue<>( + new ArrayBlockingQueue>(3)); + ExecutorService es = Executors.newFixedThreadPool(3); + es.submit(new Producer("foo", "1111", "user", queue)).get(); + assertEquals(1, queue.size()); + assertEquals("foo", es.submit(new Consumer(queue)).get()); + es.submit(new Producer("bar", "2222", "user", queue)).get(); + assertEquals("bar", queue.peek()); + assertEquals("bar", queue.element()); + assertEquals(1, queue.size()); + assertEquals("bar", queue.poll()); + assertTrue(queue.isEmpty()); + es.shutdown(); + } + + @Test + public void testCollectionOps() throws InterruptedException, ExecutionException { + BlockingQueue queue = new SecureBlockingQueue<>( + new ArrayBlockingQueue>(6)); + for (int i = 1; i <= 3; i++) + queue.add("User" + i); + Iterator it = queue.iterator(); + while (it.hasNext()) + assertTrue(it.next().startsWith("User")); + assertEquals(3, queue.toArray().length); + List actual = Arrays.asList(queue.toArray(new String[0])); + assertEquals("User1", actual.iterator().next()); + assertTrue(queue.containsAll(actual)); + queue.addAll(actual); + assertEquals(6, queue.size()); + queue.retainAll(Arrays.asList(new String[] { "User2" })); + assertEquals(2, queue.size()); + assertEquals("User2", queue.iterator().next()); + queue.removeAll(actual); + assertTrue(queue.isEmpty()); + queue.add("hello"); + assertEquals(1, queue.size()); + queue.clear(); + assertTrue(queue.isEmpty()); + } + + @Test + public void testBlockingQueueOps() throws InterruptedException { + BlockingQueue queue = new SecureBlockingQueue<>( + new ArrayBlockingQueue>(3)); + queue.offer("foo"); + assertEquals(1, queue.size()); + queue.offer("bar", 500, TimeUnit.MILLISECONDS); + assertEquals(2, queue.size()); + assertEquals("foo", queue.poll()); + assertTrue(queue.contains("bar")); + queue.remove("bar"); + assertEquals(3, queue.remainingCapacity()); + queue.addAll(Arrays.asList(new String[] { "foo", "bar", "tom" })); + assertEquals(3, queue.size()); + assertEquals("foo", queue.poll(500, TimeUnit.MILLISECONDS)); + assertEquals(2, queue.size()); + List drain = new LinkedList<>(); + queue.drainTo(drain); + assertTrue(queue.isEmpty()); + assertEquals(2, drain.size()); + queue.addAll(Arrays.asList(new String[] { "foo", "bar", "tom" })); + drain.clear(); + queue.drainTo(drain, 1); + assertEquals(2, queue.size()); + assertEquals(1, drain.size()); + } + + // Task to run in a ThreadPoolExecutor + private class Task implements Callable { + Task(String name, String userId, String role) { + // Mock that each task has its original authentication context + AuthenticationManager.instance().set( + new AuthenticationBuilder(new ClaimBuilder().setUser(name).setUserId(userId) + .addRole(role).build()).build()); + } + + @Override + public Authentication call() throws Exception { + return AuthenticationManager.instance().get(); + } + } + + // Producer sets auth context + private class Producer implements Callable { + private final String name; + private final String userId; + private final String role; + private final BlockingQueue queue; + + Producer(String name, String userId, String role, BlockingQueue queue) { + this.name = name; + this.userId = userId; + this.role = role; + this.queue = queue; + } + + @Override + public String call() throws InterruptedException { + AuthenticationManager.instance().set( + new AuthenticationBuilder(new ClaimBuilder().setUser(name).setUserId(userId) + .addRole(role).build()).build()); + queue.put(name); + return name; + } + } + + // Consumer gets producer's auth context via data element in queue + private class Consumer implements Callable { + private final BlockingQueue queue; + + Consumer(BlockingQueue queue) { + this.queue = queue; + } + + @Override + public String call() { + queue.remove(); + Authentication auth = AuthenticationManager.instance().get(); + return (auth == null) ? null : auth.user(); + } + } + +} -- cgit 1.2.3-korg