provider.class.php 36 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259
  1. <?php
  2. class PluginSinglesignonProvider extends CommonDBTM {
  3. // From CommonDBTM
  4. public $dohistory = true;
  5. static $rightname = 'config';
  6. /**
  7. * @var array
  8. */
  9. static $default = null;
  10. /**
  11. *
  12. * @var string
  13. */
  14. protected $_code = null;
  15. /**
  16. *
  17. * @var null|string
  18. */
  19. protected $_token = null;
  20. /**
  21. *
  22. * @var null|array
  23. */
  24. protected $_resource_owner = null;
  25. public static function canCreate() {
  26. return static::canUpdate();
  27. }
  28. public static function canDelete() {
  29. return static::canUpdate();
  30. }
  31. public static function canPurge() {
  32. return static::canUpdate();
  33. }
  34. public static function canView() {
  35. return static::canUpdate();
  36. }
  37. // Should return the localized name of the type
  38. static function getTypeName($nb = 0) {
  39. return __sso('Single Sign-on Provider');
  40. }
  41. /**
  42. * @see CommonGLPI::getMenuName()
  43. * */
  44. static function getMenuName() {
  45. return __sso('Single Sign-on');
  46. }
  47. function defineTabs($options = []) {
  48. $ong = [];
  49. $this->addDefaultFormTab($ong);
  50. $this->addStandardTab(__CLASS__, $ong, $options);
  51. $this->addStandardTab('Log', $ong, $options);
  52. return $ong;
  53. }
  54. function post_getEmpty() {
  55. $this->fields["type"] = 'generic';
  56. $this->fields["is_active"] = 1;
  57. }
  58. function showForm($ID, $options = []) {
  59. global $CFG_GLPI;
  60. $this->initForm($ID, $options);
  61. $this->showFormHeader($options);
  62. if (empty($this->fields["type"])) {
  63. $this->fields["type"] = 'generic';
  64. }
  65. echo "<tr class='tab_bg_1'>";
  66. echo "<td>" . __('Name') . "</td>";
  67. echo "<td>";
  68. Html::autocompletionTextField($this, "name");
  69. echo "</td>";
  70. echo "<td>" . __('Comments') . "</td>";
  71. echo "<td>";
  72. echo "<textarea name='comment' >" . $this->fields["comment"] . "</textarea>";
  73. echo "</td></tr>";
  74. $on_change = 'var _value = this.options[this.selectedIndex].value; $(".sso_url").toggle(_value == "generic");';
  75. echo "<tr class='tab_bg_1'>";
  76. echo "<td>" . __sso('SSO Type') . "</td><td>";
  77. self::dropdownType('type', ['value' => $this->fields["type"], 'on_change' => $on_change]);
  78. echo "<td>" . __('Active') . "</td>";
  79. echo "<td>";
  80. Dropdown::showYesNo("is_active", $this->fields["is_active"]);
  81. echo "</td></tr>\n";
  82. echo "<tr class='tab_bg_1'>";
  83. echo "<td>" . __sso('Client ID') . "</td>";
  84. echo "<td><input type='text' style='width:96%' name='client_id' value='" . $this->fields["client_id"] . "'></td>";
  85. echo "<td>" . __sso('Client Secret') . "</td>";
  86. echo "<td><input type='text' style='width:96%' name='client_secret' value='" . $this->fields["client_secret"] . "'></td>";
  87. echo "</tr>\n";
  88. echo "<tr class='tab_bg_1'>";
  89. echo "<td>" . __sso('Scope') . "</td>";
  90. echo "<td><input type='text' style='width:96%' name='scope' value='" . $this->fields["scope"] . "'></td>";
  91. echo "<td>" . __sso('Extra Options') . "</td>";
  92. echo "<td><input type='text' style='width:96%' name='extra_options' value='" . $this->fields["extra_options"] . "'></td>";
  93. echo "</tr>\n";
  94. $url_style = "";
  95. if ($this->fields["type"] != 'generic') {
  96. $url_style = 'style="display: none;"';
  97. }
  98. echo "<tr class='tab_bg_1 sso_url' $url_style>";
  99. echo "<td>" . __sso('Authorize URL') . "</td>";
  100. echo "<td colspan='3'><input type='text' style='width:96%' name='url_authorize' value='" . $this->fields["url_authorize"] . "'></td>";
  101. echo "</tr>\n";
  102. echo "<tr class='tab_bg_1 sso_url' $url_style>";
  103. echo "<td>" . __sso('Access Token URL') . "</td>";
  104. echo "<td colspan='3'><input type='text' style='width:96%' name='url_access_token' value='" . $this->fields["url_access_token"] . "'></td>";
  105. echo "</tr>\n";
  106. echo "<tr class='tab_bg_1 sso_url' $url_style>";
  107. echo "<td>" . __sso('Resource Owner Details URL') . "</td>";
  108. echo "<td colspan='3'><input type='text' style='width:96%' name='url_resource_owner_details' value='" . $this->fields["url_resource_owner_details"] . "'></td>";
  109. echo "</tr>\n";
  110. echo "<tr class='tab_bg_1'>";
  111. echo "<th colspan='4'>" . __('Personalization') . "</th>";
  112. echo "</tr>\n";
  113. echo "<tr class='tab_bg_1'>";
  114. echo "<td>" . __('Background color') . "</td>";
  115. echo "<td>";
  116. Html::showColorField(
  117. 'bgcolor',
  118. [
  119. 'value' => $this->fields['bgcolor'],
  120. ]
  121. );
  122. echo "&nbsp;";
  123. echo Html::getCheckbox([
  124. 'title' => __('Clear'),
  125. 'name' => '_blank_bgcolor',
  126. 'checked' => empty($this->fields['bgcolor']),
  127. ]);
  128. echo "&nbsp;" . __('Clear');
  129. echo "</td>";
  130. echo "<td>" . __('Color') . "</td>";
  131. echo "<td>";
  132. Html::showColorField(
  133. 'color',
  134. [
  135. 'value' => $this->fields['color'],
  136. ]
  137. );
  138. echo "&nbsp;";
  139. echo Html::getCheckbox([
  140. 'title' => __('Clear'),
  141. 'name' => '_blank_color',
  142. 'checked' => empty($this->fields['color']),
  143. ]);
  144. echo "&nbsp;" . __('Clear');
  145. echo "</td>";
  146. echo "</tr>\n";
  147. echo "<tr class='tab_bg_1'>";
  148. echo "<td>" . __('Picture') . "</td>";
  149. echo "<td colspan='3'>";
  150. if (!empty($this->fields['picture'])) {
  151. echo Html::image(static::getPictureUrl($this->fields['picture']), [
  152. 'style' => '
  153. max-width: 100px;
  154. max-height: 100px;
  155. background-image: linear-gradient(45deg, #b0b0b0 25%, transparent 25%), linear-gradient(-45deg, #b0b0b0 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #b0b0b0 75%), linear-gradient(-45deg, transparent 75%, #b0b0b0 75%);
  156. background-size: 10px 10px;
  157. background-position: 0 0, 0 5px, 5px -5px, -5px 0px;',
  158. 'class' => 'picture_square'
  159. ]);
  160. echo "&nbsp;";
  161. echo Html::getCheckbox([
  162. 'title' => __('Clear'),
  163. 'name' => '_blank_picture'
  164. ]);
  165. echo "&nbsp;" . __('Clear');
  166. } else {
  167. echo Html::file([
  168. 'name' => 'picture',
  169. 'onlyimages' => true,
  170. ]);
  171. }
  172. echo "</td>";
  173. echo "</tr>\n";
  174. echo '<script type="text/javascript">
  175. $("[name=bgcolor]").on("change", function (e) {
  176. $("[name=_blank_bgcolor]").prop("checked", false).attr("checked", false);
  177. });
  178. $("[name=color]").on("change", function (e) {
  179. $("[name=_blank_color]").prop("checked", false).attr("checked", false);
  180. });
  181. </script>';
  182. if ($ID) {
  183. echo "<tr class='tab_bg_1'>";
  184. echo "<th colspan='4'>" . __('Test') . "</th>";
  185. echo "</tr>\n";
  186. $url = self::getCallbackUrl($ID);
  187. $fullUrl = $this->getBaseURL() . $url;
  188. echo "<tr class='tab_bg_1'>";
  189. echo "<td>" . __sso('Callback URL') . "</td>";
  190. echo "<td colspan='3'><a id='singlesignon_callbackurl' href='$fullUrl' data-url='$url'>$fullUrl</a></td>";
  191. echo "</tr>\n";
  192. $options['addbuttons'] = ['test_singlesignon' => __sso('Test Single Sign-on')];
  193. }
  194. $this->showFormButtons($options);
  195. if ($ID) {
  196. echo '<script type="text/javascript">
  197. $("[name=test_singlesignon]").on("click", function (e) {
  198. e.preventDefault();
  199. var url = $("#singlesignon_callbackurl").attr("data-url") + "/test/1";
  200. var left = ($(window).width()/2)-(600/2);
  201. var top = ($(window).height()/2)-(800/2);
  202. var newWindow = window.open(url, "singlesignon", "width=600,height=800,left=" + left + ",top=" + top);
  203. if (window.focus) {
  204. newWindow.focus();
  205. }
  206. });
  207. </script>';
  208. }
  209. return true;
  210. }
  211. function prepareInputForAdd($input) {
  212. return $this->prepareInput($input);
  213. }
  214. function prepareInputForUpdate($input) {
  215. return $this->prepareInput($input);
  216. }
  217. function cleanDBonPurge() {
  218. static::deletePicture($this->fields['picture']);
  219. }
  220. /**
  221. * Prepares input (for update and add)
  222. *
  223. * @param array $input Input data
  224. *
  225. * @return array
  226. */
  227. private function prepareInput($input) {
  228. $error_detected = [];
  229. $type = '';
  230. //check for requirements
  231. if (isset($input['type'])) {
  232. $type = $input['type'];
  233. }
  234. if (!isset($input['name']) || empty($input['name'])) {
  235. $error_detected[] = __sso('A Name is required');
  236. }
  237. if (empty($type)) {
  238. $error_detected[] = __('An item type is required');
  239. } else if (!isset(static::getTypes()[$type])) {
  240. $error_detected[] = sprintf(__sso('The "%s" is a Invalid type'), $type);
  241. }
  242. if (!isset($input['client_id']) || empty($input['client_id'])) {
  243. $error_detected[] = __sso('A Client ID is required');
  244. }
  245. if (!isset($input['client_secret']) || empty($input['client_secret'])) {
  246. $error_detected[] = __sso('A Client Secret is required');
  247. }
  248. if ($type === 'generic') {
  249. if (!isset($input['url_authorize']) || empty($input['url_authorize'])) {
  250. $error_detected[] = __sso('An Authorize URL is required');
  251. } else if (!filter_var($input['url_authorize'], FILTER_VALIDATE_URL)) {
  252. $error_detected[] = __sso('The Authorize URL is invalid');
  253. }
  254. if (!isset($input['url_access_token']) || empty($input['url_access_token'])) {
  255. $error_detected[] = __sso('An Access Token URL is required');
  256. } else if (!filter_var($input['url_access_token'], FILTER_VALIDATE_URL)) {
  257. $error_detected[] = __sso('The Access Token URL is invalid');
  258. }
  259. if (!isset($input['url_resource_owner_details']) || empty($input['url_resource_owner_details'])) {
  260. $error_detected[] = __sso('A Resource Owner Details URL is required');
  261. } else if (!filter_var($input['url_resource_owner_details'], FILTER_VALIDATE_URL)) {
  262. $error_detected[] = __sso('The Resource Owner Details URL is invalid');
  263. }
  264. }
  265. if (count($error_detected)) {
  266. foreach ($error_detected as $error) {
  267. Session::addMessageAfterRedirect(
  268. $error,
  269. true,
  270. ERROR
  271. );
  272. }
  273. return false;
  274. }
  275. if (
  276. isset($input["_blank_bgcolor"])
  277. && $input["_blank_bgcolor"]
  278. ) {
  279. $input['bgcolor'] = '';
  280. }
  281. if (
  282. isset($input["_blank_color"])
  283. && $input["_blank_color"]
  284. ) {
  285. $input['color'] = '';
  286. }
  287. if (
  288. isset($input["_blank_picture"])
  289. && $input["_blank_picture"]
  290. ) {
  291. $input['picture'] = '';
  292. if (array_key_exists('picture', $this->fields)) {
  293. static::deletePicture($this->fields['picture']);
  294. }
  295. }
  296. if (isset($input["_picture"])) {
  297. $picture = array_shift($input["_picture"]);
  298. if ($dest = static::savePicture(GLPI_TMP_DIR . '/' . $picture)) {
  299. $input['picture'] = $dest;
  300. } else {
  301. Session::addMessageAfterRedirect(__('Unable to save picture file.'), true, ERROR);
  302. }
  303. if (array_key_exists('picture', $this->fields)) {
  304. static::deletePicture($this->fields['picture']);
  305. }
  306. }
  307. return $input;
  308. }
  309. function getSearchOptions() {
  310. // For GLPI <= 9.2
  311. $options = [];
  312. foreach ($this->rawSearchOptions() as $opt) {
  313. if (!isset($opt['id'])) {
  314. continue;
  315. }
  316. $optid = $opt['id'];
  317. unset($opt['id']);
  318. if (isset($options[$optid])) {
  319. $message = "Duplicate key $optid ({$options[$optid]['name']}/{$opt['name']}) in " .
  320. get_class($this) . " searchOptions!";
  321. Toolbox::logDebug($message);
  322. }
  323. foreach ($opt as $k => $v) {
  324. $options[$optid][$k] = $v;
  325. }
  326. }
  327. return $options;
  328. }
  329. function rawSearchOptions() {
  330. $tab = [];
  331. $tab[] = [
  332. 'id' => 'common',
  333. 'name' => __('Characteristics'),
  334. ];
  335. $tab[] = [
  336. 'id' => 1,
  337. 'table' => $this->getTable(),
  338. 'field' => 'name',
  339. 'name' => __('Name'),
  340. 'datatype' => 'itemlink',
  341. ];
  342. $tab[] = [
  343. 'id' => 2,
  344. 'table' => $this->getTable(),
  345. 'field' => 'type',
  346. 'name' => __('Type'),
  347. 'searchtype' => 'equals',
  348. 'datatype' => 'specific',
  349. ];
  350. $tab[] = [
  351. 'id' => 3,
  352. 'table' => $this->getTable(),
  353. 'field' => 'client_id',
  354. 'name' => __sso('Client ID'),
  355. 'datatype' => 'text',
  356. ];
  357. $tab[] = [
  358. 'id' => 4,
  359. 'table' => $this->getTable(),
  360. 'field' => 'client_secret',
  361. 'name' => __sso('Client Secret'),
  362. 'datatype' => 'text',
  363. ];
  364. $tab[] = [
  365. 'id' => 5,
  366. 'table' => $this->getTable(),
  367. 'field' => 'scope',
  368. 'name' => __sso('Scope'),
  369. 'datatype' => 'text',
  370. ];
  371. $tab[] = [
  372. 'id' => 6,
  373. 'table' => $this->getTable(),
  374. 'field' => 'extra_options',
  375. 'name' => __sso('Extra Options'),
  376. 'datatype' => 'specific',
  377. ];
  378. $tab[] = [
  379. 'id' => 7,
  380. 'table' => $this->getTable(),
  381. 'field' => 'url_authorize',
  382. 'name' => __sso('Authorize URL'),
  383. 'datatype' => 'weblink',
  384. ];
  385. $tab[] = [
  386. 'id' => 8,
  387. 'table' => $this->getTable(),
  388. 'field' => 'url_access_token',
  389. 'name' => __sso('Access Token URL'),
  390. 'datatype' => 'weblink',
  391. ];
  392. $tab[] = [
  393. 'id' => 9,
  394. 'table' => $this->getTable(),
  395. 'field' => 'url_resource_owner_details',
  396. 'name' => __sso('Resource Owner Details URL'),
  397. 'datatype' => 'weblink',
  398. ];
  399. $tab[] = [
  400. 'id' => 10,
  401. 'table' => $this->getTable(),
  402. 'field' => 'is_active',
  403. 'name' => __('Active'),
  404. 'searchtype' => 'equals',
  405. 'datatype' => 'bool',
  406. ];
  407. $tab[] = [
  408. 'id' => 30,
  409. 'table' => $this->getTable(),
  410. 'field' => 'id',
  411. 'name' => __('ID'),
  412. 'datatype' => 'itemlink',
  413. ];
  414. return $tab;
  415. }
  416. static function getSpecificValueToDisplay($field, $values, array $options = []) {
  417. if (!is_array($values)) {
  418. $values = [$field => $values];
  419. }
  420. switch ($field) {
  421. case 'type':
  422. return self::getTicketTypeName($values[$field]);
  423. case 'extra_options':
  424. return '<pre>' . $values[$field] . '</pre>';
  425. }
  426. return '';
  427. }
  428. static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = []) {
  429. if (!is_array($values)) {
  430. $values = [$field => $values];
  431. }
  432. $options['display'] = false;
  433. switch ($field) {
  434. case 'type':
  435. $options['value'] = $values[$field];
  436. return self::dropdownType($name, $options);
  437. }
  438. return parent::getSpecificValueToSelect($field, $name, $values, $options);
  439. }
  440. /**
  441. * Get ticket types
  442. *
  443. * @return array of types
  444. * */
  445. static function getTypes() {
  446. $options['generic'] = __sso('Generic');
  447. $options['facebook'] = __sso('Facebook');
  448. $options['github'] = __sso('GitHub');
  449. $options['google'] = __sso('Google');
  450. $options['instagram'] = __sso('Instagram');
  451. $options['linkedin'] = __sso('LinkdeIn');
  452. return $options;
  453. }
  454. /**
  455. * Get ticket type Name
  456. *
  457. * @param $value type ID
  458. * */
  459. static function getTicketTypeName($value) {
  460. $tab = static::getTypes();
  461. // Return $value if not defined
  462. return (isset($tab[$value]) ? $tab[$value] : $value);
  463. }
  464. /**
  465. * Dropdown of ticket type
  466. *
  467. * @param $name select name
  468. * @param $options array of options:
  469. * - value : integer / preselected value (default 0)
  470. * - toadd : array / array of specific values to add at the begining
  471. * - on_change : string / value to transmit to "onChange"
  472. * - display : boolean / display or get string (default true)
  473. *
  474. * @return string id of the select
  475. * */
  476. static function dropdownType($name, $options = []) {
  477. $params['value'] = 0;
  478. $params['toadd'] = [];
  479. $params['on_change'] = '';
  480. $params['display'] = true;
  481. if (is_array($options) && count($options)) {
  482. foreach ($options as $key => $val) {
  483. $params[$key] = $val;
  484. }
  485. }
  486. $items = [];
  487. if (count($params['toadd']) > 0) {
  488. $items = $params['toadd'];
  489. }
  490. $items += self::getTypes();
  491. return Dropdown::showFromArray($name, $items, $params);
  492. }
  493. /**
  494. * Get an history entry message
  495. *
  496. * @param $data Array from glpi_logs table
  497. *
  498. * @since GLPI version 0.84
  499. *
  500. * @return string
  501. * */
  502. static function getHistoryEntry($data) {
  503. switch ($data['linked_action'] - Log::HISTORY_PLUGIN) {
  504. case 0:
  505. return __('History from plugin example', 'example');
  506. }
  507. return '';
  508. }
  509. //////////////////////////////
  510. ////// SPECIFIC MODIF MASSIVE FUNCTIONS ///////
  511. /**
  512. * @since version 0.85
  513. *
  514. * @see CommonDBTM::getSpecificMassiveActions()
  515. * */
  516. function getSpecificMassiveActions($checkitem = null) {
  517. $actions = parent::getSpecificMassiveActions($checkitem);
  518. $actions['Document_Item' . MassiveAction::CLASS_ACTION_SEPARATOR . 'add'] = _x('button', 'Add a document'); // GLPI core one
  519. $actions[__CLASS__ . MassiveAction::CLASS_ACTION_SEPARATOR . 'do_nothing'] = __('Do Nothing - just for fun', 'example'); // Specific one
  520. return $actions;
  521. }
  522. /**
  523. * @since version 0.85
  524. *
  525. * @see CommonDBTM::showMassiveActionsSubForm()
  526. * */
  527. static function showMassiveActionsSubForm(MassiveAction $ma) {
  528. switch ($ma->getAction()) {
  529. case 'DoIt':
  530. echo "&nbsp;<input type='hidden' name='toto' value='1'>" .
  531. Html::submit(_x('button', 'Post'), ['name' => 'massiveaction']) .
  532. " " . __('Write in item history', 'example');
  533. return true;
  534. case 'do_nothing':
  535. echo "&nbsp;" . Html::submit(_x('button', 'Post'), ['name' => 'massiveaction']) .
  536. " " . __('but do nothing :)', 'example');
  537. return true;
  538. }
  539. return parent::showMassiveActionsSubForm($ma);
  540. }
  541. /**
  542. * @since version 0.85
  543. *
  544. * @see CommonDBTM::processMassiveActionsForOneItemtype()
  545. * */
  546. static function processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item, array $ids) {
  547. global $DB;
  548. switch ($ma->getAction()) {
  549. case 'DoIt':
  550. if ($item->getType() == 'Computer') {
  551. Session::addMessageAfterRedirect(__("Right it is the type I want...", 'example'));
  552. Session::addMessageAfterRedirect(__('Write in item history', 'example'));
  553. $changes = [0, 'old value', 'new value'];
  554. foreach ($ids as $id) {
  555. if ($item->getFromDB($id)) {
  556. Session::addMessageAfterRedirect("- " . $item->getField("name"));
  557. Log::history($id, 'Computer', $changes, 'PluginExampleExample', Log::HISTORY_PLUGIN);
  558. $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
  559. } else {
  560. // Example of ko count
  561. $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
  562. }
  563. }
  564. } else {
  565. // When nothing is possible ...
  566. $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO);
  567. }
  568. return;
  569. case 'do_nothing':
  570. if ($item->getType() == 'PluginExampleExample') {
  571. Session::addMessageAfterRedirect(__("Right it is the type I want...", 'example'));
  572. Session::addMessageAfterRedirect(__("But... I say I will do nothing for:", 'example'));
  573. foreach ($ids as $id) {
  574. if ($item->getFromDB($id)) {
  575. Session::addMessageAfterRedirect("- " . $item->getField("name"));
  576. $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
  577. } else {
  578. // Example for noright / Maybe do it with can function is better
  579. $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
  580. }
  581. }
  582. } else {
  583. $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO);
  584. }
  585. return;
  586. }
  587. parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
  588. }
  589. static function getIcon() {
  590. return "fas fa-user-lock";
  591. }
  592. public static function getDefault($type, $key, $default = null) {
  593. if (static::$default === null) {
  594. $content = file_get_contents(dirname(__FILE__) . '/../providers.json');
  595. static::$default = json_decode($content, true);
  596. }
  597. if (isset(static::$default[$type]) && static::$default[$type][$key]) {
  598. return static::$default[$type][$key];
  599. }
  600. return $default;
  601. }
  602. public function getClientType() {
  603. $value = "generic";
  604. if (isset($this->fields['type']) && !empty($this->fields['type'])) {
  605. $value = $this->fields['type'];
  606. }
  607. return $value;
  608. }
  609. public function getClientId() {
  610. $value = "";
  611. if (isset($this->fields['client_id']) && !empty($this->fields['client_id'])) {
  612. $value = $this->fields['client_id'];
  613. }
  614. return $value;
  615. }
  616. public function getClientSecret() {
  617. $value = "";
  618. if (isset($this->fields['client_secret']) && !empty($this->fields['client_secret'])) {
  619. $value = $this->fields['client_secret'];
  620. }
  621. return $value;
  622. }
  623. public function getScope() {
  624. $type = $this->getClientType();
  625. $value = static::getDefault($type, "scope");
  626. $fields = $this->fields;
  627. if (!isset($fields['scope']) || empty($fields['scope'])) {
  628. $fields['scope'] = $value;
  629. }
  630. $fields = Plugin::doHookFunction("sso:scope", $fields);
  631. return $fields['scope'];
  632. }
  633. public function getAuthorizeUrl() {
  634. $type = $this->getClientType();
  635. $value = static::getDefault($type, "url_authorize");
  636. $fields = $this->fields;
  637. if (!isset($fields['url_authorize']) || empty($fields['url_authorize'])) {
  638. $fields['url_authorize'] = $value;
  639. }
  640. $fields = Plugin::doHookFunction("sso:url_authorize", $fields);
  641. return $fields['url_authorize'];
  642. }
  643. public function getAccessTokenUrl() {
  644. $type = $this->getClientType();
  645. $value = static::getDefault($type, "url_access_token");
  646. $fields = $this->fields;
  647. if (!isset($fields['url_access_token']) || empty($fields['url_access_token'])) {
  648. $fields['url_access_token'] = $value;
  649. }
  650. $fields = Plugin::doHookFunction("sso:url_access_token", $fields);
  651. return $fields['url_access_token'];
  652. }
  653. public function getResourceOwnerDetailsUrl($access_token) {
  654. $type = $this->getClientType();
  655. $value = static::getDefault($type, "url_resource_owner_details", "");
  656. $fields = $this->fields;
  657. $fields['access_token'] = $access_token;
  658. if (!isset($fields['url_resource_owner_details']) || empty($fields['url_resource_owner_details'])) {
  659. $fields['url_resource_owner_details'] = $value;
  660. }
  661. $fields = Plugin::doHookFunction("sso:url_resource_owner_details", $fields);
  662. $url = $fields['url_resource_owner_details'];
  663. $url = str_replace("<access_token>", $access_token, $url);
  664. $url = str_replace("<appsecret_proof>", hash_hmac('sha256', $access_token, $this->getClientSecret()), $url);
  665. return $url;
  666. }
  667. /**
  668. * Get current URL without query string
  669. * @return string
  670. */
  671. private function getBaseURL() {
  672. $baseURL = "";
  673. if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
  674. $baseURL = ($_SERVER["HTTP_X_FORWARDED_PROTO"] == "https") ? "https://" : "http://";
  675. } else if (isset($_SERVER["HTTPS"])) {
  676. $baseURL = ($_SERVER["HTTPS"] == "on") ? "https://" : "http://";
  677. } else {
  678. $baseURL = "http://";
  679. }
  680. if (isset($_SERVER["HTTP_X_FORWARDED_HOST"])) {
  681. $baseURL .= $_SERVER["HTTP_X_FORWARDED_HOST"];
  682. } else if (isset($_SERVER["HTTP_X_FORWARDED_HOST"])) {
  683. $baseURL .= $_SERVER["HTTP_X_FORWARDED_HOST"];
  684. } else {
  685. $baseURL .= $_SERVER["SERVER_NAME"];
  686. }
  687. $port = $_SERVER["SERVER_PORT"];
  688. if (isset($_SERVER["HTTP_X_FORWARDED_PORT"])) {
  689. $port = $_SERVER["HTTP_X_FORWARDED_PORT"];
  690. }
  691. if ($port != "80" && $port != "443") {
  692. $baseURL .= ":" . $_SERVER["SERVER_PORT"];
  693. }
  694. return $baseURL;
  695. }
  696. /**
  697. * Get current URL without query string
  698. * @return string
  699. */
  700. private function getCurrentURL() {
  701. $currentURL = $this->getBaseURL();
  702. // $currentURL .= $_SERVER["REQUEST_URI"];
  703. // Ignore Query String
  704. if (isset($_SERVER["SCRIPT_NAME"])) {
  705. $currentURL .= $_SERVER["SCRIPT_NAME"];
  706. }
  707. if (isset($_SERVER["PATH_INFO"])) {
  708. $currentURL .= $_SERVER["PATH_INFO"];
  709. }
  710. return $currentURL;
  711. }
  712. /**
  713. *
  714. * @return boolean|string
  715. */
  716. public function checkAuthorization() {
  717. if (isset($_GET['error'])) {
  718. $error_description = isset($_GET['error_description']) ? $_GET['error_description'] : __("The action you have requested is not allowed.");
  719. Html::displayErrorAndDie(__($error_description), true);
  720. }
  721. if (!isset($_GET['code'])) {
  722. $params = [
  723. 'client_id' => $this->getClientId(),
  724. 'scope' => $this->getScope(),
  725. 'state' => Session::getNewCSRFToken(),
  726. 'response_type' => 'code',
  727. 'approval_prompt' => 'auto',
  728. 'redirect_uri' => $this->getCurrentURL(),
  729. ];
  730. $params = Plugin::doHookFunction("sso:authorize_params", $params);
  731. $url = $this->getAuthorizeUrl();
  732. $glue = strstr($url, '?') === false ? '?' : '&';
  733. $url .= $glue . http_build_query($params);
  734. header('Location: ' . $url);
  735. exit;
  736. }
  737. // Check given state against previously stored one to mitigate CSRF attack
  738. $state = isset($_GET['state']) ? $_GET['state'] : '';
  739. Session::checkCSRF([
  740. '_glpi_csrf_token' => $state,
  741. ]);
  742. $this->_code = $_GET['code'];
  743. return $_GET['code'];
  744. }
  745. /**
  746. *
  747. * @return boolean|string
  748. */
  749. public function getAccessToken() {
  750. if ($this->_token !== null) {
  751. return $this->_token;
  752. }
  753. if ($this->_code === null) {
  754. return false;
  755. }
  756. $params = [
  757. 'client_id' => $this->getClientId(),
  758. 'client_secret' => $this->getClientSecret(),
  759. 'redirect_uri' => $this->getCurrentURL(),
  760. 'grant_type' => 'authorization_code',
  761. 'code' => $this->_code,
  762. ];
  763. $params = Plugin::doHookFunction("sso:access_token_params", $params);
  764. $url = $this->getAccessTokenUrl();
  765. $content = Toolbox::callCurl($url, [
  766. CURLOPT_HTTPHEADER => [
  767. "Accept: application/json",
  768. ],
  769. CURLOPT_POST => true,
  770. CURLOPT_POSTFIELDS => http_build_query($params),
  771. CURLOPT_SSL_VERIFYHOST => false,
  772. CURLOPT_SSL_VERIFYPEER => false,
  773. ]);
  774. try {
  775. $data = json_decode($content, true);
  776. if (!isset($data['access_token'])) {
  777. return false;
  778. }
  779. $this->_token = $data['access_token'];
  780. } catch (\Exception $ex) {
  781. return false;
  782. }
  783. return $this->_token;
  784. }
  785. /**
  786. *
  787. * @return boolean|array
  788. */
  789. public function getResourceOwner() {
  790. if ($this->_resource_owner !== null) {
  791. return $this->_resource_owner;
  792. }
  793. $token = $this->getAccessToken();
  794. if (!$token) {
  795. return false;
  796. }
  797. $url = $this->getResourceOwnerDetailsUrl($token);
  798. $headers = [
  799. "Accept:application/json",
  800. "Authorization:Bearer $token",
  801. ];
  802. $headers = Plugin::doHookFunction("sso:resource_owner_header", $headers);
  803. $content = Toolbox::callCurl($url, [
  804. CURLOPT_HTTPHEADER => $headers,
  805. CURLOPT_SSL_VERIFYHOST => false,
  806. CURLOPT_SSL_VERIFYPEER => false,
  807. ]);
  808. try {
  809. $data = json_decode($content, true);
  810. $this->_resource_owner = $data;
  811. } catch (\Exception $ex) {
  812. return false;
  813. }
  814. if ($this->getClientType() === "linkedin") {
  815. $email_url = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))";
  816. $content = Toolbox::callCurl($email_url, [
  817. CURLOPT_HTTPHEADER => $headers,
  818. CURLOPT_SSL_VERIFYHOST => false,
  819. CURLOPT_SSL_VERIFYPEER => false,
  820. ]);
  821. try {
  822. $data = json_decode($content, true);
  823. $this->_resource_owner['email-address'] = $data['elements'][0]['handle~']['emailAddress'];
  824. } catch (\Exception $ex) {
  825. return false;
  826. }
  827. }
  828. return $this->_resource_owner;
  829. }
  830. public function findUser() {
  831. $resource_array = $this->getResourceOwner();
  832. if (!$resource_array) {
  833. return false;
  834. }
  835. $user = new User();
  836. //First: check linked user
  837. $id = Plugin::doHookFunction("sso:find_user", $resource_array);
  838. if (is_numeric($id) && $user->getFromDB($id)) {
  839. return $user;
  840. }
  841. $email = false;
  842. $email_fields = ['email', 'e-mail', 'email-address', 'mail'];
  843. foreach ($email_fields as $field) {
  844. if (isset($resource_array[$field]) && is_string($resource_array[$field])) {
  845. $email = $resource_array[$field];
  846. break;
  847. }
  848. }
  849. $default_condition = '';
  850. if (version_compare(GLPI_VERSION, '9.3', '>=')) {
  851. $default_condition = [];
  852. }
  853. if ($email && $user->getFromDBbyEmail($email, $default_condition)) {
  854. return $user;
  855. }
  856. $login = false;
  857. $login_fields = ['login', 'username', 'id'];
  858. foreach ($login_fields as $field) {
  859. if (isset($resource_array[$field]) && is_string($resource_array[$field])) {
  860. $login = $resource_array[$field];
  861. break;
  862. }
  863. }
  864. if ($login && $user->getFromDBbyName($login)) {
  865. return $user;
  866. }
  867. return false;
  868. }
  869. public function login() {
  870. $user = $this->findUser();
  871. if (!$user) {
  872. return false;
  873. }
  874. //Create fake auth
  875. $auth = new Auth();
  876. $auth->user = $user;
  877. $auth->auth_succeded = true;
  878. $auth->extauth = 1;
  879. $auth->user_present = $auth->user->getFromDBbyName(addslashes($user->fields['name']));
  880. $auth->user->fields['authtype'] = Auth::DB_GLPI;
  881. Session::init($auth);
  882. return $auth->auth_succeded;
  883. }
  884. public function linkUser($user_id) {
  885. /** @var User */
  886. $user = User::getById($user_id);
  887. if (!$user) {
  888. return;
  889. }
  890. $resource_array = $this->getResourceOwner();
  891. if (!$resource_array) {
  892. return false;
  893. }
  894. $link = new PluginSinglesignonProvider_User();
  895. return $link->add([
  896. 'plugin_singlesignon_providers_id' => $this->fields['id'],
  897. 'users_id' => $user_id,
  898. 'remote_id' => $resource_array['id'],
  899. ]);
  900. }
  901. /**
  902. * Generate a URL to callback
  903. * Some providers don't accept query string, it convert to PATH
  904. * @global array $CFG_GLPI
  905. * @param integer $id
  906. * @param array $query
  907. * @return string
  908. */
  909. public static function getCallbackUrl($id, $query = []) {
  910. global $CFG_GLPI;
  911. $url = $CFG_GLPI['root_doc'] . '/plugins/singlesignon/front/callback.php';
  912. $url .= "/provider/$id";
  913. if (!empty($query)) {
  914. $url .= "/q/" . base64_encode(http_build_query($query));
  915. }
  916. return $url;
  917. }
  918. public static function getCallbackParameters($name = null) {
  919. $data = [];
  920. if (isset($_SERVER['PATH_INFO'])) {
  921. $path_info = trim($_SERVER['PATH_INFO'], '/');
  922. $parts = explode('/', $path_info);
  923. $key = null;
  924. foreach ($parts as $part) {
  925. if ($key === null) {
  926. $key = $part;
  927. } else {
  928. if ($key === "provider" || $key === "test") {
  929. $part = intval($part);
  930. } else {
  931. $tmp = base64_decode($part);
  932. parse_str($tmp, $part);
  933. }
  934. if ($key === $name) {
  935. return $part;
  936. }
  937. $data[$key] = $part;
  938. $key = null;
  939. }
  940. }
  941. }
  942. if (!isset($data[$name])) {
  943. return null;
  944. }
  945. return $data;
  946. }
  947. static public function startsWith($haystack, $needle) {
  948. $length = strlen($needle);
  949. return (substr($haystack, 0, $length) === $needle);
  950. }
  951. static function getPictureUrl($path) {
  952. global $CFG_GLPI;
  953. $path = Html::cleanInputText($path); // prevent xss
  954. if (empty($path)) {
  955. return null;
  956. }
  957. return $CFG_GLPI['root_doc'] . '/plugins/singlesignon/front/picture.send.php?path=' . $path;
  958. }
  959. static public function savePicture($src, $uniq_prefix = null) {
  960. if (function_exists('Document::isImage') && !Document::isImage($src)) {
  961. return false;
  962. }
  963. $filename = uniqid($uniq_prefix);
  964. $ext = pathinfo($src, PATHINFO_EXTENSION);
  965. $subdirectory = substr($filename, -2); // subdirectory based on last 2 hex digit
  966. $basePath = GLPI_PLUGIN_DOC_DIR . "/singlesignon";
  967. $i = 0;
  968. do {
  969. // Iterate on possible suffix while dest exists.
  970. // This case will almost never exists as dest is based on an unique id.
  971. $dest = $basePath
  972. . '/' . $subdirectory
  973. . '/' . $filename . ($i > 0 ? '_' . $i : '') . '.' . $ext;
  974. $i++;
  975. } while (file_exists($dest));
  976. if (!is_dir($basePath . '/' . $subdirectory) && !mkdir($basePath . '/' . $subdirectory)) {
  977. return false;
  978. }
  979. if (!rename($src, $dest)) {
  980. return false;
  981. }
  982. return substr($dest, strlen($basePath . '/')); // Return dest relative to GLPI_PICTURE_DIR
  983. }
  984. public static function deletePicture($path) {
  985. $basePath = GLPI_PLUGIN_DOC_DIR . "/singlesignon";
  986. $fullpath = $basePath . '/' . $path;
  987. if (!file_exists($fullpath)) {
  988. return false;
  989. }
  990. $fullpath = realpath($fullpath);
  991. if (!static::startsWith($fullpath, realpath($basePath))) {
  992. return false;
  993. }
  994. return @unlink($fullpath);
  995. }
  996. public static function renderButton($url, $data, $class = 'oauth-login') {
  997. $btn = '<span><a href="' . $url . '" class="singlesignon vsubmit ' . $class . '"';
  998. $style = '';
  999. if ((isset($data['bgcolor']) && $data['bgcolor'])) {
  1000. $style .= 'background-color: ' . $data['bgcolor'] . ';';
  1001. }
  1002. if ((isset($data['color']) && $data['color'])) {
  1003. $style .= 'color: ' . $data['color'] . ';';
  1004. }
  1005. if ($style) {
  1006. $btn .= ' style="' . $style . '"';
  1007. }
  1008. $btn .= '>';
  1009. if (isset($data['picture']) && $data['picture']) {
  1010. $btn .= Html::image(
  1011. static::getPictureUrl($data['picture']),
  1012. [
  1013. 'style' => 'max-height: 20px;',
  1014. ]
  1015. );
  1016. $btn .= ' ';
  1017. }
  1018. $btn .= sprintf(__sso('Login with %s'), $data['name']);
  1019. $btn .= '</a></span>';
  1020. return $btn;
  1021. }
  1022. }