2 * Copyright (c) 2017 Inocybe Technologies 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.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.collect.Iterables;
14 import com.google.common.util.concurrent.Futures;
15 import com.google.common.util.concurrent.ListenableFuture;
16 import java.util.Collection;
17 import java.util.HashSet;
18 import java.util.Optional;
20 import java.util.concurrent.ExecutionException;
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.authz.AuthorizationInfo;
26 import org.apache.shiro.authz.SimpleAuthorizationInfo;
27 import org.apache.shiro.realm.AuthorizingRealm;
28 import org.apache.shiro.subject.PrincipalCollection;
29 import org.apache.shiro.util.Destroyable;
30 import org.opendaylight.aaa.api.password.service.PasswordHashService;
31 import org.opendaylight.aaa.api.shiro.principal.ODLPrincipal;
32 import org.opendaylight.aaa.shiro.principal.ODLPrincipalImpl;
33 import org.opendaylight.aaa.shiro.realm.util.TokenUtils;
34 import org.opendaylight.aaa.shiro.realm.util.http.header.HeaderUtils;
35 import org.opendaylight.mdsal.binding.api.ClusteredDataTreeChangeListener;
36 import org.opendaylight.mdsal.binding.api.DataBroker;
37 import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
38 import org.opendaylight.mdsal.binding.api.DataTreeModification;
39 import org.opendaylight.mdsal.binding.api.ReadTransaction;
40 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
41 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.Authentication;
42 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.Grant;
43 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.authentication.Grants;
44 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.authentication.Roles;
45 import org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.authentication.Users;
46 import org.opendaylight.yangtools.concepts.ListenerRegistration;
47 import org.opendaylight.yangtools.concepts.Registration;
48 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
49 import org.slf4j.Logger;
50 import org.slf4j.LoggerFactory;
53 * A Realm based on <code>aaa.yang</code> model.
55 public class MdsalRealm extends AuthorizingRealm implements Destroyable {
56 private static final Logger LOG = LoggerFactory.getLogger(MdsalRealm.class);
59 * InstanceIdentifier for the authentication container.
61 private static final DataTreeIdentifier<Authentication> AUTH_TREE_ID = DataTreeIdentifier.create(
62 LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(Authentication.class));
64 private static final ThreadLocal<PasswordHashService> PASSWORD_HASH_SERVICE_TL = new ThreadLocal<>();
65 private static final ThreadLocal<DataBroker> DATABROKER_TL = new ThreadLocal<>();
67 private final PasswordHashService passwordHashService;
68 private final ListenerRegistration<?> reg;
70 private volatile ListenableFuture<Optional<Authentication>> authentication;
73 this(verifyLoad(PASSWORD_HASH_SERVICE_TL), verifyLoad(DATABROKER_TL));
76 private static <T> T verifyLoad(final ThreadLocal<T> threadLocal) {
77 return verifyNotNull(threadLocal.get(), "MdsalRealm not prepared for loading");
80 public MdsalRealm(final PasswordHashService passwordHashService, final DataBroker dataBroker) {
81 this.passwordHashService = requireNonNull(passwordHashService);
83 try (ReadTransaction tx = dataBroker.newReadOnlyTransaction()) {
84 authentication = tx.read(AUTH_TREE_ID.getDatastoreType(), AUTH_TREE_ID.getRootIdentifier());
87 reg = dataBroker.registerDataTreeChangeListener(AUTH_TREE_ID,
88 (ClusteredDataTreeChangeListener<Authentication>) this::onAuthenticationChanged);
90 LOG.info("MdsalRealm created");
93 public static Registration prepareForLoad(final PasswordHashService passwordHashService,
94 final DataBroker dataBroker) {
95 PASSWORD_HASH_SERVICE_TL.set(requireNonNull(passwordHashService));
96 DATABROKER_TL.set(requireNonNull(dataBroker));
98 PASSWORD_HASH_SERVICE_TL.remove();
99 DATABROKER_TL.remove();
103 private void onAuthenticationChanged(final Collection<DataTreeModification<Authentication>> changes) {
104 final Authentication newVal = Iterables.getLast(changes).getRootNode().getDataAfter();
105 LOG.debug("Updating authentication information to {}", newVal);
106 authentication = Futures.immediateFuture(Optional.ofNullable(newVal));
110 protected AuthorizationInfo doGetAuthorizationInfo(final PrincipalCollection principalCollection) {
111 // the final set or roles to return to the caller; empty to start
112 final Set<String> authRoles = new HashSet<>();
113 final ODLPrincipal odlPrincipal = (ODLPrincipal)principalCollection.getPrimaryPrincipal();
114 final Optional<Authentication> opt = getAuthenticationContainer();
115 if (opt.isPresent()) {
116 final Authentication auth = opt.orElseThrow();
118 // iterate through and determine the appropriate roles based on the programmed grants
119 final Grants grants = auth.getGrants();
120 for (Grant grant : grants.nonnullGrants().values()) {
121 if (grant.getUserid().equals(odlPrincipal.getUserId())) {
122 final Roles roles = auth.getRoles();
124 for (org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214
125 .authentication.roles.Roles role : roles.nonnullRoles().values()) {
126 if (role.getRoleid().equals(grant.getRoleid())) {
127 authRoles.add(role.getRoleid());
134 return new SimpleAuthorizationInfo(authRoles);
138 * Utility method to extract the authentication container.
140 * @return the <code>authentication</code> container
142 private Optional<Authentication> getAuthenticationContainer() {
144 return authentication.get();
145 } catch (final InterruptedException | ExecutionException e) {
146 LOG.error("Couldn't access authentication container", e);
148 return Optional.empty();
152 protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken authenticationToken)
153 throws AuthenticationException {
155 final String username = TokenUtils.extractUsername(authenticationToken);
156 final Optional<Authentication> opt = getAuthenticationContainer();
157 if (opt.isPresent()) {
158 final Authentication auth = opt.orElseThrow();
159 final Users users = auth.getUsers();
160 for (org.opendaylight.yang.gen.v1.urn.opendaylight.params.xml.ns.yang.aaa.rev161214.authentication.users
161 .Users u : users.nonnullUsers().values()) {
162 final String inputUsername = HeaderUtils.extractUsername(username);
163 final String domainId = HeaderUtils.extractDomain(username);
164 final String inputUserId = String.format("%s@%s", inputUsername, domainId);
165 final boolean userEnabled = u.getEnabled();
167 LOG.trace("userId={} is skipped because it is disabled", u.getUserid());
169 if (userEnabled && u.getUserid().equals(inputUserId)) {
170 final String inputPassword = TokenUtils.extractPassword(authenticationToken);
171 if (passwordHashService.passwordsMatch(inputPassword, u.getPassword(), u.getSalt())) {
172 final ODLPrincipal odlPrincipal = ODLPrincipalImpl
173 .createODLPrincipal(inputUsername, domainId, inputUserId);
174 return new SimpleAuthenticationInfo(odlPrincipal, inputPassword, getName());
179 LOG.debug("Couldn't access the authentication container");
180 throw new AuthenticationException(String.format("Couldn't authenticate %s", username));
184 public void destroy() {