aboutsummaryrefslogtreecommitdiffstats
path: root/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealm.java
diff options
context:
space:
mode:
Diffstat (limited to 'upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealm.java')
-rw-r--r--upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealm.java369
1 files changed, 369 insertions, 0 deletions
diff --git a/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealm.java b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealm.java
new file mode 100644
index 00000000..f9ae5051
--- /dev/null
+++ b/upstream/odl-aaa-moon/aaa/aaa-shiro/src/main/java/org/opendaylight/aaa/shiro/realm/TokenAuthRealm.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (c) 2015 Brocade Communications Systems, Inc. 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.shiro.realm;
+
+import com.google.common.base.Strings;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authz.AuthorizationInfo;
+import org.apache.shiro.authz.SimpleAuthorizationInfo;
+import org.apache.shiro.codec.Base64;
+import org.apache.shiro.realm.AuthorizingRealm;
+import org.apache.shiro.subject.PrincipalCollection;
+import org.opendaylight.aaa.api.Authentication;
+import org.opendaylight.aaa.api.TokenAuth;
+import org.opendaylight.aaa.basic.HttpBasicAuth;
+import org.opendaylight.aaa.sts.ServiceLocator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * TokenAuthRealm is an adapter between the AAA shiro subsystem and the existing
+ * <code>TokenAuth</code> mechanisms. Thus, one can enable use of
+ * <code>IDMStore</code> and <code>IDMMDSALStore</code>.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+public class TokenAuthRealm extends AuthorizingRealm {
+
+ private static final String USERNAME_DOMAIN_SEPARATOR = "@";
+
+ /**
+ * The unique identifying name for <code>TokenAuthRealm</code>
+ */
+ private static final String TOKEN_AUTH_REALM_DEFAULT_NAME = "TokenAuthRealm";
+
+ /**
+ * The message that is displayed if no <code>TokenAuth</code> interface is
+ * available yet
+ */
+ private static final String AUTHENTICATION_SERVICE_UNAVAILABLE_MESSAGE = "{\"error\":\"Authentication service unavailable\"}";
+
+ /**
+ * The message that is displayed if credentials are missing or malformed
+ */
+ private static final String FATAL_ERROR_DECODING_CREDENTIALS = "{\"error\":\"Unable to decode credentials\"}";
+
+ /**
+ * The message that is displayed if non-Basic Auth is attempted
+ */
+ private static final String FATAL_ERROR_BASIC_AUTH_ONLY = "{\"error\":\"Only basic authentication is supported by TokenAuthRealm\"}";
+
+ /**
+ * The purposefully generic message displayed if <code>TokenAuth</code> is
+ * unable to validate the given credentials
+ */
+ private static final String UNABLE_TO_AUTHENTICATE = "{\"error\":\"Could not authenticate\"}";
+
+ private static final Logger LOG = LoggerFactory.getLogger(TokenAuthRealm.class);
+
+ public TokenAuthRealm() {
+ super();
+ super.setName(TOKEN_AUTH_REALM_DEFAULT_NAME);
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * Roles are derived from <code>TokenAuth.authenticate()</code>. Shiro roles
+ * are identical to existing IDM roles.
+ *
+ * @see
+ * org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache
+ * .shiro.subject.PrincipalCollection)
+ */
+ @Override
+ protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principalCollection) {
+ final Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
+ final ODLPrincipal odlPrincipal;
+ try {
+ odlPrincipal = (ODLPrincipal) primaryPrincipal;
+ return new SimpleAuthorizationInfo(odlPrincipal.getRoles());
+ } catch(ClassCastException e) {
+ LOG.error("Couldn't decode authorization request", e);
+ }
+ return new SimpleAuthorizationInfo();
+ }
+
+ /**
+ * Bridge new to old style <code>TokenAuth</code> interface.
+ *
+ * @param username The request username
+ * @param password The request password
+ * @param domain The request domain
+ * @return <code>username:password:domain</code>
+ */
+ static String getUsernamePasswordDomainString(final String username, final String password,
+ final String domain) {
+ return username + HttpBasicAuth.AUTH_SEP + password + HttpBasicAuth.AUTH_SEP + domain;
+ }
+
+ /**
+ *
+ * @param credentialToken
+ * @return Base64 encoded token
+ */
+ static String getEncodedToken(final String credentialToken) {
+ return Base64.encodeToString(credentialToken.getBytes());
+ }
+
+ /**
+ *
+ * @param encodedToken
+ * @return Basic <code>encodedToken</code>
+ */
+ static String getTokenAuthHeader(final String encodedToken) {
+ return HttpBasicAuth.BASIC_PREFIX + encodedToken;
+ }
+
+ /**
+ *
+ * @param tokenAuthHeader
+ * @return a map with the basic auth header
+ */
+ Map<String, List<String>> formHeadersWithToken(final String tokenAuthHeader) {
+ final Map<String, List<String>> headers = new HashMap<String, List<String>>();
+ final List<String> headerValue = new ArrayList<String>();
+ headerValue.add(tokenAuthHeader);
+ headers.put(HttpBasicAuth.AUTH_HEADER, headerValue);
+ return headers;
+ }
+
+ /**
+ * Adapter between basic authentication mechanism and existing
+ * <code>TokenAuth</code> interface.
+ *
+ * @param username Username from the request
+ * @param password Password from the request
+ * @param domain Domain from the request
+ * @return input map for <code>TokenAuth.validate()</code>
+ */
+ Map<String, List<String>> formHeaders(final String username, final String password,
+ final String domain) {
+ String usernamePasswordToken = getUsernamePasswordDomainString(username, password, domain);
+ String encodedToken = getEncodedToken(usernamePasswordToken);
+ String tokenAuthHeader = getTokenAuthHeader(encodedToken);
+ return formHeadersWithToken(tokenAuthHeader);
+ }
+
+ /**
+ * Adapter to check for available <code>TokenAuth<code> implementations.
+ *
+ * @return
+ */
+ boolean isTokenAuthAvailable() {
+ return ServiceLocator.getInstance().getAuthenticationService() != null;
+ }
+
+ /*
+ * (non-Javadoc)
+ *
+ * Authenticates against any <code>TokenAuth</code> registered with the
+ * <code>ServiceLocator</code>
+ *
+ * @see
+ * org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org
+ * .apache.shiro.authc.AuthenticationToken)
+ */
+ @Override
+ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken authenticationToken)
+ throws AuthenticationException {
+
+ String username = "";
+ String password = "";
+ String domain = HttpBasicAuth.DEFAULT_DOMAIN;
+
+ try {
+ final String qualifiedUser = extractUsername(authenticationToken);
+ if (qualifiedUser.contains(USERNAME_DOMAIN_SEPARATOR)) {
+ final String [] qualifiedUserArray = qualifiedUser.split(USERNAME_DOMAIN_SEPARATOR);
+ try {
+ username = qualifiedUserArray[0];
+ domain = qualifiedUserArray[1];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ LOG.trace("Couldn't parse domain from {}; trying without one",
+ qualifiedUser, e);
+ }
+ } else {
+ username = qualifiedUser;
+ }
+ password = extractPassword(authenticationToken);
+
+ } catch (NullPointerException e) {
+ throw new AuthenticationException(FATAL_ERROR_DECODING_CREDENTIALS, e);
+ } catch (ClassCastException e) {
+ throw new AuthenticationException(FATAL_ERROR_BASIC_AUTH_ONLY, e);
+ }
+
+ // check to see if there are TokenAuth implementations available
+ if (!isTokenAuthAvailable()) {
+ throw new AuthenticationException(AUTHENTICATION_SERVICE_UNAVAILABLE_MESSAGE);
+ }
+
+ // if the password is empty, this is an OAuth2 request, not a Basic HTTP
+ // Auth request
+ if (!Strings.isNullOrEmpty(password)) {
+ if (ServiceLocator.getInstance().getAuthenticationService().isAuthEnabled()) {
+ Map<String, List<String>> headers = formHeaders(username, password, domain);
+ // iterate over <code>TokenAuth</code> implementations and
+ // attempt to
+ // authentication with each one
+ final List<TokenAuth> tokenAuthCollection = ServiceLocator.getInstance()
+ .getTokenAuthCollection();
+ for (TokenAuth ta : tokenAuthCollection) {
+ try {
+ LOG.debug("Authentication attempt using {}", ta.getClass().getName());
+ final Authentication auth = ta.validate(headers);
+ if (auth != null) {
+ LOG.debug("Authentication attempt successful");
+ ServiceLocator.getInstance().getAuthenticationService().set(auth);
+ final ODLPrincipal odlPrincipal = ODLPrincipal.createODLPrincipal(auth);
+ return new SimpleAuthenticationInfo(odlPrincipal, password.toCharArray(),
+ getName());
+ }
+ } catch (AuthenticationException ae) {
+ LOG.debug("Authentication attempt unsuccessful");
+ throw new AuthenticationException(UNABLE_TO_AUTHENTICATE, ae);
+ }
+ }
+ }
+ }
+
+ // extract the authentication token and attempt validation of the token
+ final String token = extractUsername(authenticationToken);
+ final Authentication auth;
+ try {
+ auth = validate(token);
+ if (auth != null) {
+ final ODLPrincipal odlPrincipal = ODLPrincipal.createODLPrincipal(auth);
+ return new SimpleAuthenticationInfo(odlPrincipal, "", getName());
+ }
+ } catch (AuthenticationException e) {
+ LOG.debug("Unknown OAuth2 Token Access Request", e);
+ }
+
+ LOG.debug("Authentication failed: exhausted TokenAuth resources");
+ return null;
+ }
+
+ private Authentication validate(final String token) {
+ Authentication auth = ServiceLocator.getInstance().getTokenStore().get(token);
+ if (auth == null) {
+ throw new AuthenticationException("Could not validate the token " + token);
+ } else {
+ ServiceLocator.getInstance().getAuthenticationService().set(auth);
+ }
+ return auth;
+ }
+
+ /**
+ * extract the username from an <code>AuthenticationToken</code>
+ *
+ * @param authenticationToken
+ * @return
+ * @throws ClassCastException
+ * @throws NullPointerException
+ */
+ static String extractUsername(final AuthenticationToken authenticationToken)
+ throws ClassCastException, NullPointerException {
+
+ return (String) authenticationToken.getPrincipal();
+ }
+
+ /**
+ * extract the password from an <code>AuthenticationToken</code>
+ *
+ * @param authenticationToken
+ * @return
+ * @throws ClassCastException
+ * @throws NullPointerException
+ */
+ static String extractPassword(final AuthenticationToken authenticationToken)
+ throws ClassCastException, NullPointerException {
+
+ final UsernamePasswordToken upt = (UsernamePasswordToken) authenticationToken;
+ return new String(upt.getPassword());
+ }
+
+ /**
+ * Since <code>TokenAuthRealm</code> is an <code>AuthorizingRealm</code>, it supports
+ * individual steps for authentication and authorization. In ODL's existing <code>TokenAuth</code>
+ * mechanism, authentication and authorization are currently done in a single monolithic step.
+ * <code>ODLPrincipal</code> is abstracted as a DTO between the two steps. It fulfills the
+ * responsibility of a <code>Principal</code>, since it contains identification information
+ * but no credential information.
+ *
+ * @author Ryan Goulding (ryandgoulding@gmail.com)
+ */
+ private static class ODLPrincipal {
+
+ private final String username;
+ private final String domain;
+ private final String userId;
+ private final Set<String> roles;
+
+ private ODLPrincipal(final String username, final String domain, final String userId, final Set<String> roles) {
+ this.username = username;
+ this.domain = domain;
+ this.userId = userId;
+ this.roles = roles;
+ }
+
+ /**
+ * A static factory method to create <code>ODLPrincipal</code> instances.
+ *
+ * @param username The authenticated user
+ * @param domain The domain <code>username</code> belongs to.
+ * @param userId The unique key for <code>username</code>
+ * @param roles The roles associated with <code>username</code>@<code>domain</code>
+ * @return A Principal for the given session; essentially a DTO.
+ */
+ static ODLPrincipal createODLPrincipal(final String username, final String domain,
+ final String userId, final Set<String> roles) {
+
+ return new ODLPrincipal(username, domain, userId, roles);
+ }
+
+ /**
+ * A static factory method to create <code>ODLPrincipal</code> instances.
+ *
+ * @param auth Contains identifying information for the particular request.
+ * @return A Principal for the given session; essentially a DTO.
+ */
+ static ODLPrincipal createODLPrincipal(final Authentication auth) {
+ return createODLPrincipal(auth.user(), auth.domain(), auth.userId(), auth.roles());
+ }
+
+ String getUsername() {
+ return this.username;
+ }
+
+ String getDomain() {
+ return this.domain;
+ }
+
+ String getUserId() {
+ return this.userId;
+ }
+
+ Set<String> getRoles() {
+ return this.roles;
+ }
+ }
+}