Migrate callers of Optional.get()
[aaa.git] / aaa-shiro / impl / src / main / java / org / opendaylight / aaa / shiro / realm / MdsalRealm.java
1 /*
2  * Copyright (c) 2017 Inocybe Technologies 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.Verify.verifyNotNull;
11 import static java.util.Objects.requireNonNull;
12
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;
19 import java.util.Set;
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;
51
52 /**
53  * A Realm based on <code>aaa.yang</code> model.
54  */
55 public class MdsalRealm extends AuthorizingRealm implements Destroyable {
56     private static final Logger LOG = LoggerFactory.getLogger(MdsalRealm.class);
57
58     /**
59      * InstanceIdentifier for the authentication container.
60      */
61     private static final DataTreeIdentifier<Authentication> AUTH_TREE_ID = DataTreeIdentifier.create(
62             LogicalDatastoreType.CONFIGURATION, InstanceIdentifier.create(Authentication.class));
63
64     private static final ThreadLocal<PasswordHashService> PASSWORD_HASH_SERVICE_TL = new ThreadLocal<>();
65     private static final ThreadLocal<DataBroker> DATABROKER_TL = new ThreadLocal<>();
66
67     private final PasswordHashService passwordHashService;
68     private final ListenerRegistration<?> reg;
69
70     private volatile ListenableFuture<Optional<Authentication>> authentication;
71
72     public MdsalRealm() {
73         this(verifyLoad(PASSWORD_HASH_SERVICE_TL), verifyLoad(DATABROKER_TL));
74     }
75
76     private static <T> T verifyLoad(final ThreadLocal<T> threadLocal) {
77         return verifyNotNull(threadLocal.get(), "MdsalRealm not prepared for loading");
78     }
79
80     public MdsalRealm(final PasswordHashService passwordHashService, final DataBroker dataBroker) {
81         this.passwordHashService = requireNonNull(passwordHashService);
82
83         try (ReadTransaction tx = dataBroker.newReadOnlyTransaction()) {
84             authentication = tx.read(AUTH_TREE_ID.getDatastoreType(), AUTH_TREE_ID.getRootIdentifier());
85         }
86
87         reg = dataBroker.registerDataTreeChangeListener(AUTH_TREE_ID,
88             (ClusteredDataTreeChangeListener<Authentication>) this::onAuthenticationChanged);
89
90         LOG.info("MdsalRealm created");
91     }
92
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));
97         return () -> {
98             PASSWORD_HASH_SERVICE_TL.remove();
99             DATABROKER_TL.remove();
100         };
101     }
102
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));
107     }
108
109     @Override
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();
117
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();
123                     if (roles != null) {
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());
128                             }
129                         }
130                     }
131                 }
132             }
133         }
134         return new SimpleAuthorizationInfo(authRoles);
135     }
136
137     /**
138      * Utility method to extract the authentication container.
139      *
140      * @return the <code>authentication</code> container
141      */
142     private Optional<Authentication> getAuthenticationContainer() {
143         try {
144             return authentication.get();
145         } catch (final InterruptedException | ExecutionException e) {
146             LOG.error("Couldn't access authentication container", e);
147         }
148         return Optional.empty();
149     }
150
151     @Override
152     protected AuthenticationInfo doGetAuthenticationInfo(final AuthenticationToken authenticationToken)
153             throws AuthenticationException {
154
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();
166                 if (!userEnabled) {
167                     LOG.trace("userId={} is skipped because it is disabled", u.getUserid());
168                 }
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());
175                     }
176                 }
177             }
178         }
179         LOG.debug("Couldn't access the authentication container");
180         throw new AuthenticationException(String.format("Couldn't authenticate %s", username));
181     }
182
183     @Override
184     public void destroy() {
185         reg.close();
186     }
187 }