2 * Copyright (c) 2016 Orange and others. All rights reserved.
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
8 package org.opendaylight.aaa.shiro.realm;
10 import com.google.common.collect.ImmutableSet;
11 import com.google.gson.JsonParser;
12 import java.net.MalformedURLException;
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;
30 * MoonRealm is a Shiro Realm that authenticates users from OPNFV/moon platform.
32 * @author Alioune BA alioune.ba@orange.com
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";
38 private URL moonServerURL;
41 protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principalCollection) {
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);
53 if (!(authenticationToken instanceof UsernamePasswordToken)) {
54 throw new AuthenticationException("Token is not UsernamePasswordToken: " + authenticationToken);
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());
64 public MoonPrincipal moonAuthenticate(final String username, final String password, final String domain) {
65 final String server = moonServerURL != null ? moonServerURL.getHost() : null;
67 LOG.debug("moon server was not specified appropriately, cannot authenticate");
71 final int portFromShiro = moonServerURL != null ? moonServerURL.getPort() : -1;
72 if (portFromShiro <= 0) {
73 LOG.debug("moon server was not specified appropriately, cannot authetnicate");
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);
81 final String output = ClientBuilder.newClient()
83 .request(MediaType.APPLICATION_JSON)
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),
92 final var element = JsonParser.parseString(output);
93 if (!element.isJsonObject()) {
94 throw new IllegalStateException("Authentication error: returned output is not a JSON object");
97 final var object = element.getAsJsonObject();
98 final var error = object.get("error").getAsJsonObject();
100 throw new IllegalStateException("Authentication Error : " + error.get("title").getAsString());
103 final var token = object.get("token");
108 final var userRoles = ImmutableSet.<String>builder();
109 final var roles = object.get("roles");
111 for (var role : roles.getAsJsonArray()) {
113 userRoles.add(role.getAsString());
114 } catch (ClassCastException e) {
115 LOG.debug("Unable to cast role as String, skipping {}", role, e);
119 return new MoonPrincipal(username, domain, username + "@" + domain, userRoles.build(), token.getAsString());
123 * Injected from {@code shiro.ini}.
125 * @param moonServerURL specified in {@code shiro.ini}
127 public void setMoonServerURL(final String moonServerURL) {
129 this.moonServerURL = new URL(moonServerURL);
130 } catch (MalformedURLException e) {
131 throw new IllegalArgumentException("Cannot parse moon server URL \"" + moonServerURL + "\"", e);