<?php

	include_once( PLOG_CLASS_PATH."class/object/object.class.php" );
	include_once( PLOG_CLASS_PATH."plugins/atom/class/data/sha1.class.php" );
	
	/**
	 * implements HTTP-based WSSE authentication
	 *
	 * See more information here: http://www.xml.com/pub/a/2003/12/17/dive.html
	 */
	class WSSEAuthentication extends Object
	{

		var $_userName;
		var $_passwordDigest;
		var $_nonce;
		var $_created;
	
		function WSSEAuthentication()
		{
			$this->Object();

			// initialize with some default values
			$this->_userName = "";
			$this->_passwordDigest = "";
			$this->_nonce = "";
			$this->_created = "";
		}
		
		/**
		 * returns true if authentication was successful or false otherwise
		 *
		 * @return True if successful or false otherwise
		 */
		function authenticate()
		{
			// fetch the headers
			$authHeader = $this->getWSSEHeader();
			$this->parseWSSEHeader( $authHeader );

			AtomLogger::Log( "** WSSE Authentication data" );
            AtomLogger::Log( "   Header = $authHeader" );
			AtomLogger::Log( "   Username = ".$this->userName );
			AtomLogger::Log( "   Password Digest = ".$this->passwordDigest );
			AtomLogger::Log( "   Nonce = ".$this->nonce );
			AtomLogger::Log( "   Created = ".$this->created );
			
			// use the app-specific hook for loading application data
			$userData = $this->getUserData( $this->userName );
			if( !$userData ) {
				// if there is no user information in the WSSE header, then we quit
				AtomLogger::Log( "Missing or incorrect username in WSSE header" );
				return false;
			}
			$password = $userData["password"];
			AtomLogger::Log( "Using password = $password" );

			// no authentication... everybody is happy :)
			if( WSSE_AUTHENTICATION_DISABLED ) {
			     AtomLogger::Log( "WARNING: authentication disabled!" );
			     return true;		
			 }

            // fscking hell, it took me forever to figure out how this worked... 			
			$passwordDigest = base64_encode(pack("H*",SHA1::sha1(base64_decode($this->nonce).$this->created.$password)));
			// alternative password digest. Somehow Ecto for OS X will send the nonce in Base64 format while
			// Nokia's Lifeblog will not. Weird, but all we can do is check for both and see who gets it right!
			$passwordDigestAlt = base64_encode(pack("H*",SHA1::sha1($this->nonce.$this->created.$password)));
		
			AtomLogger::Log( "pLog's computed password digest: $passwordDigest" );
			AtomLogger::Log( "pLog's alternative computed password digest: $passwordDigestAlt" );			

			// finally, compare if they are the same or not...
			return(( $passwordDigest == $this->passwordDigest ) || ( $passwordDigestAlt == $this->passwordDigest ));
		}

		/**
		 * retrieves the X-WSSE header from the server
		 * @private
		 */
		function getWSSEHeader()
		{
		    if( function_exists( "getallheaders" )) { 
			     $headers = getallheaders();
			     // the header could be X-WSSE, or X-Wsse or whatever so let's convert them all
			     // to uppercase
			     $headers = array_change_key_case( $headers, CASE_UPPER );			
			     $wsseHeader = $headers["X-WSSE"];
			 }
			 else {
			     // the method above is better but we can still try to use another one, maybe it will
			     // work better!
			     $wsseHeader = stripslashes($_SERVER["HTTP_X_WSSE"]);
			 }

			AtomLogger::Log( $wsseHeader );

			return $wsseHeader;
		}

		/**
		 * splits the WSSE header in its tokens
		 * @private
		 */
		function parseWSSEHeader( $header )
		{
			// this regular expression should help to match the different tokens that we need
			$regexp = '/UsernameToken Username="?([^"]+)"?, *PasswordDigest="?([^"]+)"?, *Nonce="?([^"]+)"?, *Created="?([^"]+)"?$/';

			preg_match( $regexp, $header, $matches );

			// collect the results from preg_match_all
			$this->userName = $matches[1];
			$this->passwordDigest = $matches[2];
			$this->nonce = $matches[3];
			$this->created = $matches[4];
		}
		
		
		/**
		 * This method must be implemented by classes implementing application-specific
		 * authentication. In the case of plog, we will simply use the Users class to find
		 * information related to the user we're trying to authenticate. This class is expecting
		 * an associative array with the fields "username" and "password", nothing more,
		 * nothing else :)
		 *
		 * @param userName the username of the user who is trying to authenticate
		 * @return an associative array with two positions: username and password
		 */
		function getUserData( $userName )
		{
			return( Array( "username" => "", "password" => "" ));
		}

		/**
		 * returns the username that came in the header
		 *
		 * @return The username that was sent in the WSSE authentication header
		 */
		function getUsernameFromAuthentication()
		{
			AtomLogger::Log( "WSSEAuthentication:getUsernameFromAuthentication username = $this->userName" );
			return $this->userName;
		}
	}
?>