Thursday, April 4, 2013

Custom JDBC user store manager with Custom properties as Claims in WSO2IS 4.1.1 alpha

In my early blog post I have describe how to write the custom user store manager and plug in to the WSO2 Identity Server. Now I'm going to explain how we can plug the user properties as claims and how to do some authorization with that properties. First of all you need to read my early blog post (How to write Custom JDBC user store manager with WSO2IS 4.1.1 alpha)

I have modify the DB Schema as follows
[sourcecode language="sql"]
CREATE TABLE CUSTOMER_DATA (
CUSTOMER_ID INTEGER NOT NULL AUTO_INCREMENT,
CUSTOMER_NAME VARCHAR(255) NOT NULL,
PASSWORD VARCHAR(255) NOT NULL,
EMAIL VARCHAR(255) NOT NULL,
AGE VARCHAR(255) NOT NULL,
STATUS VARCHAR(255) NOT NULL,
PRIMARY KEY (CUSTOMER_ID),
UNIQUE(CUSTOMER_NAME)
);

INSERT INTO CUSTOMER_DATA (CUSTOMER_NAME, PASSWORD,EMAIL,AGE,STATUS) VALUES("dinuka" ,"dinuka","dinukam@wso2.com","25","ACTIVE");
INSERT INTO CUSTOMER_DATA (CUSTOMER_NAME, PASSWORD,EMAIL,AGE,STATUS) VALUES("malinda" ,"malinda","malinda@gmail.com","25","INACTIVE");
[/sourcecode]

As I mention in the early post we need to create the data sources in master-datasources.xml and plug the newly created JDBCUserStoreManager through the user-mgt.xml

1. Lets look at the modification of JDBCUserStoreManager.

If we need to get some user properties as claims then you need to override the following methods in JDBCUserStoreManager (Download the mvn project here).
[sourcecode language="java"]
@Override
protected String getProperty(Connection dbConnection, String userName, String propertyName,
String profileName) throws UserStoreException {
String sqlStmt = realmConfig.getUserStoreProperty(JDBCRealmConstants.GET_PROPS_FOR_PROFILE);
if (sqlStmt == null) {
throw new UserStoreException("The sql statement for add user property sql is null");
}
PreparedStatement prepStmt = null;
ResultSet rs = null;
String value = null;
try {
prepStmt = dbConnection.prepareStatement(sqlStmt);
prepStmt.setString(1, userName);

rs = prepStmt.executeQuery();
while (rs.next()) {
if(propertyName.equals("EMAIL")){
value = rs.getString(1);
}else if(propertyName.equals("AGE")){
value = rs.getString(2);
}else if(propertyName.equals("STATUS")){
value = rs.getString(3);
}
}
return value;
} catch (SQLException e) {
log.error("Using sql : " + sqlStmt);
throw new UserStoreException(e.getMessage(), e);
} finally {
DatabaseUtil.closeAllConnections(null, rs, prepStmt);
}
}

@Override
public Map<String, String> getUserPropertyValues(String userName, String[] propertyNames,
String profileName) throws UserStoreException {
if (profileName == null) {
profileName = UserCoreConstants.DEFAULT_PROFILE;
}
Connection dbConnection = null;
String sqlStmt = null;
PreparedStatement prepStmt = null;
ResultSet rs = null;
String[] propertyNamesSorted = propertyNames.clone();
Arrays.sort(propertyNamesSorted);
Map<String, String> map = new HashMap<String, String>();
try {
dbConnection = getDBConnection();
sqlStmt = realmConfig.getUserStoreProperty(JDBCRealmConstants.GET_PROPS_FOR_PROFILE);
prepStmt = dbConnection.prepareStatement(sqlStmt);
prepStmt.setString(1, userName);

rs = prepStmt.executeQuery();
while (rs.next()) {
String email = rs.getString(1);
String age = rs.getString(2);
String status = rs.getString(3);
if (Arrays.binarySearch(propertyNamesSorted, "EMAIL") >= 0) {
map.put("EMAIL", email);
}

if(Arrays.binarySearch(propertyNamesSorted, "AGE") >= 0){
map.put("AGE", age);
}

if(Arrays.binarySearch(propertyNamesSorted, "STATUS") >= 0){
map.put("STATUS", status);
}

}

return map;
} catch (SQLException e) {
throw new UserStoreException(e.getMessage(), e);
} finally {
DatabaseUtil.closeAllConnections(dbConnection, rs, prepStmt);
}
}

[/sourcecode]

2. In this code you can see we are using some external sql query as GET_PROPS_FOR_PROFILE (realmConfig.getUserStoreProperty(JDBCRealmConstants.GET_PROPS_FOR_PROFILE);) so we need to pass it from user-mgt.xml as follows.

[sourcecode language="xml"]
<UserManager>
<Realm>
<Configuration>
<AddAdmin>true</AddAdmin>
<AdminRole>admin</AdminRole>
<AdminUser>
<UserName>admin</UserName>
<Password>admin</Password>
</AdminUser>
<EveryOneRoleName>everyone</EveryOneRoleName> <!-- By default users in this role sees the registry root -->
<Property name="dataSource">jdbc/WSO2CarbonDB</Property>
<Property name="MultiTenantRealmConfigBuilder">org.wso2.carbon.user.core.config.multitenancy.SimpleRealmConfigBuilder</Property>
<!-- Use the following MultiTenantRealmConfigBuilder with LDAP based UserStoreManagers-->
<!--Property name="MultiTenantRealmConfigBuilder">org.wso2.carbon.user.core.config.multitenancy.CommonLDAPRealmConfigBuilder</Property-->
</Configuration>

