fe4ac2c101ff0b8589163e130be65b4944b095d8
[aaa.git] / aaa-shiro / impl / src / main / java / org / opendaylight / aaa / shiro / realm / MoonRealm.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.realm;
9
10 import static com.google.common.base.Preconditions.checkArgument;
11
12 import com.google.common.collect.ImmutableSet;
13 import com.google.gson.JsonParser;
14 import java.net.MalformedURLException;
15 import java.net.URL;
16 import javax.ws.rs.client.ClientBuilder;
17 import javax.ws.rs.client.Entity;
18 import javax.ws.rs.client.WebTarget;
19 import javax.ws.rs.core.MediaType;
20 import org.apache.shiro.authc.AuthenticationException;
21 import org.apache.shiro.authc.AuthenticationInfo;
22 import org.apache.shiro.authc.AuthenticationToken;
23 import org.apache.shiro.authc.SimpleAuthenticationInfo;
24 import org.apache.shiro.authc.UsernamePasswordToken;
25 import org.apache.shiro.authz.AuthorizationInfo;
26 import org.apache.shiro.realm.AuthorizingRealm;
27 import org.apache.shiro.subject.PrincipalCollection;
28 import org.opendaylight.aaa.shiro.moon.MoonPrincipal;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 /**
33  * MoonRealm is a Shiro Realm that authenticates users from OPNFV/moon platform.
34  *
35  * @author Alioune BA alioune.ba@orange.com
36  */
37 public class MoonRealm extends AuthorizingRealm {
38     private static final Logger LOG = LoggerFactory.getLogger(MoonRealm.class);
39     private static final String MOON_DEFAULT_DOMAIN = "sdn";
40
41     private volatile WebTarget moonServer;
42
43     @Override
44     protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principalCollection) {
45         return null;
46     }
47
48     @Override
49     protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken authenticationToken)
50             throws AuthenticationException {
51         final var principal = authenticationToken.getPrincipal();
52         if (!(principal instanceof String)) {
53             throw new AuthenticationException("Non-string principal " + principal);
54         }
55
56         if (!(authenticationToken instanceof UsernamePasswordToken)) {
57             throw new AuthenticationException("Token is not UsernamePasswordToken: " + authenticationToken);
58         }
59
60         final var password = new String(((UsernamePasswordToken) authenticationToken).getPassword());
61         // FIXME: make the domain name configurable
62         final var moonPrincipal = moonAuthenticate((String) principal, password, MOON_DEFAULT_DOMAIN);
63         return moonPrincipal == null ? null
64             : new SimpleAuthenticationInfo(moonPrincipal, password.toCharArray(), getName());
65     }
66
67     public MoonPrincipal moonAuthenticate(final String username, final String password, final String domain) {
68         final var moon = moonServer;
69         if (moon == null) {
70             LOG.debug("moon server not specified, cannot authenticate");
71             return null;
72         }
73
74         final var element = JsonParser.parseString(moon.request(MediaType.APPLICATION_JSON).post(
75             // FIXME: String literal when we have JDK17
76             Entity.entity("{\"username\": \"" + username + "\",\n"
77                 + "  \"password\": \"" + password + "\",\n"
78                 + "  \"project\": \"" + domain + "\"\n}",
79                 MediaType.APPLICATION_JSON),
80             String.class));
81         if (!element.isJsonObject()) {
82             throw new IllegalStateException("Authentication error: returned output is not a JSON object");
83         }
84
85         final var object = element.getAsJsonObject();
86         final var error = object.get("error").getAsJsonObject();
87         if (error != null) {
88             throw new IllegalStateException("Authentication Error : " + error.get("title").getAsString());
89         }
90
91         final var token = object.get("token");
92         if (token == null) {
93             return null;
94         }
95
96         final var userRoles = ImmutableSet.<String>builder();
97         final var roles = object.get("roles");
98         if (roles != null) {
99             for (var role : roles.getAsJsonArray()) {
100                 try {
101                     userRoles.add(role.getAsString());
102                 } catch (ClassCastException e) {
103                     LOG.debug("Unable to cast role as String, skipping {}", role, e);
104                 }
105             }
106         }
107         return new MoonPrincipal(username, domain, username + "@" + domain, userRoles.build(), token.getAsString());
108     }
109
110     /**
111      * Injected from {@code shiro.ini}.
112      *
113      * @param moonServerURL specified in {@code shiro.ini}
114      * @throws NullPointerException If {@code moonServerURL} is {@code null}
115      * @throws IllegalArgumentException If the given string violates RFC&nbsp;2396 or it does not specify a host
116      *                                  and a port.
117      */
118     public void setMoonServerURL(final String moonServerURL) {
119         final URL url;
120         try {
121             url = new URL(moonServerURL);
122         } catch (MalformedURLException e) {
123             throw new IllegalArgumentException(e);
124         }
125
126         final var uriHost = url.getHost();
127         checkArgument(uriHost != null, "moon host not specified in %s", url);
128         final var uriPort = url.getPort();
129         checkArgument(uriPort >= 0, "moon port not specified in %s", url);
130
131         final var port = Integer.toString(uriPort);
132         // FIXME: allow HTTPS!
133         // FIXME: allow authentication: and that really means configuring a Client!
134         final var server = String.format("http://%s:%s/moon/auth/tokens", uriHost, port);
135         LOG.debug("Moon server is at: {}:{} and will be accessed through {}", uriHost, port, server);
136         moonServer = ClientBuilder.newClient().target(server);
137     }
138 }