Just one geek's opinions and epiphanies

PHP and Josso - Transparency Rocks!


Bogdan recently asked in my comments:

I can't seem to find a call like josso_authenticate($name, $pass), returning an array to be appended to user's SESSION. I was expecting such a method, because SOAP is already used everywhere, so it shouldn't be too hard implementing this. Has anyone had success implementing this kind of "transparent" login?

JOSSO simply doesn't make it easy to log people in with a single call. Instead you need to make an interface to the API to do so. I just so happen to have an example of such a wrapper function.

Logging In A User

To do everything and make it easy for future reuse I built two classes. The first is a Login Class and the second a Josso Class (below). My Login Class is pretty sparse, but it includes a wrapper to handle all the tasks required to login a user through Josso and get back the Josso details for that user.

The full code of each class is down at the bottom of the post, just expand the code block, but here is the function that is really what you are looking for Login->authorize()

Login->authorize()

[php]public function authorize($username, $password){

# make sure we have a valid username if(empty($username)){ # return invalid username throw new LoginException('Invalid username'); } else { $this->username = $username; }

# make sure we have a valid password if(empty($password)){ # return invalid username throw new LoginException('Invalid password'); }

# check that user exists if( ! $this->josso->userExists($this->username)){ throw new LoginException('Invalid username'); }

# check Josso with username and password $this->assertion = $this->josso->assertIdentityWithSimpleAuthentication($username, $password); if($this->josso->error){ throw new LoginException('Invalid username/password combination'); }

# resolve Authentication Assertion $this->token = $this->josso->resolveAuthenticationAssertion($this->assertion); if($this->josso->error){ throw new LoginException('Interal Error [Login]: '. LINE); }

$this->session = $this->josso->getSession($this->token); if($this->josso->error){ throw new LoginException('Interal Error [Login]: '. LINE); }

$this->user = $this->josso->findUserInSession($this->token); if($this->josso->error){ throw new LoginException('Interal Error [Login]: '. LINE); }

$this->cleanUpUser();

$this->roles = $this->josso->findRolesByUsername($this->username); if($this->josso->error){ throw new LoginException('Interal Error [Login]: '. LINE); }

$this->cleanUpRoles();

$this->user->sosPermissions =& $this->roles; }[/php]

What That Actually Did

As you can see you simply pass the $username and $password to the function and it runs through a ton of work for you.

First it validates the input, make sure you aren't working for no reason.

Next it makes sure the userExists(), this will save you heartache later when JOSSO pretends a user exists, but really doesn't.

Then I asserIdentityWithSimpleAuthentication() which literally means I pass those to JOSSO and wait for an answer. The answer returned is an assertion_id, which really doesn't mean anything.

You then take the assertionid returned and call resolveAuthenticationAssertion(), this will return your sessionid if the login was valid.

With the sessionid you call getSession(). This returns all the details of the session that JOSSO now has open. This varies depending on who setup your JOSSO implementation but you should have an identifying is (userid) if nothing else.

I go on to make sure that JOSSO hasn't forgotten anything by calling findUserInSession(), which literally just tells me who I am supposed to be working with.

The cleanUpUser() you could probably ignore, but our system returns an object with dots ( . ) in the name and I can't stand that, so I change them to underscores ( _ ).

Finally I call back to my JOSSO install to get any roles the user has, again this will vary if your JOSSO doesn't handle roles.

Login Class

[php collapse="true"]

class Login {

public $token; public $username; public $session; public $roles; public $user; public $request; private $assertion; # this will hold the instance of Josso Controller we need private $josso;

function Login($request = null){

$this->josso = new Josso; if(!empty($request)){ $this->request = $request; } }

#validate an existing token public function validateToken($usertoken){ $session = $this->josso->accessSession($usertoken);

/* * For whatever reason Josso returns nothing if the session is valid * and the operation has been completed. */ if( empty($session)){ return true; }

if($this->josso->error){ throw new LoginException('Internal Error [Login]: '. LINE); } return false; }

# authorize a user with password public function authorize($username, $password){

# make sure we have a valid username if(empty($username)){ # return invalid username throw new LoginException('Invalid username'); } else { $this->username = $username; }

