Creating a SmartOS Dataset for Windows Server 2012 r2

Flattr this!

I postet about SmartOS almost 1 1/2 Year ago. Since i am still using SmartOS here and then for primary testing purpose, i decided to create a new Post about how to make your own Datasets, to make several VMs based on the same Image possible. As Microsoft also introduces their latest Server OS (Windows Server 2012 r2) as a Preview Version, i choosed this OS for the Test, since i guess besides SmartOS itself and Linux, which has already a lot of Dataset present, Windows might be the next common OS to use in a VM.

Prerequisites

You should already have these Software Packages:

SmartOS Download
http://wiki.smartos.org/display/DOC/Download+SmartOS
Windows Server 2012 R2 Preview
http://technet.microsoft.com/de-DE/evalcenter/dn205287.aspx you should rename the ISO e.g. to win2k12r2_de.iso
TightVNC Java Viewer JAR
http://www.tightvnc.com/download.php

You should have SmartOS installed on a proper Hardware (at least > 50GB Disk and proper KVM support – this means a recent Intel CPU and working VT-x/EPT). I am using VMWare Fusion 5 for this, since there is a specific Setting for supporting VM internal Virtualization.

The Setup of SmartOS is pretty straigh forward. You can have a short description here [1].

In this tutorial i will choose [client] when you have to run the command on your Workstation and [smartos] when you should run it on your SmartOS Host (e.g. via SSH).

SmartOS Configuration

You should check the current Host Configuration of you SmartOS System first. The CLI Tools for this are sometimes different than e.g. in Linux [2].

First you need to find your Host Network Gateway (here 172.16.108.2)

[smartos] /usr/bin/netstat -r

Routing Table: IPv4
  Destination           Gateway           Flags  Ref     Use     Interface
-------------------- -------------------- ----- ----- ---------- ---------
default              172.16.108.2         UG        3        443 e1000g0
localhost            localhost            UH        2        310 lo0
172.16.108.0         00-50-56-28-a8-14    U         5    1694314 e1000g0

Routing Table: IPv6
  Destination/Mask            Gateway                   Flags Ref   Use    If
--------------------------- --------------------------- ----- --- ------- -----
localhost                   localhost                   UH      2      12 lo0

[smartos] /usr/bin/sysinfo -p

Live_Image='20130629T040542Z'
System_Type='SunOS'
Boot_Time='1375215743'
Manufacturer='VMware, Inc.'
Product='VMware Virtual Platform'
Serial_Number='VMware-56 4d 56 5e e8 ff 08 7b-38 81 e6 57 ac d8 b9 7d'
VM_Capable='true'
CPU_Type='Unknown'
CPU_Virtualization='vmx'
CPU_Physical_Cores=4
Nic_Tags=admin
Setup=''
UUID='564d565e-e8ff-087b-3881-e657acd8b97d'
Hostname='00-50-56-28-a8-14'
CPU_Total_Cores=4
MiB_of_Memory=8191
Disk_c1t0d0_size_in_GB=268
Disk_c1t1d0_size_in_GB=268
NIC_admin='e1000g0'
Network_Interface_e1000g0_MAC_Address='00:50:56:28:a8:14'
Network_Interface_e1000g0_IPv4_Address='172.16.108.135'
Network_Interface_e1000g0_NIC_Names='admin'
Network_Interface_e1000g0_Link_Status='up'
Network_Interface_e1000g1_MAC_Address='00:50:56:21:c3:ec'
Network_Interface_e1000g1_IPv4_Address=''
Network_Interface_e1000g1_NIC_Names=''
Network_Interface_e1000g1_Link_Status='up'
Bootparam_console='text'
Bootparam_root_shadow='$5$2HOHRnK3$NvLlm.1KQBbB0WjoP7xcIwGnllhzp2HnT.mDO7DpxYA'
Bootparam_smartos='true'

[smartos] /usr/sbin/dladm show-link

LINK        CLASS     MTU    STATE    BRIDGE     OVER
e1000g0     phys      1500   up       vmwarebr   --
e1000g1     phys      1500   up       --         --
vmwarebr0   bridge    1500   up       --         e1000g0

VM Setup

VM Configuration

[smartos] /usr/bin/cat /opt/win2k12r2_vm.json

