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