Use pattern match on instanceof
[aaa.git] / aaa-shiro / impl / src / main / java / org / opendaylight / aaa / shiro / realm / TokenAuthRealm.java
1 /*
2  * Copyright (c) 2015 - 2017 Brocade Communications Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.aaa.shiro.realm;
9
10 import static com.google.common.base.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.base.Strings;
14 import java.util.List;
15 import java.util.Map;
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;
37
38 /**
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}.
41  */
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<>();
47
48     private final TokenAuthenticators authenticators;
49     private final AuthenticationService authService;
50     private final TokenStore tokenStore;
51
52     public TokenAuthRealm() {
53         this(verifyLoad(AUTH_SERVICE_TL), verifyLoad(AUTHENICATORS_TL), TOKEN_STORE_TL.get());
54     }
55
56     public TokenAuthRealm(final AuthenticationService authService, final TokenAuthenticators authenticators) {
57         this(authService, authenticators, null);
58     }
59
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");
66     }
67
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);
73         return () -> {
74             AUTH_SERVICE_TL.remove();
75             AUTHENICATORS_TL.remove();
76             TOKEN_STORE_TL.remove();
77         };
78     }
79
80     private static <T> T verifyLoad(final ThreadLocal<T> threadLocal) {
81         return verifyNotNull(threadLocal.get(), "TokenAuthRealm loading not prepared");
82     }
83
84     /**
85      * {@inheritDoc}
86      *
87      * <p>
88      * Roles are derived from {@code TokenAuth.authenticate()}. Shiro roles are identical to existing IDM roles.
89      */
90     @Override
91     protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principalCollection) {
92         final var primaryPrincipal = principalCollection.getPrimaryPrincipal();
93         if (primaryPrincipal instanceof ODLPrincipal odlPrincipal) {
94             return new SimpleAuthorizationInfo(odlPrincipal.getRoles());
95         }
96
97         LOG.error("Could not decode authorization request: {} is not a known principal type", primaryPrincipal);
98         return new SimpleAuthorizationInfo();
99     }
100
101     /**
102      * {@inheritDoc}
103      *
104      * <p>
105      * Authenticates against any {@code TokenAuth} registered with the {@code ServiceLocator}.
106      */
107     @Override
108     protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken authenticationToken)
109             throws AuthenticationException {
110         if (authenticationToken == null) {
111             throw new AuthenticationException("{\"error\":\"Unable to decode credentials\"}");
112         }
113
114         final String username;
115         final String password;
116         final String domain;
117
118         try {
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);
126         }
127
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
132             // attempt to
133             // authentication with each one
134             for (TokenAuth ta : authenticators.getTokenAuthCollection()) {
135                 try {
136                     LOG.debug("Authentication attempt using {}", ta.getClass().getName());
137                     final Authentication auth = ta.validate(headers);
138                     if (auth != null) {
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());
143                     }
144                 } catch (AuthenticationException ae) {
145                     LOG.debug("Authentication attempt unsuccessful", ae);
146                     // Purposefully generic message
147                     throw new AuthenticationException("{\"error\":\"Could not authenticate\"}", ae);
148                 }
149             }
150         }
151
152         // extract the authentication token and attempt validation of the token
153         final String token = TokenUtils.extractUsername(authenticationToken);
154         try {
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);
160         }
161
162         LOG.debug("Authentication failed: exhausted TokenAuth resources");
163         return null;
164     }
165
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);
169         }
170
171         final Authentication auth = tokenStore.get(token);
172         if (auth == null) {
173             throw new AuthenticationException("Could not validate the token " + token);
174         }
175         authService.set(auth);
176         return auth;
177     }
178 }