{
  "brand": "kvm",
  "alias": "win2k12r2",
  "vcpus": 2,
  "autoboot": false,
  "ram": 2048,
  "resolvers": ["8.8.8.8"],
  "disks": [
    {
       "boot": true,
       "model": "ide", 
      "size": 40960
    }
  ],
  "nics": [
    {
      "nic_tag": "admin",
      "model": "e1000",
      "ip": "172.16.108.201",
      "netmask": "255.255.255.0",
      "gateway": "172.16.108.2",
      "primary": 1
    }
  ]
}

Your main Tool for VM Administration ist vmadm [3].

[smartos] /usr/sbin/vmadm create -f /opt/win2k12r2_vm.json

Successfully created VM 37d3cef6-01a6-4b25-a927-928cc2681744

[smartos] /usr/sbin/vmadm list

UUID                                  TYPE  RAM      STATE             ALIAS
37d3cef6-01a6-4b25-a927-928cc2681744  KVM   2048     stopped           win2k12r2

[smartos] /usr/sbin/zfs list

NAME                                               USED  AVAIL  REFER  MOUNTPOINT
zones                                             52,3G   192G   652K  /zones
zones/37d3cef6-01a6-4b25-a927-928cc2681744        39,5K  10,0G  39,5K  /zones/37d3cef6-01a6-4b25-a927-928cc2681744
zones/37d3cef6-01a6-4b25-a927-928cc2681744-disk0    40G   232G    16K  -
zones/config                                        41K   192G    41K  legacy
zones/cores                                         62K  10,0G    31K  /zones/global/cores
zones/cores/37d3cef6-01a6-4b25-a927-928cc2681744    31K  10,0G    31K  /zones/37d3cef6-01a6-4b25-a927-928cc2681744/cores
zones/dump                                        4,00G   192G  4,00G  -
zones/opt                                         31,5K   192G  31,5K  legacy
zones/swap                                        8,25G   200G    16K  -
zones/usbkey                                       127K   192G   127K  legacy
zones/var                                         2,08M   192G  2,08M  legacy

You need to copy the installation ISO Image into your VM Zone:

[client] scp win2k12r2_de.iso root@172.16.108.135:/zones/37d3cef6-01a6-4b25-a927-928cc2681744/root/

After that you can boot your VM with the ISO Image attached:

[smartos] /usr/sbin/vmadm boot 37d3cef6-01a6-4b25-a927-928cc2681744 order=cd,once=d cdrom=/win2k12r2_de.iso,ide

Successfully started VM 37d3cef6-01a6-4b25-a927-928cc2681744

You can access your VM via a VNC Viewer (we are using TightVNC). You can get the necessary connection setting via:

[smartos] /usr/sbin/vmadm info 37d3cef6-01a6-4b25-a927-928cc2681744 vnc

{
  "vnc": {
    "host": "172.16.108.135",
    "port": 34783,
    "display": 28883
  }
}

You can now extract the TightVNC Zip you downloaded before. Withing the folder classes you type:

[client] java VncViewer HOST 172.16.108.135 PORT 34783

The Port and the Host URL may differ. The Port will change everytime your VM reboots.

Setup

VNCSetup1
VNCSetup2

Running System

After a Reboot, the Login Screen greets us:

VNCRunning1

The Network also seems to work :-).

VNCRunning2

Dataset Creation

We can now start to create our VM Package. For that we need to shutdown our VM:

VNCShutdown1

VNCShutdown2

[smartos] /usr/sbin/vmadm list

UUID                                  TYPE  RAM      STATE             ALIAS
37d3cef6-01a6-4b25-a927-928cc2681744  KVM   2048     stopped           win2k12r2

The first step is to create a ZFS Snapshot of that specific VM Disk:

So let’s have a look:
[smartos] /usr/sbin/zfs list

NAME                                               USED  AVAIL  REFER  MOUNTPOINT
zones                                             52,3G   192G   652K  /zones
zones/37d3cef6-01a6-4b25-a927-928cc2681744        39,5K  10,0G  39,5K  /zones/37d3cef6-01a6-4b25-a927-928cc2681744
zones/37d3cef6-01a6-4b25-a927-928cc2681744-disk0    40G   232G    16K  -
zones/config                                        41K   192G    41K  legacy
zones/cores                                         62K  10,0G    31K  /zones/global/cores
zones/cores/37d3cef6-01a6-4b25-a927-928cc2681744    31K  10,0G    31K  /zones/37d3cef6-01a6-4b25-a927-928cc2681744/cores
zones/dump                                        4,00G   192G  4,00G  -
zones/opt                                         31,5K   192G  31,5K  legacy
zones/swap                                        8,25G   200G    16K  -
zones/usbkey                                       127K   192G   127K  legacy
zones/var                                         2,08M   192G  2,08M  legacy

