2 * Copyright (c) 2015 - 2017 Brocade Communications Systems, Inc. and others. All rights reserved.
4 * This program and the accompanying materials are made available under the
5 * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6 * and is available at http://www.eclipse.org/legal/epl-v10.html
9 package org.opendaylight.aaa.shiro.realm;
11 import com.google.common.base.Strings;
12 import java.util.List;
14 import java.util.Objects;
15 import org.apache.shiro.authc.AuthenticationException;
16 import org.apache.shiro.authc.AuthenticationInfo;
17 import org.apache.shiro.authc.AuthenticationToken;
18 import org.apache.shiro.authc.SimpleAuthenticationInfo;
19 import org.apache.shiro.authz.AuthorizationInfo;
20 import org.apache.shiro.authz.SimpleAuthorizationInfo;
21 import org.apache.shiro.realm.AuthorizingRealm;
22 import org.apache.shiro.subject.PrincipalCollection;
23 import org.opendaylight.aaa.api.Authentication;
24 import org.opendaylight.aaa.api.AuthenticationService;
25 import org.opendaylight.aaa.api.TokenAuth;
26 import org.opendaylight.aaa.api.TokenStore;
27 import org.opendaylight.aaa.api.shiro.principal.ODLPrincipal;
28 import org.opendaylight.aaa.shiro.principal.ODLPrincipalImpl;
29 import org.opendaylight.aaa.shiro.realm.util.TokenUtils;
30 import org.opendaylight.aaa.shiro.realm.util.http.header.HeaderUtils;
31 import org.opendaylight.aaa.shiro.web.env.ThreadLocals;
32 import org.opendaylight.aaa.tokenauthrealm.auth.TokenAuthenticators;
33 import org.slf4j.Logger;
34 import org.slf4j.LoggerFactory;
37 * TokenAuthRealm is an adapter between the AAA shiro subsystem and the existing
38 * <code>TokenAuth</code> mechanisms. Thus, one can enable use of
39 * <code>IDMStore</code> and <code>IDMMDSALStore</code>.
41 public class TokenAuthRealm extends AuthorizingRealm {
44 * The unique identifying name for <code>TokenAuthRealm</code>.
46 private static final String TOKEN_AUTH_REALM_DEFAULT_NAME = "TokenAuthRealm";
49 * The message that is displayed if no <code>TokenAuth</code> interface is available yet.
51 private static final String AUTHENTICATION_SERVICE_UNAVAILABLE_MESSAGE =
52 "{\"error\":\"Authentication service unavailable\"}";
55 * The message that is displayed if credentials are missing or malformed.
57 private static final String FATAL_ERROR_DECODING_CREDENTIALS = "{\"error\":\"Unable to decode credentials\"}";
60 * The message that is displayed if non-Basic Auth is attempted.
62 private static final String FATAL_ERROR_BASIC_AUTH_ONLY
63 = "{\"error\":\"Only basic authentication is supported by TokenAuthRealm\"}";
66 * The purposefully generic message displayed if <code>TokenAuth</code> is
67 * unable to validate the given credentials.
69 private static final String UNABLE_TO_AUTHENTICATE = "{\"error\":\"Could not authenticate\"}";
71 private static final Logger LOG = LoggerFactory.getLogger(TokenAuthRealm.class);
73 private final AuthenticationService authenticationService;
74 private final TokenStore tokenStore;
75 private final TokenAuthenticators tokenAuthenticators;
77 public TokenAuthRealm() {
78 super.setName(TOKEN_AUTH_REALM_DEFAULT_NAME);
79 authenticationService = Objects.requireNonNull(ThreadLocals.AUTH_SETVICE_TL.get());
80 tokenStore = ThreadLocals.TOKEN_STORE_TL.get();
81 tokenAuthenticators = Objects.requireNonNull(ThreadLocals.TOKEN_AUTHENICATORS_TL.get());
87 * Roles are derived from <code>TokenAuth.authenticate()</code>. Shiro roles
88 * are identical to existing IDM roles.
91 * org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache
92 * .shiro.subject.PrincipalCollection)
95 protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principalCollection) {
96 final Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
97 final ODLPrincipal odlPrincipal;
99 odlPrincipal = (ODLPrincipal) primaryPrincipal;
100 return new SimpleAuthorizationInfo(odlPrincipal.getRoles());
101 } catch (ClassCastException e) {
102 LOG.error("Couldn't decode authorization request", e);
104 return new SimpleAuthorizationInfo();
110 * Authenticates against any <code>TokenAuth</code> registered with the
111 * <code>ServiceLocator</code>
114 * org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org
115 * .apache.shiro.authc.AuthenticationToken)
118 protected AuthenticationInfo doGetAuthenticationInfo(
119 final AuthenticationToken authenticationToken) throws AuthenticationException {
121 final String username;
122 final String password;
126 final String possiblyQualifiedUser = TokenUtils.extractUsername(authenticationToken);
127 username = HeaderUtils.extractUsername(possiblyQualifiedUser);
128 domain = HeaderUtils.extractDomain(possiblyQualifiedUser);
129 password = TokenUtils.extractPassword(authenticationToken);
131 } catch (NullPointerException e) {
132 throw new AuthenticationException(FATAL_ERROR_DECODING_CREDENTIALS, e);
133 } catch (ClassCastException e) {
134 throw new AuthenticationException(FATAL_ERROR_BASIC_AUTH_ONLY, e);
137 // if the password is empty, this is an OAuth2 request, not a Basic HTTP
139 if (!Strings.isNullOrEmpty(password)) {
140 Map<String, List<String>> headers = HeaderUtils.formHeaders(username, password, domain);
141 // iterate over <code>TokenAuth</code> implementations and
143 // authentication with each one
144 for (TokenAuth ta : tokenAuthenticators.getTokenAuthCollection()) {
146 LOG.debug("Authentication attempt using {}", ta.getClass().getName());
147 final Authentication auth = ta.validate(headers);
149 LOG.debug("Authentication attempt successful");
150 authenticationService.set(auth);
151 final ODLPrincipal odlPrincipal = ODLPrincipalImpl.createODLPrincipal(auth);
152 return new SimpleAuthenticationInfo(odlPrincipal, password.toCharArray(), getName());
154 } catch (AuthenticationException ae) {
155 LOG.debug("Authentication attempt unsuccessful");
156 throw new AuthenticationException(UNABLE_TO_AUTHENTICATE, ae);
161 // extract the authentication token and attempt validation of the token
162 final String token = TokenUtils.extractUsername(authenticationToken);
164 final Authentication auth = validate(token);
165 final ODLPrincipal odlPrincipal = ODLPrincipalImpl.createODLPrincipal(auth);
166 return new SimpleAuthenticationInfo(odlPrincipal, "", getName());
167 } catch (AuthenticationException e) {
168 LOG.debug("Unknown OAuth2 Token Access Request", e);
171 LOG.debug("Authentication failed: exhausted TokenAuth resources");
175 private Authentication validate(final String token) {
176 if (tokenStore == null) {
177 throw new AuthenticationException("Token store not available, could not validate the token " + token);
180 final Authentication auth = tokenStore.get(token);
182 throw new AuthenticationException("Could not validate the token " + token);
184 authenticationService.set(auth);