Thursday, February 18, 2010

Soa 11g Human Task client Authentication

In Soa Suite 10g & 11g you can make your own HumanTask Client ( instead of the Worklist Application ) and integrate this in your own Application. Together with Ronald van Luttikhuizen we made an Human Task Client with works with the Soa Suite 10g & 11g version. The 10g version was a bit tricky because we called the 10g Human Task EJB on an OC4J container from an Weblogic Web application. With Soa Suite 11g I don't have these problems because everything is working on the Weblogic Container. In Soa 11g you have three ways to connect to Human Task Service. Use the local or remote EJB and you can call the soap service. The local EJB option is not acceptable because we need to deploy the customer application on the Soa Suite server else it won't work. With the Soap client I need to configure OWSM on the Weblogic Service and soap is not so fast as the Remote EJB option. This gives a other problem I don't want to use the soa suite weblogic account in the customer application.
The user have to authenticate in the Customer application and this user is also known in the Human Task ( I add the same authenticator in the ADF & Soa Suite server) So why don't we use the same user for the EJB call to the Soa Suite.
To make this works you need to make a domain trust between the two Weblogic domains.
To do this you need to go to the weblogic console and change some domain properties.
First change, enable the "Cross Domain Security Enabled" option.
Save this change and go the advanced options where you need to change the domain credential, this password must match with the Soa suite domain password.


Do this on both domains.

The next step is to create Workflow Client in your application. It will use your Application authentication for this remote EJB, so you don't to have a Soa Suite account in your application code.

String wlsserver = "HumanWorkFlow";
String soaserver = System.getProperty("humantask.url");
String wsurl = "http://"+soaserver;
String t3url = "t3://"+soaserver;
String contextFactory = "weblogic.jndi.WLInitialContextFactory";

String identityDomain = "jazn.com";
IWorkflowContext context = null;

IWorkflowServiceClient workflowServiceClient;
BPMIdentityService bpmClient;


WorkflowServicesClientConfigurationType wscct = new WorkflowServicesClientConfigurationType();

List<ServerType> servers = wscct.getServer();
ServerType server = new ServerType();
server.setDefault(true);
server.setName(wlsserver);
servers.add(server);

RemoteClientType rct = new RemoteClientType();
rct.setServerURL(t3url);

rct.setInitialContextFactory(contextFactory);
rct.setParticipateInClientTransaction(false);

server.setRemoteClient(rct);

workflowServiceClient = WorkflowServiceClientFactory.getWorkflowServiceClient(
WorkflowServiceClientFactory.REMOTE_CLIENT,
wscct,
logger2);

Map<IWorkflowServiceClientConstants.CONNECTION_PROPERTY,java.lang.String> properties =
new HashMap<IWorkflowServiceClientConstants.CONNECTION_PROPERTY,java.lang.String>();

properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.SOAP_END_POINT_ROOT
, wsurl);

bpmClient = WorkflowServiceClientFactory.getSOAPIdentityServiceClient(identityDomain
, properties
, logger2);


The last step is to acquire and release the Human Tasks on behalf of the application user. Because you don't know the application user password you need to have an account which can do that for the application user. This code can do that for the application user. Make sure that this account don't have too much authorization rights in Weblogic and your application.

String identityUsername = System.getProperty("humantask.user" );
String identityPassword = System.getProperty("humantask.password");

IWorkflowContext contextBehalf = null;

ITaskQueryService taskQueryService = getTaskQueryService();

if ( context == null ) {
System.out.println("HumanWorkflow "+identityUsername+ " context created");
context = taskQueryService.authenticate( identityUsername
, identityPassword.toCharArray()
, identityDomain);
}
contextBehalf = taskQueryService.authenticateOnBehalfOf(context, onBehalfOfUser);

Thursday, February 11, 2010

Soa 11g Identity Service and Human Task

