Showing posts with label OSGi. Show all posts
Showing posts with label OSGi. Show all posts

Sunday, March 6, 2016

How to write OSGi tests for C5 compoments

WSO2 C5 Carbon Kernel will be the heart of all the next generation Carbon products. With Kernel version 5.0.0 we introduced PAX OSGi testing.

Now we are trying to ease the life of C5 components developers by providing a utility, which will take care of most of the generic configurations need in OSGi testing. This will enable the C5 component developer to just specify a small number of dependencies and start writing PAX tests for C5 components.

You will have to depend on the following library, except for other PAX dependencies


1
2
3
4
5
6
<dependency>
    <groupId>org.wso2.carbon</groupId>
    <artifactId>carbon-kernel-osgi-test-util</artifactId>
    <version>5.1.0-SNAPSHOT</version>
    <scope>test</scope>
</dependency>


You can find a working sample on the following Git repo
https://github.com/jsdjayanga/c5-sample-osgi-test

Above will load the dependencies you need by default to test Carbon Kernel functionalities. But as a component developer you will have to specify your components jars into the testing environment. This is done via @Configuration annotation in your test class.

Lets assume you work on a bundle org.wso2.carbon.jndi:org.wso2.carbon.jndi, below is how you should specify your dependencies.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
    @Configuration
    public Option[] createConfiguration() {
        List<Option> customOptions = new ArrayList<>();
        customOptions.add(mavenBundle().artifactId("org.wso2.carbon.jndi").groupId("org.wso2.carbon.jndi")
                .versionAsInProject());

        CarbonOSGiTestEnvConfigs configs = new CarbonOSGiTestEnvConfigs();
        configs.setCarbonHome("/home/jayanga/WSO2/Training/TestUser/target/carbon-home");
        return CarbonOSGiTestUtils.getAllPaxOptions(configs, customOptions);
    }


Once these are done, your test should ideally work :)

Wednesday, December 23, 2015

The core of the next-generation WSO2 Carbon platform : WSO2 Carbon Kernel 5.0.0

A whole new revamp of the heart of all WSO2 products : WSO2 Carbon Kernel 5.0.0, was released 21 Dec 2015.

Previous versions of the WSO2 Carbon Kernel, (1.x.x to 4.x.x) had a much similar architecture and was tightly coupled with relatively old technologies (axiom, axis2, SOAP, etc.). Which is the same reason, which made us to re-think and re-architecture everything from the ground, and to come up with WSO2 Carbon Kernel 5.0.0.

The new Kernel is armed with the latest technologies and patterns. It will provide the key functionality for server developers on top of the underline OSGi runtime.

Key Features
  • Transport Management Framework
  • Logging Framework with Log4j 2.0 as the Backend
  • Carbon Startup Order Resolver
  • Dropins Support for OSGi Ready Bundles
  • Jar to Bundle Conversion Tool
  • Artifact Deployment Engine
  • Pluggable Runtime Support

You can download the product from [1], and find more information on [2].

[1] http://product-dist.wso2.com/products/carbon/5.0.0/wso2carbon-kernel-5.0.0.zip
[2] https://docs.wso2.com/display/Carbon500/WSO2+Carbon+Documentation

Thursday, March 26, 2015

WSO2 Carbon : Get notified just after the server start and just before server shutdown

WSO2 Carbon [1] is a 100% open source, integrated and componentized middleware platform which enables you to develop your business and enterprise solutions rapidly. WSO2 Carbon is based on OSGi framework [2]. It inherits molecularity and dynamism from the OSGi.

In this post I am going to show you how to get notified, when the server is starting up and when the server is about to shut down. 

In OSGi, bundle start up sequence is random. So you can't rely on the bundle start up sequence.

There are real world scenarios where you have some dependencies amount bundles, hence need to perform some actions before other dependent bundles get deactivated in the server shutdown.

Eg. Let's say you have to send messages to a external system. Your message sending module use your authentication module to authenticate the request and send it to the external system and your message sending module try to send all the buffered messages before the server shutdown.

Bundle unloading sequence in OSGi not happened in a guaranteed sequence. So, what would happen if your authentication bundle get deactivated before your message sending bundle get deactivated. In this case message sending module can't send the messages

To help these type of scenarios WSO2 Carbon framework provide you with a special OSGi service which can be used to detect the server start up and server shutdown

1. How to get notified the server startup

Implement the interface org.wso2.carbon.core.ServerStartupObserver [3], and register it as a service via the bundle context.

When the server is starting you will receive notifications via completingServerStartup() and completedServerStartup()


