provider.class.php 35 KB

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