From dc1f95d35ace2b8e4b43ca63407a528b7afb090e Mon Sep 17 00:00:00 2001
From: Thomas Pathier <tpxp@live.fr>
Date: Wed, 26 Dec 2018 20:52:42 +0100
Subject: [PATCH] Add a first revision of the project

---
 .gitignore              |   1 +
 LICENCE                 |  47 +++++++++++++
 README.md               |  14 +++-
 html/auth.php           |   9 +++
 html/index.php          |  33 +++++++++
 html/logout.php         |  10 +++
 lib/config.template.php |  37 ++++++++++
 lib/init.php            |  31 +++++++++
 lib/oauth.php           | 148 ++++++++++++++++++++++++++++++++++++++++
 9 files changed, 327 insertions(+), 3 deletions(-)
 create mode 100644 .gitignore
 create mode 100644 LICENCE
 create mode 100644 html/auth.php
 create mode 100644 html/index.php
 create mode 100644 html/logout.php
 create mode 100644 lib/config.template.php
 create mode 100644 lib/init.php
 create mode 100644 lib/oauth.php

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..4867f1f
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1 @@
+example/lib/config.php
diff --git a/LICENCE b/LICENCE
new file mode 100644
index 0000000..164a6d4
--- /dev/null
+++ b/LICENCE
@@ -0,0 +1,47 @@
+                Licence  "Tout le monde sauf ViaRézo" (TLMSVR)
+
+D'après la licence "Anyone but" (AB) et la licence MIT
+
+Copyright (c) 2018 Thomas Pathier
+
+Il est gratuitement permis à toute personne obtenant une copie de ce logiciel
+et de la documentation asssociée (le "Logiciel") ou à qui est fourni ce Logiciel
+d'en bénéficier sans restriction, et ce sans limitation de droits, copie,
+fusion, publication, distribution, sous-licenciement et/ou vente de copies du
+Logiciel, à condition de respecter les conditions suivantes :
+
+La licence ci-dessus et ces informations de permissions doivent être inclues
+dans toutes les copies du Logiciel.
+
+La mention du logiciel ci-dessous doit apparaître sur tous les produits
+utilisant le Logiciel (par exemple dans le footer) :
+"""
+Connexion gentiment fournie par Thomas Pathier (https://tpxp.ddns.net). <3
+"""
+Si la technologie le permet un lien hypertexte vers "https://tpxp.ddns.net" peut
+être créé sur le texte "Thomas Pathier", la mention "(https://tpxp.ddns.net)"
+peut alors être enlevée.
+
+Toutes les altérations du Logiciel mentionnées ci-dessus (notamment copie,
+modification, fusion, publication, distribution, sous-licenciement,...) doivent
+être placées sous licence TLMSVR.
+
+LE LOGICIEL EST FOURNI "EN L'ETAT", SANS AUCUNE GARANTIE EXPRESSE OU IMPLICITE,
+NOTAMMENT MAIS NON LIMITE AUX GARANTIES DE VENDICITE, ADAPTABILITE A UN BESOIN
+PARTICULIER ET RESPECT DES LOIS.
+
+EN AUCUN CAS LE(S) AUTEUR(S) OU PROPRIETAIRE(S) DU COPYRIGHT NE SAURAI(EN)T ETRE
+RESPONSABLE(S) POUR UNE QUELCONQUE PLAINTE, DOMMAGE OU AUTRE RESPONSABILITE,
+QU'ELLE SOIT DANS L'ACTION D'UN CONTRAT, UN TORT OU AUTRE, QU'ELLE RESULTE OU
+NON, OU SOIT EN LIEN AVEC LE LOGICIEL, SON USAGE OU D'AUTRES TRANSACTIONS DANS
+LE LOGICIEL.
+
+EXCEPTIONS
+
+Le Logiciel ne doit pas être utilisé dans un logiciel de l'Association des
+Réseaux de CentraleSupélec (ARCS), également connue de son nom d'usage ViaRézo
+sans accord explicite de l'ayant-droit du Logiciel. Les membres de
+l'association susmentionnée, qu'ils soient actifs, anciennement actifs ou
+futurs membres actifs (également nommés GPAs ou n1As) ne sont pas autorisés à
+utiliser le Logiciel ni à y contribuer sauf autorisation expression de l'ayant
+droit du Logiciel.
diff --git a/README.md b/README.md
index 6e8ec8d..78ae6c9 100644
--- a/README.md
+++ b/README.md
@@ -22,9 +22,17 @@ en `/auth.php`, sans quoi le serveur oAuth refusera la redirection !
 Une fois votre client validé par une personne ayant les droits sur le serveur
 oAuth (ne me demandez pas, je ne les ai plus !), vous pouvez continuer.
 
