Posts Tagged: ‘Java’

LotusScript Web Services and NullPointerException

7. August 2015 Posted by Bernd Hort

DominoDesigner_90x90.png

Recently I worked on a demo application for a customer to show how to use Web Service Consumer in LotusScript. At one point I got a java.lang.NullPointerException error. It took me quite some time to figure out the reason for this error. So this blog post should help anyone how might run in the same problem. Because frankly the solution was really simple once I had found it.

I give you the summary first and the full technical explanation later in this blog post. So even if you are not interested in the details the main point to take away is:

LotusScript Web Services Consumer and Provider use internally Java and Java is case sensitive. If you do not obey this you might loose some hours of your life time chasing an error where no error is.

Here are the technical details.


One part of the demo were the different styles to use to generate the WSDL file from a Web Service Provider. There is a great article from IBM developerWorks Which style of WSDL should I use? on this topic. Highly recommended!

To be able to show the differences between RPC/encoded and Document/literal style I implemented two different Web Service Providers. Both providers had the same identical LotusScript code.

Then I implemented two Web Service Consumers just by importing the WSDL file from the previous generated Web Service Providers. Here are the two methods from the Web Service Consumers.

Web Service Consumer calling a RPC/encoded Web Service Provider
Web-Service-Provider-RPC-encoded-Settings.png

Function checkout_status(auftragnr As String) As String
  If Not IsDebugMode Then On Error Goto errorHandler
  On Error lsERR_NOTES_WSENGINE_UNINIT_METHOD_ARG Goto wsErrorHandler
  On Error lsERR_NOTES_WSENGINE_NOTINIT Goto wsErrorHandler
  On Error lsERR_NOTES_WSENGINE_ERROR Goto wsErrorHandler
  On Error lsERR_NOTES_WSENGINE_METHOD_ERROR Goto wsErrorHandler
  On Error lsERR_NOTES_WSENGINE_METHOD_FAULT Goto wsErrorHandler
  
  Let checkout_status = Service.Invoke("checkout_status", auftragnr)
  
  Exit Function
  
errorHandler:
  If HandleErrorWithContext(CreateErrorContext(Nothing,|auftragnr: "| & auftragnr & |"| )) = RESUME_NEXT_LINE Then Resume Next
  Call RethrowError()
  Exit Function
  
wsErrorHandler:
  If HandleErrorWithContext(CreateErrorContext(Nothing,|auftragnr: "| & auftragnr & |"| )) = RESUME_NEXT_LINE Then Resume Next
  Call CreateWebServiceLogEntry(Me, |auftragnr: "| & auftragnr & |"|, Nothing)
  Call RethrowError()
End Function

Web Service Consumer calling a Document/literal Web Service Provider
Web-Service-Provider-RPC-encoded-Settings.png

Function checkout_status(auftragnr As String) As String
  If Not IsDebugMode Then On Error Goto errorHandler
  On Error lsERR_NOTES_WSENGINE_UNINIT_METHOD_ARG Goto wsErrorHandler
  On Error lsERR_NOTES_WSENGINE_NOTINIT Goto wsErrorHandler
  On Error lsERR_NOTES_WSENGINE_ERROR Goto wsErrorHandler
  On Error lsERR_NOTES_WSENGINE_METHOD_ERROR Goto wsErrorHandler
  On Error lsERR_NOTES_WSENGINE_METHOD_FAULT Goto wsErrorHandler
  
  Let checkout_status = Service.Invoke("CHECKOUT_STATUS", auftragnr)
  
  Exit Function
  
errorHandler:
  If HandleErrorWithContext(CreateErrorContext(Nothing,|auftragnr: "| & auftragnr & |"| )) = RESUME_NEXT_LINE Then Resume Next
  Call RethrowError()
  Exit Function
  
wsErrorHandler:
  If HandleErrorWithContext(CreateErrorContext(Nothing,|auftragnr: "| & auftragnr & |"| )) = RESUME_NEXT_LINE Then Resume Next
  Call CreateWebServiceLogEntry(Me, |auftragnr: "| & auftragnr & |"|, Nothing)
  Call RethrowError()
End Function

Can you spot the difference? I guess it is easier after giving you the solution.

In the Document/literal version the line for actually calling the Web Service must be
Let CHECKOUT_STATUS = Service.Invoke("CHECKOUT_STATUS", auftragnr)
with capital CHECKOUT_STATUS.

I found out because I took the original generated code and enhanced it with some error handling. Since there could be not code without properly error handling. This is specially true for calling Web Service.

Since I'm a lazy programmer I copied the code from the one Web Service Consumer to the next not looking at the case. I was very confusing that the first Web Service Consumer worked and the second one returned a java.lang.NullPointerException.

After checking for all kind of reasons I finally started again with a new Web Service Consumer and then found the difference in the case.

