001/* 002 * (C) Copyright 2010-2015 Nuxeo SA (http://nuxeo.com/) and others. 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 * 016 * Contributors: 017 * Thierry Delprat 018 * Julien Carsique 019 * 020 */ 021 022package org.nuxeo.ecm.admin.setup; 023 024import static org.nuxeo.common.Environment.NUXEO_DATA_DIR; 025import static org.nuxeo.common.Environment.NUXEO_LOG_DIR; 026import static org.nuxeo.common.Environment.PRODUCT_NAME; 027import static org.nuxeo.common.Environment.PRODUCT_VERSION; 028import static org.nuxeo.launcher.config.ConfigurationGenerator.NUXEO_CONF; 029import static org.nuxeo.launcher.config.ConfigurationGenerator.NUXEO_DEV_SYSTEM_PROP; 030import static org.nuxeo.launcher.config.ConfigurationGenerator.PARAM_BIND_ADDRESS; 031import static org.nuxeo.launcher.config.ConfigurationGenerator.PARAM_DB_HOST; 032import static org.nuxeo.launcher.config.ConfigurationGenerator.PARAM_DB_NAME; 033import static org.nuxeo.launcher.config.ConfigurationGenerator.PARAM_DB_PORT; 034import static org.nuxeo.launcher.config.ConfigurationGenerator.PARAM_DB_PWD; 035import static org.nuxeo.launcher.config.ConfigurationGenerator.PARAM_DB_USER; 036import static org.nuxeo.launcher.config.ConfigurationGenerator.PARAM_NUXEO_URL; 037import static org.nuxeo.launcher.config.ConfigurationGenerator.PARAM_TEMPLATE_DBNAME; 038import static org.nuxeo.launcher.config.ConfigurationGenerator.SECRET_KEYS; 039 040import java.io.IOException; 041import java.io.Serializable; 042import java.math.BigDecimal; 043import java.sql.SQLException; 044import java.util.HashMap; 045import java.util.Map; 046import java.util.Map.Entry; 047import java.util.Optional; 048import java.util.Properties; 049import java.util.TreeMap; 050import java.util.stream.Stream; 051 052import javax.faces.application.FacesMessage; 053import javax.faces.component.UIComponent; 054import javax.faces.component.UIInput; 055import javax.faces.component.ValueHolder; 056import javax.faces.context.FacesContext; 057import javax.faces.event.AbortProcessingException; 058import javax.faces.event.AjaxBehaviorEvent; 059import javax.faces.validator.ValidatorException; 060import javax.naming.AuthenticationException; 061import javax.naming.NamingException; 062 063import org.apache.commons.lang3.StringUtils; 064import org.apache.commons.logging.Log; 065import org.apache.commons.logging.LogFactory; 066import org.jboss.seam.ScopeType; 067import org.jboss.seam.annotations.Factory; 068import org.jboss.seam.annotations.In; 069import org.jboss.seam.annotations.Name; 070import org.jboss.seam.annotations.Scope; 071import org.jboss.seam.contexts.Contexts; 072import org.jboss.seam.faces.FacesMessages; 073import org.jboss.seam.international.StatusMessage; 074import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 075import org.nuxeo.launcher.commons.DatabaseDriverException; 076import org.nuxeo.launcher.config.ConfigurationException; 077import org.nuxeo.launcher.config.ConfigurationGenerator; 078 079/** 080 * Serves UI for the setup screen, handling properties that can be saved on the bin/nuxeo.conf file on server. 081 * <p> 082 * Manages some important parameters to perform validation on them, and accepts custom parameters that would be present 083 * in the server nuxeo.conf file, and moves to advanced mode any property that would not be in that list. 084 * 085 * @since 5.5 086 */ 087@Scope(ScopeType.SESSION) 088@Name("setupWizardAction") 089public class SetupWizardActionBean implements Serializable { 090 091 private static final long serialVersionUID = 1L; 092 093 protected static final Log log = LogFactory.getLog(SetupWizardActionBean.class); 094 095 /** 096 * The list of important parameters that need to be presented first to the user 097 */ 098 private static final String[] managedKeyParameters = { PARAM_BIND_ADDRESS, PARAM_NUXEO_URL, NUXEO_DATA_DIR, 099 NUXEO_LOG_DIR, PRODUCT_NAME, PRODUCT_VERSION, NUXEO_CONF, PARAM_TEMPLATE_DBNAME, PARAM_DB_NAME, 100 PARAM_DB_USER, PARAM_DB_PWD, PARAM_DB_HOST, PARAM_DB_PORT, "nuxeo.db.min-pool-size", 101 "nuxeo.db.min-pool-size", "nuxeo.db.max-pool-size", "nuxeo.vcs.min-pool-size", "nuxeo.vcs.max-pool-size", 102 "nuxeo.notification.eMailSubjectPrefix", "mailservice.user", "mailservice.password", "mail.store.protocol", 103 "mail.transport.protocol", "mail.store.host", "mail.store.port", "mail.store.user", "mail.store.password", 104 "mail.debug", "mail.transport.host", "mail.transport.port", "mail.transport.auth", "mail.transport.user", 105 "mail.transport.password", "mail.from", "mail.user", "mail.transport.usetls", "nuxeo.http.proxy.host", 106 "nuxeo.http.proxy.port", "nuxeo.http.proxy.login", "nuxeo.http.proxy.password", NUXEO_DEV_SYSTEM_PROP, 107 "nuxeo.directory.type", "nuxeo.user.group.storage", "nuxeo.ldap.url", "nuxeo.ldap.binddn", 108 "nuxeo.ldap.bindpassword", "nuxeo.ldap.retries", "nuxeo.ldap.user.searchBaseDn", 109 "nuxeo.ldap.user.searchClass", "nuxeo.ldap.user.searchFilter", "nuxeo.ldap.user.searchScope", 110 "nuxeo.ldap.user.readonly", "nuxeo.ldap.user.mapping.rdn", "nuxeo.ldap.user.mapping.username", 111 "nuxeo.ldap.user.mapping.password", "nuxeo.ldap.user.mapping.firstname", "nuxeo.ldap.user.mapping.lastname", 112 "nuxeo.ldap.user.mapping.email", "nuxeo.ldap.user.mapping.company", "nuxeo.ldap.group.searchBaseDn", 113 "nuxeo.ldap.group.searchFilter", "nuxeo.ldap.group.searchScope", "nuxeo.ldap.group.readonly", 114 "nuxeo.ldap.group.mapping.rdn", "nuxeo.ldap.group.mapping.name", "nuxeo.ldap.group.mapping.label", 115 "nuxeo.ldap.group.mapping.members.staticAttributeId", "nuxeo.ldap.group.mapping.members.dynamicAttributeId", 116 "nuxeo.ldap.defaultAdministratorId", "nuxeo.ldap.defaultMembersGroup", "nuxeo.user.anonymous.enable", 117 "nuxeo.user.emergency.enable", "nuxeo.user.emergency.username", "nuxeo.user.emergency.password", 118 "nuxeo.user.emergency.firstname", "nuxeo.user.emergency.lastname" }; 119 120 protected Map<String, Serializable> parameters; 121 122 protected Map<String, Serializable> advancedParameters; 123 124 protected static final String PROXY_NONE = "none"; 125 126 protected static final String PROXY_ANONYMOUS = "anonymous"; 127 128 protected static final String PROXY_AUTHENTICATED = "authenticated"; 129 130 protected static final String DIRECTORY_DEFAULT = "default"; 131 132 protected static final String DIRECTORY_LDAP = "ldap"; 133 134 protected static final String DIRECTORY_MULTI = "multi"; 135 136 private static final String ERROR_DB_DRIVER = "error.db.driver.notfound"; 137 138 private static final String ERROR_DB_CONNECTION = "error.db.connection"; 139 140 private static final String ERROR_LDAP_CONNECTION = "error.ldap.connection"; 141 142 private static final String ERROR_LDAP_AUTHENTICATION = "error.ldap.authentication"; 143 144 private static final String ERROR_DB_FS = "error.db.fs"; 145 146 protected String proxyType = PROXY_NONE; 147 148 protected String directoryType = DIRECTORY_DEFAULT; 149 150 protected boolean needsRestart = false; 151 152 @In(create = true) 153 private transient ConfigurationGenerator setupConfigGenerator; 154 155 protected Properties userConfig; 156 157 @In(create = true, required = false) 158 protected FacesMessages facesMessages; 159 160 @In(create = true) 161 protected Map<String, String> messages; 162 163 private Boolean needGroupConfiguration; 164 165 @Factory(value = "setupRequiresRestart", scope = ScopeType.EVENT) 166 public boolean isNeedsRestart() { 167 return needsRestart; 168 } 169 170 public void setNeedsRestart(boolean needsRestart) { 171 this.needsRestart = needsRestart; 172 } 173 174 @Factory(value = "setupConfigGenerator", scope = ScopeType.PAGE) 175 public ConfigurationGenerator getConfigurationGenerator() { 176 if (setupConfigGenerator == null) { 177 setupConfigGenerator = new ConfigurationGenerator(); 178 if (setupConfigGenerator.init()) { 179 setParameters(); 180 } 181 } 182 return setupConfigGenerator; 183 } 184 185 @Factory(value = "setupConfigurable", scope = ScopeType.APPLICATION) 186 public boolean isConfigurable() { 187 return setupConfigGenerator.isConfigurable(); 188 } 189 190 @Factory(value = "advancedParams", scope = ScopeType.EVENT) 191 public Map<String, Serializable> getAdvancedParameters() { 192 return advancedParameters; 193 } 194 195 @Factory(value = "setupParams", scope = ScopeType.EVENT) 196 public Map<String, Serializable> getParameters() { 197 return parameters; 198 } 199 200 /** 201 * Fill {@link #parameters} and {@link #advancedParameters} with properties from # 202 * {@link ConfigurationGenerator#getUserConfig()} 203 * 204 * @since 5.6 205 */ 206 protected void setParameters() { 207 userConfig = setupConfigGenerator.getUserConfig(); 208 parameters = new HashMap<>(); 209 advancedParameters = new TreeMap<>(); 210 // will remove managed parameters later in setParameter() 211 for (String key : userConfig.stringPropertyNames()) { 212 if (System.getProperty(key) == null || key.matches("^(nuxeo|org\\.nuxeo|catalina|derby|h2|java\\.home|" 213 + "java\\.io\\.tmpdir|tomcat|sun\\.rmi\\.dgc).*")) { 214 advancedParameters.put(key, userConfig.getProperty(key).trim()); 215 } 216 } 217 for (String keyParam : managedKeyParameters) { 218 String parameter = userConfig.getProperty(keyParam); 219 setParameter(keyParam, parameter); 220 } 221 222 proxyType = PROXY_NONE; 223 if (parameters.get("nuxeo.http.proxy.host") != null) { 224 proxyType = PROXY_ANONYMOUS; 225 if (parameters.get("nuxeo.http.proxy.login") != null) { 226 proxyType = PROXY_AUTHENTICATED; 227 } 228 } 229 230 if (parameters.get("nuxeo.directory.type") != null) { 231 directoryType = (String) parameters.get("nuxeo.directory.type"); 232 } 233 } 234 235 /** 236 * Adds parameter value to the 237 * 238 * @param key parameter key such as used in templates and nuxeo.conf 239 */ 240 private void setParameter(String key, String value) { 241 if (value != null) { 242 parameters.put(key, value.trim()); 243 advancedParameters.remove(key); 244 } 245 } 246 247 public void save() { 248 saveParameters(); 249 setNeedsRestart(true); 250 resetParameters(); 251 // initialize setupConfigurator again, as it's in scope page 252 getConfigurationGenerator(); 253 facesMessages.add(StatusMessage.Severity.INFO, messages.get("label.parameters.saved")); 254 } 255 256 @SuppressWarnings("unchecked") 257 protected void saveParameters() { 258 // manage httpProxy settings (setting null is not accepted) 259 if (!PROXY_AUTHENTICATED.equals(proxyType)) { 260 parameters.put("nuxeo.http.proxy.login", ""); 261 parameters.put("nuxeo.http.proxy.password", ""); 262 } 263 if (PROXY_NONE.equals(proxyType)) { 264 parameters.put("nuxeo.http.proxy.host", ""); 265 parameters.put("nuxeo.http.proxy.port", ""); 266 } 267 268 // Remove empty values for password keys 269 for (String pwdKey : SECRET_KEYS) { 270 if (StringUtils.isEmpty((String) parameters.get(pwdKey))) { 271 parameters.remove(pwdKey); 272 } 273 } 274 275 // compute <String, String> parameters for the ConfigurationGenerator 276 Stream<Entry<String, Serializable>> parametersStream = parameters.entrySet().stream(); 277 Stream<Entry<String, Serializable>> advancedParametersStream = advancedParameters.entrySet().stream(); 278 Map<String, String> customParameters = Stream.concat(parametersStream, advancedParametersStream).collect( 279 HashMap::new, (m, e) -> m.put(e.getKey(), Optional.ofNullable(e.getValue()) 280 .map(Object::toString) 281 .orElse(null)), 282 HashMap::putAll); 283 try { 284 setupConfigGenerator.saveFilteredConfiguration(customParameters); 285 } catch (ConfigurationException e) { 286 log.error(e, e); 287 } 288 } 289 290 public void resetParameters() { 291 setupConfigGenerator = null; 292 parameters = null; 293 advancedParameters = null; 294 Contexts.getPageContext().remove("setupConfigGenerator"); 295 } 296 297 /** 298 * @since 5.6 299 */ 300 public void checkDatabaseParameters(FacesContext context, UIComponent component, Object value) { 301 Map<String, Object> attributes = component.getAttributes(); 302 String dbNameInputId = (String) attributes.get("dbNameInputId"); 303 String dbUserInputId = (String) attributes.get("dbUserInputId"); 304 String dbPwdInputId = (String) attributes.get("dbPwdInputId"); 305 String dbHostInputId = (String) attributes.get("dbHostInputId"); 306 String dbPortInputId = (String) attributes.get("dbPortInputId"); 307 308 if (dbNameInputId == null || dbUserInputId == null || dbPwdInputId == null || dbHostInputId == null 309 || dbPortInputId == null) { 310 log.error("Cannot validate database parameters: missing inputIds"); 311 return; 312 } 313 314 UIInput dbNameComp = (UIInput) component.findComponent(dbNameInputId); 315 UIInput dbUserComp = (UIInput) component.findComponent(dbUserInputId); 316 UIInput dbPwdComp = (UIInput) component.findComponent(dbPwdInputId); 317 UIInput dbHostComp = (UIInput) component.findComponent(dbHostInputId); 318 UIInput dbPortComp = (UIInput) component.findComponent(dbPortInputId); 319 if (dbNameComp == null || dbUserComp == null || dbPwdComp == null || dbHostComp == null || dbPortComp == null) { 320 log.error("Cannot validate inputs: not found"); 321 return; 322 } 323 324 String dbName = (String) dbNameComp.getLocalValue(); 325 String dbUser = (String) dbUserComp.getLocalValue(); 326 String dbPwd = (String) dbPwdComp.getLocalValue(); 327 String dbHost = (String) dbHostComp.getLocalValue(); 328 Long dbPortLong = ((BigDecimal) dbPortComp.getLocalValue()).longValue(); 329 String dbPort = dbPortLong.toString(); 330 331 if (StringUtils.isEmpty(dbPwd)) { 332 dbPwd = (String) parameters.get("nuxeo.db.password"); 333 } 334 335 String errorLabel = null; 336 Exception error = null; 337 try { 338 setupConfigGenerator.checkDatabaseConnection( 339 (String) parameters.get(ConfigurationGenerator.PARAM_TEMPLATE_DBNAME), dbName, dbUser, dbPwd, 340 dbHost, dbPort); 341 } catch (IOException e) { 342 errorLabel = ERROR_DB_FS; 343 error = e; 344 } catch (DatabaseDriverException e) { 345 errorLabel = ERROR_DB_DRIVER; 346 error = e; 347 } catch (SQLException e) { 348 errorLabel = ERROR_DB_CONNECTION; 349 error = e; 350 } 351 if (error != null) { 352 log.error(error, error); 353 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, 354 ComponentUtils.translate(context, errorLabel), null); 355 throw new ValidatorException(message); 356 } 357 358 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, 359 ComponentUtils.translate(context, "error.db.none"), null); 360 message.setSeverity(FacesMessage.SEVERITY_INFO); 361 context.addMessage(component.getClientId(context), message); 362 } 363 364 public void templateChange(AjaxBehaviorEvent event) { 365 String dbTemplate; 366 UIComponent select = event.getComponent(); 367 if (select instanceof ValueHolder) { 368 dbTemplate = (String) ((ValueHolder) select).getValue(); 369 } else { 370 log.error("Bad component returned " + select); 371 throw new AbortProcessingException("Bad component returned " + select); 372 } 373 setupConfigGenerator.changeDBTemplate(dbTemplate); 374 setParameters(); 375 Contexts.getEventContext().remove("setupParams"); 376 Contexts.getEventContext().remove("advancedParams"); 377 FacesContext context = FacesContext.getCurrentInstance(); 378 context.renderResponse(); 379 } 380 381 public void proxyChange(AjaxBehaviorEvent event) { 382 UIComponent select = event.getComponent(); 383 if (select instanceof ValueHolder) { 384 proxyType = (String) ((ValueHolder) select).getValue(); 385 } else { 386 log.error("Bad component returned " + select); 387 throw new AbortProcessingException("Bad component returned " + select); 388 } 389 Contexts.getEventContext().remove("setupParams"); 390 FacesContext context = FacesContext.getCurrentInstance(); 391 context.renderResponse(); 392 } 393 394 /** 395 * Initialized by {@link #getParameters()} 396 */ 397 public String getProxyType() { 398 return proxyType; 399 } 400 401 public void setProxyType(String proxyType) { 402 this.proxyType = proxyType; 403 } 404 405 public String getDirectoryType() { 406 return directoryType; 407 } 408 409 public void setDirectoryType(String directoryType) { 410 parameters.put("nuxeo.directory.type", directoryType); 411 this.directoryType = directoryType; 412 } 413 414 public void setDirectoryStorage(String directoryStorage) { 415 parameters.put("nuxeo.user.group.storage", directoryStorage); 416 } 417 418 public void ldapStorageChange() { 419 needGroupConfiguration = null; 420 } 421 422 public boolean getNeedGroupConfiguration() { 423 if (needGroupConfiguration == null) { 424 String storageType = (String) parameters.get("nuxeo.user.group.storage"); 425 if ("userLdapOnly".equals(storageType) || "multiUserSqlGroup".equals(storageType)) { 426 needGroupConfiguration = Boolean.FALSE; 427 } else { 428 needGroupConfiguration = Boolean.TRUE; 429 } 430 } 431 return needGroupConfiguration; 432 } 433 434 public void directoryChange(AjaxBehaviorEvent event) { 435 UIComponent select = event.getComponent(); 436 if (select instanceof ValueHolder) { 437 directoryType = (String) ((ValueHolder) select).getValue(); 438 } else { 439 log.error("Bad component returned " + select); 440 throw new AbortProcessingException("Bad component returned " + select); 441 } 442 if ("multi".equals(directoryType)) { 443 setDirectoryStorage("multiUserGroup"); 444 } else { 445 setDirectoryStorage("default"); 446 } 447 needGroupConfiguration = null; 448 Contexts.getEventContext().remove("setupParams"); 449 FacesContext context = FacesContext.getCurrentInstance(); 450 context.renderResponse(); 451 } 452 453 public void checkLdapNetworkParameters(FacesContext context, UIComponent component, Object value) { 454 Map<String, Object> attributes = component.getAttributes(); 455 String ldapUrlId = (String) attributes.get("directoryLdapUrl"); 456 457 if (ldapUrlId == null) { 458 log.error("Cannot validate LDAP parameters: missing inputIds"); 459 return; 460 } 461 462 UIInput ldapUrlComp = (UIInput) component.findComponent(ldapUrlId); 463 if (ldapUrlComp == null) { 464 log.error("Cannot validate LDAP inputs: not found"); 465 return; 466 } 467 468 String ldapUrl = (String) ldapUrlComp.getLocalValue(); 469 470 String errorLabel = null; 471 Exception error = null; 472 try { 473 setupConfigGenerator.checkLdapConnection(ldapUrl, null, null, false); 474 } catch (NamingException e) { 475 errorLabel = ERROR_LDAP_CONNECTION; 476 error = e; 477 } 478 if (error != null) { 479 log.error(error, error); 480 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, 481 ComponentUtils.translate(context, errorLabel), null); 482 throw new ValidatorException(message); 483 } 484 485 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, 486 ComponentUtils.translate(context, "error.ldap.network.none"), null); 487 message.setSeverity(FacesMessage.SEVERITY_INFO); 488 context.addMessage(component.getClientId(context), message); 489 } 490 491 public void checkLdapAuthenticationParameters(FacesContext context, UIComponent component, Object value) { 492 493 Map<String, Object> attributes = component.getAttributes(); 494 String ldapUrlId = (String) attributes.get("ldapUrl"); 495 String ldapBinddnId = (String) attributes.get("ldapBindDn"); 496 String ldapBindpwdId = (String) attributes.get("ldapBindPwd"); 497 498 if (ldapUrlId == null || ldapBinddnId == null || ldapBindpwdId == null) { 499 log.error("Cannot validate LDAP parameters: missing inputIds"); 500 return; 501 } 502 503 UIInput ldapUrlComp = (UIInput) component.findComponent(ldapUrlId); 504 UIInput ldapBinddnComp = (UIInput) component.findComponent(ldapBinddnId); 505 UIInput ldapBindpwdComp = (UIInput) component.findComponent(ldapBindpwdId); 506 if (ldapUrlComp == null || ldapBinddnComp == null || ldapBindpwdComp == null) { 507 log.error("Cannot validate LDAP inputs: not found"); 508 return; 509 } 510 511 String ldapUrl = (String) ldapUrlComp.getLocalValue(); 512 String ldapBindDn = (String) ldapBinddnComp.getLocalValue(); 513 String ldapBindPwd = (String) ldapBindpwdComp.getLocalValue(); 514 515 String errorLabel = null; 516 Exception error = null; 517 try { 518 setupConfigGenerator.checkLdapConnection(ldapUrl, ldapBindDn, ldapBindPwd, true); 519 } catch (NamingException e) { 520 if (e instanceof AuthenticationException) { 521 errorLabel = ERROR_LDAP_AUTHENTICATION; 522 } else { 523 errorLabel = ERROR_LDAP_CONNECTION; 524 } 525 error = e; 526 } 527 if (error != null) { 528 log.error(error, error); 529 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, 530 ComponentUtils.translate(context, errorLabel), null); 531 throw new ValidatorException(message); 532 } 533 534 FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_INFO, 535 ComponentUtils.translate(context, "error.ldap.auth.none"), null); 536 message.setSeverity(FacesMessage.SEVERITY_INFO); 537 context.addMessage(component.getClientId(context), message); 538 } 539 540}