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