Skip to content

Commit f5ad331

Browse files
authored
Merge pull request #4946 from mpmadhavig/custom-outbound-connector
Custom outbound provisioning connector documentation
2 parents daed921 + 78f56a8 commit f5ad331

File tree

8 files changed

+318
-1
lines changed

8 files changed

+318
-1
lines changed

en/identity-server/6.1.0/docs/deploy/change-to-oracle.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,18 @@ Apart from the basic configurations specified above, WSO2 Identity Server suppor
109109

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

112+
### Using an alternate user to connect to database
113+
114+
When the database owner is not the user used to connect to the database, specify the parent schema in the datasource declarion.
115+
116+
``` toml
117+
[database.identity_db.db_props]
118+
parentSchema = "<parent_schema_name>"
119+
120+
[database.shared_db.db_props]
121+
parentSchema = "<parent_schema_name>"
122+
```
123+
112124
---
113125

114126
## Configure the connection pool behavior on return

en/identity-server/7.0.0/docs/deploy/configure/databases/carbon-database/change-to-oracle.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,8 +106,19 @@ Apart from the basic configurations specified above, WSO2 Identity Server suppor
106106

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

109+
### Using an alternate user to connect to database
110+
111+
When the database owner is not the user used to connect to the database, specify the parent schema in the datasource declarion.
112+
113+
``` toml
114+
[database.identity_db.db_props]
115+
parentSchema = "<parent_schema_name>"
116+
117+
[database.shared_db.db_props]
118+
parentSchema = "<parent_schema_name>"
119+
```
120+
109121
---
110-
111122
## Configure the connection pool behavior on return
112123

113124
{% include "../../../../includes/connection-pool-behavior.md" %}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{% include "../../../../../../../includes/guides/users/outbound-provisioning/outbound-connectors/custom-outbound-connectors.md" %}

en/identity-server/7.0.0/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -507,6 +507,7 @@ nav:
507507
- Google: guides/users/outbound-provisioning/outbound-connectors/google.md
508508
- Salesforce: guides/users/outbound-provisioning/outbound-connectors/salesforce.md
509509
- SCIM2: guides/users/outbound-provisioning/outbound-connectors/scim2.md
510+
- Custom Outbound Connector: guides/users/outbound-provisioning/outbound-connectors/custom-outbound-connectors.md
510511
- Provisioning patterns: guides/users/outbound-provisioning/provisioning-patterns.md
511512
- Sync User Accounts:
512513
- Overview: guides/users/sync-user-accounts/sync-account-overview.md

en/identity-server/next/docs/deploy/configure/databases/carbon-database/change-to-oracle.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,18 @@ Apart from the basic configurations specified above, WSO2 Identity Server suppor
106106

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

109+
### Using an alternate user to connect to database
110+
111+
When the database owner is not the user used to connect to the database, specify the parent schema in the datasource declarion.
112+
113+
``` toml
114+
[database.identity_db.db_props]
115+
parentSchema = "<parent_schema_name>"
116+
117+
[database.shared_db.db_props]
118+
parentSchema = "<parent_schema_name>"
119+
```
120+
109121
---
110122

111123
## Configure the connection pool behavior on return
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{% include "../../../../../../../includes/guides/users/outbound-provisioning/outbound-connectors/custom-outbound-connectors.md" %}

