Inject WebEnvironment into ODLAuthenticator
[aaa.git] / aaa-shiro / impl / src / main / java / org / opendaylight / aaa / authenticator / ODLAuthenticator.java
1 /*
2  * Copyright © 2018 Inocybe Technologies 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.authenticator;
9
10 import static java.util.Objects.requireNonNull;
11
12 import java.nio.charset.StandardCharsets;
13 import java.util.Base64;
14 import javax.servlet.http.HttpServletRequest;
15 import org.apache.shiro.ShiroException;
16 import org.apache.shiro.authc.AuthenticationException;
17 import org.apache.shiro.authc.UsernamePasswordToken;
18 import org.apache.shiro.session.Session;
19 import org.apache.shiro.session.UnknownSessionException;
20 import org.apache.shiro.subject.Subject;
21 import org.apache.shiro.web.env.WebEnvironment;
22 import org.jolokia.osgi.security.Authenticator;
23 import org.slf4j.Logger;
24 import org.slf4j.LoggerFactory;
25
26 /**
27  * AAA hook for <code>odl-jolokia</code> configured w/ <code>org.jolokia.authMode=service-all</code>.
28  */
29 public class ODLAuthenticator implements Authenticator {
30     private static final Logger LOG = LoggerFactory.getLogger(ODLAuthenticator.class);
31
32     private final WebEnvironment env;
33
34     public ODLAuthenticator(final WebEnvironment env) {
35         this.env = requireNonNull(env);
36     }
37
38     @Override
39     public boolean authenticate(final HttpServletRequest httpServletRequest) {
40         final String authorization = httpServletRequest.getHeader("Authorization");
41
42         LOG.trace("Incoming Jolokia authentication attempt: {}", authorization);
43
44         if (authorization == null || !authorization.startsWith("Basic")) {
45             return false;
46         }
47
48         try {
49             final String base64Creds = authorization.substring("Basic".length()).trim();
50             final String credentials = new String(Base64.getDecoder().decode(base64Creds), StandardCharsets.UTF_8);
51             final String[] values = credentials.split(":", 2);
52             final UsernamePasswordToken upt = new UsernamePasswordToken();
53             upt.setUsername(values[0]);
54             upt.setPassword(values[1].toCharArray());
55
56             final Subject subject = new Subject.Builder(env.getSecurityManager()).buildSubject();
57             try {
58                 return login(subject, upt);
59             } catch (UnknownSessionException e) {
60                 LOG.debug("Couldn't log in {} - logging out and retrying...", upt, e);
61                 logout(subject);
62                 return login(subject, upt);
63             }
64         } catch (ArrayIndexOutOfBoundsException e) {
65             // FIXME: who throws this above and why do we need to catch it? Should this be error or warn?
66             LOG.trace("Formatting issue with basic auth credentials: {}", authorization, e);
67         }
68
69         return false;
70     }
71
72     private static void logout(final Subject subject) {
73         try {
74             subject.logout();
75             Session session = subject.getSession(false);
76             if (session != null) {
77                 session.stop();
78             }
79         } catch (ShiroException e) {
80             LOG.debug("Couldn't log out {}", subject, e);
81         }
82     }
83
84     private static boolean login(final Subject subject, final UsernamePasswordToken upt) {
85         try {
86             subject.login(upt);
87         } catch (AuthenticationException e) {
88             LOG.trace("Couldn't authenticate the subject: {}", subject, e);
89             return false;
90         }
91         return true;
92     }
93 }