The reason behind is the internal use of Java. While calling Service.Invoke in the background the Java Reflection API is used to find the corresponding method to call. Those two different WSDL styles lead to two different method names: checkout_status and CHECKOUT_STATUS. In LotusScript the case of a method name makes no difference. In Java it can mean you can spend hours looking for an error.

By the way if I had used Java in the first place to program the Web Service Consumer (or Provider) I would not have had this problem.

XPages: An optimized JavaScript Resource Renderer

21. Juni 2015 Posted by Sven Hasselbach

Ferry Kranenburg created a nice hack to solve the AMD loader problem with XPages and Dojo, and because of the missing ability to add a resource to the bottom of an XPage by a property, I have created a new JavaScriptRenderer which allows to control where a CSJS script will be rendered.

The renderer has multiple options:

  • NORMAL – handles the CSJS resource as always
  • ASYNC – loads the script in an asynchronous way (with an own script tag)
  • NOAMD – adds the no amd scripts around the resource
  • NORMAL_BOTTOM – adds the script at the bottom of the <body> tag
  • ASYNC_BOTTOM – async, but at the end of the generated HTML page
  • NOAMD_BOTTOM – at the end, with the surrounding no amd scripts

To use the normal mode, you don’t have to change you resource definition. If you want to use the other modes, you have to change the content type of the resource with one of the entries in the list above. This for example would add a script block to the end of the page, including the non amd script blocks around it:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
    <xp:this.resources>
        <xp:script clientSide="true" type="NOAMD_BOTTOM">
            <xp:this.contents><![CDATA[alert("Hello World!");]]></xp:this.contents>
        </xp:script>
    </xp:this.resources>
</xp:view>

2015-06-21 11_04_09

Here is the code for the resource renderer:

package ch.hasselba.xpages;

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

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import com.ibm.xsp.component.UIViewRootEx;
import com.ibm.xsp.renderkit.html_basic.ScriptResourceRenderer;
import com.ibm.xsp.resource.Resource;
import com.ibm.xsp.resource.ScriptResource;
import com.ibm.xsp.util.JSUtil;

public class OptimizedScriptResourceRenderer extends ScriptResourceRenderer {
    private static final String TYPE = "type";
    private static final String SCRIPT = "script";
    private static final String CSJSTYPE = "text/javascript";
    private boolean isBottom = false;
    
    private static enum Mode {
        NORMAL, ASYNC, NOAMD, ASYNC_BOTTOM, NOAMD_BOTTOM, NORMAL_BOTTOM
    }

    public void encodeResourceAtBottom(FacesContext fc,
            UIComponent uiComponent, Resource resource) throws IOException {
        isBottom = true;
        encodeResource(fc, uiComponent, resource);
        isBottom = false;
    }

    @Override
    public void encodeResource(FacesContext fc, UIComponent uiComponent,
            Resource resource) throws IOException {

        ScriptResource scriptResource = (ScriptResource) resource;
        ResponseWriter rw = fc.getResponseWriter();
        String type = scriptResource.getType();
        String charset = scriptResource.getCharset();
        String src = scriptResource.getSrc();

        Mode mode = Mode.NORMAL;
        try{
            mode = Mode.valueOf( type );
        }catch(Exception e){};

        if (mode == Mode.NORMAL || mode == Mode.NORMAL_BOTTOM ) {
            normalBottomJSRenderer( fc, uiComponent, scriptResource, (mode == Mode.NORMAL_BOTTOM), type );
        } else {
            if (mode == Mode.ASYNC || mode == Mode.ASYNC_BOTTOM) {
                asyncJSRenderer(fc, uiComponent, scriptResource, (mode == Mode.ASYNC_BOTTOM), rw, type,
                        charset, src );
            }else if (mode == Mode.NOAMD || mode == Mode.NOAMD_BOTTOM ) {
                noAMDJSRenderer(fc, uiComponent, scriptResource, (mode == Mode.NOAMD_BOTTOM) , rw, 
                        type, charset, src);
            }

        }

    }

