provider.class.php 35 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235
  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 (isset($input["_blank_bgcolor"])
  276. && $input["_blank_bgcolor"]
  277. ) {
  278. $input['bgcolor'] = '';
  279. }
  280. if (isset($input["_blank_color"])
  281. && $input["_blank_color"]
  282. ) {
  283. $input['color'] = '';
  284. }
  285. if (isset($input["_blank_picture"])
  286. && $input["_blank_picture"]
  287. ) {
  288. $input['picture'] = '';
  289. if (array_key_exists('picture', $this->fields)) {
  290. static::deletePicture($this->fields['picture']);
  291. }
  292. }
  293. if (isset($input["_picture"])) {
  294. $picture = array_shift($input["_picture"]);
  295. if ($dest = static::savePicture(GLPI_TMP_DIR . '/' . $picture)) {
  296. $input['picture'] = $dest;
  297. } else {
  298. Session::addMessageAfterRedirect(__('Unable to save picture file.'), true, ERROR);
  299. }
  300. if (array_key_exists('picture', $this->fields)) {
  301. static::deletePicture($this->fields['picture']);
  302. }
  303. }
  304. return $input;
  305. }
  306. function getSearchOptions() {
  307. // For GLPI <= 9.2
  308. $options = [];
  309. foreach ($this->rawSearchOptions() as $opt) {
  310. if (!isset($opt['id'])) {
  311. continue;
  312. }
  313. $optid = $opt['id'];
  314. unset($opt['id']);
  315. if (isset($options[$optid])) {
  316. $message = "Duplicate key $optid ({$options[$optid]['name']}/{$opt['name']}) in " .
  317. get_class($this) . " searchOptions!";
  318. Toolbox::logDebug($message);
  319. }
  320. foreach ($opt as $k => $v) {
  321. $options[$optid][$k] = $v;
  322. }
  323. }
  324. return $options;
  325. }
  326. function rawSearchOptions() {
  327. $tab = [];
  328. $tab[] = [
  329. 'id' => 'common',
  330. 'name' => __('Characteristics'),
  331. ];
  332. $tab[] = [
  333. 'id' => 1,
  334. 'table' => $this->getTable(),
  335. 'field' => 'name',
  336. 'name' => __('Name'),
  337. 'datatype' => 'itemlink',
  338. ];
  339. $tab[] = [
  340. 'id' => 2,
  341. 'table' => $this->getTable(),
  342. 'field' => 'type',
  343. 'name' => __('Type'),
  344. 'searchtype' => 'equals',
  345. 'datatype' => 'specific',
  346. ];
  347. $tab[] = [
  348. 'id' => 3,
  349. 'table' => $this->getTable(),
  350. 'field' => 'client_id',
  351. 'name' => __sso('Client ID'),
  352. 'datatype' => 'text',
  353. ];
  354. $tab[] = [
  355. 'id' => 4,
  356. 'table' => $this->getTable(),
  357. 'field' => 'client_secret',
  358. 'name' => __sso('Client Secret'),
  359. 'datatype' => 'text',
  360. ];
  361. $tab[] = [
  362. 'id' => 5,
  363. 'table' => $this->getTable(),
  364. 'field' => 'scope',
  365. 'name' => __sso('Scope'),
  366. 'datatype' => 'text',
  367. ];
  368. $tab[] = [
  369. 'id' => 6,
  370. 'table' => $this->getTable(),
  371. 'field' => 'extra_options',
  372. 'name' => __sso('Extra Options'),
  373. 'datatype' => 'specific',
  374. ];
  375. $tab[] = [
  376. 'id' => 7,
  377. 'table' => $this->getTable(),
  378. 'field' => 'url_authorize',
  379. 'name' => __sso('Authorize URL'),
  380. 'datatype' => 'weblink',
  381. ];
  382. $tab[] = [
  383. 'id' => 8,
  384. 'table' => $this->getTable(),
  385. 'field' => 'url_access_token',
  386. 'name' => __sso('Access Token URL'),
  387. 'datatype' => 'weblink',
  388. ];
  389. $tab[] = [
  390. 'id' => 9,
  391. 'table' => $this->getTable(),
  392. 'field' => 'url_resource_owner_details',
  393. 'name' => __sso('Resource Owner Details URL'),
  394. 'datatype' => 'weblink',
  395. ];
  396. $tab[] = [
  397. 'id' => 10,
  398. 'table' => $this->getTable(),
  399. 'field' => 'is_active',
  400. 'name' => __('Active'),
  401. 'searchtype' => 'equals',
  402. 'datatype' => 'bool',
  403. ];
  404. $tab[] = [
  405. 'id' => 30,
  406. 'table' => $this->getTable(),
  407. 'field' => 'id',
  408. 'name' => __('ID'),
  409. 'datatype' => 'itemlink',
  410. ];
  411. return $tab;
  412. }
  413. static function getSpecificValueToDisplay($field, $values, array $options = []) {
  414. if (!is_array($values)) {
  415. $values = [$field => $values];
  416. }
  417. switch ($field) {
  418. case 'type':
  419. return self::getTicketTypeName($values[$field]);
  420. case 'extra_options':
  421. return '<pre>' . $values[$field] . '</pre>';
  422. }
  423. return '';
  424. }
  425. static function getSpecificValueToSelect($field, $name = '', $values = '', array $options = []) {
  426. if (!is_array($values)) {
  427. $values = [$field => $values];
  428. }
  429. $options['display'] = false;
  430. switch ($field) {
  431. case 'type':
  432. $options['value'] = $values[$field];
  433. return self::dropdownType($name, $options);
  434. }
  435. return parent::getSpecificValueToSelect($field, $name, $values, $options);
  436. }
  437. /**
  438. * Get ticket types
  439. *
  440. * @return array of types
  441. * */
  442. static function getTypes() {
  443. $options['generic'] = __sso('Generic');
  444. $options['facebook'] = __sso('Facebook');
  445. $options['github'] = __sso('GitHub');
  446. $options['google'] = __sso('Google');
  447. $options['instagram'] = __sso('Instagram');
  448. $options['linkedin'] = __sso('LinkdeIn');
  449. return $options;
  450. }
  451. /**
  452. * Get ticket type Name
  453. *
  454. * @param $value type ID
  455. * */
  456. static function getTicketTypeName($value) {
  457. $tab = static::getTypes();
  458. // Return $value if not defined
  459. return (isset($tab[$value]) ? $tab[$value] : $value);
  460. }
  461. /**
  462. * Dropdown of ticket type
  463. *
  464. * @param $name select name
  465. * @param $options array of options:
  466. * - value : integer / preselected value (default 0)
  467. * - toadd : array / array of specific values to add at the begining
  468. * - on_change : string / value to transmit to "onChange"
  469. * - display : boolean / display or get string (default true)
  470. *
  471. * @return string id of the select
  472. * */
  473. static function dropdownType($name, $options = []) {
  474. $params['value'] = 0;
  475. $params['toadd'] = [];
  476. $params['on_change'] = '';
  477. $params['display'] = true;
  478. if (is_array($options) && count($options)) {
  479. foreach ($options as $key => $val) {
  480. $params[$key] = $val;
  481. }
  482. }
  483. $items = [];
  484. if (count($params['toadd']) > 0) {
  485. $items = $params['toadd'];
  486. }
  487. $items += self::getTypes();
  488. return Dropdown::showFromArray($name, $items, $params);
  489. }
  490. /**
  491. * Get an history entry message
  492. *
  493. * @param $data Array from glpi_logs table
  494. *
  495. * @since GLPI version 0.84
  496. *
  497. * @return string
  498. * */
  499. static function getHistoryEntry($data) {
  500. switch ($data['linked_action'] - Log::HISTORY_PLUGIN) {
  501. case 0:
  502. return __('History from plugin example', 'example');
  503. }
  504. return '';
  505. }
  506. //////////////////////////////
  507. ////// SPECIFIC MODIF MASSIVE FUNCTIONS ///////
  508. /**
  509. * @since version 0.85
  510. *
  511. * @see CommonDBTM::getSpecificMassiveActions()
  512. * */
  513. function getSpecificMassiveActions($checkitem = null) {
  514. $actions = parent::getSpecificMassiveActions($checkitem);
  515. $actions['Document_Item' . MassiveAction::CLASS_ACTION_SEPARATOR . 'add'] = _x('button', 'Add a document'); // GLPI core one
  516. $actions[__CLASS__ . MassiveAction::CLASS_ACTION_SEPARATOR . 'do_nothing'] = __('Do Nothing - just for fun', 'example'); // Specific one
  517. return $actions;
  518. }
  519. /**
  520. * @since version 0.85
  521. *
  522. * @see CommonDBTM::showMassiveActionsSubForm()
  523. * */
  524. static function showMassiveActionsSubForm(MassiveAction $ma) {
  525. switch ($ma->getAction()) {
  526. case 'DoIt':
  527. echo "&nbsp;<input type='hidden' name='toto' value='1'>" .
  528. Html::submit(_x('button', 'Post'), ['name' => 'massiveaction']) .
  529. " " . __('Write in item history', 'example');
  530. return true;
  531. case 'do_nothing':
  532. echo "&nbsp;" . Html::submit(_x('button', 'Post'), ['name' => 'massiveaction']) .
  533. " " . __('but do nothing :)', 'example');
  534. return true;
  535. }
  536. return parent::showMassiveActionsSubForm($ma);
  537. }
  538. /**
  539. * @since version 0.85
  540. *
  541. * @see CommonDBTM::processMassiveActionsForOneItemtype()
  542. * */
  543. static function processMassiveActionsForOneItemtype(MassiveAction $ma, CommonDBTM $item, array $ids) {
  544. global $DB;
  545. switch ($ma->getAction()) {
  546. case 'DoIt':
  547. if ($item->getType() == 'Computer') {
  548. Session::addMessageAfterRedirect(__("Right it is the type I want...", 'example'));
  549. Session::addMessageAfterRedirect(__('Write in item history', 'example'));
  550. $changes = [0, 'old value', 'new value'];
  551. foreach ($ids as $id) {
  552. if ($item->getFromDB($id)) {
  553. Session::addMessageAfterRedirect("- " . $item->getField("name"));
  554. Log::history($id, 'Computer', $changes, 'PluginExampleExample', Log::HISTORY_PLUGIN);
  555. $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
  556. } else {
  557. // Example of ko count
  558. $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
  559. }
  560. }
  561. } else {
  562. // When nothing is possible ...
  563. $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO);
  564. }
  565. return;
  566. case 'do_nothing':
  567. if ($item->getType() == 'PluginExampleExample') {
  568. Session::addMessageAfterRedirect(__("Right it is the type I want...", 'example'));
  569. Session::addMessageAfterRedirect(__("But... I say I will do nothing for:", 'example'));
  570. foreach ($ids as $id) {
  571. if ($item->getFromDB($id)) {
  572. Session::addMessageAfterRedirect("- " . $item->getField("name"));
  573. $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_OK);
  574. } else {
  575. // Example for noright / Maybe do it with can function is better
  576. $ma->itemDone($item->getType(), $id, MassiveAction::ACTION_KO);
  577. }
  578. }
  579. } else {
  580. $ma->itemDone($item->getType(), $ids, MassiveAction::ACTION_KO);
  581. }
  582. return;
  583. }
  584. parent::processMassiveActionsForOneItemtype($ma, $item, $ids);
  585. }
  586. static function getIcon() {
  587. return "fas fa-user-lock";
  588. }
  589. public static function getDefault($type, $key, $default = null) {
  590. if (static::$default === null) {
  591. $content = file_get_contents(dirname(__FILE__) . '/../providers.json');
  592. static::$default = json_decode($content, true);
  593. }
  594. if (isset(static::$default[$type]) && static::$default[$type][$key]) {
  595. return static::$default[$type][$key];
  596. }
  597. return $default;
  598. }
  599. public function getClientType() {
  600. $value = "generic";
  601. if (isset($this->fields['type']) && !empty($this->fields['type'])) {
  602. $value = $this->fields['type'];
  603. }
  604. return $value;
  605. }
  606. public function getClientId() {
  607. $value = "";
  608. if (isset($this->fields['client_id']) && !empty($this->fields['client_id'])) {
  609. $value = $this->fields['client_id'];
  610. }
  611. return $value;
  612. }
  613. public function getClientSecret() {
  614. $value = "";
  615. if (isset($this->fields['client_secret']) && !empty($this->fields['client_secret'])) {
  616. $value = $this->fields['client_secret'];
  617. }
  618. return $value;
  619. }
  620. public function getScope() {
  621. $type = $this->getClientType();
  622. $value = static::getDefault($type, "scope");
  623. $fields = $this->fields;
  624. if (!isset($fields['scope']) || empty($fields['scope'])) {
  625. $fields['scope'] = $value;
  626. }
  627. $fields = Plugin::doHookFunction("sso:scope", $fields);
  628. return $fields['scope'];
  629. }
  630. public function getAuthorizeUrl() {
  631. $type = $this->getClientType();
  632. $value = static::getDefault($type, "url_authorize");
  633. $fields = $this->fields;
  634. if (!isset($fields['url_authorize']) || empty($fields['url_authorize'])) {
  635. $fields['url_authorize'] = $value;
  636. }
  637. $fields = Plugin::doHookFunction("sso:url_authorize", $fields);
  638. return $fields['url_authorize'];
  639. }
  640. public function getAccessTokenUrl() {
  641. $type = $this->getClientType();
  642. $value = static::getDefault($type, "url_access_token");
  643. $fields = $this->fields;
  644. if (!isset($fields['url_access_token']) || empty($fields['url_access_token'])) {
  645. $fields['url_access_token'] = $value;
  646. }
  647. $fields = Plugin::doHookFunction("sso:url_access_token", $fields);
  648. return $fields['url_access_token'];
  649. }
  650. public function getResourceOwnerDetailsUrl($access_token) {
  651. $type = $this->getClientType();
  652. $value = static::getDefault($type, "url_resource_owner_details", "");
  653. $fields = $this->fields;
  654. $fields['access_token'] = $access_token;
  655. if (!isset($fields['url_resource_owner_details']) || empty($fields['url_resource_owner_details'])) {
  656. $fields['url_resource_owner_details'] = $value;
  657. }
  658. $fields = Plugin::doHookFunction("sso:url_resource_owner_details", $fields);
  659. $url = $fields['url_resource_owner_details'];
  660. $url = str_replace("<access_token>", $access_token, $url);
  661. $url = str_replace("<appsecret_proof>", hash_hmac('sha256', $access_token, $this->getClientSecret()), $url);
  662. return $url;
  663. }
  664. /**
  665. * Get current URL without query string
  666. * @return string
  667. */
  668. private function getBaseURL() {
  669. $baseURL = "";
  670. if (isset($_SERVER['HTTP_X_FORWARDED_PROTO'])) {
  671. $baseURL = ($_SERVER["HTTP_X_FORWARDED_PROTO"] == "https") ? "https://" : "http://";
  672. } else if (isset($_SERVER["HTTPS"])) {
  673. $baseURL = ($_SERVER["HTTPS"] == "on") ? "https://" : "http://";
  674. } else {
  675. $baseURL = "http://";
  676. }
  677. if (isset($_SERVER["HTTP_X_FORWARDED_HOST"])) {
  678. $baseURL .= $_SERVER["HTTP_X_FORWARDED_HOST"];
  679. } else if (isset($_SERVER["HTTP_X_FORWARDED_HOST"])) {
  680. $baseURL .= $_SERVER["HTTP_X_FORWARDED_HOST"];
  681. } else {
  682. $baseURL .= $_SERVER["SERVER_NAME"];
  683. }
  684. $port = $_SERVER["SERVER_PORT"];
  685. if (isset($_SERVER["HTTP_X_FORWARDED_PORT"])) {
  686. $port = $_SERVER["HTTP_X_FORWARDED_PORT"];
  687. }
  688. if ($port != "80" && $port != "443") {
  689. $baseURL .= ":" . $_SERVER["SERVER_PORT"];
  690. }
  691. return $baseURL;
  692. }
  693. /**
  694. * Get current URL without query string
  695. * @return string
  696. */
  697. private function getCurrentURL() {
  698. $currentURL = $this->getBaseURL();
  699. // $currentURL .= $_SERVER["REQUEST_URI"];
  700. // Ignore Query String
  701. if (isset($_SERVER["SCRIPT_NAME"])) {
  702. $currentURL .= $_SERVER["SCRIPT_NAME"];
  703. }
  704. if (isset($_SERVER["PATH_INFO"])) {
  705. $currentURL .= $_SERVER["PATH_INFO"];
  706. }
  707. return $currentURL;
  708. }
  709. /**
  710. *
  711. * @return boolean|string
  712. */
  713. public function checkAuthorization() {
  714. if (isset($_GET['error'])) {
  715. $error_description = isset($_GET['error_description']) ? $_GET['error_description'] : __("The action you have requested is not allowed.");
  716. Html::displayErrorAndDie(__($error_description), true);
  717. }
  718. if (!isset($_GET['code'])) {
  719. $params = [
  720. 'client_id' => $this->getClientId(),
  721. 'scope' => $this->getScope(),
  722. 'state' => Session::getNewCSRFToken(),
  723. 'response_type' => 'code',
  724. 'approval_prompt' => 'auto',
  725. 'redirect_uri' => $this->getCurrentURL(),
  726. ];
  727. $params = Plugin::doHookFunction("sso:authorize_params", $params);
  728. $url = $this->getAuthorizeUrl();
  729. $glue = strstr($url, '?') === false ? '?' : '&';
  730. $url .= $glue . http_build_query($params);
  731. header('Location: ' . $url);
  732. exit;
  733. }
  734. // Check given state against previously stored one to mitigate CSRF attack
  735. $state = isset($_GET['state']) ? $_GET['state'] : '';
  736. Session::checkCSRF([
  737. '_glpi_csrf_token' => $state,
  738. ]);
  739. $this->_code = $_GET['code'];
  740. return $_GET['code'];
  741. }
  742. /**
  743. *
  744. * @return boolean|string
  745. */
  746. public function getAccessToken() {
  747. if ($this->_token !== null) {
  748. return $this->_token;
  749. }
  750. if ($this->_code === null) {
  751. return false;
  752. }
  753. $params = [
  754. 'client_id' => $this->getClientId(),
  755. 'client_secret' => $this->getClientSecret(),
  756. 'redirect_uri' => $this->getCurrentURL(),
  757. 'grant_type' => 'authorization_code',
  758. 'code' => $this->_code,
  759. ];
  760. $params = Plugin::doHookFunction("sso:access_token_params", $params);
  761. $url = $this->getAccessTokenUrl();
  762. $content = Toolbox::callCurl($url, [
  763. CURLOPT_HTTPHEADER => [
  764. "Accept: application/json",
  765. ],
  766. CURLOPT_POST => true,
  767. CURLOPT_POSTFIELDS => http_build_query($params),
  768. CURLOPT_SSL_VERIFYHOST => false,
  769. CURLOPT_SSL_VERIFYPEER => false,
  770. ]);
  771. try {
  772. $data = json_decode($content, true);
  773. if (!isset($data['access_token'])) {
  774. return false;
  775. }
  776. $this->_token = $data['access_token'];
  777. } catch (\Exception $ex) {
  778. return false;
  779. }
  780. return $this->_token;
  781. }
  782. /**
  783. *
  784. * @return boolean|array
  785. */
  786. public function getResourceOwner() {
  787. if ($this->_resource_owner !== null) {
  788. return $this->_resource_owner;
  789. }
  790. $token = $this->getAccessToken();
  791. if (!$token) {
  792. return false;
  793. }
  794. $url = $this->getResourceOwnerDetailsUrl($token);
  795. $headers = [
  796. "Accept:application/json",
  797. "Authorization:Bearer $token",
  798. ];
  799. $headers = Plugin::doHookFunction("sso:resource_owner_header", $headers);
  800. $content = Toolbox::callCurl($url, [
  801. CURLOPT_HTTPHEADER => $headers,
  802. CURLOPT_SSL_VERIFYHOST => false,
  803. CURLOPT_SSL_VERIFYPEER => false,
  804. ]);
  805. try {
  806. $data = json_decode($content, true);
  807. $this->_resource_owner = $data;
  808. } catch (\Exception $ex) {
  809. return false;
  810. }
  811. if ($this->getClientType() === "linkedin") {
  812. $email_url = "https://api.linkedin.com/v2/emailAddress?q=members&projection=(elements*(handle~))";
  813. $content = Toolbox::callCurl($email_url, [
  814. CURLOPT_HTTPHEADER => $headers,
  815. CURLOPT_SSL_VERIFYHOST => false,
  816. CURLOPT_SSL_VERIFYPEER => false,
  817. ]);
  818. try {
  819. $data = json_decode($content, true);
  820. $this->_resource_owner['email-address'] = $data['elements'][0]['handle~']['emailAddress'];
  821. } catch (\Exception $ex) {
  822. return false;
  823. }
  824. }
  825. return $this->_resource_owner;
  826. }
  827. public function findUser() {
  828. $resource_array = $this->getResourceOwner();
  829. if (!$resource_array) {
  830. return false;
  831. }
  832. $user = new User();
  833. //First: check linked user
  834. $id = Plugin::doHookFunction("sso:find_user", $resource_array);
  835. if (is_numeric($id) && $user->getFromDB($id)) {
  836. return $user;
  837. }
  838. $email = false;
  839. $email_fields = ['email', 'e-mail', 'email-address', 'mail'];
  840. foreach ($email_fields as $field) {
  841. if (isset($resource_array[$field]) && is_string($resource_array[$field])) {
  842. $email = $resource_array[$field];
  843. break;
  844. }
  845. }
  846. $default_condition = '';
  847. if (version_compare(GLPI_VERSION, '9.3', '>=')) {
  848. $default_condition = [];
  849. }
  850. if ($email && $user->getFromDBbyEmail($email, $default_condition)) {
  851. return $user;
  852. }
  853. $login = false;
  854. $login_fields = ['login', 'username', 'id'];
  855. foreach ($login_fields as $field) {
  856. if (isset($resource_array[$field]) && is_string($resource_array[$field])) {
  857. $login = $resource_array[$field];
  858. break;
  859. }
  860. }
  861. if ($login && $user->getFromDBbyName($login)) {
  862. return $user;
  863. }
  864. return false;
  865. }
  866. public function login() {
  867. $user = $this->findUser();
  868. if (!$user) {
  869. return false;
  870. }
  871. //Create fake auth
  872. $auth = new Auth();
  873. $auth->user = $user;
  874. $auth->auth_succeded = true;
  875. $auth->extauth = 1;
  876. $auth->user_present = $auth->user->getFromDBbyName(addslashes($user->fields['name']));
  877. $auth->user->fields['authtype'] = Auth::DB_GLPI;
  878. Session::init($auth);
  879. return $auth->auth_succeded;
  880. }
  881. /**
  882. * Generate a URL to callback
  883. * Some providers don't accept query string, it convert to PATH
  884. * @global array $CFG_GLPI
  885. * @param integer $id
  886. * @param array $query
  887. * @return string
  888. */
  889. public static function getCallbackUrl($id, $query = []) {
  890. global $CFG_GLPI;
  891. $url = $CFG_GLPI['root_doc'] . '/plugins/singlesignon/front/callback.php';
  892. $url .= "/provider/$id";
  893. if (!empty($query)) {
  894. $url .= "/q/" . base64_encode(http_build_query($query));
  895. }
  896. return $url;
  897. }
  898. public static function getCallbackParameters($name = null) {
  899. $data = [];
  900. if (isset($_SERVER['PATH_INFO'])) {
  901. $path_info = trim($_SERVER['PATH_INFO'], '/');
  902. $parts = explode('/', $path_info);
  903. $key = null;
  904. foreach ($parts as $part) {
  905. if ($key === null) {
  906. $key = $part;
  907. } else {
  908. if ($key === "provider" || $key === "test") {
  909. $part = intval($part);
  910. } else {
  911. $tmp = base64_decode($part);
  912. parse_str($tmp, $part);
  913. }
  914. if ($key === $name) {
  915. return $part;
  916. }
  917. $data[$key] = $part;
  918. $key = null;
  919. }
  920. }
  921. }
  922. if (!isset($data[$name])) {
  923. return null;
  924. }
  925. return $data;
  926. }
  927. static public function startsWith($haystack, $needle) {
  928. $length = strlen($needle);
  929. return (substr($haystack, 0, $length) === $needle);
  930. }
  931. static function getPictureUrl($path) {
  932. global $CFG_GLPI;
  933. $path = Html::cleanInputText($path); // prevent xss
  934. if (empty($path)) {
  935. return null;
  936. }
  937. return $CFG_GLPI['root_doc'] . '/plugins/singlesignon/front/picture.send.php?path=' . $path;
  938. }
  939. static public function savePicture($src, $uniq_prefix = null) {
  940. if (function_exists('Document::isImage') && !Document::isImage($src)) {
  941. return false;
  942. }
  943. $filename = uniqid($uniq_prefix);
  944. $ext = pathinfo($src, PATHINFO_EXTENSION);
  945. $subdirectory = substr($filename, -2); // subdirectory based on last 2 hex digit
  946. $basePath = GLPI_PLUGIN_DOC_DIR . "/singlesignon";
  947. $i = 0;
  948. do {
  949. // Iterate on possible suffix while dest exists.
  950. // This case will almost never exists as dest is based on an unique id.
  951. $dest = $basePath
  952. . '/' . $subdirectory
  953. . '/' . $filename . ($i > 0 ? '_' . $i : '') . '.' . $ext;
  954. $i++;
  955. } while (file_exists($dest));
  956. if (!is_dir($basePath . '/' . $subdirectory) && !mkdir($basePath . '/' . $subdirectory)) {
  957. return false;
  958. }
  959. if (!rename($src, $dest)) {
  960. return false;
  961. }
  962. return substr($dest, strlen($basePath . '/')); // Return dest relative to GLPI_PICTURE_DIR
  963. }
  964. public static function deletePicture($path) {
  965. $basePath = GLPI_PLUGIN_DOC_DIR . "/singlesignon";
  966. $fullpath = $basePath . '/' . $path;
  967. if (!file_exists($fullpath)) {
  968. return false;
  969. }
  970. $fullpath = realpath($fullpath);
  971. if (!static::startsWith($fullpath, realpath($basePath))) {
  972. return false;
  973. }
  974. return @unlink($fullpath);
  975. }
  976. public static function renderButton($url, $data, $class = 'oauth-login') {
  977. $btn = '<span><a href="' . $url . '" class="singlesignon vsubmit ' . $class . '"';
  978. $style = '';
  979. if ((isset($data['bgcolor']) && $data['bgcolor'])) {
  980. $style .= 'background-color: ' . $data['bgcolor'] . ';';
  981. }
  982. if ((isset($data['color']) && $data['color'])) {
  983. $style .= 'color: ' . $data['color'] . ';';
  984. }
  985. if ($style) {
  986. $btn .= ' style="' . $style . '"';
  987. }
  988. $btn .= '>';
  989. if (isset($data['picture']) && $data['picture']) {
  990. $btn .= Html::image(
  991. static::getPictureUrl($data['picture']),
  992. [
  993. 'style' => 'max-height: 20px;',
  994. ]
  995. );
  996. $btn .= ' ';
  997. }
  998. $btn .= sprintf(__sso('Login with %s'), $data['name']);
  999. $btn .= '</a></span>';
  1000. return $btn;
  1001. }
  1002. }