The XPages EL Directory

I am currently working on an overview of available objects and properties for XPages Expression Language. A first incomplete and horrible designed version can be found here.

 

Veröffentlicht unter Expression Language, XPages | Verschlagwortet mit , , | 2 Kommentare

XPages: SSJS, EL and Bindings

Because of reasons you should already know I avoid the use of SSJS in my XPages applications, but there are still some parts which can be easy realized in SSJS, but with EL only with a lot of effort. One of this things is accessing properties of a component which has only a getter or a setter – this will not work when using a binding.

Let’s look for example at a repeat control which is bound to the variable repeat. It can be easily accessed everywhere in SSJS, EL or Java, and it is much faster than searching the component in the tree. Here it is accessed in a label:

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

    <xp:label
        value="#{repeat.rowCount}"
        id="labelCount">
    </xp:label>
    
    <xp:repeat
        id="repeatDemo"
        rows="30"
        binding="#{repeat}">
        <xp:this.value><![CDATA[#{javascript:[1,2,3,4,5]}]]></xp:this.value>
    </xp:repeat>
    
</xp:view>

But this will lead into an error, because the XspDataIterator component has only a getter method for the rowCount property, not a corresponding setter.

2015-07-01 23_11_16-Runtime Error

When changing the code to a SSJS method call, it works without any problems:

    <xp:label
        value="#{javascript:repeat.getRowCount()}"
        id="labelCount">
    </xp:label>

The reason for this behaviour can be found deep down in the underlying JSF classes: The BeanInfoManager ignores all methods without a „valid“ getter/setter methods pair during the inspection of a class, and doesn’t add it to the available properties for the PropertyResover. The rows property for example has a pair of those methods, that’s why this works without any problem:

<xp:label
    value="#{repeat.rows}"
    id="labelCount">
</xp:label>

 

Veröffentlicht unter Allgemein, JSF, XPages | Verschlagwortet mit , , , | Hinterlasse einen Kommentar

XPages: An optimized JavaScript Resource Renderer

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.

Veröffentlicht unter Java, JSF, XPages | Verschlagwortet mit , , , , | 8 Kommentare

xsp.application.context.proxy

Just a reminder for myself: To use a CDN for XPage resources, you can add a leading slash to the xsp.application.context.proxy property.

xsp.application.context.proxy=/cdn.hasselba.ch

2015-06-17 10_52_08-view-source

 

Veröffentlicht unter Performance, Web, XPages | Verschlagwortet mit , , , , | 2 Kommentare

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

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();
        }
    }
}
Veröffentlicht unter ServerSide JavaScript, XPages | Verschlagwortet mit , , , , , | 2 Kommentare

XPages: Empty HTML5 Attibutes & PassThroughTags

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>
Veröffentlicht unter HTML5, XPages | Verschlagwortet mit , , , | 2 Kommentare

REST & Security: Why HTTP GET is insecure (and the other methods too)

Yesterday René commented that submitting username and password with HTTP GET is insecure, because they are submitted in clear text over the wire as part of the URI.

At the first moment, I did not give some thought about it, because it is known fact that data added to an URI are less secure. They are added to the browser history, are logged in the requests on servers, and every proxy between the user’s browser and the requested server are seeing (and maybe logging) these URI’s too. That’s why HTTP GET is problematic: The credentials are accidentially spread over the whole world, like any other information which are appended to an URI.

In René’s example, he pointed out that a hotspot provider of a public WiFi can easily grab the URIs (and the credentials), and that’s the reason why HTTP GET is insecure. But this is not the whole truth: As soon HTTP data are going over the wire, the data can be fetched from anyone in the chain of the transport, and it doesn’t matter which HTTP method is used.

Some clever guys are running TOR exit nodes and grab the whole traffic through a transparent proxy, and because they have access to the whole traffic, they are able to fetch credentials from these requests. As long as the data are unprotected.

To protect your data, you have two choices: Use HTTPS connections or encrypt the data by yourself (f.e. with JWE). Manually encrypting won’t solve the problem of logging, but it can help to secure the data. But the preferred way is first one, because it solves all the problems of insecure data during their transport, and is really easy to implement. TLS / SSL is a layer between the TCP layer and the HTTP layer, and the whole traffic (the HTTP data) is encrypted. This includes the URI, only the target host is known to everyone involved of the transport

You shouldn’t use HTTP GET to transfer sensitive informations. The browser history is problematic, and the logs on the target server would contain the requested URIs. When developing a RESTful API you must bear this in mind.

Veröffentlicht unter REST, Security, Server, Web | Verschlagwortet mit , , , | 2 Kommentare

REST & Security: More about the DominoStatelessTokenServlet

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.

Veröffentlicht unter Allgemein, Java, REST, Security, Web | Verschlagwortet mit , , , , , , , , | 12 Kommentare

REST & Security: A Stateless Token Servlet

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.

Veröffentlicht unter Java, REST, Security, Web | Verschlagwortet mit , , , , | Hinterlasse einen Kommentar

IBM Bluemix: Filemode-Problems with GitHub

When creating your own build pack for IBM Bluemix applications (or other Cloud Foundry based solutions), it is required to set the correct file mode for the executables before initially pushing them to GitHub. Otherwise the compilation will fail, and it seems to be a known bug for GitHub based repositories that the mode cannot be changed later.

The command for this is

git update-index --chmod=+x <path-to-file>

You can see the result when you commit the files to your respository:

> git commit -m "first commit"

[master (root-commit) 680979a] first commit
 4 files changed, 34 insertions(+)
 create mode 100644 README.md
 create mode 100755 bin/compile
 create mode 100755 bin/detect
 create mode 100755 bin/release

[The three files in the folder „/bin“ have the correct mode 755 instead 644]

You can find an example build pack here https://github.com/hasselbach/cf-buildpack-basic.git.

Veröffentlicht unter IBM Bluemix | Verschlagwortet mit , , | Hinterlasse einen Kommentar