Remove ServiceLocator
[aaa.git] / aaa-shiro / impl / src / main / java / org / opendaylight / aaa / shiro / filters / MoonOAuthFilter.java
1 /*
2  * Copyright (c) 2016 Orange 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.filters;
9
10 import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST;
11 import static javax.servlet.http.HttpServletResponse.SC_CREATED;
12 import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
13 import static javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED;
14
15 import java.io.IOException;
16 import java.io.PrintWriter;
17 import javax.servlet.ServletRequest;
18 import javax.servlet.ServletResponse;
19 import javax.servlet.http.HttpServletRequest;
20 import javax.servlet.http.HttpServletResponse;
21 import org.apache.oltu.oauth2.as.response.OAuthASResponse;
22 import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
23 import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
24 import org.apache.oltu.oauth2.common.message.OAuthResponse;
25 import org.apache.oltu.oauth2.common.message.types.TokenType;
26 import org.apache.shiro.SecurityUtils;
27 import org.apache.shiro.authc.AuthenticationException;
28 import org.apache.shiro.authc.AuthenticationToken;
29 import org.apache.shiro.authc.UsernamePasswordToken;
30 import org.apache.shiro.subject.Subject;
31 import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
32 import org.opendaylight.aaa.api.Authentication;
33 import org.opendaylight.aaa.api.Claim;
34 import org.opendaylight.aaa.api.TokenStore;
35 import org.opendaylight.aaa.shiro.moon.MoonPrincipal;
36 import org.opendaylight.aaa.shiro.oauth2.OAuthRequest;
37 import org.opendaylight.aaa.shiro.tokenauthrealm.auth.AuthenticationBuilder;
38 import org.opendaylight.aaa.shiro.tokenauthrealm.auth.ClaimBuilder;
39 import org.opendaylight.aaa.shiro.web.env.ThreadLocals;
40 import org.slf4j.Logger;
41 import org.slf4j.LoggerFactory;
42
43 /**
44  * MoonOAuthFilter filters oauth1 requests form token based authentication
45  *
46  * @author Alioune BA alioune.ba@orange.com
47  */
48 public class MoonOAuthFilter extends AuthenticatingFilter {
49
50     private static final Logger LOG = LoggerFactory.getLogger(MoonOAuthFilter.class);
51
52     private static final String DOMAIN_SCOPE_REQUIRED = "Domain scope required";
53     private static final String NOT_IMPLEMENTED = "not_implemented";
54     private static final String UNAUTHORIZED = "unauthorized";
55     private static final String UNAUTHORIZED_CREDENTIALS = "Unauthorized: Login/Password incorrect";
56
57     static final String TOKEN_GRANT_ENDPOINT = "/token";
58     static final String TOKEN_REVOKE_ENDPOINT = "/revoke";
59     static final String TOKEN_VALIDATE_ENDPOINT = "/validate";
60
61     private final TokenStore tokenStore;
62
63     public MoonOAuthFilter() {
64         tokenStore = ThreadLocals.TOKEN_STORE_TL.get();
65     }
66
67     @Override
68     protected UsernamePasswordToken createToken(final ServletRequest request, final ServletResponse response) throws Exception {
69         final HttpServletRequest httpRequest;
70         final OAuthRequest oauthRequest;
71         try {
72             httpRequest = (HttpServletRequest) request;
73             oauthRequest = new OAuthRequest(httpRequest);
74         } catch (final ClassCastException e) {
75             LOG.debug("createToken() failed since the request could not be cast appropriately", e);
76             throw e;
77         }
78         return new UsernamePasswordToken(oauthRequest.getUsername(), oauthRequest.getPassword());
79     }
80
81     @Override
82     protected boolean onAccessDenied(final ServletRequest request, final ServletResponse response) throws Exception {
83         final Subject currentUser = SecurityUtils.getSubject();
84         return executeLogin(request, response);
85     }
86
87     @Override
88     protected boolean onLoginSuccess(final AuthenticationToken token, final Subject subject,
89             final ServletRequest request, final ServletResponse response) throws Exception {
90
91         final HttpServletResponse httpResponse;
92         try {
93             httpResponse = (HttpServletResponse) response;
94         } catch (final ClassCastException e) {
95             LOG.debug("onLoginSuccess() failed since the response could not be cast appropriately", e);
96             throw e;
97         }
98
99         final MoonPrincipal principal;
100         try {
101             principal = (MoonPrincipal) subject.getPrincipals().getPrimaryPrincipal();
102         } catch (final ClassCastException e) {
103             LOG.debug("onLoginSuccess() failed since the subject could not be cast appropriately", e);
104             throw e;
105         }
106
107         final Claim claim = principal.principalToClaim();
108         oauthAccessTokenResponse(httpResponse, claim, "", principal.getToken());
109         return true;
110     }
111
112     @Override
113     protected boolean onLoginFailure(final AuthenticationToken token, final AuthenticationException e,
114             final ServletRequest request, final ServletResponse response) {
115
116         final HttpServletResponse resp;
117         try {
118             resp = (HttpServletResponse) response;
119             error(resp, SC_BAD_REQUEST, UNAUTHORIZED_CREDENTIALS);
120         } catch (final ClassCastException cce) {
121             LOG.warn("onLoginFailure() failed since the response could not be cast appropriately", cce);
122         }
123
124         return false;
125     }
126
127     @Override
128     protected boolean executeLogin(final ServletRequest request, final ServletResponse response) throws Exception {
129
130         final HttpServletRequest req;
131         try {
132             req = (HttpServletRequest) request;
133         } catch (final ClassCastException e) {
134             LOG.debug("executeLogin() failed since the request could not be cast appropriately", e);
135             throw e;
136         }
137
138         final HttpServletResponse resp;
139         try {
140             resp = (HttpServletResponse) response;
141         } catch (final ClassCastException e) {
142             LOG.debug("executeLogin() failed since the request could not be cast apprioately", e);
143             throw e;
144         }
145
146         try {
147             if (req.getServletPath().equals(TOKEN_GRANT_ENDPOINT)) {
148                 final UsernamePasswordToken token = createToken(request, response);
149                 if (token == null) {
150                     final String msg = "A valid non-null AuthenticationToken " +
151                             "must be created in order to execute a login attempt.";
152                     throw new IllegalStateException(msg);
153                 }
154                 try {
155                     final Subject subject = getSubject(request, response);
156                     subject.login(token);
157                     return onLoginSuccess(token, subject, request, response);
158                 } catch (final AuthenticationException e) {
159                     return onLoginFailure(token, e, request, response);
160                 }
161             } else if (req.getServletPath().equals(TOKEN_REVOKE_ENDPOINT)) {
162                 //TODO: deleteAccessToken(req, resp);
163             } else if (req.getServletPath().equals(TOKEN_VALIDATE_ENDPOINT)) {
164                 //TODO: validateToken(req, resp);
165             }
166         } catch (final AuthenticationException e) {
167             error(resp, SC_UNAUTHORIZED, e.getMessage());
168         } catch (final OAuthProblemException oe) {
169             error(resp, oe);
170         } catch (final Exception e) {
171             error(resp, e);
172         }
173         return false;
174     }
175
176     private void oauthAccessTokenResponse(final HttpServletResponse resp, final Claim claim, final String clientId, final String token)
177             throws OAuthSystemException, IOException {
178
179         if (claim == null) {
180             throw new AuthenticationException(UNAUTHORIZED);
181         }
182
183         // Cache this token...
184         final Authentication auth = new AuthenticationBuilder(new ClaimBuilder(claim).setClientId(
185                 clientId).build()).setExpiration(tokenExpiration()).build();
186         tokenStore.put(token, auth);
187
188         final OAuthResponse r = OAuthASResponse.tokenResponse(SC_CREATED).setAccessToken(token)
189                                          .setTokenType(TokenType.BEARER.toString())
190                                          .setExpiresIn(Long.toString(auth.expiration()))
191                                          .buildJSONMessage();
192         write(resp, r);
193     }
194
195     private void write(final HttpServletResponse resp, final OAuthResponse r) throws IOException {
196         resp.setStatus(r.getResponseStatus());
197         PrintWriter pw = resp.getWriter();
198         pw.print(r.getBody());
199         pw.flush();
200         pw.close();
201     }
202
203     private long tokenExpiration() {
204         return tokenStore.tokenExpiration();
205     }
206
207     /**
208      * Utility method used to emit an error OAuthResponse with the given HTTP code
209      */
210     private void error(final HttpServletResponse resp, final int httpCode, final String error) {
211         try {
212             final OAuthResponse r = OAuthResponse.errorResponse(httpCode).setError(error)
213                                            .buildJSONMessage();
214             write(resp, r);
215         } catch (final IOException | OAuthSystemException ex) {
216             LOG.error("Failed to write the error ", ex);
217         }
218     }
219
220     private void error(final HttpServletResponse resp, final OAuthProblemException e) {
221         try {
222             final OAuthResponse r = OAuthResponse.errorResponse(SC_BAD_REQUEST).error(e)
223                                            .buildJSONMessage();
224             write(resp, r);
225         } catch (final IOException | OAuthSystemException ex) {
226             LOG.error("Failed to write the error ", ex);
227         }
228     }
229
230     private void error(final HttpServletResponse resp, final Exception e) {
231         try {
232             final OAuthResponse r = OAuthResponse.errorResponse(SC_INTERNAL_SERVER_ERROR)
233                                            .setError(e.getClass().getName())
234                                            .setErrorDescription(e.getMessage()).buildJSONMessage();
235             write(resp, r);
236         } catch (final IOException | OAuthSystemException ex) {
237             LOG.error("Failed to write the error ", ex);
238         }
239     }
240
241 }