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