Split out datastore implementation from aaa-shiro
[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
9 package org.opendaylight.aaa.shiro.realm;
10
11 import com.google.common.base.Strings;
12 import java.util.List;
13 import java.util.Map;
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;
35
36 /**
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>.
40  */
41 public class TokenAuthRealm extends AuthorizingRealm {
42
43     /**
44      * The unique identifying name for <code>TokenAuthRealm</code>.
45      */
46     private static final String TOKEN_AUTH_REALM_DEFAULT_NAME = "TokenAuthRealm";
47
48     /**
49      * The message that is displayed if no <code>TokenAuth</code> interface is available yet.
50      */
51     private static final String AUTHENTICATION_SERVICE_UNAVAILABLE_MESSAGE =
52             "{\"error\":\"Authentication service unavailable\"}";
53
54     /**
55      * The message that is displayed if credentials are missing or malformed.
56      */
57     private static final String FATAL_ERROR_DECODING_CREDENTIALS = "{\"error\":\"Unable to decode credentials\"}";
58
59     /**
60      * The message that is displayed if non-Basic Auth is attempted.
61      */
62     private static final String FATAL_ERROR_BASIC_AUTH_ONLY
63             = "{\"error\":\"Only basic authentication is supported by TokenAuthRealm\"}";
64
65     /**
66      * The purposefully generic message displayed if <code>TokenAuth</code> is
67      * unable to validate the given credentials.
68      */
69     private static final String UNABLE_TO_AUTHENTICATE = "{\"error\":\"Could not authenticate\"}";
70
71     private static final Logger LOG = LoggerFactory.getLogger(TokenAuthRealm.class);
72
73     private final AuthenticationService authenticationService;
74     private final TokenStore tokenStore;
75     private final TokenAuthenticators tokenAuthenticators;
76
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());
82     }
83
84     /*
85      * (non-Javadoc)
86      *
87      * Roles are derived from <code>TokenAuth.authenticate()</code>. Shiro roles
88      * are identical to existing IDM roles.
89      *
90      * @see
91      * org.apache.shiro.realm.AuthorizingRealm#doGetAuthorizationInfo(org.apache
92      * .shiro.subject.PrincipalCollection)
93      */
94     @Override
95     protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principalCollection) {
96         final Object primaryPrincipal = principalCollection.getPrimaryPrincipal();
97         final ODLPrincipal odlPrincipal;
98         try {
99             odlPrincipal = (ODLPrincipal) primaryPrincipal;
100             return new SimpleAuthorizationInfo(odlPrincipal.getRoles());
101         } catch (ClassCastException e) {
102             LOG.error("Couldn't decode authorization request", e);
103         }
104         return new SimpleAuthorizationInfo();
105     }
106
107     /*
108      * (non-Javadoc)
109      *
110      * Authenticates against any <code>TokenAuth</code> registered with the
111      * <code>ServiceLocator</code>
112      *
113      * @see
114      * org.apache.shiro.realm.AuthenticatingRealm#doGetAuthenticationInfo(org
115      * .apache.shiro.authc.AuthenticationToken)
116      */
117     @Override
118     protected AuthenticationInfo doGetAuthenticationInfo(
119             final AuthenticationToken authenticationToken) throws AuthenticationException {
120
121         final String username;
122         final String password;
123         final String domain;
124
125         try {
126             final String possiblyQualifiedUser = TokenUtils.extractUsername(authenticationToken);
127             username = HeaderUtils.extractUsername(possiblyQualifiedUser);
128             domain = HeaderUtils.extractDomain(possiblyQualifiedUser);
129             password = TokenUtils.extractPassword(authenticationToken);
130
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);
135         }
136
137         // if the password is empty, this is an OAuth2 request, not a Basic HTTP
138         // Auth request
139         if (!Strings.isNullOrEmpty(password)) {
140             Map<String, List<String>> headers = HeaderUtils.formHeaders(username, password, domain);
141             // iterate over <code>TokenAuth</code> implementations and
142             // attempt to
143             // authentication with each one
144             for (TokenAuth ta : tokenAuthenticators.getTokenAuthCollection()) {
145                 try {
146                     LOG.debug("Authentication attempt using {}", ta.getClass().getName());
147                     final Authentication auth = ta.validate(headers);
148                     if (auth != null) {
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());
153                     }
154                 } catch (AuthenticationException ae) {
155                     LOG.debug("Authentication attempt unsuccessful");
156                     throw new AuthenticationException(UNABLE_TO_AUTHENTICATE, ae);
157                 }
158             }
159         }
160
161         // extract the authentication token and attempt validation of the token
162         final String token = TokenUtils.extractUsername(authenticationToken);
163         try {
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);
169         }
170
171         LOG.debug("Authentication failed: exhausted TokenAuth resources");
172         return null;
173     }
174
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);
178         }
179
180         final Authentication auth = tokenStore.get(token);
181         if (auth == null) {
182             throw new AuthenticationException("Could not validate the token " + token);
183         }
184         authenticationService.set(auth);
185         return auth;
186     }
187 }