/*
* Copyright 2015 EMC Corporation
* Copyright 2016 Intel Corporation
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package controllers.auth;
import static com.emc.vipr.client.core.util.ResourceUtils.uris;
import java.net.URI;
import java.util.*;
import com.emc.storageos.cinder.CinderConstants;
import com.emc.storageos.db.client.model.AuthnProvider;
import com.emc.storageos.model.keystone.OpenStackTenantListParam;
import com.emc.storageos.model.keystone.OpenStackTenantParam;
import models.SearchScopes;
import models.TenantsSynchronizationOptions;
import models.datatable.LDAPsourcesDataTable;
import models.datatable.LDAPsourcesDataTable.LDAPsourcesInfo;
import models.datatable.OpenStackTenantsDataTable;
import org.apache.commons.lang.StringUtils;
import org.springframework.util.CollectionUtils;
import play.data.binding.As;
import play.data.validation.MaxSize;
import play.data.validation.MinSize;
import play.data.validation.Required;
import play.data.validation.Validation;
import play.mvc.With;
import util.*;
import com.emc.storageos.model.auth.AuthnCreateParam;
import com.emc.storageos.model.auth.AuthnProviderRestRep;
import com.emc.storageos.model.auth.AuthnUpdateParam;
import com.emc.storageos.model.auth.AuthnUpdateParam.DomainChanges;
import com.emc.storageos.model.auth.AuthnUpdateParam.TenantsSynchronizationOptionsChanges;
import com.emc.storageos.model.auth.AuthnUpdateParam.GroupWhitelistValueChanges;
import com.emc.storageos.model.auth.AuthnUpdateParam.ServerUrlChanges;
import com.emc.storageos.model.auth.AuthnUpdateParam.GroupObjectClassChanges;
import com.emc.storageos.model.auth.AuthnUpdateParam.GroupMemberAttributeChanges;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import controllers.Common;
import controllers.deadbolt.Restrict;
import controllers.deadbolt.Restrictions;
import controllers.util.ViprResourceController;
import controllers.util.FlashException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import util.datatable.DataTablesSupport;
@With(Common.class)
@Restrictions({ @Restrict("SECURITY_ADMIN") })
public class LDAPsources extends ViprResourceController {
private static Logger log = LoggerFactory.getLogger(LDAPsources.class);
private static String authProviderName = "";
private static Boolean authProviderAutoReg = false;
protected static final String SAVED = "LDAPsources.saved";
protected static final String DELETED = "LDAPsources.deleted";
protected static final String FAILED = "LDAPsources.failed";
protected static final String UNKNOWN = "LDAPsources.unknown";
protected static final String MODEL_NAME = "LDAPsources";
public static final String EXPECTED_GEO_VERSION_FOR_LDAP_GROUP_SUPPORT = AuthnProvider.getExpectedGeoVDCVersionForLDAPGroupSupport();
private static final String[] DEFAULT_GROUP_OBJECT_CLASSES = { "groupOfNames", "groupOfUniqueNames", "posixGroup", "organizationalRole" };
private static final String[] DEFAULT_GROUP_MEMBER_ATTRIBUTES = { "member", "uniqueMember", "memberUid", "roleOccupant" };
private static final String KEYSTONE_SERVER_URL = CinderConstants.HTTP_URL + "[IP Address]" + CinderConstants.COLON
+ CinderConstants.OS_ADMIN_PORT
+ CinderConstants.REST_API_VERSION_2;
// Interval delay between each execution in seconds.
private static final String DEFAULT_INTERVAL_DELAY = "900";
// Minimum interval in seconds.
public static final int MIN_INTERVAL_DELAY = 10;
//
// Add reference data so that they can be reference in html template
//
private static void addReferenceData() {
renderArgs.put("authSourceTypeList", Arrays.asList(EnumOption.options(AuthSourceType.values())));
renderArgs.put("adType", AuthSourceType.ad);
renderArgs.put("ldapType", AuthSourceType.ldap);
renderArgs.put("keyStoneType", AuthSourceType.keystone);
renderArgs.put("keystoneServerURL", KEYSTONE_SERVER_URL);
renderArgs.put("defaultInterval", DEFAULT_INTERVAL_DELAY);
renderArgs.put("searchScopeTypeList", SearchScopes.options(SearchScopes.ONELEVEL, SearchScopes.SUBTREE));
renderArgs.put("tenantsOptions", TenantsSynchronizationOptions.options(TenantsSynchronizationOptions.ADDITION, TenantsSynchronizationOptions.DELETION));
renderArgs.put("showLdapGroup", VCenterUtils.checkCompatibleVDCVersion(EXPECTED_GEO_VERSION_FOR_LDAP_GROUP_SUPPORT));
renderArgs.put("tenants", new LDAPSourcesTenantsDataTable());
}
/**
* if it was not redirect from another page, clean flash
*
* @param redirect
*/
public static void list() {
renderArgs.put("dataTable", new LDAPsourcesDataTable());
render();
}
public static void listJson() {
performListJson(AuthnProviderUtils.getAuthnProviders(), new JsonItemOperation());
}
/**
* Shows the dialog for OpenStack tenants editing form.
*
*/
public static void tenantsList() {
renderArgs.put("tenants", new OpenStackTenantsDataTable());
render();
}
/**
* Gets the list of tenants.
*
*/
public static void tenantsListJson() {
List<OpenStackTenantsDataTable.OpenStackTenant> tenants = Lists.newArrayList();
for (OpenStackTenantParam tenant : OpenStackTenantsUtils.getOpenStackTenants()) {
tenants.add(new OpenStackTenantsDataTable.OpenStackTenant(tenant));
}
renderJSON(DataTablesSupport.createJSON(tenants, params));
}
private static List<String> parseMultiLineString(String multiValueList) {
List<String> cleanedValues = new ArrayList();
List<String> list = Arrays.asList(multiValueList.split("\n"));
for (String str : list) {
str = StringUtils.trim(str);
// do not add any blank lines from multiline field
if (StringUtils.isNotBlank(str)) {
cleanedValues.add(str);
}
}
return cleanedValues;
}
/**
* If the argument is null or empty, or contains 1 empty string, return an empty list If the argument contains one
* comma delimited String, split it up Otherwise, return the original list
*/
private static List<String> parseMultiLineInput(String string) {
List<String> result = null;
if ((string == null) || (string.isEmpty())) {
result = Collections.EMPTY_LIST;
}
else if ((StringUtils.isNotEmpty(string)) && StringUtils.contains(string, '\n')) {
result = parseMultiLineString(string);
}
else {
// result = Collections.EMPTY_LIST;
List<String> single = new ArrayList();
single.add(0, string);
result = single;
}
return result;
}
public static void create() {
LDAPsourcesForm ldapSources = new LDAPsourcesForm();
// put all "initial create only" defaults here rather than field initializers
edit(ldapSources);
}
private static void edit(LDAPsourcesForm ldapSources) {
addReferenceData();
render("@edit", ldapSources);
}
@FlashException("list")
public static void edit(String id) {
AuthnProviderRestRep authnProvider = AuthnProviderUtils.getAuthnProvider(id);
if (authnProvider == null) {
flash.error(MessagesUtils.get(UNKNOWN, id));
list();
}
authProviderAutoReg = authnProvider.getAutoRegCoprHDNImportOSProjects();
edit(new LDAPsourcesForm(authnProvider));
}
public static void addTenants(String ldadSourceId, @As(",") String[] ids) {
List<OpenStackTenantParam> tenants = OpenStackTenantsUtils.getOpenStackTenants();
if (ids != null) {
List<String> idList = Arrays.asList(ids);
for (OpenStackTenantParam tenant : tenants) {
if (!idList.contains(tenant.getOsId())) {
tenant.setExcluded(true);
}
}
}
OpenStackTenantListParam params = new OpenStackTenantListParam();
params.setOpenstackTenants(tenants);
OpenStackTenantsUtils.addOpenStackTenants(params);
flash.success(MessagesUtils.get(SAVED, authProviderName));
list();
}
@FlashException(keep = true)
public static void save(LDAPsourcesForm ldapSources) {
ldapSources.validate("ldapSources");
if (Validation.hasErrors()) {
Common.handleError();
}
AuthnProviderRestRep authnProvider = ldapSources.save();
authProviderName = ldapSources.name;
flash.success(MessagesUtils.get(SAVED, ldapSources.name));
if (ldapSources.autoRegCoprHDNImportOSProjects && !authProviderAutoReg) {
renderArgs.put("showDialog", "true");
edit(new LDAPsourcesForm(authnProvider));
}
list();
}
public static void delete(@As(",") String[] ids) {
delete(uris(ids));
}
private static void delete(List<URI> ids) {
performSuccessFail(ids, new DeleteOperation(), DELETED, FAILED);
list();
}
@SuppressWarnings("ClassVariableVisibilityCheck")
public static class LDAPsourcesForm {
public String id;
@Required
@MaxSize(128)
@MinSize(2)
public String name;
@Required
public String mode;
public String description;
public Boolean disable;
public Boolean autoRegCoprHDNImportOSProjects;
public List<String> tenantsSynchronizationOptions;
public String synchronizationInterval;
@Required
public List<String> domains;
public List<String> groupObjectClasses;
public List<String> groupMemberAttributes;
public String groupAttribute;
public List<String> groupWhiteListValues;
@Required
public String managerDn;
public String managerPassword;
public String searchBase;
public String searchFilter;
public String searchScope;
@Required
public List<String> serverUrls;
public LDAPsourcesForm() {
groupObjectClasses = Arrays.asList(DEFAULT_GROUP_OBJECT_CLASSES);
groupMemberAttributes = Arrays.asList(DEFAULT_GROUP_MEMBER_ATTRIBUTES);
renderArgs.put("groupObjectClassesString", StringUtils.join(this.groupObjectClasses, "\n"));
renderArgs.put("groupMemberAttributesString", StringUtils.join(this.groupMemberAttributes, "\n"));
}
public LDAPsourcesForm(AuthnProviderRestRep authnProviderResponse) {
readFrom(authnProviderResponse);
// render the list items to textareas one item per line
renderArgs.put("domainString", StringUtils.join(this.domains, "\n"));
renderArgs.put("groupWhiteListString", StringUtils.join(this.groupWhiteListValues, "\n"));
renderArgs.put("serverUrlsString", StringUtils.join(this.serverUrls, "\n"));
renderArgs.put("groupObjectClassesString", StringUtils.join(this.groupObjectClasses, "\n"));
renderArgs.put("groupMemberAttributesString", StringUtils.join(this.groupMemberAttributes, "\n"));
renderArgs.put("readOnlyGroupAttribute", !isGroupAttributeBlankOrNull(this.groupAttribute));
renderArgs.put("readOnlyCheckboxForAutomaticRegistration", this.autoRegCoprHDNImportOSProjects);
renderArgs.put("readOnlySynchronizationInterval", this.synchronizationInterval);
}
public boolean isNew() {
return StringUtils.isBlank(id);
}
public void readFrom(AuthnProviderRestRep ldapSources) {
this.id = stringId(ldapSources);
this.name = ldapSources.getName();
this.mode = ldapSources.getMode();
this.description = ldapSources.getDescription();
this.disable = ldapSources.getDisable();
this.autoRegCoprHDNImportOSProjects = ldapSources.getAutoRegCoprHDNImportOSProjects();
this.tenantsSynchronizationOptions = Lists.newArrayList(ldapSources.getTenantsSynchronizationOptions());
this.synchronizationInterval = getInterval(ldapSources.getTenantsSynchronizationOptions());
this.domains = Lists.newArrayList(ldapSources.getDomains());
this.groupAttribute = isGroupAttributeBlankOrNull(ldapSources.getGroupAttribute()) ? "" : ldapSources.getGroupAttribute();
this.groupWhiteListValues = Lists.newArrayList(ldapSources.getGroupWhitelistValues());
this.managerDn = ldapSources.getManagerDN();
this.managerPassword = ""; // the platform will never return the real password //NOSONAR
// ("Suppressing Sonar violation of Password Hardcoded. Password is not hardcoded here.")
this.searchBase = ldapSources.getSearchBase();
this.searchFilter = ldapSources.getSearchFilter();
this.serverUrls = Lists.newArrayList(ldapSources.getServerUrls());
this.searchScope = ldapSources.getSearchScope();
this.groupObjectClasses = Lists.newArrayList(ldapSources.getGroupObjectClasses());
this.groupMemberAttributes = Lists.newArrayList(ldapSources.getGroupMemberAttributes());
}
public AuthnProviderRestRep save() {
if (isNew()) {
return create();
} else {
return update();
}
}
private AuthnProviderRestRep update() {
AuthnUpdateParam param = new AuthnUpdateParam();
AuthnProviderRestRep provider = AuthnProviderUtils.getAuthnProvider(this.id);
param.setLabel(this.name);
param.setMode(this.mode);
param.setDescription(StringUtils.trimToNull(this.description));
param.setDisable(this.disable);
param.setAutoRegCoprHDNImportOSProjects(this.autoRegCoprHDNImportOSProjects);
if (this.autoRegCoprHDNImportOSProjects) {
param.setTenantsSynchronizationOptionsChanges(getTenantsSynchronizationOptionsChanges(provider));
}
param.setManagerDn(this.managerDn);
param.setManagerPassword(StringUtils.trimToNull(this.managerPassword));
param.setSearchBase(this.searchBase);
param.setSearchFilter(this.searchFilter);
param.setSearchScope(this.searchScope);
param.setDomainChanges(getDomainChanges(provider));
param.setGroupWhitelistValueChanges(getGroupWhitelistValueChanges(provider));
param.setServerUrlChanges(getServerUrlChanges(provider));
param.setGroupObjectClassChanges(getGroupObjectClassChanges(provider));
param.setGroupMemberAttributeChanges(getGroupMemberAttributeChanges(provider));
if (isGroupAttributeBlankOrNull(provider.getGroupAttribute())) {
param.setGroupAttribute(this.groupAttribute);
return AuthnProviderUtils.forceUpdate(this.id, param);
} else {
return AuthnProviderUtils.update(this.id, param);
}
}
private DomainChanges getDomainChanges(AuthnProviderRestRep provider) {
Set<String> newValues = Sets.newHashSet(parseMultiLineInput(this.domains.get(0)));
Set<String> oldValues = provider.getDomains();
DomainChanges changes = new DomainChanges();
changes.getAdd().addAll(newValues);
changes.getAdd().removeAll(oldValues);
changes.getRemove().addAll(oldValues);
changes.getRemove().removeAll(newValues);
return changes;
}
public static String getInterval(Set<String> tenantsSynchronizationOptions) {
String interval = "";
for (String option : tenantsSynchronizationOptions) {
// There is only ADDITION, DELETION and interval in this StringSet.
if (!AuthnProvider.TenantsSynchronizationOptions.ADDITION.toString().equals(option) &&
!AuthnProvider.TenantsSynchronizationOptions.DELETION.toString().equals(option)) {
interval = option;
}
}
return interval;
}
private TenantsSynchronizationOptionsChanges getTenantsSynchronizationOptionsChanges(AuthnProviderRestRep provider) {
Set<String> newValues;
if (this.tenantsSynchronizationOptions != null) {
newValues = Sets.newHashSet(this.tenantsSynchronizationOptions);
newValues.add(this.synchronizationInterval);
} else {
newValues = Sets.newHashSet(this.synchronizationInterval);
}
Set<String> oldValues = provider.getTenantsSynchronizationOptions();
TenantsSynchronizationOptionsChanges changes = new TenantsSynchronizationOptionsChanges();
changes.getAdd().addAll(newValues);
changes.getAdd().removeAll(oldValues);
changes.getRemove().addAll(oldValues);
changes.getRemove().removeAll(newValues);
return changes;
}
private GroupWhitelistValueChanges getGroupWhitelistValueChanges(AuthnProviderRestRep provider) {
Set<String> oldValues = provider.getGroupWhitelistValues();
Set<String> newValues = Sets.newHashSet(parseMultiLineInput(this.groupWhiteListValues.get(0)));
GroupWhitelistValueChanges changes = new GroupWhitelistValueChanges();
changes.getAdd().addAll(newValues);
changes.getAdd().removeAll(oldValues);
changes.getRemove().addAll(oldValues);
changes.getRemove().removeAll(newValues);
return changes;
}
private ServerUrlChanges getServerUrlChanges(AuthnProviderRestRep provider) {
Set<String> oldValues = provider.getServerUrls();
Set<String> newValues = Sets.newHashSet(parseMultiLineInput(this.serverUrls.get(0)));
ServerUrlChanges changes = new ServerUrlChanges();
changes.getAdd().addAll(newValues);
changes.getAdd().removeAll(oldValues);
changes.getRemove().addAll(oldValues);
changes.getRemove().removeAll(newValues);
return changes;
}
private GroupObjectClassChanges getGroupObjectClassChanges(AuthnProviderRestRep provider) {
Set<String> oldValues = provider.getGroupObjectClasses();
Set<String> newValues = Sets.newHashSet(parseMultiLineInput(this.groupObjectClasses.get(0)));
GroupObjectClassChanges changes = new GroupObjectClassChanges();
changes.getAdd().addAll(newValues);
changes.getAdd().removeAll(oldValues);
changes.getRemove().addAll(oldValues);
changes.getRemove().removeAll(newValues);
return changes;
}
private GroupMemberAttributeChanges getGroupMemberAttributeChanges(AuthnProviderRestRep provider) {
Set<String> oldValues = provider.getGroupMemberAttributes();
Set<String> newValues = Sets.newHashSet(parseMultiLineInput(this.groupMemberAttributes.get(0)));
GroupMemberAttributeChanges changes = new GroupMemberAttributeChanges();
changes.getAdd().addAll(newValues);
changes.getAdd().removeAll(oldValues);
changes.getRemove().addAll(oldValues);
changes.getRemove().removeAll(newValues);
return changes;
}
private AuthnProviderRestRep create() {
AuthnCreateParam param = new AuthnCreateParam();
param.setLabel(this.name);
param.setMode(this.mode);
param.setDescription(StringUtils.trimToNull(this.description));
param.setDisable(this.disable);
param.setAutoRegCoprHDNImportOSProjects(this.autoRegCoprHDNImportOSProjects);
if (this.autoRegCoprHDNImportOSProjects) {
if (tenantsSynchronizationOptions != null) {
param.setTenantsSynchronizationOptions((Sets.newHashSet(this.tenantsSynchronizationOptions)));
} else {
param.setTenantsSynchronizationOptions(Sets.<String> newHashSet());
}
param.getTenantsSynchronizationOptions().add(this.synchronizationInterval);
}
param.setGroupAttribute(this.groupAttribute);
param.setManagerDn(this.managerDn);
param.setManagerPassword(this.managerPassword);
param.setSearchBase(this.searchBase);
param.setSearchFilter(this.searchFilter);
param.setSearchScope(this.searchScope);
param.getDomains().addAll(parseMultiLineInput(this.domains.get(0)));
param.getServerUrls().addAll(parseMultiLineInput(this.serverUrls.get(0)));
param.getGroupWhitelistValues().addAll(parseMultiLineInput(this.groupWhiteListValues.get(0)));
param.getGroupObjectClasses().addAll(parseMultiLineInput(this.groupObjectClasses.get(0)));
param.getGroupMemberAttributes().addAll(parseMultiLineInput(this.groupMemberAttributes.get(0)));
return AuthnProviderUtils.create(param);
}
public void validate(String fieldName) {
Validation.valid(fieldName, this);
if (StringUtils.equals(AuthSourceType.ad.name(), mode)) {
Validation.required(fieldName + ".groupAttribute", groupAttribute);
}
Validation.required(fieldName + ".domains", parseMultiLineInput(this.domains.get(0)));
Validation.required(fieldName + ".serverUrls", parseMultiLineInput(this.serverUrls.get(0)));
if (isNew()) {
Validation.required(fieldName + ".managerPassword", this.managerPassword);
}
if (this.autoRegCoprHDNImportOSProjects ) {
Validation.required(fieldName + ".synchronizationInterval", this.synchronizationInterval);
if (!StringUtils.isNumeric(this.synchronizationInterval) ||
(StringUtils.isNumeric(this.synchronizationInterval) && (Integer.parseInt(this.synchronizationInterval)) < MIN_INTERVAL_DELAY)){
Validation.addError(fieldName + ".synchronizationInterval",
MessagesUtils.get("ldapSources.synchronizationInterval.integerRequired"));
}
}
if (!StringUtils.equals(AuthSourceType.keystone.name(), mode)) {
if (StringUtils.lastIndexOf(this.searchFilter, "=") < 0) {
Validation.addError(fieldName + ".searchFilter",
MessagesUtils.get("ldapSources.searchFilter.equalsRequired"));
}
else {
String afterEquals = StringUtils.substringAfterLast(this.searchFilter, "=");
if (StringUtils.contains(afterEquals, "%u") == false
&& StringUtils.contains(afterEquals, "%U") == false) {
Validation.addError(fieldName + ".searchFilter",
MessagesUtils.get("ldapSources.searchFilter.variableRequired"));
}
}
validateLDAPGroupProperties(fieldName);
}
}
private void validateLDAPGroupProperties(String fieldName) {
boolean groupObjectClassesEmpty = checkIfEmptyList(this.groupObjectClasses);
boolean groupMemberAttributesEmpty = checkIfEmptyList(this.groupMemberAttributes);
// Return error if only one of either objectClasses or memberAttributes
// is entered and other one is empty.
if (groupObjectClassesEmpty ^ groupMemberAttributesEmpty) {
if (groupObjectClassesEmpty) {
Validation.addError(fieldName + ".groupObjectClasses",
MessagesUtils.get(fieldName + ".groupObjectClasses.variableRequired"));
} else {
Validation.addError(fieldName + ".groupMemberAttributes",
MessagesUtils.get(fieldName + ".groupMemberAttributes.variableRequired"));
}
}
}
private boolean checkIfEmptyList(List<String> list) {
boolean isListEmpty = false;
if (CollectionUtils.isEmpty(list) ||
(list.size() == 1 && StringUtils.isBlank(list.get(0)))) {
isListEmpty = true;
}
return isListEmpty;
}
boolean isGroupAttributeBlankOrNull(String groupAttribute) {
boolean isBlankOrNull = false;
if (StringUtils.isBlank(groupAttribute) ||
groupAttribute.equalsIgnoreCase("null")) {
isBlankOrNull = true;
}
return isBlankOrNull;
}
}
public static class LDAPSourcesTenantsDataTable extends OpenStackTenantsDataTable {
public LDAPSourcesTenantsDataTable() {
alterColumn("name").setRenderFunction(null);
}
}
protected static class JsonItemOperation implements ResourceValueOperation<LDAPsourcesInfo, AuthnProviderRestRep> {
@Override
public LDAPsourcesInfo performOperation(AuthnProviderRestRep provider) throws Exception {
return new LDAPsourcesInfo(provider);
}
}
protected static class DeleteOperation implements ResourceIdOperation<Void> {
@Override
public Void performOperation(URI id) throws Exception {
AuthnProviderUtils.delete(id);
return null;
}
}
}