Skip to content

Commit

Permalink
Merge pull request #4946 from mpmadhavig/custom-outbound-connector
Browse files Browse the repository at this point in the history
Custom outbound provisioning connector documentation
  • Loading branch information
mpmadhavig authored Jan 6, 2025
2 parents daed921 + 78f56a8 commit f5ad331
Show file tree
Hide file tree
Showing 8 changed files with 318 additions and 1 deletion.
12 changes: 12 additions & 0 deletions en/identity-server/6.1.0/docs/deploy/change-to-oracle.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,18 @@ Apart from the basic configurations specified above, WSO2 Identity Server suppor

{!./includes/db-config-table.md !}

### Using an alternate user to connect to database

When the database owner is not the user used to connect to the database, specify the parent schema in the datasource declarion.

``` toml
[database.identity_db.db_props]
parentSchema = "<parent_schema_name>"

[database.shared_db.db_props]
parentSchema = "<parent_schema_name>"
```

---

## Configure the connection pool behavior on return
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,8 +106,19 @@ Apart from the basic configurations specified above, WSO2 Identity Server suppor

{% include "../../../../includes/db-config-table.md" %}

### Using an alternate user to connect to database

When the database owner is not the user used to connect to the database, specify the parent schema in the datasource declarion.

``` toml
[database.identity_db.db_props]
parentSchema = "<parent_schema_name>"

[database.shared_db.db_props]
parentSchema = "<parent_schema_name>"
```

---

## Configure the connection pool behavior on return

{% include "../../../../includes/connection-pool-behavior.md" %}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{% include "../../../../../../../includes/guides/users/outbound-provisioning/outbound-connectors/custom-outbound-connectors.md" %}
1 change: 1 addition & 0 deletions en/identity-server/7.0.0/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,7 @@ nav:
- Google: guides/users/outbound-provisioning/outbound-connectors/google.md
- Salesforce: guides/users/outbound-provisioning/outbound-connectors/salesforce.md
- SCIM2: guides/users/outbound-provisioning/outbound-connectors/scim2.md
- Custom Outbound Connector: guides/users/outbound-provisioning/outbound-connectors/custom-outbound-connectors.md
- Provisioning patterns: guides/users/outbound-provisioning/provisioning-patterns.md
- Sync User Accounts:
- Overview: guides/users/sync-user-accounts/sync-account-overview.md
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,18 @@ Apart from the basic configurations specified above, WSO2 Identity Server suppor

{% include "../../../../includes/db-config-table.md" %}

### Using an alternate user to connect to database

When the database owner is not the user used to connect to the database, specify the parent schema in the datasource declarion.

``` toml
[database.identity_db.db_props]
parentSchema = "<parent_schema_name>"

[database.shared_db.db_props]
parentSchema = "<parent_schema_name>"
```

---

## Configure the connection pool behavior on return
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{% include "../../../../../../../includes/guides/users/outbound-provisioning/outbound-connectors/custom-outbound-connectors.md" %}
1 change: 1 addition & 0 deletions en/identity-server/next/mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,7 @@ nav:
- Google: guides/users/outbound-provisioning/outbound-connectors/google.md
- Salesforce: guides/users/outbound-provisioning/outbound-connectors/salesforce.md
- SCIM2: guides/users/outbound-provisioning/outbound-connectors/scim2.md
- Custom Outbound Connector: guides/users/outbound-provisioning/outbound-connectors/custom-outbound-connectors.md
- Provisioning patterns: guides/users/outbound-provisioning/provisioning-patterns.md
- Sync User Accounts:
- Overview: guides/users/sync-user-accounts/sync-account-overview.md
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,278 @@
# Write a custom Outbound Provisioning Connector

In addition to Google, Salesforce, SCIM, and SPML, it is possible to create custom connectors.

Follow the steps given below to write an outbound provisioning connector.

