/*
* Copyright 2013 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 com.emc.storageos.api.service.impl.resource;
import static com.emc.storageos.api.mapper.AuthMapper.map;
import static com.emc.storageos.api.mapper.DbObjectMapper.map;
import java.net.*;
import java.util.*;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import com.emc.storageos.api.service.impl.resource.utils.OpenStackSynchronizationTask;
import com.emc.storageos.cinder.CinderConstants;
import com.emc.storageos.db.client.model.*;
import com.emc.storageos.keystone.restapi.utils.KeystoneUtils;
import com.emc.storageos.model.project.ProjectElement;
import com.emc.storageos.model.project.ProjectParam;
import com.emc.storageos.model.tenant.TenantOrgRestRep;
import com.emc.storageos.model.tenant.TenantCreateParam;
import com.emc.storageos.model.tenant.UserMappingParam;
import com.emc.storageos.security.authorization.*;
import com.emc.storageos.security.authorization.Role;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.util.CollectionUtils;
import com.emc.storageos.api.mapper.AuthMapper;
import com.emc.storageos.coordinator.exceptions.CoordinatorException;
import com.emc.storageos.db.client.URIUtil;
import com.emc.storageos.db.client.constraint.AlternateIdConstraint;
import com.emc.storageos.db.client.constraint.NamedElementQueryResultList;
import com.emc.storageos.db.client.constraint.URIQueryResultList;
import com.emc.storageos.db.client.model.AuthnProvider.ProvidersType;
import com.emc.storageos.db.client.model.AuthnProvider.SearchScope;
import com.emc.storageos.db.common.VdcUtil;
import com.emc.storageos.db.exceptions.DatabaseException;
import com.emc.storageos.model.ResourceTypeEnum;
import com.emc.storageos.model.auth.AuthnCreateParam;
import com.emc.storageos.model.auth.AuthnProviderBaseParam;
import com.emc.storageos.model.auth.AuthnProviderList;
import com.emc.storageos.model.auth.AuthnProviderParamsToValidate;
import com.emc.storageos.model.auth.AuthnProviderRestRep;
import com.emc.storageos.model.auth.AuthnUpdateParam;
import com.emc.storageos.model.auth.RoleAssignmentEntry;
import com.emc.storageos.security.authentication.AuthSvcEndPointLocator;
import com.emc.storageos.security.authentication.AuthSvcInternalApiClientIterator;
import com.emc.storageos.security.authentication.StorageOSUser;
import com.emc.storageos.security.authorization.BasePermissionsHelper.UserMapping;
import com.emc.storageos.security.validator.Validator;
import com.emc.storageos.services.OperationTypeEnum;
import com.emc.storageos.svcs.errorhandling.resources.APIException;
import com.emc.storageos.svcs.errorhandling.resources.BadRequestException;
import com.sun.jersey.api.client.ClientResponse;
/**
* API for creating and manipulating authentication providers
*/
@Path("/vdc/admin/authnproviders")
@DefaultPermissions(readRoles = { Role.SECURITY_ADMIN },
writeRoles = { Role.SECURITY_ADMIN })
public class AuthnConfigurationService extends TaggedResource {
private static final Logger _log = LoggerFactory.getLogger(AuthnConfigurationService.class);
private static String FEATURE_NAME_LDAP_GROUP_SUPPORT = "Group support for LDAP Authentication Provider";
@Autowired
private TenantsService _tenantsService;
@Autowired
private AuthSvcEndPointLocator _authSvcEndPointLocator;
private static final URI _URI_RELOAD = URI.create("/internal/reload");
private KeystoneUtils _keystoneUtils;
private OpenStackSynchronizationTask _openStackSynchronizationTask;
private static final String EVENT_SERVICE_TYPE = "authconfig";
@Override
public String getServiceType() {
return EVENT_SERVICE_TYPE;
}
public void setTenantsService(TenantsService tenantsService) {
this._tenantsService = tenantsService;
}
public void setKeystoneUtils(KeystoneUtils keystoneUtils) {
this._keystoneUtils = keystoneUtils;
}
public void setOpenStackSynchronizationTask(OpenStackSynchronizationTask openStackSynchronizationTask) {
this._openStackSynchronizationTask = openStackSynchronizationTask;
}
/**
* Get detailed information for the authentication provider with the given URN
*
* @param id authentication provider URN
* @brief Show authentication provider
* @return Provider details
* @see AuthnProviderRestRep
*/
@GET
@Path("/{id}")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public AuthnProviderRestRep getProvider(@PathParam("id") URI id) {
// TODO: if you need to copy/paste this code, please modify the AbstractPermissionFilter class instead and
// related CheckPermission annotation code to support "TENANT_ADMIN_IN_ANY_TENANT" permission.
StorageOSUser user = getUserFromContext();
if (!_permissionsHelper.userHasGivenRoleInAnyTenant(user, Role.SECURITY_ADMIN, Role.TENANT_ADMIN)) {
throw APIException.forbidden.insufficientPermissionsForUser(user.getName());
}
return map(getProviderById(id, false));
}
/**
* List authentication providers in the zone.
*
* @brief List authentication providers
* @return List of authentication providers
*/
@GET
// no id, just "/"
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
public
AuthnProviderList listProviders() {
// TODO: if you need to copy/paste this code, please modify the AbstractPermissionFilter class instead and
// related CheckPermission annotation code to support "TENANT_ADMIN_IN_ANY_TENANT" permission.
StorageOSUser user = getUserFromContext();
if (!_permissionsHelper.userHasGivenRoleInAnyTenant(user, Role.SECURITY_ADMIN, Role.TENANT_ADMIN)) {
throw APIException.forbidden.insufficientPermissionsForUser(user.getName());
}
NamedElementQueryResultList providers = new NamedElementQueryResultList();
List<URI> uris = _dbClient.queryByType(AuthnProvider.class, true);
List<AuthnProvider> configs = _dbClient.queryObject(AuthnProvider.class, uris);
List<NamedElementQueryResultList.NamedElement> elements =
new ArrayList<NamedElementQueryResultList.NamedElement>(configs.size());
for (AuthnProvider p : configs) {
elements.add(NamedElementQueryResultList.NamedElement.createElement(p.getId(), p.getLabel()));
}
providers.setResult(elements.iterator());
AuthnProviderList list = new AuthnProviderList();
list.getProviders().addAll(map(ResourceTypeEnum.AUTHN_PROVIDER, providers));
return list;
}
@Override
protected DataObject queryResource(URI id) {
return getProviderById(id, false);
}
@Override
protected URI getTenantOwner(URI id) {
return null;
}
/**
* Create an authentication provider.
* The submitted provider element values will be validated.
* <p>
* The minimal set of parameters include: mode, server_urls, manager_dn, manager_password, domains, search_base, search_filter and
* group_attribute
* <p>
*
* @param param AuthnCreateParam The provider representation with all necessary elements.
* @brief Create an authentication provider
* @return Newly created provider details as AuthnProviderRestRep
* @see com.emc.storageos.model.auth.AuthnCreateParam
* @see AuthnProviderRestRep
*/
@POST
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SECURITY_ADMIN })
public AuthnProviderRestRep createProvider(AuthnCreateParam param) {
validateAuthnCreateParam(param);
if (param.getDisable() == null || !param.getDisable()) {
_log.debug("Validating manager dn credentials before provider creation...");
AuthnProviderParamsToValidate validateP = AuthMapper.mapToValidateCreate(param, null);
validateP.setUrls(new ArrayList<String>(param.getServerUrls()));
StringBuilder errorString = new StringBuilder();
if (!Validator.isUsableAuthenticationProvider(validateP, errorString)) {
throw BadRequestException.badRequests.
authnProviderCouldNotBeValidated(errorString.toString());
}
}
AuthnProvider provider = map(param);
provider.setId(URIUtil.createId(AuthnProvider.class));
String mode = provider.getMode();
if (null != mode && AuthnProvider.ProvidersType.keystone.toString().equalsIgnoreCase(mode)) {
provider.setKeys(_keystoneUtils.populateKeystoneToken(provider.getServerUrls(), provider.getManagerDN(), getPassword(provider, null)));
// If the checkbox is checked, then register CoprHD.
if(provider.getAutoRegCoprHDNImportOSProjects()){
_keystoneUtils.registerCoprhdInKeystone(provider.getManagerDN(), provider.getServerUrls(), provider.getManagerPassword());
String interval = _openStackSynchronizationTask.getIntervalFromTenantSyncSet(provider.getTenantsSynchronizationOptions());
// Set default interval time when chosen interval is lower than minimal value.
if (Integer.parseInt(interval) < OpenStackSynchronizationTask.MIN_INTERVAL_DELAY) {
_log.debug("Setting default interval time as chosen interval is lower than minimal value.");
provider.getTenantsSynchronizationOptions().remove(interval);
provider.getTenantsSynchronizationOptions().add(Integer.toString(OpenStackSynchronizationTask.DEFAULT_INTERVAL_DELAY));
}
}
} else {
// Now validate the authn provider to make sure
// either both group object classes and
// member attributes present or
// both of them empty. Throw bad request exception
// if only one of them presents.
validateLDAPGroupProperties(provider);
}
_log.debug("Saving the provider: {}: {}", provider.getId(), provider.toString());
persistProfileAndNotifyChange(provider, true);
auditOp(OperationTypeEnum.CREATE_AUTHPROVIDER, true, null,
provider.toString(), provider.getId().toString());
// TODO:
// recordTenantEvent(RecordableEventManager.EventType.ProfiletCreated,
// "Authentication Profile created", provider.getId());
return map(provider);
}
/**
* Returns new password when old password is different from new one. Otherwise returns old password.
*
* @param provider AuthnProvider with new password.
* @param oldPassword Old password for AuthnProvider.
* @return Password.
*/
private String getPassword(AuthnProvider provider, String oldPassword){
String password = "";
if (null == oldPassword) {
// create case
password = provider.getManagerPassword();
} else {
// Update case
String newPassword = provider.getManagerPassword();
password = (null == newPassword) ? oldPassword :
(oldPassword.equals(newPassword) ? oldPassword : newPassword);
}
return password;
}
public void createTenantsAndProjectsForAutomaticKeystoneRegistration() {
List<URI> osTenantURI = _dbClient.queryByType(OSTenant.class, true);
Iterator<OSTenant> osTenantIter = _dbClient.queryIterativeObjects(OSTenant.class, osTenantURI);
while (osTenantIter.hasNext()) {
OSTenant osTenant = osTenantIter.next();
if (!osTenant.getExcluded()) {
createTenantAndProjectForOpenstackTenant(osTenant);
}
}
}
public void createTenantAndProjectForOpenstackTenant(OSTenant tenant) {
TenantCreateParam param = prepareTenantMappingForOpenstack(tenant);
// Create a tenant.
TenantOrgRestRep tenantOrgRestRep = _tenantsService.createSubTenant(_permissionsHelper.getRootTenant().getId(), param);
// Create a project.
ProjectParam projectParam = new ProjectParam(tenant.getName() + CinderConstants.PROJECT_NAME_SUFFIX);
ProjectElement projectElement = _tenantsService.createProject(tenantOrgRestRep.getId(), projectParam);
_keystoneUtils.tagProjectWithOpenstackId(projectElement.getId(), tenant.getOsId(), tenantOrgRestRep.getId().toString());
}
public TenantCreateParam prepareTenantMappingForOpenstack(OSTenant tenant) {
List<UserMappingParam> userMappings = _keystoneUtils.prepareUserMappings(tenant.getOsId());
TenantCreateParam param = new TenantCreateParam(CinderConstants.TENANT_NAME_PREFIX + " " + tenant.getName(), userMappings);
if (tenant.getDescription() != null) {
param.setDescription(tenant.getDescription());
} else {
param.setDescription(CinderConstants.TENANT_NAME_PREFIX);
}
return param;
}
/**
* Update the parameters of the target authentication provider. The ID is the URN of the authentication provider.
*
* @param param required The representation of the provider parameters to be modified.
* @param id the URN of a ViPR authentication provider
* @param allow Set this field to true to allow modification of the group-attribute field
* @brief Update authentication provider
* @return Provider details with updated values
* @see AuthnProviderRestRep
* @see com.emc.storageos.model.auth.AuthnUpdateParam
*/
@PUT
@Path("/{id}")
@Consumes({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SECURITY_ADMIN })
public AuthnProviderRestRep updateProvider(@PathParam("id") URI id,
@DefaultValue("false") @QueryParam("allow_group_attr_change") boolean allow,
AuthnUpdateParam param) {
AuthnProvider provider = getProviderById(id, false);
ArgValidator.checkEntityNotNull(provider, id, isIdEmbeddedInURL(id));
validateAuthnUpdateParam(param, provider);
// after the overlay, manager dn or manager password may end up null if they were
// not part of the param object. If so we need to grab a copy now from the db model
// object. When we validate, we cannot have null values.
AuthnProviderParamsToValidate validateP = AuthMapper.mapToValidateUpdate(param, provider);
boolean wasAlreadyDisabled = provider.getDisable();
String mode = provider.getMode();
if (null != mode && AuthnProvider.ProvidersType.keystone.toString().equalsIgnoreCase(mode)) {
return updateKeystoneProvider(id, param, provider, validateP);
} else {
return updateAdOrLDAPProvider(id, allow, param, provider, validateP, wasAlreadyDisabled);
}
}
/**
* Checks if the only options that changed were Tenants Synchronization Options.
*
* @param authnProvider old AuthnProvider
* @param param AuthnUpdateParam with params for updated AuthnProvider
* @return true if only Tenants Synchronization Options changed, false otherwise
*/
private boolean isTenantsSynchronizationOptionsChanged(AuthnProvider authnProvider, AuthnUpdateParam param) {
if (param.getTenantsSynchronizationOptionsChanges().getAdd().isEmpty() &&
param.getTenantsSynchronizationOptionsChanges().getRemove().isEmpty()) {
return false;
}
if (param.getLabel() != null && !param.getLabel().equals(authnProvider.getLabel())) {
return false;
}
if (param.getGroupAttribute() != null) {
return false;
}
if (param.getManagerDn() != null) {
return false;
}
if (param.getManagerPassword() != null) {
return false;
}
if (param.getSearchBase() != null) {
return false;
}
if (param.getSearchFilter() != null) {
return false;
}
if (param.getSearchScope() != null) {
return false;
}
if (param.getMode() != null && !param.getMode().equals(authnProvider.getMode())) {
return false;
}
if (param.getLabel() != null) {
return false;
}
if (param.getDescription() != null) {
return false;
}
if (param.getDisable() != null) {
return false;
}
if (param.getAutoRegCoprHDNImportOSProjects() != null) {
return false;
}
if (param.getMaxPageSize() != null) {
return false;
}
if (!param.getGroupWhitelistValueChanges().getAdd().isEmpty() || !param.getGroupWhitelistValueChanges().getRemove().isEmpty()) {
return false;
}
if (!param.getDomainChanges().getAdd().isEmpty() || !param.getDomainChanges().getRemove().isEmpty()) {
return false;
}
if (!param.getServerUrlChanges().getAdd().isEmpty() || !param.getServerUrlChanges().getRemove().isEmpty()) {
return false;
}
if (!param.getGroupObjectClassChanges().getAdd().isEmpty() || !param.getGroupObjectClassChanges().getRemove().isEmpty()) {
return false;
}
if (!param.getGroupMemberAttributeChanges().getAdd().isEmpty() || !param.getGroupMemberAttributeChanges().getRemove().isEmpty()) {
return false;
}
return true;
}
/**
* Updates Keystone Auth Provider with given id.
*
* @param id URI of AuthnProvider
* @param param AuthnUpdateParam with params for updated AuthnProvider
* @param provider AuthnProvider to update
* @param validateP AuthnProviderParamsToValidate with parameters to validate the Provider.
* @return AuthnProviderRestRep updated Provider
*/
private AuthnProviderRestRep updateKeystoneProvider(URI id, AuthnUpdateParam param,
AuthnProvider provider, AuthnProviderParamsToValidate validateP) {
String oldPassword = provider.getManagerPassword();
boolean isAutoRegistered = provider.getAutoRegCoprHDNImportOSProjects();
int synchronizationInterval = OpenStackSynchronizationTask.DEFAULT_INTERVAL_DELAY;
// if Auto Registration was done, get interval value from Tenant Sync Options
if (isAutoRegistered) {
synchronizationInterval = Integer
.parseInt(_openStackSynchronizationTask.getIntervalFromTenantSyncSet(provider.getTenantsSynchronizationOptions()));
}
// if the configured domain has tenant then we can't update that domain unless
// the only options that changed were Tenants Synchronization Options
// (update to Auth Provider was done from Tenants section)
if (!isTenantsSynchronizationOptionsChanged(provider, param)) {
checkForActiveTenantsUsingDomains(provider.getDomains());
}
overlayProvider(provider, param);
// Set old password if new one is a blank or null.
provider.setManagerPassword(getPassword(provider, oldPassword));
// get interval value from updated Tenant Sync Options
int newSynchronizationInterval = Integer
.parseInt(_openStackSynchronizationTask.getIntervalFromTenantSyncSet(provider.getTenantsSynchronizationOptions()));
// Whenever new interval value is below minimum, then set default interval time for keystone auth provider.
if (newSynchronizationInterval < OpenStackSynchronizationTask.MIN_INTERVAL_DELAY) {
_log.debug("Setting default interval time as chosen interval is lower than minimal value.");
provider.getTenantsSynchronizationOptions().remove(Integer.toString(newSynchronizationInterval));
provider.getTenantsSynchronizationOptions().add(Integer.toString(OpenStackSynchronizationTask.DEFAULT_INTERVAL_DELAY));
}
if (!provider.getDisable()) {
_log.debug("Validating provider before modification...");
validateP.setUrls(new ArrayList<String>(provider.getServerUrls()));
StringBuilder errorString = new StringBuilder();
if (!Validator.isUsableAuthenticationProvider(validateP, errorString)) {
throw BadRequestException.badRequests.authnProviderCouldNotBeValidated(errorString.toString());
}
}
provider.setKeys(_keystoneUtils.populateKeystoneToken(provider.getServerUrls(), provider.getManagerDN(),
getPassword(provider, oldPassword)));
_log.debug("Saving to the DB the updated provider: {}", provider.toString());
persistProfileAndNotifyChange(provider, false);
// if the Auto Registration was not done before, then register CoprHD in Keystone
if (provider.getAutoRegCoprHDNImportOSProjects() && !isAutoRegistered) {
_keystoneUtils.registerCoprhdInKeystone(provider.getManagerDN(), provider.getServerUrls(), provider.getManagerPassword());
}
// if the Automatic Registration is selected and new interval value is not the same as the old one,
// then reschedule the task
if (isAutoRegistered && synchronizationInterval != newSynchronizationInterval) {
_openStackSynchronizationTask.rescheduleTask(newSynchronizationInterval);
}
auditOp(OperationTypeEnum.UPDATE_AUTHPROVIDER, true, null,
provider.getId().toString(), provider.toString());
return map(getProviderById(id, false));
}
private AuthnProviderRestRep updateAdOrLDAPProvider(URI id, boolean allow,
AuthnUpdateParam param, AuthnProvider provider,
AuthnProviderParamsToValidate validateP, boolean wasAlreadyDisabled) {
// copy existing domains in separate set
StringSet existingDomains = new StringSet(provider.getDomains());
// up front check to see if we are modifying the group attribute.
// this may trigger an audit.
boolean groupAttributeModified = false;
if (StringUtils.isNotBlank(param.getGroupAttribute()) &&
StringUtils.isNotBlank(provider.getGroupAttribute()) &&
!provider.getGroupAttribute().equalsIgnoreCase(param.getGroupAttribute())) {
if (!allow) {
throw APIException.badRequests.
modificationOfGroupAttributeNotAllowed(param.getGroupAttribute());
} else {
_log.warn("GROUP ATTRIBUTE FOR PROVIDER {} {} IS BEING " +
"MODIFIED USING THE FORCE FLAG, TENANT MAPPINGS, " +
"ROLE ASSIGNMENTS OR PROJECT ACLS MAY BE AFFECTED", provider.getLabel(),
provider.getId().toString());
groupAttributeModified = true;
}
}
overlayProvider(provider, param);
// after overlay, copy new set of domains
StringSet newDomains = new StringSet(provider.getDomains());
// do a diff between the two sets and check any domain
// that was removed
existingDomains.replace(newDomains);
Set<String> removed = existingDomains.getRemovedSet();
if (removed != null) {
verifyDomainsIsNotInUse(new StringSet(removed));
}
if (!provider.getDisable()) {
_log.debug("Validating provider before modification...");
validateP.setUrls(new ArrayList<String>(provider.getServerUrls()));
StringBuilder errorString = new StringBuilder();
if (!Validator.isUsableAuthenticationProvider(validateP, errorString)) {
throw BadRequestException.badRequests.
authnProviderCouldNotBeValidated(errorString.toString());
}
} else if (!wasAlreadyDisabled) {
// if we are disabling it and it wasn't already disabled, then
// check if its domains still be in use.
verifyDomainsIsNotInUse(provider.getDomains());
}
// Now validate the authn provider to make sure
// either both group object classes and
// member attributes present or
// both of them empty. Throw bad request exception
// if only one of them presents.
validateLDAPGroupProperties(provider);
_log.debug("Saving to the DB the updated provider: {}", provider.toString());
persistProfileAndNotifyChange(provider, false);
if (groupAttributeModified) {
auditOp(OperationTypeEnum.UPDATE_AUTHPROVIDER_GROUP_ATTR, true, null,
provider.getId().toString(), provider.toString());
} else {
auditOp(OperationTypeEnum.UPDATE_AUTHPROVIDER, true, null,
provider.getId().toString(), provider.toString());
}
return map(getProviderById(id, false));
}
/**
* Overlay an existing AuthnConfiguration object using a new AuthnConfiguration
* update parameter. Regardless of whether the new AuthnConfiguration update
* param's attribute is null or not, it will be used to overwrite existingi
* AuthnConfiguration object's attribute or merge with existing AuthnConfiguration
* object's attribute, because DbClient#persistObject supports partial writes.
*
* @param authn
* The existing AuthnConfiguration object
*
* @param param
* AuthnConfiguration update param to overlay existing AuthnConfiguration
* object
*/
private void overlayProvider(AuthnProvider authn, AuthnUpdateParam param) {
if (param == null) {
return;
}
authn.setGroupAttribute(param.getGroupAttribute());
if (param.getGroupWhitelistValueChanges() != null) {
StringSet ssOld = authn.getGroupWhitelistValues();
if (ssOld == null) {
ssOld = new StringSet();
}
if (param.getGroupWhitelistValueChanges().getAdd() != null) {
ssOld.addAll(param.getGroupWhitelistValueChanges().getAdd());
}
if (param.getGroupWhitelistValueChanges().getRemove() != null) {
ssOld.removeAll(new HashSet<String>(param.getGroupWhitelistValueChanges().getRemove()));
}
authn.setGroupWhitelistValues(ssOld);
}
if (param.getDomainChanges() != null) {
StringSet ssOld = authn.getDomains();
if (ssOld == null) {
ssOld = new StringSet();
}
Set<String> toAdd = param.getDomainChanges().getAdd();
if (toAdd != null) {
for (String s : toAdd) {
// When Authn provider is added, the domains are converted to lower case, we need to do the same when update provider.
// This change is made to fix bug CTRL-5076
ssOld.add(s.toLowerCase());
}
}
Set<String> toRemove = param.getDomainChanges().getRemove();
ssOld.removeAll(toRemove);
// We shouldn't convert toRemove to lower case. It will disallow us removing the domain we don't want, moreover it might remove
// a entry we want to keep.
authn.setDomains(ssOld);
}
if (param.getManagerDn() != null) {
authn.setManagerDN(param.getManagerDn());
}
authn.setManagerPassword(param.getManagerPassword());
authn.setSearchBase(param.getSearchBase());
authn.setSearchFilter(param.getSearchFilter());
authn.setSearchScope(param.getSearchScope());
if (param.getServerUrlChanges() != null) {
StringSet ssOld = authn.getServerUrls();
if (ssOld == null) {
ssOld = new StringSet();
}
if (param.getServerUrlChanges().getAdd() != null) {
ssOld.addAll(param.getServerUrlChanges().getAdd());
}
if (param.getServerUrlChanges().getRemove() != null) {
ssOld.removeAll(new HashSet<String>(param.getServerUrlChanges().getRemove()));
}
if (ssOld.isEmpty()) {
ArgValidator.checkFieldNotEmpty(ssOld,
"Attempt to remove the last url is not allowed. At least one url must be in the provider.");
}
authn.setServerUrls(ssOld);
}
if (param.getMode() != null
&& param.getMode().equals(AuthnProvider.ProvidersType.keystone.toString())
&& param.getTenantsSynchronizationOptionsChanges() != null) {
StringSet oldOptions = authn.getTenantsSynchronizationOptions();
if (oldOptions == null) {
oldOptions = new StringSet();
}
if (param.getTenantsSynchronizationOptionsChanges().getAdd() != null) {
oldOptions.addAll(param.getTenantsSynchronizationOptionsChanges().getAdd());
}
if (param.getTenantsSynchronizationOptionsChanges().getRemove() != null) {
oldOptions.removeAll(new HashSet<String>(param.getTenantsSynchronizationOptionsChanges().getRemove()));
}
if (oldOptions.isEmpty()) {
ArgValidator.checkFieldNotEmpty(oldOptions,
"Interval cannot be empty. Please provide the value.");
}
authn.setTenantsSynchronizationOptions(oldOptions);
}
if (param.getMode() != null) {
authn.setMode(param.getMode());
}
authn.setLabel(param.getLabel());
authn.setDescription(param.getDescription());
authn.setDisable(param.getDisable() != null ? param.getDisable() : authn.getDisable());
authn.setAutoRegCoprHDNImportOSProjects(param.getAutoRegCoprHDNImportOSProjects() != null ? param.getAutoRegCoprHDNImportOSProjects()
: authn.getAutoRegCoprHDNImportOSProjects());
authn.setMaxPageSize(param.getMaxPageSize());
if (param.getGroupObjectClassChanges() != null) {
StringSet ssOld = authn.getGroupObjectClassNames();
if (ssOld == null) {
ssOld = new StringSet();
}
if (param.getGroupObjectClassChanges().getRemove() != null) {
ssOld.removeAll(new HashSet<String>(param.getGroupObjectClassChanges().getRemove()));
}
if (param.getGroupObjectClassChanges().getAdd() != null) {
ssOld.addAll(param.getGroupObjectClassChanges().getAdd());
}
authn.setGroupObjectClassNames(ssOld);
}
if (param.getGroupMemberAttributeChanges() != null) {
StringSet ssOld = authn.getGroupMemberAttributeTypeNames();
if (ssOld == null) {
ssOld = new StringSet();
}
if (param.getGroupMemberAttributeChanges().getRemove() != null) {
ssOld.removeAll(new HashSet<String>(param.getGroupMemberAttributeChanges().getRemove()));
}
if (param.getGroupMemberAttributeChanges().getAdd() != null) {
ssOld.addAll(param.getGroupMemberAttributeChanges().getAdd());
}
authn.setGroupMemberAttributeTypeNames(ssOld);
}
}
/**
* check if given domains are in use or not,
*
* if any of them is in use, throw exception.
*
* @param domains
*/
private void verifyDomainsIsNotInUse(StringSet domains) {
// check that there are no active tenants with a mapping for given domain(s).
checkForActiveTenantsUsingDomains(domains);
// check that there are no users/groups from given domain(s) has vdc role(s).
checkForVdcRolesUsingDomains(domains);
// check tenants that no users/groups from given domain(s) has tenant role(s).
checkForTenantRolesUsingDomains(domains);
// check if any user attributes group using the domains.
checkForUserGroupsUsingDomains(domains);
}
/**
* Queries all tenants in the system for which the passed in domain set
* has a match in their user mapping. If so, will throw an exception.
* Else will just return normally.
*
* @param domains the domains to check
*/
private void checkForActiveTenantsUsingDomains(StringSet domains) {
// check that there are no active tenants with a mapping for this
// provider's domain(s).
List<URI> matchingTenants = new ArrayList<URI>();
for (String domainToCheck : domains) {
Map<URI, List<UserMapping>> mappings = _permissionsHelper.getAllUserMappingsForDomain(domainToCheck);
Set<URI> tenantIDset;
if (mappings == null) {
_log.debug("No matching tenant found for domain {}", domainToCheck);
continue;
}
tenantIDset = mappings.keySet();
_log.debug("{} matching tenants found for domain {}", tenantIDset.size(), domainToCheck);
List<TenantOrg> tenants = _dbClient.queryObject(TenantOrg.class,
new ArrayList<URI>(tenantIDset));
if (tenants != null) {
for (TenantOrg tenant : tenants) {
if (!tenant.getInactive()) {
matchingTenants.add(tenant.getId());
}
}
}
}
if (!matchingTenants.isEmpty()) {
throw APIException.badRequests.cannotDeleteAuthProviderWithTenants(matchingTenants.size(),
matchingTenants);
}
}
/**
* check local vdc to see if any vdc role assignments belongs to the passed in domain set.
* if so, throw an exception;
* else, return silently.
*
* @param domains the domains to check
*/
private void checkForVdcRolesUsingDomains(StringSet domains) {
// get local vdc's role assignments
VirtualDataCenter localVdc = VdcUtil.getLocalVdc();
List<RoleAssignmentEntry> vdcRoles =
_permissionsHelper.convertToRoleAssignments(localVdc.getRoleAssignments(), true);
List<String> matchingUsers = checkRolesUsingDomains(vdcRoles, domains);
if (!matchingUsers.isEmpty()) {
throw APIException.badRequests.cannotDeleteAuthProviderWithVdcRoles(matchingUsers.size(), matchingUsers);
}
}
/**
* check tenants to see if any tenant role assignments belongs to the passed in domain set.
* if so, throw an exception;
* else, return silently.
*
* @param domains the domains to check
*/
private void checkForTenantRolesUsingDomains(StringSet domains) {
List<URI> tenantURIList = _dbClient.queryByType(TenantOrg.class, true);
for (URI tenantURI : tenantURIList) {
TenantOrg tenant = _dbClient.queryObject(TenantOrg.class, tenantURI);
_log.debug("checking " + tenant.getLabel());
List<RoleAssignmentEntry> tenantRoles =
_permissionsHelper.convertToRoleAssignments(tenant.getRoleAssignments(), false);
List<String> matchingUsers = checkRolesUsingDomains(tenantRoles, domains);
if (!matchingUsers.isEmpty()) {
throw APIException.badRequests.cannotDeleteAuthProviderWithTenantRoles(tenant.getLabel(), matchingUsers.size(),
matchingUsers);
}
}
}
/**
* compare role assignments against domain(s), return matching users.
*
* @param roleAssignments
* @param domains
*/
private List<String> checkRolesUsingDomains(List<RoleAssignmentEntry> roleAssignments, StringSet domains) {
List<String> matchingUsers = new ArrayList<String>();
for (RoleAssignmentEntry roleAssignment : roleAssignments) {
String idOrGroup = !StringUtils.isEmpty(roleAssignment.getSubjectId()) ?
roleAssignment.getSubjectId() : roleAssignment.getGroup();
_log.debug("checking " + idOrGroup);
String domain = "";
if (idOrGroup.lastIndexOf("@") != -1) {
domain = idOrGroup.substring(idOrGroup.lastIndexOf("@") + 1);
} else {
continue;
}
for (String domainToCheck : domains) {
if (domainToCheck.equalsIgnoreCase(domain)) {
matchingUsers.add(idOrGroup);
}
}
}
return matchingUsers;
}
/**
* Delete the provider with the provided id.
* Authentication services will no longer use this provider for authentication.
*
* @param id the URN of a ViPR provider authentication to be deleted
* @brief Delete authentication provider
* @return No data returned in response body
*/
@DELETE
@Path("/{id}")
@Produces({ MediaType.APPLICATION_XML, MediaType.APPLICATION_JSON })
@CheckPermission(roles = { Role.SECURITY_ADMIN })
public Response deleteProvider(@PathParam("id") URI id) {
AuthnProvider provider = getProviderById(id, false);
ArgValidator.checkEntityNotNull(provider, id, isIdEmbeddedInURL(id));
checkForActiveTenantsUsingDomains(provider.getDomains());
if (!AuthnProvider.ProvidersType.keystone.toString()
.equalsIgnoreCase(provider.getMode())) {
checkForVdcRolesUsingDomains(provider.getDomains());
checkForUserGroupsUsingDomains(provider.getDomains());
verifyDomainsIsNotInUse(provider.getDomains());
} else {
_openStackSynchronizationTask.stopSynchronizationTask();
// Remove all OSTenant objects from DB when Keystone Provider is deleted.
List<URI> osTenantURIs = _dbClient.queryByType(OSTenant.class, true);
List<OSTenant> tenants = _dbClient.queryObject(OSTenant.class, osTenantURIs);
for (OSTenant osTenant : tenants) {
_dbClient.removeObject(osTenant);
}
// Delete Cinder endpoints.
_keystoneUtils.deleteCinderEndpoints(provider.getManagerDN(), provider.getServerUrls(), provider.getManagerPassword());
}
_dbClient.removeObject(provider);
notifyChange();
// TODO - record event
auditOp(OperationTypeEnum.DELETE_AUTHPROVIDER, true, null,
provider.getId().toString());
return Response.ok().build();
}
/**
* @param id the URN of a ViPR authentication provider to get details from
* @param checkInactive If true, make sure that provider is not inactive
* @return AuthnProvider object for the given id
*/
private AuthnProvider getProviderById(URI id, boolean checkInactive) {
if (id == null) {
_log.debug("Provider ID is NULL");
return null;
}
_log.debug("Provider ID is {}", id.toString());
AuthnProvider provider = _permissionsHelper.getObjectById(id, AuthnProvider.class);
ArgValidator.checkEntity(provider, id, isIdEmbeddedInURL(id), checkInactive);
return provider;
}
/**
* Checks if an authn provider exists for the given domain
*
* @param domain
* @return true if a provider exists, false otherwise
*/
private boolean doesProviderExistForDomain(String domain) {
URIQueryResultList providers = new URIQueryResultList();
try {
_dbClient.queryByConstraint(
AlternateIdConstraint.Factory.getAuthnProviderDomainConstraint(domain),
providers);
} catch (DatabaseException ex) {
_log.error("Could not query for authn providers to check for existing domain {}", domain, ex.getStackTrace());
throw ex;
}
return providers.iterator().hasNext();
}
private void validateAuthnProviderParam(AuthnProviderBaseParam param, AuthnProvider provider,
Set<String> server_urls, Set<String> domains, Set<String> group_whitelist_values) {
if (param.getLabel() != null && !param.getLabel().isEmpty()) {
if (provider == null || !provider.getLabel().equalsIgnoreCase(param.getLabel())) {
checkForDuplicateName(param.getLabel(), AuthnProvider.class);
}
}
if (param.getMode() != null) {
ArgValidator.checkFieldNotEmpty(param.getMode(), "mode");
try {
ProvidersType.valueOf(param.getMode());
} catch (IllegalArgumentException ex) {
throw APIException.badRequests.invalidParameterWithCause("mode", param.getMode(), ex);
}
}
if (param.getManagerDn() != null) {
ArgValidator.checkFieldNotEmpty(param.getManagerDn(), "manager_dn");
}
if (param.getManagerPassword() != null) {
ArgValidator.checkFieldNotEmpty(param.getManagerPassword(), "manager_password");
}
// validate syntax of the provided parameters for POST and PUT operations
if (domains != null) {
ArgValidator.checkFieldNotEmpty(domains, "domains");
for (String domain : domains) {
ArgValidator.checkFieldNotEmpty(domain, "domain");
if (provider == null || !provider.getDomains().contains(domain.toLowerCase())) {
if (doesProviderExistForDomain(domain)) {
throw APIException.badRequests.domainAlreadyExists(domain);
}
}
}
}
if (param.getGroupAttribute() != null) {
ArgValidator.checkFieldNotEmpty(param.getGroupAttribute(), "group_attribute");
}
// Just validate that the mode is one of the enums
if (group_whitelist_values != null) {
for (String groupName : group_whitelist_values) {
ArgValidator.checkFieldNotEmpty(groupName, "group_whitelist_values");
}
}
if (!AuthnProvider.ProvidersType.keystone.toString().equalsIgnoreCase(param.getMode())) {
if (server_urls != null) {
boolean isNonSecure = false;
boolean isSecure = false;
for (String url : server_urls) {
ArgValidator.checkFieldNotEmpty(url, "server_urls");
String lowerCaseUrl = url.toLowerCase();
if (lowerCaseUrl.startsWith("ldap://")) {
isNonSecure = true;
} else if (lowerCaseUrl.startsWith("ldaps://")) {
isSecure = true;
} else {
throw APIException.badRequests.invalidParameter("server_url", url);
}
if (isNonSecure && isSecure) {
throw APIException.badRequests.cannotMixLdapAndLdapsUrls();
}
}
}
String searchFilter = param.getSearchFilter();
if (searchFilter != null) {
if (!searchFilter.contains("=")) {
throw APIException.badRequests.searchFilterMustContainEqualTo();
}
String afterEqual = searchFilter.substring(searchFilter.indexOf("="));
if (!afterEqual.contains("%u") && !afterEqual.contains("%U")) {
throw APIException.badRequests.searchFilterMustContainPercentU();
}
}
String searchScope = param.getSearchScope();
if (searchScope != null) {
try {
// Just validate that the scope is one of the enums
SearchScope.valueOf(param.getSearchScope());
} catch (IllegalArgumentException ex) {
throw APIException.badRequests.invalidParameterWithCause("search_scope", param.getSearchScope(), ex);
}
}
if (param.getSearchBase() != null) {
ArgValidator.checkFieldNotEmpty(param.getSearchBase(), "search_base");
}
if (param.getMaxPageSize() != null) {
if (param.getMaxPageSize() <= 0) {
throw APIException.badRequests.parameterMustBeGreaterThan("max_page_size", 0);
}
}
} else {
String managerDn = param.getManagerDn();
if (managerDn != null) {
if (!managerDn.contains("=")) {
throw APIException.badRequests.managerDNMustcontainEqualTo();
}
if (!(managerDn.contains("username") || managerDn.contains("userName"))
|| !(managerDn.contains("tenantName") || managerDn.contains("tenantname"))) {
throw APIException.badRequests.managerDNMustcontainUserNameAndTenantName();
}
}
}
}
private void validateAuthnCreateParam(AuthnCreateParam param) {
if (param == null) {
throw APIException.badRequests.resourceEmptyConfiguration("authn provider");
}
_log.debug("Zone authentication create param: {}", param);
ArgValidator.checkFieldNotNull(param.getMode(), "mode");
ArgValidator.checkFieldNotNull(param.getManagerDn(), "manager_dn");
ArgValidator.checkFieldNotNull(param.getManagerPassword(), "manager_password");
// The syntax for search_filter will be checked in the following section of this function
// Check that the LDAP server URL is present.
// The syntax will be checked in the following section of this function
ArgValidator.checkFieldNotEmpty(param.getServerUrls(), "server_urls");
// The domains tag must be present in any new profile
ArgValidator.checkFieldNotNull(param.getDomains(), "domains");
if (!AuthnProvider.ProvidersType.keystone.toString().equalsIgnoreCase(param.getMode())) {
ArgValidator.checkFieldNotNull(param.getSearchFilter(), "search_filter");
ArgValidator.checkFieldNotNull(param.getSearchBase(), "search_base");
} else {
ensureSingleKeystoneProvider(param);
}
checkIfCreateLDAPGroupPropertiesSupported(param);
validateAuthnProviderParam(param, null, param.getServerUrls(), param.getDomains(),
param.getGroupWhitelistValues());
}
/**
* Ensures that there exists a single keystone provider always.
*
* Requests for keystone authentication provider will be coming from OpenStack
* environment, since it is not possible to figure out which keystone provider to
* use for which request, hence only single keystone authentication provider is
* supported.
*
* @param param
*/
private void ensureSingleKeystoneProvider(AuthnCreateParam param) {
List<URI> allProviders = _dbClient.queryByType(AuthnProvider.class, true);
for (URI providerURI : allProviders) {
AuthnProvider provider = getProviderById(providerURI, true);
if (AuthnProvider.ProvidersType.keystone.toString().equalsIgnoreCase(provider.getMode().toString())) {
throw APIException.badRequests.keystoneProviderAlreadyPresent();
}
}
}
private void validateAuthnUpdateParam(AuthnUpdateParam param, AuthnProvider provider) {
if (param == null) {
throw APIException.badRequests.resourceEmptyConfiguration("authn provider");
}
_log.debug("Vdc authentication update param: {}", param);
Set<String> server_urls = null;
Set<String> domains = null;
Set<String> group_whitelist_values = null;
if (param.getServerUrlChanges() != null && !param.getServerUrlChanges().getAdd().isEmpty()) {
server_urls = param.getServerUrlChanges().getAdd();
}
// validate syntax of the provided parameters for POST and PUT operations
if (param.getDomainChanges() != null && !param.getDomainChanges().getAdd().isEmpty()) {
domains = param.getDomainChanges().getAdd();
}
if (param.getGroupWhitelistValueChanges() != null &&
!param.getGroupWhitelistValueChanges().getAdd().isEmpty()) {
group_whitelist_values = param.getGroupWhitelistValueChanges().getAdd();
}
checkIfUpdateLDAPGroupPropertiesSupported(param);
validateAuthnProviderParam(param, provider, server_urls, domains, group_whitelist_values);
}
/**
* Call the internode URI on all authSvc endpoints to reload the auth handlers
*/
private void notifyChange() {
try {
AuthSvcInternalApiClientIterator authSvcItr = new AuthSvcInternalApiClientIterator(_authSvcEndPointLocator, _coordinator);
while (authSvcItr.hasNext()) {
String endpoint = authSvcItr.peek().toString();
try {
ClientResponse response = authSvcItr.post(_URI_RELOAD, null);
if (response.getStatus() != ClientResponse.Status.OK.getStatusCode()) {
_log.error("Failed to reload authN providers on endpoint {} response {}", endpoint, response.toString());
}
} catch (Exception e) {
_log.error("Caught exception trying to reload an authsvc on {} continuing", endpoint, e);
}
}
} catch (CoordinatorException e) {
_log.error("Caught coordinator exception trying to find an authsvc endpoint", e);
}
}
/**
* update the timestamp and notify
*/
private synchronized void persistProfileAndNotifyChange(AuthnProvider modifiedProvider,
boolean newObject) {
modifiedProvider.setLastModified(System.currentTimeMillis());
if (newObject) {
_dbClient.createObject(modifiedProvider);
} else {
_dbClient.persistObject(modifiedProvider);
}
notifyChange();
}
@SuppressWarnings("unchecked")
@Override
public Class<AuthnProvider> getResourceClass() {
return AuthnProvider.class;
}
@Override
protected ResourceTypeEnum getResourceType() {
return ResourceTypeEnum.AUTHN_PROVIDER;
}
/***
* Make sure all the VDCs in the federation are in the minimum expected version
* for using the ldap group support.
*
* @param createParam set of group object classes.
*/
private void checkIfCreateLDAPGroupPropertiesSupported(AuthnCreateParam createParam) {
boolean checkCompatibleVersion = false;
if (createParam != null) {
if (StringUtils.isNotEmpty(createParam.getGroupAttribute())) {
checkCompatibleVersion = true;
} else if (!CollectionUtils.isEmpty(createParam.getGroupWhitelistValues())) {
checkCompatibleVersion = true;
} else if (!CollectionUtils.isEmpty(createParam.getGroupObjectClasses())) {
checkCompatibleVersion = true;
} else if (!CollectionUtils.isEmpty(createParam.getGroupMemberAttributes())) {
checkCompatibleVersion = true;
}
}
if (checkCompatibleVersion) {
checkCompatibleVersionForLDAPGroupSupport();
}
}
/***
* Make sure all the VDCs in the federation are in the minimum expected version
* for using the ldap group support.
*
* @param updateParam set of group object classes.
*/
private void checkIfUpdateLDAPGroupPropertiesSupported(AuthnUpdateParam updateParam) {
boolean checkCompatibleVersion = false;
if (updateParam != null) {
if (StringUtils.isNotBlank(updateParam.getGroupAttribute())) {
checkCompatibleVersion = true;
} else if (updateParam.getGroupWhitelistValueChanges() != null &&
(!CollectionUtils.isEmpty(updateParam.getGroupWhitelistValueChanges().getAdd()) ||
!CollectionUtils.isEmpty(updateParam.getGroupWhitelistValueChanges().getRemove()))) {
checkCompatibleVersion = true;
} else if (updateParam.getGroupObjectClassChanges() != null &&
(!CollectionUtils.isEmpty(updateParam.getGroupObjectClassChanges().getAdd()) ||
!CollectionUtils.isEmpty(updateParam.getGroupObjectClassChanges().getRemove()))) {
checkCompatibleVersion = true;
} else if (updateParam.getGroupMemberAttributeChanges() != null &&
(!CollectionUtils.isEmpty(updateParam.getGroupMemberAttributeChanges().getAdd()) ||
!CollectionUtils.isEmpty(updateParam.getGroupMemberAttributeChanges().getRemove()))) {
checkCompatibleVersion = true;
}
}
if (checkCompatibleVersion) {
checkCompatibleVersionForLDAPGroupSupport();
}
}
/**
* Check if all the VDCs in the federation are in the same expected
* or minimum supported version for this api.
*
*/
private void checkCompatibleVersionForLDAPGroupSupport() {
if (!_dbClient.checkGeoCompatible(AuthnProvider.getExpectedGeoVDCVersionForLDAPGroupSupport())) {
throw APIException.badRequests.incompatibleGeoVersions(AuthnProvider.getExpectedGeoVDCVersionForLDAPGroupSupport(),
FEATURE_NAME_LDAP_GROUP_SUPPORT);
}
}
/***
* Validate the ldap group properties (object classes and member attributes)
* and throws bad request exception if either one of object classes
* or member attributes present in the authn provider.
*
* @param authnProvider to be validated for ldap group properties.
* @throws APIException
*/
private void validateLDAPGroupProperties(AuthnProvider authnProvider) {
if (authnProvider == null) {
_log.error("Invalid authentication provider");
return;
}
boolean isGroupObjectClassesEmpty = CollectionUtils.isEmpty(authnProvider.getGroupObjectClassNames());
boolean isGroupMemberAttributesEmpty = CollectionUtils.isEmpty(authnProvider.getGroupMemberAttributeTypeNames());
// Return error if either only one of object classes or member attributes
// is entered and other one is empty.
if (isGroupObjectClassesEmpty ^ isGroupMemberAttributesEmpty) {
String param = "Group object classes";
if (!isGroupObjectClassesEmpty) {
param = "Group member attributes";
}
throw APIException.badRequests.authnProviderGroupObjectClassesAndMemberAttributesRequired(param);
}
}
/**
* Check if any of the user group using the domains in the authnprovider or not.
* if so, throw an exception;
* else, return silently.
*
* @param domains the domains to check
*/
private void checkForUserGroupsUsingDomains(StringSet domains) {
if (CollectionUtils.isEmpty(domains)) {
_log.error("Invalid domains");
return;
}
Set<URI> matchingUserGroupsURI = new HashSet<URI>();
for (String domain : domains) {
if (StringUtils.isBlank(domain)) {
_log.warn("Invalid domain");
continue;
}
List<UserGroup> matchingUserGroup = _permissionsHelper.getAllUserGroupForDomain(domain);
if (CollectionUtils.isEmpty(matchingUserGroup)) {
_log.debug("No user group found for the domain {}", domain);
continue;
}
for (UserGroup userGroup : matchingUserGroup) {
matchingUserGroupsURI.add(userGroup.getId());
}
}
if (!CollectionUtils.isEmpty(matchingUserGroupsURI)) {
throw APIException.badRequests.cannotDeleteAuthnProviderWithUserGroup(matchingUserGroupsURI.size(),
matchingUserGroupsURI);
}
}
}