Soa 11g has an Identity web service which you can use to retrieve your Human Workflow Groups and Users. In this Blog I will show you how you can use this identity service to add users and groups with their managers ( build a hierarchy ) without the need of an expensive Directory server. In the Human Task you can use this hierarchy for Task Escalation and these groups and users can also be used for the Human Task owner or assignee. Basically the identity service will return all the users and groups of the myrealm weblogic security realm. Too bad building this hierarchy is not possible in the Weblogic Console or Enterprise Manager website.
When you have an LDAP server like Oracle Internet Directory or Active Directory you can use this for the Human workflow groups and users. Make sure that the control type of all the authenticators has the SUFFICIENT value and that is LDAP is the first server in the Authentication providers.
So the first step is to retrieve all the Human Task groups & users. For this we need to call the Identity web service, this WS only runs on the Weblogic soa_server1 server ( so we need to connect to port 8001 and no authorization is required). This information can't be retrieved from the Human Task EJB and the security realm is always jazn.com or just leave it empty.

package nl.whitehorses.soa.human.workflow;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Logger;

import oracle.bpel.services.workflow.client.IWorkflowServiceClientConstants;
import oracle.bpel.services.workflow.client.WorkflowServiceClientFactory;

import oracle.tip.pc.services.identity.BPMGroup;
import oracle.tip.pc.services.identity.BPMIdentityException;
import oracle.tip.pc.services.identity.BPMIdentityService;
import oracle.tip.pc.services.identity.BPMUser;

public class WorkflowGroupUser {

private String wsurl = "http://localhost:8001";
private String secdomain = "jazn.com";

private BPMIdentityService bpmClient;

private Logger logger = Logger.getLogger(WorkflowGroupUser.class.getName());


public WorkflowGroupUser() {
Map<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, java.lang.String> properties =
new HashMap<IWorkflowServiceClientConstants.CONNECTION_PROPERTY, java.lang.String>();

properties.put(IWorkflowServiceClientConstants.CONNECTION_PROPERTY.SOAP_END_POINT_ROOT, wsurl);
bpmClient = WorkflowServiceClientFactory.getSOAPIdentityServiceClient(secdomain,
properties,
logger);
}

public List<BPMGroup> getGroups() throws BPMIdentityException {
List<BPMGroup> groups = bpmClient.searchGroups("name", "*");
return groups;
}

public List<BPMUser> getUsers() throws BPMIdentityException {

List<BPMUser> users = bpmClient.searchUsers("name", "*");
return users;
}


public static void main(String[] args) {
WorkflowGroupUser workflowGroupUser = new WorkflowGroupUser();

try {

for (BPMGroup group : workflowGroupUser.getGroups()) {
System.out.println("groups: " + group.getDisplayName());
}
for (BPMUser user : workflowGroupUser.getUsers()) {
System.out.println("users: " + user.getDisplayName()+" manager " +user.getManager());

}
} catch (BPMIdentityException e) {
e.printStackTrace();
}
}
}

For Security reasons I need to make a Servlet or EJB Session Bean and deploy this to the Weblogic soa server ( not the admin server ). In this Servlet or EJB Session Bean we can retrieve IdentityConfigService and initialize the IdentityStore. In this blog I choose for an EJB Session Bean.

package nl.whitehorses.soa.human.workflow.model;

import java.util.List;

import javax.ejb.Remote;
import javax.ejb.Stateless;

import oracle.security.idm.IMException;
import oracle.security.idm.IdentityStore;
import oracle.security.idm.Property;
import oracle.security.idm.PropertySet;
import oracle.security.idm.Role;
import oracle.security.idm.RoleManager;
import oracle.security.idm.RoleProfile;
import oracle.security.idm.User;
import oracle.security.idm.UserManager;
import oracle.security.idm.UserProfile;

import oracle.tip.pc.services.common.ServiceFactory;
import oracle.tip.pc.services.identity.BPMIdentityException;
import oracle.tip.pc.services.identity.config.BPMIdentityConfigService;
import oracle.tip.pc.services.identity.config.ProviderCfg;
import oracle.tip.pc.services.identity.jps.JpsProvider;


