001/* 002 * Copyright 2010 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.plugins.site; 017 018import java.io.ByteArrayInputStream; 019import java.io.ByteArrayOutputStream; 020import java.io.File; 021import java.util.ArrayList; 022import java.util.Collection; 023import java.util.Collections; 024import java.util.Comparator; 025import java.util.HashMap; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Map; 029import java.util.Set; 030import java.util.TreeMap; 031import java.util.stream.Collectors; 032 033import org.apache.avalon.framework.component.Component; 034import org.apache.avalon.framework.configuration.Configuration; 035import org.apache.avalon.framework.configuration.ConfigurationException; 036import org.apache.avalon.framework.configuration.DefaultConfigurationBuilder; 037import org.apache.avalon.framework.service.ServiceException; 038import org.apache.avalon.framework.service.ServiceManager; 039import org.apache.avalon.framework.service.Serviceable; 040import org.apache.commons.lang.StringUtils; 041import org.apache.http.client.methods.CloseableHttpResponse; 042import org.apache.http.client.methods.HttpGet; 043import org.apache.http.impl.client.CloseableHttpClient; 044 045import org.ametys.core.authentication.CredentialProvider; 046import org.ametys.core.authentication.CredentialProviderFactory; 047import org.ametys.core.authentication.CredentialProviderModel; 048import org.ametys.core.datasource.AbstractDataSourceManager.DataSourceDefinition; 049import org.ametys.core.datasource.DataSourceConsumerExtensionPoint; 050import org.ametys.core.datasource.LDAPDataSourceManager; 051import org.ametys.core.datasource.SQLDataSourceManager; 052import org.ametys.core.user.directory.UserDirectory; 053import org.ametys.core.user.directory.UserDirectoryFactory; 054import org.ametys.core.user.directory.UserDirectoryModel; 055import org.ametys.core.user.population.UserPopulation; 056import org.ametys.core.user.population.UserPopulationDAO; 057import org.ametys.runtime.config.Config; 058import org.ametys.runtime.config.ConfigManager; 059import org.ametys.runtime.i18n.I18nizableText; 060import org.ametys.runtime.parameter.Parameter; 061import org.ametys.runtime.parameter.ParameterHelper.ParameterType; 062import org.ametys.runtime.plugin.component.AbstractLogEnabled; 063import org.ametys.runtime.servlet.RuntimeServlet; 064import org.ametys.runtime.util.AmetysHomeHelper; 065import org.ametys.site.BackOfficeRequestHelper; 066 067/** 068 * A cache for site information provided by the Back-Office. 069 */ 070public class SiteInformationCache extends AbstractLogEnabled implements Serviceable, Component 071{ 072 /** Avalon Role */ 073 public static final String ROLE = SiteInformationCache.class.getName(); 074 075 /** Prefix for backoffice synchronized userpopulations */ 076 public static final String BACKOFFICE_PREFIX_IDENTIFIER = "bo-"; 077 078 private Map<SiteUrl, Site> _sites; 079 080 private LDAPDataSourceManager _ldapDataSourceManager; 081 private SQLDataSourceManager _sqlDataSourceManager; 082 private UserPopulationDAO _userPopulationDAO; 083 private UserDirectoryFactory _userDirectoryFactory; 084 private CredentialProviderFactory _credentialProviderFactory; 085 private DataSourceConsumerExtensionPoint _dataSourceConsumerEP; 086 087 public void service(ServiceManager manager) throws ServiceException 088 { 089 _sqlDataSourceManager = (SQLDataSourceManager) manager.lookup(SQLDataSourceManager.ROLE); 090 _ldapDataSourceManager = (LDAPDataSourceManager) manager.lookup(LDAPDataSourceManager.ROLE); 091 _dataSourceConsumerEP = (DataSourceConsumerExtensionPoint) manager.lookup(DataSourceConsumerExtensionPoint.ROLE); 092 _userPopulationDAO = (UserPopulationDAO) manager.lookup(UserPopulationDAO.ROLE); 093 _userDirectoryFactory = (UserDirectoryFactory) manager.lookup(UserDirectoryFactory.ROLE); 094 _credentialProviderFactory = (CredentialProviderFactory) manager.lookup(CredentialProviderFactory.ROLE); 095 } 096 097 /** 098 * Clear cached informations. 099 */ 100 public void resetSitesCache() 101 { 102 _sites = null; 103 } 104 105 /** 106 * Returns the cached informations. 107 * @return the cached informations. 108 */ 109 public Map<SiteUrl, Site> getSites() 110 { 111 _synchronize(); 112 return _sites; 113 } 114 115 private void _synchronize() 116 { 117 if (_sites == null) 118 { 119 try 120 { 121 _synchronizeSites(); 122 _synchronizePopulationsAndDatasources(); 123 } 124 catch (Exception e) 125 { 126 throw new RuntimeException("Unable to synchronize sites data", e); 127 } 128 } 129 } 130 131 private void _synchronizeSites() throws ConfigurationException 132 { 133 Collection<Site> sites = new ArrayList<>(); 134 135 Configuration boConfiguration = _getBackofficeConfiguration("/_sites.xml"); 136 _configureSites (boConfiguration, sites); 137 138 _sites = new TreeMap<>(new SiteUrlComparator()); 139 140 for (Site site : sites) 141 { 142 for (SiteUrl url : site.getSiteUrls()) 143 { 144 _sites.put(url, site); 145 } 146 } 147 } 148 149 private void _configureSites (Configuration conf, Collection<Site> sites) throws ConfigurationException 150 { 151 Configuration[] sitesConf = conf.getChildren("site"); 152 for (Configuration siteConf : sitesConf) 153 { 154 String name = siteConf.getAttribute("name"); 155 156 List<String> populationIds = new ArrayList<>(); 157 for (Configuration populationConf : siteConf.getChild("populations").getChildren()) 158 { 159 populationIds.add(populationConf.getValue()); 160 } 161 162 List<SiteUrl> urls = new ArrayList<>(); 163 for (Configuration urlConf : siteConf.getChildren("url")) 164 { 165 String serverName = urlConf.getAttribute("serverName"); 166 String serverPort = urlConf.getAttribute("serverPort"); 167 String serverPath = urlConf.getAttribute("serverPath"); 168 169 urls.add(new SiteUrl(serverName, serverPort, serverPath)); 170 } 171 172 List<String> languages = new ArrayList<>(); 173 for (Configuration langConf : siteConf.getChild("languages").getChildren()) 174 { 175 languages.add(langConf.getName()); 176 } 177 178 sites.add(new Site(name, urls, languages, populationIds)); 179 180 // Sub sites 181 _configureSites (siteConf, sites); 182 } 183 } 184 185 class SiteUrlComparator implements Comparator<SiteUrl> 186 { 187 @Override 188 public int compare(SiteUrl url1, SiteUrl url2) 189 { 190 int result = url2.getServerName().compareTo(url1.getServerName()); 191 192 if (result != 0) 193 { 194 return result; 195 } 196 197 result = url2.getServerPort().compareTo(url1.getServerPort()); 198 199 if (result != 0) 200 { 201 return result; 202 } 203 204 return url2.getServerPath().compareTo(url1.getServerPath()); 205 } 206 } 207 208 private Configuration _getBackofficeConfiguration(String url) 209 { 210 // Get site names and URLs from the CMS 211 String cmsURL = Config.getInstance().getValueAsString("org.ametys.site.bo"); 212 213 try (CloseableHttpClient httpClient = BackOfficeRequestHelper.getHttpClient()) 214 { 215 HttpGet httpGet = new HttpGet(cmsURL + url); 216 httpGet.addHeader("X-Ametys-FO", "true"); 217 218 try (CloseableHttpResponse response = httpClient.execute(httpGet); ByteArrayOutputStream os = new ByteArrayOutputStream()) 219 { 220 switch (response.getStatusLine().getStatusCode()) 221 { 222 case 200: 223 break; 224 225 case 403: 226 throw new IllegalStateException("The CMS back-office refused the connection"); 227 228 case 500: 229 default: 230 throw new IllegalStateException("The CMS back-office returned an error"); 231 } 232 233 response.getEntity().writeTo(os); 234 try (ByteArrayInputStream is = new ByteArrayInputStream(os.toByteArray())) 235 { 236 Configuration conf = new DefaultConfigurationBuilder().build(is); 237 return conf; 238 } 239 } 240 } 241 catch (Exception e) 242 { 243 throw new RuntimeException("Unable to synchronize site data", e); 244 } 245 } 246 247 /** 248 * Synchronize the local user populations and datasources with backoffice 249 */ 250 private void _synchronizePopulationsAndDatasources() 251 { 252 Configuration boConfiguration = _getBackofficeConfiguration("/_sites-populations.xml"); 253 254 _removeBackOfficeSynchronizedElements(); 255 256 _addBackOfficeSynchronizedElements(boConfiguration); 257 } 258 259 private void _synchronizeMonitoringAndCaptcha(Configuration boConfiguration, Map<String, String> mapping) 260 { 261 // MONITORING 262 Configuration monitoringConfiguration = boConfiguration.getChild("Monitoring"); 263 boolean enabled = monitoringConfiguration.getAttributeAsBoolean("enabled", false); 264 boolean wasEnabled = Config.getInstance().getValueAsBoolean("front.cache.monitoring.schedulers.enable"); 265 String datasourceId = monitoringConfiguration.getChild("Datasource").getValue(""); 266 267 Map<String, String> valuesToChange = new HashMap<>(); 268 if (enabled != wasEnabled) 269 { 270 valuesToChange.put("front.cache.monitoring.schedulers.enable", enabled ? "true" : "false"); 271 } 272 if (wasEnabled) 273 { 274 String oldDatasourceId = Config.getInstance().getValueAsString("front.cache.monitoring.datasource.jdbc.pool"); 275 if (!_dataSourceConsumerEP.isInUse(oldDatasourceId)) 276 { 277 _sqlDataSourceManager.delete(Collections.singletonList(oldDatasourceId), true); 278 } 279 } 280 if (enabled && !StringUtils.equals(datasourceId, Config.getInstance().getValueAsString("front.cache.monitoring.datasource.jdbc.pool"))) 281 { 282 valuesToChange.put("front.cache.monitoring.datasource.jdbc.pool", mapping.get(datasourceId)); 283 } 284 285 // CAPTCHA 286 Configuration captchaConfiguration = boConfiguration.getChild("Captcha"); 287 String captchaType = captchaConfiguration.getAttribute("type", null); 288 String captchaPublicKey = captchaConfiguration.getAttribute("publicKey", null); 289 String captchaSecretKey = captchaConfiguration.getAttribute("secretKey", null); 290 291 if (!StringUtils.equals(captchaType, Config.getInstance().getValueAsString("runtime.captcha.type")) 292 || !StringUtils.equals(captchaPublicKey, Config.getInstance().getValueAsString("runtime.captcha.recaptcha.publickey")) 293 || !!StringUtils.equals(captchaSecretKey, Config.getInstance().getValueAsString("runtime.captcha.recaptcha.secretkey"))) 294 { 295 valuesToChange.put("runtime.captcha.type", captchaType); 296 valuesToChange.put("runtime.captcha.recaptcha.publickey", captchaPublicKey); 297 valuesToChange.put("runtime.captcha.recaptcha.secretkey", captchaSecretKey); 298 } 299 300 // UPDATE CONFIG 301 if (!valuesToChange.isEmpty()) 302 { 303 try 304 { 305 Map<String, String> existingValues = Config.read(); 306 existingValues.putAll(valuesToChange); 307 ConfigManager.getInstance().save(existingValues, new File(AmetysHomeHelper.getAmetysHomeConfig(), RuntimeServlet.CONFIG_FILE_NAME).getCanonicalPath()); 308 // Need a restart to be taken in account 309 } 310 catch (Exception e) 311 { 312 getLogger().error("The monitoring synchronization failed", e); 313 } 314 } 315 } 316 317 private void _addBackOfficeSynchronizedElements(Configuration configuration) 318 { 319 Map<String, String> mapping = _addBackofficeDatasources(configuration); 320 _addBackOfficeUserPopulations(configuration, mapping); 321 _synchronizeMonitoringAndCaptcha(configuration, mapping); 322 } 323 324 private Map<String, String> _addBackofficeDatasources(Configuration configuration) 325 { 326 Map<String, String> mapping = new HashMap<>(); 327 mapping.putAll(_addBackofficeSQLDatasources(configuration)); 328 mapping.putAll(_addBackofficeLDAPDatasources(configuration)); 329 return mapping; 330 } 331 332 private Map<String, String> _addBackofficeSQLDatasources(Configuration configuration) 333 { 334 Map<String, String> mapping = new HashMap<>(); 335 336 for (Configuration datasourceConfiguration : configuration.getChild("SQLDatasources").getChild("datasources").getChildren("datasource")) 337 { 338 String id = datasourceConfiguration.getAttribute("id", ""); 339 340 String defaultStatus = datasourceConfiguration.getAttribute("default", "false"); 341 String privateStatus = datasourceConfiguration.getAttribute("private", "false"); 342 String name = "DO NOT SELECT OR USE Back-office Synchronized Datasource (" + datasourceConfiguration.getChild("name").getValue("") + ")"; 343 String description = datasourceConfiguration.getChild("description").getValue(""); 344 345 Map<String, Object> parameters = new HashMap<>(); 346 for (Configuration parameterConfiguration : datasourceConfiguration.getChild("parameters").getChildren()) 347 { 348 // Assuming all parameters are String (that is what .add is doing) 349 parameters.put(parameterConfiguration.getName(), parameterConfiguration.getValue("")); 350 } 351 352 DataSourceDefinition newDatasource = _sqlDataSourceManager.add(new I18nizableText(name), new I18nizableText(description), parameters, "true".equals(privateStatus)); 353 mapping.put(id, newDatasource.getId()); 354 if ("true".equals(defaultStatus)) 355 { 356 mapping.put(_sqlDataSourceManager.getDefaultDataSourceId(), newDatasource.getId()); 357 } 358 } 359 360 return mapping; 361 } 362 363 private Map<String, String> _addBackofficeLDAPDatasources(Configuration configuration) 364 { 365 Map<String, String> mapping = new HashMap<>(); 366 367 for (Configuration datasourceConfiguration : configuration.getChild("LDAPDatasources").getChild("datasources").getChildren("datasource")) 368 { 369 String id = datasourceConfiguration.getAttribute("id", ""); 370 371 String defaultStatus = datasourceConfiguration.getAttribute("default", "false"); 372 String privateStatus = datasourceConfiguration.getAttribute("private", "false"); 373 String name = "DO NOT SELECT OR USE Back-office Synchronized Datasource (" + datasourceConfiguration.getChild("name").getValue("") + ")"; 374 String description = datasourceConfiguration.getChild("description").getValue(""); 375 376 Map<String, Object> parameters = new HashMap<>(); 377 for (Configuration parameterConfiguration : datasourceConfiguration.getChild("parameters").getChildren()) 378 { 379 // Assuming all parameters are String (that is what .add is doing) 380 parameters.put(parameterConfiguration.getName(), parameterConfiguration.getValue("")); 381 } 382 383 DataSourceDefinition newDatasource = _ldapDataSourceManager.add(new I18nizableText(name), new I18nizableText(description), parameters, "true".equals(privateStatus)); 384 mapping.put(id, newDatasource.getId()); 385 if ("true".equals(defaultStatus)) 386 { 387 mapping.put(_ldapDataSourceManager.getDefaultDataSourceId(), newDatasource.getId()); 388 } 389 } 390 391 return mapping; 392 } 393 394 private void _addBackOfficeUserPopulations(Configuration configuration, Map<String, String> mapping) 395 { 396 for (Configuration userPopulationConfiguration : configuration.getChild("UserPopulations").getChild("userPopulations").getChildren("userPopulation")) 397 { 398 String id = userPopulationConfiguration.getAttribute("id", ""); 399 String enabled = userPopulationConfiguration.getAttribute("enabled", "true"); 400 if ("true".equals(enabled)) 401 { 402 String label = userPopulationConfiguration.getChild("label").getValue(""); 403 404 // USER DIRECTORIES 405 List<Map<String, String>> userDirectories = new ArrayList<>(); 406 for (Configuration userDirectoryConfiguration : userPopulationConfiguration.getChild("userDirectories").getChildren("userDirectory")) 407 { 408 Map<String, String> userDirectory = new HashMap<>(); 409 userDirectories.add(userDirectory); 410 411 String udid = userDirectoryConfiguration.getAttribute("id", ""); 412 userDirectory.put("id", udid); 413 414 String modelId = userDirectoryConfiguration.getAttribute("modelId", ""); 415 userDirectory.put("udModelId", modelId); 416 417 String additionalLabel = userDirectoryConfiguration.getAttribute("label", ""); 418 userDirectory.put("label", additionalLabel); 419 420 UserDirectoryModel userDirectoryModel = _userDirectoryFactory.getExtension(modelId); 421 422 Map<String, ? extends Parameter<ParameterType>> parameters = userDirectoryModel.getParameters(); 423 for (String parameterId : parameters.keySet()) 424 { 425 Parameter<ParameterType> parameter = parameters.get(parameterId); 426 427 String value = userDirectoryConfiguration.getChild(parameterId).getValue(""); 428 429 if (parameter.getType() == ParameterType.DATASOURCE) 430 { 431 value = mapping.get(value); 432 } 433 434 userDirectory.put(modelId + "$" + parameterId, value); 435 } 436 } 437 438 // CREDENTIAL PROVIDERS 439 List<Map<String, String>> credentialProviders = new ArrayList<>(); 440 for (Configuration credentialProviderConfiguration : userPopulationConfiguration.getChild("credentialProviders").getChildren("credentialProvider")) 441 { 442 Map<String, String> credentialProvider = new HashMap<>(); 443 credentialProviders.add(credentialProvider); 444 445 String cpid = credentialProviderConfiguration.getAttribute("id", ""); 446 credentialProvider.put("id", cpid); 447 448 String modelId = credentialProviderConfiguration.getAttribute("modelId", ""); 449 credentialProvider.put("cpModelId", modelId); 450 451 String additionalLabel = credentialProviderConfiguration.getAttribute("label", ""); 452 credentialProvider.put("label", additionalLabel); 453 454 CredentialProviderModel credentialProviderModel = _credentialProviderFactory.getExtension(modelId); 455 456 Map<String, ? extends Parameter<ParameterType>> parameters = credentialProviderModel.getParameters(); 457 for (String parameterId : parameters.keySet()) 458 { 459 Parameter<ParameterType> parameter = parameters.get(parameterId); 460 461 String value = credentialProviderConfiguration.getChild(parameterId).getValue(""); 462 463 if (parameter.getType() == ParameterType.DATASOURCE) 464 { 465 value = mapping.get(value); 466 } 467 468 credentialProvider.put(modelId + "$" + parameterId, value); 469 } 470 } 471 472 _userPopulationDAO.add(id, label, userDirectories, credentialProviders); 473 } 474 } 475 } 476 477 478 479 private void _removeBackOfficeSynchronizedElements() 480 { 481 Set<String> datasourceIds = _removeBackOfficeUserPopulations(); 482 _removeBackofficeDatasources(datasourceIds); 483 } 484 485 private Set<String> _removeBackOfficeUserPopulations() 486 { 487 Set<String> datasourceIds = new HashSet<>(); 488 489 for (UserPopulation userPopulation : _userPopulationDAO.getUserPopulations(false)) 490 { 491 datasourceIds.addAll(_getDatasourcesUsedByPopulation(userPopulation)); 492 _userPopulationDAO.remove(userPopulation.getId(), true); 493 } 494 495 return datasourceIds; 496 } 497 498 private void _removeBackofficeDatasources(Set<String> datasourceIds) 499 { 500 _sqlDataSourceManager.delete(datasourceIds.stream().filter(id -> id.startsWith(SQLDataSourceManager.SQL_DATASOURCE_PREFIX)).collect(Collectors.toList()), true); 501 _ldapDataSourceManager.delete(datasourceIds.stream().filter(id -> id.startsWith(LDAPDataSourceManager.LDAP_DATASOURCE_PREFIX)).collect(Collectors.toList()), true); 502 } 503 504 private Set<String> _getDatasourcesUsedByPopulation(UserPopulation userPopulation) 505 { 506 Set<String> datasourcesInUse = new HashSet<>(); 507 508 for (UserDirectory userDirectory : userPopulation.getUserDirectories()) 509 { 510 String userDirectoryModelId = userDirectory.getUserDirectoryModelId(); 511 UserDirectoryModel userDirectoryModel = _userDirectoryFactory.getExtension(userDirectoryModelId); 512 513 Map<String, Object> parameterValues = userDirectory.getParameterValues(); 514 515 Map<String, ? extends Parameter<ParameterType>> userDirectoryModelParameters = userDirectoryModel.getParameters(); 516 for (String userDirectoryModelParameterId : userDirectoryModelParameters.keySet()) 517 { 518 Parameter<ParameterType> userDirectoryModelParameter = userDirectoryModelParameters.get(userDirectoryModelParameterId); 519 if (ParameterType.DATASOURCE.equals(userDirectoryModelParameter.getType())) 520 { 521 String datasourceId = (String) parameterValues.get(userDirectoryModelParameterId); 522 datasourcesInUse.add(datasourceId); 523 } 524 } 525 } 526 527 for (CredentialProvider credentialProvider : userPopulation.getCredentialProviders()) 528 { 529 String credentialProviderModelId = credentialProvider.getCredentialProviderModelId(); 530 CredentialProviderModel credentialProviderModel = _credentialProviderFactory.getExtension(credentialProviderModelId); 531 532 Map<String, Object> parameterValues = credentialProvider.getParameterValues(); 533 534 Map<String, ? extends Parameter<ParameterType>> credentialProviderModelParameters = credentialProviderModel.getParameters(); 535 for (String credentialProviderModelParameterId : credentialProviderModelParameters.keySet()) 536 { 537 Parameter<ParameterType> credentialProviderModelParameter = credentialProviderModelParameters.get(credentialProviderModelParameterId); 538 if (ParameterType.DATASOURCE.equals(credentialProviderModelParameter.getType())) 539 { 540 String datasourceId = (String) parameterValues.get(credentialProviderModelParameterId); 541 datasourcesInUse.add(datasourceId); 542 } 543 } 544 } 545 546 return datasourcesInUse; 547 } 548 549}