1. The following API is used to configure a custom connector. The connector API can be obtained [here](https://github.com/wso2/carbon-identity-framework/blob/v7.0.78/components/provisioning/).

??? info "Click here to view the API source code"
```java
/*
* Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
*
* WSO2 Inc. 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.provisioning;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.wso2.carbon.CarbonConstants;
import org.wso2.carbon.context.PrivilegedCarbonContext;
import org.wso2.carbon.identity.application.common.model.ClaimMapping;
import org.wso2.carbon.identity.application.common.model.Property;
import org.wso2.carbon.user.core.UserCoreConstants;

import java.io.Serializable;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;

public abstract class AbstractOutboundProvisioningConnector implements Serializable {

private static final long serialVersionUID = 8619915839101228583L;

private static final String PROVISIONING_IDP = "IDP";
private static final String PROVISIONING_TENANT = "TD";
private static final String PROVISIONING_DOMAIN = "UD";
private static final String PROVISIONING_USER = "UN";
protected boolean jitProvisioningEnabled;

/**
* @param provisioningProperties
* @throws IdentityProvisioningException
*/
public abstract void init(Property[] provisioningProperties)
throws IdentityProvisioningException;

/**
* @param provisioningEntity
* @throws IdentityProvisioningException
*/
public abstract ProvisionedIdentifier provision(ProvisioningEntity provisioningEntity)
throws IdentityProvisioningException;

/**
* override only if needed - if claims are controlled by the identity provider, this will return
* null. If it is connector specific this must return the corresponding claim dialect.
*
* @return
* @throws IdentityProvisioningException
*/
public String getClaimDialectUri() throws IdentityProvisioningException {
return null;
}

/**
* @return
* @throws IdentityProvisioningException
*/
protected boolean isJitProvisioningEnabled() throws IdentityProvisioningException {
return jitProvisioningEnabled;
}

/**
* @param attributeMap
* @return
*/
protected List<String> getUserNames(Map<ClaimMapping, List<String>> attributeMap) {
return ProvisioningUtil.getClaimValues(attributeMap,
IdentityProvisioningConstants.USERNAME_CLAIM_URI, getUserStoreDomainName());
}

/**
* @param attributeMap
* @return
*/
protected List<String> getGroupNames(Map<ClaimMapping, List<String>> attributeMap) {
return ProvisioningUtil.getClaimValues(attributeMap,
IdentityProvisioningConstants.GROUP_CLAIM_URI, getUserStoreDomainName());
}

/**
* @param attributeMap
* @return
*/
protected String getPassword(Map<ClaimMapping, List<String>> attributeMap) {
List<String> claimValue = ProvisioningUtil.getClaimValues(attributeMap,
IdentityProvisioningConstants.PASSWORD_CLAIM_URI, null);

if (CollectionUtils.isNotEmpty(claimValue) && claimValue.get(0) != null) {
return claimValue.get(0);
}

return UUID.randomUUID().toString();

}

/**
* @param attributeMap
* @return claimValues
*/
protected Map<String, String> getSingleValuedClaims(Map<ClaimMapping, List<String>> attributeMap) {

Map<String, String> claimValues = new HashMap<>();

for (Map.Entry<ClaimMapping, List<String>> entry : attributeMap.entrySet()) {
ClaimMapping mapping = entry.getKey();
if (mapping.getRemoteClaim() != null && mapping.getRemoteClaim().getClaimUri() != null) {
String claimUri = mapping.getRemoteClaim().getClaimUri();

if (!(IdentityProvisioningConstants.GROUP_CLAIM_URI.equals(claimUri)
|| IdentityProvisioningConstants.PASSWORD_CLAIM_URI.equals(claimUri) || IdentityProvisioningConstants.USERNAME_CLAIM_URI
.equals(claimUri))) {
if (CollectionUtils.isNotEmpty(entry.getValue()) && entry.getValue().get(0) != null) {
claimValues.put(claimUri, entry.getValue().get(0));
} else {
claimValues.put(claimUri, mapping.getDefaultValue());
}
}
}
}

return claimValues;
}

/**
* @return
*/
protected String getUserStoreDomainName() {
// return null by default. concrete implementations can override this value whenever
// required.
return null;
}

protected String buildUserId(ProvisioningEntity provisioningEntity, String provisioningPattern,
String separator, String idpName) throws IdentityProvisioningException {

Map<String, String> provValues = new HashMap<>();
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
String username = provisioningEntity.getEntityName();
String userStoreDomain = getDomainFromUserName(username);

if (separator == null) {
separator = "";
}

String provIdentifier = "";
provValues.put(PROVISIONING_TENANT, tenantDomain.replaceAll(separator, ""));

if (username != null) {
provValues.put(PROVISIONING_USER, removeDomainFromUserName(username));
}
provValues.put(PROVISIONING_IDP, idpName.replaceAll(separator, ""));

if (userStoreDomain != null) {
provValues.put(PROVISIONING_DOMAIN, userStoreDomain.replaceAll(separator, ""));
}

String[] provisioningEntries = buildProvisioningEntries(provisioningPattern);

for (int i = 0; i < provisioningEntries.length; i++) {
if (StringUtils.isNotBlank(provisioningEntries[i])) {
if (StringUtils.isBlank(provIdentifier)) {
provIdentifier = provValues.get(provisioningEntries[i].trim());
} else {
provIdentifier = provIdentifier.concat(separator).concat(provValues.get(provisioningEntries[i].trim()));
}
}
}

return provIdentifier.toLowerCase();
}

private String[] buildProvisioningEntries(String provisioningPattern) throws IdentityProvisioningException {

if (!provisioningPattern.contains("{") || !provisioningPattern.contains("}")) {
throw new IdentityProvisioningException("Invalid Provisioning Pattern");
}

String provisioningPatternWithoutCurlBrace = provisioningPattern.replaceAll("\\{", "").replaceAll("\\}", "");
return provisioningPatternWithoutCurlBrace.split(",");
}

private String getDomainFromUserName(String username) {
int index;
String domain = UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME;
if (StringUtils.isNotBlank(username)) {
if ((index = username.indexOf("/")) > 0) {
domain = username.substring(0, index);
}
return domain;
}
return domain;
}

private String removeDomainFromUserName(String username) {
int index;
if ((index = username.indexOf(CarbonConstants.DOMAIN_SEPARATOR)) >= 0) {
// remove domain name if exist
username = username.substring(index + 1);
}
return username;
}

}
```

2. Refer the source code of the [sample outbound connector](https://github.com/wso2/samples-is/tree/master/sample-outbound-connector) to get an understanding about the structure of the connector. The Google provisioning connector that uses the API given above can be found [here](https://github.com/wso2-extensions/identity-outbound-provisioning-google/blob/v5.1.9/components/org.wso2.carbon.identity.provisioning.connector.google/src/main/java/org/wso2/carbon/identity/provisioning/connector/google/GoogleProvisioningConnector.java). Using these connectors as examples, you can customize this code according to your requirements.

!!! note
To ensure that the connector works, check whether the following configurations are added in the **pom.xml** file (found in the above connector API link) inside the `<project>` tags.

``` xml
<dependency>
<groupId>org.wso2.carbon.identity.framework</groupId>
<artifactId>org.wso2.carbon.identity.provisioning</artifactId>
<version>5.20.25</version>
</dependency>
```

``` xml
<repositories>
<repository>
<id>wso2-nexus</id>
<name>WSO2 internal Repository</name>
<url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url>
<releases>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>ignore</checksumPolicy>
</releases>
</repository>
<repository>
<id>wso2.releases</id>
<name>WSO2 internal Repository</name>
<url>http://maven.wso2.org/nexus/content/repositories/releases/</url>
<releases>
<enabled>true</enabled>
<updatePolicy>daily</updatePolicy>
<checksumPolicy>ignore</checksumPolicy>
</releases>
</repository>
</repositories>
```

3. Additionally, the custom connector metadata such as description, display name can be added as follows to supplement the UI.

```toml
[[console.extensions.outboundProvisioningConnectors]]
connectorId="U2FtcGxl"
description="Sample outbound provisioning connector"
displayName="Sample outbound"
icon="<network-resolvable-image-url>"
```

0 comments on commit f5ad331

Please sign in to comment.