@Stateless(name = "HumanWorkFlowEJB", mappedName = "HumanWorkFlowEJB")
@Remote
public class HumanWorkFlowEJBBean implements HumanWorkFlowEJB {


private BPMIdentityConfigService bpmConfig;
private IdentityStore identityStore;

public HumanWorkFlowEJBBean() throws BPMIdentityException, Exception {

bpmConfig = ServiceFactory.getIdentityConfigServiceInstance();

String realmName = bpmConfig.getDefaultRealmName();
ProviderCfg conf = bpmConfig.getConfiguration(realmName).getProviderCfg("Authorization");

JpsProvider provider = (JpsProvider)JpsProvider.getInstance(conf);
identityStore = provider.getIdentityStore();
System.out.println("default realm: " + realmName);
}


public void addUser(String name, String email,
String manager) throws IMException {
UserManager usrmgr = identityStore.getUserManager();

User user = null;
try {
user = identityStore.searchUser(name);
} catch (IMException e) {
}

if (user != null) {
System.out.println("user " + name + " exists");
} else {
PropertySet pset = new PropertySet();
pset.put(new Property("DESCRIPTION", name));
pset.put(new Property("USER_NAME", name));
pset.put(new Property("USER_ID", name));

char pwd[] = "weblogic".toCharArray();

user = usrmgr.createUser(name, pwd, pset);
user = identityStore.searchUser(user.getName());
UserProfile up = user.getUserProfile();
if ( email != null ) {
up.setBusinessEmail(email);
}
if ( manager != null ) {
User userManager = null;
try {
userManager = identityStore.searchUser(manager);
} catch (IMException ie) {
}
if (manager != null) {
up.setManager(userManager.getUniqueName());
}
}
}
}

public void addGroup(String group,
List<String> members) throws IMException {
RoleManager rolemgr = identityStore.getRoleManager();
Role role = null;
try {
role = identityStore.searchRole(1, group);
} catch (IMException e) {
}
if (role == null) {
role = rolemgr.createRole(group);
}

RoleProfile rolePrf = role.getRoleProfile();

if (members != null && members.size() > 0) {
for (int m = 0; m < members.size(); m++) {
User user = null;
try {
user = identityStore.searchUser((String)members.get(m));
} catch (IMException e) {
}
if (user != null) {
rolemgr.grantRole(role, user.getPrincipal());
}
}
}
}


public void removeUser(String user) throws Exception {
UserManager usrmgr = identityStore.getUserManager();
if (user != null) {
User idtyUser = null;
try {
idtyUser = identityStore.searchUser(user);
} catch (IMException e) {
}

if (idtyUser != null) {
usrmgr.dropUser(idtyUser);
}
}
}

public void removeGroup(String role) throws Exception {
RoleManager rolemgr = identityStore.getRoleManager();
Role idtyRole = null;
try {
idtyRole = identityStore.searchRole(1, role);
} catch (IMException e) {
}

if (idtyRole != null) {
rolemgr.dropRole(idtyRole);
}
}

}

And the Remote interface of this EJB Session Bean.

package nl.whitehorses.soa.human.workflow.model;

import java.util.List;

import javax.ejb.Remote;

import oracle.security.idm.IMException;

@Remote
public interface HumanWorkFlowEJB {

public void removeGroup(String role) throws Exception;
public void removeUser(String user) throws Exception;
public void addGroup(String group, List<String> members) throws IMException;
public void addUser(String name, String email,String manager) throws IMException;

}

This EJB Session needs the bpm-services.jar located in the following folder Soa11gPS1\Oracle_SOA1\soa\modules\oracle.soa.workflow_11.1.1\bpm-services.jar
Add this jar as a library in Weblogic, use the name bpm-services and make sure it is targeted on the soa server and not the admin server.
Add the bpm-services library reference to the weblogic-application.xml deployment descriptor. this file will be added to the META-INF of the ear deployment.

<?xml version = '1.0' encoding = 'windows-1252'?>
<weblogic-application xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.bea.com/ns/weblogic/weblogic-application http://www.bea.com/ns/weblogic/weblogic-application/1.0/weblogic-application.xsd"
xmlns="http://www.bea.com/ns/weblogic/weblogic-application">
<library-ref>
<library-name>bpm-services</library-name>
</library-ref>
</weblogic-application>