It seems, that our disk is zones/37d3cef6-01a6-4b25-a927-928cc2681744-disk0 a zvol.

[smartos] /usr/sbin/zfs snapshot zones/37d3cef6-01a6-4b25-a927-928cc2681744-disk0@dataset

Let’s look for the Snapshot:

[smartos] /usr/sbin/zfs list -t snapshot

NAME                                                       USED  AVAIL  REFER  MOUNTPOINT
zones/37d3cef6-01a6-4b25-a927-928cc2681744-disk0@dataset      0      -  7,36G  -

There it is. Now we need to compress it, since this will be our vanilla Image.

[smartos] /usr/sbin/zfs send zones/37d3cef6-01a6-4b25-a927-928cc2681744-disk0@dataset | /usr/bin/gzip > /tmp/win2k12r2_de.zvol.gz

Wait…

Now we only need size and the SHA1 Checksum of that Image for the Manifest file.

[smartos] /usr/bin/digest -a sha1 win2k12r2_de.zvol.gz

Wait…

0d955b91973bdaccc30ffa75b856709ce9a1953a

[smartos] /usr/bin/ls /tmp

...
-rw-r--r--   1 root     root     3933399939 Jul 30 22:20 win2k12r2_de.zvol.gz
...

So our matching Dataset Manifest File might look like this:

[smartos] /usr/bin/cat /tmp/win2k12r2_vm.json

{
    "name": "win2k12r2de",
    "version": "1.0",
    "type": "zvol",
    "cpu_type": "host",
    "description": "Windows Server 2012 R2 Preview DE",
    "created_at": "2013-07-31T11:40:00.000Z",
    "updated_at": "2013-07-31T11:40:00.000Z",
    "published_at": "2013-07-31T11:40:00.000Z",
    "os": "windows",
    "image_size": "40960",
    "files": [
        {
            "path": "win2k12r2_de.zvol.gz",
            "sha1": "0d955b91973bdaccc30ffa75b856709ce9a1953a",
            "size": 3933399939
        }
    ],
    "requirements": {
        "networks": [
            {
                "name": "net0",
                "description": "public"
            }
        ]
    },
    "disk_driver": "ide",
    "nic_driver": "e1000",
    "uuid": "857ea9a0-f965-11e2-b778-0800200c9a66",
    "creator_uuid": "0e70a5a1-0115-4a2b-b260-94723fd31bf1",
    "vendor_uuid": "0e70a5a1-0115-4a2b-b260-94723fd31bf1",
    "owner_uuid": "0e70a5a1-0115-4a2b-b260-94723fd31bf1",
    "creator_name": "Philipp Haussleiter",
    "platform_type": "smartos",
    "urn": "smartos:phaus:win2k12r2de:1.0"
}

You can now install the new Dataset to your local Image Store:

[smartos] /usr/sbin/imgadm install -m /tmp/win2k12r2_de.json -f /tmp/win2k12r2_de.zvol.gz

Installing image 857ea9a0-f965-11e2-b778-0800200c9a66 (win2k12r2de 1.0)
857ea9a0-f965-11e2-b778-0800200c9a66      [==============================>                 ]  19% 727.97MB  11.39MB/s  4m25s
…
Installed image 857ea9a0-f965-11e2-b778-0800200c9a66 to "zones/857ea9a0-f965-11e2-b778-0800200c9a66".

Wait…

[smartos] /usr/sbin/imgadm list

UUID                                  NAME         VERSION  OS       PUBLISHED
…
857ea9a0-f965-11e2-b778-0800200c9a66  win2k12r2de  1.0      windows  2013-07-31T11:40:00Z
…

You can now using this image as a normal SmartOS Dataset with the UUID 857ea9a0-f965-11e2-b778-0800200c9a66.

Feel free to comment if you have questions or problems.

Links