    private void normalBottomJSRenderer(FacesContext fc,UIComponent uiComponent,
            ScriptResource scriptResource, final boolean addToBottom, final String type ) throws IOException {
        
        if( addToBottom && !isBottom )
            return;
        scriptResource.setType(null);
        super.encodeResource(fc, uiComponent, scriptResource);
        scriptResource.setType(type);
        
    }
    private void asyncJSRenderer(FacesContext fc,
            UIComponent uiComponent, ScriptResource scriptResource, 
             final boolean addToBottom, ResponseWriter rw, final String type, final String charset,
            final String src) throws IOException {
        
        if( addToBottom && !isBottom )
            return;
        
        Map<String, String> attrs = null;
        String key = null;
        String value = null;
        String id = "";

        if (scriptResource.getContents() == null) {
            attrs = scriptResource.getAttributes();
            if (!attrs.isEmpty()) {
                StringBuilder strBuilder = new StringBuilder(124);
                for (Iterator<String> it = attrs.keySet().iterator(); it
                        .hasNext();) {
                    key = it.next();
                    value = attrs.get(key);
                    strBuilder.append(key).append('(').append(value)
                            .append(')');
                }
                id = strBuilder.toString();
            }

            // check if already added
            UIViewRootEx view = (UIViewRootEx) fc.getViewRoot();

            String resId = "resource_" + ScriptResource.class.getName() + src
                    + '|' + type + '|' + charset + id;
            if (view.hasEncodeProperty(resId)) {
                return;
            }
            view.putEncodeProperty(resId, Boolean.TRUE);

        }
        if (!scriptResource.isClientSide()) {
            return;
        }

        rw.startElement(SCRIPT, uiComponent);
        JSUtil.writeln(rw);
        rw.write("var s = document.createElement('" + SCRIPT + "');");
        JSUtil.writeln(rw);
        rw.write("s.src = '" + src + "';");
        JSUtil.writeln(rw);
        rw.write("s.async = true;");
        JSUtil.writeln(rw);
        rw.write("document.getElementsByTagName('head')[0].appendChild(s);");
        JSUtil.writeln(rw);
        rw.endElement(SCRIPT);
        JSUtil.writeln(rw);
    }

    
    private void noAMDJSRenderer(FacesContext fc,
             UIComponent uiComponent,ScriptResource scriptResource,
            final boolean addToBottom, ResponseWriter rw, final String type, final String charset,
            final String src ) throws IOException {
        
        if( addToBottom && !isBottom )
            return;

        // write the "disable AMD" script
        rw.startElement(SCRIPT, uiComponent);
        rw.writeAttribute(TYPE, CSJSTYPE, TYPE);
        rw.writeText(
                        "'function'==typeof define&&define.amd&&'dojotoolkit.org'==define.amd.vendor&&(define._amd=define.amd,delete define.amd);",
                        null);
        rw.endElement(SCRIPT);
        JSUtil.writeln(rw);

        // write the normal CSJS
        scriptResource.setType(null);
        super.encodeResource(fc, uiComponent, scriptResource);
        scriptResource.setType(type);
        // write the "reenable AMD" script
        rw.startElement(SCRIPT, uiComponent);
        rw.writeAttribute(TYPE, CSJSTYPE, TYPE);
        rw
                .writeText(
                        "'function'==typeof define&&define._amd&&(define.amd=define._amd,delete define._amd);",
                        null);
        rw.endElement(SCRIPT);
        JSUtil.writeln(rw);

    }
}

The ViewRenderer must also be modified, otherwise it is not possible to add the resources at the bottom of the <body> tag:

package ch.hasselba.xpages;

import java.io.IOException;
import java.util.List;

import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;
import javax.faces.render.Renderer;

import com.ibm.xsp.component.UIViewRootEx;
import com.ibm.xsp.render.ResourceRenderer;
import com.ibm.xsp.renderkit.html_basic.ViewRootRendererEx2;
import com.ibm.xsp.resource.Resource;
import com.ibm.xsp.resource.ScriptResource;
import com.ibm.xsp.util.FacesUtil;

public class ViewRootRendererEx3 extends ViewRootRendererEx2 {

    protected void encodeHtmlEnd(UIViewRootEx uiRoot, ResponseWriter rw)
            throws IOException {
        FacesContext fc = FacesContext.getCurrentInstance();

        List<Resource> resources = uiRoot.getResources();
        for (Resource r : resources) {
            if (r instanceof ScriptResource) {
                ScriptResource scriptRes = (ScriptResource) r;
                if (scriptRes.isRendered()) {
                    Renderer renderer = FacesUtil.getRenderer(fc, scriptRes.getFamily(), scriptRes.getRendererType());
                    ResourceRenderer resRenderer = (ResourceRenderer) FacesUtil.getRendererAs(renderer, ResourceRenderer.class);
                    if( resRenderer instanceof OptimizedScriptResourceRenderer ){
                        ((OptimizedScriptResourceRenderer) resRenderer).encodeResourceAtBottom(fc, uiRoot, r);
                    }
                }
            }
        }

        rw.endElement("body");
        writeln(rw);
        rw.endElement("html");
    }

}

To activate the new renderes, you have to add them to the faces-config.xml:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
  <render-kit>
    <renderer>
      <component-family>com.ibm.xsp.resource.Resource</component-family>
      <renderer-type>com.ibm.xsp.resource.Script</renderer-type>
      <renderer-class>ch.hasselba.xpages.OptimizedScriptResourceRenderer</renderer-class>
    </renderer>
    <renderer>
      <component-family>javax.faces.ViewRoot</component-family>
      <renderer-type>com.ibm.xsp.ViewRootEx</renderer-type>
      <renderer-class>ch.hasselba.xpages.ViewRootRendererEx3</renderer-class>
    </renderer>
  </render-kit>
