001/* 002 * (C) Copyright 2010-2015 Nuxeo SA (http://nuxeo.com/) and contributors. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the GNU Lesser General Public License 006 * (LGPL) version 2.1 which accompanies this distribution, and is available at 007 * http://www.gnu.org/licenses/lgpl-2.1.html 008 * 009 * This library is distributed in the hope that it will be useful, 010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 012 * Lesser General Public License for more details. 013 * 014 * Contributors: 015 * Thierry Delprat 016 * Julien Carsique 017 * 018 */ 019 020package org.nuxeo.ecm.admin.setup; 021 022import static org.nuxeo.launcher.config.ConfigurationGenerator.PARAM_TEMPLATE_DBNAME; 023 024import java.io.FileNotFoundException; 025import java.io.IOException; 026import java.io.Serializable; 027import java.math.BigDecimal; 028import java.sql.SQLException; 029import java.util.HashMap; 030import java.util.Iterator; 031import java.util.Map; 032import java.util.Map.Entry; 033import java.util.Properties; 034import java.util.TreeMap; 035 036import javax.faces.application.FacesMessage; 037import javax.faces.component.UIComponent; 038import javax.faces.component.UIInput; 039import javax.faces.component.ValueHolder; 040import javax.faces.context.FacesContext; 041import javax.faces.event.AbortProcessingException; 042import javax.faces.event.AjaxBehaviorEvent; 043import javax.faces.validator.ValidatorException; 044import javax.naming.AuthenticationException; 045import javax.naming.NamingException; 046 047import org.apache.commons.lang.StringUtils; 048import org.apache.commons.logging.Log; 049import org.apache.commons.logging.LogFactory; 050import org.jboss.seam.ScopeType; 051import org.jboss.seam.annotations.Factory; 052import org.jboss.seam.annotations.In; 053import org.jboss.seam.annotations.Name; 054import org.jboss.seam.annotations.Scope; 055import org.jboss.seam.contexts.Contexts; 056import org.jboss.seam.faces.FacesMessages; 057import org.jboss.seam.international.StatusMessage; 058 059import org.nuxeo.common.Environment; 060import org.nuxeo.ecm.platform.ui.web.util.ComponentUtils; 061import org.nuxeo.launcher.commons.DatabaseDriverException; 062import org.nuxeo.launcher.config.ConfigurationException; 063import org.nuxeo.launcher.config.ConfigurationGenerator; 064 065/** 066 * Serves UI for the setup screen, handling properties that can be saved on the bin/nuxeo.conf file on server. 067 * <p> 068 * Manages some important parameters to perform validation on them, and accepts custom parameters that would be present 069 * in the server nuxeo.conf file, and moves to advanced mode any property that would not be in that list. 070 * 071 * @since 5.5 072 */ 073@Scope(ScopeType.SESSION) 074@Name("setupWizardAction") 075public class SetupWizardActionBean implements Serializable { 076 077 private static final long serialVersionUID = 1L; 078 079 protected static final Log log = LogFactory.getLog(SetupWizardActionBean.class); 080 081 /** 082 * The list of important parameters that need to be presented first to the user 083 */ 084 private static final String[] managedKeyParameters = { "nuxeo.bind.address", "nuxeo.url", 085 Environment.NUXEO_DATA_DIR, Environment.NUXEO_LOG_DIR, Environment.PRODUCT_NAME, 086 Environment.PRODUCT_VERSION, "nuxeo.conf", PARAM_TEMPLATE_DBNAME, "nuxeo.db.name", "nuxeo.db.user", 087 "nuxeo.db.password", "nuxeo.db.host", "nuxeo.db.port", "nuxeo.db.min-pool-size", "nuxeo.db.min-pool-size", 088 "nuxeo.db.max-pool-size", "nuxeo.vcs.min-pool-size", "nuxeo.vcs.max-pool-size", 089 "nuxeo.notification.eMailSubjectPrefix", "mailservice.user", "mailservice.password", "mail.store.protocol", 090 "mail.transport.protocol", "mail.store.host", "mail.store.port", "mail.store.user", "mail.store.password", 091 "mail.debug", "mail.transport.host", "mail.transport.port", "mail.transport.auth", "mail.transport.user", 092 "mail.transport.password", "mail.from", "mail.user", "mail.transport.usetls", "nuxeo.http.proxy.host", 093 "nuxeo.http.proxy.port", "nuxeo.http.proxy.login", "nuxeo.http.proxy.password", "org.nuxeo.dev", 094 "nuxeo.directory.type", "nuxeo.user.group.storage", "nuxeo.ldap.url", "nuxeo.ldap.binddn", 095 "nuxeo.ldap.bindpassword", "nuxeo.ldap.retries", "nuxeo.ldap.user.searchBaseDn", 096 "nuxeo.ldap.user.searchClass", "nuxeo.ldap.user.searchFilter", "nuxeo.ldap.user.searchScope", 097 "nuxeo.ldap.user.readonly", "nuxeo.ldap.user.mapping.rdn", "nuxeo.ldap.user.mapping.username", 098 "nuxeo.ldap.user.mapping.password", "nuxeo.ldap.user.mapping.firstname", 099 "nuxeo.ldap.user.mapping.lastname", "nuxeo.ldap.user.mapping.email", "nuxeo.ldap.user.mapping.company", 100 "nuxeo.ldap.group.searchBaseDn", "nuxeo.ldap.group.searchFilter", "nuxeo.ldap.group.searchScope", 101 "nuxeo.ldap.group.readonly", "nuxeo.ldap.group.mapping.rdn", "nuxeo.ldap.group.mapping.name", 102 "nuxeo.ldap.group.mapping.label", "nuxeo.ldap.group.mapping.members.staticAttributeId", 103 "nuxeo.ldap.group.mapping.members.dynamicAttributeId", "nuxeo.ldap.defaultAdministratorId", 104 "nuxeo.ldap.defaultMembersGroup", "nuxeo.user.anonymous.enable", "nuxeo.user.emergency.enable", 105 "nuxeo.user.emergency.username", "nuxeo.user.emergency.password", "nuxeo.user.emergency.firstname", 106 "nuxeo.user.emergency.lastname" }; 107 108 protected Map<String, String> parameters; 109 110 protected Map<String, String> advancedParameters; 111 112 protected static final String PROXY_NONE = "none"; 113 114 protected static final String PROXY_ANONYMOUS = "anonymous"; 115 116 protected static final String PROXY_AUTHENTICATED = "authenticated"; 117 118 protected static final String DIRECTORY_DEFAULT = "default"; 119 120 protected static final String DIRECTORY_LDAP = "ldap"; 121 122 protected static final String DIRECTORY_MULTI = "multi"; 123 124 private static final String ERROR_DB_DRIVER = "error.db.driver.notfound"; 125 126 private static final String ERROR_DB_CONNECTION = "error.db.connection"; 127 128 private static final String ERROR_LDAP_CONNECTION = "error.ldap.connection"; 129 130 private static final String ERROR_LDAP_AUTHENTICATION = "error.ldap.authentication"; 131 132 private static final String ERROR_DB_FS = "error.db.fs"; 133 134 protected String proxyType = PROXY_NONE; 135 136 protected String directoryType = DIRECTORY_DEFAULT; 137 138 protected boolean needsRestart = false; 139 140 @In(create = true) 141 private transient ConfigurationGenerator setupConfigGenerator; 142 143 protected Properties userConfig; 144 145 @In(create = true, required = false) 146 protected FacesMessages facesMessages; 147 148 @In(create = true) 149 protected Map<String, String> messages; 150 151 private Boolean needGroupConfiguration; 152 153 @Factory(value = "setupRequiresRestart", scope = ScopeType.EVENT) 154 public boolean isNeedsRestart() { 155 return needsRestart; 156 } 157 158 public void setNeedsRestart(boolean needsRestart) { 159 this.needsRestart = needsRestart; 160 } 161 162 @Factory(value = "setupConfigGenerator", scope = ScopeType.PAGE) 163 public ConfigurationGenerator getConfigurationGenerator() { 164 if (setupConfigGenerator == null) { 165 setupConfigGenerator = new ConfigurationGenerator(); 166 if (setupConfigGenerator.init()) { 167 setParameters(); 168 } 169 } 170 return setupConfigGenerator; 171 } 172 173 @Factory(value = "setupConfigurable", scope = ScopeType.APPLICATION) 174 public boolean isConfigurable() { 175 return setupConfigGenerator.isConfigurable(); 176 } 177 178 @Factory(value = "advancedParams", scope = ScopeType.EVENT) 179 public Map<String, String> getAdvancedParameters() { 180 return advancedParameters; 181 } 182 183 @Factory(value = "setupParams", scope = ScopeType.EVENT) 184 public Map<String, String> getParameters() { 185 return parameters; 186 } 187 188 /** 189 * Fill {@link #parameters} and {@link #advancedParameters} with properties from # 190 * {@link ConfigurationGenerator#getUserConfig()} 191 * 192 * @since 5.6 193 */ 194 protected void setParameters() { 195 userConfig = setupConfigGenerator.getUserConfig(); 196 parameters = new HashMap<>(); 197 advancedParameters = new TreeMap<>(); 198 // will remove managed parameters later in setParameter() 199 for (String key : userConfig.stringPropertyNames()) { 200 if (System.getProperty(key) == null 201 || key.matches("^(nuxeo|org\\.nuxeo|catalina|derby|h2|java\\.home|" 202 + "java\\.io\\.tmpdir|tomcat|sun\\.rmi\\.dgc).*")) { 203 advancedParameters.put(key, userConfig.getProperty(key).trim()); 204 } 205 } 206 for (String keyParam : managedKeyParameters) { 207 String parameter = userConfig.getProperty(keyParam); 208 setParameter(keyParam, parameter); 209 } 210 211 proxyType = PROXY_NONE; 212 if (parameters.get("nuxeo.http.proxy.host") != null) { 213 proxyType = PROXY_ANONYMOUS; 214 if (parameters.get("nuxeo.http.proxy.login") != null) { 215 proxyType = PROXY_AUTHENTICATED; 216 } 217 } 218 219 if (parameters.get("nuxeo.directory.type") != null) { 220 directoryType = parameters.get("nuxeo.directory.type"); 221 } 222 } 223 224 /** 225 * Adds parameter value to the 226 * 227 * @param key parameter key such as used in templates and nuxeo.conf 228 */ 229 private void setParameter(String key, String value) { 230 if (value != null) { 231 parameters.put(key, value.trim()); 232 advancedParameters.remove(key); 233 } 234 } 235 236 public void save() { 237 saveParameters(); 238 setNeedsRestart(true); 239 resetParameters(); 240 // initialize setupConfigurator again, as it's in scope page 241 getConfigurationGenerator(); 242 facesMessages.add(StatusMessage.Severity.INFO, messages.get("label.parameters.saved")); 243 } 244 245 @SuppressWarnings("unchecked") 246 protected void saveParameters() { 247 // Fix types (replace Long and Boolean with String) 248 for (Iterator<?> entries = parameters.entrySet().iterator(); entries.hasNext();) { 249 @SuppressWarnings("rawtypes") 250 Entry entry = (Entry<String, ?>) entries.next(); 251 if (entry.getValue() instanceof Long) { 252 entry.setValue(((Long) entry.getValue()).toString()); 253 } 254 if (entry.getValue() instanceof Boolean) { 255 entry.setValue(((Boolean) entry.getValue()).toString()); 256 } 257 if (entry.getValue() instanceof BigDecimal) { 258 entry.setValue(entry.getValue().toString()); 259 } 260 } 261 262 // manage httpProxy settings (setting null is not accepted) 263 if (!PROXY_AUTHENTICATED.equals(proxyType)) { 264 parameters.put("nuxeo.http.proxy.login", ""); 265 parameters.put("nuxeo.http.proxy.password", ""); 266 } 267 if (PROXY_NONE.equals(proxyType)) { 268 parameters.put("nuxeo.http.proxy.host", ""); 269 parameters.put("nuxeo.http.proxy.port", ""); 270 } 271 272 // Remove empty values for password keys 273 for (String pwdKey : new String[] { "nuxeo.db.password", "mailservice.password", "mail.transport.password", 274 "nuxeo.http.proxy.password" }) { 275 if (StringUtils.isEmpty(parameters.get(pwdKey))) { 276 parameters.remove(pwdKey); 277 } 278 } 279 280 Map<String, String> customParameters = new HashMap<>(); 281 customParameters.putAll(parameters); 282 customParameters.putAll(advancedParameters); 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 = parameters.get("nuxeo.db.password"); 333 } 334 335 String errorLabel = null; 336 Exception error = null; 337 try { 338 setupConfigGenerator.checkDatabaseConnection(parameters.get(ConfigurationGenerator.PARAM_TEMPLATE_DBNAME), 339 dbName, dbUser, dbPwd, dbHost, dbPort); 340 } catch (FileNotFoundException e) { 341 errorLabel = ERROR_DB_FS; 342 error = e; 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.booleanValue(); 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}