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