# make sure we have a valid password if(empty($password)){ # return invalid username throw new LoginException('Invalid password'); }

# check that user exists if( ! $this->josso->userExists($this->username)){ throw new LoginException('Invalid username'); }

# check Josso with username and password $this->assertion = $this->josso->assertIdentityWithSimpleAuthentication($username, $password); if($this->josso->error){ throw new LoginException('Invalid username/password combination'); }

# resolve Authentication Assertion $this->token = $this->josso->resolveAuthenticationAssertion($this->assertion); if($this->josso->error){ throw new LoginException('Interal Error [Login]: '. LINE); }

$this->session = $this->josso->getSession($this->token); if($this->josso->error){ throw new LoginException('Interal Error [Login]: '. LINE); }

$this->user = $this->josso->findUserInSession($this->token); if($this->josso->error){ throw new LoginException('Interal Error [Login]: '. LINE); }

$this->cleanUpUser();

$this->roles = $this->josso->findRolesByUsername($this->username); if($this->josso->error){ throw new LoginException('Interal Error [Login]: '. LINE); }

$this->cleanUpRoles();

$this->user->sosPermissions =& $this->roles; }

public function createLoginFromToken($token){

$this->user = $this->josso->findUserInSession($token); if($this->josso->error){ throw new LoginException('Interal Error [Login]: '. LINE); }

$this->cleanUpUser();

$this->roles = $this->josso->findRolesByUsername($this->user->name); if($this->josso->error){ throw new LoginException('Interal Error [Login]: '. LINE); }

$this->cleanUpRoles();

$this->user->sosPermissions =& $this->roles; }

# get a Josso hashed version of a password public function getPassHash($password){ $passhash = $this->josso->getPassHash($password); return $passhash; }

# acquire roles for the user from josso public function getUserRoles(){ return $this->roles; }

# send the signal to Josso to signoff (closes all sessions) public function signoff($sessionid = null){ if( ! $sessionid && ! $this->session->id){ return false; }

if(!$sessionid){ if(!empty($this->session)){ if($this->josso->globalSignoff($this->session->id)){ return true; } } else { return false; } } else { if($this->josso->globalSignoff($sessionid)){ return true; } } return false; }

private function cleanUpUser(){ if(!empty($this->user)){ foreach($this->user->properties as $prop => $details){ $name = $details->name; if($pos = strpos($name, '.')){ $name = explode('.',$name); $name = $name[0] . ucwords($name[1]); } $this->user->$name = $details->value; } unset($this->user->properties); } }

private function cleanUpRoles(){ if(!empty($this->roles)){ foreach($this->roles as $key => $details){ $tmpRoles[$key] = $details->name; } $this->roles = $tmpRoles; } } } ?>[/php]

Josso Class

[php collapse="true"] <pre>class Josso {

var $SSOIdentityManager; var $SSOSessionManager; var $SSOIdentityProvider; var $JossoPashHash; var $error;

function Josso(){

// create the soapclients $this->SSOIdentityManager = new Soapclient(JOSSOSERVER .'/josso/services/SSOIdentityManager?wsdl', array('Trace' => 1)); $this->SSOSessionManager = new Soapclient(JOSSOSERVER .'/josso/services/SSOSessionManager?wsdl', array('trace' => 1)); $this->SSOIdentityProvider = new Soapclient(JOSSO_SERVER .'/josso/services/SSOIdentityProvider?wsdl', array('trace' => 1)); }

function accessSession($sessionid){ try{ $accessSession = $this->SSOSessionManager->accessSession($sessionid); // we expect a null return $accessSession; } catch(Exception $e){ $this->error = $e; return false; } }

// check if a user exists function userExists($username){ try { $userExists = $this->SSOIdentityManager->userExists($username); return true; } catch (Exception $e){ $this->error = $e; return false; } }

// assert a login function assertIdentityWithSimpleAuthentication($username, $password){ try{ $loginAssertion = $this->SSOIdentityProvider->assertIdentityWithSimpleAuthentication($username, $password); return $loginAssertion; } catch (Exception $e){ $this->error = $e; return false; } }

// check assertion and get session function resolveAuthenticationAssertion($loginAssertion){ try{ $session = $this->SSOIdentityProvider->resolveAuthenticationAssertion($loginAssertion); return $session; } catch (Exception $e){ $this->error = $e; return false; } }

// get session details function getSession($session){ try { $sessionDetails = $this->SSOSessionManager->getSession($session); return $sessionDetails; } catch (Exception $e){ $this->error = $e; return false; } }

// make sure the session we found belongs to our user function findUserInSession($session){ try { $userInSession = $this->SSOIdentityManager->findUserInSession($session); return $userInSession; } catch (Exception $e){ return false; } }

// not used function findUser($username){ try{ $userDetails = $this->SSOIdentityManager->findUser($username); return $userDetails; } catch (Exception $e){ return $e; } }

// get a users roles function findRolesByUsername($username){ try { $userRoles = $this->SSOIdentityManager->findRolesByUsername($username); return $userRoles; } catch (Exception $e){ $this->error = $e; return false; } }

function globalSignoff($session){ try { $signoff = $this->SSOIdentityProvider->globalSignoff($session); } catch (Exception $e){ $signoff = $e; } unset($_SESSION['josso']); return $signoff; }

function getPassHash($password){ try{ $result = $this->JossoPashHash->hash($password); return $result; } catch (Exception $e){ return $e; } }

}[/php]

That's It!

I hope I have at least in part helped with your quest to make logging people in through JOSSO more bearable.

Josso & PHP


I recently had the pleasure, if you would like to call it that, to implement JOSSO with PHP at my work. I quickly learned a few things about the process of setting up Josso with PHP, that aren't well documented, and as such I am going to share my findings.

Downloading
Let's start with downloading the PHP library for Josso. Most people will head straight to the SF.net repository for Josso and look for the PHP link. Well, let me save you the headache, it ain't there. The PHP library is stored inside of the josso main package (currently josso-1.7.zip as of this writing). The main package by-the-way is 80+ MB. You are looking for roughly 30Kb of files inside that zip.

Inside the zip you will find tons of files, and folders . Let me guide you to where the PHP files are: (after unzipping)

josso-1.7jossocoresrcpluginsphpphp

No, the double php is not a typo. Inside of that directory you will find three class files, a config file, a couple of login/logout views, the security check, and the josso file. Be forewarned there is also a nusoap directory, and this thing will cause some headache if you are not prepared.

Installation
Unless you have full control of your server that you are installing the JOSSO + PHP interface on, you will not have access to add Josso to the php.ini file to make is autoload on every single page. For this I really suggest using a MVC with Front Controller setup. This allows you to force the user through a single point to access any part of your site, and thus eliminates the need to make Josso autoload for every single php file on your site.

SOAP
Finally I want to talk about Josso's implementation of SOAP. On their PHP site they write:

In case of using PHP5 be sure of disabling the native SOAP support in order to avoid conflicts with the SOAP API used by JOSSO.

That is 100% completely not needed. Here is why. The SOAP class they do use doesn't touch the SOAP class from PHP, and as such it can simply have all references changed. I simply loaded all the files from Josso into Eclipse and did a find and replace on soapclient (the actual class that conflicts) and renamed it to josso_soapclient. There were only a handful of places to change this, maybe 10. Once they were renamed the Josso + PHP experience was quite simple.

Example Scripts
Lastly I want to quickly mention that when you load the sample scripts provided by Josso, you will get errors. Apparently some of the methods have changed since they wrote the Josso examples. I have submitted my changes to the Josso project, and I hope that they will be included as they only make sense. So if you get errors, just go into the code and look to see if 1) the method exists anywhere, and 2) find a similar method and call it.

Conclusion
I don't want to sound as though I am bagging on Josso at all, instead I am just letting people know the problems I had, and how I fixed them. The rest of the setup was really quite simple. For Single Sign-on, Josso is great.

Update
So another small annoyance I found while using Josso was that when you send a bad login to Josso, Josso will hijack your user experience. I was passing the "jossobackto" string, and I couldn't think why it wouldn't send the user back. It turns out that Josso only uses the "jossobackto" string when there is a valid login, and a second parameter of "jossoonerror" is required for bad logins (or errors).