addDefaultFormTab($ong); $this->addStandardTab(__CLASS__, $ong, $options); $this->addStandardTab('Log', $ong, $options); return $ong; } function post_getEmpty() { $this->fields["type"] = 'generic'; $this->fields["is_active"] = 1; } function showForm($ID, $options = []) { global $CFG_GLPI; $this->initForm($ID, $options); $this->showFormHeader($options); if (empty($this->fields["type"])) { $this->fields["type"] = 'generic'; } echo ""; echo "" . __('Name') . ""; echo ""; Html::autocompletionTextField($this, "name"); echo ""; echo "" . __('Comments') . ""; echo ""; echo ""; echo ""; $on_change = 'var _value = this.options[this.selectedIndex].value; $(".sso_url").toggle(_value == "generic");'; echo ""; echo "" . __sso('SSO Type') . ""; self::dropdownType('type', ['value' => $this->fields["type"], 'on_change' => $on_change]); echo "" . __('Active') . ""; echo ""; Dropdown::showYesNo("is_active", $this->fields["is_active"]); echo "\n"; echo ""; echo "" . __sso('Client ID') . ""; echo ""; echo "" . __sso('Client Secret') . ""; echo ""; echo "\n"; echo ""; echo "" . __sso('Scope') . ""; echo ""; echo "" . __sso('Extra Options') . ""; echo ""; echo "\n"; $url_style = ""; if ($this->fields["type"] != 'generic') { $url_style = 'style="display: none;"'; } echo ""; echo "" . __sso('Authorize URL') . ""; echo ""; echo "\n"; echo ""; echo "" . __sso('Access Token URL') . ""; echo ""; echo "\n"; echo ""; echo "" . __sso('Resource Owner Details URL') . ""; echo ""; echo "\n"; $this->showFormButtons($options); return true; } function prepareInputForAdd($input) { return $this->prepareInput($input); } function prepareInputForUpdate($input) { return $this->prepareInput($input); } /** * Prepares input (for update and add) * * @param array $input Input data * * @return array */ private function prepareInput($input) { $error_detected = []; $type = ''; //check for requirements if (isset($input['type'])) { $type = $input['type']; } if (!isset($input['name']) || empty($input['name'])) { $error_detected[] = __sso('A Name is required'); } if (empty($type)) { $error_detected[] = __('An item type is required'); } else if (!isset(static::getTypes()[$type])) { $error_detected[] = sprintf(__sso('The "%s" is a Invalid type'), $type); } if (!isset($input['client_id']) || empty($input['client_id'])) { $error_detected[] = __sso('A Client ID is required'); } if (!isset($input['client_secret']) || empty($input['client_secret'])) { $error_detected[] = __sso('A Client Secret is required'); } if ($type === 'generic') { if (!isset($input['url_authorize']) || empty($input['url_authorize'])) { $error_detected[] = __sso('An Authorize URL is required'); } else if (!filter_var($input['url_authorize'], FILTER_VALIDATE_URL)) { $error_detected[] = __sso('The Authorize URL is invalid'); } if (!isset($input['url_access_token']) || empty($input['url_access_token'])) { $error_detected[] = __sso('An Access Token URL is required'); } else if (!filter_var($input['url_access_token'], FILTER_VALIDATE_URL)) { $error_detected[] = __sso('The Access Token URL is invalid'); } if (!isset($input['url_resource_owner_details']) || empty($input['url_resource_owner_details'])) { $error_detected[] = __sso('A Resource Owner Details URL is required'); } else if (!filter_var($input['url_resource_owner_details'], FILTER_VALIDATE_URL)) { $error_detected[] = __sso('The Resource Owner Details URL is invalid'); } } if (count($error_detected)) { foreach ($error_detected as $error) { Session::addMessageAfterRedirect( $error, true, ERROR ); } return false; } return $input; } function getSearchOptions() { // For GLPI <= 9.2 $options = []; foreach ($this->rawSearchOptions() as $opt) { if (!isset($opt['id'])) { continue; } $optid = $opt['id']; unset($opt['id']); if (isset($options[$optid])) { $message = "Duplicate key $optid ({$options[$optid]['name']}/{$opt['name']}) in " . get_class($this) . " searchOptions!"; Toolbox::logDebug($message); } foreach ($opt as $k => $v) { $options[$optid][$k] = $v; } } return $options; } function rawSearchOptions() { $tab = []; $tab[] = [ 'id' => 'common', 'name' => __('Characteristics'), ]; $tab[] = [ 'id' => 1, 'table' => $this->getTable(), 'field' => 'name', 'name' => __('Name'), 'datatype' => 'itemlink', ]; $tab[] = [ 'id' => 2, 'table' => $this->getTable(), 'field' => 'type', 'name' => __('Type'), 'searchtype' => 'equals', 'datatype' => 'specific', ]; $tab[] = [ 'id' => 3, 'table' => $this->getTable(), 'field' => 'client_id', 'name' => __sso('Client ID'), 'datatype' => 'text', ]; $tab[] = [ 'id' => 4, 'table' => $this->getTable(), 'field' => 'client_secret', 'name' => __sso('Client Secret'), 'datatype' => 'text', ]; $tab[] = [ 'id' => 5, 'table' => $this->getTable(), 'field' => 'scope', 'name' => __sso('Scope'), 'datatype' => 'text', ]; $tab[] = [ 'id' => 6, 'table' => $this->getTable(), 'field' => 'extra_options', 'name' => __sso('Extra Options'), 'datatype' => 'specific', ]; $tab[] = [ 'id' => 7, 'table' => $this->getTable(), 'field' => 'url_authorize', 'name' => __sso('Authorize URL'), 'datatype' => 'weblink', ]; $tab[] = [ 'id' => 8, 'table' => $this->getTable(), 'field' => 'url_access_token', 'name' => __sso('Access Token URL'), 'datatype' => 'weblink', ]; $tab[] = [ 'id' => 9, 'table' => $this->getTable(), 'field' => 'url_resource_owner_details', 'name' => __sso('Resource Owner Details URL'), 'datatype' => 'weblink', ]; $tab[] = [ 'id' => 10, 'table' => $this->getTable(), 'field' => 'is_active', 'name' => __('Active'), 'searchtype' => 'equals', 'datatype' => 'bool', ]; $tab[] = [ 'id' => 30, 'table' => $this->getTable(), 'field' => 'id', 'name' => __('ID'), 'datatype' => 'itemlink', ]; return $tab; } static function getSpecificValueToDisplay($field, $values, array $options = []) { if (!is_array($values)) { $values = [$field => $values]; } switch ($field) { case 'type': return self::getTicketTypeName($values[$field]); case 'extra_options': return '
' . $values[$field] . '
'; } return ''; } static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = []) { if (!is_array($values)) { $values = [$field => $values]; } $options['display'] = false; switch ($field) { case 'type': $options['value'] = $values[$field]; return self::dropdownType($name, $options); } return parent::getSpecificValueToSelect($field, $name, $values, $options); } /** * Get ticket types * * @return array of types * */ static function getTypes() { $options['generic'] = __sso('Generic'); $options['facebook'] = __sso('Facebook'); $options['github'] = __sso('GitHub'); $options['google'] = __sso('Google'); $options['instagram'] = __sso('Instagram'); $options['linkedin'] = __sso('LinkdeIn'); return $options; } /** * Get ticket type Name * * @param $value type ID * */ static function getTicketTypeName($value) { $tab = static::getTypes(); // Return $value if not defined return (isset($tab[$value]) ? $tab[$value] : $value); } /** * Dropdown of ticket type * * @param $name select name * @param $options array of options: * - value : integer / preselected value (default 0) * - toadd : array / array of specific values to add at the begining * - on_change : string / value to transmit to "onChange" * - display : boolean / display or get string (default true) * * @return string id of the select * */ static function dropdownType($name, $options = []) { $params['value'] = 0; $params['toadd'] = []; $params['on_change'] = ''; $params['display'] = true; if (is_array($options) && count($options)) { foreach ($options as $key => $val) { $params[$key] = $val; } } $items = []; if (count($params['toadd']) > 0) { $items = $params['toadd']; } $items += self::getTypes(); return Dropdown::showFromArray($name, $items, $params); } /** * Get an history entry message * * @param $data Array from glpi_logs table * * @since GLPI version 0.84 * * @return string * */ static function getHistoryEntry($data) { switch ($data['linked_action'] - Log::HISTORY_PLUGIN) { case 0: return __('History from plugin example', 'example'); } return ''; } ////////////////////////////// ////// SPECIFIC MODIF MASSIVE FUNCTIONS /////// /** * @since version 0.85 * * @see CommonDBTM::getSpecificMassiveActions() * */ function getSpecificMassiveActions($checkitem = null) { $actions = parent::getSpecificMassiveActions($checkitem); $actions['Document_Item' . MassiveAction::CLASS_ACTION_SEPARATOR . 'add'] = _x('button', 'Add a document'); // GLPI core one $actions[__CLASS__ . MassiveAction::CLASS_ACTION_SEPARATOR . 'do_nothing'] = __('Do Nothing - just for fun', 'example'); // Specific one return $actions; } /** * @since version 0.85 * * @see CommonDBTM::showMassiveActionsSubForm() * */ static function showMassiveActionsSubForm(MassiveAction $ma) { switch ($ma->getAction()) { case 'DoIt': echo " " . Html::submit(_x('button', 'Post'), ['name' => 'massiveaction']) . " " . __('Write in item history', 'example'); return true; case 'do_nothing' : echo " " . Html::submit(_x('button', 'Post'), ['name' => 'massiveaction']) . " " . __('but do nothing :)', 'example'); return true; } return parent::showMassiveActionsSubForm($ma); } /** * @since version 0.85 * * @see CommonDBTM::processMassiveActionsForOneItemtype() * */ static function processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item, array $ids) { global $DB; switch ($ma->getAction()) { case 'DoIt' : if ($item->getType() == 'Computer') { Session::addMessageAfterRedirect(__("Right it is the type I want...", 'example')); Session::addMessageAfterRedirect(__('Write in item history', 'example')); $changes = [0, 'old value', 'new value']; foreach ($ids as $id) { if ($item->getFromDB($id)) { Session::addMessageAfterRedirect("- " . $item->getField("name")); Log::history($id, 'Computer', $changes, 'PluginExampleExample', Log::HISTORY_PLUGIN); $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK); } else { // Example of ko count $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO); } } } else { // When nothing is possible ... $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO); } return; case 'do_nothing' : If ($item->getType() == 'PluginExampleExample') { Session::addMessageAfterRedirect(__("Right it is the type I want...", 'example')); Session::addMessageAfterRedirect(__("But... I say I will do nothing for:", 'example')); foreach ($ids as $id) { if ($item->getFromDB($id)) { Session::addMessageAfterRedirect("- " . $item->getField("name")); $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK); } else { // Example for noright / Maybe do it with can function is better $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO); } } } else { $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO); } Return; } parent::processMassiveActionsForOneItemtype($ma, $item, $ids); } public static function getDefault($type, $key, $default = null) { if (static::$default === null) { $content = file_get_contents(dirname(__FILE__) . '/../providers.json'); static::$default = json_decode($content, true); } if (isset(static::$default[$type]) && static::$default[$type][$key]) { return static::$default[$type][$key]; } return $default; } public function getClientType() { $value = "generic"; if (isset($this->fields['type']) && !empty($this->fields['type'])) { $value = $this->fields['type']; } return $value; } public function getClientId() { $value = ""; if (isset($this->fields['client_id']) && !empty($this->fields['client_id'])) { $value = $this->fields['client_id']; } return $value; } public function getClientSecret() { $value = ""; if (isset($this->fields['client_secret']) && !empty($this->fields['client_secret'])) { $value = $this->fields['client_secret']; } return $value; } public function getScope() { $value = ""; $default = [ 'github' => 'user:email', 'linkedin' => 'r_emailaddress', ]; $type = $this->getClientType(); if (isset($default[$type])) { $value = $default[$type]; } $fields = $this->fields; if (!isset($fields['scope']) || empty($fields['scope'])) { $fields['scope'] = $value; } $fields = Plugin::doHookFunction("sso:scope", $fields); return $fields['scope']; } public function getAuthorizeUrl() { $type = $this->getClientType(); $value = static::getDefault($type, "url_authorize"); $fields = $this->fields; if (!isset($fields['url_authorize']) || empty($fields['url_authorize'])) { $fields['url_authorize'] = $value; } $fields = Plugin::doHookFunction("sso:url_authorize", $fields); return $fields['url_authorize']; } public function getAccessTokenUrl() { $type = $this->getClientType(); $value = static::getDefault($type, "url_access_token"); $fields = $this->fields; if (!isset($fields['url_access_token']) || empty($fields['url_access_token'])) { $fields['url_access_token'] = $value; } $fields = Plugin::doHookFunction("sso:url_access_token", $fields); return $fields['url_access_token']; } public function getResourceOwnerDetailsUrl($access_token) { $type = $this->getClientType(); $value = static::getDefault($type, "url_resource_owner_details", ""); $fields = $this->fields; $fields['access_token'] = $access_token; if (!isset($fields['url_resource_owner_details']) || empty($fields['url_resource_owner_details'])) { $fields['url_resource_owner_details'] = $value; } $fields = Plugin::doHookFunction("sso:url_resource_owner_details", $fields); $url = $fields['url_resource_owner_details']; $url = str_replace("", $access_token, $url); $url = str_replace("", hash_hmac('sha256', $access_token, $this->getClientSecret()), $url); return $url; } /** * Get current URL without query string * @return string */ private function getCurrentURL() { $currentURL = (isset($_SERVER["HTTPS"]) && $_SERVER["HTTPS"] == "on") ? "https://" : "http://"; $currentURL .= $_SERVER["SERVER_NAME"]; if ($_SERVER["SERVER_PORT"] != "80" && $_SERVER["SERVER_PORT"] != "443") { $currentURL .= ":" . $_SERVER["SERVER_PORT"]; } // $currentURL .= $_SERVER["REQUEST_URI"]; // Ignore Query String if (isset($_SERVER["SCRIPT_NAME"])) { $currentURL .= $_SERVER["SCRIPT_NAME"]; } if (isset($_SERVER["PATH_INFO"])) { $currentURL .= $_SERVER["PATH_INFO"]; } return $currentURL; } /** * * @return boolean|string */ public function checkAuthorization() { if (isset($_GET['error'])) { $error_description = isset($_GET['error_description']) ? $_GET['error_description'] : __("The action you have requested is not allowed."); Html::displayErrorAndDie(__($error_description), true); } if (!isset($_GET['code'])) { $params = [ 'client_id' => $this->getClientId(), 'scope' => $this->getScope(), 'state' => Session::getNewCSRFToken(), 'response_type' => 'code', 'approval_prompt' => 'auto', 'redirect_uri' => $this->getCurrentURL(), ]; $params = Plugin::doHookFunction("sso:authorize_params", $params); $url = $this->getAuthorizeUrl(); $glue = strstr($url, '?') === false ? '?' : '&'; $url .= $glue . http_build_query($params); header('Location: ' . $url); exit; } // Check given state against previously stored one to mitigate CSRF attack $state = isset($_GET['state']) ? $_GET['state'] : ''; Session::checkCSRF([ '_glpi_csrf_token' => $state, ]); $this->_code = $_GET['code']; return $_GET['code']; } /** * * @return boolean|string */ public function getAccessToken() { if ($this->_token !== null) { return $this->_token; } if ($this->_code === null) { return false; } $params = [ 'client_id' => $this->getClientId(), 'client_secret' => $this->getClientSecret(), 'redirect_uri' => $this->getCurrentURL(), 'grant_type' => 'authorization_code', 'code' => $this->_code, ]; $params = Plugin::doHookFunction("sso:access_token_params", $params); $url = $this->getAccessTokenUrl(); $content = Toolbox::callCurl($url, [ CURLOPT_HTTPHEADER => [ "Accept: application/json", ], CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($params), CURLOPT_SSL_VERIFYHOST => false, CURLOPT_SSL_VERIFYPEER => false, ]); try { $data = json_decode($content, true); $this->_token = $data['access_token']; } catch (\Exception $ex) { return false; } return $this->_token; } /** * * @return boolean|array */ public function getResourceOwner() { if ($this->_resource_owner !== null) { return $this->_resource_owner; } $token = $this->getAccessToken(); if (!$token) { return false; } $url = $this->getResourceOwnerDetailsUrl($token); $headers = [ "Accept:application/json", "Authorization:Bearer $token", ]; $headers = Plugin::doHookFunction("sso:resource_owner_header", $headers); $content = Toolbox::callCurl($url, [ CURLOPT_HTTPHEADER => $headers, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_SSL_VERIFYPEER => false, ]); try { $data = json_decode($content, true); $this->_resource_owner = $data; } catch (\Exception $ex) { return false; } if ($this->getClientType() === "linkedin") { $email_url = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))"; $content = Toolbox::callCurl($email_url, [ CURLOPT_HTTPHEADER => $headers, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_SSL_VERIFYPEER => false, ]); try { $data = json_decode($content, true); $this->_resource_owner['email-address'] = $data['elements'][0]['handle~']['emailAddress']; } catch (\Exception $ex) { return false; } } return $this->_resource_owner; } public function findUser() { $resource_array = $this->getResourceOwner(); if (!$resource_array) { return false; } $user = new User(); //First: check linked user $id = Plugin::doHookFunction("sso:find_user", $resource_array); if (is_numeric($id) && $user->getFromDB($id)) { return $user; } $email = false; $email_fields = ['email', 'e-mail', 'email-address']; foreach ($email_fields as $field) { if (isset($resource_array[$field]) && is_string($resource_array[$field])) { $email = $resource_array[$field]; break; } } $default_condition = ''; if (version_compare(GLPI_VERSION, '9.3', '>=')) { $default_condition = []; } if ($email && $user->getFromDBbyEmail($email, $default_condition)) { return $user; } $login = false; $login_fields = ['login', 'username', 'id']; foreach ($login_fields as $field) { if (isset($resource_array[$field]) && is_string($resource_array[$field])) { $login = $resource_array[$field]; break; } } if ($login && $user->getFromDBbyName($login)) { return $user; } return false; } public function login() { $user = $this->findUser(); if (!$user) { return false; } //Create fake auth $auth = new Auth(); $auth->user = $user; $auth->auth_succeded = true; $auth->extauth = 1; $auth->user_present = $auth->user->getFromDBbyName(addslashes($user->fields['name'])); $auth->user->fields['authtype'] = Auth::DB_GLPI; Session::init($auth); 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"; http_build_query($url); 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") { $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; } }