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