2. How to get notified the server shutdown

Implement the interface org.wso2.carbon.core.ServerShutdownHandler [4], and register it as a service via the bundle context.

When the server is about to shutdown you will receive the notification via invoke()

eg:

1
2
3
4
5
6
7
protected void activate(ComponentContext componentContext) {
 try {
     componentContext.getBundleContext().registerService(ServerStartupObserver.class.getName(), new CustomServerStartupObserver(), null) ;
 } catch (Throwable e) {
     log.error("Failed to activate the bundle ", e);
 }
}



Tuesday, September 30, 2014

Custom Authenticator for WSO2 Identity Server (WSO2IS) with Custom Claims

WSO2IS is one of the best Identity Servers, which enables you to offload your identity and user entitlement management burden totally from your application. It comes with many features, supports many industry standards and most importantly it allows you to extent it according to your security requirements.

In this post I am going to show you how to write your own Authenticator, which uses some custom claim to validate users and how to invoke your custom authenticator with your web app.

Create your Custom Authenticator Bundle

WSO2IS is based OSGi, so if you want to add a new authenticator you have to crate an OSGi bungle. Following is the source of the OSGi bundle you have to prepare.

This bundle will consist of three files,
1. CustomAuthenticatorServiceComponent
2. CustomAuthenticator
3. CustomAuthenticatorConstants

CustomAuthenticatorServiceComponent is an OSGi service component it basically registers the CustomAuthenticator (service). CustomAuthenticator is an implementation of org.wso2.carbon.identity.application.authentication.framework.ApplicationAuthenticator, which actually provides our custom authentication.


1. CustomAuthenticatorServiceComponent


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package org.wso2.carbon.identity.application.authenticator.customauth.internal;

import java.util.Hashtable;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.osgi.service.component.ComponentContext;
import org.wso2.carbon.identity.application.authentication.framework.ApplicationAuthenticator;
import org.wso2.carbon.identity.application.authenticator.customauth.CustomAuthenticator;
import org.wso2.carbon.user.core.service.RealmService;

/**
 * @scr.component name="identity.application.authenticator.customauth.component" immediate="true"
 * @scr.reference name="realm.service"
 * interface="org.wso2.carbon.user.core.service.RealmService"cardinality="1..1"
 * policy="dynamic" bind="setRealmService" unbind="unsetRealmService"
 */
public class CustomAuthenticatorServiceComponent {

    private static Log log = LogFactory.getLog(CustomAuthenticatorServiceComponent.class);

    private static RealmService realmService;
    
    protected void activate(ComponentContext ctxt) {

        CustomAuthenticator customAuth = new CustomAuthenticator();
     Hashtable<String, String> props = new Hashtable<String, String>();
     
        ctxt.getBundleContext().registerService(ApplicationAuthenticator.class.getName(), customAuth, props);
        
        if (log.isDebugEnabled()) {
            log.info("CustomAuthenticator bundle is activated");
        }
    }

    protected void deactivate(ComponentContext ctxt) {
        if (log.isDebugEnabled()) {
            log.info("CustomAuthenticator bundle is deactivated");
        }
    }
    
    protected void setRealmService(RealmService realmService) {
        log.debug("Setting the Realm Service");
        CustomAuthenticatorServiceComponent.realmService = realmService;
    }

    protected void unsetRealmService(RealmService realmService) {
        log.debug("UnSetting the Realm Service");
        CustomAuthenticatorServiceComponent.realmService = null;
    }

    public static RealmService getRealmService() {
        return realmService;
    }

}


2. CustomAuthenticator

This is where your actual authentication logic is implemented


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
package org.wso2.carbon.identity.application.authenticator.customauth;

import java.io.IOException;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.wso2.carbon.identity.application.authentication.framework.AbstractApplicationAuthenticator;
import org.wso2.carbon.identity.application.authentication.framework.AuthenticatorFlowStatus;
import org.wso2.carbon.identity.application.authentication.framework.LocalApplicationAuthenticator;
import org.wso2.carbon.identity.application.authentication.framework.config.ConfigurationFacade;
import org.wso2.carbon.identity.application.authentication.framework.context.AuthenticationContext;
import org.wso2.carbon.identity.application.authentication.framework.exception.AuthenticationFailedException;
import org.wso2.carbon.identity.application.authentication.framework.exception.InvalidCredentialsException;
import org.wso2.carbon.identity.application.authentication.framework.exception.LogoutFailedException;
import org.wso2.carbon.identity.application.authentication.framework.util.FrameworkUtils;
import org.wso2.carbon.identity.application.authenticator.customauth.internal.CustomAuthenticatorServiceComponent;
import org.wso2.carbon.identity.base.IdentityException;
import org.wso2.carbon.identity.core.util.IdentityUtil;
import org.wso2.carbon.user.api.UserRealm;
import org.wso2.carbon.user.core.UserStoreManager;
import org.wso2.carbon.utils.multitenancy.MultitenantUtils;