[1] SmartOS – Basic Setup
http://blog.smartcore.net.au/smartos-the-basics/
[2] The Linux-to-SmartOS Cheat Sheet
http://wiki.smartos.org/display/DOC/The+Linux-to-SmartOS+Cheat+Sheet
[3] Using vmadm to manage virtual machines
http://wiki.smartos.org/display/DOC/Using+vmadm+to+manage+virtual+machines
[4] Some extra Configuration Options for SmartOS
http://wiki.smartos.org/display/DOC/extra+configuration+options
[5] How to create a SmartOS Dataset
http://www.nickebo.net/creating-smartos-datasets

Adding LDAP Authentication to a Play! 2 Application

Flattr this!

As of Play! 1 is not really supported anymore, i will describe the steps for accessing Data from your LDAP Directory with your Play! 2 with this Post.

Prerequisites

As also mentioned in my last Post, for this example we are using the Vagrant vagrant-rundeck-ldap VM, I already mentioned here.

Setup

After you setup a basic Play! 2 Application with

play new ldap-test

We need to update our Applications Dependencies. To achieve this, we need to change project/Build.scala:


import sbt._
import Keys._
import play.Project._

object ApplicationBuild extends Build {

  val appName         = "play2-ldap-example"
  val appVersion      = "1.0-SNAPSHOT"

  val appDependencies = Seq(
    // Add your project dependencies here,
    javaCore,
    javaJdbc,
    javaEbean,
    "com.innoq.liqid" % "ldap-connector" % "1.3"
  )

  val main = play.Project(appName, appVersion, appDependencies).settings(
    // Add your own project settings here      
  )
}

The Command:


play compile

Should download all necessary Project Depedencies and will do an initial Compilation of all your Project Files (currently not really many).

You also need to add the LDAP Settings to your Applications configuration. For this example we will use an external Properties File conf/ldap.properties:


# Settings for LiQID
# ~~~~~
ldap.user.objectClasses=person
ldap.group.objectClasses=groupOfUniqueNames

default.ldap=ldap1
# LDAP Listsing, divided by ","
ldap.listing=ldap1

ldap1.ou_people=ou=users
ldap1.ou_group=ou=roles
ldap1.url=ldap://localhost:3890
ldap1.principal=dc=Manager,dc=example,dc=com
ldap1.credentials=password

ldap1.base_dn=dc=example,dc=com
ldap1.admin.group.id=admin

ldap1.user.id.attribute=cn
ldap1.user.object.class=person

ldap1.group.id.attribute=cn
ldap1.group.object.class=groupOfUniqueNames
ldap1.group.member.attribute=uniqueMember

Implementation

Since Play1 2 does not really support the use of Before Filters, we will use Custom Actions for making our Login Authentication work.
We will create a new Package app/actions with two new files: an annotation interface BasicAuth and the implementation itself BasicAuthAction.
Annotations will be used to Set a specific Controller to use Basic Auth.
So lets start with BasicAuth:


package actions;

import play.mvc.With;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@With(BasicAuthAction.class)
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD, ElementType.TYPE })
@Inherited
@Documented
public @interface BasicAuth {
}

After that you can annotate Controllers with @BasicAuth (but this won’t work, since the Implementation is still missing).
Here then the BasicAuthAction:



package actions;

import com.ning.http.util.Base64;

import models.User;
import play.mvc.Action;
import play.mvc.Http.Context;
import play.mvc.Result;

public class BasicAuthAction extends Action {

	private static final String AUTHORIZATION = "authorization";
	private static final String WWW_AUTHENTICATE = "WWW-Authenticate";
	private static final String REALM = "Basic realm=\"play2-ldap-example\"";

	@Override
	public Result call(Context context) throws Throwable {

		String authHeader = context.request().getHeader(AUTHORIZATION);
		if (authHeader == null) {
			return sendAuthRequest(context);
		}

		String auth = authHeader.substring(6);

		byte[] decodedAuth = Base64.decode(auth);
		String[] credString = new String(decodedAuth, "UTF-8").split(":");

		if (credString == null || credString.length != 2) {
			return sendAuthRequest(context);
		}

		String username = credString[0];
		String password = credString[1];
		User authUser = User.authenticate(username, password);
		if (authUser == null) {
			return sendAuthRequest(context);
		}
		context.request().setUsername(username);
		return delegate.call(context);
	}

	private Result sendAuthRequest(Context context) {
		context.response().setHeader(WWW_AUTHENTICATE, REALM);
		return unauthorized();
	}
}

As you can see, there are no LDAP specific Dependencies at all, since all necessary Logic is the User Model, in User.authenticate(username, password).So let’s have a look into that Model:


