provider.class.php 36 KB

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