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