/**
 * Username Password based Authenticator
 * 
 */
public class CustomAuthenticator extends AbstractApplicationAuthenticator
        implements LocalApplicationAuthenticator {

    private static final long serialVersionUID = 192277307414921623L;

    private static Log log = LogFactory.getLog(CustomAuthenticator.class);

 @Override
 public boolean canHandle(HttpServletRequest request) {
        String userName = request.getParameter("username");
  String password = request.getParameter("password");

  if (userName != null && password != null) {
   return true;
  }

  return false;
 }

    @Override
    public AuthenticatorFlowStatus process(HttpServletRequest request,
                                           HttpServletResponse response, AuthenticationContext context)
            throws AuthenticationFailedException, LogoutFailedException {

        if (context.isLogoutRequest()) {
            return AuthenticatorFlowStatus.SUCCESS_COMPLETED;
        } else {
            return super.process(request, response, context);
        }
    }

 @Override
 protected void initiateAuthenticationRequest(HttpServletRequest request,
   HttpServletResponse response, AuthenticationContext context)
   throws AuthenticationFailedException {

  String loginPage = ConfigurationFacade.getInstance().getAuthenticationEndpointURL();
  String queryParams = FrameworkUtils
    .getQueryStringWithFrameworkContextId(context.getQueryParams(),
      context.getCallerSessionKey(),
      context.getContextIdentifier());
  
  try {
      String retryParam = "";
            
            if (context.isRetrying()) {
                retryParam = "&authFailure=true&authFailureMsg=login.fail.message";
            }
      
            response.sendRedirect(response.encodeRedirectURL(loginPage + ("?" + queryParams))
                    + "&authenticators=" + getName() + ":" + "LOCAL" + retryParam);
  } catch (IOException e) {
   throw new AuthenticationFailedException(e.getMessage(), e);
  }
 }

 @Override
 protected void processAuthenticationResponse(HttpServletRequest request,
   HttpServletResponse response, AuthenticationContext context)
   throws AuthenticationFailedException {

  String username = request.getParameter("username");
  String password = request.getParameter("password");

  boolean isAuthenticated = false;

  // Check the authentication
  try {
   int tenantId = IdentityUtil.getTenantIdOFUser(username);
            UserRealm userRealm = CustomAuthenticatorServiceComponent.getRealmService()
                    .getTenantUserRealm(tenantId);
            
            if (userRealm != null) {
                UserStoreManager userStoreManager = (UserStoreManager)userRealm.getUserStoreManager();
                isAuthenticated = userStoreManager.authenticate(MultitenantUtils.getTenantAwareUsername(username),password);

                Map<String, String> parameterMap = getAuthenticatorConfig().getParameterMap();
                String blockSPLoginClaim = null;
                if(parameterMap != null) {
                    blockSPLoginClaim = parameterMap.get("BlockSPLoginClaim");
                }
                if (blockSPLoginClaim == null) {
                    blockSPLoginClaim = "http://wso2.org/claims/blockSPLogin";
                }
                if(log.isDebugEnabled()) {
                    log.debug("BlockSPLoginClaim has been set as : " + blockSPLoginClaim);
                }

                String blockSPLogin = userStoreManager.getUserClaimValue(MultitenantUtils.getTenantAwareUsername(username),
                        blockSPLoginClaim, null);

                boolean isBlockSpLogin = Boolean.parseBoolean(blockSPLogin);
                if (isAuthenticated && isBlockSpLogin) {
                    if (log.isDebugEnabled()) {
                        log.debug("user authentication failed due to user is blocked for the SP");
                    }
                    throw new AuthenticationFailedException("SPs are blocked");
                }
            } else {
                throw new AuthenticationFailedException("Cannot find the user realm for the given tenant: " + tenantId);
            }
  } catch (IdentityException e) {
   log.error("CustomAuthentication failed while trying to get the tenant ID of the use", e);
   throw new AuthenticationFailedException(e.getMessage(), e);
  } catch (org.wso2.carbon.user.api.UserStoreException e) {
   log.error("CustomAuthentication failed while trying to authenticate", e);
   throw new AuthenticationFailedException(e.getMessage(), e);
  }

  if (!isAuthenticated) {
   if (log.isDebugEnabled()) {
    log.debug("user authentication failed due to invalid credentials.");
            }

            throw new InvalidCredentialsException();
  }

  context.setSubject(username);
  String rememberMe = request.getParameter("chkRemember");

  if (rememberMe != null && "on".equals(rememberMe)) {
   context.setRememberMe(true);
  }
 }
 
 @Override
 protected boolean retryAuthenticationEnabled() {
  return true;
 }
 
 @Override
 public String getContextIdentifier(HttpServletRequest request) {
  return request.getParameter("sessionDataKey");
 }

 @Override
 public String getFriendlyName() {
  return CustomAuthenticatorConstants.AUTHENTICATOR_FRIENDLY_NAME;
 }

 @Override
 public String getName() {
  return CustomAuthenticatorConstants.AUTHENTICATOR_NAME;
 }
}

