4a6bcd57f9da7085eae6395014bca8302fe57913
[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 java.util.Objects.requireNonNull;
11
12 import com.google.common.base.Strings;
13 import java.util.List;
14 import java.util.Map;
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;
35
36 /**
37  * TokenAuthRealm is an adapter between the AAA shiro subsystem and the existing {@code TokenAuth} mechanisms. Thus, one
38  * can enable use of {@code IDMStore} and {@code IDMMDSALStore}.
39  */
40 public class TokenAuthRealm extends AuthorizingRealm {
41     private static final Logger LOG = LoggerFactory.getLogger(TokenAuthRealm.class);
42
43     private final AuthenticationService authenticationService;
44     private final TokenStore tokenStore;
45     private final TokenAuthenticators tokenAuthenticators;
46
47     public TokenAuthRealm() {
48         authenticationService = requireNonNull(ThreadLocals.AUTH_SETVICE_TL.get());
49         tokenStore = ThreadLocals.TOKEN_STORE_TL.get();
50         tokenAuthenticators = requireNonNull(ThreadLocals.TOKEN_AUTHENICATORS_TL.get());
51         super.setName("TokenAuthRealm");
52     }
53
54     /**
55      * {@inheritDoc}
56      *
57      * <p>
58      * Roles are derived from {@code TokenAuth.authenticate()}. Shiro roles are identical to existing IDM roles.
59      */
60     @Override
61     protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principalCollection) {
62         final var primaryPrincipal = principalCollection.getPrimaryPrincipal();
63         if (primaryPrincipal instanceof ODLPrincipal) {
64             return new SimpleAuthorizationInfo(((ODLPrincipal) primaryPrincipal).getRoles());
65         }
66
67         LOG.error("Could not decode authorization request: {} is not a known principal type", primaryPrincipal);
68         return new SimpleAuthorizationInfo();
69     }
70
71     /**
72      * {@inheritDoc}
73      *
74      * <p>
75      * Authenticates against any {@code TokenAuth} registered with the {@code ServiceLocator}.
76      */
77     @Override
78     protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken authenticationToken)
79             throws AuthenticationException {
80         if (authenticationToken == null) {
81             throw new AuthenticationException("{\"error\":\"Unable to decode credentials\"}");
82         }
83
84         final String username;
85         final String password;
86         final String domain;
87
88         try {
89             final String possiblyQualifiedUser = TokenUtils.extractUsername(authenticationToken);
90             username = HeaderUtils.extractUsername(possiblyQualifiedUser);
91             domain = HeaderUtils.extractDomain(possiblyQualifiedUser);
92             password = TokenUtils.extractPassword(authenticationToken);
93         } catch (ClassCastException e) {
94             throw new AuthenticationException(
95                 "{\"error\":\"Only basic authentication is supported by TokenAuthRealm\"}", e);
96         }
97
98         // if the password is empty, this is an OAuth2 request, not a Basic HTTP Auth request
99         if (!Strings.isNullOrEmpty(password)) {
100             Map<String, List<String>> headers = HeaderUtils.formHeaders(username, password, domain);
101             // iterate over <code>TokenAuth</code> implementations and
102             // attempt to
103             // authentication with each one
104             for (TokenAuth ta : tokenAuthenticators.getTokenAuthCollection()) {
105                 try {
106                     LOG.debug("Authentication attempt using {}", ta.getClass().getName());
107                     final Authentication auth = ta.validate(headers);
108                     if (auth != null) {
109                         LOG.debug("Authentication attempt successful");
110                         authenticationService.set(auth);
111                         final ODLPrincipal odlPrincipal = ODLPrincipalImpl.createODLPrincipal(auth);
112                         return new SimpleAuthenticationInfo(odlPrincipal, password.toCharArray(), getName());
113                     }
114                 } catch (AuthenticationException ae) {
115                     LOG.debug("Authentication attempt unsuccessful", ae);
116                     // Purposefully generic message
117                     throw new AuthenticationException("{\"error\":\"Could not authenticate\"}", ae);
118                 }
119             }
120         }
121
122         // extract the authentication token and attempt validation of the token
123         final String token = TokenUtils.extractUsername(authenticationToken);
124         try {
125             final Authentication auth = validate(token);
126             final ODLPrincipal odlPrincipal = ODLPrincipalImpl.createODLPrincipal(auth);
127             return new SimpleAuthenticationInfo(odlPrincipal, "", getName());
128         } catch (AuthenticationException e) {
129             LOG.debug("Unknown OAuth2 Token Access Request", e);
130         }
131
132         LOG.debug("Authentication failed: exhausted TokenAuth resources");
133         return null;
134     }
135
136     private Authentication validate(final String token) {
137         if (tokenStore == null) {
138             throw new AuthenticationException("Token store not available, could not validate the token " + token);
139         }
140
141         final Authentication auth = tokenStore.get(token);
142         if (auth == null) {
143             throw new AuthenticationException("Could not validate the token " + token);
144         }
145         authenticationService.set(auth);
146         return auth;
147     }
148 }