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