/*
* Copyright (c) 2015 EMC Corporation
* All Rights Reserved
*/
package controllers;
import com.emc.storageos.security.password.Constants;
import com.emc.storageos.systemservices.impl.validate.PropertiesConfigurationValidator;
import com.emc.vipr.client.exceptions.ServiceErrorException;
import com.emc.vipr.client.exceptions.ViPRException;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.common.net.InetAddresses;
import controllers.deadbolt.Deadbolt;
import controllers.deadbolt.Restrict;
import controllers.deadbolt.Restrictions;
import controllers.infra.ConfigProperties;
import controllers.security.Security;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang.StringUtils;
import play.Logger;
import play.Play;
import play.data.validation.MinSize;
import play.data.validation.Required;
import play.data.validation.Valid;
import play.data.validation.Validation;
import play.mvc.Catch;
import play.mvc.Controller;
import play.mvc.Util;
import play.mvc.With;
import util.*;
import util.validation.HostNameOrIpAddressCheck;
import java.io.File;
import java.io.IOException;
import java.util.Map;
import java.util.Set;
import static controllers.Common.flashException;
@With(Deadbolt.class)
public class Setup extends Controller {
private static boolean isComplete = false;
@Catch(ServiceErrorException.class)
public static void jerseyException(Throwable e) {
flashException(e);
protectPasswords();
params.flash();
if (StringUtils.equals(request.actionMethod, "upload")) {
license();
}
else {
index();
}
}
/**
* Determines if initial setup has been completed.
*
* @return true if initial setup is complete.
*/
public static boolean isInitialSetupComplete() {
if (isComplete) {
return true;
}
isComplete = SetupUtils.isSetupComplete();
return isComplete;
}
/**
* Determines if the product is licensed.
*
* @return true if the product is licensed.
*/
public static boolean isLicensed() {
if (SetupUtils.isOssBuild()) {
return true;
}
return LicenseUtils.isLicensed(false);
}
/**
* Checks to see if setup is complete, and it not locks out non-admin users by redirecting to a static page instead
* of showing a forbidden error.
*/
private static void checkCompleteAndLicensed() {
// If setup is complete, redirect
if (isInitialSetupComplete() && isLicensed()) {
complete();
}
// If this user does not have the right roles, redirect to a static page
if (Security.isSystemAdminOrRestrictedSystemAdmin() == false &&
Security.isSecurityAdminOrRestrictedSecurityAdmin() == false) {
notLicensed();
}
if (!Common.isClusterStable()) {
Maintenance.maintenance(request.url);
}
}
/**
* Redirects to the admin dashboard if setup is complete.
*/
private static void complete() {
Dashboard.index();
}
/**
* Renders a page showing that the product is not licensed. This will be shown to non-admin users if setup has not
* been completed.
*/
public static void notLicensed() {
render();
}
/**
* Displays the initial setup screen. If initial setup is already complete, this will forward to the license page.
*/
public static void index() {
checkCompleteAndLicensed();
if (!isLicensed()) {
license();
}
SetupForm setup = new SetupForm();
setup.loadDefaults();
render(setup);
}
/**
* Saves the initial setup form. This is restricted to admin users.
*
* @param setup
* the initial setup form.
*/
@Restrictions({ @Restrict({ "SYSTEM_ADMIN", "SECURITY_ADMIN" }), @Restrict({ "RESTRICTED_SYSTEM_ADMIN", "RESTRICTED_SECURITY_ADMIN" }) })
public static
void save(@Valid SetupForm setup) {
checkCompleteAndLicensed();
setup.validate();
if (Validation.hasErrors()) {
protectPasswords();
params.flash();
Validation.keep();
index();
}
Map<String, String> properties = getUpdatedProperties(setup);
completeInitialSetup(properties);
}
/**
* Completes initial setup with the given configuration properties.
*
* @param properties
* the properties to update.
*/
private static void completeInitialSetup(Map<String, String> properties) {
SetupUtils.markSetupComplete();
ConfigPropertyUtils.saveProperties(BourneUtil.getSysClient(), properties);
complete();
}
/**
* Skips initial setup, advancing to the license screen. This is only available in DEV mode.
*/
public static void skip() {
if (!Play.mode.isDev()) {
forbidden();
}
checkCompleteAndLicensed();
SetupUtils.markSetupComplete();
license();
}
public static String getPasswordValidPromptRule() {
String promptString = PasswordUtil.getPasswordValidPromptRules(Constants.PASSWORD_VALID_PROMPT);
return promptString;
}
/**
* Validate passwords dynamically from the initial setup form, rendering the result as JSON.
*
* @param setup
* the initial setup form.
*/
@Restrictions({ @Restrict({ "SECURITY_ADMIN" }), @Restrict({ "RESTRICTED_SECURITY_ADMIN" }) })
public static void validatePasswordDynamic(String password, String fieldName) {
boolean passed = true;
if (fieldName.contains("root")) {
fieldName = "setup.rootPassword";
}
if (fieldName.contains("system")) {
fieldName = "setup.systemPasswords";
}
if (PasswordUtil.isNotValid(password)) {
Validation.addError(fieldName + ".value", "setup.password.notValid");
passed = false;
}
if (passed) {
String validation = PasswordUtil.validatePassword(password);
if (StringUtils.isNotBlank(validation)) {
Validation.addError(fieldName + ".value", validation);
}
}
if (Validation.hasErrors()) {
renderJSON(ValidationResponse.collectErrors());
}
else {
renderJSON(ValidationResponse.valid());
}
}
/**
* Validate passwords from the initial setup form, rendering the result as JSON.
*
* @param setup
* the initial setup form.
*/
@Restrictions({ @Restrict({ "SYSTEM_ADMIN", "SECURITY_ADMIN" }), @Restrict({ "RESTRICTED_SYSTEM_ADMIN", "RESTRICTED_SECURITY_ADMIN" }) })
public static
void validatePasswords(SetupForm setup) {
setup.validatePasswords();
if (Validation.hasErrors()) {
renderJSON(ValidationResponse.collectErrors());
}
else {
renderJSON(ValidationResponse.valid());
}
}
/**
* Tests the SMTP settings from the initial setup form, rendering the result as JSON.
*
* @param setup
* the initial setup form.
*/
@Restrictions({ @Restrict({ "SYSTEM_ADMIN", "SECURITY_ADMIN" }), @Restrict({ "RESTRICTED_SYSTEM_ADMIN", "RESTRICTED_SECURITY_ADMIN" }) })
public static
void testSmtpSettings(SetupForm setup) {
setup.validateSmtp();
Validation.required("setup.smtpTo", setup.smtpTo);
Validation.email("setup.smtpTo", setup.smtpTo);
if (Validation.hasErrors()) {
renderJSON(ValidationResponse.collectErrors());
}
MailSettingsValidator.Settings settings = new MailSettingsValidator.Settings();
if (StringUtils.isNotEmpty(setup.nameservers) && !InetAddresses.isInetAddress(setup.smtpServer)) {
Set<String> ips = Sets.newHashSet(setup.nameservers.split(","));
try {
settings.server = DnsUtils.getHostIpAddress(ips, setup.smtpServer).getHostAddress();
} catch (ViPRException e) {
renderJSON(ValidationResponse.invalid(e.getMessage()));
}
}
else {
settings.server = setup.smtpServer;
}
settings.port = ConfigProperties.getPort(setup.smtpPort, setup.smtpEnableTls);
settings.username = setup.smtpUsername;
settings.password = PasswordUtil.decryptedValue(setup.smtpPassword);
settings.channel = StringUtils.equals("yes", setup.smtpEnableTls) ? "starttls" : "clear";
settings.authType = setup.smtpAuthType;
settings.fromAddress = setup.smtpFrom;
try {
MailSettingsValidator.validate(settings, setup.smtpTo);
} catch (RuntimeException e) {
Logger.error(e, "Failed to send email");
Validation.addError(null, "setup.testEmail.failure", e.getMessage());
if (StringUtils.isEmpty(setup.nameservers)) {
Validation.addError(null, "setup.smtpServer.invalidEmptyNameserver");
}
}
if (Validation.hasErrors()) {
renderJSON(ValidationResponse.collectErrors());
}
else {
renderJSON(ValidationResponse.valid(MessagesUtils.get("setup.testEmail.success")));
}
}
/**
* Displays the restarting screen.
*/
@Util
private static void restarting() {
flash.success(MessagesUtils.get("setup.waitStable.description"));
Maintenance.maintenance(Common.reverseRoute(Setup.class, "index"));
}
/**
* Displays the license upload screen.
*/
public static void license() {
checkCompleteAndLicensed();
if (Common.isClusterStable()) {
render();
}
else {
restarting();
}
}
/**
* Renders the cluster state as JSON.
*/
@Restrictions({ @Restrict({ "SYSTEM_ADMIN", "SECURITY_ADMIN" }), @Restrict({ "RESTRICTED_SYSTEM_ADMIN", "RESTRICTED_SECURITY_ADMIN" }) })
public static
void clusterState() {
renderJSON(Common.getClusterInfo());
}
/**
* Uploads a license file. This is restricted to admin users.
*
* @param licenseFile
* the license file.
*/
@Restrictions({ @Restrict({ "SYSTEM_ADMIN", "SECURITY_ADMIN" }), @Restrict({ "RESTRICTED_SYSTEM_ADMIN", "RESTRICTED_SECURITY_ADMIN" }) })
public static
void upload(@Required File licenseFile) {
if (Validation.hasErrors()) {
params.flash();
Validation.keep();
license();
}
try {
String license = FileUtils.readFileToString(licenseFile);
if (StringUtils.isBlank(license)) {
Logger.error("License file is empty");
Validation.addError("setup.licenseFile", MessagesUtils.get("license.uploadFailed"));
params.flash();
Validation.keep();
license();
}
LicenseUtils.updateLicenseText(license);
index();
} catch (IOException e) {
Validation.addError("setup.licenseFile", MessagesUtils.get("license.uploadFailed"));
Logger.error(e, "Failed to read license file");
Validation.keep();
license();
}
}
private static Map<String, String> getProperties() {
Map<String, String> properties = (Map<String, String>) request.args.get("properties");
if (properties == null) {
properties = ConfigPropertyUtils.getPropertiesFromCoordinator();
request.args.put("properties", properties);
}
return properties;
}
private static Map<String, String> getUpdatedProperties(SetupForm setup) {
Map<String, String> properties = Maps.newHashMap();
// Network
properties.put(ConfigProperty.NAMESERVERS, setup.nameservers);
properties.put(ConfigProperty.NTPSERVERS, setup.ntpservers);
// Passwords
properties.put(ConfigProperty.ROOT_PASSWORD, setup.rootPassword.hashedValue());
// Use the same password for proxyuser, svcuser, sysmonitor
properties.put(ConfigProperty.PROXYUSER_PASSWORD, PasswordUtil.decryptedValue(setup.systemPasswords.value));
properties.put(ConfigProperty.SVCUSER_PASSWORD, setup.systemPasswords.hashedValue());
properties.put(ConfigProperty.SYSMONITOR_PASSWORD, setup.systemPasswords.hashedValue());
// SMTP settings
if (StringUtils.isNotBlank(setup.smtpServer)) {
properties.put(ConfigProperty.SMTP_SERVER, setup.smtpServer);
properties.put(ConfigProperty.SMTP_ENABLE_TLS, setup.smtpEnableTls);
properties.put(ConfigProperty.SMTP_FROM_ADDRESS, setup.smtpFrom);
properties.put(ConfigProperty.SMTP_AUTH_TYPE, setup.smtpAuthType);
if (!StringUtils.equalsIgnoreCase(setup.smtpAuthType, "None")) {
properties.put(ConfigProperty.SMTP_USERNAME, setup.smtpUsername);
properties.put(ConfigProperty.SMTP_PASSWORD, PasswordUtil.decryptedValue(setup.smtpPassword));
}
}
if (!SetupUtils.isOssBuild()) {
// ConnectEMC settings
properties.put(ConfigProperty.CONNECTEMC_TRANSPORT, setup.connectEmcTransport);
if (!StringUtils.equalsIgnoreCase(setup.connectEmcTransport, "None")) {
properties.put(ConfigProperty.CONNECTEMC_NOTIFY_EMAIL, setup.connectEmcNotifyEmail);
}
}
return properties;
}
/**
* Protects any passwords on the page by blanking them in case of an error, or encrypting them so they can be
* redisplayed so a user doesn't have to retype them.
*/
private static void protectPasswords() {
String[] fields = { "setup.rootPassword", "setup.systemPasswords" };
for (String field : fields) {
protectPassword(field);
}
protectField("setup.smtpPassword");
}
/**
* Protects a password field by erasing it if there are validation errors, or encrypting it if there aren't.
*
* @param field
* the base password field name.
*/
private static void protectPassword(String field) {
String passwordFieldName = field + ".value";
String confirmFieldName = field + ".confirm";
if (Validation.hasError(passwordFieldName) || Validation.hasError(confirmFieldName)) {
params.remove(passwordFieldName);
params.remove(confirmFieldName);
}
else {
protectField(passwordFieldName);
protectField(confirmFieldName);
}
}
private static void protectField(String field) {
String value = params.get(field);
if (Validation.hasError(field)) {
params.remove(field);
}
else if (StringUtils.isNotBlank(value)) {
params.put(field, PasswordUtil.encryptedValue(value));
}
}
public static class Password {
@Required
@MinSize(8)
public String value;
@Required
@MinSize(8)
public String confirm;
public String hashedValue() {
String decrypted = PasswordUtil.decryptedValue(value);
return PasswordUtil.generateHash(decrypted);
}
public void validate(String fieldName) {
// If local validations have passed, validate against remote api
if (localValidation(fieldName)) {
remoteValidation(fieldName);
}
}
private boolean localValidation(String fieldName) {
boolean passed = true;
if (PasswordUtil.isNotValid(value)) {
Validation.addError(fieldName + ".value", "setup.password.notValid");
passed = false;
}
if (PasswordUtil.isNotValid(confirm)) {
Validation.addError(fieldName + ".confirm", "setup.password.notValid");
passed = false;
}
value = PasswordUtil.decryptedValue(value);
confirm = PasswordUtil.decryptedValue(confirm);
Validation.valid(fieldName, this);
if (!StringUtils.equals(value, confirm)) {
Validation.addError(fieldName + ".value", "setup.password.notEqual");
passed = false;
}
return passed;
}
private void remoteValidation(String fieldName) {
String validation = PasswordUtil.validatePassword(value);
if (StringUtils.isNotBlank(validation)) {
Validation.addError(fieldName + ".value", validation);
}
}
}
public static class SetupForm {
/* Network Page */
@Required
public String nameservers;
@Required
public String ntpservers;
/* Passwords page */
public Password rootPassword = new Password();
public Password systemPasswords = new Password();
/* SMTP page */
public String smtpServer;
public String smtpPort;
public String smtpAuthType;
public String smtpFrom;
public String smtpUsername;
public String smtpPassword;
public String smtpEnableTls;
// For test email settings
public String smtpTo;
public String fieldName;
/* ConnectEMC page */
public String connectEmcTransport;
public String connectEmcNotifyEmail;
public void loadDefaults() {
Map<String, String> properties = getProperties();
nameservers = properties.get(ConfigProperty.NAMESERVERS);
ntpservers = properties.get(ConfigProperty.NTPSERVERS);
smtpServer = properties.get(ConfigProperty.SMTP_SERVER);
smtpPort = properties.get(ConfigProperty.SMTP_PORT);
smtpAuthType = properties.get(ConfigProperty.SMTP_AUTH_TYPE);
smtpUsername = properties.get(ConfigProperty.SMTP_USERNAME);
smtpEnableTls = properties.get(ConfigProperty.SMTP_ENABLE_TLS);
smtpFrom = properties.get(ConfigProperty.SMTP_FROM_ADDRESS);
connectEmcTransport = properties.get(ConfigProperty.CONNECTEMC_TRANSPORT);
connectEmcNotifyEmail = properties.get(ConfigProperty.CONNECTEMC_NOTIFY_EMAIL);
}
public void validate() {
if (!PropertiesConfigurationValidator.validateIpList(nameservers)) {
Validation.addError("setup.nameservers", "configProperties.error.iplist");
}
if (!PropertiesConfigurationValidator.validateIpList(ntpservers)) {
Validation.addError("setup.ntpservers", "configProperties.error.iplist");
}
validatePasswords();
if (StringUtils.isNotBlank(smtpServer) || StringUtils.equalsIgnoreCase(connectEmcTransport, "SMTP")) {
validateSmtp();
}
if (!SetupUtils.isOssBuild()) {
Validation.required("setup.connectEmcTransport", connectEmcTransport);
if (!StringUtils.equalsIgnoreCase(connectEmcTransport, "None")) {
Validation.required("setup.connectEmcNotifyEmail", connectEmcNotifyEmail);
Validation.email("setup.connectEmcNotifyEmail", connectEmcNotifyEmail);
}
}
}
public void validatePasswords() {
rootPassword.validate("setup.rootPassword");
systemPasswords.validate("setup.systemPasswords");
}
public void validateSmtp() {
Validation.required("setup.smtpServer", smtpServer);
if (HostNameOrIpAddressCheck.isValidHostName(smtpServer)) {
if (PropertiesConfigurationValidator.validateIpList(nameservers)) {
Set<String> ips = Sets.newHashSet(nameservers.split(","));
if (!DnsUtils.validateHostname(ips, smtpServer)) {
Validation.addError("setup.smtpServer", "setup.smtpServer.invalidSmtpServer");
}
}
else if (StringUtils.isNotEmpty(nameservers)) {
Validation.addError("setup.nameservers", "setup.smtpServer.invalidNameserver", nameservers);
}
}
if (!HostNameOrIpAddressCheck.isValidHostNameOrIp(smtpServer)) {
Validation.addError("setup.smtpServer", "setup.smtpServer.invalid");
}
if (!StringUtils.isNumeric(smtpPort)) {
Validation.addError("setup.smtpServer", "setup.smtpServer.invalidPort");
}
Validation.required("setup.smtpFrom", smtpFrom);
Validation.email("setup.smtpFrom", smtpFrom);
if (StringUtils.isNotBlank(smtpAuthType) && !StringUtils.equalsIgnoreCase(smtpAuthType, "None")) {
Validation.required("setup.smtpUsername", smtpUsername);
Validation.required("setup.smtpPassword", smtpPassword);
if (PasswordUtil.isNotValid(smtpPassword)) {
Validation.addError("setup.smtpPassword", "setup.password.notValid");
}
}
}
}
}