package models;

import play.Play;

import com.innoq.ldap.connector.LdapHelper;
import com.innoq.ldap.connector.LdapUser;
import com.innoq.liqid.utils.Configuration;

public class User {

	private static LdapHelper HELPER = getHelper();

	public String sn;
	public String cn;
	public String dn;

	public User(String cn) {
		this.cn = cn;
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("cn: ").append(cn).append("\n");
		sb.append("sn: ").append(sn).append("\n");
		sb.append("dn: ").append(dn).append("\n");
		return sb.toString();
	}

	public static User authenticate(String username, String password) {
		if (HELPER.checkCredentials(username, password)) {
			return new User(username);
		}
		return null;
	}

	public static User getUser(String username) {
		LdapUser ldapUser = (LdapUser) LdapHelper.getInstance().getUser(
				username);
		User user = new User(username);
		user.cn = ldapUser.get("cn");
		user.sn = ldapUser.get("sn");
		user.dn = ldapUser.getDn();
		return user;
	}

	private static LdapHelper getHelper() {
		Configuration.setPropertiesLocation(Play.application().path()
				.getAbsolutePath()
				+ "/conf/ldap.properties");
		return LdapHelper.getInstance();
	}
}

You also have a static Instace of that LDAP Helper, but authenticate User Credentials and Login are in two different Methods.Last thing is to Load a User from the LDAP Directory:Here the Admin Controller:


package controllers;

import models.User;
import actions.BasicAuth;
import play.mvc.Controller;
import play.mvc.Result;
import views.html.Admin.index;

@BasicAuth
public class Admin extends Controller {
    public static Result index() {
    	User u = User.getUser(request().username());
        return ok(index.render("Hello Admin!", u));
    }
}

And here the used Template File:


@(message: String, user: User)

@main("Admin Index") {
@{message} 
<p>   
<pre>
@{user}
</pre>
</p>
}

Links

You can find the Sources of that Library here: https://github.com/innoq/LiQIDYou can find an example Project here: https://github.com/phaus/play-ldap/tree/master/play2-ldap-example

Adding LDAP Authentication to a Play! 1 Application

Flattr this!

You will often find yourself in a situation where you need a public and a private (normally some Administration) Area in your application.

This Post is about how easy you can access User Data stored in you companies LDAP Directory with a public availible Library in a Play! 1 Application. I will also give you an example for Play! 2 in one of the upcoming Posts here.

Prerequisites

For this example we are using the Vagrant vagrant-rundeck-ldap VM, I already mentioned here.

Setup

After you setup a basic Play! 1 Application with

play new ldap-test

We need to add the necessary Dependencies to our Application. Since the Library is availible over the public Maven Repository the Changes in your conf/dependecies.yml are quite simple:


# Application dependencies
require:
    - play
    - com.innoq.liqid -> ldap-connector 1.3

You then need to update your Applications Dependencies with:

play deps --sync

We also need to add some Configration Settings to the conf/application.conf File:
(there are some more, since the Example Directory did not match to the Libraries default LDAP Layout)


...
# Settings for LiQID
# ~~~~~
# Basic LDAP objectClasses for Users and Groups
ldap.user.objectClasses=person
ldap.group.objectClasses=groupOfUniqueNames

# You can add several LDAP for Writing Changes, but you need one default LDAP for Access without a specifiy Instance String (e.g. ldap1, ldap2, etc).
default.ldap=ldap1
ldap.listing=ldap1

# Instance specific settings (e.g. credentials, layout, etc. )

ldap1.ou_people=ou=users
ldap1.ou_group=ou=roles
ldap1.url=ldap://localhost:3890
ldap1.principal=dc=Manager,dc=example,dc=com
ldap1.credentials=password

ldap1.base_dn=dc=example,dc=com
ldap1.admin.group.id=admin

ldap1.user.id.attribute=cn
ldap1.user.object.class=person

ldap1.group.id.attribute=cn
ldap1.group.object.class=groupOfUniqueNames
ldap1.group.member.attribute=uniqueMember

Implementation

We will use two different techniques:

  • Controller inheritance
  • Before Filters

The goal is, that for a specific Count of Controllers, the Request should be checked for a valid Autorization Header.
So all Controllers that need to be “protected” will inherit from a Controller named SecureApplication.
This SecureApplication Controller will implement a Before Filter and some Utility Methods to perform all necessary Login Verification.

This is the basic Implementation of that Controller:


package controllers;

import com.innoq.ldap.connector.LdapHelper;
import com.innoq.liqid.utils.Configuration;

import play.Logger;
import play.Play;
import play.mvc.Before;
import play.mvc.Controller;

public class SecureApplication extends Controller {
	private final static LdapHelper HELPER = getLdapHelper();
	@Before
	public static void checkLogin() {
		if (request.user == null || request.password == null
				|| !HELPER.checkCredentials(request.user, request.password)) {
			unauthorized("You need to Login first!");
		}
	}
	private static LdapHelper getLdapHelper() {
		Configuration.setPropertiesLocation(Play.applicationPath
				+ "/conf/application.conf");
		return LdapHelper.getInstance();
	}	
}

In the getLdapHelper() Methods you also see, that the location of the Configuration is changed to ../conf/application.conf.
Otherwise the Default Location would be in ~/.liqid/ldap.properties.

The Before Action checkLogin is triggered on every Request to a Controller that inherits from SecureApplication.
All other Controllers (inheriting the normal Controller Class), won’t trigger that Application.

You also can access all Data stored in the LDAP Directory. So if you want to access Attributes like cn, dn and sn, you need to add some more Code:
First we will create a basic POJO that contains all User data:


package models;

public class User {
	public String cn;
	public String sn;
	public String dn;

	public User(String cn, String sn, String dn) {
		this.cn = cn;
		this.sn = sn;
		this.dn = dn;
	}

	public String toString() {
		StringBuilder sb = new StringBuilder();
		sb.append("cn: ").append(cn).append("\n");
		sb.append("sn: ").append(sn).append("\n");
		sb.append("dn: ").append(dn).append("\n");
		return sb.toString();
	}
}

We then will update our SecureApplication Class:


package controllers;

import models.User;

import com.innoq.ldap.connector.LdapHelper;
import com.innoq.ldap.connector.LdapNode;
import com.innoq.liqid.model.Node;
import com.innoq.liqid.utils.Configuration;

import play.Logger;
import play.Play;
import play.mvc.Before;
import play.mvc.Controller;

public class SecureApplication extends Controller {
	private final static LdapHelper HELPER = getLdapHelper();
	protected static User ActiveUser;
	@Before
	public static void checkLogin() {
		if (request.user == null || request.password == null
				|| !HELPER.checkCredentials(request.user, request.password)) {
			unauthorized("You need to Login first!");
		} else {
			ActiveUser = getUser(request.user);
		}
	}

	private static LdapHelper getLdapHelper() {
		Configuration.setPropertiesLocation(Play.applicationPath
				+ "/conf/application.conf");
		return LdapHelper.getInstance();
	}

	private static User getUser(String cn){
		LdapNode ldapNode = (LdapNode) HELPER.getUser(cn);
		return new User(cn, ldapNode.get("sn"), ldapNode.getDn());
	}
}

As you can see, there is a specific NodeType LdapNode that has some Convenience Methods for accessing the LDAP Data.
As Example you can invoke the toString() Method of an User Object in your template.

So first we will have to create our Admin Controller:


package controllers;

import models.User;

public class Admin extends SecureApplication {
	public static void index(){
		User u = ActiveUser;
		render(u);
	}
}

You have to use another Object u for that User, since Play! 1 has a specific Behavior of how to map Variables in Templates.
Our Template app/views/Admin/index.html might look like this:


#{extends 'main.html' /}
#{set title:'Admin Area' /}
Hello Admin!
<pre>
${u}
</pre>

If you login then with the Credendials build/build your output might look then like this:

 Index | Administration
Hello Admin!

cn: build
sn: The account to use to demonstrate managing builds only
dn: cn=build,ou=users,dc=example,dc=com

Links

You can find the Sources of that Library here: https://github.com/innoq/LiQID
You can find an example Project here: https://github.com/phaus/play-ldap/tree/master/play1-ldap-example

Feel free to add issues about bugs or missing features to this :-).

IMHO: Differences between a mobile Web-Page and a mobile Web-Application

Flattr this!

About

Since i did some Web-Development for Mobile Device for my last projects, i would like to cover some of my thoughts and expierences i had during these months.

To come straight to the point, it is not really easy to develop stuff for mobile Devices these days, exspecially if you want to support older ones like Android <=2.3, since there are a lot of vendor specific hoops you have to jump through (and althought a lot of testing).

But i would like to start from the other site: the users site (the so called user experience).

 

mobile Web-Page

For a User, a mobile Web-Page is just a different view to a common know Homepage.
He can use saved or received links to access a specific resource.
He can rapidly change the Page Location while changing the URL in the address bar.
The Page opens in the Mobile Web-Browser (or an Browser like environment like WebView, hybrid App, etc.)
The Page need online Access to work.
The Page needs some time to load, although the content has been adjusted to mobile use (responsiveness, minifying, image optimazation, aggrssive Caching, CND, etc.)
Every Action will use a Request and will go through the Web-Page and will receive a Response for Displaying.
The User can add a Bookmark to Homescreen/Browser Favorites to have fast and easy access to that URL.

To summarize all this, a mobile Web-Page is the result of a web-optimized Web-Development Project, so e.g. you start with your common (Desktop-) Web-Flows using mobile agnostic CSS (if you must bootstrap or foundation) and modify Navigation, Javascript and Content-size to work with the mobile devices display.

In most cases you will find something like this on your screen:

Ohne Titel.002

You have a fitting View, on top your URL (that might be scrolled up via JS) and on the bottom your Browsers controls (that might be hidden by the Browsers default – e.g. Landscape view on iOS).

 

mobile Web-Application

A mobile Web-Application is specially developed for a mobile Device. You won’t start with already existing views or other assets. Do a App-Like Design (e.g. Views instead of Pages).
You should think about different Behaviour for this one (like more Icons instead of Full-Tested Links), hidden Menus that appear via slide. To be clear: use gestures and icons, your device has a touch sensor, so use it.
Use the availble Devices Settings to adjust your Applications appearance.
The User has to add the Application to her/his Homescreen to enter “App-Mode” (although the first Start uses the mobile Browser).
The App starts alway Full-Screen, so no URL is present at all, so the URL and Links are less relevant, although the Application my use a specific Model Routing like Backbone.js.
The App uses local storage (up to 10 megs) – if it has to store Data – first, then uses the Servers persistence logic.
The App should use the availible Device specific JavaScript Methods
An Action does not trigger a request automatically back to the Server (Backend).
A common use-case might be: Load Data from Backend, do something with the Data, save it locally, user other local Data, save the Data, close the App, …, restart the App, continue working, …, send the Data back to the Backend.
The App should work Offline (you have to implement some kind for update Logic – e.g. using a manifest file).
You should use a less Request/Response Cycles as possible to save Battery Time and Bandwidth.
You should use optimzied Resources (e.g. pack the structe of a survey in on JSON Feed instead of using Links to appending resources – with usefull exeption like sounds or images).
Consider using alternative Communications ways (like Websockets).

Ohne Titel.003

You will find a common Menu with Icons at the Bottom of the Page. Some Apps also using hidden Menus (that appear via swipe or a small Button).
You also will need some Kind of Activity Indicator (like a progress bar), if you need to fetch Data from or send Data to the Backend (or doing other operations that might use some time).

 

Disclaimer 🙂

That’s for the moment. It really needs some more clearifications and examples. Of course you can argue a lot about some statements here. This i just my personal opinion, first of all to differentiate between the two things.

Most of the Links go to iOS Resources, since i had to do Web Devlopment mostly for iOS and since iOS was first planed as a Web-Application only Platform, it has a pretty good documenation of all availible features. But i am sure, there are similar Documentations for (at least the common) Android and Windows Phone Devices.

Nice Vagrant Script for setting up a LDAP VM for Testing

Flattr this!

I found a nice Setup Script at https://github.com/gschueler/vagrant-rundeck-ldap for creating a working LDAP VM with a basic

vagrant up

You just need to do some more steps before:

  • make sure you have installed Version 4.2.12 of Virtualbox! (the latest version – 4.2.14 – breaks vagrant)
  • get your vagrant setup from here
  • download the necessary ubuntu vagrant box:
    vagrant box add precise32 http://files.vagrantup.com/precise32.box

The LDAP port will be mapped to port 3890 in your local machine (the host where virtualbox is installed)
You can login (e.g. with Apache Directory Studio) with the following settings:

Encryption: none
Host: localhost
Port: 3890
User: dc=Manager,dc=example,dc=com
Pass: password