Accéder à Sharepoint avec PHP via SOAP en passant par l’authentification Windows (NTLM)

by Jean-françois JOLY on nov.22, 2010, under Developpement Web

Alors voilà, j’avais un problème tout simple (enfin il semblait simple) à résoudre : accéder aux données de microsoft sharepoint en utilisant PHP.
Facile me direz vous Sourire : il suffit d’y accéder via SOAP avec un webservice et le tour est joué. C’est exactement l’approche que j’ai utilisé, sauf que voilà, à chaque fois : problème d’authentification.

Je n’ai pourtant pas grand chose d’exceptionnel : Un serveur microsoft sharepoint, relié à active directory en authentification windows classique, du php 5.3 en utilisant le client SOAP natif.
Le code devrait être relativement simple pour se connecter : (le wdsl d’un serveur sharepoint est généralement de la forme http://monserveurmoss/monsite/_vti_bin/Lists.asmx?WSDL)
Ce qui nous donnerait donc dans un monde idéal :

$client = new SoapClient("http://monserveurmoss/monsite/_vti_bin/Lists.asmx?WSDL", 
			array(	'soap_version'   => SOAP_1_2,
				'login'          => "some_name",
				'password'       => "some_password"));
$params = array(
	'listName' =>'{0CD81D15-2793-4C63-BEED-1594788A3030}',
	'viewName'=>'{75B8995C-9148-40AA-924E-991684D20015}';
$rawXMLresponse = $client->GetListItems($params)->GetListItemsResult->any;

Donc là normalement, je devrais me connecter tranquillement à ma liste et la vue associée et récupérer des données sous la forme d’un fichier XML. Sauf que pas du tout … Au lieu de ça j’ai une erreur d’authentification, accès refusé.

Après quelques tests, j’en conclus que ce qui ne marche pas c’est l’authentification windows plus connues sous son petit nom de NTLM Tire la langue. Muni de cette information capitale (NTML), je me relance dans la recherche de solutions …

Et après pas mal de recherches, j’ai fini par trouver une solution qui fonctionne : http://rabaix.net/en/articles/2008/03/13/using-soap-php-with-ntlm-authentication

Comme cette page est sous licence creative commons, je me permets d’en copier le code, car sait on jamais, si la source disparait ce serait bien dommage …

Donc, bien que celà semble surprenant, on dirait que PHP n’implémente pas de base la gestion de l’authentification Windows, se coupant donc de pas mal de webservice sous Windows…

La solution proposée est très sympa car à l’aide de quelques surcharges elle permet d’accéder aux fonctionnalités SOAP natives sans utiliser de solutions ultra compliquées :

Une classe qui permet de surcharger SOAPClient en utilisant CURL :

class NTLMSoapClient extends SoapClient {
	function __doRequest($request, $location, $action, $version) {
		$headers = array(
			'Method: POST',
			'Connection: Keep-Alive',
			'User-Agent: PHP-SOAP-CURL',
			'Content-Type: text/xml; charset=utf-8',
			'SOAPAction: "'.$action.'"',
		);
		$this->__last_request_headers = $headers;
		$ch = curl_init($location);
		curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
		curl_setopt($ch, CURLOPT_POST, true );
		curl_setopt($ch, CURLOPT_POSTFIELDS, $request);
		curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
		curl_setopt($ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
		curl_setopt($ch, CURLOPT_USERPWD, MossParameter::MOSS_USER.':'.MossParameter::MOSS_PASSWORD);
		$response = curl_exec($ch);
		return $response;
	}
	function __getLastRequestHeaders() {
		return implode("\n", $this->__last_request_headers)."\n";
	}
}

 

Une autre classe qui permet de redéfinir un “stream wrapper” :

class NTLMStream {
	private $path;
	private $mode;
	private $options;
	private $opened_path;
	private $buffer;
	private $pos;
	/**
	 * Open the stream 
	 *
	 * @param unknown_type $path
	 * @param unknown_type $mode
	 * @param unknown_type $options
	 * @param unknown_type $opened_path
	 * @return unknown
	 */
	public function stream_open($path, $mode, $options, $opened_path) {
		$this->path = $path;
		$this->mode = $mode;
		$this->options = $options;
		$this->opened_path = $opened_path;
		$this->createBuffer($path);
		return true;
	}
	/**
	 * Close the stream
	 *
	 */
	public function stream_close() {
		curl_close($this->ch);
	}
	/**
	 * Read the stream
	 *
	 * @param int $count number of bytes to read
	 * @return content from pos to count
	 */
	public function stream_read($count) {
		if(strlen($this->buffer) == 0) {
			return false;
		}
		$read = substr($this->buffer,$this->pos, $count);
		$this->pos += $count;
		return $read;
	}
	/**
	 * write the stream
	 *
	 * @param int $count number of bytes to read
	 * @return content from pos to count
	 */
	public function stream_write($data) {
		if(strlen($this->buffer) == 0) {
			return false;
		}
		return true;
	}
	/**
	 *
	 * @return true if eof else false
	 */
	public function stream_eof() {
		if($this->pos > strlen($this->buffer)) {
			return true;
		}
		return false;
	}
	/**
	 * @return int the position of the current read pointer
	 */
	public function stream_tell() {
		return $this->pos;
	}
	/**
	 * Flush stream data
	 */
	public function stream_flush() {
		$this->buffer = null;
		$this->pos = null;
	}
	/**
	 * Stat the file, return only the size of the buffer
	 *
	 * @return array stat information
	 */
	public function stream_stat() {
		$this->createBuffer($this->path);
		$stat = array(
			'size' => strlen($this->buffer),
		);
		return $stat;
	}
	/**
	 * Stat the url, return only the size of the buffer
	 *
	 * @return array stat information
	 */
	public function url_stat($path, $flags) {
		$this->createBuffer($path);
		$stat = array(
			'size' => strlen($this->buffer),
		);
 
		return $stat;
	}
	/**
	 * Create the buffer by requesting the url through cURL
	 *
	 * @param unknown_type $path
	 */
	private function createBuffer($path) {
		if($this->buffer) {
			return;
		}
		$this->ch = curl_init($path);
		curl_setopt($this->ch, CURLOPT_RETURNTRANSFER, true);
		curl_setopt($this->ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
		curl_setopt($this->ch, CURLOPT_HTTPAUTH, CURLAUTH_NTLM);
		curl_setopt($this->ch, CURLOPT_USERPWD, MossParameter::MOSS_USER.':'.MossParameter::MOSS_PASSWORD);
		$this->buffer = curl_exec($this->ch);
		$this->pos = 0;
	}
}

Bon j’ai juste fait une petite variation par rapport à l’original en utilisant une classe de constante MossParameter, histoire de ne pas dupliquer le paramétrage user/password.

Une fois ces deux classes faites, il suffit d’accéder à sharepoint très facilement de cette manière :

stream_wrapper_unregister('http');
stream_wrapper_register('http', 'NTLMStream') or die("Failed to register protocol");
$client = new NTLMSoapClient("http://monserveurmoss/monsite/_vti_bin/Lists.asmx?WSDL", 
			array(	'soap_version'   => SOAP_1_2));
$params = array(
	'listName' =>'{0CD81D15-2793-4C63-BEED-1594788A3030}',
	'viewName'=>'{75B8995C-9148-40AA-924E-991684D20015}';
$rawXMLresponse = $client->GetListItems($params)->GetListItemsResult->any;
stream_wrapper_restore('http');

 

Et voilà le tour est joué, plus de problème d’authentification SOAP entre PHP et Windows.

Autre petite précision pour ceux qui se demandent comment obtenir les uid des list et des view de sharepoint : il y a un outil très pratique (Stramit SharePoint 2007 Caml Viewer ) que vous trouverez ici : http://www.codeplex.com/wikipage?ProjectName=SPCamlViewer

A la prochaine pour de nouvelle aventure entre php et sharepoint Sourire.


3 Commentaires pour cet article

A la recherche de quelque chose ?

Utiliser ce champs pour faire votre recherche :

Vous n'avez pas trouvé votre bonheur ? N'hésitez pas à contacter l'auteur de ce blog ou alors laissez un commentaire sur un post !

Les liens !

Quelques liens intéressants...

Les archives

Tout, classé par ordre chronologique ...