<UserStoreManager class="org.wso2.carbon.user.core.jdbc.JDBCUserStoreManager">
<Property name="ReadOnly">false</Property>
<Property name="dataSource">jdbc/WSO2CarbonDB</Property>
<Property name="MaxUserNameListLength">100</Property>
<Property name="Disabled">false</Property>
<Property name="IsEmailUserName">false</Property>
<Property name="DomainCalculation">default</Property>
<Property name="PasswordDigest">SHA-256</Property>
<Property name="StoreSaltedPassword">true</Property>
<Property name="ReadGroups">true</Property>
<Property name="WriteGroups">true</Property>
<Property name="UserNameUniqueAcrossTenants">false</Property>
<Property name="PasswordJavaRegEx">^[\S]{5,30}$</Property>
<Property name="PasswordJavaScriptRegEx">^[\S]{5,30}$</Property>
<Property name="UsernameJavaRegEx">^[^~!#$;%^*+={}\\|\\\\&lt;&gt;,\'\"]{3,30}$</Property>
<Property name="UsernameJavaScriptRegEx">^[\S]{3,30}$</Property>
<Property name="RolenameJavaRegEx">^[^~!#$;%^*+={}\\|\\\\&lt;&gt;,\'\"]{3,30}$</Property>
<Property name="RolenameJavaScriptRegEx">^[\S]{3,30}$</Property>
<Property name="UserRolesCacheEnabled">true</Property>
<Property name="MaxRoleNameListLength">100</Property>
<Property name="MaxUserNameListLength">100</Property>
</UserStoreManager>

<UserStoreManager class="org.wso2.carbon.jdbc.sample.SampleJDBCUserStoreManager">
<Property name="ReadOnly">false</Property>
<Property name="dataSource">jdbc/SampleUserStore</Property>
<Property name="MaxUserNameListLength">100</Property>
<Property name="Disabled">false</Property>
<Property name="IsEmailUserName">false</Property>
<Property name="DomainCalculation">default</Property>
<Property name="PasswordDigest">SHA-256</Property>
<Property name="StoreSaltedPassword">true</Property>
<Property name="ReadGroups">true</Property>
<Property name="WriteGroups">true</Property>
<Property name="UserNameUniqueAcrossTenants">false</Property>
<Property name="PasswordJavaRegEx">^[\S]{5,30}$</Property>
<Property name="PasswordJavaScriptRegEx">^[\S]{5,30}$</Property>
<Property name="UsernameJavaRegEx">^[^~!#$;%^*+={}\\|\\\\&lt;&gt;,\'\"]{3,30}$</Property>
<Property name="UsernameJavaScriptRegEx">^[\S]{3,30}$</Property>
<Property name="RolenameJavaRegEx">^[^~!#$;%^*+={}\\|\\\\&lt;&gt;,\'\"]{3,30}$</Property>
<Property name="RolenameJavaScriptRegEx">^[\S]{3,30}$</Property>
<Property name="UserRolesCacheEnabled">true</Property>
<Property name="MaxRoleNameListLength">100</Property>
<Property name="MaxUserNameListLength">100</Property>
<Property name="DomainName">sample.com</Property>

<Property name="SelectUserSQL">SELECT * FROM CUSTOMER_DATA WHERE CUSTOMER_NAME=?</Property>
<Property name="UserFilterSQL">SELECT CUSTOMER_NAME FROM CUSTOMER_DATA WHERE CUSTOMER_NAME LIKE ? ORDER BY CUSTOMER_ID</Property>
<Property name="GetUserPropertiesForProfileSQL">SELECT EMAIL,AGE,STATUS FROM CUSTOMER_DATA WHERE CUSTOMER_NAME=?</Property>
</UserStoreManager>

<AuthorizationManager
class="org.wso2.carbon.user.core.authorization.JDBCAuthorizationManager">
<Property name="AdminRoleManagementPermissions">/permission</Property>
<Property name="AuthorizationCacheEnabled">true</Property>
</AuthorizationManager>
</Realm>
</UserManager>
[/sourcecode]

3. Same as you did in early post you have to place the mysql-connector-java-5.1.7-bin.jar and Sample_user_store-1.0.jar in to $IS_HOME/repository/component/libs

4. Start the Identity server and Sign in as admin.

5. Go to claim management.
is
Now you can see some Available Claim Dialects here. In the user store manager is using the http://wso2.org/claims so we need to add the new claim mapping on this.

5. Go to http://wso2.org/claims Dialect.
is

6. Add new Claim EMAIL
is1
Here you need to specify the Mapped Attribute (s) as EMAIL because in our custom user store manager check the property name as "EMAIL","AGE","STATUS"

7. Add new Claim AGE
is

8. Add new Claim STATUS
is1

9. Lets look at the users
is

And go to User profile
is

Now you can see the properties in the database is coming under user profile.

10. Now we need to use those properties and do some authorization. So we can easily move to XACML engine coming with the Identity server to do the needful. Here I'm not going to explain the behavior of the XACML Engine in Identity server but you can follow my early blog posts such as
Authentication and Authorization with WSO2ESB and WSO2IS
XACML Authorization

11. Now I'm going to create the XACML policy buy using the claims that we newly added.
is1

You can see the claim names that we mapped early so you can select those from the UI.
is

Click on the policy name and go to source view of XACML policy.
is

is1

and replace the Deny with Permit and update the policy.
is2

12. Now enable the created policy as follows.

is

13. Go to Tryit and test your policy.

is1

is2

This is very basic example but when its coming to the real world example you can do so many things with this user store extensible facility. as well you can do all the above stuff with the use of web services which are exposed by WSO2 Identity Server.

1 comment:

  1. Nice, thanks for posting. Is there a date set for when all of the wso2 components have a release compatible with the new identity server user/reg store kernel?

    ReplyDelete