</faces-config>

Needless to say that this works in Themes too.

Neue JVM-Patches für Notes Domino 8.5.3 und 9.0.1

5. Juni 2015 Posted by Oliver Regelmann

Oracle fixt mal wieder Sicherheitslücken und IBM zieht diese im JVM-Patch SR16FP4 für ND 8.5.3 FP6 und 9.0.1 FP3 nach:

IBM Security Bulletin: Multiple vulnerabilities in IBM Java 6 SR16FP3 IF1 affect IBM Notes and Domino – United States

42. DNUG Konferenz: Preview auf den Techniktrack Entwicklung

28. April 2015 Posted by Roswitha Boldt

Track 2.4: Technik - Entwicklung

11. Juni 2015, 8:30 bis 15:45 Uhr

 

Integration von Salesforce und IBM Software - Andreas Rosen, QKom GmbH
Domino im Dialog mit einer OpenSource-Workflow-Engine - Jens Ribbeck / Veit Weber, ULC Business Solutions GmbH

Speed up your development in XPages or Javascript using Open Source - Tim Clark, Teamstudio (angefragt)

Vom XPages Held zum OSGI Guru! Wie man eine Extension Library entwickelt. Christian Güdemann, WebGate Consulting AG
XPages auf Bluemix - Niklas Heidloff, IBM


Track Manager sind Oliver Busse, We4IT GmbH, und Thorsten Hindermann, GWDG.

 

Hier erfahren Sie mehr über die 42. DNUG Konferenz in Dortmund:

Überblick

Preview der Tracks

Anmeldung als Teilnehmer

Übersicht über Sponsoren und Aussteller mit Kontaktmöglichkeit

 

XPages: Running Google’s Chrome V8 Javascript Engine (2)

10. April 2015 Posted by Sven Hasselbach

A while ago I tried to run Google’s V8 Javascript engine on top of XPages, and today I found the reason why my server crashed after the first click: I have tried to load the engine only once (statically), and that killed Domino completly.

Today I moved the code back into the processAction method, and now it works without problems.

package ch.hasselba.xpages.jav8;

import javax.faces.event.AbortProcessingException;
import javax.faces.event.ActionEvent;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import javax.script.ScriptException;

public class JAV8Test implements javax.faces.event.ActionListener {

    public void processAction(ActionEvent actionEvent)
            throws AbortProcessingException {

        ScriptEngineManager factory = new ScriptEngineManager();
        ScriptEngine engine = factory.getEngineByName("jav8");

        try {
            System.out.println(engine.getClass().getCanonicalName());
            engine.eval("var i=1+1;");
            System.out.println("i = " + engine.get("i"));
        } catch (ScriptException ex) {
            ex.printStackTrace();
        }
    }
}

In Java 0 is not always equal to 0

22. März 2015 Posted by Ralf Petter

I am a big fan, of the Java BigDecimal class for arithmetic operations. It provides fast and easy to use methods to do calculations in arbitrary precision. But last week i have got a bug report that in one of my programs a check whether a BigDecimal variable is 0 returns a wrong result. Here is the code:


if(value.equals(BigDecimal.ZERO))
System.out.println("Value is zero");
else
System.out.println("Value is not zero");


The check for a zero value has always worked, but suddenly last week it stopped working and after some investigation i have found out, that it stops working, because the scale of the value has changed from zero, to two. When you have a look in the java doc of the equals method you see, that this works as designed.