Deploy the EJB Session Bean to the Soa server and not to the Admin server.
After this we can add some users with their managers. I will use this EJB test client for this( connect to the SoaSuite server ( port 8001) not the Admin server

package nl.whitehorses.soa.human.workflow;

import java.util.ArrayList;
import java.util.Hashtable;

import java.util.List;

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;

import nl.whitehorses.soa.human.workflow.model.HumanWorkFlowEJB;

public class HumanWorkFlowEJBClient {
public static void main(String [] args) {
try {
final Context context = getInitialContext();
HumanWorkFlowEJB humanWorkFlowEJB = (HumanWorkFlowEJB)context.lookup("HumanWorkFlowEJB#nl.whitehorses.soa.human.workflow.model.HumanWorkFlowEJB");

humanWorkFlowEJB.addUser("admin1", "admin1@wh.nl", null);

humanWorkFlowEJB.addUser("admin3", "admin3@wh.nl", "admin1");

List<String> members = new ArrayList();
members.add("admin3");
members.add("admin1");

humanWorkFlowEJB.addGroup("App-MHS-Admin_2", members);


} catch (Exception ex) {
ex.printStackTrace();
}
}

private static Context getInitialContext() throws NamingException {
Hashtable env = new Hashtable();
// WebLogic Server 10.x connection details
env.put( Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory" );
env.put(Context.PROVIDER_URL, "t3://localhost:8001");
return new InitialContext( env );
}
}

These Human Taks users & groups are normal Weblogic users & groups and are visible in the Weblogic Security Realm ( Weblogic Console ).

Sunday, February 7, 2010

Web service references and Soa configuration plan

With Soa Suite 11g you can off course use web service adapters in your composite application and use a configuration plan to change these reference endpoints, so it can work in production or acceptance environment. In a project I did this for a customer where I used composites as a reference and I just generated a configuration plan.
JDeveloper generates a plan and already did some good work for you by detecting the web service references. We only have to change the location attribute of the ws binding of this reference.
But when I deployed these composite to acceptance, I got deployment errors. Looking at the error in the soa_server1-diagnostic.log file I saw that it is complaining about there are more versions of one XSD. This can be true because the calling composite in development has a higher release than that of acceptance. Somehow there is still a reference to the development enviroment. Analyzing the soa project I saw there are some import references in the composite.xml
and in my mediator.wsdl. I also need to change these endpoints in the just generated configuration plan. First let change the import references of the composite.xml. JDeveloper already generated did some work for you.
Change this from
To this
And now I have to change the component WSDL's and XSD's of this composite application. JDeveloper already generates a wsdlAndSchema element with all your project files. When you add composite references or add components, you probably need to update your configuration plan.
So change this
to this.
and now you can deploy this composite to acceptance or production.

Thursday, February 4, 2010

Invoking FMW Application MBeans in Weblogic

In the Fusion Middleware 11g Oracle added a lot of ADF, MDS and Soa Suite Application MBeans. These MBeans have attributes and operations which you can set , get or invoke. So with these MBeans you can control ADF, MDS or a Soa composite. Off course you do the same in the FMW Enterprise Manager(EM) application but sometimes it can be better to script actions so you can garantee that your Acceptance or Production Weblogic are the same. So lets take a look add these FMW MBeans and play with these FMW Mbeans. First we need to go the EM application to get an overview. The EM has a nice MBeans Browser.
Here you get a list of the Application Defined MBeans. These MBeans can't be invoked with a specific WLST command.
For example we can select a Soa composite application.
Here we can see or change the attributes of this Composite Mbean, We can retrieve these attributes and some of them we can change when the Access of the attribute = RW

In the operation tab we can see the operation methods of this MBean. You also can invoke the operation in this EM application just by selecting the operation.
Now lets invoke these operations from WLST or rettrieve an attribute
We need to start WLST
cd FMW_HOME\wlserver_10.3\common\bin
wlst.cmd
connect('weblogic','weblogic1','t3://localhost:7001')

To stop WLST we can use
disconnect()
exit()

In the WLST windows after we are connected we can go the domainRuntime where the application MBeans are located. domainRuntime()
Then we can lookup the MBean, the MBean can be seen in the MBean Browser ( above the atrributes & operations) For example mdsBean = ObjectName('oracle.mds.lcm:name=MDSDomainRuntime,type=MDSDomainRuntime')
We can invoke for example the operation listRepositories, this operation has no parameters.
Type repositories = mbs.invoke(mdsBean, 'listRepositories',None,None)
When the operation has parameters then we need to provide the parameters and the type of these parameters like this.
params = [Name]
sign = ['java.lang.String']
partitions = mbs.invoke(mdsBean, "listPartitions", params, sign)
To get an MBean attribute value we can use getAttribute
mdsObject = ObjectName(repository)
Name = mbs.getAttribute(mdsObject, 'Name')
Here is an example where I retrieve all the MDS repositories with the parttions.
domainRuntime()

mdsBean = ObjectName('oracle.mds.lcm:name=MDSDomainRuntime,type=MDSDomainRuntime')

print 'invoke listRepositories without parameters on mds bean'
repositories = mbs.invoke(mdsBean, 'listRepositories',None,None)
for repository in repositories:
mdsObject = ObjectName(repository)
Name = mbs.getAttribute(mdsObject, 'Name')
print 'mds repository object: ', Name
params = [Name]
sign = ['java.lang.String']
partitions = mbs.invoke(mdsBean, "listPartitions", params, sign)
for partition in partitions:
print 'partition: ', partition

or a Soa Suite example to get a list with all the deployed composites
domainRuntime()

soaBean = ObjectName('oracle.soa.config:Location=soa_server1,name=soa-infra,j2eeType=CompositeLifecycleConfig,Application=soa-infra')
print soaBean
composites = mbs.getAttribute(soaBean, 'DeployedComposites')
for composite in composites:
print 'composite: ',composite


Here an example to set a SOA Composite auditlevel to Development mode
# function to help locate a mbean(s)
# that match a specific name
def findMBean(prefix, name):
    # get a listing of everything in the current directory
    mydirs = ls(returnMap='true');
        
    # we're going to use a regular expression for our test
    pattern = java.util.regex.Pattern.compile(str(prefix) + str('.*name=') + str(name) + str('.*$'));
        
    # loop through the listing
    for mydir in mydirs:
        x = java.lang.String(mydir);
        matcher = pattern.matcher(x);
        # if we find a match, add it to the found list
        while matcher.find():
            return x;

print 'starting the script ....'
username = 'weblogic'
password = 'weblogic1'
url='t3://localhost:7001'

connect(username,password,url)

custom();
cd('oracle.soa.config');

composite = findMBean('oracle.soa.config:partition=default,j2eeType=SCAComposite',
                      '"CallingComposite"');
print 'bean ' + str(composite);
cd( composite );
print 'path: ' + pwd();

params = jarray.array(['auditLevel','Development'],
                       java.lang.Object)
sign   = jarray.array(['java.lang.String','java.lang.String'], 
                      java.lang.String)
invoke('setProperty', 
       params, 
       sign); 

disconnect();

The same but then in an other way
print 'starting the script ....'
username = 'weblogic'
password = 'weblogic1'
url='t3://localhost:7001'

connect(username,password,url)

domainRuntime()

helloworldComposite = ObjectName('oracle.soa.config:Location=AdminServer,partition=default,j2eeType=SCAComposite,revision=1.0,label=soa_d84546d1-22b0-496a-8b33-a5705caf19e6,Application=soa-infra,wsconfigtype=WebServicesConfig,name="Helloworld"')
print helloworldComposite
params = ['auditLevel','Development'] 
sign = ['java.lang.String','java.lang.String'] 
mbs.invoke(helloworldComposite, 'setProperty', params, sign) 
print mbs.getAttribute(helloworldComposite, 'Properties')

Monday, February 1, 2010

Weblogic / ADF web application security

On my company blog I made two blogs how to implement security in your Web Application. The first part is the standard container security. In this blog I describe the Weblogic Role mapping, How to retrieve the user roles ( Weblogic Subject ) and how you can use this in a managed bean or in a JSF page.
The second part is ADF Security. With ADF Security you can protect your JSP or JSPX pages just like the default container security but ADF Security can do more like protecting your Task Flows ( fragments), Anonymous support, retrieve all the user roles, can create test users and roles in Weblogic. And how you can use ADF Security in your managed bean or JSF page. In this blog I also explain how the JAZN Enterprises roles maps to the Weblogic groups.