3. CustomAuthenticatorConstants

This is a helper class to just to hold the constants you are using in your authenticaator


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package org.wso2.carbon.identity.application.authenticator.customauth;

/**
 * Constants used by the CustomAuthenticator
 *
 */
public abstract class CustomAuthenticatorConstants {
 
 public static final String AUTHENTICATOR_NAME = "CustomAuthenticator";
 public static final String AUTHENTICATOR_FRIENDLY_NAME = "custom";
 public static final String AUTHENTICATOR_STATUS = "CustomAuthenticatorStatus";
}

Once you are done with these files, your authenticator is ready. Now you can build you OSGi bundle and place the bundle inside <CRBON_HOME>/repository/components/dropins.

*sample pom.xml file [3]

Create new Claim

Now you have to create a new claim in WSO2IS. To do this, log into the management console of WSO2IS and do the steps described in [1]. In this example, I am going to create new claim "Block SP Login".

So, goto configuration section of the management console click on "Claim Management", then select "http://wso2.org/claims" Dialect

Click on "Add New Claim Mapping", and fill the details related to your claim.

Display Name   Block SP Login
Description   Block SP Login
Claim Uri http://wso2.org/claims/blockSPLogin
Mapped Attribute (s)  localityName
Regular Expression  
Display Order   0
Supported by Default  true
Required   false
Read-only   false

Now, your new claim is ready in WSO2IS. As you select "Supported by Default" as true, this claim will be available in your user profile. So you will see this field appear, when you try to create a user, but this field in not mandatory as you didn't specify it as "Required"

Change application-authentication.xml

There is another configuration change you have to do, as it is going to take the claim name from the configuration file (CustomAuthenticator.java, 107-114). Add the information about the your new claim into repository/conf/security/application-authentication.xml


1
2
3
<AuthenticatorConfig name="CustomAuthenticator" enabled="true">
<Parameter name="BlockSPLoginClaim">http://wso2.org/claims/blockSPLogin</Parameter>
</AuthenticatorConfig> 

If you check the code CustomAuthenticator.java line,107-128. You will see in the processAuthenticationResponse, in addition to authenticating the user from the user store, it checks for the new claim,

So, this finishes the, basic steps to setup your custom authentication. Now you have to setup new Service Provider in WSO2IS and set you custom authentication to it. So that when ever your SP try to authenticate a user from WSO2IS, it will use your custom authenticator.

Create Service Provider and set the Authenticator

Follow the basic steps given in [2] to create a new Service Provider.

Then, goto, "Inbound Authentication Configuration"->"SAML2 Web SSO Configuration", and make the following changes,


1
2
3
4
5
6
Issuer* = <name of you SP>
Assertion Consumer URL = <http://localhost:8080/your-app/samlsso-home.jsp>
Enable Response Signing = true
Enable Assertion Signing = true 
Enable Single Logout = true
Enable Attribute Profile = true

Then goto, "Local & Outbound Authentication Configuration" section,
select "Local Authentication" as the authentication type, and select your authenticator, here "custom".

Now you have completed all the steps needed to setup your custom autheticator with your custom claims

You can now start the WSO2IS, and start using your service. Meanwhile, change the value of the "Block SP Login" of a particular user and see the effect.


[1] https://docs.wso2.com/display/IS500/Adding+New+Claim+Mapping
[2] https://docs.wso2.com/display/IS500/Adding+a+Service+Provider
[3] https://drive.google.com/file/d/0B25Kjdxz8EhCQktfdG5MYkFnTUk/view?usp=sharing