This method considers two BigDecimal objects equal only if they are equal in value and scale (thus 2.0 is not equal to 2.00 when compared by this method.

As a workaround for this problem you can replace all your equals method to compareTo which only compares the value and not the scale of the BigDecimal object. 

if(value.compareTo(BigDecimal.ZERO)==0)
System.out.println("Value is zero");
else
System.out.println("Value is not zero");

HTML aus RichText extrahieren

20. März 2015 Posted by Henning Schmidt

Da ich gerade vor dem Problem saß, aus einem nicht als MIME gespeicherten RichText Feld HTML Content zu extrahieren und ein wenig gebraucht hatte, bis ich alles beisammen hatte, möchte ich meine Lösung teilen, damit Ihr ggf. weniger lange suchen müsst. Die Anforderung war, aus einem als Notes RT gespeichertem Feld den Inhalt als anständig […]

XPages: Empty HTML5 Attibutes & PassThroughTags

10. März 2015 Posted by Sven Hasselbach

A while ago I developed some HTML5 XPages applications, but the development process was a little bit frustrating because of the missing possibility to add empty attributes to a PassThroughTag.  A single empty attribute is not allowed, because this would result in invalid XML, and you cannot use “xp:attributes” with “UIPassThroughTag” components.

A simple example like this…

<input type="text" value="John Doe" disabled />

… always ended up in something like this:

<xp:text tagName="input" disableTheme="true">
   <xp:this.attrs>
      <xp:attr name="disabled" minimized="true" value="disabled" />
      <xp:attr name="value" value="John Doe" />
   </xp:this.attrs>
</xp:text>

To fit my requirements, I had extended the “UIPassThroughTag” with a special attribute named “emptyAttrs“. This allowed me to write the example above in the following syntax:

<input type="text" value="John Doe" emptyAttrs="disabled" />

(Multiple empty attributes can be added comma separated.)

Here is the Java class:

package ch.hasselba.xpages;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.List;

import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.context.ResponseWriter;

import com.ibm.xsp.component.UIPassThroughTag;
import com.ibm.xsp.renderkit.html_basic.PassThroughTagRenderer;
import com.ibm.xsp.webapp.XspHttpServletResponse;

public class PassThroughTagRendererEx extends PassThroughTagRenderer {

    private final static String EMPTY_ATTRIBUTE_NAME = "emptyAttrs";
    
    public void encodeBegin(FacesContext fc, UIComponent uiComponent)
            throws IOException {

        // Component is rendered?
        if (uiComponent.isRendered() == false) {
            return;
        }
        
        // only process instances of UIPassThroughTags
        if ((uiComponent instanceof UIPassThroughTag)) {
            
            UIPassThroughTag uiPTTag = (UIPassThroughTag) uiComponent;
            
            ResponseWriter rw = fc.getResponseWriter();
            
            // grab the printer writer directly from the response
            XspHttpServletResponse response = (XspHttpServletResponse) 
                fc.getExternalContext().getResponse();

            PrintWriter rwPrinter = response.getWriter();
            
            // start the element tag
            rw.startElement(uiPTTag.getTag(), uiComponent);
            
            // process all attributes
            List<UIPassThroughTag.TagAttribute> attrList = uiPTTag
                    .getTagAttributes();
            
            if (attrList != null) {

                UIPassThroughTag.TagAttribute tagAttribute = null;
                String attrName = null;
                String attrValue = null;

                for (int i = 0; i < attrList.size(); i++) {
                    tagAttribute = attrList.get(i);
                    attrName = tagAttribute.getName();
                    attrValue = tagAttribute.getValue();
                    if (EMPTY_ATTRIBUTE_NAME.equalsIgnoreCase(attrName)) {
                        
                        // process all empty tags
                        String tags[] = attrValue.split(",");
                        for( int j=0; j<tags.length; j++){
                            // write the attribute name only
                            rwPrinter.write( " " );
                            rwPrinter.write( tags[j].trim() );
                        }
                    }else{
                            // write the attribute data
                            rw.writeAttribute(attrName, attrValue, null);
                    }
                }
                
            }
        } else {
            // process other instances "as always"
            super.encodeBegin(fc, uiComponent);
        }
    }

}

To activate the class you have to overwrite the renderer in the “faces-config.xml“:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
  <render-kit>
    <renderer>
      <component-family>javax.faces.Output</component-family>
      <renderer-type>com.ibm.xsp.PassThroughTagRenderer</renderer-type>
      <renderer-class>ch.hasselba.xpages.PassThroughTagRendererEx</renderer-class>
    </renderer>
  </render-kit>
</faces-config>

EntwicklerCamp 2015: XPages – WebServices und REST

4. März 2015 Posted by Bernd Hort

EntwicklerCamp

Der letzte Tag des EntwicklerCamps ist gekommen. Heute war mein zweiter Vortrag zum Thema "XPages - WebServices und REST". In 90 Minuten habe ich ausführlich beschrieben, wie Webservices und RESTful Webservices innerhalb von XPages konsumiert werden können. Die erste Hälfte des Vortrages konzentrierte sich auf das Backend mittels Java. In der zweiten Hälfte habe ich den Blick auf JavaScript im Browser gerichtet.

Wie immer können die Folien und die Beispiel-Anwendung heruntergeladen werden. Basis für diesen Vortrag war mein Vortrag auf der IBM ConnectED 2015: BP 108 - Be Open - Use Web Services and REST in XPages Applications. Insofern ist die Beispiel-Anwendung die gleiche wie beim IBM ConnectED-Vortrag.

 

In der Beispiel-Anwendung befinden sich nicht nur XPages, um die Webservices und RESTful Webservices zu konsumieren. Damit die Beispiele auch bei Ihnen ohne Probleme laufen, sind ein Webservice und drei RESTful Webservices implementiert.

EntwicklerCamp 2015: Java Managed Beans

3. März 2015 Posted by Bernd Hort

EntwicklerCamp

Der zweite Tag des EntwicklerCamps nähert sich dem Ende. Heute war mein Vortrag "Java Managed Beans". Nach einer kurzen Einführung in Java Managed Beans haben wir uns die Verwendung in XPages angesehen. Wobei gerade auch das Verständnis für die internen Abläufe in der XPages-Laufzeitumgebung ein wichtiger Aspekt war. Am Schluß wurden die Einsatzmöglichkeiten diskutiert.

Wie immer können die Folien und die Beispiel-Anwendung heruntergeladen werden.


Rest & Security: More about the DominoStatelessTokenServlet

25. Februar 2015 Posted by Sven Hasselbach

During the last days I have refined the DominoStatelessTokenServlet a little bit. It is now a pre-beta release, and I think it is time to explain some details about it. While it is still a proof-of-concept, it demonstrates how a stateless authentication can easily be implemented. A lot of testing is still required until it is ready for production use, but I think it provides really cool things for the domino environment.

First, it fixes the problematic 200er HTTP response code when an authentication was not successfull. Then it introduces a higher security level for web based applications, because the authentication token is only transferred in the HTTP headers: A CSRF attack as shown here is not possible anymore. The authentication works accross multiple servers / clusters, which can become interesting for example when you want to share the authentication securely between a Bluemix hosted application and your companies hosted infrastructure; the token is created from a server running in your company, and is then used to identify a user in the cloud-based application (It’s a real authentication, not a “misused authorization” like OAuth).

The token can also be safely stored in an mobile app: When the device gets lost, the user credentials are not compromised. And it allows to create different tokens for different applications for the same user (currently not implemented).

As a short demonstration, I have added a Angular JS example (with a hardcoded token) to  show how it works in practise: An AJAX request is sent to the servlet, and the JSON response contains the name of the current Domino user.

The HTML page is not really complicated, it will just show the returned username:

<!doctype html>
<html ng-app>
   <head>
      <title>Hello AngularJS</title>
      <script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
      <script src="hello.js"></script>
   </head>
   <body>
      <div ng-controller="Hello">
         <p>Hello {{greeting.username}}!</p>
      </div>
   </body>
</html>

The “Hello” controller which performs the AJAX request adds the “X-AUTH-TOKEN” to the HTTP headers of the request:

function Hello($scope, $http) {

   // add a valid token to the headers
   $http.defaults.headers.common['X-AUTH-TOKEN'] = 
     'MTQyNDI2OTE0NDgxNCFUVGVzdFVzZXIwMQ==!HyC1mnvvdaneLaW0Wn48kZ1MaTrdowr1e4nWBRWRX8Y=';

   // load the data
   $http.get('http://localhost/token/validate/').
      success(function(data) {
         $scope.greeting = data;
      });

}

And this is the result:

StatelessToken 01 - Validate Token

The Token consist of two parts: The data part, and the Hmac hash of the data. Both are Base64 encoded, and when the data part is decoded, you can currently see the username and the timestamp of the token generation:

  • Encoded
MTQyNDI2OTE0NDgxNCFUVGVzdFVzZXIwMQ==
  • Decoded
1424269144814!TTestUser01

Because the data are hashed it is not possible to modify them. The timestamp is validated from the servlet; as soon it is too old, it is not longer valid.

To create a token, the servlet must be currently opened with the username and the password as URL parameters:

http://localhost/token/create/?username=TTestUser01&password=test01

In the response, the newly generated token is added to the HTTP headers:

StatelessToken 02 - Create Token

In the servlet configuration (web.xml), a backend user is defined on whose behalf a lookup to the NAB is made to verify the provided HTTP password.  The password for the hash is also stored there, and the maximum age of the token.

Rest & Security: A Stateless Token Servlet

10. Februar 2015 Posted by Sven Hasselbach

I have uploaded some of my projects to GitHub, including an alpha version of a stateless token servlet.

The servlet has it’s own authentication mechanism (the password is currently not validated), and for developing purposes it uses HTTP GET. In a future release, the token will be transfered as a HTTP header. Additionally, the HTTP method will be changed to POST. Last but not least must the code be optimized. For example there is no recycling implemented at this moment, and there is a dubious bug in the token validation (which was solved by encoding it to Base64 and back again).

  • Generate a Token

To generate a new token, you have to open the servlet with the following URL format:

"/create?username=<CANONICAL USER>&password=<WHATEVER>"

Example for “CN=Sven Hasselbach/OU=Hasselba/O=CH

http://example.com/token/create/?username=CN%3DSven%20Hasselbach%2FOU%3DHasselba%2FO%3DDE&password=XXX

This will return a JSON string containg the newly generated token:

{token: 'MDkgRmViIDIwMTUgMjI6NDk6MTIuMzU2IUNOPVN2ZW4gSGFzc2VsYmFjaC9PVT1IYXNzZWxiYS9PPURF!9s7dSS7F67hSS/lLODZHVM0NsTR4IurkZtjiysGzoWA='}

The Token is a Base64-encoded String containing a Timestamp, the Username and a Hmac Hash and only valid for one hour.

  • Validate a Token

To validate the token, you can use the URL

/validate/?token=<token>

Example:

http://example.com/token/validate/?token=MDkgRmViIDIwMTUgMjI6NDk6MTIuMzU2IUNOPVN2ZW4gSGFzc2VsYmFjaC9PVT1IYXNzZWxiYS9PPURF%219s7dSS7F67hSS%2FlLODZHVM0NsTR4IurkZtjiysGzoWA%3D

If the token is valid, a new “NotesSession” is created for the given user. The server response contains a JSON String with the current “EffectiveUserName”:

{user: 'CN=Sven Hasselbach/OU=Hasselba/O=CH'}

Because the servlet creates its own session, you can be logged in as a different user: The Domino authentication is not affected by the servlet.

Updates für die JVM in Notes und Domino

18. Dezember 2014 Posted by Oliver Regelmann

Der JVM-Patch “SR16FP2” behebt zahlreiche Sicherheitslücken in der JVM, die von Oracle veröffentlicht wurden. Den Patch gibt es außer der Reihe für Notes und Domino 9.0.1 FP2 sowie 8.5.3 FP6.

 

Details und Verweise auf die einzelnen Probleme in einem Security Bulletin.

XPages: WebContent Files (3) – Create a Minimizer Servlet

26. November 2014 Posted by Sven Hasselbach

XPages: WebContent Files (2) – Manipulate exitsting files using the Java NAPI

19. November 2014 Posted by Sven Hasselbach

In this article, I will shortly give an overview how you can edit existing file from the WebContent folder (Don’t miss the first article on this topic).

First, let’s create a view to display the design elements of the WebContent folder. To do this, I have an old school LotusScript Agent which updates the selection formula of a view (Some details about this technique can be found here).

Sub Initialize

    Dim session As New NotesSession
    Dim doc As NotesDocument
    Dim db As NotesDatabase
    Dim view As NotesView
      
    Set db = session.Currentdatabase
    Set view = db.Getview("DesignView")
    
    view.SelectionFormula = |@Contains($FlagsExt; "w")|
    
    Set doc = db.GetDocumentByUNID(view.UniversalID)
    Delete view
    
    doc.ReplaceItemValue "$FormulaClass", "7FFF"
    doc.Sign
    doc.Save True, False

End Sub

The agent has to run once to change the view’s selection criteria. In this example the view has the name “DesignView”. After that, we can add a single column to the view to display the files and their names:

Now lets build a simple XPage named Files.xsp to select the file you want to edit:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    <xp:viewPanel
        rows="30"
        id="viewPanel1"
        var="rowEntry">
        <xp:this.facets>
            <xp:pager
                partialRefresh="true"
                layout="Previous Group Next"
                xp:key="headerPager"
                id="pager1">
            </xp:pager>
        </xp:this.facets>
        <xp:this.data>
            <xp:dominoView
                var="viewDesign"
                viewName="DesignView">
            </xp:dominoView>
        </xp:this.data>
        <xp:viewColumn
            columnName="$FileNames"
            id="viewColumnFileNames"
            displayAs="link"
            >
            <xp:this.pageUrl>
                <![CDATA[#{javascript:"/Editor.xsp?filename=" +
                   rowEntry.getColumnValues().get(0)}]]>
        </xp:this.pageUrl>
        </xp:viewColumn>
    </xp:viewPanel>
    <xp:br />

