/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package controllers.infra; import static com.emc.storageos.model.property.PropertyConstants.ENCRYPTEDTEXT; import static com.emc.storageos.model.property.PropertyConstants.TEXT; import static controllers.Common.flashException; import java.net.ConnectException; import java.net.InetSocketAddress; import java.net.Socket; import java.util.Collection; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import com.emc.storageos.management.backup.ExternalServerType; import com.emc.storageos.management.backup.util.BackupClient; import com.emc.storageos.management.backup.util.CifsClient; import com.emc.storageos.management.backup.util.FtpClient; import models.properties.BackupPropertyPage; import models.properties.ControllerPropertyPage; import models.properties.DefaultPropertyPage; import models.properties.DiscoveryPropertyPage; import models.properties.NetworkPropertyPage; import models.properties.PasswordPropertyPage; import models.properties.Property; import models.properties.PropertyPage; import models.properties.SecurityPropertyPage; import models.properties.SmtpPropertyPage; import models.properties.SupportPropertyPage; import models.properties.SyslogPropertiesPage; import models.properties.UpgradePropertyPage; import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.math.NumberUtils; import org.apache.http.auth.AuthenticationException; import play.Logger; import play.data.validation.Required; import play.data.validation.Validation; import play.i18n.Messages; import play.mvc.Controller; import play.mvc.With; import util.BourneUtil; import util.ConfigPropertyUtils; import util.DisasterRecoveryUtils; import util.MailSettingsValidator; import util.MessagesUtils; import util.PasswordUtil; import util.SetupUtils; import util.ValidationResponse; import util.validation.HostNameOrIpAddressCheck; import com.emc.storageos.model.property.PropertyMetadata; import com.emc.storageos.security.password.Constants; import com.emc.storageos.services.util.PlatformUtils; import com.emc.vipr.model.sys.ClusterInfo; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import controllers.Common; import controllers.Maintenance; import controllers.deadbolt.Restrict; import controllers.deadbolt.Restrictions; import controllers.security.Security; @With(Common.class) @Restrictions({ @Restrict("SECURITY_ADMIN"), @Restrict("RESTRICTED_SECURITY_ADMIN") }) public class ConfigProperties extends Controller { private static final String DEFAULT_PAGE = "general"; private static final String OTHER = "Other"; private static final String SECURITY_PAGE = "Security"; private static final String PERMIT_ROOT_CONSOLE = "system_permit_root_console"; private static final String PERMIT_ROOT_SSH = "system_permit_root_ssh"; private static final int MAX_FLASH = 2048; public static void properties() { ClusterInfo clusterInfo = Common.getClusterInfoWithRoleCheck(); List<PropertyPage> pages = loadPropertyPages(); render(pages, clusterInfo); } private static void handleError(Map<String, String> updated) { // Limit the amount of data flashed to 2K, hopefully leaving enough for other things int maxFlash = MAX_FLASH; if (updated.size() != 0) { int avgFlash = maxFlash / updated.size(); // Only flash properties that have been updated for (Map.Entry<String, String> entry : updated.entrySet()) { String name = entry.getKey(); String value = entry.getValue(); // Avoid blowing up the cookie by flashing too much if (StringUtils.length(value) < avgFlash) { params.flash(name); } else { Logger.info("Could not flash property: %s, value is too long: %s", name, value.length()); } } } Validation.keep(); properties(); } public static void saveProperties() { Map<String, String> properties = params.allSimple(); for (Entry<String, String> entry : properties.entrySet()) { entry.setValue(StringUtils.trim(entry.getValue())); } List<PropertyPage> pages = loadPropertyPages(); for (PropertyPage page : pages) { page.validate(properties); } boolean rebootRequired = false; Map<String, String> updated = Maps.newHashMap(); for (PropertyPage page : pages) { Map<String, String> pageUpdates = page.getUpdatedValues(properties); updated.putAll(pageUpdates); // Update the reboot required flag based on the values set for this page rebootRequired |= page.isRebootRequired(pageUpdates.keySet()); } if (validation.hasErrors()) { flash.error(MessagesUtils.get("configProperties.error.validationError")); handleError(updated); } else if (!Common.isClusterStable()) { flash.error(MessagesUtils.get("configProperties.error.clusterNotStable")); handleError(updated); } else { if (!updated.isEmpty()) { // If reboot is required, submit as a job and go to maintenance page. The cluster reboots immediately if (rebootRequired) { try { ConfigPropertyUtils.saveProperties(updated); flash.success(MessagesUtils.get("configProperties.submittedReboot")); Maintenance.maintenance(Common.reverseRoute(ConfigProperties.class, "properties")); } catch (Exception e) { Logger.error("reboot exception - ", e); flashException(e); handleError(updated); } } try { ConfigPropertyUtils.saveProperties(updated); flash.success(MessagesUtils.get("configProperties.saved")); } catch (Exception e) { flashException(e); handleError(updated); } } properties(); } } private static List<PropertyPage> loadPropertyPages() { boolean isActiveSite = DisasterRecoveryUtils.isActiveSite(); Map<String, Property> properties = loadProperties(); Map<String, PropertyPage> pages = Maps.newLinkedHashMap(); Map<String, PropertyPage> excludePages = Maps.newLinkedHashMap(); if (isActiveSite) { addPage(pages, new NetworkPropertyPage(properties)); if (PlatformUtils.isAppliance()) { // This done to maintain the current tab order addPage(pages, new SecurityPropertyPage(properties)); } addPage(pages, new ControllerPropertyPage(properties)); addPage(pages, new DiscoveryPropertyPage(properties)); if (!SetupUtils.isOssBuild()) { addPage(pages, new SupportPropertyPage(properties)); } addPage(pages, new SmtpPropertyPage(properties)); addPage(pages, new UpgradePropertyPage(properties)); addPage(pages, new PasswordPropertyPage(properties)); addPage(pages, new SyslogPropertiesPage(properties)); addPage(pages, new BackupPropertyPage(properties)); } else { if (PlatformUtils.isAppliance()) { addPage(pages, new SecurityPropertyPage(properties)); } addPage(excludePages, new NetworkPropertyPage(properties)); addPage(excludePages, new ControllerPropertyPage(properties)); addPage(excludePages, new DiscoveryPropertyPage(properties)); addPage(excludePages, new SupportPropertyPage(properties)); addPage(excludePages, new SmtpPropertyPage(properties)); addPage(excludePages, new UpgradePropertyPage(properties)); addPage(excludePages, new DefaultPropertyPage(OTHER)); addPage(excludePages, new PasswordPropertyPage(properties)); addPage(excludePages, new SyslogPropertiesPage(properties)); addPage(excludePages, new BackupPropertyPage(properties)); } addDefaultPages(pages, properties.values(), excludePages, isActiveSite); return Lists.newArrayList(pages.values()); } private static PropertyPage addPage(Map<String, PropertyPage> pages, PropertyPage page) { pages.put(page.getName(), page); return page; } private static void addDefaultPages(Map<String, PropertyPage> pages, Collection<Property> properties, Map<String, PropertyPage> excludePages, boolean isActiveSite) { for (Property property : properties) { String pageName = StringUtils.defaultIfBlank(property.getPageName(), DEFAULT_PAGE); PropertyPage page = pages.get(pageName); if (excludePages.get(pageName) == null) { if (page == null) { page = addPage(pages, new DefaultPropertyPage(pageName)); } if (isActiveSite) { page.getProperties().add(property); } else { String propertyName = StringUtils.defaultIfBlank(property.getName(), DEFAULT_PAGE); if (StringUtils.equals(propertyName, PERMIT_ROOT_CONSOLE) || StringUtils.equals(propertyName, PERMIT_ROOT_SSH)) { if (!StringUtils.equals(pageName, SECURITY_PAGE)) { page.getProperties().add(property); } } else { page.getProperties().add(property); } } } } } private static Map<String, Property> loadProperties() { Map<String, String> values = ConfigPropertyUtils.getProperties(); Map<String, Property> properties = Maps.newLinkedHashMap(); for (Map.Entry<String, PropertyMetadata> entry : ConfigPropertyUtils.getPropertiesMetadata().getMetadata() .entrySet()) { /* * image server configuration has been moved to Physical assets but, image server properties meta data * needs to remain for migration */ if (entry.getKey().startsWith("image_server")) { continue; } PropertyMetadata metadata = entry.getValue(); if ((metadata.getUserMutable() != null && metadata.getUserMutable()) && (metadata.getHidden() == null || !metadata.getHidden())) { String name = entry.getKey(); String value = values.get(name); PropertyMetadata meta = entry.getValue(); if (value != null) { if (meta.getType().equals(TEXT) || meta.getType().equals(ENCRYPTEDTEXT)) { value = value.replace("\\\\n", "\r\n"); } } Set<String> allSupportPageProperties = SupportPropertyPage.getAllProperties(); if (!(allSupportPageProperties.contains(name) && SetupUtils.isOssBuild())) { Property property = new Property(name, value, meta); properties.put(name, property); } } } return properties; } public static String getPort(final String port, final String enableTls) { if ("0".equals(port) || StringUtils.isBlank(port)) { // use default values return StringUtils.equals("yes", enableTls) ? "465" : "25"; } else { return port; } } public static void validateExternalSettings(String serverType, String serverUrl, String serverDomain, String user, String password) { BackupClient client; String passwd = PasswordUtil.decryptedValue(password); if (serverType.equalsIgnoreCase(ExternalServerType.CIFS.name())) { if (!serverUrl.startsWith("smb://")) { Validation.addError(null,Messages.get("configProperties.backup.serverType.invalid")); renderJSON(ValidationResponse.collectErrors()); } client = new CifsClient(serverUrl, serverDomain, user, passwd); } else { if (!(serverUrl.startsWith("ftp://")|| serverUrl.startsWith("ftps://"))) { Validation.addError(null,Messages.get("configProperties.backup.serverType.invalid")); renderJSON(ValidationResponse.collectErrors()); } client = new FtpClient(serverUrl, user, passwd); } try { client.validate(); }catch (AuthenticationException e ){ Validation.addError(null,Messages.get("configProperties.backup.credential.invalid"),e.getMessage()); }catch (ConnectException e) { Validation.addError(null,Messages.get("configProperties.backup.server.invalid"), e.getMessage()); } if (Validation.hasErrors()) { renderJSON(ValidationResponse.collectErrors()); } else { renderJSON(ValidationResponse.valid(Messages.get("configProperties.backup.testSuccessful"))); } } public static void connectExternalServerIpPort(String ipPortListStr){ if (ipPortListStr.isEmpty()) { renderJSON(ValidationResponse.invalid(Messages.get("configProperties.syslog.serverPort.unavailable",null))); } String[] ipPortList = ipPortListStr.split(","); for (String ipPort : ipPortList) { String[] ipPortSpliter = ipPort.split(":"); String ip = ipPortSpliter[0]; String port = ipPortSpliter[1]; Socket socket = new Socket(); try { socket.connect(new InetSocketAddress(ip,Integer.parseInt(port)),2*1000); socket.close(); }catch (Exception e ){ Validation.addError(null,Messages.get("configProperties.syslog.serverPort.unavailable",ipPort)); } } if (Validation.hasErrors()) { renderJSON(ValidationResponse.collectErrors()); } else { renderJSON(ValidationResponse.valid(Messages.get("configProperties.syslog.serverPort.successful"))); } } public static void validateMailSettings(String server, String port, String username, String password, String enableTls, String authType, String fromAddress, String toAddress) { password = PasswordUtil.decryptedValue(password); if (StringUtils.isBlank(server)) { Validation.addError(null, "configProperties.smtp.server.required"); } else if (!HostNameOrIpAddressCheck.isValidHostNameOrIp(server)) { Validation.addError(null, "configProperties.smtp.server.invalid", server); } if (StringUtils.isNotBlank(port)) { int value = NumberUtils.toInt(port, 0); if (value < 0) { Validation.addError(null, "configProperties.smtp.port.invalid", port); } } if (StringUtils.isBlank(fromAddress)) { Validation.addError(null, "configProperties.smtp.fromAddress.required"); } else if (!Property.VALIDATOR.validateEmail(fromAddress)) { Validation.addError(null, "configProperties.smtp.fromAddress.invalid"); } if (StringUtils.isBlank(toAddress)) { Validation.addError(null, "configProperties.smtp.toAddress.required"); } else if (!Property.VALIDATOR.validateEmail(toAddress)) { Validation.addError(null, "configProperties.smtp.toAddress.invalid"); } if (!StringUtils.equalsIgnoreCase("none", authType)) { if (StringUtils.isBlank(username)) { Validation.addError(null, "configProperties.smtp.username.required"); } if (StringUtils.isBlank(password)) { Validation.addError(null, "configProperties.smtp.password.required"); } } if (Validation.hasErrors()) { renderJSON(ValidationResponse.collectErrors()); } MailSettingsValidator.Settings settings = new MailSettingsValidator.Settings(); settings.server = server; settings.port = getPort(port, enableTls); settings.username = username; settings.password = password; settings.channel = StringUtils.equals("yes", enableTls) ? "starttls" : "clear"; settings.authType = authType; settings.fromAddress = fromAddress; try { MailSettingsValidator.validate(settings, toAddress); } catch (RuntimeException e) { Logger.error(e, "Failed to send email"); Validation.addError(null, "configProperties.smtp.validationFailed", e.getMessage()); } if (Validation.hasErrors()) { renderJSON(ValidationResponse.collectErrors()); } else { renderJSON(ValidationResponse.valid(Messages.get("configProperties.smtp.testSuccessful"))); } } public static String getPasswordValidPromptRule() { String promptString = PasswordUtil.getPasswordValidPromptRules(Constants.PASSWORD_VALID_PROMPT); return promptString; } public static void validatePasswords(@Required String password, @Required String fieldName) { String validation = PasswordUtil.validatePassword(password); if (StringUtils.isNotBlank(validation)) { Validation.addError(fieldName, validation); } if (Validation.hasErrors()) { renderJSON(ValidationResponse.collectErrors()); } else { renderJSON(ValidationResponse.valid()); } } public static void passwords() { ClusterInfo clusterInfo = Common.getClusterInfoWithRoleCheck(); render(clusterInfo); } public static void changePassword(@Required String user, @Required String password, @Required String passwordConfirm) { if (Validation.hasErrors()) { params.flash("user"); validation.keep(); passwords(); } else if (!Common.isClusterStable()) { flash.error(MessagesUtils.get("configProperties.error.clusterNotStable")); params.flash("user"); validation.keep(); passwords(); } else { if (!StringUtils.equals(password, passwordConfirm)) { flash.error(MessagesUtils.get("configProperties.password.doesNotMatch")); } else { try { updateUserPassword(user, password); flash.success(Messages.get("configProperties.passwordChange.success", user)); // We are changing our own password so clear our token forcing us to go to the login page if (StringUtils.equals(Security.getUserInfo().getCommonName(), user)) { Security.clearAuthToken(); } } catch (Exception e) { Logger.error(e, "Failed to change password for user '%s'", user); String message = Common.getUserMessage(e); flash.error(Messages.get("configProperties.passwordChange.error", user, message)); } } params.flash("user"); passwords(); } } private static void updateUserPassword(String user, String password) throws Exception { BourneUtil.getSysClient().password().reset(user, password, false); } }