Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce pre add user validations for email domain discovery feature #261

Merged
merged 2 commits into from
Oct 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
org.apache.commons.logging;version="${org.apache.commons.logging.imp.pkg.version.range}",
org.osgi.framework;version="${osgi.framework.imp.pkg.version.range}",
org.osgi.service.component;version="${osgi.service.component.imp.pkg.version.range}",
org.wso2.carbon.context;version="${carbon.kernel.package.import.version.range}",
org.wso2.carbon.database.utils.jdbc;version="${org.wso2.carbon.database.utils.version.range}",
org.wso2.carbon.database.utils.jdbc.exceptions;version="${org.wso2.carbon.database.utils.version.range}",
org.wso2.carbon.identity.organization.config.service;version="${org.wso2.identity.organization.mgt.imp.pkg.version.range}",
Expand All @@ -83,7 +84,11 @@
org.wso2.carbon.identity.organization.management.service;version="${org.wso2.identity.organization.mgt.core.imp.pkg.version.range}",
org.wso2.carbon.identity.organization.management.service.constant;version="${org.wso2.identity.organization.mgt.core.imp.pkg.version.range}",
org.wso2.carbon.identity.organization.management.service.exception;version="${org.wso2.identity.organization.mgt.core.imp.pkg.version.range}",
org.wso2.carbon.identity.organization.management.service.util;version="${org.wso2.identity.organization.mgt.core.imp.pkg.version.range}"
org.wso2.carbon.identity.organization.management.service.util;version="${org.wso2.identity.organization.mgt.core.imp.pkg.version.range}",
org.wso2.carbon.identity.core;version="${carbon.identity.package.import.version.range}",
org.wso2.carbon.identity.core.util;version="${carbon.identity.package.import.version.range}",
org.wso2.carbon.user.core;version="${carbon.kernel.package.import.version.range}",
org.wso2.carbon.user.core.listener;version="${carbon.kernel.package.import.version.range}"
</Import-Package>
</instructions>
</configuration>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException;

import java.util.List;

/**
* Interface for handling organization discovery types.
*/
Expand Down Expand Up @@ -50,4 +52,12 @@ boolean isDiscoveryConfigurationEnabled(String organizationId)
* @return The extracted attribute value.
*/
String extractAttributeValue(String discoveryInput);

/**
* Get the list of validations that are required for events triggered during organization discovery related
* operations.
*
* @return the list of events.
*/
List<String> requiredEventValidations();
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import java.util.List;
import java.util.Optional;

import static org.wso2.carbon.identity.organization.discovery.service.constant.DiscoveryConstants.PRE_ADD_USER_EMAIL_DOMAIN_VALIDATE;
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_DISCOVERY_CONFIG_DISABLED;
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_ERROR_RETRIEVING_DISCOVERY_CONFIGURATION;
import static org.wso2.carbon.identity.organization.management.service.util.Utils.getOrganizationId;
Expand Down Expand Up @@ -81,4 +82,10 @@ public String extractAttributeValue(String discoveryInput) {
}
return null;
}