</xp:view>

The Files.xsp lists all files as links and opens them via the Editor.xsp XPage:

This is the Editor.xsp:

<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">

    FileName:
    <xp:inputText
        id="inputTextFileName"
        value="#{fileBean.fileName}"
        defaultValue="#{param.filename}"
        disabled="true" />
    <xp:br />
    FileData:

    <xp:inputTextarea
        id="inputTextarea1"
        value="#{fileBean.fileData}"
        rows="40"
        cols="80" />
    <xp:br />
    <xp:button
        value="Load"
        id="buttonLoad">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="complete"
            action="#{fileBean.loadData}">
        </xp:eventHandler>
    </xp:button>
    <xp:button
        value="Save"
        id="buttonSave">
        <xp:eventHandler
            event="onclick"
            submit="true"
            refreshMode="complete"
            action="#{fileBean.saveData}">
        </xp:eventHandler>
    </xp:button>
    
</xp:view>

It uses a simple managed bean…

package ch.hasselba.napi;

import java.io.Serializable;

public class FileDataBean implements Serializable {

    private static final long serialVersionUID = 1L;
    private String fileName;
    private String fileData;
    private String dbPath;
    private String dbServer;

    public String getDbPath() {
        return dbPath;
    }

    public void setDbPath(String dbPath) {
        this.dbPath = dbPath;
    }

