While you can use the JavaScripting API JSR223, I choosed the jav8 project for a test how this can be realized. So I downloaded the Windows binaries to get the required DLL and imported it into a new database. I also imported the source files of the lu.fler.script package to recompile all required classes.
Then, I registered the factory service by creating a javax.script.ScriptEngineFactory file in the /META-INF/services folder and added the line lu.flier.script.V8ScriptEngineFactory.
Image may be NSFW. Clik here to view.
The package explorer looked like this:
Image may be NSFW. Clik here to view.
To prevent collisions, I commented out some names in the V8ScriptEngineFactory class:
Image may be NSFW. Clik here to view.
For a simple test, I decided to invoke the engine manually when clicking on a button on a XPage. To do this, I created a simple ActionListener in Java which loads the JavaScript Engine and evals a simple ” var i = 1+1″. The Javascript variable is then accessed and printed out to the server console.
When the button is clicked, the V8 engine works as it should: Image may be NSFW. Clik here to view.
But now comes a hard problems: It works only once! After doing this, my test server crashes completly. During playing with it, I was able to run it as it should, but I have no idea anymore how I did it. I think it is the DLL and static classes, but I am currently to busy to investigate the problem further. The @Override notations added to the methods (which must removed before the code can be compiled) do not override exitisting ones (I checked the bundled javax.script JAR of the binary package), this does not seem to be the problem. Maybe someone else has an idea?
The first example demonstrates the creation of a file using a Java Agent. Before you can compile the code, you have to import the required jars as described here.
import lotus.domino.AgentBase;
import com.ibm.designer.domino.napi.NotesConstants;
import com.ibm.designer.domino.napi.NotesDatabase;
import com.ibm.designer.domino.napi.NotesNote;
import com.ibm.designer.domino.napi.NotesSession;
import com.ibm.designer.domino.napi.design.FileAccess;
import com.ibm.jvm.util.ByteArrayOutputStream;
public class JavaAgent extends AgentBase {
public void NotesMain() {
try {
final String serverName = "Dev01/Hasselba/CH";
final String dbPath = "NAPI.nsf";
final String fileName = "dummy.txt";
NotesSession nSession = new NotesSession();
NotesDatabase nDatabase = nSession.getDatabaseByPath(serverName
+ "!!" + dbPath);
nDatabase.open();
// create a new note
NotesNote nNote = nDatabase.createNote();
// define $Flags item
StringBuffer flags = new StringBuffer();
flags.append(NotesConstants.DESIGN_FLAG_HIDEFROMDESIGNLIST);
flags.append(NotesConstants.DESIGN_FLAG_NO_COMPOSE);
flags.append(NotesConstants.DESIGN_FLAG_HIDE_FROM_V4);
flags.append(NotesConstants.DESIGN_FLAG_FILE);
// define $FlagsExt item
StringBuffer extFlags = new StringBuffer();
extFlags.append(NotesConstants.DESIGN_FLAGEXT_WEBCONTENTFILE);
// init the file with the flags
nNote.initAsFile(flags.toString(), extFlags.toString());
// add required fields
nNote.setItemText("$TITLE", fileName);
nNote.setItemText("$MimeType", "text/plain");
// generate some random data
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for (int i = 0; i < 128000; i++) {
int hlp = (i % 24) + 60;
bos.write(hlp);
}
// store the data to the Note
FileAccess.saveData(nNote, fileName, bos.toByteArray());
// recycle the NAPI objects
nNote.recycle();
nDatabase.recycle();
nSession.recycle();
} catch (Exception e) {
e.printStackTrace();
}
}
}
As you can see, when using the NAPI you don’t have to save a note. It is directly written to the NSF.
After running the agent and refreshing the project explorer view (hit F9) the newly created file appears in the WebContent folder…
Image may be NSFW. Clik here to view.
… and is accessible in the browser:
Image may be NSFW. Clik here to view.
If you want to allow public access to your element, you have to add a single line in the code above:
nNote.setItemText("$PublicAccess", "1");
This allows unauthenticated users to access the file while the rest of the application is protected:
Image may be NSFW. Clik here to view.
On the other hand, you can use reader fields enhance the security of the file. But this can not be done with the note object directly, you have to use the standard Domino document classes for this. To access the document, you have to convert the note’s note id (an integer value) into his hexadecimal representation:
int noteId = nNote.getNoteId();
Document doc = db.getDocumentByID(Integer.toHexString(noteId));
Then, you can add the reader field to the document and save the result.
Session session = getAgentSession();
Database db = session.getCurrentDatabase();
Document doc = db.getDocumentByID(Integer.toHexString(noteId));
// create the field
Item item = doc.replaceItemValue("DocReaders", "[ReadAll]");
item.setReaders(true);
// save the document
doc.save();
// recycle the instances
item.recycle();
doc.recycle();
db.recycle();
session.recycle();
Now, the file is protected when opening in the browser:
Image may be NSFW. Clik here to view.
Keep in mind: As the developer it is required to have the access rights for the file too.
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:
Image may be NSFW. Clik here to view.
Now lets build a simple XPage named Files.xsp to select the file you want to edit:
Because of Stefano Fois comment I decided to write an example about how to create a minimizer servlet for Domino which compresses JavaScript resources on the fly. This is, again, a simple Proof-Of-Concept, nothing more and nothing less.
First, I downloaded the YUICompressor, a Java based minimizer for JavaScript code from the project page. There are other compressors outside, I decided to use this one because it was the first result in my StartPage.com search.
The project is a single jar file and can be easily imported into an existing Domino database, in my case to my demonstration NAPI.nsf.
Image may be NSFW. Clik here to view.
The next step is to create a servlet and a servlet factory, the basics are described here. To enable the servlet, an additional service file is required.
Image may be NSFW. Clik here to view.
The file com.ibm.xsp-adapter.servletFactory contains the name of the factory class:
ch.hasselba.jsf.servlet.MinimizerServletFactory
The servlet factory is simple, it just defines the servlet class to use and the path which the servlet listens to requests:
package ch.hasselba.jsf.servlet;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import com.ibm.designer.runtime.domino.adapter.ComponentModule;
import com.ibm.designer.runtime.domino.adapter.IServletFactory;
import com.ibm.designer.runtime.domino.adapter.ServletMatch;
public class MinimizerServletFactory implements IServletFactory {
private static final String SERVLET_WIDGET_CLASS = "ch.hasselba.jsf.servlet.MinimizerServlet";
private static final String SERVLET_WIDGET_NAME = "JS Minimizer";
private ComponentModule module;
public void init(ComponentModule module) {
this.module = module;
}
public ServletMatch getServletMatch(String contextPath, String path)
throws ServletException {
String servletPath = "";
if (path.contains("/minimizer")) {
String pathInfo = path;
return new ServletMatch(getWidgetServlet(), servletPath, pathInfo);
}
return null;
}
public Servlet getWidgetServlet() throws ServletException {
return module.createServlet(SERVLET_WIDGET_CLASS, SERVLET_WIDGET_NAME, null);
}
}
The servlet is now reachable by the following URL
http://example.com/path/to/db.nsf/xsp/minimizer/
How does the minimizer servlet work? It appends all required files into a single string and compresses the string before sending the result to the browser. So the first information needed is which files should be used. This can be done with the parameter “files“, the list of files is concatenated with “+“:
The given files are then loaded via NAPI into a large StringBuffer and then compressed and mimimized by YUI and GZIP.
package ch.hasselba.jsf.servlet;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.io.Serializable;
import java.io.StringWriter;
import java.io.Writer;
import java.util.zip.GZIPOutputStream;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.mozilla.javascript.ErrorReporter;
import org.mozilla.javascript.EvaluatorException;
import ch.hasselba.napi.NAPIUtils;
import com.ibm.xsp.webapp.DesignerFacesServlet;
import com.yahoo.platform.yui.compressor.JavaScriptCompressor;
public class MinimizerServlet extends DesignerFacesServlet implements
Serializable {
private static final long serialVersionUID = -1L;
@Override
public void service(ServletRequest servletRequest,
ServletResponse servletResponse) throws ServletException,
IOException {
HttpServletRequest req = (HttpServletRequest) servletRequest;
HttpServletResponse res = (HttpServletResponse) servletResponse;
ServletOutputStream out = servletResponse.getOutputStream();
try {
res.setContentType("application/x-javascript");
res.setCharacterEncoding("utf-8");
res.addHeader("Content-Encoding", "gzip");
// load the js requested files
StringBuffer fileData = new StringBuffer();
String tmpFile = "";
String paramFiles = req.getParameter("files");
String[] files = paramFiles.split(" ");
// and add them to a buffer
for (String file : files) {
try {
tmpFile = NAPIUtils.loadFile("DEV01", "NAPI.nsf", file);
fileData.append(tmpFile);
} catch (Exception e) {
// ignore errors
e.printStackTrace();
}
}
// Compress the JS Code with compressor
StringWriter sWriter = new StringWriter();
compress(stringBufferToInputStreamReader(fileData), sWriter);
// and GZIP it
ByteArrayOutputStream obj = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(obj);
gzip.write(sWriter.toString().getBytes("UTF-8"));
gzip.close();
// send it to the client
out.write(obj.toByteArray());
} catch (Exception e) {
e.printStackTrace(new PrintStream(out));
} finally {
out.close();
}
}
/**
* Helper to convert a StringBuffer to an InputStreamReader
*
* @param strBuffer
* the StringBuffer to convert
* @return the converted InputStreamReader
*/
public static InputStreamReader stringBufferToInputStreamReader(
final StringBuffer strBuffer) {
return new InputStreamReader(new ByteArrayInputStream(strBuffer
.toString().getBytes()));
}
/**
* compresses the JS code using YUI
*
* @param in
* the InputStreamReader containing the JS
* @param out
* the Writer Object
* @throws EvaluatorException
* @throws IOException
*/
public static void compress(final InputStreamReader in, Writer out)
throws EvaluatorException, IOException {
JavaScriptCompressor compressor = new JavaScriptCompressor(in,
new ErrorReporter() {
public void warning(String message, String sourceName,
int line, String lineSource, int lineOffset) {
System.out.println("\n[WARNING]");
if (line < 0) {
System.out.println(" " + message);
} else {
System.out.println(" " + line + ':' + lineOffset
+ ':' + message);
}
}
public void error(String message, String sourceName,
int line, String lineSource, int lineOffset) {
System.out.println("[ERROR] ");
if (line < 0) {
System.out.println(" " + message);
} else {
System.out.println(" " + line + ':' + lineOffset
+ ':' + message);
}
}
public EvaluatorException runtimeError(String message,
String sourceName, int line, String lineSource,
int lineOffset) {
error(message, sourceName, line, lineSource, lineOffset);
return new EvaluatorException(message);
}
});
// call YUI
compressor.compress(out, 0, true, false, false, false);
}
}
For testing purposes I imported an uncompressed version of jQuery and created a file named helloWorld.js. The helloWorld.js contains a single function only, just to test and verify the outcome of the servlet.
Image may be NSFW. Clik here to view.
Then, I created a test.html, a simple HTML page which loads the mimimized JavaScript files:
In this post I will demonstrate how a do a CSRF attack against a XPages REST service.
Let’s assume that we have a custom REST service on a XPage. To keep the example as simple as possible, this service returns the posted data back to the requesting browser only, nothing more and nothing less:
I had a strange issue with TbGridView‘s (YiiStrap‘s version of CGridView) selectionChanged event: In all browsers, the defined function was executed when a row was selected, but not on devices with iOS 7 & 8.
While trying to hack around the issue, I found a simple solution by adding
'htmlOptions' => array( 'onclick' => '' )
to the declaration of the grid. This little hack kills the inherited event handler from the body element by adding an empty onclick event to the generated <div>:
<div class="grid-view" id="yw0" onclick="">
Now everything works accross all browsers and devices.
After re-enabling the insecure default configuration by commenting out the both lines in the configuration allowed me to reconnect to the server again.
Here is a list of usefull HTTP headers for responses you should know about:
X-Content-Type-Options
When set to “nosniff“, this header will prevent browsers from MIME-sniffing a response away from the declared content-type. While this header is more relevant for “normal” web applications (it protects against some types of drive-by-downloads), it does not hurt to add it to your REST service, if
allow-from: <DOMAIN> allow rendering if framed by frame loaded from DOMAIN
X-Frame-Options: deny
X-XSS-Protection
Re-enables cross side scripting protection in IE and Chrome if user has disabled it.
X-XSS-Protection: 1; mode=block
Strict-Transport-Security
Enables HTTP Strict Transport Security (HSTS). This prevents browsers from using an insecure connection to a server for a given time (in seconds). Additionally, you can include all subdomains:
Es ist schon faszinierend, wenn mans sich den Wandel in der Lotus Notes Welt näher vor Augen führt: Wäre man für das Einreichen von Themen ausserhalb der Domino-Welt vor ein paar Jahren noch geteert und gefedert worden, stehen dieses Jahr auf der Agenda vom Entwicklercamp 2015 nicht nur vereinzelte Session, die sich mit Migration befassen, sondern es widmet sich dem Thema praktisch gleich ein ganzer Track.
Zwischenzeitlich (2013/2014) haben sich einige führende Technologie-Köpfe der IBM aus dem Domino-Umfeld verabschiedet, und die ConnectED (nur noch halb so lang wie bisher) wird wohl nach 2015 die Pforten schliessen. Die verbliebenen Business-Partner pushen derweil andere Technologien, um nicht den Anschluss zu verpassen.
Und waren in den vorigen Jahren wenigstens noch Themen wie “Ko-Existenz mit anderen Systemen”* auf der Agenda, ist davon heute, im Jahr 2015, praktisch nichts mehr zu hören.
Vom führenden System in die Bedeutungslosigkeit. Wie BTX. Nur ohne offizielle Abschaltung.
Ich für meinen Teil bin froh, mir die “Domino über alles”-Schere vor ein paar Jahren aus dem Kopf gezogen zu haben, denn ich habe noch ein paar Jahrzehnte Arbeitsleben vor mir. Und irgendwann schaffe ich es auch, mein geliebtes cyccle Projekt endlich aus der Taufe zu heben…
contextRoot defines the URL pattern where the Vaadin servlet is reachable. contentLocation is a folder where the required web.xml configuration file can be found.
The <init-param> tag inside <servlet> defines our UI class of our application. We will create this class later. The <servlet-mapping> defines a mapping inside the webapplication path.
This means, if you would add a url-pattern like “/helloVaadinServlet/*” to the Vaadin servlet, the URL to reach the application is
The “/helloVaadin/” part is the defined in the contextPath parameter in the web application. When using another pattern as “/*“, an additional mapping for the Vaadin resources is required:
When running your own servlet, you eventually want to access the Domino environment. To do this, some changes has to be made to the HelloVaadin plug-in.
1. Open the “MANFIFEST.MF” and open the “Dependencies” tab
2. Add the plug-in “com.ibm.osgi.domino.core” to the list of required plug-ins
Let’s create another application, based on Vaadin’s AddressBook example. You can download the source code directly or grab the code from the repository; it is a single class file named “AddressbookUI” only.
After importing (or manually creating) the class in the HelloVaadin plug-in, the servlet configuration in “web.xml” must be updated:
The “<param-value>” must contain the complete class name, I have additionally changed the name of the servlet and updated the path in the “plugin.xml“:
I have created a database named “VaadinResources.nsf“, containing a normal image resource, and an image added via package explorer to the “WEB-INF” folder:
Vaadin provides stream resources, which allows creating of dynamic resources. These resources handle “InputStream” objects, which we can grab from Domino via Java NAPI.
To do this, it is first required to add some plug-ins to the dependencies of the “HelloVaadin” plug-in:
Then we can import the “NAPIUtils” class to the project and add a new method “loadBinaryFile” to it:
/**
* loads given file from a database and returns the Inputsstream
*
* @param serverName
* the server to use
* @param dbPath
* the database path
* @param fileName
* the file to load
* @return the file data as InputStream
*
*/
static public InputStream loadBinaryFile(final String serverName, final String dbPath,
final String fileName) {
NotesSession nSession = null;
NotesDatabase nDatabase = null;
NotesNote nNote = null;
try {
nSession = new NotesSession();
// open database
try {
nDatabase = nSession.getDatabaseByPath(serverName + "!!" + dbPath);
} catch (NotesAPIException e) {
e.printStackTrace();
}
nDatabase.open();
// load existing data
nNote = FileAccess.getFileByPath(nDatabase, fileName);
// get Filedate and return String
InputStream is = FileAccess.readFileContentAsInputStream(nNote);
return is;
} catch (Exception e) {
e.printStackTrace();
} finally {
// recycle NAPI objects
recycleNAPIObject(nNote, nDatabase, nSession);
}
return null;
}
A new class “DominoImageSource” which implements the “StreamResource.StreamSource” interface uses this method to pipe the “InputStream” object returned from the Domino Java NAPI:
package ch.hasselba.vaadin;
import java.io.InputStream;
import ch.hasselba.napi.NAPIUtils;
import com.vaadin.server.StreamResource.StreamSource;
@SuppressWarnings("serial")
public class DominoImageSource implements StreamSource {
private String fileName;
private String serverName;
private String dbPath;
public DominoImageSource(final String serverName, final String dbPath,
final String fileName){
super();
this.serverName = serverName;
this.dbPath = dbPath;
this.fileName = fileName;
}
public InputStream getStream () {
return NAPIUtils.loadBinaryFile( this.serverName, this.dbPath, this.fileName );
}
}
Now, we can create a new Application named “ResourceUI” which displays these two resources:
package ch.hasselba.vaadin;
import com.vaadin.server.StreamResource;
import com.vaadin.server.StreamResource.StreamSource;
import com.vaadin.server.VaadinRequest;
import com.vaadin.ui.Image;
import com.vaadin.ui.UI;
import com.vaadin.ui.VerticalLayout;
@SuppressWarnings("serial")
public class ResourcesUI extends UI {
@Override
protected void init(VaadinRequest request) {
VerticalLayout layout = new VerticalLayout();
setContent(layout);
layout.setSizeFull();
// Create an instance of our stream source.
StreamSource webinfResource = new DominoImageSource (
"Dev01", "VaadinResources.nsf" , "/WEB-INF/WEB-INFResource.png");
StreamSource imageResource = new DominoImageSource (
"Dev01", "VaadinResources.nsf" , "ImageResource.gif");
// Create an image component that gets its contents
// from the resource.
layout.addComponent(new Image("WEB-INF Resource",
new StreamResource(webinfResource, "image01.png")));
layout.addComponent(new Image("Image Resource",
new StreamResource(imageResource, "image02.gif")));
}
}
Again, no rocket science and self-explaining code. When we open the application, the resources are loaded from the database and displayed in the browser:
I had some time last night (the whole family had gone to bed early), so I spent some to look at the XPages integration into Bluemix. I found the Greenwell Travel Expenses Demo:
That’s why I checked, if the datasources are protected. I recommend this for years. Fredrik Norling wrote a little snippet for this. Or better use the “ignoreRequestParam“. Then all your problems are gone.
I now was able to see a little bit more of the application and to check the underlying environment. But then came the moment where my brain forced me to try out some things:
First, I had to look again on the IP address in the error page: “109.228.14.66“. This is not an internal address. Let’s check it:
Not reachable. Whois for “109.228.14.66” ? “Fasthosts Internet Limited“. A provider in UK.
A ping to “greenwellexpenses.mybluemix.net” returned “75.126.81.6″, which belongs to Softlayer. The server is allowed access other servers? Maybe the application can call me?
Now I can try to DoS the application. Because the outgoing connection from the application waits for a response (think about “telnet www.example.com 80“), I can create a bunch of requests, and the server will quickly reach it’s limit.
That’s why I created a simple bash script which makes HTTP request to the Bluemix instance. The script runs on a Raspberry Pi, to demonstrate the low demand of hardware requirements and to show how easy it is do make a DoS attack against a XPage application (if it is was not developed under security aspects).
Here is a short video (the source of the bash script is NOT shown, but it has fewer then 10 lines of code):
This was a “friendly” attack. I have not done anything harmfull. And this is a demo app; if it is not secure, this is not a real problem. The application is available again ten minutes later.
But last night I searched for some XPages servers in the WWW, and I found a lot of misconfigured systems: Error Page enabled, the “Ignore request parameter” is not set to “true” or at least the hack from Fredrik running. And the servers are allowed to access the whole internet… Dev’s and Admins should do their jobs better!
If you plan to migrate your apps to the cloud, please learn more about security. Or hire some specialists with the knowledge and experience in this sector. It is worth the time and the money.
The “Same-orginin policy“ is an important concept for protecting web applications. In short, only resources from the same domain are allowed, everything else is permitted. To allow access other domains in your application, you have to enable “CORS“, a tutorial how to enable this on a Domino server was written by Mark Barton a while ago.
It works fine for protecting an applications against DOM manipulations and/or injection of malicous script code, but this client side security restriction only blocks the response from the server. The client still sends a request, and this can be problematic for the security of a RESTful application.
To clearify this, here is a short example:
I have created a small HTML page containing an Ajax request to load some code of a XPages-based REST service on another server. This file is hosted on my hasselba.ch server, and wants to access some data on my local Domino server:
<html>
<body>
<h1>SOP Demo</h1>
<script>
var xhr =(window.XMLHttpRequest)?new XMLHttpRequest():
new ActiveXObject("Microsoft.XMLHTTP");
xhr.open("GET","http://localhost/REST.nsf/SOPDemo.xsp/foo/",true);
xhr.withCredentials = true;
xhr.send();
</script>
</body>
</html>
The “withCredential” options ensures that an eventually existing Domino session is used when performing the request.
The REST service on my Domino server prints the actual username to the console:
When opening this page, the response of the request is blocked, and that’s what the “Same-origin policy” was made for: If the response contains malicious Javascript code, this script won’t get executed.
The request was made with my credentials, and that is why the “Same origin-policy” does not protect RESTful applications: If a victim visits my page, I am able perform malicious requests against a RESTful webservice in his context.
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:
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 dubiousbug 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:
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.
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:
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;
});
}
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:
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.
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.
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.
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: