aboutsummaryrefslogtreecommitdiffstats
path: root/odl-aaa-moon/aaa/aaa-authn-basic
diff options
context:
space:
mode:
Diffstat (limited to 'odl-aaa-moon/aaa/aaa-authn-basic')
-rw-r--r--odl-aaa-moon/aaa/aaa-authn-basic/pom.xml76
-rw-r--r--odl-aaa-moon/aaa/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/Activator.java31
-rw-r--r--odl-aaa-moon/aaa/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/HttpBasicAuth.java129
-rw-r--r--odl-aaa-moon/aaa/aaa-authn-basic/src/test/java/org/opendaylight/aaa/basic/HttpBasicAuthTest.java102
4 files changed, 338 insertions, 0 deletions
diff --git a/odl-aaa-moon/aaa/aaa-authn-basic/pom.xml b/odl-aaa-moon/aaa/aaa-authn-basic/pom.xml
new file mode 100644
index 00000000..47562896
--- /dev/null
+++ b/odl-aaa-moon/aaa/aaa-authn-basic/pom.xml
@@ -0,0 +1,76 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-parent</artifactId>
+ <version>0.3.2-Beryllium-SR2</version>
+ <relativePath>../parent</relativePath>
+ </parent>
+
+ <artifactId>aaa-authn-basic</artifactId>
+ <packaging>bundle</packaging>
+
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.aaa</groupId>
+ <artifactId>aaa-authn-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-api</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>com.sun.jersey</groupId>
+ <artifactId>jersey-server</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.core</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>org.apache.felix.dependencymanager</artifactId>
+ <scope>provided</scope>
+ </dependency>
+ <!-- Testing Dependencies -->
+ <dependency>
+ <groupId>junit</groupId>
+ <artifactId>junit</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.slf4j</groupId>
+ <artifactId>slf4j-simple</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
+ <groupId>org.mockito</groupId>
+ <artifactId>mockito-all</artifactId>
+ <scope>test</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Bundle-Activator>org.opendaylight.aaa.basic.Activator</Bundle-Activator>
+ </instructions>
+ <manifestLocation>${project.basedir}/META-INF</manifestLocation>
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+</project>
diff --git a/odl-aaa-moon/aaa/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/Activator.java b/odl-aaa-moon/aaa/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/Activator.java
new file mode 100644
index 00000000..bd57c9d3
--- /dev/null
+++ b/odl-aaa-moon/aaa/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/Activator.java
@@ -0,0 +1,31 @@
+/*
+ * 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.basic;
+
+import org.apache.felix.dm.DependencyActivatorBase;
+import org.apache.felix.dm.DependencyManager;
+import org.opendaylight.aaa.api.CredentialAuth;
+import org.opendaylight.aaa.api.TokenAuth;
+import org.osgi.framework.BundleContext;
+
+public class Activator extends DependencyActivatorBase {
+
+ @Override
+ public void init(BundleContext context, DependencyManager manager) throws Exception {
+ manager.add(createComponent()
+ .setInterface(new String[] { TokenAuth.class.getName() }, null)
+ .setImplementation(HttpBasicAuth.class)
+ .add(createServiceDependency().setService(CredentialAuth.class).setRequired(true)));
+ }
+
+ @Override
+ public void destroy(BundleContext context, DependencyManager manager) throws Exception {
+ }
+
+}
diff --git a/odl-aaa-moon/aaa/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/HttpBasicAuth.java b/odl-aaa-moon/aaa/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/HttpBasicAuth.java
new file mode 100644
index 00000000..eff47e63
--- /dev/null
+++ b/odl-aaa-moon/aaa/aaa-authn-basic/src/main/java/org/opendaylight/aaa/basic/HttpBasicAuth.java
@@ -0,0 +1,129 @@
+/*
+ * 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.basic;
+
+import com.sun.jersey.core.util.Base64;
+import java.util.List;
+import java.util.Map;
+import org.opendaylight.aaa.AuthenticationBuilder;
+import org.opendaylight.aaa.PasswordCredentialBuilder;
+import org.opendaylight.aaa.api.Authentication;
+import org.opendaylight.aaa.api.AuthenticationException;
+import org.opendaylight.aaa.api.Claim;
+import org.opendaylight.aaa.api.CredentialAuth;
+import org.opendaylight.aaa.api.PasswordCredentials;
+import org.opendaylight.aaa.api.TokenAuth;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * An HTTP Basic authenticator. Note that this is provided as a Hydrogen
+ * backward compatible authenticator, but usage of this authenticator or HTTP
+ * Basic Authentication is highly discouraged due to its vulnerability.
+ *
+ * To obtain a token using the HttpBasicAuth Strategy, add a header to your HTTP
+ * request in the form:
+ * <code>Authorization: Basic BASE_64_ENCODED_CREDENTIALS</code>
+ *
+ * Where <code>BASE_64_ENCODED_CREDENTIALS</code> is the base 64 encoded value
+ * of the user's credentials in the following form: <code>user:password</code>
+ *
+ * For example, assuming the user is "admin" and the password is "admin":
+ * <code>Authorization: Basic YWRtaW46YWRtaW4=</code>
+ *
+ * @author liemmn
+ *
+ */
+public class HttpBasicAuth implements TokenAuth {
+
+ public static final String AUTH_HEADER = "Authorization";
+
+ public static final String AUTH_SEP = ":";
+
+ public static final String BASIC_PREFIX = "Basic ";
+
+ // TODO relocate this constant
+ public static final String DEFAULT_DOMAIN = "sdn";
+
+ /**
+ * username and password
+ */
+ private static final int NUM_HEADER_CREDS = 2;
+
+ /**
+ * username, password and domain
+ */
+ private static final int NUM_TOKEN_CREDS = 3;
+
+ private static final Logger LOG = LoggerFactory.getLogger(HttpBasicAuth.class);
+
+ volatile CredentialAuth<PasswordCredentials> credentialAuth;
+
+ private static boolean checkAuthHeaderFormat(final String authHeader) {
+ return (authHeader != null && authHeader.startsWith(BASIC_PREFIX));
+ }
+
+ private static String extractAuthHeader(final Map<String, List<String>> headers) {
+ return headers.get(AUTH_HEADER).get(0);
+ }
+
+ private static String[] extractCredentialArray(final String authHeader) {
+ return new String(Base64.base64Decode(authHeader.substring(BASIC_PREFIX.length())))
+ .split(AUTH_SEP);
+ }
+
+ private static boolean verifyCredentialArray(final String[] creds) {
+ return (creds != null && creds.length == NUM_HEADER_CREDS);
+ }
+
+ private static String[] addDomainToCredentialArray(final String[] creds) {
+ String newCredentialArray[] = new String[NUM_TOKEN_CREDS];
+ System.arraycopy(creds, 0, newCredentialArray, 0, creds.length);
+ newCredentialArray[2] = DEFAULT_DOMAIN;
+ return newCredentialArray;
+ }
+
+ private static Authentication generateAuthentication(
+ CredentialAuth<PasswordCredentials> credentialAuth, final String[] creds)
+ throws ArrayIndexOutOfBoundsException {
+ final PasswordCredentials pc = new PasswordCredentialBuilder().setUserName(creds[0])
+ .setPassword(creds[1]).setDomain(creds[2]).build();
+ final Claim claim = credentialAuth.authenticate(pc);
+ return new AuthenticationBuilder(claim).build();
+ }
+
+ @Override
+ public Authentication validate(final Map<String, List<String>> headers)
+ throws AuthenticationException {
+ if (headers.containsKey(AUTH_HEADER)) {
+ final String authHeader = extractAuthHeader(headers);
+ if (checkAuthHeaderFormat(authHeader)) {
+ // HTTP Basic Auth
+ String[] creds = extractCredentialArray(authHeader);
+ // If no domain was supplied then use the default one, which is
+ // "sdn".
+ if (verifyCredentialArray(creds)) {
+ creds = addDomainToCredentialArray(creds);
+ }
+ // Assumes correct formatting in form Base64("user:password").
+ // Throws an exception if an unknown format is used.
+ try {
+ return generateAuthentication(this.credentialAuth, creds);
+ } catch (ArrayIndexOutOfBoundsException e) {
+ final String message = "Login Attempt in Bad Format."
+ + " Please provide user:password in Base64 format.";
+ LOG.info(message);
+ throw new AuthenticationException(message);
+ }
+ }
+ }
+ return null;
+ }
+
+}
diff --git a/odl-aaa-moon/aaa/aaa-authn-basic/src/test/java/org/opendaylight/aaa/basic/HttpBasicAuthTest.java b/odl-aaa-moon/aaa/aaa-authn-basic/src/test/java/org/opendaylight/aaa/basic/HttpBasicAuthTest.java
new file mode 100644
index 00000000..4ee439df
--- /dev/null
+++ b/odl-aaa-moon/aaa/aaa-authn-basic/src/test/java/org/opendaylight/aaa/basic/HttpBasicAuthTest.java
@@ -0,0 +1,102 @@
+/*
+ * 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.basic;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+import com.sun.jersey.core.util.Base64;
+import java.io.UnsupportedEncodingException;
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import org.junit.Before;
+import org.junit.Test;
+import org.opendaylight.aaa.ClaimBuilder;
+import org.opendaylight.aaa.PasswordCredentialBuilder;
+import org.opendaylight.aaa.api.AuthenticationException;
+import org.opendaylight.aaa.api.Claim;
+import org.opendaylight.aaa.api.CredentialAuth;
+
+public class HttpBasicAuthTest {
+ private static final String USERNAME = "admin";
+ private static final String PASSWORD = "admin";
+ private static final String DOMAIN = "sdn";
+ private HttpBasicAuth auth;
+
+ @SuppressWarnings("unchecked")
+ @Before
+ public void setup() {
+ auth = new HttpBasicAuth();
+ auth.credentialAuth = mock(CredentialAuth.class);
+ when(
+ auth.credentialAuth.authenticate(new PasswordCredentialBuilder()
+ .setUserName(USERNAME).setPassword(PASSWORD).setDomain(DOMAIN).build()))
+ .thenReturn(
+ new ClaimBuilder().setUser("admin").addRole("admin").setUserId("123")
+ .build());
+ when(
+ auth.credentialAuth.authenticate(new PasswordCredentialBuilder()
+ .setUserName(USERNAME).setPassword("bozo").setDomain(DOMAIN).build()))
+ .thenThrow(new AuthenticationException("barf"));
+ }
+
+ @Test
+ public void testValidateOk() throws UnsupportedEncodingException {
+ String data = USERNAME + ":" + PASSWORD + ":" + DOMAIN;
+ Map<String, List<String>> headers = new HashMap<>();
+ headers.put("Authorization",
+ Arrays.asList("Basic " + new String(Base64.encode(data.getBytes("utf-8")))));
+ Claim claim = auth.validate(headers);
+ assertNotNull(claim);
+ assertEquals(USERNAME, claim.user());
+ assertEquals("admin", claim.roles().iterator().next());
+ }
+
+ @Test(expected = AuthenticationException.class)
+ public void testValidateBadPassword() throws UnsupportedEncodingException {
+ String data = USERNAME + ":bozo:" + DOMAIN;
+ Map<String, List<String>> headers = new HashMap<>();
+ headers.put("Authorization",
+ Arrays.asList("Basic " + new String(Base64.encode(data.getBytes("utf-8")))));
+ auth.validate(headers);
+ }
+
+ @Test(expected = AuthenticationException.class)
+ public void testValidateBadPasswordNoDOMAIN() throws UnsupportedEncodingException {
+ String data = USERNAME + ":bozo";
+ Map<String, List<String>> headers = new HashMap<>();
+ headers.put("Authorization",
+ Arrays.asList("Basic " + new String(Base64.encode(data.getBytes("utf-8")))));
+ auth.validate(headers);
+ }
+
+ @Test(expected = AuthenticationException.class)
+ public void testBadHeaderFormatNoPassword() throws UnsupportedEncodingException {
+ // just provide the username
+ String data = USERNAME;
+ Map<String, List<String>> headers = new HashMap<>();
+ headers.put("Authorization",
+ Arrays.asList("Basic " + new String(Base64.encode(data.getBytes("utf-8")))));
+ auth.validate(headers);
+ }
+
+ @Test(expected = AuthenticationException.class)
+ public void testBadHeaderFormat() throws UnsupportedEncodingException {
+ // provide username:
+ String data = USERNAME + "$" + PASSWORD;
+ Map<String, List<String>> headers = new HashMap<>();
+ headers.put("Authorization",
+ Arrays.asList("Basic " + new String(Base64.encode(data.getBytes("utf-8")))));
+ auth.validate(headers);
+ }
+}