001/* 002 * Copyright 2016 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.core.user.population; 017 018import java.io.File; 019import java.io.FileInputStream; 020import java.io.FileNotFoundException; 021import java.io.FileOutputStream; 022import java.io.IOException; 023import java.io.InputStream; 024import java.io.OutputStream; 025import java.nio.file.Files; 026import java.nio.file.StandardCopyOption; 027import java.sql.Connection; 028import java.time.Instant; 029import java.time.temporal.ChronoUnit; 030import java.util.ArrayList; 031import java.util.Arrays; 032import java.util.Collections; 033import java.util.HashMap; 034import java.util.HashSet; 035import java.util.LinkedHashMap; 036import java.util.List; 037import java.util.Map; 038import java.util.Properties; 039import java.util.Set; 040import java.util.regex.Pattern; 041import java.util.stream.Collectors; 042 043import javax.xml.transform.OutputKeys; 044import javax.xml.transform.TransformerConfigurationException; 045import javax.xml.transform.TransformerFactory; 046import javax.xml.transform.TransformerFactoryConfigurationError; 047import javax.xml.transform.sax.SAXTransformerFactory; 048import javax.xml.transform.sax.TransformerHandler; 049import javax.xml.transform.stream.StreamResult; 050 051import org.apache.avalon.framework.activity.Disposable; 052import org.apache.avalon.framework.activity.Initializable; 053import org.apache.avalon.framework.component.Component; 054import org.apache.avalon.framework.configuration.Configuration; 055import org.apache.avalon.framework.configuration.ConfigurationException; 056import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 057import org.apache.avalon.framework.service.ServiceException; 058import org.apache.avalon.framework.service.ServiceManager; 059import org.apache.avalon.framework.service.Serviceable; 060import org.apache.cocoon.xml.AttributesImpl; 061import org.apache.cocoon.xml.XMLUtils; 062import org.apache.commons.lang3.StringUtils; 063import org.apache.xml.serializer.OutputPropertiesFactory; 064import org.xml.sax.SAXException; 065 066import org.ametys.core.ObservationConstants; 067import org.ametys.core.authentication.CredentialProvider; 068import org.ametys.core.authentication.CredentialProviderFactory; 069import org.ametys.core.authentication.CredentialProviderModel; 070import org.ametys.core.datasource.ConnectionHelper; 071import org.ametys.core.datasource.SQLDataSourceManager; 072import org.ametys.core.observation.Event; 073import org.ametys.core.observation.ObservationManager; 074import org.ametys.core.script.SQLScriptHelper; 075import org.ametys.core.ui.Callable; 076import org.ametys.core.user.CurrentUserProvider; 077import org.ametys.core.user.InvalidModificationException; 078import org.ametys.core.user.UserIdentity; 079import org.ametys.core.user.directory.ModifiableUserDirectory; 080import org.ametys.core.user.directory.UserDirectory; 081import org.ametys.core.user.directory.UserDirectoryFactory; 082import org.ametys.core.user.directory.UserDirectoryModel; 083import org.ametys.core.util.I18nUtils; 084import org.ametys.plugins.core.impl.user.directory.StaticUserDirectory; 085import org.ametys.runtime.i18n.I18nizableText; 086import org.ametys.runtime.parameter.Parameter; 087import org.ametys.runtime.parameter.ParameterCheckerDescriptor; 088import org.ametys.runtime.parameter.ParameterHelper; 089import org.ametys.runtime.parameter.ParameterHelper.ParameterType; 090import org.ametys.runtime.plugin.PluginsManager; 091import org.ametys.runtime.plugin.PluginsManager.Status; 092import org.ametys.runtime.plugin.component.AbstractLogEnabled; 093import org.ametys.runtime.util.AmetysHomeHelper; 094 095/** 096 * DAO for accessing {@link UserPopulation} 097 */ 098public class UserPopulationDAO extends AbstractLogEnabled implements Component, Serviceable, Initializable, Disposable 099{ 100 /** Avalon Role */ 101 public static final String ROLE = UserPopulationDAO.class.getName(); 102 103 /** The id of the "admin" population */ 104 public static final String ADMIN_POPULATION_ID = "admin_population"; 105 106 /** The id of the "user system" login */ 107 public static final String SYSTEM_USER_LOGIN = "system-user"; 108 /** The id of the "user system" population */ 109 public static final UserIdentity SYSTEM_USER_IDENTITY = new UserIdentity(SYSTEM_USER_LOGIN, ADMIN_POPULATION_ID); 110 111 /** The sql table for admin users */ 112 private static final String __ADMIN_TABLENAME = "AdminUsers"; 113 114 /** The path of the XML file containing the user populations */ 115 private static final File __USER_POPULATIONS_FILE = new File(AmetysHomeHelper.getAmetysHome(), "config" + File.separator + "user-populations.xml"); 116 117 /** The regular expression for an id of a user population */ 118 private static final String __ID_REGEX = "^[a-z][a-z0-9_-]*"; 119 120 /** The date (as a long) of the last time the {@link #__USER_POPULATIONS_FILE Populations file} was read (last update) */ 121 private long _lastFileReading; 122 123 /** The whole user populations of the application */ 124 private Map<String, UserPopulation> _userPopulations; 125 /** The misconfigured user populations */ 126 private Set<String> _misconfiguredUserPopulations; 127 128 /** The list of population ids which are declared in the user population file but were not instanciated since their configuration led to an error */ 129 private Set<String> _ignoredPopulations; 130 131 /** The population admin */ 132 private UserPopulation _adminUserPopulation; 133 134 /** The user directories factory */ 135 private UserDirectoryFactory _userDirectoryFactory; 136 137 /** The credential providers factory */ 138 private CredentialProviderFactory _credentialProviderFactory; 139 140 /** The extension point for population consumers */ 141 private PopulationConsumerExtensionPoint _populationConsumerEP; 142 143 private ObservationManager _observationManager; 144 145 private CurrentUserProvider _currentUserProvider; 146 147 private I18nUtils _i18nutils; 148 149 @Override 150 public void initialize() 151 { 152 _userPopulations = new LinkedHashMap<>(); 153 _misconfiguredUserPopulations = new HashSet<>(); 154 _ignoredPopulations = new HashSet<>(); 155 _lastFileReading = 0; 156 } 157 158 @Override 159 public void service(ServiceManager manager) throws ServiceException 160 { 161 _userDirectoryFactory = (UserDirectoryFactory) manager.lookup(UserDirectoryFactory.ROLE); 162 _credentialProviderFactory = (CredentialProviderFactory) manager.lookup(CredentialProviderFactory.ROLE); 163 _populationConsumerEP = (PopulationConsumerExtensionPoint) manager.lookup(PopulationConsumerExtensionPoint.ROLE); 164 try 165 { 166 _observationManager = (ObservationManager) manager.lookup(ObservationManager.ROLE); 167 } 168 catch (ServiceException e) 169 { 170 // Not a safe component... ignore it 171 } 172 _currentUserProvider = (CurrentUserProvider) manager.lookup(CurrentUserProvider.ROLE); 173 _i18nutils = (I18nUtils) manager.lookup(I18nUtils.ROLE); 174 } 175 176 /** 177 * Gets all the populations to JSON format 178 * @param withAdmin True to include the "admin" population 179 * @return A list of object representing the {@link UserPopulation}s 180 */ 181 public List<Object> getUserPopulationsAsJson(boolean withAdmin) 182 { 183 return getUserPopulations(withAdmin).stream().map(this::getUserPopulationAsJson).collect(Collectors.toList()); 184 } 185 186 /** 187 * Gets a population to JSON format 188 * @param userPopulation The user population to get 189 * @return An object representing a {@link UserPopulation} 190 */ 191 public Map<String, Object> getUserPopulationAsJson(UserPopulation userPopulation) 192 { 193 Map<String, Object> result = new LinkedHashMap<>(); 194 result.put("id", userPopulation.getId()); 195 result.put("label", userPopulation.getLabel()); 196 result.put("enabled", userPopulation.isEnabled()); 197 result.put("valid", isValid(userPopulation.getId())); 198 result.put("isInUse", _populationConsumerEP.isInUse(userPopulation.getId())); 199 200 List<Object> userDirectories = new ArrayList<>(); 201 for (UserDirectory ud : userPopulation.getUserDirectories()) 202 { 203 String udModelId = ud.getUserDirectoryModelId(); 204 UserDirectoryModel udModel = _userDirectoryFactory.getExtension(udModelId); 205 206 Map<String, Object> directory = new HashMap<>(); 207 directory.put("id", ud.getId()); 208 209 if (StringUtils.isNotBlank(ud.getLabel())) 210 { 211 directory.put("label", _i18nutils.translate(udModel.getLabel()) + " (" + ud.getLabel() + ")"); 212 } 213 else 214 { 215 directory.put("label", udModel.getLabel()); 216 } 217 directory.put("modifiable", ud instanceof ModifiableUserDirectory); 218 userDirectories.add(directory); 219 } 220 result.put("userDirectories", userDirectories); 221 222 List<Object> credentialProviders = new ArrayList<>(); 223 for (CredentialProvider cp : userPopulation.getCredentialProviders()) 224 { 225 String cpModelId = cp.getCredentialProviderModelId(); 226 CredentialProviderModel cpModel = _credentialProviderFactory.getExtension(cpModelId); 227 228 Map<String, Object> credentialProvider = new HashMap<>(); 229 credentialProvider.put("id", cp.getId()); 230 231 if (StringUtils.isNotBlank(cp.getLabel())) 232 { 233 credentialProvider.put("label", _i18nutils.translate(cpModel.getLabel()) + " (" + cp.getLabel() + ")"); 234 } 235 else 236 { 237 credentialProvider.put("label", cpModel.getLabel()); 238 } 239 credentialProviders.add(credentialProvider); 240 } 241 result.put("credentialProviders", credentialProviders); 242 243 return result; 244 } 245 246 /** 247 * Gets all the populations of this application 248 * @param includeAdminPopulation True to include the "admin" population 249 * @return A list of {@link UserPopulation} 250 */ 251 public List<UserPopulation> getUserPopulations(boolean includeAdminPopulation) 252 { 253 List<UserPopulation> result = new ArrayList<>(); 254 if (includeAdminPopulation) 255 { 256 result.add(getAdminPopulation()); 257 } 258 259 // Don't read in safe mode, we know that only the admin population is needed in this case and we want to prevent some warnings in the logs for non-safe features not found 260 if (Status.OK.equals(PluginsManager.getInstance().getStatus())) 261 { 262 _readPopulations(false); 263 result.addAll(_userPopulations.values()); 264 } 265 266 return result; 267 } 268 269 /** 270 * Gets all the enabled populations of this application 271 * @param withAdmin True to include the "admin" population 272 * @return A list of enabled {@link UserPopulation} 273 */ 274 public List<UserPopulation> getEnabledUserPopulations(boolean withAdmin) 275 { 276 return getUserPopulations(withAdmin).stream().filter(UserPopulation::isEnabled).collect(Collectors.toList()); 277 } 278 279 /** 280 * Gets a population with its id. 281 * @param id The id of the population 282 * @return The {@link UserPopulation}, or null if not found 283 */ 284 public UserPopulation getUserPopulation(String id) 285 { 286 if (ADMIN_POPULATION_ID.equals(id)) 287 { 288 return getAdminPopulation(); 289 } 290 291 _readPopulations(false); 292 return _userPopulations.get(id); 293 } 294 295 /** 296 * Gets the list of the ids of all the population of the application 297 * @return The list of the ids of all the populations 298 */ 299 @Callable 300 public List<String> getUserPopulationsIds() 301 { 302 _readPopulations(false); 303 return new ArrayList<>(_userPopulations.keySet()); 304 } 305 306 /** 307 * Returns the id of population which have a fatal invalid configuration. 308 * These populations can NOT be used by application. 309 * @return The ignored populations 310 */ 311 public Set<String> getIgnoredPopulations() 312 { 313 _readPopulations(false); 314 return _ignoredPopulations; 315 } 316 317 /** 318 * Returns the id of population which have at least one user directory or one credential provider misconfigured 319 * These populations can be used by application. 320 * @return The misconfigured populations. 321 */ 322 public Set<String> getMisconfiguredPopulations() 323 { 324 _readPopulations(false); 325 return _misconfiguredUserPopulations; 326 } 327 328 /** 329 * Return the stream of the configuration file 330 * @return The input stream to be closed. 331 */ 332 public InputStream getConfigurationFile() 333 { 334 try 335 { 336 return new FileInputStream(__USER_POPULATIONS_FILE); 337 } 338 catch (FileNotFoundException e) 339 { 340 throw new RuntimeException("Cannot get the configuration file", e); 341 } 342 } 343 344 /** 345 * Gets the configuration for creating/editing a user population. 346 * @return A map containing information about what is needed to create/edit a user population 347 * @throws Exception If an error occurs. 348 */ 349 @Callable 350 public Map<String, Object> getEditionConfiguration() throws Exception 351 { 352 Map<String, Object> result = new LinkedHashMap<>(); 353 354 List<Object> userDirectoryModels = new ArrayList<>(); 355 for (String extensionId : _userDirectoryFactory.getExtensionsIds()) 356 { 357 UserDirectoryModel udModel = _userDirectoryFactory.getExtension(extensionId); 358 Map<String, Object> udMap = new LinkedHashMap<>(); 359 udMap.put("id", extensionId); 360 udMap.put("label", udModel.getLabel()); 361 udMap.put("description", udModel.getDescription()); 362 363 Map<String, Object> params = new LinkedHashMap<>(); 364 for (String paramId : udModel.getParameters().keySet()) 365 { 366 // prefix in case of two parameters from two different models have the same id which can lead to some errorsin client-side 367 params.put(extensionId + "$" + paramId, ParameterHelper.toJSON(udModel.getParameters().get(paramId))); 368 } 369 udMap.put("parameters", params); 370 371 Map<String, Object> paramCheckers = new LinkedHashMap<>(); 372 for (String paramCheckerId : udModel.getParameterCheckers().keySet()) 373 { 374 ParameterCheckerDescriptor paramChecker = udModel.getParameterCheckers().get(paramCheckerId); 375 paramCheckers.put(extensionId + "$" + paramCheckerId, paramChecker.toJSON()); 376 } 377 udMap.put("parameterCheckers", paramCheckers); 378 379 userDirectoryModels.add(udMap); 380 } 381 result.put("userDirectoryModels", userDirectoryModels); 382 383 List<Object> credentialProviderModels = new ArrayList<>(); 384 for (String extensionId : _credentialProviderFactory.getExtensionsIds()) 385 { 386 CredentialProviderModel cpModel = _credentialProviderFactory.getExtension(extensionId); 387 Map<String, Object> cpMap = new LinkedHashMap<>(); 388 cpMap.put("id", extensionId); 389 cpMap.put("label", cpModel.getLabel()); 390 cpMap.put("description", cpModel.getDescription()); 391 392 Map<String, Object> params = new LinkedHashMap<>(); 393 for (String paramId : cpModel.getParameters().keySet()) 394 { 395 // prefix in case of two parameters from two different models have the same id which can lead to some errors in client-side 396 params.put(extensionId + "$" + paramId, ParameterHelper.toJSON(cpModel.getParameters().get(paramId))); 397 } 398 cpMap.put("parameters", params); 399 400 Map<String, Object> paramCheckers = new LinkedHashMap<>(); 401 for (String paramCheckerId : cpModel.getParameterCheckers().keySet()) 402 { 403 ParameterCheckerDescriptor paramChecker = cpModel.getParameterCheckers().get(paramCheckerId); 404 paramCheckers.put(extensionId + "$" + paramCheckerId, paramChecker.toJSON()); 405 } 406 cpMap.put("parameterCheckers", paramCheckers); 407 408 credentialProviderModels.add(cpMap); 409 } 410 result.put("credentialProviderModels", credentialProviderModels); 411 412 return result; 413 } 414 415 /** 416 * Gets the values of the parameters of the given population 417 * @param id The id of the population 418 * @return The values of the parameters 419 */ 420 @Callable 421 public Map<String, Object> getPopulationParameterValues(String id) 422 { 423 Map<String, Object> result = new LinkedHashMap<>(); 424 425 _readPopulations(false); 426 UserPopulation up = _userPopulations.get(id); 427 428 if (up == null) 429 { 430 getLogger().error("The UserPopulation of id '{}' does not exists.", id); 431 result.put("error", "unknown"); 432 return result; 433 } 434 435 // Population Label 436 result.put("label", up.getLabel()); 437 result.put("id", up.getId()); 438 439 // User Directories 440 List<Map<String, Object>> userDirectories = new ArrayList<>(); 441 442 for (UserDirectory ud : up.getUserDirectories()) 443 { 444 Map<String, Object> ud2json = new HashMap<>(); 445 String udModelId = ud.getUserDirectoryModelId(); 446 UserDirectoryModel model = _userDirectoryFactory.getExtension(udModelId); 447 ud2json.put("id", ud.getId()); 448 ud2json.put("udModelId", udModelId); 449 ud2json.put("label", ud.getLabel()); 450 Map<String, Object> params = new HashMap<>(); 451 for (String key : ud.getParameterValues().keySet()) 452 { 453 Parameter<ParameterType> parameter = model.getParameters().get(key); 454 params.put(udModelId + "$" + key, parameter.getType() == ParameterType.PASSWORD ? "PASSWORD" : ud.getParameterValues().get(key)); 455 } 456 ud2json.put("params", params); 457 userDirectories.add(ud2json); 458 } 459 result.put("userDirectories", userDirectories); 460 461 // Credential Providers 462 List<Map<String, Object>> credentialProviders = new ArrayList<>(); 463 464 for (CredentialProvider cp : up.getCredentialProviders()) 465 { 466 Map<String, Object> cp2json = new HashMap<>(); 467 String cpModelId = cp.getCredentialProviderModelId(); 468 CredentialProviderModel model = _credentialProviderFactory.getExtension(cpModelId); 469 cp2json.put("id", cp.getId()); 470 cp2json.put("cpModelId", cpModelId); 471 cp2json.put("label", cp.getLabel()); 472 Map<String, Object> params = new HashMap<>(); 473 for (String key : cp.getParameterValues().keySet()) 474 { 475 Parameter<ParameterType> parameter = model.getParameters().get(key); 476 params.put(cpModelId + "$" + key, parameter.getType() == ParameterType.PASSWORD ? "PASSWORD" : cp.getParameterValues().get(key)); 477 } 478 cp2json.put("params", params); 479 credentialProviders.add(cp2json); 480 } 481 result.put("credentialProviders", credentialProviders); 482 483 return result; 484 } 485 486 /** 487 * Gets the "admin" population 488 * @return The "admin" population 489 */ 490 public synchronized UserPopulation getAdminPopulation() 491 { 492 if (_adminUserPopulation != null) 493 { 494 return _adminUserPopulation; 495 } 496 497 _adminUserPopulation = new UserPopulation(); 498 _adminUserPopulation.setId(ADMIN_POPULATION_ID); 499 500 Map<String, String> userDirectory1 = new HashMap<>(); 501 userDirectory1.put("udModelId", "org.ametys.plugins.core.user.directory.Static"); 502 userDirectory1.put("id", "static"); 503 userDirectory1.put("org.ametys.plugins.core.user.directory.Static$runtime.users.static.users", SYSTEM_USER_LOGIN + ":System:User:"); 504 505 Map<String, String> userDirectory2 = new HashMap<>(); 506 userDirectory2.put("udModelId", "org.ametys.plugins.core.user.directory.Jdbc"); 507 userDirectory2.put("org.ametys.plugins.core.user.directory.Jdbc$runtime.users.jdbc.datasource", SQLDataSourceManager.AMETYS_INTERNAL_DATASOURCE_ID); 508 userDirectory2.put("org.ametys.plugins.core.user.directory.Jdbc$runtime.users.jdbc.table", __ADMIN_TABLENAME); 509 510 Map<String, String> credentialProvider = new HashMap<>(); 511 String cpModelId = "org.ametys.core.authentication.FormBased"; 512 credentialProvider.put("cpModelId", cpModelId); 513 credentialProvider.put(cpModelId + "$" + "runtime.authentication.form.security.level", "high"); 514 credentialProvider.put(cpModelId + "$" + "runtime.authentication.form.security.storage", SQLDataSourceManager.AMETYS_INTERNAL_DATASOURCE_ID); 515 516 // We may need to create the admin user 517 boolean wasExisting = false; 518 try (Connection connection = ConnectionHelper.getInternalSQLDataSourceConnection()) 519 { 520 wasExisting = SQLScriptHelper.tableExists(connection, __ADMIN_TABLENAME); 521 } 522 catch (Exception e) 523 { 524 throw new RuntimeException("Cannot test if " + __ADMIN_TABLENAME + " table exists in internal database", e); 525 } 526 527 _fillUserPopulation(_adminUserPopulation, new I18nizableText("plugin.core", "PLUGINS_CORE_USER_POPULATION_ADMIN_LABEL"), Arrays.asList(userDirectory1, userDirectory2), Collections.singletonList(credentialProvider)); 528 529 ((StaticUserDirectory) _adminUserPopulation.getUserDirectory("static")).setGrantAllCredentials(false); 530 531 if (!wasExisting) 532 { 533 Map<String, String> adminUserInformations = new HashMap<>(); 534 adminUserInformations.put("login", "admin"); 535 adminUserInformations.put("password", "admin"); 536 adminUserInformations.put("firstname", "User"); 537 adminUserInformations.put("lastname", "Administrator"); 538 adminUserInformations.put("email", ""); 539 540 ModifiableUserDirectory adminJdbcUserDirectoy = (ModifiableUserDirectory) _adminUserPopulation.getUserDirectories().get(1); 541 try 542 { 543 adminJdbcUserDirectoy.add(adminUserInformations); 544 } 545 catch (InvalidModificationException e) 546 { 547 throw new RuntimeException("Cannot create the 'admin' user", e); 548 } 549 } 550 551 return _adminUserPopulation; 552 } 553 554 /** 555 * Adds a new population 556 * @param id The unique id of the population 557 * @param label The label of the population 558 * @param userDirectories A list of user directory parameters 559 * @param credentialProviders A list of credential provider parameters 560 * @return A map containing the id of the created population, or the kind of error that occured 561 */ 562 @Callable 563 public Map<String, Object> add(String id, String label, List<Map<String, String>> userDirectories, List<Map<String, String>> credentialProviders) 564 { 565 _readPopulations(false); 566 567 Map<String, Object> result = new LinkedHashMap<>(); 568 569 if (!_isCorrectId(id)) 570 { 571 return null; 572 } 573 574 UserPopulation up = new UserPopulation(); 575 up.setId(id); 576 577 _fillUserPopulation(up, new I18nizableText(label), userDirectories, credentialProviders); 578 579 _userPopulations.put(id, up); 580 if (_writePopulations()) 581 { 582 getLogger().error("An error occured when writing the configuration file which contains the user populations.", id); 583 result.put("error", "server"); 584 return result; 585 } 586 587 if (_observationManager != null) 588 { 589 Map<String, Object> eventParams = new HashMap<>(); 590 eventParams.put(ObservationConstants.ARGS_USERPOPULATION_ID, id); 591 _observationManager.notify(new Event(ObservationConstants.EVENT_USERPOPULATION_ADDED, _currentUserProvider.getUser(), eventParams)); 592 } 593 594 result.put("id", id); 595 return result; 596 } 597 598 private boolean _isCorrectId(String id) 599 { 600 if (_userPopulations.get(id) != null || ADMIN_POPULATION_ID.equals(id)) 601 { 602 getLogger().error("The id '{}' is already used for a population.", id); 603 return false; 604 } 605 606 if (!Pattern.matches(__ID_REGEX, id)) 607 { 608 getLogger().error("The id '{}' is not a correct id for a user population.", id); 609 return false; 610 } 611 612 return true; 613 } 614 615 /** 616 * Edits the given population. 617 * @param id The id of the population to edit 618 * @param label The label of the population 619 * @param userDirectories A list of user directory parameters 620 * @param credentialProviders A list of credential provider parameters 621 * @return A map containing the id of the edited population, or the kind of error that occured 622 */ 623 @Callable 624 public Map<String, Object> edit(String id, String label, List<Map<String, String>> userDirectories, List<Map<String, String>> credentialProviders) 625 { 626 _readPopulations(false); 627 628 Map<String, Object> result = new LinkedHashMap<>(); 629 630 UserPopulation up = _userPopulations.get(id); 631 if (up == null) 632 { 633 getLogger().error("The UserPopulation with id '{}' does not exist, it cannot be edited.", id); 634 result.put("error", "unknown"); 635 return result; 636 } 637 638 up.dispose(); 639 _fillUserPopulation(up, new I18nizableText(label), userDirectories, credentialProviders); 640 641 if (_writePopulations()) 642 { 643 getLogger().error("An error occured when writing the configuration file which contains the user populations.", id); 644 result.put("error", "server"); 645 return result; 646 } 647 648 if (_observationManager != null) 649 { 650 Map<String, Object> eventParams = new HashMap<>(); 651 eventParams.put(ObservationConstants.ARGS_USERPOPULATION_ID, id); 652 _observationManager.notify(new Event(ObservationConstants.EVENT_USERPOPULATION_UPDATED, _currentUserProvider.getUser(), eventParams)); 653 } 654 655 result.put("id", id); 656 return result; 657 } 658 659 private void _fillUserPopulation(UserPopulation up, I18nizableText label, List<Map<String, String>> userDirectories, List<Map<String, String>> credentialProviders) 660 { 661 up.setLabel(label); 662 663 // Create the user directories 664 List<UserDirectory> uds = new ArrayList<>(); 665 for (Map<String, String> userDirectoryParameters : userDirectories) 666 { 667 String id = userDirectoryParameters.remove("id"); 668 String modelId = userDirectoryParameters.remove("udModelId"); 669 String additionnalLabel = userDirectoryParameters.remove("label"); 670 Map<String, Object> typedParamValues = _getTypedUDParameters(userDirectoryParameters, modelId); 671 672 if (StringUtils.isBlank(id)) 673 { 674 id = org.ametys.core.util.StringUtils.generateKey(); 675 } 676 else 677 { 678 _keepExistingUserDirectoryPassword(up, modelId, typedParamValues, id); 679 } 680 681 uds.add(_userDirectoryFactory.createUserDirectory(id, modelId, typedParamValues, up.getId(), additionnalLabel)); 682 } 683 up.setUserDirectories(uds); 684 685 // Create the credential providers 686 List<CredentialProvider> cps = new ArrayList<>(); 687 for (Map<String, String> credentialProviderParameters : credentialProviders) 688 { 689 String id = credentialProviderParameters.remove("id"); 690 String modelId = credentialProviderParameters.remove("cpModelId"); 691 String additionnalLabel = credentialProviderParameters.remove("label"); 692 Map<String, Object> typedParamValues = _getTypedCPParameters(credentialProviderParameters, modelId); 693 694 if (StringUtils.isBlank(id)) 695 { 696 id = org.ametys.core.util.StringUtils.generateKey(); 697 } 698 else 699 { 700 _keepExistingCredentialProviderPassword(up, modelId, typedParamValues, id); 701 } 702 703 CredentialProvider credentialProvider = _credentialProviderFactory.createCredentialProvider(id, modelId, typedParamValues, additionnalLabel); 704 if (credentialProvider != null) 705 { 706 cps.add(credentialProvider); 707 } 708 } 709 up.setCredentialProviders(cps); 710 } 711 712 private void _keepExistingUserDirectoryPassword(UserPopulation up, String modelId, Map<String, Object> typedParamValues, String id) 713 { 714 // An existing id means an existing user directory 715 UserDirectory existingUserDirectory = up.getUserDirectory(id); 716 UserDirectoryModel userDirectoryModel = _userDirectoryFactory.getExtension(modelId); 717 if (StringUtils.equals(modelId, userDirectoryModel.getId())) 718 { 719 // The model did not changed, we want to kee the unmodified passwords 720 for (Map.Entry<String, ? extends Parameter<ParameterType>> parameterEntry : userDirectoryModel.getParameters().entrySet()) 721 { 722 Parameter<ParameterType> parameter = parameterEntry.getValue(); 723 // If the parameter is a password AND has no value 724 if (parameter.getType() == ParameterType.PASSWORD && typedParamValues.get(parameterEntry.getKey()) == null) 725 { 726 // Update the submitted data with the existing password 727 typedParamValues.put(parameterEntry.getKey(), existingUserDirectory.getParameterValues().get(parameterEntry.getKey())); 728 } 729 } 730 } 731 } 732 733 private void _keepExistingCredentialProviderPassword(UserPopulation up, String modelId, Map<String, Object> typedParamValues, String id) 734 { 735 // An existing id means an existing credential provider 736 CredentialProvider existingCredentialProvider = up.getCredentialProvider(id); 737 CredentialProviderModel credentialProviderModel = _credentialProviderFactory.getExtension(modelId); 738 if (StringUtils.equals(modelId, credentialProviderModel.getId())) 739 { 740 // The model did not changed, we want to kee the unmodified passwords 741 for (Map.Entry<String, ? extends Parameter<ParameterType>> parameterEntry : credentialProviderModel.getParameters().entrySet()) 742 { 743 Parameter<ParameterType> parameter = parameterEntry.getValue(); 744 // If the parameter is a password AND has no value 745 if (parameter.getType() == ParameterType.PASSWORD && typedParamValues.get(parameterEntry.getKey()) == null) 746 { 747 // Update the submitted data with the existing password 748 typedParamValues.put(parameterEntry.getKey(), existingCredentialProvider.getParameterValues().get(parameterEntry.getKey())); 749 } 750 } 751 } 752 } 753 754 private Map<String, Object> _getTypedUDParameters(Map<String, String> parameters, String modelId) 755 { 756 Map<String, Object> resultParameters = new LinkedHashMap<>(); 757 758 Map<String, ? extends Parameter<ParameterType>> declaredParameters = _userDirectoryFactory.getExtension(modelId).getParameters(); 759 for (String paramNameWithPrefix : parameters.keySet()) 760 { 761 String[] splitStr = paramNameWithPrefix.split("\\$", 2); 762 String prefix = splitStr[0]; 763 String paramName = splitStr[1]; 764 if (prefix.equals(modelId) && declaredParameters.containsKey(paramName)) 765 { 766 String originalValue = parameters.get(paramNameWithPrefix); 767 768 Parameter<ParameterType> parameter = declaredParameters.get(paramName); 769 ParameterType type = parameter.getType(); 770 771 Object typedValue = ParameterHelper.castValue(originalValue, type); 772 resultParameters.put(paramName, typedValue); 773 } 774 else if (prefix.equals(modelId)) 775 { 776 getLogger().warn("The parameter {} is not declared in extension {}. It will be ignored", paramName, modelId); 777 } 778 } 779 780 return resultParameters; 781 } 782 783 private Map<String, Object> _getTypedCPParameters(Map<String, String> parameters, String modelId) 784 { 785 Map<String, Object> resultParameters = new LinkedHashMap<>(); 786 787 Map<String, ? extends Parameter<ParameterType>> declaredParameters = _credentialProviderFactory.getExtension(modelId).getParameters(); 788 for (String paramNameWithPrefix : parameters.keySet()) 789 { 790 String[] splitStr = paramNameWithPrefix.split("\\$", 2); 791 String prefix = splitStr[0]; 792 String paramName = splitStr[1]; 793 if (prefix.equals(modelId) && declaredParameters.containsKey(paramName)) 794 { 795 String originalValue = parameters.get(paramNameWithPrefix); 796 797 Parameter<ParameterType> parameter = declaredParameters.get(paramName); 798 ParameterType type = parameter.getType(); 799 800 Object typedValue = ParameterHelper.castValue(originalValue, type); 801 resultParameters.put(paramName, typedValue); 802 } 803 else if (prefix.equals(modelId)) 804 { 805 getLogger().warn("The parameter {} is not declared in extension {}. It will be ignored", paramName, modelId); 806 } 807 } 808 809 return resultParameters; 810 } 811 812 /** 813 * Removes the given population. 814 * @param id The id of the population to remove 815 * @return A map containing the id of the removed population 816 */ 817 @Callable 818 public Map<String, Object> remove(String id) 819 { 820 return remove(id, false); 821 } 822 823 /** 824 * Removes the given population. 825 * @param id The id of the population to remove 826 * @param forceDeletion Delete the population even if it is still in use 827 * @return A map containing the id of the removed population 828 */ 829 public Map<String, Object> remove(String id, boolean forceDeletion) 830 { 831 Map<String, Object> result = new LinkedHashMap<>(); 832 833 // Check if the population is not the admin population 834 if (ADMIN_POPULATION_ID.equals("id")) 835 { 836 return null; 837 } 838 839 // Check if the population is used 840 if (!forceDeletion && _populationConsumerEP.isInUse(id)) 841 { 842 getLogger().error("The UserPopulation with id '{}' is used, it cannot be removed.", id); 843 result.put("error", "used"); 844 return result; 845 } 846 847 _readPopulations(false); 848 849 UserPopulation up = _userPopulations.remove(id); 850 851 if (up == null) 852 { 853 getLogger().error("The UserPopulation with id '{}' does not exist, it cannot be removed.", id); 854 result.put("error", "unknown"); 855 return result; 856 } 857 858 up.dispose(); 859 860 if (_writePopulations()) 861 { 862 result.put("error", "server"); 863 return result; 864 } 865 866 if (_observationManager != null) 867 { 868 Map<String, Object> eventParams = new HashMap<>(); 869 eventParams.put(ObservationConstants.ARGS_USERPOPULATION_ID, id); 870 _observationManager.notify(new Event(ObservationConstants.EVENT_USERPOPULATION_DELETED, _currentUserProvider.getUser(), eventParams)); 871 } 872 873 result.put("id", id); 874 return result; 875 } 876 877 /** 878 * Enables/Disables the given population 879 * @param populationId The id of the population to enable/disable 880 * @param enabled True to enable the population, false to disable it. 881 * @return A map containing the id of the enabled/disabled population, or with an error. 882 */ 883 @Callable 884 public Map<String, Object> enable(String populationId, boolean enabled) 885 { 886 Map<String, Object> result = new LinkedHashMap<>(); 887 888 UserPopulation population = getUserPopulation(populationId); 889 if (population != null) 890 { 891 population.enable(enabled); 892 result.put("id", populationId); 893 } 894 else 895 { 896 getLogger().error("The UserPopulation with id '{}' does not exist, it cannot be enabled/disabled.", populationId); 897 result.put("error", "unknown"); 898 } 899 900 if (_writePopulations()) 901 { 902 result.put("error", "server"); 903 return result; 904 } 905 906 return result; 907 } 908 909 /** 910 * Determines if a population has a valid configuration 911 * @param populationId The id of the population to retrieve state 912 * @return A map, with the response as a boolean, or an error. 913 */ 914 @Callable 915 public boolean isValid(String populationId) 916 { 917 return !_misconfiguredUserPopulations.contains(populationId); 918 } 919 920 /** 921 * Returns the enabled state of the given population 922 * @param populationId The id of the population to retrieve state 923 * @return A map, with the response as a booolean, or an error. 924 */ 925 @Callable 926 public Map<String, Object> isEnabled(String populationId) 927 { 928 Map<String, Object> result = new LinkedHashMap<>(); 929 930 UserPopulation population = getUserPopulation(populationId); 931 if (population != null) 932 { 933 result.put("enabled", population.isEnabled()); 934 } 935 else 936 { 937 result.put("error", "unknown"); 938 } 939 940 return result; 941 } 942 943 /** 944 * Determines if a population can be removed 945 * @param populationId The id of the population 946 * @return A map, with the response as a boolean, or an error. 947 */ 948 @Callable 949 public Map<String, Object> canRemove(String populationId) 950 { 951 Map<String, Object> result = new HashMap<>(); 952 953 //Can remove only if not used, event if the population is disabled 954 result.put("canRemove", !_populationConsumerEP.isInUse(populationId)); 955 956 return result; 957 } 958 959 /** 960 * If needed, reads the config file representing the populations and then 961 * reinitializes and updates the internal representation of the populations. 962 * @param forceRead True to avoid the use of the cache and force the reading of the file 963 */ 964 private synchronized void _readPopulations(boolean forceRead) 965 { 966 try 967 { 968 if (!__USER_POPULATIONS_FILE.exists()) 969 { 970 if (getLogger().isDebugEnabled()) 971 { 972 getLogger().debug("No user population file found at {}. Creating a new one.", __USER_POPULATIONS_FILE.getAbsolutePath()); 973 } 974 _createPopulationsFile(__USER_POPULATIONS_FILE); 975 } 976 977 // In Linux file systems, the precision of java.io.File.lastModified() is the second, so we need here to always have 978 // this (bad!) precision by doing the truncation to second precision (/1000 * 1000) on the millis time value. 979 // Therefore, the boolean outdated is computed with '>=' operator, and not '>', which will lead to sometimes (but rarely) unnecessarily re-read the file. 980 long fileLastModified = (__USER_POPULATIONS_FILE.lastModified() / 1000) * 1000; 981 if (forceRead || fileLastModified >= _lastFileReading) 982 { 983 getLogger().debug("Reading user population file at {}", __USER_POPULATIONS_FILE.getAbsolutePath()); 984 985 long lastFileReading = Instant.now().truncatedTo(ChronoUnit.SECONDS).toEpochMilli(); 986 Map<String, UserPopulation> userPopulations = new LinkedHashMap<>(); 987 Set<String> ignoredPopulations = new HashSet<>(); 988 Set<String> misconfiguredUserPopulations = new HashSet<>(); 989 990 Configuration cfg = new DefaultConfigurationBuilder().buildFromFile(__USER_POPULATIONS_FILE); 991 for (Configuration childCfg : cfg.getChildren("userPopulation")) 992 { 993 try 994 { 995 UserPopulation up = _configurePopulation(childCfg, misconfiguredUserPopulations); 996 userPopulations.put(up.getId(), up); 997 } 998 catch (ConfigurationException e) 999 { 1000 getLogger().error("Fatal configuration error for population of id '{}'. The population will be ignored.", childCfg.getAttribute("id", ""), e); 1001 ignoredPopulations.add(childCfg.getAttribute("id", "")); 1002 } 1003 } 1004 1005 getLogger().debug("User population file read. Found {} valid population(s), {} invalid population(s) and {} misconfigured population(s)", userPopulations.size(), ignoredPopulations.size(), misconfiguredUserPopulations.size()); 1006 1007 // Release previous components 1008 this.dispose(); 1009 1010 _lastFileReading = lastFileReading; 1011 _userPopulations = userPopulations; 1012 _ignoredPopulations = ignoredPopulations; 1013 _misconfiguredUserPopulations = misconfiguredUserPopulations; 1014 } 1015 else if (getLogger().isDebugEnabled()) 1016 { 1017 getLogger().debug("No need to reload the user population file. The file modified at {} was not modified since {}", fileLastModified, _lastFileReading); 1018 } 1019 } 1020 catch (Exception e) 1021 { 1022 getLogger().error("Failed to retrieve user populations from the configuration file " + __USER_POPULATIONS_FILE, e); 1023 } 1024 } 1025 1026 private void _createPopulationsFile(File file) throws IOException, TransformerConfigurationException, SAXException 1027 { 1028 file.createNewFile(); 1029 try (OutputStream os = new FileOutputStream(file)) 1030 { 1031 // create a transformer for saving sax into a file 1032 TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); 1033 1034 StreamResult result = new StreamResult(os); 1035 th.setResult(result); 1036 1037 // create the format of result 1038 Properties format = new Properties(); 1039 format.put(OutputKeys.METHOD, "xml"); 1040 format.put(OutputKeys.INDENT, "yes"); 1041 format.put(OutputKeys.ENCODING, "UTF-8"); 1042 format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "4"); 1043 th.getTransformer().setOutputProperties(format); 1044 th.startDocument(); 1045 XMLUtils.createElement(th, "userPopulations"); 1046 th.endDocument(); 1047 } 1048 1049 if (getLogger().isDebugEnabled()) 1050 { 1051 getLogger().debug("Empty file for user populations created"); 1052 } 1053 } 1054 1055 private UserPopulation _configurePopulation(Configuration configuration, Set<String> misconfiguredUserPopulations) throws ConfigurationException 1056 { 1057 UserPopulation up = new UserPopulation(); 1058 1059 String upId = configuration.getAttribute("id"); 1060 up.setId(upId); 1061 up.setLabel(new I18nizableText(configuration.getChild("label").getValue())); 1062 1063 List<UserDirectory> userDirectories = _configureUserDirectories(configuration, upId, misconfiguredUserPopulations); 1064 up.setUserDirectories(userDirectories); 1065 1066 List<CredentialProvider> credentialProviders = _configureCredentialProviders(configuration, upId); 1067 up.setCredentialProviders(credentialProviders); 1068 1069 boolean enabled = configuration.getAttributeAsBoolean("enabled", true) && userDirectories.size() > 0 && credentialProviders.size() > 0; 1070 up.enable(enabled); 1071 1072 return up; 1073 } 1074 1075 private List<UserDirectory> _configureUserDirectories(Configuration configuration, String upId, Set<String> misconfiguredUserPopulations) throws ConfigurationException 1076 { 1077 List<UserDirectory> userDirectories = new ArrayList<>(); 1078 1079 Configuration[] userDirectoriesConf = configuration.getChild("userDirectories").getChildren("userDirectory"); 1080 for (Configuration userDirectoryConf : userDirectoriesConf) 1081 { 1082 String id = userDirectoryConf.getAttribute("id"); 1083 String modelId = userDirectoryConf.getAttribute("modelId"); 1084 String label = userDirectoryConf.getAttribute("label", null); 1085 1086 try 1087 { 1088 Map<String, Object> paramValues = _getUDParametersFromConfiguration(userDirectoryConf, modelId, upId); 1089 UserDirectory ud = _userDirectoryFactory.createUserDirectory(id, modelId, paramValues, upId, label); 1090 if (ud != null) 1091 { 1092 userDirectories.add(ud); 1093 } 1094 } 1095 catch (Exception e) 1096 { 1097 getLogger().warn("The population of id '" + upId + "' declares a user directory with an invalid configuration", e); 1098 misconfiguredUserPopulations.add(upId); 1099 } 1100 } 1101 1102 if (userDirectories.isEmpty()) 1103 { 1104 misconfiguredUserPopulations.add(upId); 1105 getLogger().warn("The population of id '" + upId + "' does not have user directory with a valid configuration. It will be disabled until it will be fixed."); 1106 } 1107 1108 return userDirectories; 1109 } 1110 1111 private List<CredentialProvider> _configureCredentialProviders(Configuration configuration, String upId) throws ConfigurationException 1112 { 1113 List<CredentialProvider> credentialProviders = new ArrayList<>(); 1114 1115 Configuration[] credentialProvidersConf = configuration.getChild("credentialProviders").getChildren("credentialProvider"); 1116 for (Configuration credentialProviderConf : credentialProvidersConf) 1117 { 1118 String id = credentialProviderConf.getAttribute("id"); 1119 String modelId = credentialProviderConf.getAttribute("modelId"); 1120 String additionnalLabel = credentialProviderConf.getAttribute("label", null); 1121 1122 try 1123 { 1124 Map<String, Object> paramValues = _getCPParametersFromConfiguration(credentialProviderConf, modelId, upId); 1125 CredentialProvider cp = _credentialProviderFactory.createCredentialProvider(id, modelId, paramValues, additionnalLabel); 1126 if (cp != null) 1127 { 1128 credentialProviders.add(cp); 1129 } 1130 } 1131 catch (Exception e) 1132 { 1133 getLogger().warn("The population of id '" + upId + "' declares a credential provider with an invalid configuration", e); 1134 _misconfiguredUserPopulations.add(upId); 1135 } 1136 } 1137 1138 if (credentialProviders.isEmpty()) 1139 { 1140 _misconfiguredUserPopulations.add(upId); 1141 getLogger().warn("The population of id '" + upId + "' does not have credential provider with a valid configuration. It will be disabled until it will be fixed."); 1142 } 1143 1144 return credentialProviders; 1145 } 1146 1147 /** 1148 * Get the typed parameters of a user directory used by a population 1149 * @param conf The user directory's configuration. 1150 * @param modelId The id of user directory model 1151 * @param populationId The id of population 1152 * @return The typed parameters 1153 * @throws IllegalArgumentException if the configured user directory references a non-existing user directory model 1154 * @throws ConfigurationException if a parameter is missing 1155 */ 1156 private Map<String, Object> _getUDParametersFromConfiguration(Configuration conf, String modelId, String populationId) throws ConfigurationException, IllegalArgumentException 1157 { 1158 Map<String, Object> parameters = new LinkedHashMap<>(); 1159 1160 if (!_userDirectoryFactory.hasExtension(modelId)) 1161 { 1162 throw new IllegalArgumentException(String.format("The population of id '%s' declares a non-existing user directory model with id '%s'. It will be ignored.", populationId, modelId)); 1163 } 1164 1165 Map<String, ? extends Parameter<ParameterType>> declaredParameters = _userDirectoryFactory.getExtension(modelId).getParameters(); 1166 1167 for (String udParamName : declaredParameters.keySet()) 1168 { 1169 Configuration paramConf = conf.getChild(udParamName, false); 1170 if (paramConf == null) 1171 { 1172 throw new ConfigurationException(String.format("The population of id '%s' declares a user directory model with id '%s' but the parameter '%s' is missing. This user directory will be ignored.", populationId, modelId, udParamName)); 1173 } 1174 else 1175 { 1176 String valueAsString = paramConf.getValue(""); 1177 1178 Parameter<ParameterType> parameter = declaredParameters.get(udParamName); 1179 ParameterType type = parameter.getType(); 1180 1181 Object typedValue = ParameterHelper.castValue(valueAsString, type); 1182 parameters.put(udParamName, typedValue); 1183 } 1184 } 1185 1186 return parameters; 1187 } 1188 1189 /** 1190 * Get the typed parameters of a credential provider used by a population 1191 * @param conf The credential provider's configuration. 1192 * @param modelId The id of credential provider model 1193 * @param populationId The id of population 1194 * @return The typed parameters 1195 * @throws IllegalArgumentException if the configured credential provider references a non-existing credential provider model 1196 * @throws ConfigurationException if a parameter is missing 1197 */ 1198 private Map<String, Object> _getCPParametersFromConfiguration(Configuration conf, String modelId, String populationId) throws ConfigurationException, IllegalArgumentException 1199 { 1200 Map<String, Object> parameters = new LinkedHashMap<>(); 1201 1202 if (!_credentialProviderFactory.hasExtension(modelId)) 1203 { 1204 throw new IllegalArgumentException(String.format("The population of id '%s' declares a non-existing credential provider model with id '%s'. It will be ignored.", populationId, modelId)); 1205 } 1206 1207 Map<String, ? extends Parameter<ParameterType>> declaredParameters = _credentialProviderFactory.getExtension(modelId).getParameters(); 1208 1209 for (String cpParamName : declaredParameters.keySet()) 1210 { 1211 Configuration paramConf = conf.getChild(cpParamName, false); 1212 if (paramConf == null) 1213 { 1214 throw new ConfigurationException(String.format("The population of id '%s' declares a credential provider model with id '%s' but the parameter '%s' is missing. This credential provider will be ignored.", populationId, modelId, cpParamName)); 1215 } 1216 else 1217 { 1218 String valueAsString = paramConf.getValue(""); 1219 1220 Parameter<ParameterType> parameter = declaredParameters.get(cpParamName); 1221 ParameterType type = parameter.getType(); 1222 1223 Object typedValue = ParameterHelper.castValue(valueAsString, type); 1224 parameters.put(cpParamName, typedValue); 1225 1226 } 1227 } 1228 1229 return parameters; 1230 } 1231 1232 /** 1233 * Erases the config file representing the populations and rebuild it 1234 * from the internal representation of the populations. 1235 * @return True if an error occured 1236 */ 1237 private boolean _writePopulations() 1238 { 1239 File backup = new File(__USER_POPULATIONS_FILE.getPath() + ".tmp"); 1240 boolean errorOccured = false; 1241 1242 // Create a backup file 1243 try 1244 { 1245 Files.copy(__USER_POPULATIONS_FILE.toPath(), backup.toPath()); 1246 } 1247 catch (IOException e) 1248 { 1249 if (getLogger().isErrorEnabled()) 1250 { 1251 getLogger().error("Error when creating backup '" + __USER_POPULATIONS_FILE + "' file", e); 1252 } 1253 } 1254 1255 // Do writing 1256 try (OutputStream os = new FileOutputStream(__USER_POPULATIONS_FILE)) 1257 { 1258 // create a transformer for saving sax into a file 1259 TransformerHandler th = ((SAXTransformerFactory) TransformerFactory.newInstance()).newTransformerHandler(); 1260 1261 StreamResult result = new StreamResult(os); 1262 th.setResult(result); 1263 1264 // create the format of result 1265 Properties format = new Properties(); 1266 format.put(OutputKeys.METHOD, "xml"); 1267 format.put(OutputKeys.INDENT, "yes"); 1268 format.put(OutputKeys.ENCODING, "UTF-8"); 1269 format.put(OutputPropertiesFactory.S_KEY_INDENT_AMOUNT, "4"); 1270 th.getTransformer().setOutputProperties(format); 1271 1272 // sax the config 1273 try 1274 { 1275 _toSAX(th); 1276 } 1277 catch (Exception e) 1278 { 1279 if (getLogger().isErrorEnabled()) 1280 { 1281 getLogger().error("Error when saxing the userPopulations", e); 1282 } 1283 errorOccured = true; 1284 } 1285 } 1286 catch (IOException | TransformerConfigurationException | TransformerFactoryConfigurationError e) 1287 { 1288 if (getLogger().isErrorEnabled()) 1289 { 1290 getLogger().error("Error when trying to modify the user populations with the configuration file " + __USER_POPULATIONS_FILE, e); 1291 } 1292 } 1293 1294 // Restore the file if an error previously occured 1295 try 1296 { 1297 if (errorOccured) 1298 { 1299 // An error occured, restore the original 1300 Files.copy(backup.toPath(), __USER_POPULATIONS_FILE.toPath(), StandardCopyOption.REPLACE_EXISTING); 1301 // Force to reread the file 1302 _readPopulations(true); 1303 } 1304 Files.deleteIfExists(backup.toPath()); 1305 } 1306 catch (IOException e) 1307 { 1308 if (getLogger().isErrorEnabled()) 1309 { 1310 getLogger().error("Error when restoring backup '" + __USER_POPULATIONS_FILE + "' file", e); 1311 } 1312 } 1313 1314 return errorOccured; 1315 } 1316 1317 private void _toSAX(TransformerHandler handler) 1318 { 1319 try 1320 { 1321 handler.startDocument(); 1322 XMLUtils.startElement(handler, "userPopulations"); 1323 for (UserPopulation up : _userPopulations.values()) 1324 { 1325 _saxUserPopulation(up, handler); 1326 } 1327 1328 XMLUtils.endElement(handler, "userPopulations"); 1329 handler.endDocument(); 1330 } 1331 catch (SAXException e) 1332 { 1333 getLogger().error("Error when saxing the userPopulations", e); 1334 } 1335 } 1336 1337 private void _saxUserPopulation(UserPopulation userPopulation, TransformerHandler handler) 1338 { 1339 try 1340 { 1341 AttributesImpl atts = new AttributesImpl(); 1342 atts.addCDATAAttribute("id", userPopulation.getId()); 1343 atts.addCDATAAttribute("enabled", Boolean.toString(userPopulation.isEnabled())); 1344 XMLUtils.startElement(handler, "userPopulation", atts); 1345 1346 userPopulation.getLabel().toSAX(handler, "label"); 1347 1348 // SAX user directories 1349 XMLUtils.startElement(handler, "userDirectories"); 1350 for (UserDirectory ud : userPopulation.getUserDirectories()) 1351 { 1352 AttributesImpl attr = new AttributesImpl(); 1353 attr.addCDATAAttribute("id", ud.getId()); 1354 attr.addCDATAAttribute("modelId", ud.getUserDirectoryModelId()); 1355 attr.addCDATAAttribute("label", ud.getLabel() != null ? ud.getLabel() : ""); 1356 XMLUtils.startElement(handler, "userDirectory", attr); 1357 1358 Map<String, Object> paramValues = ud.getParameterValues(); 1359 for (String paramName : paramValues.keySet()) 1360 { 1361 Object value = paramValues.get(paramName); 1362 XMLUtils.createElement(handler, paramName, ParameterHelper.valueToString(value)); 1363 } 1364 XMLUtils.endElement(handler, "userDirectory"); 1365 } 1366 XMLUtils.endElement(handler, "userDirectories"); 1367 1368 // SAX credential providers 1369 XMLUtils.startElement(handler, "credentialProviders"); 1370 for (CredentialProvider cp : userPopulation.getCredentialProviders()) 1371 { 1372 AttributesImpl attr = new AttributesImpl(); 1373 attr.addCDATAAttribute("id", cp.getId()); 1374 attr.addCDATAAttribute("modelId", cp.getCredentialProviderModelId()); 1375 attr.addCDATAAttribute("label", cp.getLabel() != null ? cp.getLabel() : ""); 1376 XMLUtils.startElement(handler, "credentialProvider", attr); 1377 1378 Map<String, Object> paramValues = cp.getParameterValues(); 1379 for (String paramName : paramValues.keySet()) 1380 { 1381 Object value = paramValues.get(paramName); 1382 XMLUtils.createElement(handler, paramName, ParameterHelper.valueToString(value)); 1383 } 1384 XMLUtils.endElement(handler, "credentialProvider"); 1385 } 1386 XMLUtils.endElement(handler, "credentialProviders"); 1387 1388 XMLUtils.endElement(handler, "userPopulation"); 1389 } 1390 catch (SAXException e) 1391 { 1392 if (getLogger().isErrorEnabled()) 1393 { 1394 getLogger().error("Error when saxing the userPopulation " + userPopulation, e); 1395 } 1396 } 1397 } 1398 1399 @Override 1400 public void dispose() 1401 { 1402 for (UserPopulation up : _userPopulations.values()) 1403 { 1404 up.dispose(); 1405 } 1406 1407 _lastFileReading = 0; 1408 } 1409}