001/* 002 * Copyright 2021 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.extrausermgt.users.aad; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.List; 021import java.util.Map; 022 023import org.apache.commons.lang3.StringUtils; 024 025import org.ametys.core.user.User; 026import org.ametys.core.user.UserIdentity; 027import org.ametys.core.user.directory.NotUniqueUserException; 028import org.ametys.core.user.directory.UserDirectory; 029import org.ametys.plugins.core.impl.user.directory.AbstractCachingUserDirectory; 030 031import com.azure.identity.ClientSecretCredential; 032import com.azure.identity.ClientSecretCredentialBuilder; 033import com.microsoft.graph.authentication.TokenCredentialAuthProvider; 034import com.microsoft.graph.options.HeaderOption; 035import com.microsoft.graph.options.Option; 036import com.microsoft.graph.options.QueryOption; 037import com.microsoft.graph.requests.GraphServiceClient; 038import com.microsoft.graph.requests.UserCollectionPage; 039import com.microsoft.graph.requests.UserCollectionRequest; 040import com.microsoft.graph.requests.UserCollectionRequestBuilder; 041 042/** 043 * {@link UserDirectory} listing users in Azure Active Directory. 044 */ 045public class AADUserDirectory extends AbstractCachingUserDirectory 046{ 047 private GraphServiceClient _graphClient; 048 private String _filter; 049 050 @Override 051 public void init(String id, String udModelId, Map<String, Object> paramValues, String label) 052 { 053 super.init(id, udModelId, paramValues, label); 054 055 String clientID = (String) paramValues.get("org.ametys.plugins.extrausermgt.users.aad.appid"); 056 String clientSecret = (String) paramValues.get("org.ametys.plugins.extrausermgt.users.aad.clientsecret"); 057 String tenant = (String) paramValues.get("org.ametys.plugins.extrausermgt.users.aad.tenant"); 058 _filter = (String) paramValues.get("org.ametys.plugins.extrausermgt.users.aad.filter"); 059 060 ClientSecretCredential clientSecretCredential = new ClientSecretCredentialBuilder().clientId(clientID) 061 .clientSecret(clientSecret) 062 .tenantId(tenant) 063 .build(); 064 065 TokenCredentialAuthProvider tokenCredentialAuthProvider = new TokenCredentialAuthProvider(clientSecretCredential); 066 _graphClient = GraphServiceClient.builder() 067 .authenticationProvider(tokenCredentialAuthProvider) 068 .buildClient(); 069 070 createCaches(); 071 } 072 073 @Override 074 protected String getCacheTypeLabel() 075 { 076 return "AzureAD"; 077 } 078 079 public Collection<User> getUsers() 080 { 081 return getUsers(-1, 0, null); 082 } 083 084 public List<User> getUsers(int count, int offset, Map<String, Object> parameters) 085 { 086 List<Option> options = new ArrayList<>(); 087 options.add(new HeaderOption("ConsistencyLevel", "eventual")); 088 089 String pattern = parameters != null ? (String) parameters.get("pattern") : null; 090 091 if (StringUtils.isNotEmpty(pattern)) 092 { 093 options.add(new QueryOption("$search", "\"givenName:" + pattern + "\" OR \"surname:" + pattern + "\" OR \"userPrincipalName:" + pattern + "\"")); 094 } 095 096 UserCollectionRequest userCollectionRequest = _graphClient.users().buildRequest(options); 097 098 int maxUsers = -1; 099 if (count > 0 && count < Integer.MAX_VALUE) 100 { 101 maxUsers = count; 102 userCollectionRequest.top(count + offset); 103 } 104 105 if (StringUtils.isNotEmpty(_filter)) 106 { 107 userCollectionRequest.filter(_filter); 108 } 109 110 UserCollectionPage userCollectionPage = userCollectionRequest.count().get(); 111 112 List<User> result = new ArrayList<>(); 113 _handlePage(userCollectionPage, result, maxUsers, offset); 114 115 return result; 116 } 117 118 private void _handlePage(UserCollectionPage userCollectionPage, List<User> users, int maxUsers, int offset) 119 { 120 int currentOffset = offset; 121 int currentCount = maxUsers; 122 for (com.microsoft.graph.models.User u : userCollectionPage.getCurrentPage()) 123 { 124 if (currentOffset > 0) 125 { 126 currentOffset--; 127 } 128 else 129 { 130 User user = new User(new UserIdentity(u.userPrincipalName, getPopulationId()), u.surname, u.givenName, u.mail, this); 131 users.add(user); 132 133 if (isCachingEnabled()) 134 { 135 getCacheByLogin().put(user.getIdentity().getLogin(), user); 136 } 137 138 if (currentCount > 0) 139 { 140 currentCount--; 141 if (currentCount == 0) 142 { 143 break; 144 } 145 } 146 } 147 } 148 149 if (currentCount != 0) 150 { 151 UserCollectionRequestBuilder nextPage = userCollectionPage.getNextPage(); 152 if (nextPage != null) 153 { 154 _handlePage(nextPage.buildRequest(new HeaderOption("ConsistencyLevel", "eventual")).get(), users, currentCount, currentOffset); 155 } 156 } 157 } 158 159 public User getUser(String login) 160 { 161 if (isCachingEnabled() && getCacheByLogin().hasKey(login)) 162 { 163 User user = getCacheByLogin().get(login); 164 return user; 165 } 166 167 com.microsoft.graph.models.User u = _graphClient.users(login).buildRequest().select("userPrincipalName, surname, givenName, mail").get(); 168 169 User user = new User(new UserIdentity(u.userPrincipalName, getPopulationId()), u.surname, u.givenName, u.mail, this); 170 171 if (isCachingEnabled()) 172 { 173 getCacheByLogin().put(user.getIdentity().getLogin(), user); 174 } 175 176 return user; 177 } 178 179 /* 180 * As we do not know how to search for email in a "case insensitive" way, we also fill the cache "case sensitively" 181 */ 182 public User getUserByEmail(String email) throws NotUniqueUserException 183 { 184 if (StringUtils.isBlank(email)) 185 { 186 return null; 187 } 188 189 if (isCachingEnabled() && getCacheByMail().hasKey(email)) 190 { 191 User user = getCacheByMail().get(email); 192 return user; 193 } 194 195 List<com.microsoft.graph.models.User> users = _graphClient.users().buildRequest(new HeaderOption("ConsistencyLevel", "eventual")) 196 .filter("mail eq '" + email + "'") 197 .select("userPrincipalName, surname, givenName, mail") 198 .get() 199 .getCurrentPage(); 200 201 if (users.size() == 1) 202 { 203 com.microsoft.graph.models.User u = users.get(0); 204 User user = new User(new UserIdentity(u.userPrincipalName, getPopulationId()), u.surname, u.givenName, u.mail, this); 205 206 if (isCachingEnabled()) 207 { 208 getCacheByMail().put(user.getEmail(), user); 209 } 210 211 return user; 212 } 213 else if (users.isEmpty()) 214 { 215 return null; 216 } 217 else 218 { 219 throw new NotUniqueUserException("Find " + users.size() + " users matching the email " + email); 220 } 221 } 222 223 public boolean checkCredentials(String login, String password) 224 { 225 throw new UnsupportedOperationException("The AADUserDirectory cannot authenticate users"); 226 } 227}