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
8 package org.opendaylight.aaa.shiro.realm;
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.base.Strings;
14 import java.util.List;
16 import org.apache.shiro.authc.AuthenticationException;
17 import org.apache.shiro.authc.AuthenticationInfo;
18 import org.apache.shiro.authc.AuthenticationToken;
19 import org.apache.shiro.authc.SimpleAuthenticationInfo;
20 import org.apache.shiro.authz.AuthorizationInfo;
21 import org.apache.shiro.authz.SimpleAuthorizationInfo;
22 import org.apache.shiro.realm.AuthorizingRealm;
23 import org.apache.shiro.subject.PrincipalCollection;
24 import org.eclipse.jdt.annotation.Nullable;
25 import org.opendaylight.aaa.api.Authentication;
26 import org.opendaylight.aaa.api.AuthenticationService;
27 import org.opendaylight.aaa.api.TokenAuth;
28 import org.opendaylight.aaa.api.TokenStore;
29 import org.opendaylight.aaa.api.shiro.principal.ODLPrincipal;
30 import org.opendaylight.aaa.shiro.principal.ODLPrincipalImpl;
31 import org.opendaylight.aaa.shiro.realm.util.TokenUtils;
32 import org.opendaylight.aaa.shiro.realm.util.http.header.HeaderUtils;
33 import org.opendaylight.aaa.tokenauthrealm.auth.TokenAuthenticators;
34 import org.opendaylight.yangtools.concepts.Registration;
35 import org.slf4j.Logger;
36 import org.slf4j.LoggerFactory;
39 * TokenAuthRealm is an adapter between the AAA shiro subsystem and the existing {@code TokenAuth} mechanisms. Thus, one
40 * can enable use of {@code IDMStore} and {@code IDMMDSALStore}.
42 public class TokenAuthRealm extends AuthorizingRealm {
43 private static final Logger LOG = LoggerFactory.getLogger(TokenAuthRealm.class);
44 private static final ThreadLocal<TokenAuthenticators> AUTHENICATORS_TL = new ThreadLocal<>();
45 private static final ThreadLocal<AuthenticationService> AUTH_SERVICE_TL = new ThreadLocal<>();
46 private static final ThreadLocal<TokenStore> TOKEN_STORE_TL = new ThreadLocal<>();
48 private final TokenAuthenticators authenticators;
49 private final AuthenticationService authService;
50 private final TokenStore tokenStore;
52 public TokenAuthRealm() {
53 this(verifyLoad(AUTH_SERVICE_TL), verifyLoad(AUTHENICATORS_TL), TOKEN_STORE_TL.get());
56 public TokenAuthRealm(final AuthenticationService authService, final TokenAuthenticators authenticators) {
57 this(authService, authenticators, null);
60 public TokenAuthRealm(final AuthenticationService authService, final TokenAuthenticators authenticators,
61 final @Nullable TokenStore tokenStore) {
62 this.authService = requireNonNull(authService);
63 this.authenticators = requireNonNull(authenticators);
64 this.tokenStore = tokenStore;
65 super.setName("TokenAuthRealm");
68 public static Registration prepareForLoad(final AuthenticationService authService,
69 final TokenAuthenticators authenticators, final @Nullable TokenStore tokenStore) {
70 AUTH_SERVICE_TL.set(requireNonNull(authService));
71 AUTHENICATORS_TL.set(requireNonNull(authenticators));
72 TOKEN_STORE_TL.set(tokenStore);
74 AUTH_SERVICE_TL.remove();
75 AUTHENICATORS_TL.remove();
76 TOKEN_STORE_TL.remove();
80 private static <T> T verifyLoad(final ThreadLocal<T> threadLocal) {
81 return verifyNotNull(threadLocal.get(), "TokenAuthRealm loading not prepared");
88 * Roles are derived from {@code TokenAuth.authenticate()}. Shiro roles are identical to existing IDM roles.
91 protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principalCollection) {
92 final var primaryPrincipal = principalCollection.getPrimaryPrincipal();
93 if (primaryPrincipal instanceof ODLPrincipal) {
94 return new SimpleAuthorizationInfo(((ODLPrincipal) primaryPrincipal).getRoles());
97 LOG.error("Could not decode authorization request: {} is not a known principal type", primaryPrincipal);
98 return new SimpleAuthorizationInfo();
105 * Authenticates against any {@code TokenAuth} registered with the {@code ServiceLocator}.
108 protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken authenticationToken)
109 throws AuthenticationException {
110 if (authenticationToken == null) {
111 throw new AuthenticationException("{\"error\":\"Unable to decode credentials\"}");
114 final String username;
115 final String password;
119 final String possiblyQualifiedUser = TokenUtils.extractUsername(authenticationToken);
120 username = HeaderUtils.extractUsername(possiblyQualifiedUser);
121 domain = HeaderUtils.extractDomain(possiblyQualifiedUser);
122 password = TokenUtils.extractPassword(authenticationToken);
123 } catch (ClassCastException e) {
124 throw new AuthenticationException(
125 "{\"error\":\"Only basic authentication is supported by TokenAuthRealm\"}", e);
128 // if the password is empty, this is an OAuth2 request, not a Basic HTTP Auth request
129 if (!Strings.isNullOrEmpty(password)) {
130 Map<String, List<String>> headers = HeaderUtils.formHeaders(username, password, domain);
131 // iterate over <code>TokenAuth</code> implementations and
133 // authentication with each one
134 for (TokenAuth ta : authenticators.getTokenAuthCollection()) {
136 LOG.debug("Authentication attempt using {}", ta.getClass().getName());
137 final Authentication auth = ta.validate(headers);
139 LOG.debug("Authentication attempt successful");
140 authService.set(auth);
141 final ODLPrincipal odlPrincipal = ODLPrincipalImpl.createODLPrincipal(auth);
142 return new SimpleAuthenticationInfo(odlPrincipal, password.toCharArray(), getName());
144 } catch (AuthenticationException ae) {
145 LOG.debug("Authentication attempt unsuccessful", ae);
146 // Purposefully generic message
147 throw new AuthenticationException("{\"error\":\"Could not authenticate\"}", ae);
152 // extract the authentication token and attempt validation of the token
153 final String token = TokenUtils.extractUsername(authenticationToken);
155 final Authentication auth = validate(token);
156 final ODLPrincipal odlPrincipal = ODLPrincipalImpl.createODLPrincipal(auth);
157 return new SimpleAuthenticationInfo(odlPrincipal, "", getName());
158 } catch (AuthenticationException e) {
159 LOG.debug("Unknown OAuth2 Token Access Request", e);
162 LOG.debug("Authentication failed: exhausted TokenAuth resources");
166 private Authentication validate(final String token) {
167 if (tokenStore == null) {
168 throw new AuthenticationException("Token store not available, could not validate the token " + token);
171 final Authentication auth = tokenStore.get(token);
173 throw new AuthenticationException("Could not validate the token " + token);
175 authService.set(auth);