    public String getDbServer() {
        return dbServer;
    }

    public void setDbServer(String dbServer) {
        this.dbServer = dbServer;
    }

    public void setFileData(String fileData) {
        this.fileData = fileData;
    }

    public String getFileData() {
        return fileData;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }

    public String getFileName() {
        return fileName;
    }

    public void loadData() {
        this.fileData = NAPIUtils.loadFile(this.dbServer, this.dbPath, this.fileName);
    }

    public void saveData() {
        NAPIUtils.saveFile(this.dbServer, this.dbPath, this.fileName, this.fileData);
    }
}

… which is initialized with the properties defined in the faces-config.xml. There you can find the database server and the database path:

<?xml version="1.0" encoding="UTF-8"?>
<faces-config>
  <managed-bean>
    <managed-bean-name>fileBean</managed-bean-name>
    <managed-bean-class>ch.hasselba.napi.FileDataBean</managed-bean-class>
    <managed-bean-scope>view</managed-bean-scope>
    <managed-property>
      <property-name>dbPath</property-name>
      <value>NAPI.nsf</value>
    </managed-property>
    <managed-property>
      <property-name>dbServer</property-name>
      <value>DEV01/Hasselba/CH</value>
    </managed-property>
  </managed-bean>
</faces-config>

And last but not least, the required NAPIUtil class:

