001/* 002 * Copyright 2015 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.core.user; 017 018import java.util.HashMap; 019import java.util.List; 020import java.util.Map; 021 022import org.apache.avalon.framework.component.Component; 023import org.apache.avalon.framework.context.Context; 024import org.apache.avalon.framework.context.ContextException; 025import org.apache.avalon.framework.context.Contextualizable; 026import org.apache.avalon.framework.logger.AbstractLogEnabled; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030import org.apache.cocoon.ProcessingException; 031import org.apache.cocoon.components.ContextHelper; 032import org.apache.cocoon.environment.Redirector; 033import org.apache.cocoon.environment.Request; 034import org.apache.commons.lang3.StringUtils; 035 036import org.ametys.core.authentication.AbstractCredentialProvider; 037import org.ametys.core.authentication.AuthenticateAction; 038import org.ametys.core.authentication.BlockingCredentialProvider; 039import org.ametys.core.right.RightManager; 040import org.ametys.core.ui.Callable; 041import org.ametys.core.user.CurrentUserProvider; 042import org.ametys.core.user.InvalidModificationException; 043import org.ametys.core.user.User; 044import org.ametys.core.user.UserIdentity; 045import org.ametys.core.user.UserManager; 046import org.ametys.core.user.directory.ModifiableUserDirectory; 047import org.ametys.core.user.directory.UserDirectory; 048import org.ametys.core.user.population.UserPopulation; 049import org.ametys.core.user.population.UserPopulationDAO; 050import org.ametys.runtime.authentication.AccessDeniedException; 051import org.ametys.runtime.i18n.I18nizableText; 052import org.ametys.runtime.model.View; 053import org.ametys.runtime.parameter.Errors; 054 055/** 056 * DAO for manipulating {@link User} 057 * 058 */ 059public class UserDAO extends AbstractLogEnabled implements Component, Contextualizable, Serviceable 060{ 061 /** The avalon role */ 062 public static final String ROLE = UserDAO.class.getName(); 063 064 /** The service manager */ 065 protected ServiceManager _smanager; 066 /** The user manager */ 067 protected UserManager _userManager; 068 /** The user population DAO */ 069 protected UserPopulationDAO _userPopulationDAO; 070 /** The current user provider. */ 071 protected CurrentUserProvider _currentUserProvider; 072 /** The Avalon context */ 073 protected Context _context; 074 /** The user helper */ 075 protected UserHelper _userHelper; 076 /** The right manager */ 077 protected RightManager _rightManager; 078 079 public void contextualize(Context context) throws ContextException 080 { 081 _context = context; 082 } 083 084 public void service(ServiceManager smanager) throws ServiceException 085 { 086 _smanager = smanager; 087 _userManager = (UserManager) smanager.lookup(UserManager.ROLE); 088 _userPopulationDAO = (UserPopulationDAO) smanager.lookup(UserPopulationDAO.ROLE); 089 _userHelper = (UserHelper) smanager.lookup(UserHelper.ROLE); 090 _rightManager = (RightManager) smanager.lookup(RightManager.ROLE); 091 } 092 093 /** 094 * Get user's information 095 * @param login The user's login 096 * @param populationId The id of the population 097 * @return The user's information 098 */ 099 @Callable 100 public Map<String, Object> getUser (String login, String populationId) 101 { 102 return _userHelper.user2json(_userManager.getUser(populationId, login), true); 103 } 104 105 /** 106 * Checks if the user is modifiable 107 * @param login The users's login 108 * @param populationId The id of the population of the user 109 * @return A map with the "isModifiable" at true if the user is modifiable 110 */ 111 @Callable 112 public Map<String, Object> isModifiable (String login, String populationId) 113 { 114 Map<String, Object> result = new HashMap<>(); 115 result.put("isModifiable", _userManager.getUserDirectory(populationId, login) instanceof ModifiableUserDirectory); 116 result.put("additionalDescription", new I18nizableText("plugin.core-ui", "PLUGINS_CORE_UI_USERS_EDIT_NO_MODIFIABLE_DESCRIPTION")); 117 return result; 118 } 119 120 /** 121 * Checks if the user is removable 122 * @param login The users's login 123 * @param populationId The id of the population of the user 124 * @return A map with the "isRemovable" at true if the user is removable 125 */ 126 @Callable 127 public Map<String, Object> isRemovable (String login, String populationId) 128 { 129 Map<String, Object> result = new HashMap<>(); 130 131 UserDirectory userDirectory = _userManager.getUserDirectory(populationId, login); 132 if (userDirectory != null && populationId.equals(UserPopulationDAO.ADMIN_POPULATION_ID) && userDirectory.getUsers().size() == 1) 133 { 134 // Impossible to delete the last user of the admin population 135 result.put("isRemovable", false); 136 result.put("additionalDescription", new I18nizableText("plugin.core-ui", "PLUGINS_CORE_UI_USERS_DELETE_LAST_ADMIN_DESCRIPTION")); 137 } 138 else 139 { 140 result.put("isRemovable", userDirectory instanceof ModifiableUserDirectory); 141 result.put("additionalDescription", new I18nizableText("plugin.core-ui", "PLUGINS_CORE_UI_USERS_DELETE_NO_MODIFIABLE_DESCRIPTION")); 142 } 143 return result; 144 } 145 146 /** 147 * Creates a User 148 * @param populationId The id of the user population 149 * @param userDirectoryId The id of the user directory 150 * @param untypedValues The untyped user's parameters 151 * @return The created user as JSON object 152 * @throws InvalidModificationException If modification is not possible 153 */ 154 @Callable (right = "Runtime_Rights_User_Handle") 155 public Map<String, Object> addUser (String populationId, String userDirectoryId, Map<String, String> untypedValues) throws InvalidModificationException 156 { 157 Map<String, Object> result = new HashMap<>(); 158 159 UserPopulation userPopulation = _userPopulationDAO.getUserPopulation(populationId); 160 UserDirectory userDirectory = userPopulation.getUserDirectory(userDirectoryId); 161 162 if (!(userDirectory instanceof ModifiableUserDirectory)) 163 { 164 getLogger().error("Users are not modifiable !"); 165 throw new InvalidModificationException("Users are not modifiable !"); 166 } 167 168 ModifiableUserDirectory modifiableUserDirectory = (ModifiableUserDirectory) userDirectory; 169 170 String login = untypedValues.get("login"); 171 172 try 173 { 174 if (getLogger().isInfoEnabled()) 175 { 176 getLogger().info(String.format("User %s is adding a new user '%s'", _getCurrentUser(), login)); 177 } 178 179 modifiableUserDirectory.add(untypedValues); 180 return _userHelper.user2json(modifiableUserDirectory.getUser(login), true); 181 } 182 catch (InvalidModificationException e) 183 { 184 Map<String, Errors> fieldErrors = e.getFieldErrors(); 185 186 if (fieldErrors != null && fieldErrors.size() > 0) 187 { 188 result.put("errors", fieldErrors); 189 } 190 else 191 { 192 throw e; 193 } 194 } 195 196 if (getLogger().isDebugEnabled()) 197 { 198 getLogger().debug("Ending user's edition"); 199 } 200 201 return result; 202 } 203 204 /** 205 * Edits a User 206 * @param populationId The id of the population of the user to edit 207 * @param untypedValues The untyped user's parameters 208 * @return The update user as JSON object 209 * @throws InvalidModificationException If modification is not possible 210 */ 211 @Callable (right = "Runtime_Rights_User_Handle") 212 public Map<String, Object> editUser (String populationId, Map<String, String> untypedValues) throws InvalidModificationException 213 { 214 Map<String, Object> result = new HashMap<>(); 215 216 String login = untypedValues.get("login"); 217 UserDirectory userDirectory = _userManager.getUserDirectory(populationId, login); 218 219 if (!(userDirectory instanceof ModifiableUserDirectory)) 220 { 221 getLogger().error("Users are not modifiable !"); 222 throw new InvalidModificationException("Users are not modifiable !"); 223 } 224 225 ModifiableUserDirectory modifiableUserDirectory = (ModifiableUserDirectory) userDirectory; 226 227 try 228 { 229 if (getLogger().isInfoEnabled()) 230 { 231 getLogger().info(String.format("User %s is updating information about user '%s'", _getCurrentUser(), login)); 232 } 233 234 modifiableUserDirectory.update(untypedValues); 235 return _userHelper.user2json(modifiableUserDirectory.getUser(login), true); 236 } 237 catch (InvalidModificationException e) 238 { 239 Map<String, Errors> fieldErrors = e.getFieldErrors(); 240 241 if (fieldErrors != null && fieldErrors.size() > 0) 242 { 243 result.put("errors", fieldErrors); 244 } 245 else 246 { 247 throw e; 248 } 249 } 250 251 if (getLogger().isDebugEnabled()) 252 { 253 getLogger().debug("Ending user's edition"); 254 } 255 256 return result; 257 } 258 259 /** 260 * Deletes users 261 * @param users The users to delete 262 * @throws InvalidModificationException If modification is not possible 263 */ 264 @Callable (right = "Runtime_Rights_User_Handle") 265 public void deleteUsers (List<Map<String, String>> users) throws InvalidModificationException 266 { 267 for (Map<String, String> user : users) 268 { 269 String login = user.get("login"); 270 String populationId = user.get("populationId"); 271 _deleteUser(login, populationId); 272 } 273 274 if (getLogger().isDebugEnabled()) 275 { 276 getLogger().debug("Ending user's removal"); 277 } 278 } 279 280 private void _deleteUser(String login, String populationId) throws InvalidModificationException 281 { 282 UserDirectory userDirectory = _userManager.getUserDirectory(populationId, login); 283 284 if (!(userDirectory instanceof ModifiableUserDirectory)) 285 { 286 getLogger().error("Users are not modifiable !"); 287 throw new InvalidModificationException("Users are not modifiable !"); 288 } 289 290 if (populationId.equals(UserPopulationDAO.ADMIN_POPULATION_ID) && userDirectory.getUsers().size() == 1) 291 { 292 // Impossible to delete the last user of the admin population 293 getLogger().error("Deletion forbidden: last user of the 'admin' population."); 294 throw new InvalidModificationException("You cannot delete the last user of the 'admin' population !"); 295 } 296 297 ModifiableUserDirectory modifiableUserDirectory = (ModifiableUserDirectory) userDirectory; 298 299 if (getLogger().isInfoEnabled()) 300 { 301 getLogger().info(String.format("User %s is removing user '%s'", _getCurrentUser(), login)); 302 } 303 304 modifiableUserDirectory.remove(login); 305 } 306 307 /** 308 * Get the users edition model 309 * @param login The user's login to edit 310 * @param populationId The id of the population of the user to edit 311 * @throws InvalidModificationException If modification is not possible 312 * @throws ProcessingException If there is another exception 313 * @return The edition model as an object 314 */ 315 @Callable (right = "Runtime_Rights_User_Handle") 316 public Map<String, Object> getEditionModelForUSer(String login, String populationId) throws InvalidModificationException, ProcessingException 317 { 318 UserDirectory userDirectory = _userManager.getUserDirectory(populationId, login); 319 return _getEditionModel(userDirectory); 320 } 321 322 /** 323 * Get the users edition model 324 * @param populationId The id of the population where to add a user 325 * @param userDirectoryId The id of the user directory where to add a user 326 * @throws InvalidModificationException If modification is not possible 327 * @throws ProcessingException If there is another exception 328 * @return The edition model as an object 329 */ 330 @Callable (right = "Runtime_Rights_User_Handle") 331 public Map<String, Object> getEditionModelForDirectory(String populationId, String userDirectoryId) throws InvalidModificationException, ProcessingException 332 { 333 UserDirectory userDirectory = _userPopulationDAO.getUserPopulation(populationId).getUserDirectory(userDirectoryId); 334 return _getEditionModel(userDirectory); 335 } 336 337 private Map<String, Object> _getEditionModel (UserDirectory userDirectory) throws InvalidModificationException, ProcessingException 338 { 339 if (!(userDirectory instanceof ModifiableUserDirectory)) 340 { 341 getLogger().error("Users are not modifiable !"); 342 throw new InvalidModificationException("Users are not modifiable !"); 343 } 344 345 ModifiableUserDirectory modifiableUserDirectory = (ModifiableUserDirectory) userDirectory; 346 View view = modifiableUserDirectory.getView(); 347 return view.toJSON(); 348 } 349 350 /** 351 * Impersonate the selected user 352 * @param login the login of the user to impersonate 353 * @param populationId The id of the population 354 * @return a map of information on the user 355 * @throws AccessDeniedException If the currently connected user has no right to do so 356 */ 357 @Callable (right = "Runtime_Rights_User_Handle", context = "/admin") 358 public Map<String, String> impersonate(String login, String populationId) throws AccessDeniedException 359 { 360 UserIdentity currentUser = _getCurrentUser(); 361 362 if (StringUtils.isEmpty(login)) 363 { 364 throw new IllegalArgumentException("'login' parameter is null or empty"); 365 } 366 367 Map<String, String> result = new HashMap<>(); 368 369 User user = _userManager.getUser(populationId, login); 370 if (user == null) 371 { 372 result.put("error", "unknown-user"); 373 } 374 else 375 { 376 try 377 { 378 _currentUserProvider.logout(); 379 } 380 catch (ProcessingException e) 381 { 382 getLogger().error("An error occurred while logging out current user " + currentUser); 383 } 384 385 Request request = ContextHelper.getRequest(_context); 386 387 AuthenticateAction.setUserIdentityInSession(request, user.getIdentity(), new ImpersonateCredentialProvider(), true); 388 389 result.put("login", login); 390 result.put("populationId", populationId); 391 result.put("name", user.getFullName()); 392 393 if (getLogger().isInfoEnabled()) 394 { 395 getLogger().info("Impersonation of the user '" + login + "' from IP " + request.getRemoteAddr() + " done by " + currentUser); 396 } 397 } 398 399 return result; 400 } 401 402 /** 403 * Provides the login of the current user. 404 * @return the login which cannot be <code>null</code>. 405 */ 406 protected UserIdentity _getCurrentUser() 407 { 408 if (_currentUserProvider == null) 409 { 410 try 411 { 412 _currentUserProvider = (CurrentUserProvider) _smanager.lookup(CurrentUserProvider.ROLE); 413 } 414 catch (ServiceException e) 415 { 416 throw new IllegalStateException(e); 417 } 418 } 419 420 return _currentUserProvider.getUser(); 421 } 422 423 /** 424 * A fake credential provider used by the impersonate process 425 */ 426 public static class ImpersonateCredentialProvider extends AbstractCredentialProvider implements BlockingCredentialProvider 427 { 428 429 public boolean blockingGrantAnonymousRequest() 430 { 431 return false; 432 } 433 434 public boolean blockingIsStillConnected(UserIdentity userIdentity, Redirector redirector) throws Exception 435 { 436 return true; 437 } 438 439 public UserIdentity blockingGetUserIdentity(Redirector redirector) throws Exception 440 { 441 return null; 442 } 443 444 public void blockingUserNotAllowed(Redirector redirector) throws Exception 445 { 446 // nothing 447 } 448 449 public void blockingUserAllowed(UserIdentity userIdentity) 450 { 451 // nothing 452 } 453 454 public boolean requiresNewWindow() 455 { 456 return false; 457 } 458 459 } 460}