en/identity-server/next/mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -509,6 +509,7 @@ nav:
509509
- Google: guides/users/outbound-provisioning/outbound-connectors/google.md
510510
- Salesforce: guides/users/outbound-provisioning/outbound-connectors/salesforce.md
511511
- SCIM2: guides/users/outbound-provisioning/outbound-connectors/scim2.md
512+
- Custom Outbound Connector: guides/users/outbound-provisioning/outbound-connectors/custom-outbound-connectors.md
512513
- Provisioning patterns: guides/users/outbound-provisioning/provisioning-patterns.md
513514
- Sync User Accounts:
514515
- Overview: guides/users/sync-user-accounts/sync-account-overview.md
Lines changed: 278 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,278 @@
1+
# Write a custom Outbound Provisioning Connector
2+
3+
In addition to Google, Salesforce, SCIM, and SPML, it is possible to create custom connectors.
4+
5+
Follow the steps given below to write an outbound provisioning connector.
6+
7+
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/).
8+
9+
??? info "Click here to view the API source code"
10+
```java
11+
/*
12+
* Copyright (c) 2014, WSO2 Inc. (http://www.wso2.org) All Rights Reserved.
13+
*
14+
* WSO2 Inc. licenses this file to you under the Apache License,
15+
* Version 2.0 (the "License"); you may not use this file except
16+
* in compliance with the License.
17+
* You may obtain a copy of the License at
18+
*
19+
* http://www.apache.org/licenses/LICENSE-2.0
20+
*
21+
* Unless required by applicable law or agreed to in writing,
22+
* software distributed under the License is distributed on an
23+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
24+
* KIND, either express or implied. See the License for the
25+
* specific language governing permissions and limitations
26+
* under the License.
27+
*/
28+
29+
package org.wso2.carbon.identity.provisioning;
30+
31+
import org.apache.commons.collections.CollectionUtils;
32+
import org.apache.commons.lang.StringUtils;
33+
import org.wso2.carbon.CarbonConstants;
34+
import org.wso2.carbon.context.PrivilegedCarbonContext;
35+
import org.wso2.carbon.identity.application.common.model.ClaimMapping;
36+
import org.wso2.carbon.identity.application.common.model.Property;
37+
import org.wso2.carbon.user.core.UserCoreConstants;
38+
39+
import java.io.Serializable;
40+
import java.util.HashMap;
41+
import java.util.List;
42+
import java.util.Map;
43+
import java.util.UUID;
44+
45+
public abstract class AbstractOutboundProvisioningConnector implements Serializable {
46+
47+
private static final long serialVersionUID = 8619915839101228583L;
48+
49+
private static final String PROVISIONING_IDP = "IDP";
50+
private static final String PROVISIONING_TENANT = "TD";
51+
private static final String PROVISIONING_DOMAIN = "UD";
52+
private static final String PROVISIONING_USER = "UN";
53+
protected boolean jitProvisioningEnabled;
54+
55+
/**
56+
* @param provisioningProperties
57+
* @throws IdentityProvisioningException
58+
*/
59+
public abstract void init(Property[] provisioningProperties)
60+
throws IdentityProvisioningException;
61+
62+
/**
63+
* @param provisioningEntity
64+
* @throws IdentityProvisioningException
65+
*/
66+
public abstract ProvisionedIdentifier provision(ProvisioningEntity provisioningEntity)
67+
throws IdentityProvisioningException;
68+
69+
/**
70+
* override only if needed - if claims are controlled by the identity provider, this will return
71+
* null. If it is connector specific this must return the corresponding claim dialect.
72+
*
73+
* @return
74+
* @throws IdentityProvisioningException
75+
*/
76+
public String getClaimDialectUri() throws IdentityProvisioningException {
77+
return null;
78+
}
79+
80+
/**
81+
* @return
82+
* @throws IdentityProvisioningException
83+
*/
84+
protected boolean isJitProvisioningEnabled() throws IdentityProvisioningException {
85+
return jitProvisioningEnabled;
86+
}
87+
88+
/**
89+
* @param attributeMap
90+
* @return
91+
*/
92+
protected List<String> getUserNames(Map<ClaimMapping, List<String>> attributeMap) {
93+
return ProvisioningUtil.getClaimValues(attributeMap,
94+
IdentityProvisioningConstants.USERNAME_CLAIM_URI, getUserStoreDomainName());
95+
}
96+
97+
/**
98+
* @param attributeMap
99+
* @return
100+
*/
101+
protected List<String> getGroupNames(Map<ClaimMapping, List<String>> attributeMap) {
102+
return ProvisioningUtil.getClaimValues(attributeMap,
103+
IdentityProvisioningConstants.GROUP_CLAIM_URI, getUserStoreDomainName());
104+
}
105+
106+
/**
107+
* @param attributeMap
108+
* @return
109+
*/
110+
protected String getPassword(Map<ClaimMapping, List<String>> attributeMap) {
111+
List<String> claimValue = ProvisioningUtil.getClaimValues(attributeMap,
112+
IdentityProvisioningConstants.PASSWORD_CLAIM_URI, null);
113+
114+
if (CollectionUtils.isNotEmpty(claimValue) && claimValue.get(0) != null) {
115+
return claimValue.get(0);
116+
}
117+
118+
return UUID.randomUUID().toString();
119+
120+
}
121+
122+
/**
123+
* @param attributeMap
124+
* @return claimValues
125+
*/
126+
protected Map<String, String> getSingleValuedClaims(Map<ClaimMapping, List<String>> attributeMap) {
127+
128+
Map<String, String> claimValues = new HashMap<>();
129+
130+
for (Map.Entry<ClaimMapping, List<String>> entry : attributeMap.entrySet()) {
131+
ClaimMapping mapping = entry.getKey();
132+
if (mapping.getRemoteClaim() != null && mapping.getRemoteClaim().getClaimUri() != null) {
133+
String claimUri = mapping.getRemoteClaim().getClaimUri();
134+
135+
if (!(IdentityProvisioningConstants.GROUP_CLAIM_URI.equals(claimUri)
136+
|| IdentityProvisioningConstants.PASSWORD_CLAIM_URI.equals(claimUri) || IdentityProvisioningConstants.USERNAME_CLAIM_URI
137+
.equals(claimUri))) {
138+
if (CollectionUtils.isNotEmpty(entry.getValue()) && entry.getValue().get(0) != null) {
139+
claimValues.put(claimUri, entry.getValue().get(0));
140+
} else {
141+
claimValues.put(claimUri, mapping.getDefaultValue());
142+
}
143+
}
144+
}
145+
}
146+
147+
return claimValues;
148+
}
149+
150+
/**
151+
* @return
152+
*/
153+
protected String getUserStoreDomainName() {
154+
// return null by default. concrete implementations can override this value whenever
155+
// required.
156+
return null;
157+
}
158+
159+
protected String buildUserId(ProvisioningEntity provisioningEntity, String provisioningPattern,
160+
String separator, String idpName) throws IdentityProvisioningException {
161+
162+
Map<String, String> provValues = new HashMap<>();
163+
String tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain();
164+
String username = provisioningEntity.getEntityName();
165+
String userStoreDomain = getDomainFromUserName(username);
166+
167+
if (separator == null) {
168+
separator = "";
169+
}
170+
171+
String provIdentifier = "";
172+
provValues.put(PROVISIONING_TENANT, tenantDomain.replaceAll(separator, ""));
173+
174+
if (username != null) {
175+
provValues.put(PROVISIONING_USER, removeDomainFromUserName(username));
176+
}
177+
provValues.put(PROVISIONING_IDP, idpName.replaceAll(separator, ""));
178+
179+
if (userStoreDomain != null) {
180+
provValues.put(PROVISIONING_DOMAIN, userStoreDomain.replaceAll(separator, ""));
181+
}
182+
183+
String[] provisioningEntries = buildProvisioningEntries(provisioningPattern);
184+
185+
for (int i = 0; i < provisioningEntries.length; i++) {
186+
if (StringUtils.isNotBlank(provisioningEntries[i])) {
187+
if (StringUtils.isBlank(provIdentifier)) {
188+
provIdentifier = provValues.get(provisioningEntries[i].trim());
189+
} else {
190+
provIdentifier = provIdentifier.concat(separator).concat(provValues.get(provisioningEntries[i].trim()));
191+
}
192+
}
193+
}
194+
195+
return provIdentifier.toLowerCase();
196+
}
197+
198+
private String[] buildProvisioningEntries(String provisioningPattern) throws IdentityProvisioningException {
199+
200+
if (!provisioningPattern.contains("{") || !provisioningPattern.contains("}")) {
201+
throw new IdentityProvisioningException("Invalid Provisioning Pattern");
202+
}
203+
204+
String provisioningPatternWithoutCurlBrace = provisioningPattern.replaceAll("\\{", "").replaceAll("\\}", "");
205+
return provisioningPatternWithoutCurlBrace.split(",");
206+
}
207+
208+
private String getDomainFromUserName(String username) {
209+
int index;
210+
String domain = UserCoreConstants.PRIMARY_DEFAULT_DOMAIN_NAME;
211+
if (StringUtils.isNotBlank(username)) {
212+
if ((index = username.indexOf("/")) > 0) {
213+
domain = username.substring(0, index);
214+
}
215+
return domain;
216+
}
217+
return domain;
218+
}
219+
220+
private String removeDomainFromUserName(String username) {
221+
int index;
222+
if ((index = username.indexOf(CarbonConstants.DOMAIN_SEPARATOR)) >= 0) {
223+
// remove domain name if exist
224+
username = username.substring(index + 1);
225+
}
226+
return username;
227+
}
228+
229+
}
230+
```
231+
232+
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.
233+
234+
!!! note
235+
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.
236+
237+
``` xml
238+
<dependency>
239+
<groupId>org.wso2.carbon.identity.framework</groupId>
240+
<artifactId>org.wso2.carbon.identity.provisioning</artifactId>
241+
<version>5.20.25</version>
242+
</dependency>
243+
```
244+
245+
``` xml
246+
<repositories>
247+
<repository>
248+
<id>wso2-nexus</id>
249+
<name>WSO2 internal Repository</name>
250+
<url>http://maven.wso2.org/nexus/content/groups/wso2-public/</url>
251+
<releases>
252+
<enabled>true</enabled>
253+
<updatePolicy>daily</updatePolicy>
254+
<checksumPolicy>ignore</checksumPolicy>
255+
</releases>
256+
</repository>
257+
<repository>
258+
<id>wso2.releases</id>
259+
<name>WSO2 internal Repository</name>
260+
<url>http://maven.wso2.org/nexus/content/repositories/releases/</url>
261+
<releases>
262+
<enabled>true</enabled>
263+
<updatePolicy>daily</updatePolicy>
264+
<checksumPolicy>ignore</checksumPolicy>
265+
</releases>
266+
</repository>
267+
</repositories>
268+
```
269+
270+
3. Additionally, the custom connector metadata such as description, display name can be added as follows to supplement the UI.
271+
272+
```toml
273+
[[console.extensions.outboundProvisioningConnectors]]
274+
connectorId="U2FtcGxl"
275+
description="Sample outbound provisioning connector"
276+
displayName="Sample outbound"
277+
icon="<network-resolvable-image-url>"
278+
```

0 commit comments

Comments
 (0)