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 static com.google.common.base.Preconditions.checkArgument;
11 import static com.google.common.base.Verify.verifyNotNull;
12 import static java.util.Objects.requireNonNull;
14 import com.google.common.collect.ImmutableSet;
15 import com.google.gson.JsonParser;
16 import java.net.MalformedURLException;
18 import javax.ws.rs.client.Entity;
19 import javax.ws.rs.client.WebTarget;
20 import javax.ws.rs.core.MediaType;
21 import org.apache.shiro.authc.AuthenticationException;
22 import org.apache.shiro.authc.AuthenticationInfo;
23 import org.apache.shiro.authc.AuthenticationToken;
24 import org.apache.shiro.authc.SimpleAuthenticationInfo;
25 import org.apache.shiro.authc.UsernamePasswordToken;
26 import org.apache.shiro.authz.AuthorizationInfo;
27 import org.apache.shiro.realm.AuthorizingRealm;
28 import org.apache.shiro.subject.PrincipalCollection;
29 import org.opendaylight.aaa.shiro.moon.MoonPrincipal;
30 import org.opendaylight.aaa.web.servlet.ServletSupport;
31 import org.opendaylight.yangtools.concepts.Registration;
32 import org.slf4j.Logger;
33 import org.slf4j.LoggerFactory;
36 * MoonRealm is a Shiro Realm that authenticates users from OPNFV/moon platform.
38 * @author Alioune BA alioune.ba@orange.com
40 public class MoonRealm extends AuthorizingRealm {
41 private static final Logger LOG = LoggerFactory.getLogger(MoonRealm.class);
42 private static final ThreadLocal<ServletSupport> SERVLET_SUPPORT_TL = new ThreadLocal<>();
43 private static final String MOON_DEFAULT_DOMAIN = "sdn";
45 private final ServletSupport servletSupport;
46 private volatile WebTarget moonServer;
49 this(verifyNotNull(SERVLET_SUPPORT_TL.get(), "MoonRealm loading not prepared"));
52 public MoonRealm(final ServletSupport servletSupport) {
53 this.servletSupport = requireNonNull(servletSupport);
56 public static Registration prepareForLoad(final ServletSupport jaxrsSupport) {
57 SERVLET_SUPPORT_TL.set(requireNonNull(jaxrsSupport));
58 return SERVLET_SUPPORT_TL::remove;
62 protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principalCollection) {
67 protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken authenticationToken)
68 throws AuthenticationException {
69 final var principal = authenticationToken.getPrincipal();
70 if (!(principal instanceof String)) {
71 throw new AuthenticationException("Non-string principal " + principal);
74 if (!(authenticationToken instanceof UsernamePasswordToken)) {
75 throw new AuthenticationException("Token is not UsernamePasswordToken: " + authenticationToken);
78 final var password = new String(((UsernamePasswordToken) authenticationToken).getPassword());
79 // FIXME: make the domain name configurable
80 final var moonPrincipal = moonAuthenticate((String) principal, password, MOON_DEFAULT_DOMAIN);
81 return moonPrincipal == null ? null
82 : new SimpleAuthenticationInfo(moonPrincipal, password.toCharArray(), getName());
85 public MoonPrincipal moonAuthenticate(final String username, final String password, final String domain) {
86 final var moon = moonServer;
88 LOG.debug("moon server not specified, cannot authenticate");
92 final var element = JsonParser.parseString(moon.request(MediaType.APPLICATION_JSON).post(
93 // FIXME: String literal when we have JDK17
94 Entity.entity("{\"username\": \"" + username + "\",\n"
95 + " \"password\": \"" + password + "\",\n"
96 + " \"project\": \"" + domain + "\"\n}",
97 MediaType.APPLICATION_JSON),
99 if (!element.isJsonObject()) {
100 throw new IllegalStateException("Authentication error: returned output is not a JSON object");
103 final var object = element.getAsJsonObject();
104 final var error = object.get("error").getAsJsonObject();
106 throw new IllegalStateException("Authentication Error : " + error.get("title").getAsString());
109 final var token = object.get("token");
114 final var userRoles = ImmutableSet.<String>builder();
115 final var roles = object.get("roles");
117 for (var role : roles.getAsJsonArray()) {
119 userRoles.add(role.getAsString());
120 } catch (ClassCastException e) {
121 LOG.debug("Unable to cast role as String, skipping {}", role, e);
125 return new MoonPrincipal(username, domain, username + "@" + domain, userRoles.build(), token.getAsString());
129 * Injected from {@code shiro.ini}.
131 * @param moonServerURL specified in {@code shiro.ini}
132 * @throws NullPointerException If {@code moonServerURL} is {@code null}
133 * @throws IllegalArgumentException If the given string violates RFC 2396 or it does not specify a host
136 public void setMoonServerURL(final String moonServerURL) {
139 url = new URL(moonServerURL);
140 } catch (MalformedURLException e) {
141 throw new IllegalArgumentException(e);
144 final var uriHost = url.getHost();
145 checkArgument(uriHost != null, "moon host not specified in %s", url);
146 final var uriPort = url.getPort();
147 checkArgument(uriPort >= 0, "moon port not specified in %s", url);
149 final var port = Integer.toString(uriPort);
150 // FIXME: allow HTTPS!
151 // FIXME: allow authentication: and that really means configuring a Client!
152 final var server = String.format("http://%s:%s/moon/auth/tokens", uriHost, port);
153 LOG.debug("Moon server is at: {}:{} and will be accessed through {}", uriHost, port, server);
154 moonServer = servletSupport.newClientBuilder().build().target(server);