@Override
public List<String> requiredEventValidations() {

return Collections.singletonList(PRE_ADD_USER_EMAIL_DOMAIN_VALIDATE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ List<OrgDiscoveryAttribute> updateOrganizationDiscoveryAttributes(String organiz
*/
boolean isDiscoveryAttributeValueAvailable(String type, String value) throws OrganizationManagementException;

/**
* Check if the given discovery attribute is already mapped to an organization within the hierarchy under the given
* organization.
*
* @param organizationId The organization ID.
* @param type The organization discovery attribute type.
* @param value The organization discovery attribute value.
* @return If the discovery attribute already exists within the hierarchy.
* @throws OrganizationManagementException The exception thrown when checking if the discovery attribute already
* exists within the hierarchy.
*/
boolean isDiscoveryAttributeValueAvailable(String organizationId, String type, String value) throws
OrganizationManagementException;

/**
* List the discovery attributes of all the organizations under the root organization.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,14 @@ public boolean isDiscoveryAttributeValueAvailable(String type, String value) thr
null, type, Collections.singletonList(value));
}

@Override
public boolean isDiscoveryAttributeValueAvailable(String organizationId, String type, String value)
throws OrganizationManagementException {

return !organizationDiscoveryDAO.isDiscoveryAttributeExistInHierarchy(false, organizationId,
null, type, Collections.singletonList(value));
}

@Override
public Map<String, List<OrgDiscoveryAttribute>> getOrganizationsDiscoveryAttributes()
throws OrganizationManagementException {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you 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 org.wso2.carbon.identity.organization.discovery.service.constant;

/**
* This class holds the constants related to organization discovery service.
*/
public class DiscoveryConstants {

public static final String ENABLE_CONFIG = ".enable";
public static final String PRE_ADD_USER_EMAIL_DOMAIN_VALIDATE = "PRE_ADD_USER_EMAIL_DOMAIN_VALIDATE";
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,9 @@
import org.wso2.carbon.identity.organization.discovery.service.EmailDomainBasedDiscoveryHandler;
import org.wso2.carbon.identity.organization.discovery.service.OrganizationDiscoveryManager;
import org.wso2.carbon.identity.organization.discovery.service.OrganizationDiscoveryManagerImpl;
import org.wso2.carbon.identity.organization.discovery.service.listener.OrganizationDiscoveryUserOperationListener;
import org.wso2.carbon.identity.organization.management.service.OrganizationManager;
import org.wso2.carbon.user.core.listener.UserOperationEventListener;

/**
* Service component class for the organization discovery service.
Expand All @@ -53,6 +55,8 @@ protected void activate(ComponentContext componentContext) {
AttributeBasedOrganizationDiscoveryHandler emailDomainDiscovery = new EmailDomainBasedDiscoveryHandler();
bundleContext.registerService(AttributeBasedOrganizationDiscoveryHandler.class.getName(), emailDomainDiscovery,
null);
bundleContext.registerService(UserOperationEventListener.class.getName(),
new OrganizationDiscoveryUserOperationListener(), null);
LOG.info("Organization discovery service component activated successfully.");
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
/*
* Copyright (c) 2023, WSO2 LLC. (http://www.wso2.com).
*
* WSO2 LLC. licenses this file to you 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 org.wso2.carbon.identity.organization.discovery.service.listener;

import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.identity.core.AbstractIdentityUserOperationEventListener;
import org.wso2.carbon.identity.core.util.IdentityCoreConstants;
import org.wso2.carbon.identity.core.util.IdentityTenantUtil;
import org.wso2.carbon.identity.organization.config.service.OrganizationConfigManager;
import org.wso2.carbon.identity.organization.config.service.exception.OrganizationConfigException;
import org.wso2.carbon.identity.organization.config.service.model.ConfigProperty;
import org.wso2.carbon.identity.organization.config.service.model.DiscoveryConfig;
import org.wso2.carbon.identity.organization.discovery.service.AttributeBasedOrganizationDiscoveryHandler;
import org.wso2.carbon.identity.organization.discovery.service.OrganizationDiscoveryManager;
import org.wso2.carbon.identity.organization.discovery.service.OrganizationDiscoveryManagerImpl;
import org.wso2.carbon.identity.organization.discovery.service.internal.OrganizationDiscoveryServiceHolder;
import org.wso2.carbon.identity.organization.discovery.service.model.OrgDiscoveryAttribute;
import org.wso2.carbon.identity.organization.management.service.OrganizationManager;
import org.wso2.carbon.identity.organization.management.service.exception.OrganizationManagementException;
import org.wso2.carbon.identity.organization.management.service.util.OrganizationManagementUtil;
import org.wso2.carbon.user.core.UserStoreException;
import org.wso2.carbon.user.core.UserStoreManager;

import java.util.List;
import java.util.Map;

import static org.wso2.carbon.identity.organization.config.service.constant.OrganizationConfigConstants.ErrorMessages.ERROR_CODE_DISCOVERY_CONFIG_NOT_EXIST;
import static org.wso2.carbon.identity.organization.discovery.service.constant.DiscoveryConstants.ENABLE_CONFIG;
import static org.wso2.carbon.identity.organization.discovery.service.constant.DiscoveryConstants.PRE_ADD_USER_EMAIL_DOMAIN_VALIDATE;
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_EMAIL_DOMAIN_ASSOCIATED_WITH_DIFFERENT_ORGANIZATION;
import static org.wso2.carbon.identity.organization.management.service.constant.OrganizationManagementConstants.ErrorMessages.ERROR_CODE_EMAIL_DOMAIN_NOT_MAPPED_TO_ORGANIZATION;

/**
* This is to perform organization discovery related validations upon user operations.
*/
public class OrganizationDiscoveryUserOperationListener extends AbstractIdentityUserOperationEventListener {

private static final Log LOG = LogFactory.getLog(OrganizationDiscoveryUserOperationListener.class);
private final OrganizationDiscoveryManager organizationDiscoveryManager = new OrganizationDiscoveryManagerImpl();

@Override
public int getExecutionOrderId() {

int orderId = getOrderId();
if (orderId != IdentityCoreConstants.EVENT_LISTENER_ORDER_ID) {
return orderId;
}
return 114;
}

@Override
public boolean doPreAddUserWithID(String userName, Object credential, String[] roleList, Map<String, String> claims,
String profile, UserStoreManager userStoreManager) throws UserStoreException {

if (!isEnable()) {
return true;
}

try {
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
if (!OrganizationManagementUtil.isOrganization(tenantDomain)) {
return true;
}
String organizationId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getOrganizationId();
if (StringUtils.isBlank(organizationId)) {
organizationId = getOrganizationManager().resolveOrganizationId(tenantDomain);
}
String primaryOrganizationId = getOrganizationManager().getPrimaryOrganizationId(organizationId);
int tenantId = IdentityTenantUtil.getTenantId(getOrganizationManager()
.resolveTenantDomain(primaryOrganizationId));
DiscoveryConfig discoveryConfig = getOrganizationConfigManager()
.getDiscoveryConfigurationByTenantId(tenantId);
List<ConfigProperty> configProperties = discoveryConfig.getConfigProperties();
Map<String, AttributeBasedOrganizationDiscoveryHandler> discoveryHandlers =
organizationDiscoveryManager.getAttributeBasedOrganizationDiscoveryHandlers();
for (ConfigProperty configProperty : configProperties) {
String type = configProperty.getKey().split(ENABLE_CONFIG)[0];
AttributeBasedOrganizationDiscoveryHandler handler = discoveryHandlers.get(type);
if (handler == null || !Boolean.parseBoolean(configProperty.getValue())) {
return true;
}

// Currently only email domain based organization discovery is supported.
if (!handler.requiredEventValidations().contains(PRE_ADD_USER_EMAIL_DOMAIN_VALIDATE)) {
return true;
}
return isValidEmailDomainForPreAddUser(userName, organizationId, primaryOrganizationId, handler);
}
} catch (OrganizationManagementException e) {
LOG.error("Error while creating user", e);
return false;
} catch (OrganizationConfigException e) {
if (ERROR_CODE_DISCOVERY_CONFIG_NOT_EXIST.getCode().equals(e.getErrorCode())) {
return true;
}
LOG.error("Error while creating user", e);
return false;
}
return true;
}

private boolean isValidEmailDomainForPreAddUser(String userName, String organizationId,
String primaryOrganizationId,
AttributeBasedOrganizationDiscoveryHandler handler)
throws UserStoreException, OrganizationManagementException {

// Username should be in the email address format.
String emailDomain = handler.extractAttributeValue(userName);
List<OrgDiscoveryAttribute> organizationDiscoveryAttributes = organizationDiscoveryManager
.getOrganizationDiscoveryAttributes(organizationId, false);

// If the organization doesn't have any email domains mapped, then we need to check if the
// email domain in the username is not mapped to any other organization .
if (organizationDiscoveryAttributes.isEmpty()) {
boolean domainAvailable = organizationDiscoveryManager.isDiscoveryAttributeValueAvailable
(primaryOrganizationId, handler.getType(), emailDomain);
if (domainAvailable) {
return true;
}
throw new UserStoreException(
ERROR_CODE_EMAIL_DOMAIN_ASSOCIATED_WITH_DIFFERENT_ORGANIZATION.getDescription(),
ERROR_CODE_EMAIL_DOMAIN_ASSOCIATED_WITH_DIFFERENT_ORGANIZATION.getCode());
}
for (OrgDiscoveryAttribute attribute : organizationDiscoveryAttributes) {
List<String> organizationMappedEmailDomains = attribute.getValues();
if (organizationMappedEmailDomains != null && organizationMappedEmailDomains.contains(emailDomain)) {
return true;
}
throw new UserStoreException(
ERROR_CODE_EMAIL_DOMAIN_NOT_MAPPED_TO_ORGANIZATION.getDescription(),
ERROR_CODE_EMAIL_DOMAIN_NOT_MAPPED_TO_ORGANIZATION.getCode());
}
return true;
}

private OrganizationConfigManager getOrganizationConfigManager() {

return OrganizationDiscoveryServiceHolder.getInstance().getOrganizationConfigManager();
}

private OrganizationManager getOrganizationManager() {

return OrganizationDiscoveryServiceHolder.getInstance().getOrganizationManager();
}
}