Edgard пре 5 година
родитељ
комит
f0cb609a17

+ 1 - 1
composer.lock

@@ -358,5 +358,5 @@
     "platform-overrides": {
     "platform-overrides": {
         "php": "7.1.2"
         "php": "7.1.2"
     },
     },
-    "plugin-api-version": "1.1.0"
+    "plugin-api-version": "2.0.0"
 }
 }

+ 14 - 5
front/callback.php

@@ -7,9 +7,9 @@ ini_set('display_errors', 1);
 ini_set('display_startup_errors', 1);
 ini_set('display_startup_errors', 1);
 error_reporting(E_ALL);
 error_reporting(E_ALL);
 
 
-include ('../../../inc/includes.php');
+include('../../../inc/includes.php');
 
 
-$provider_id = PluginSinglesignonProvider::getCallbackParameters('provider');
+$provider_id = PluginSinglesignonToolbox::getCallbackParameters('provider');
 
 
 if (!$provider_id) {
 if (!$provider_id) {
    Html::displayErrorAndDie(__sso("Provider not defined."), false);
    Html::displayErrorAndDie(__sso("Provider not defined."), false);
@@ -27,24 +27,33 @@ if (!$signon_provider->fields['is_active']) {
 
 
 $signon_provider->checkAuthorization();
 $signon_provider->checkAuthorization();
 
 
-$test = PluginSinglesignonProvider::getCallbackParameters('test');
+$test = PluginSinglesignonToolbox::getCallbackParameters('test');
 
 
 if ($test) {
 if ($test) {
    Html::nullHeader("Login", $CFG_GLPI["root_doc"] . '/index.php');
    Html::nullHeader("Login", $CFG_GLPI["root_doc"] . '/index.php');
    echo '<div class="left spaced">';
    echo '<div class="left spaced">';
    echo '<pre>';
    echo '<pre>';
+   echo "### BEGIN ###\n";
    print_r($signon_provider->getResourceOwner());
    print_r($signon_provider->getResourceOwner());
+   echo "### END ###";
    echo '</pre>';
    echo '</pre>';
    Html::nullFooter();
    Html::nullFooter();
    exit();
    exit();
 }
 }
 
 
+$user_id = Session::getLoginUserID();
 
 
 $REDIRECT = "";
 $REDIRECT = "";
 
 
-if ($signon_provider->login()) {
+if ($user_id || $signon_provider->login()) {
 
 
-   $params = PluginSinglesignonProvider::getCallbackParameters('q');
+   $user_id = $user_id ?: Session::getLoginUserID();
+
+   if ($user_id) {
+      $signon_provider->linkUser($user_id);
+   }
+
+   $params = PluginSinglesignonToolbox::getCallbackParameters('q');
 
 
    $url_redirect = '';
    $url_redirect = '';
 
 

+ 1 - 1
front/picture.send.php

@@ -22,4 +22,4 @@ if (!file_exists($path)) {
    Html::displayErrorAndDie(__('File not found'), true); // Not found
    Html::displayErrorAndDie(__('File not found'), true); // Not found
 }
 }
 
 
-Toolbox::sendFile($path, $logo);
+Toolbox::sendFile($path, "logo.png");

+ 17 - 0
front/preference.form.php

@@ -0,0 +1,17 @@
+<?php
+
+include ('../../../inc/includes.php');
+
+Session::checkLoginUser();
+
+if (isset($_POST["update"])) {
+
+   $prefer = new PluginSinglesignonPreference(Session::getLoginUserID());
+   $prefer->loadProviders();
+
+   $prefer->update($_POST);
+
+   Html::back();
+} else {
+   Html::back();
+}

+ 17 - 0
front/user.form.php

@@ -0,0 +1,17 @@
+<?php
+
+include('../../../inc/includes.php');
+
+Session::checkRight(User::$rightname, UPDATE);
+
+if (isset($_POST["update"]) && isset($_POST["user_id"])) {
+
+   $prefer = new PluginSinglesignonPreference((int) $_POST["user_id"]);
+   $prefer->loadProviders();
+
+   $prefer->update($_POST);
+
+   Html::back();
+} else {
+   Html::back();
+}

+ 15 - 3
hook.php

@@ -20,14 +20,14 @@ function plugin_singlesignon_display_login() {
          $query['redirect'] = $_REQUEST['redirect'];
          $query['redirect'] = $_REQUEST['redirect'];
       }
       }
 
 
-      $url = PluginSinglesignonProvider::getCallbackUrl($row['id'], $query);
-      $html[] = PluginSinglesignonProvider::renderButton($url, $row);
+      $url = PluginSinglesignonToolbox::getCallbackUrl($row['id'], $query);
+      $html[] = PluginSinglesignonToolbox::renderButton($url, $row);
    }
    }
 
 
    if (!empty($html)) {
    if (!empty($html)) {
       echo '<div class="singlesignon-box">';
       echo '<div class="singlesignon-box">';
       echo implode(" \n", $html);
       echo implode(" \n", $html);
-      echo PluginSinglesignonProvider::renderButton('#', ['name' => __('GLPI')], 'vsubmit old-login');
+      echo PluginSinglesignonToolbox::renderButton('#', ['name' => __('GLPI')], 'vsubmit old-login');
       echo '</div>';
       echo '</div>';
       ?>
       ?>
       <style>
       <style>
@@ -181,6 +181,18 @@ function plugin_singlesignon_install() {
                 ADD `color` varchar(7) DEFAULT NULL";
                 ADD `color` varchar(7) DEFAULT NULL";
       $DB->query($query) or die("error adding picture column " . $DB->error());
       $DB->query($query) or die("error adding picture column " . $DB->error());
    }
    }
+   if (version_compare($currentVersion, "1.3.0", '<')) {
+      $query = "CREATE TABLE `glpi_plugin_singlesignon_providers_users` (
+         `id` int(11) NOT NULL AUTO_INCREMENT,
+         `plugin_singlesignon_providers_id` int(11) NOT NULL DEFAULT '0',
+         `users_id` int(11) NOT NULL DEFAULT '0',
+         `remote_id` varchar(255) COLLATE utf8_unicode_ci DEFAULT NULL,
+         PRIMARY KEY (`id`),
+         UNIQUE KEY `unicity` (`plugin_singlesignon_providers_id`,`users_id`),
+         UNIQUE KEY `unicity_remote` (`plugin_singlesignon_providers_id`,`remote_id`)
+       ) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;";
+      $DB->query($query) or die("error creating glpi_plugin_singlesignon_providers_users " . $DB->error());
+   }
 
 
    Config::setConfigurationValues('singlesignon', [
    Config::setConfigurationValues('singlesignon', [
       'version' => PLUGIN_SINGLESIGNON_VERSION,
       'version' => PLUGIN_SINGLESIGNON_VERSION,

+ 211 - 0
inc/preference.class.php

@@ -0,0 +1,211 @@
+<?php
+
+class PluginSinglesignonPreference extends CommonDBTM {
+
+   static protected $notable = true;
+   static $rightname = '';
+
+   // Provider data
+   public $user_id = null;
+   public $providers = [];
+   public $providers_users = [];
+
+   public function __construct($user_id = null) {
+      parent::__construct();
+
+      $this->user_id = $user_id;
+   }
+
+   public function loadProviders() {
+      $signon_provider = new PluginSinglesignonProvider();
+
+      $condition = '`is_active` = 1';
+      if (version_compare(GLPI_VERSION, '9.4', '>=')) {
+         $condition = [$condition];
+      }
+      $this->providers = $signon_provider->find($condition);
+
+      $provider_user = new PluginSinglesignonProvider_User();
+
+      $condition = "`users_id` = {$this->user_id}";
+      if (version_compare(GLPI_VERSION, '9.4', '>=')) {
+         $condition = [$condition];
+      }
+      $this->providers_users = $provider_user->find($condition);
+   }
+
+   public function update(array $input, $history = 1, $options = []) {
+      if (!isset($input['_remove_sso']) || !is_array($input['_remove_sso'])) {
+         return false;
+      }
+
+      $ids = $input['_remove_sso'];
+      if (empty($ids)) {
+         return false;
+      }
+
+      $provider_user = new PluginSinglesignonProvider_User();
+      $condition = "`users_id` = {$this->user_id} AND `id` IN (" . implode(',', $ids) . ")";
+      if (version_compare(GLPI_VERSION, '9.4', '>=')) {
+         $condition = [$condition];
+      }
+
+      $providers_users = $provider_user->find($condition);
+
+      foreach ($providers_users as $pu) {
+         $provider_user->delete($pu);
+      }
+   }
+
+   function getTabNameForItem(CommonGLPI $item, $withtemplate = 0) {
+      switch (get_class($item)) {
+         case 'Preference':
+         case 'User':
+            return [1 => __sso('Single Sign-on')];
+         default:
+            return '';
+      }
+   }
+
+   static function displayTabContentForItem(CommonGLPI $item, $tabnum = 1, $withtemplate = 0) {
+      switch (get_class($item)) {
+         case 'User':
+            $prefer = new self($item->fields['id']);
+            $prefer->loadProviders();
+            $prefer->showFormUser($item);
+            break;
+         case 'Preference':
+            $prefer = new self(Session::getLoginUserID());
+            $prefer->loadProviders();
+            $prefer->showFormPreference($item);
+            break;
+      }
+      return true;
+   }
+
+   function showFormUser(CommonGLPI $item) {
+      global $CFG_GLPI;
+
+      if (!User::canView()) {
+         return false;
+      }
+      $canedit = Session::haveRight(User::$rightname, UPDATE);
+      if ($canedit) {
+         echo "<form name='form' action=\"" . $CFG_GLPI['root_doc'] . "/plugins/singlesignon/front/user.form.php\" method='post'>";
+      }
+      echo Html::hidden('user_id', ['value' => $this->user_id]);
+
+      echo "<div class='center' id='tabsbody'>";
+      echo "<table class='tab_cadre_fixe'>";
+
+      echo "<tr><th colspan='4'>" . __('Settings') . "</th></tr>";
+
+      $this->showFormDefault($item);
+
+      if ($canedit) {
+         echo "<tr class='tab_bg_2'>";
+         echo "<td colspan='4' class='center'>";
+         echo "<input type='submit' name='update' class='submit' value=\"" . _sx('button', 'Save') . "\">";
+         echo "</td></tr>";
+      }
+
+      echo "</table></div>";
+      Html::closeForm();
+   }
+
+   function showFormPreference(CommonGLPI $item) {
+      $user = new User();
+      if (!$user->can($this->user_id, READ) && ($this->user_id != Session::getLoginUserID())) {
+         return false;
+      }
+      $canedit = $this->user_id == Session::getLoginUserID();
+
+      if ($canedit) {
+         echo "<form name='form' action=\"" . Toolbox::getItemTypeFormURL(__CLASS__) . "\" method='post'>";
+      }
+
+      echo "<div class='center' id='tabsbody'>";
+      echo "<table class='tab_cadre_fixe'>";
+
+      echo "<tr><th colspan='4'>" . __('Settings') . "</th></tr>";
+
+      $this->showFormDefault($item);
+
+      if ($canedit) {
+         echo "<tr class='tab_bg_2'>";
+         echo "<td colspan='4' class='center'>";
+         echo "<input type='submit' name='update' class='submit' value=\"" . _sx('button', 'Save') . "\">";
+         echo "</td></tr>";
+      }
+
+      echo "</table></div>";
+      Html::closeForm();
+   }
+
+   function showFormDefault(CommonGLPI $item) {
+      echo "<tr class='tab_bg_2'>";
+      echo "<td> " . __sso('Single Sign-on Provider') . "</td><td>";
+
+      foreach ($this->providers as $p) {
+         switch (get_class($item)) {
+            case 'User':
+               $redirect = $item->getFormURLWithID($this->user_id, true);
+               break;
+            case 'Preference':
+               $redirect = $item->getSearchURL(false);
+               break;
+            default:
+               $redirect = '';
+         }
+
+         $url = PluginSinglesignonToolbox::getCallbackUrl($p['id'], ['redirect' => $redirect]);
+
+         echo PluginSinglesignonToolbox::renderButton($url, $p);
+         echo " ";
+      }
+
+      echo "</td></tr>";
+
+      echo "<tr class='tab_bg_2'>";
+
+      if (!empty($this->providers_users)) {
+         echo "<tr><th colspan='2'>" . __sso('Linked accounts') . "</th></tr>";
+
+         foreach ($this->providers_users as $pu) {
+            /** @var PluginSinglesignonProvider */
+            $provider = PluginSinglesignonProvider::getById($pu['plugin_singlesignon_providers_id']);
+
+            echo "<tr><td>";
+            echo $provider->fields['name'] . ' (ID:' . $pu['remote_id'] . ')';
+            echo "</td><td>";
+            echo Html::getCheckbox([
+               'title' => __('Clear'),
+               'name'  => "_remove_sso[]",
+               'value'  => $pu['id'],
+            ]);
+            echo "&nbsp;" . __('Clear');
+            echo "</td></tr>";
+         }
+      }
+
+      ?>
+      <script type="text/javascript">
+         $(document).ready(function() {
+
+            // On click, open a popup
+            $(document).on("click", ".singlesignon.oauth-login", function(e) {
+               e.preventDefault();
+
+               var url = $(this).attr("href");
+               var left = ($(window).width() / 2) - (600 / 2);
+               var top = ($(window).height() / 2) - (800 / 2);
+               var newWindow = window.open(url, "singlesignon", "width=600,height=800,left=" + left + ",top=" + top);
+               if (window.focus) {
+                  newWindow.focus();
+               }
+            });
+         });
+      </script>
+      <?php
+   }
+}

+ 65 - 145
inc/provider.class.php

@@ -180,7 +180,7 @@ class PluginSinglesignonProvider extends CommonDBTM {
       echo "<td>" . __('Picture') . "</td>";
       echo "<td>" . __('Picture') . "</td>";
       echo "<td colspan='3'>";
       echo "<td colspan='3'>";
       if (!empty($this->fields['picture'])) {
       if (!empty($this->fields['picture'])) {
-         echo Html::image(static::getPictureUrl($this->fields['picture']), [
+         echo Html::image(PluginSinglesignonToolbox::getPictureUrl($this->fields['picture']), [
             'style' => '
             'style' => '
                max-width: 100px;
                max-width: 100px;
                max-height: 100px;
                max-height: 100px;
@@ -218,7 +218,7 @@ class PluginSinglesignonProvider extends CommonDBTM {
          echo "<th colspan='4'>" . __('Test') . "</th>";
          echo "<th colspan='4'>" . __('Test') . "</th>";
          echo "</tr>\n";
          echo "</tr>\n";
 
 
-         $url = self::getCallbackUrl($ID);
+         $url = PluginSinglesignonToolbox::getCallbackUrl($ID);
          $fullUrl = $this->getBaseURL() . $url;
          $fullUrl = $this->getBaseURL() . $url;
          echo "<tr class='tab_bg_1'>";
          echo "<tr class='tab_bg_1'>";
          echo "<td>" . __sso('Callback URL') . "</td>";
          echo "<td>" . __sso('Callback URL') . "</td>";
@@ -258,7 +258,12 @@ class PluginSinglesignonProvider extends CommonDBTM {
    }
    }
 
 
    function cleanDBonPurge() {
    function cleanDBonPurge() {
-      static::deletePicture($this->fields['picture']);
+      PluginSinglesignonToolbox::deletePicture($this->fields['picture']);
+      $this->deleteChildrenAndRelationsFromDb(
+         [
+            'PluginSinglesignonProvider_User',
+         ]
+      );
    }
    }
 
 
    /**
    /**
@@ -344,21 +349,21 @@ class PluginSinglesignonProvider extends CommonDBTM {
          $input['picture'] = '';
          $input['picture'] = '';
 
 
          if (array_key_exists('picture', $this->fields)) {
          if (array_key_exists('picture', $this->fields)) {
-            static::deletePicture($this->fields['picture']);
+            PluginSinglesignonToolbox::deletePicture($this->fields['picture']);
          }
          }
       }
       }
 
 
       if (isset($input["_picture"])) {
       if (isset($input["_picture"])) {
          $picture = array_shift($input["_picture"]);
          $picture = array_shift($input["_picture"]);
 
 
-         if ($dest = static::savePicture(GLPI_TMP_DIR . '/' . $picture)) {
+         if ($dest = PluginSinglesignonToolbox::savePicture(GLPI_TMP_DIR . '/' . $picture)) {
             $input['picture'] = $dest;
             $input['picture'] = $dest;
          } else {
          } else {
             Session::addMessageAfterRedirect(__('Unable to save picture file.'), true, ERROR);
             Session::addMessageAfterRedirect(__('Unable to save picture file.'), true, ERROR);
          }
          }
 
 
          if (array_key_exists('picture', $this->fields)) {
          if (array_key_exists('picture', $this->fields)) {
-            static::deletePicture($this->fields['picture']);
+            PluginSinglesignonToolbox::deletePicture($this->fields['picture']);
          }
          }
       }
       }
 
 
@@ -1021,6 +1026,34 @@ class PluginSinglesignonProvider extends CommonDBTM {
          return $user;
          return $user;
       }
       }
 
 
+      $remote_id = false;
+      $remote_id_fields = ['id', 'username'];
+
+      foreach ($remote_id_fields as $field) {
+         if (isset($resource_array[$field]) && !empty($resource_array[$field])) {
+            $remote_id = $resource_array[$field];
+            break;
+         }
+      }
+
+      if ($remote_id) {
+         $link = new PluginSinglesignonProvider_User();
+         $condition = "`remote_id` = '{$remote_id}' AND `plugin_singlesignon_providers_id` = {$this->fields['id']}";
+         if (version_compare(GLPI_VERSION, '9.4', '>=')) {
+            $condition = [$condition];
+         }
+         $links = $link->find($condition);
+         if (!empty($links) && $first = reset($links)) {
+            $id = $first['users_id'];
+         }
+
+         $remote_id;
+      }
+
+      if (is_numeric($id) && $user->getFromDB($id)) {
+         return $user;
+      }
+
       $email = false;
       $email = false;
       $email_fields = ['email', 'e-mail', 'email-address', 'mail'];
       $email_fields = ['email', 'e-mail', 'email-address', 'mail'];
 
 
@@ -1078,158 +1111,45 @@ class PluginSinglesignonProvider extends CommonDBTM {
       return $auth->auth_succeded;
       return $auth->auth_succeded;
    }
    }
 
 
-   /**
-    * Generate a URL to callback
-    * Some providers don't accept query string, it convert to PATH
-    * @global array $CFG_GLPI
-    * @param integer $id
-    * @param array $query
-    * @return string
-    */
-   public static function getCallbackUrl($id, $query = []) {
-      global $CFG_GLPI;
-
-      $url = $CFG_GLPI['root_doc'] . '/plugins/singlesignon/front/callback.php';
-
-      $url .= "/provider/$id";
-
-      if (!empty($query)) {
-         $url .= "/q/" . base64_encode(http_build_query($query));
-      }
-
-      return $url;
-   }
-
-   public static function getCallbackParameters($name = null) {
-      $data = [];
-
-      if (isset($_SERVER['PATH_INFO'])) {
-         $path_info = trim($_SERVER['PATH_INFO'], '/');
-
-         $parts = explode('/', $path_info);
-
-         $key = null;
-
-         foreach ($parts as $part) {
-            if ($key === null) {
-               $key = $part;
-            } else {
-               if ($key === "provider" || $key === "test") {
-                  $part = intval($part);
-               } else {
-                  $tmp = base64_decode($part);
-                  parse_str($tmp, $part);
-               }
-
-               if ($key === $name) {
-                  return $part;
-               }
-
-               $data[$key] = $part;
-               $key = null;
-            }
-         }
-      }
-
-      if (!isset($data[$name])) {
-         return null;
-      }
-
-      return $data;
-   }
-
-   static public function startsWith($haystack, $needle) {
-      $length = strlen($needle);
-      return (substr($haystack, 0, $length) === $needle);
-   }
-
-   static function getPictureUrl($path) {
-      global $CFG_GLPI;
-
-      $path = Html::cleanInputText($path); // prevent xss
-
-      if (empty($path)) {
-         return null;
-      }
-
-      return $CFG_GLPI['root_doc'] . '/plugins/singlesignon/front/picture.send.php?path=' . $path;
-   }
-
-   static public function savePicture($src, $uniq_prefix = null) {
-
-      if (function_exists('Document::isImage') && !Document::isImage($src)) {
-         return false;
+   public function linkUser($user_id) {
+      /** @var User */
+      $user = User::getById($user_id);
+      if (!$user) {
+         return;
       }
       }
 
 
-      $filename     = uniqid($uniq_prefix);
-      $ext          = pathinfo($src, PATHINFO_EXTENSION);
-      $subdirectory = substr($filename, -2); // subdirectory based on last 2 hex digit
-
-      $basePath = GLPI_PLUGIN_DOC_DIR . "/singlesignon";
-      $i = 0;
-      do {
-         // Iterate on possible suffix while dest exists.
-         // This case will almost never exists as dest is based on an unique id.
-         $dest = $basePath
-         . '/' . $subdirectory
-         . '/' . $filename . ($i > 0 ? '_' . $i : '') . '.' . $ext;
-         $i++;
-      } while (file_exists($dest));
-
-      if (!is_dir($basePath . '/' . $subdirectory) && !mkdir($basePath . '/' . $subdirectory)) {
-         return false;
-      }
+      $resource_array = $this->getResourceOwner();
 
 
-      if (!rename($src, $dest)) {
+      if (!$resource_array) {
          return false;
          return false;
       }
       }
 
 
-      return substr($dest, strlen($basePath . '/')); // Return dest relative to GLPI_PICTURE_DIR
-   }
-
-   public static function deletePicture($path) {
-      $basePath = GLPI_PLUGIN_DOC_DIR . "/singlesignon";
-      $fullpath = $basePath . '/' . $path;
+      $remote_id = false;
+      $id_fields = ['id', 'sub', 'username'];
 
 
-      if (!file_exists($fullpath)) {
-         return false;
+      foreach ($id_fields as $field) {
+         if (isset($resource_array[$field]) && !empty($resource_array[$field])) {
+            $remote_id = $resource_array[$field];
+            break;
+         }
       }
       }
 
 
-      $fullpath = realpath($fullpath);
-      if (!static::startsWith($fullpath, realpath($basePath))) {
+      if (!$remote_id) {
          return false;
          return false;
       }
       }
 
 
-      return @unlink($fullpath);
-   }
-
-   public static function renderButton($url, $data, $class = 'oauth-login') {
-      $btn = '<span><a href="' . $url . '" class="singlesignon vsubmit ' . $class . '"';
+      $link = new PluginSinglesignonProvider_User();
 
 
-      $style = '';
-      if ((isset($data['bgcolor']) && $data['bgcolor'])) {
-         $style .= 'background-color: ' . $data['bgcolor'] . ';';
-      }
-      if ((isset($data['color']) && $data['color'])) {
-         $style .= 'color: ' . $data['color'] . ';';
-      }
-      if ($style) {
-         $btn .= ' style="' . $style . '"';
-      }
-      $btn .= '>';
-
-      if (isset($data['picture']) && $data['picture']) {
-         $btn .= Html::image(
-            static::getPictureUrl($data['picture']),
-            [
-               'style' => 'max-height: 20px;',
-            ]
-         );
-         $btn .= ' ';
-      }
+      // Unlink from another user
+      $link->deleteByCriteria([
+         'plugin_singlesignon_providers_id' => $this->fields['id'],
+         'remote_id' => $remote_id,
+      ]);
 
 
-      $btn .= sprintf(__sso('Login with %s'), $data['name']);
-      $btn .= '</a></span>';
-      return $btn;
+      return $link->add([
+         'plugin_singlesignon_providers_id' => $this->fields['id'],
+         'users_id' => $user_id,
+         'remote_id' => $remote_id,
+      ]);
    }
    }
 }
 }

+ 11 - 0
inc/provider_user.class.php

@@ -0,0 +1,11 @@
+<?php
+
+class PluginSinglesignonProvider_User extends CommonDBRelation {
+
+   // From CommonDBRelation
+   static public $itemtype_1   = 'PluginSinglesignonProvider';
+   static public $items_id_1   = 'plugin_singlesignon_providers_id';
+
+   static public $itemtype_2 = 'User';
+   static public $items_id_2 = 'users_id';
+}

+ 158 - 0
inc/toolbox.class.php

@@ -0,0 +1,158 @@
+<?php
+
+class PluginSinglesignonToolbox {
+   /**
+    * Generate a URL to callback
+    * Some providers don't accept query string, it convert to PATH
+    * @global array $CFG_GLPI
+    * @param integer $id
+    * @param array $query
+    * @return string
+    */
+   public static function getCallbackUrl($id, $query = []) {
+      global $CFG_GLPI;
+
+      $url = $CFG_GLPI['root_doc'] . '/plugins/singlesignon/front/callback.php';
+
+      $url .= "/provider/$id";
+
+      if (!empty($query)) {
+         $url .= "/q/" . base64_encode(http_build_query($query));
+      }
+
+      return $url;
+   }
+
+   public static function getCallbackParameters($name = null) {
+      $data = [];
+
+      if (isset($_SERVER['PATH_INFO'])) {
+         $path_info = trim($_SERVER['PATH_INFO'], '/');
+
+         $parts = explode('/', $path_info);
+
+         $key = null;
+
+         foreach ($parts as $part) {
+            if ($key === null) {
+               $key = $part;
+            } else {
+               if ($key === "provider" || $key === "test") {
+                  $part = intval($part);
+               } else {
+                  $tmp = base64_decode($part);
+                  parse_str($tmp, $part);
+               }
+
+               if ($key === $name) {
+                  return $part;
+               }
+
+               $data[$key] = $part;
+               $key = null;
+            }
+         }
+      }
+
+      if (!isset($data[$name])) {
+         return null;
+      }
+
+      return $data;
+   }
+
+   static public function startsWith($haystack, $needle) {
+      $length = strlen($needle);
+      return (substr($haystack, 0, $length) === $needle);
+   }
+
+   static function getPictureUrl($path) {
+      global $CFG_GLPI;
+
+      $path = Html::cleanInputText($path); // prevent xss
+
+      if (empty($path)) {
+         return null;
+      }
+
+      return $CFG_GLPI['root_doc'] . '/plugins/singlesignon/front/picture.send.php?path=' . $path;
+   }
+
+   static public function savePicture($src, $uniq_prefix = null) {
+
+      if (function_exists('Document::isImage') && !Document::isImage($src)) {
+         return false;
+      }
+
+      $filename     = uniqid($uniq_prefix);
+      $ext          = pathinfo($src, PATHINFO_EXTENSION);
+      $subdirectory = substr($filename, -2); // subdirectory based on last 2 hex digit
+
+      $basePath = GLPI_PLUGIN_DOC_DIR . "/singlesignon";
+      $i = 0;
+      do {
+         // Iterate on possible suffix while dest exists.
+         // This case will almost never exists as dest is based on an unique id.
+         $dest = $basePath
+         . '/' . $subdirectory
+         . '/' . $filename . ($i > 0 ? '_' . $i : '') . '.' . $ext;
+         $i++;
+      } while (file_exists($dest));
+
+      if (!is_dir($basePath . '/' . $subdirectory) && !mkdir($basePath . '/' . $subdirectory)) {
+         return false;
+      }
+
+      if (!rename($src, $dest)) {
+         return false;
+      }
+
+      return substr($dest, strlen($basePath . '/')); // Return dest relative to GLPI_PICTURE_DIR
+   }
+
+   public static function deletePicture($path) {
+      $basePath = GLPI_PLUGIN_DOC_DIR . "/singlesignon";
+      $fullpath = $basePath . '/' . $path;
+
+      if (!file_exists($fullpath)) {
+         return false;
+      }
+
+      $fullpath = realpath($fullpath);
+      if (!static::startsWith($fullpath, realpath($basePath))) {
+         return false;
+      }
+
+      return @unlink($fullpath);
+   }
+
+   public static function renderButton($url, $data, $class = 'oauth-login') {
+      $btn = '<span><a href="' . $url . '" class="singlesignon vsubmit ' . $class . '"';
+
+      $style = '';
+      if ((isset($data['bgcolor']) && $data['bgcolor'])) {
+         $style .= 'background-color: ' . $data['bgcolor'] . ';';
+      }
+      if ((isset($data['color']) && $data['color'])) {
+         $style .= 'color: ' . $data['color'] . ';';
+      }
+      if ($style) {
+         $btn .= ' style="' . $style . '"';
+      }
+      $btn .= '>';
+
+      if (isset($data['picture']) && $data['picture']) {
+         $btn .= Html::image(
+            static::getPictureUrl($data['picture']),
+            [
+               'style' => 'max-height: 20px;',
+            ]
+         );
+         $btn .= ' ';
+      }
+
+      $btn .= sprintf(__sso('Login with %s'), $data['name']);
+      $btn .= '</a></span>';
+      return $btn;
+   }
+}

+ 42 - 37
locales/en_GB.po

@@ -5,11 +5,11 @@
 #
 #
 msgid ""
 msgid ""
 msgstr ""
 msgstr ""
-"Project-Id-Version: singlesignon 1.0.0\n"
+"Project-Id-Version: singlesignon 1.3.0\n"
 "Report-Msgid-Bugs-To: https://github.com/edgardmessias/glpi-singlesignon/"
 "Report-Msgid-Bugs-To: https://github.com/edgardmessias/glpi-singlesignon/"
 "issues\n"
 "issues\n"
-"POT-Creation-Date: 2021-01-12 13:25-0300\n"
-"PO-Revision-Date: 2021-01-12 13:25-0300\n"
+"POT-Creation-Date: 2021-01-20 14:48-0300\n"
+"PO-Revision-Date: 2021-01-20 14:48-0300\n"
 "Last-Translator: Automatically generated\n"
 "Last-Translator: Automatically generated\n"
 "Language-Team: none\n"
 "Language-Team: none\n"
 "Language: en_GB\n"
 "Language: en_GB\n"
@@ -18,22 +18,18 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 "Plural-Forms: nplurals=2; plural=(n != 1);\n"
 
 
-#: hook.php:27 hook.php:75
-#, php-format
-msgid "Login with %s"
-msgstr "Login with %s"
-
 #: setup.php:8
 #: setup.php:8
 #, php-format
 #, php-format
 msgid "Please, rename the plugin folder \"%s\" to \"singlesignon\""
 msgid "Please, rename the plugin folder \"%s\" to \"singlesignon\""
 msgstr "Please, rename the plugin folder \"%s\" to \"singlesignon\""
 msgstr "Please, rename the plugin folder \"%s\" to \"singlesignon\""
 
 
-#: setup.php:36 front/provider.form.php:59 front/provider.form.php:61
-#: front/provider.php:6 front/provider.php:8 inc/provider.class.php:57
+#: setup.php:40 front/provider.form.php:59 front/provider.form.php:61
+#: front/provider.php:6 front/provider.php:8 inc/preference.class.php:64
+#: inc/provider.class.php:57
 msgid "Single Sign-on"
 msgid "Single Sign-on"
 msgstr "Single Sign-on"
 msgstr "Single Sign-on"
 
 
-#: setup.php:53
+#: setup.php:57
 msgid "This plugin requires GLPI >= 0.85"
 msgid "This plugin requires GLPI >= 0.85"
 msgstr "This plugin requires GLPI >= 0.85"
 msgstr "This plugin requires GLPI >= 0.85"
 
 
@@ -49,111 +45,120 @@ msgstr "Provider not found."
 msgid "Provider not active."
 msgid "Provider not active."
 msgstr "Provider not active."
 msgstr "Provider not active."
 
 
-#: inc/provider.class.php:50
+#: inc/preference.class.php:147 inc/provider.class.php:50
 msgid "Single Sign-on Provider"
 msgid "Single Sign-on Provider"
 msgstr "Single Sign-on Provider"
 msgstr "Single Sign-on Provider"
 
 
+#: inc/preference.class.php:172
+msgid "Linked accounts"
+msgstr "Linked accounts"
+
 #: inc/provider.class.php:98
 #: inc/provider.class.php:98
 msgid "SSO Type"
 msgid "SSO Type"
 msgstr "SSO Type"
 msgstr "SSO Type"
 
 
-#: inc/provider.class.php:106 inc/provider.class.php:298
+#: inc/provider.class.php:106 inc/provider.class.php:426
 msgid "Client ID"
 msgid "Client ID"
 msgstr "Client ID"
 msgstr "Client ID"
 
 
-#: inc/provider.class.php:108 inc/provider.class.php:306
+#: inc/provider.class.php:108 inc/provider.class.php:434
 msgid "Client Secret"
 msgid "Client Secret"
 msgstr "Client Secret"
 msgstr "Client Secret"
 
 
-#: inc/provider.class.php:113 inc/provider.class.php:314
+#: inc/provider.class.php:113 inc/provider.class.php:442
 msgid "Scope"
 msgid "Scope"
 msgstr "Scope"
 msgstr "Scope"
 
 
-#: inc/provider.class.php:115 inc/provider.class.php:322
+#: inc/provider.class.php:115 inc/provider.class.php:450
 msgid "Extra Options"
 msgid "Extra Options"
 msgstr "Extra Options"
 msgstr "Extra Options"
 
 
-#: inc/provider.class.php:126 inc/provider.class.php:330
+#: inc/provider.class.php:126 inc/provider.class.php:458
 msgid "Authorize URL"
 msgid "Authorize URL"
 msgstr "Authorize URL"
 msgstr "Authorize URL"
 
 
-#: inc/provider.class.php:131 inc/provider.class.php:338
+#: inc/provider.class.php:131 inc/provider.class.php:466
 msgid "Access Token URL"
 msgid "Access Token URL"
 msgstr "Access Token URL"
 msgstr "Access Token URL"
 
 
-#: inc/provider.class.php:136 inc/provider.class.php:346
+#: inc/provider.class.php:136 inc/provider.class.php:474
 msgid "Resource Owner Details URL"
 msgid "Resource Owner Details URL"
 msgstr "Resource Owner Details URL"
 msgstr "Resource Owner Details URL"
 
 
-#: inc/provider.class.php:144
+#: inc/provider.class.php:224
 msgid "Callback URL"
 msgid "Callback URL"
 msgstr "Callback URL"
 msgstr "Callback URL"
 
 
-#: inc/provider.class.php:148
+#: inc/provider.class.php:228
 msgid "Test Single Sign-on"
 msgid "Test Single Sign-on"
 msgstr "Test Single Sign-on"
 msgstr "Test Single Sign-on"
 
 
-#: inc/provider.class.php:197
+#: inc/provider.class.php:286
 msgid "A Name is required"
 msgid "A Name is required"
 msgstr "A Name is required"
 msgstr "A Name is required"
 
 
-#: inc/provider.class.php:203
+#: inc/provider.class.php:292
 #, php-format
 #, php-format
 msgid "The \"%s\" is a Invalid type"
 msgid "The \"%s\" is a Invalid type"
 msgstr "The \"%s\" is a Invalid type"
 msgstr "The \"%s\" is a Invalid type"
 
 
-#: inc/provider.class.php:207
+#: inc/provider.class.php:296
 msgid "A Client ID is required"
 msgid "A Client ID is required"
 msgstr "A Client ID is required"
 msgstr "A Client ID is required"
 
 
-#: inc/provider.class.php:211
+#: inc/provider.class.php:300
 msgid "A Client Secret is required"
 msgid "A Client Secret is required"
 msgstr "A Client Secret is required"
 msgstr "A Client Secret is required"
 
 
-#: inc/provider.class.php:216
+#: inc/provider.class.php:305
 msgid "An Authorize URL is required"
 msgid "An Authorize URL is required"
 msgstr "An Authorize URL is required"
 msgstr "An Authorize URL is required"
 
 
-#: inc/provider.class.php:218
+#: inc/provider.class.php:307
 msgid "The Authorize URL is invalid"
 msgid "The Authorize URL is invalid"
 msgstr "The Authorize URL is invalid"
 msgstr "The Authorize URL is invalid"
 
 
-#: inc/provider.class.php:222
+#: inc/provider.class.php:311
 msgid "An Access Token URL is required"
 msgid "An Access Token URL is required"
 msgstr "An Access Token URL is required"
 msgstr "An Access Token URL is required"
 
 
-#: inc/provider.class.php:224
+#: inc/provider.class.php:313
 msgid "The Access Token URL is invalid"
 msgid "The Access Token URL is invalid"
 msgstr "The Access Token URL is invalid"
 msgstr "The Access Token URL is invalid"
 
 
-#: inc/provider.class.php:228
+#: inc/provider.class.php:317
 msgid "A Resource Owner Details URL is required"
 msgid "A Resource Owner Details URL is required"
 msgstr "A Resource Owner Details URL is required"
 msgstr "A Resource Owner Details URL is required"
 
 
-#: inc/provider.class.php:230
+#: inc/provider.class.php:319
 msgid "The Resource Owner Details URL is invalid"
 msgid "The Resource Owner Details URL is invalid"
 msgstr "The Resource Owner Details URL is invalid"
 msgstr "The Resource Owner Details URL is invalid"
 
 
-#: inc/provider.class.php:405
+#: inc/provider.class.php:533
 msgid "Generic"
 msgid "Generic"
 msgstr "Generic"
 msgstr "Generic"
 
 
-#: inc/provider.class.php:406
+#: inc/provider.class.php:534
 msgid "Facebook"
 msgid "Facebook"
 msgstr "Facebook"
 msgstr "Facebook"
 
 
-#: inc/provider.class.php:407
+#: inc/provider.class.php:535
 msgid "GitHub"
 msgid "GitHub"
 msgstr "GitHub"
 msgstr "GitHub"
 
 
-#: inc/provider.class.php:408
+#: inc/provider.class.php:536
 msgid "Google"
 msgid "Google"
 msgstr "Google"
 msgstr "Google"
 
 
-#: inc/provider.class.php:409
+#: inc/provider.class.php:537
 msgid "Instagram"
 msgid "Instagram"
 msgstr "Instagram"
 msgstr "Instagram"
 
 
-#: inc/provider.class.php:410
+#: inc/provider.class.php:538
 msgid "LinkdeIn"
 msgid "LinkdeIn"
 msgstr "LinkdeIn"
 msgstr "LinkdeIn"
+
+#: inc/toolbox.class.php:154
+#, php-format
+msgid "Login with %s"
+msgstr "Login with %s"

+ 45 - 41
locales/pt_BR.po

@@ -8,7 +8,7 @@ msgstr ""
 "Project-Id-Version: singlesignon 1.0.0\n"
 "Project-Id-Version: singlesignon 1.0.0\n"
 "Report-Msgid-Bugs-To: https://github.com/edgardmessias/glpi-singlesignon/"
 "Report-Msgid-Bugs-To: https://github.com/edgardmessias/glpi-singlesignon/"
 "issues\n"
 "issues\n"
-"POT-Creation-Date: 2021-01-12 13:25-0300\n"
+"POT-Creation-Date: 2021-01-20 14:48-0300\n"
 "PO-Revision-Date: 2019-04-26 11:04-0300\n"
 "PO-Revision-Date: 2019-04-26 11:04-0300\n"
 "Last-Translator: Edgard Lorraine Messias <edgardmessias@gmail.com>\n"
 "Last-Translator: Edgard Lorraine Messias <edgardmessias@gmail.com>\n"
 "Language-Team: none\n"
 "Language-Team: none\n"
@@ -18,22 +18,18 @@ msgstr ""
 "Content-Transfer-Encoding: 8bit\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 "Plural-Forms: nplurals=2; plural=(n > 1);\n"
 
 
-#: hook.php:27 hook.php:75
-#, php-format
-msgid "Login with %s"
-msgstr "Entrar com %s"
-
 #: setup.php:8
 #: setup.php:8
 #, php-format
 #, php-format
 msgid "Please, rename the plugin folder \"%s\" to \"singlesignon\""
 msgid "Please, rename the plugin folder \"%s\" to \"singlesignon\""
 msgstr "Por favor, renomeie a pasta do plugin \"%s\" para \"singlesignon\""
 msgstr "Por favor, renomeie a pasta do plugin \"%s\" para \"singlesignon\""
 
 
-#: setup.php:36 front/provider.form.php:59 front/provider.form.php:61
-#: front/provider.php:6 front/provider.php:8 inc/provider.class.php:57
+#: setup.php:40 front/provider.form.php:59 front/provider.form.php:61
+#: front/provider.php:6 front/provider.php:8 inc/preference.class.php:64
+#: inc/provider.class.php:57
 msgid "Single Sign-on"
 msgid "Single Sign-on"
-msgstr "Single Sign-on"
+msgstr "Logon Único"
 
 
-#: setup.php:53
+#: setup.php:57
 msgid "This plugin requires GLPI >= 0.85"
 msgid "This plugin requires GLPI >= 0.85"
 msgstr "Este plugin requer GLPI >= 0.85"
 msgstr "Este plugin requer GLPI >= 0.85"
 
 
@@ -49,115 +45,123 @@ msgstr "Provedor não encontrado."
 msgid "Provider not active."
 msgid "Provider not active."
 msgstr "Provedor não ativo."
 msgstr "Provedor não ativo."
 
 
-#: inc/provider.class.php:50
+#: inc/preference.class.php:147 inc/provider.class.php:50
 msgid "Single Sign-on Provider"
 msgid "Single Sign-on Provider"
-msgstr "Provedor Single Sign-on"
+msgstr "Provedor de Logon Único"
+
+#: inc/preference.class.php:172
+msgid "Linked accounts"
+msgstr "Contas vinculadas"
 
 
 #: inc/provider.class.php:98
 #: inc/provider.class.php:98
 msgid "SSO Type"
 msgid "SSO Type"
 msgstr "Tipo SSO"
 msgstr "Tipo SSO"
 
 
-#: inc/provider.class.php:106 inc/provider.class.php:298
+#: inc/provider.class.php:106 inc/provider.class.php:426
 msgid "Client ID"
 msgid "Client ID"
 msgstr "ID de Cliente"
 msgstr "ID de Cliente"
 
 
-#: inc/provider.class.php:108 inc/provider.class.php:306
+#: inc/provider.class.php:108 inc/provider.class.php:434
 msgid "Client Secret"
 msgid "Client Secret"
 msgstr "Segredo do Cliente"
 msgstr "Segredo do Cliente"
 
 
-#: inc/provider.class.php:113 inc/provider.class.php:314
+#: inc/provider.class.php:113 inc/provider.class.php:442
 msgid "Scope"
 msgid "Scope"
 msgstr "Escopo"
 msgstr "Escopo"
 
 
-#: inc/provider.class.php:115 inc/provider.class.php:322
+#: inc/provider.class.php:115 inc/provider.class.php:450
 msgid "Extra Options"
 msgid "Extra Options"
 msgstr "Opções Extras"
 msgstr "Opções Extras"
 
 
-#: inc/provider.class.php:126 inc/provider.class.php:330
+#: inc/provider.class.php:126 inc/provider.class.php:458
 msgid "Authorize URL"
 msgid "Authorize URL"
 msgstr "URL de Autorização"
 msgstr "URL de Autorização"
 
 
-#: inc/provider.class.php:131 inc/provider.class.php:338
+#: inc/provider.class.php:131 inc/provider.class.php:466
 msgid "Access Token URL"
 msgid "Access Token URL"
 msgstr "URL de Token de Acesso"
 msgstr "URL de Token de Acesso"
 
 
-#: inc/provider.class.php:136 inc/provider.class.php:346
+#: inc/provider.class.php:136 inc/provider.class.php:474
 msgid "Resource Owner Details URL"
 msgid "Resource Owner Details URL"
 msgstr "URL de Detalhes do Proprietário do Recurso"
 msgstr "URL de Detalhes do Proprietário do Recurso"
 
 
-#: inc/provider.class.php:144
+#: inc/provider.class.php:224
 msgid "Callback URL"
 msgid "Callback URL"
-msgstr ""
+msgstr "URL de Retorno"
 
 
-#: inc/provider.class.php:148
-#, fuzzy
+#: inc/provider.class.php:228
 msgid "Test Single Sign-on"
 msgid "Test Single Sign-on"
-msgstr "Single Sign-on"
+msgstr "Testar Logon Único"
 
 
-#: inc/provider.class.php:197
+#: inc/provider.class.php:286
 msgid "A Name is required"
 msgid "A Name is required"
 msgstr "Nome é obrigatório"
 msgstr "Nome é obrigatório"
 
 
-#: inc/provider.class.php:203
+#: inc/provider.class.php:292
 #, php-format
 #, php-format
 msgid "The \"%s\" is a Invalid type"
 msgid "The \"%s\" is a Invalid type"
 msgstr "O \"%s\" é um tipo inválido"
 msgstr "O \"%s\" é um tipo inválido"
 
 
-#: inc/provider.class.php:207
+#: inc/provider.class.php:296
 msgid "A Client ID is required"
 msgid "A Client ID is required"
 msgstr "ID de cliente é obrigatório"
 msgstr "ID de cliente é obrigatório"
 
 
-#: inc/provider.class.php:211
+#: inc/provider.class.php:300
 msgid "A Client Secret is required"
 msgid "A Client Secret is required"
 msgstr "Segredo do Cliente é obrigatório"
 msgstr "Segredo do Cliente é obrigatório"
 
 
-#: inc/provider.class.php:216
+#: inc/provider.class.php:305
 msgid "An Authorize URL is required"
 msgid "An Authorize URL is required"
 msgstr "URL de Autorização é obrigatório"
 msgstr "URL de Autorização é obrigatório"
 
 
-#: inc/provider.class.php:218
+#: inc/provider.class.php:307
 msgid "The Authorize URL is invalid"
 msgid "The Authorize URL is invalid"
 msgstr "A URL de Autorização é inválida"
 msgstr "A URL de Autorização é inválida"
 
 
-#: inc/provider.class.php:222
+#: inc/provider.class.php:311
 msgid "An Access Token URL is required"
 msgid "An Access Token URL is required"
 msgstr "URL de Token de Acesso é obrigatório"
 msgstr "URL de Token de Acesso é obrigatório"
 
 
-#: inc/provider.class.php:224
+#: inc/provider.class.php:313
 msgid "The Access Token URL is invalid"
 msgid "The Access Token URL is invalid"
 msgstr "A URL de Token de Acesso é inválida"
 msgstr "A URL de Token de Acesso é inválida"
 
 
-#: inc/provider.class.php:228
+#: inc/provider.class.php:317
 msgid "A Resource Owner Details URL is required"
 msgid "A Resource Owner Details URL is required"
 msgstr "URL de Detalhes do Proprietário do Recurso é obrigatório"
 msgstr "URL de Detalhes do Proprietário do Recurso é obrigatório"
 
 
-#: inc/provider.class.php:230
+#: inc/provider.class.php:319
 msgid "The Resource Owner Details URL is invalid"
 msgid "The Resource Owner Details URL is invalid"
 msgstr "A URL de Detalhes do Proprietário do Recurso é inválida"
 msgstr "A URL de Detalhes do Proprietário do Recurso é inválida"
 
 
-#: inc/provider.class.php:405
+#: inc/provider.class.php:533
 msgid "Generic"
 msgid "Generic"
-msgstr "Generic"
+msgstr "Genérico"
 
 
-#: inc/provider.class.php:406
+#: inc/provider.class.php:534
 msgid "Facebook"
 msgid "Facebook"
 msgstr "Facebook"
 msgstr "Facebook"
 
 
-#: inc/provider.class.php:407
+#: inc/provider.class.php:535
 msgid "GitHub"
 msgid "GitHub"
 msgstr "GitHub"
 msgstr "GitHub"
 
 
-#: inc/provider.class.php:408
+#: inc/provider.class.php:536
 msgid "Google"
 msgid "Google"
 msgstr "Google"
 msgstr "Google"
 
 
-#: inc/provider.class.php:409
+#: inc/provider.class.php:537
 msgid "Instagram"
 msgid "Instagram"
 msgstr "Instagram"
 msgstr "Instagram"
 
 
-#: inc/provider.class.php:410
+#: inc/provider.class.php:538
 msgid "LinkdeIn"
 msgid "LinkdeIn"
 msgstr "LinkdeIn"
 msgstr "LinkdeIn"
 
 
+#: inc/toolbox.class.php:154
+#, php-format
+msgid "Login with %s"
+msgstr "Entrar com %s"
+
 #~ msgid "Run first: composer install"
 #~ msgid "Run first: composer install"
 #~ msgstr "Execute primeiro: composer install"
 #~ msgstr "Execute primeiro: composer install"

+ 41 - 36
locales/singlesignon.pot

@@ -6,10 +6,10 @@
 #, fuzzy
 #, fuzzy
 msgid ""
 msgid ""
 msgstr ""
 msgstr ""
-"Project-Id-Version: singlesignon 1.0.0\n"
+"Project-Id-Version: singlesignon 1.3.0\n"
 "Report-Msgid-Bugs-To: https://github.com/edgardmessias/glpi-singlesignon/"
 "Report-Msgid-Bugs-To: https://github.com/edgardmessias/glpi-singlesignon/"
 "issues\n"
 "issues\n"
-"POT-Creation-Date: 2021-01-12 13:25-0300\n"
+"POT-Creation-Date: 2021-01-20 14:48-0300\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
@@ -18,22 +18,18 @@ msgstr ""
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Type: text/plain; charset=UTF-8\n"
 "Content-Transfer-Encoding: 8bit\n"
 "Content-Transfer-Encoding: 8bit\n"
 
 
-#: hook.php:27 hook.php:75
-#, php-format
-msgid "Login with %s"
-msgstr ""
-
 #: setup.php:8
 #: setup.php:8
 #, php-format
 #, php-format
 msgid "Please, rename the plugin folder \"%s\" to \"singlesignon\""
 msgid "Please, rename the plugin folder \"%s\" to \"singlesignon\""
 msgstr ""
 msgstr ""
 
 
-#: setup.php:36 front/provider.form.php:59 front/provider.form.php:61
-#: front/provider.php:6 front/provider.php:8 inc/provider.class.php:57
+#: setup.php:40 front/provider.form.php:59 front/provider.form.php:61
+#: front/provider.php:6 front/provider.php:8 inc/preference.class.php:64
+#: inc/provider.class.php:57
 msgid "Single Sign-on"
 msgid "Single Sign-on"
 msgstr ""
 msgstr ""
 
 
-#: setup.php:53
+#: setup.php:57
 msgid "This plugin requires GLPI >= 0.85"
 msgid "This plugin requires GLPI >= 0.85"
 msgstr ""
 msgstr ""
 
 
@@ -49,111 +45,120 @@ msgstr ""
 msgid "Provider not active."
 msgid "Provider not active."
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:50
+#: inc/preference.class.php:147 inc/provider.class.php:50
 msgid "Single Sign-on Provider"
 msgid "Single Sign-on Provider"
 msgstr ""
 msgstr ""
 
 
+#: inc/preference.class.php:172
+msgid "Linked accounts"
+msgstr ""
+
 #: inc/provider.class.php:98
 #: inc/provider.class.php:98
 msgid "SSO Type"
 msgid "SSO Type"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:106 inc/provider.class.php:298
+#: inc/provider.class.php:106 inc/provider.class.php:426
 msgid "Client ID"
 msgid "Client ID"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:108 inc/provider.class.php:306
+#: inc/provider.class.php:108 inc/provider.class.php:434
 msgid "Client Secret"
 msgid "Client Secret"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:113 inc/provider.class.php:314
+#: inc/provider.class.php:113 inc/provider.class.php:442
 msgid "Scope"
 msgid "Scope"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:115 inc/provider.class.php:322
+#: inc/provider.class.php:115 inc/provider.class.php:450
 msgid "Extra Options"
 msgid "Extra Options"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:126 inc/provider.class.php:330
+#: inc/provider.class.php:126 inc/provider.class.php:458
 msgid "Authorize URL"
 msgid "Authorize URL"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:131 inc/provider.class.php:338
+#: inc/provider.class.php:131 inc/provider.class.php:466
 msgid "Access Token URL"
 msgid "Access Token URL"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:136 inc/provider.class.php:346
+#: inc/provider.class.php:136 inc/provider.class.php:474
 msgid "Resource Owner Details URL"
 msgid "Resource Owner Details URL"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:144
+#: inc/provider.class.php:224
 msgid "Callback URL"
 msgid "Callback URL"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:148
+#: inc/provider.class.php:228
 msgid "Test Single Sign-on"
 msgid "Test Single Sign-on"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:197
+#: inc/provider.class.php:286
 msgid "A Name is required"
 msgid "A Name is required"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:203
+#: inc/provider.class.php:292
 #, php-format
 #, php-format
 msgid "The \"%s\" is a Invalid type"
 msgid "The \"%s\" is a Invalid type"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:207
+#: inc/provider.class.php:296
 msgid "A Client ID is required"
 msgid "A Client ID is required"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:211
+#: inc/provider.class.php:300
 msgid "A Client Secret is required"
 msgid "A Client Secret is required"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:216
+#: inc/provider.class.php:305
 msgid "An Authorize URL is required"
 msgid "An Authorize URL is required"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:218
+#: inc/provider.class.php:307
 msgid "The Authorize URL is invalid"
 msgid "The Authorize URL is invalid"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:222
+#: inc/provider.class.php:311
 msgid "An Access Token URL is required"
 msgid "An Access Token URL is required"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:224
+#: inc/provider.class.php:313
 msgid "The Access Token URL is invalid"
 msgid "The Access Token URL is invalid"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:228
+#: inc/provider.class.php:317
 msgid "A Resource Owner Details URL is required"
 msgid "A Resource Owner Details URL is required"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:230
+#: inc/provider.class.php:319
 msgid "The Resource Owner Details URL is invalid"
 msgid "The Resource Owner Details URL is invalid"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:405
+#: inc/provider.class.php:533
 msgid "Generic"
 msgid "Generic"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:406
+#: inc/provider.class.php:534
 msgid "Facebook"
 msgid "Facebook"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:407
+#: inc/provider.class.php:535
 msgid "GitHub"
 msgid "GitHub"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:408
+#: inc/provider.class.php:536
 msgid "Google"
 msgid "Google"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:409
+#: inc/provider.class.php:537
 msgid "Instagram"
 msgid "Instagram"
 msgstr ""
 msgstr ""
 
 
-#: inc/provider.class.php:410
+#: inc/provider.class.php:538
 msgid "LinkdeIn"
 msgid "LinkdeIn"
 msgstr ""
 msgstr ""
+
+#: inc/toolbox.class.php:154
+#, php-format
+msgid "Login with %s"
+msgstr ""

+ 5 - 1
setup.php

@@ -1,6 +1,6 @@
 <?php
 <?php
 
 
-define('PLUGIN_SINGLESIGNON_VERSION', '1.2.0');
+define('PLUGIN_SINGLESIGNON_VERSION', '1.3.0');
 
 
 $folder = basename(dirname(__FILE__));
 $folder = basename(dirname(__FILE__));
 
 
@@ -19,6 +19,10 @@ function plugin_init_singlesignon() {
       include_once $autoload;
       include_once $autoload;
    }
    }
 
 
+   Plugin::registerClass('PluginSinglesignonPreference', [
+      'addtabon' => ['Preference', 'User']
+   ]);
+
    $PLUGIN_HOOKS['csrf_compliant']['singlesignon'] = true;
    $PLUGIN_HOOKS['csrf_compliant']['singlesignon'] = true;
 
 
    $CFG_SSO = Config::getConfigurationValues('singlesignon');
    $CFG_SSO = Config::getConfigurationValues('singlesignon');