-... A suivre ...
+### Exemple
+Un exemple d'utilisation de la librairie est dans le dossier `html`. Si vous
+êtes pressé, vous pouvez vous baser dessus !
 
 ## Contributions
 Vos contributions sont les bienvenues ! Il y a notamment un peu de travail
-sur la récupération de données avec des services externes et sur le
-rafraîchissement du token (refresh token).
+sur :
+* la récupération de données avec des services externes (LinkCS)
+* le rafraîchissement du token (refresh token - chemin /refresh)
+* des tests unitaires si ça vous chante
+* PHPDoc pour documenter les fonctions de la librairie
+
+Notez que toutes les contributions sont placées de manière irréversible sous licence TLMSVR
+(voir le fichier `LICENCE`).
\ No newline at end of file
diff --git a/html/auth.php b/html/auth.php
new file mode 100644
index 0000000..43637fb
--- /dev/null
+++ b/html/auth.php
@@ -0,0 +1,9 @@
+<?php
+require('../lib/init.php');
+
+// Perform authentication
+if(!$oauth->checkLogin())
+  $oauth->forceLogin();
+
+// Go back to index or another page if set
+header('Location: /');
diff --git a/html/index.php b/html/index.php
new file mode 100644
index 0000000..5d185a8
--- /dev/null
+++ b/html/index.php
@@ -0,0 +1,33 @@
+<?php
+// Toutes vos pages utilisant la connexion doivent inclure la librairie de connexion
+// oAuth en premier (avant d'afficher quoi que ce soit)
+
+// Ici, init.php s'occupe de la charger et de l'initialiser avec la configuration
+// CONFIG: Consultez init.php pour plus d'infos sur la configuration de la librarie
+require('../lib/init.php');
+
+// On n'impose la connexion que si le paramètre ?login est défini
+if(isset($_GET['login']))
+  // Si votre page nécessite une connexion, ne mettez pas la condition ci-dessus
+  // checkLogin permet de savoir si l'utilisateur est connecté
+  if(!$oauth->checkLogin())
+    // Sur n'importe quelle page, vous pouvez imposer la connexion avec forceLogin()
+    $oauth->forceLogin();
+
+?><!DOCTYPE html>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Bonjour!</title>
+</head>
+<body>
+  <h1>Bonjour !</h1>
+  <?php if(!$oauth->checkLogin()) { ?>
+  	<p>Vous n'avez pas l'air connecté, vous voulez <a href="index.php?login">vous connecter</a> ?</p>
+  <?php } else { ?>
+  	<p>Bonjour, <?php echo $oauth->getUserData('firstName'); ?>
+     <?php echo $oauth->getUserData('lastName'); ?>. Vous voulez <a href="logout.php">vous déconnecter</a> ?
+    </p>
+  <?php } ?>
+</body>
+</html>
diff --git a/html/logout.php b/html/logout.php
new file mode 100644
index 0000000..c11d981
--- /dev/null
+++ b/html/logout.php
@@ -0,0 +1,10 @@
+<?php
+session_start();
+// Pour déconnecter un utilisateur, il suffit de supprimer sa session PHP
+session_destroy();
+// Si vous voulez également déconnecter l'utilisateur de l'oAuth ViaRézo, vous pouvez utiliser le chemin /logout
+// Le paramètre logout_redirect permet de revenir sur votre site après la déconnexion
+require('../lib/config.php');
+header('Location:' . $config_oauth['host'] . '/logout?redirect_logout=' . $config_general['my_url']);
+
+// Sinon, un simple header('Location: /'); suffit pour ramener l'utlisateur à l'accueil
diff --git a/lib/config.template.php b/lib/config.template.php
new file mode 100644
index 0000000..ce35cf7
--- /dev/null
+++ b/lib/config.template.php
@@ -0,0 +1,37 @@
+<?php
+// Configuration générale du site
+// Commencez par copier ce ficher et renommez la copie en config.php puis modifiez-le
+
+/*** Configuration générale ***/
+$config_general = array(
+  'my_url' => 'http://monsite.test', // CONFIG: Mettez l'adresse de votre site (à changer entre votre machine et le site de production)
+);
+
+/*** oAuth ***/
+$config_oauth = array(
+  'clientId'     => 'test', // CONFIG: Mettez votre client id à la place de "test"
+  'clientSecret' => 'test', // CONFIG: Mettez votre client secret à la place de "test"
+  'scope'        => 'default', // Si vous voulez lire des données sur LinkCS, vous pouvez utiliser "linkcs:read linkcs:write"...
+  // Mais assurez-vous que ces scopes sont autorisés pour votre client!
+
+  // Si vous voulez modifier l'adresse à laquelle le serveur oAuth doit renvoyer les utilisateurs connectés, faites-le ici
+  // Notez que vous devrez également changer les URLs de redirection
+  'redirect_uri' => '/auth.php',
+
+  // Normalement, le reste ne bouge pas
+  'host'         => 'https://auth.viarezo.fr',
+  'authURL'      => '/oauth/authorize',
+  'tokenURL'     => '/oauth/token',
+  'userDataURL'  => '/api/user/show/me',
+);
+
+/*** Base de données ***/
+// Généralement, vous mettrez aussi votre configuration de Base de données dans ce fichier... Mais vous faites comme vous voulez !
+/*
+$config_bdd = array(
+  'host' => 'localhost',
+  'name' => 'mon_site',
+  'user' => 'root',
+  'password' =>  'toor'
+);*/
+
diff --git a/lib/init.php b/lib/init.php
new file mode 100644
index 0000000..2c6a89e
--- /dev/null
+++ b/lib/init.php
@@ -0,0 +1,31 @@
+<?php
+// Chargement de la configuration
+// CONFIG: Voir ../lib/config.template.php pour plus d'informations sur la configuration du site
+require(dirname(__FILE__) . '/config.php');
+
+// Chargement de la librairie oAuth
+require(dirname(__FILE__) . '/oauth.php');
+
+// Initialisation de la librarie
+$oauth = new oAuth2(
+  $config_oauth['host'],
+  $config_oauth['clientId'],
+  $config_oauth['clientSecret'],
+  $config_oauth['scope'],
+  $config_oauth['authURL'],
+  $config_oauth['tokenURL'],
+  $config_oauth['userDataURL'],
+  $config_general['my_url'],
+  $config_oauth['redirect_url']
+);
+
+/* Si vous avez une base de données, c'est le moment de l'initialiser!
+$bdd_dsn = 'mysql:dbname=' . $config_bdd['name'] . ';charset=utf8;host=' . $config_bdd['host'];
+$bdd_user = $config_bdd['user'];
+$bdd_password = $config_bdd['password'];
+try {
+  $bdd = new PDO($bdd_dsn, $bdd_user, $bdd_password);
+} catch (PDOException $e) {
+  die('Erreur de connexion à la BDD: ' . $e->getMessage());
+}
+*/
\ No newline at end of file
diff --git a/lib/oauth.php b/lib/oauth.php
new file mode 100644
index 0000000..1d99797
--- /dev/null
+++ b/lib/oauth.php
@@ -0,0 +1,148 @@
+<?php
+/* oAuth2 lib
+ * Copyright (c) 2018 Thomas Pathier
+ * Licence TLMSVR
+ * Une copie de la licence TLMSVR doit être inclue avec ce fichier.
+ * Si vous n'avez pas reçu de copie de la licence TLMSVR, consultez
+ * https://gitlab.viarezo.fr/2016pathiert/libphpoauth
+ */
+
+// Start the PHP Session if needed (used for persistency)
+if(session_status() !== PHP_SESSION_ACTIVE)
+  session_start();
+
+class oAuth2{
+  private $userData = false;
+  private $config = array();
+
+  function __construct($host, $clientId, $clientSecret, $scope, $authURL, $tokenURL, $userDataURL, $myURL, $redirectURL){
+    $this->config['host'] = $host;
+    $this->config['clientId'] = $clientId;
+    $this->config['clientSecret'] = $clientSecret;
+    $this->config['scope'] = $scope;
+    $this->config['authURL'] = $authURL;
+    $this->config['tokenURL'] = $tokenURL;
+    $this->config['userDataURL'] = $userDataURL;
+    $this->config['myURL'] = $myURL;
+    $this->config['redirectURL'] = $redirectURL;
+  }
+
+  function checkLogin(){
+    if(!isset($_SESSION['oauth_userData'], $_SESSION['oauth_access_token'], $_SESSION['oauth_expiration'], $_SESSION['oauth_refresh_token'], $_SESSION['oauth_user_roles']))
+	  return false;
+
+    // Make sure the token has not expired yet
+    if($_SESSION['oauth_expiration'] < time())
+      return false;
+
+    $this->userData = $_SESSION['oauth_userData'];
+    // OK, the user is logged in !
+    return true;
+  }
+
+  function forceLogin(){
+    /* Make sure the user is logged in.
+     * If this function returns, then the user is connected and you can call
+     * oAuth2::get('<something>') to get the value of the JWT
+     */
+    if(substr($_SERVER['REQUEST_URI'], 0, strlen($this->config['redirectURL'])) == $this->config['redirectURL'] && !isset($_GET['perform'])){
+      // This is an oauth callback
+      // Check the state
+      if(!isset($_GET['state']) || $_GET['state'] !== $_SESSION['oauth_state'])
+        exit('Invalid state');
+      // Fetch the oauth token
+      if(!isset($_GET['code']))
+        exit('Missing authorization code');
+      try{
+        $options = array(
+          'http' => array(
+            'header'  => 'Content-type: application/x-www-form-urlencoded',
+            'method'  => 'POST',
+            'content' => http_build_query(array(
+              'grant_type' => 'authorization_code',
+              'code' => $_GET['code'],
+              'redirect_uri' => $this->config['myURL'] . $this->config['redirectURL'],
+              'client_id' => $this->config['clientId'],
+              'client_secret' => $this->config['clientSecret']
+            ))
+          )
+        );
+
+        // Make the POST request to the oAuth Server...
+        $context = stream_context_create($options);
+
+        $result = file_get_contents($this->config['host'] . $this->config['tokenURL'], false, $context);
+
+        if(!$result)
+          die('You are not allowed to use this service. If you think you should, contact ViaRézo.');
+
+        // Parse the response
+        $result = json_decode($result, true);
+        // Save the refresh token
+        $_SESSION['oauth_refresh_token'] = $result['refresh_token'];
+        // ... And the expiration date
+        $_SESSION['oauth_expiration'] = $result['expires_at'];
+
+        // Extract the access token
+        $result = $result['access_token'];
+        $_SESSION['oauth_access_token'] = $result;
+
+        // Extract the roles of the token
+    		$result = explode('.', $result)[1];
+    		$result = base64_decode($result);
+    		$result = json_decode($result, true);
+    		$_SESSION['oauth_user_roles'] = explode(' ', $result['user']['roles']);
+
+        // Fetch some data about the user !
+        $options = array(
+          'http' => array(
+            'header'  => 'Authorization: Bearer ' . $_SESSION['oauth_access_token']
+          )
+        );
+
+        $context = stream_context_create($options);
+
+        $result = file_get_contents($this->config['host'] . $this->config['userDataURL'], false, $context);
+
+        $_SESSION['oauth_userData'] = json_decode($result, true);
+
+        // Parse the data we got, make sure it is correct
+        if(!$this->checkLogin())
+          throw new Exception('Invalid token');
+      } catch(Exception $e){
+        exit('Internal error');
+      }
+
+      // OK, the user logged in !
+	    header('Location: ' . (isset($_SESSION['oauth_redir_url'])?$_SESSION['oauth_redir_url']:'/'));
+      unset($_SESSION['oauth_redir_url']);
+      exit;
+    }
+
+    // Else, initialize the oauth flow
+    $_SESSION['oauth_redir_url'] = $_SERVER['REQUEST_URI'];
+    $state = sha1(rand() . time() . rand());
+    $_SESSION['oauth_state'] = $state;
+    header('Location: ' . $this->config['host'] . $this->config['authURL'] .
+                  '?redirect_uri=' . urlencode($this->config['myURL'] . $this->config['redirectURL']) .
+                  '&client_id=' . $this->config['clientId'] .
+                  '&response_type=code' .
+                  '&state=' . $state .
+                  '&scope=' . $this->config['scope']); // FIXME: Ajouter des scopes ?
+    // Stop the script right here
+    exit;
+  }
+
+  function getUserData($value){
+    if(isset($this->userData[$value]))
+      return $this->userData[$value];
+    return false;
+  }
+
+  function hasRole($role){
+    if(!isset($_SESSION['oauth_user_roles']))
+      return false;
+
+    return in_array($role, $_SESSION['oauth_user_roles']);
+  }
+}
-- 
GitLab