package ch.hasselba.napi;

import java.io.InputStream;
import com.ibm.designer.domino.napi.NotesAPIException;
import com.ibm.designer.domino.napi.NotesDatabase;
import com.ibm.designer.domino.napi.NotesNote;
import com.ibm.designer.domino.napi.NotesObject;
import com.ibm.designer.domino.napi.NotesSession;
import com.ibm.designer.domino.napi.design.FileAccess;

public class NAPIUtils {

    /**
     * loads a given WebContent file and returns the result as String
     * 
     * @param serverName
     *            the server to use
     * @param dbPath
     *            the database path
     * @param fileName
     *            the file to load
     * @return the file data as String
     */
    static public String loadFile(final String serverName, final String dbPath,
            final String fileName) {

        NotesSession nSession = null;
        NotesDatabase nDatabase = null;
        NotesNote nNote = null;

        try {
            nSession = new NotesSession();

            // open database
            nDatabase = nSession.getDatabaseByPath(serverName + "!!" + dbPath);
            nDatabase.open();

            // load existing data
            nNote = FileAccess.getFileByPath(nDatabase, fileName);

            // get Filedate and return String
            InputStream is = FileAccess.readFileContentAsInputStream(nNote);

            return convertStreamToString(is);
        } catch (NotesAPIException e) {
            e.printStackTrace();
        } finally {
            // recycle NAPI objects
            recycleNAPIObject(nNote, nDatabase, nSession);
        }

        return fileName;
    }

    /**
     * loads a given WebContent file and returns the result as String
     * 
     * @param serverName
     *            the server to use
     * @param dbPath
     *            the database path
     * @param fileName
     *            the file to load
     * @param fileData
     *            the data of the file
     */
    static public void saveFile(final String serverName, final String dbPath,
            final String fileName, final String fileData) {

        NotesSession nSession = null;
        NotesDatabase nDatabase = null;
        NotesNote nNote = null;

        try {
            nSession = new NotesSession();

            // open database
            nDatabase = nSession.getDatabaseByPath(serverName + "!!" + dbPath);
            nDatabase.open();

            // load existing data
            nNote = FileAccess.getFileByPath(nDatabase, fileName);

            // store them to note
            FileAccess.saveData(nNote, fileName, fileData.getBytes());

        } catch (NotesAPIException e) {
            e.printStackTrace();
        } finally {
            // recycle NAPI objects
            recycleNAPIObject(nNote, nDatabase, nSession);
        }
    }

    /**
     * converts an input stream to a string
     * 
     * @param is
     *            the input stream to convert
     * @return String
     */
    static String convertStreamToString(java.io.InputStream is) {
        java.util.Scanner s = new java.util.Scanner(is).useDelimiter("\A");
        return s.hasNext() ? s.next() : "";
    }

    /**
     * recycleNAPIObject helper method for recycling NAPI objects
     * 
     * @param nObjects
     *            the NAPI objects to recycle
     */
    static void recycleNAPIObject(NotesObject... nObjects) {
        for (NotesObject nObject : nObjects) {
            if (nObject != null) {
                try {
                    (nObject).recycle();
                } catch (NotesAPIException ne) {
                }
            }
        }
    }
}

If the class looks like this…

…just hover one  of the entries and select “Fix project setup”.

Then, you can choose the missed bundle:

Build the project, and open one of the files by clicking a link in the Files.xsp. Here is an example for the file WEB-INF/faces-config.xml:

Now you can click the “Load” button to read the content of the file.

You can edit the file now and save it to the NSF.

If you got to package explorer (Hit F9 for refresh) you can see the changes: