configModel.js 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606
  1. /**
  2. * SPDX-FileCopyrightText: 2017 Nextcloud GmbH and Nextcloud contributors
  3. * SPDX-FileCopyrightText: 2015 ownCloud, Inc.
  4. * SPDX-License-Identifier: AGPL-3.0-or-later
  5. */
  6. OCA = OCA || {};
  7. (function() {
  8. /**
  9. * @classdesc this class represents a server configuration. It communicates
  10. * with the Nextcloud server to ensure to always have the up to date LDAP
  11. * configuration. It sends various events that views can listen to and
  12. * provides methods so they can modify the configuration based upon user
  13. * input. This model is also extended by so-called "detectors" who let the
  14. * Nextcloud server try to auto-detect settings and manipulate the
  15. * configuration as well.
  16. *
  17. * @constructor
  18. */
  19. var ConfigModel = function() {};
  20. ConfigModel.prototype = {
  21. /** @constant {number} */
  22. FILTER_MODE_ASSISTED: 0,
  23. /** @constant {number} */
  24. FILTER_MODE_RAW: 1,
  25. /**
  26. * initializes the instance. Always call it after creating the instance.
  27. *
  28. * @param {OCA.LDAP.Wizard.WizardDetectorQueue} detectorQueue
  29. */
  30. init: function (detectorQueue) {
  31. /** @type {object} holds the configuration in key-value-pairs */
  32. this.configuration = {};
  33. /** @type {object} holds the subscribers that listen to the events */
  34. this.subscribers = {};
  35. /** @type {Array} holds registered detectors */
  36. this.detectors = [];
  37. /** @type {boolean} whether a configuration is currently loading */
  38. this.loadingConfig = false;
  39. if(detectorQueue instanceof OCA.LDAP.Wizard.WizardDetectorQueue) {
  40. /** @type {OCA.LDAP.Wizard.WizardDetectorQueue} */
  41. this.detectorQueue = detectorQueue;
  42. }
  43. },
  44. /**
  45. * loads a specified configuration
  46. *
  47. * @param {string} [configID] - the configuration id (or prefix)
  48. */
  49. load: function (configID) {
  50. if(this.loadingConfig) {
  51. return;
  52. }
  53. this._resetDetectorQueue();
  54. this.configID = configID;
  55. var url = OC.generateUrl('apps/user_ldap/ajax/getConfiguration.php');
  56. var params = OC.buildQueryString({ldap_serverconfig_chooser: configID});
  57. this.loadingConfig = true;
  58. var model = this;
  59. $.post(url, params, function (result) { model._processLoadConfig(model, result) });
  60. },
  61. /**
  62. * creates a new LDAP configuration
  63. *
  64. * @param {boolean} [copyCurrent] - if true, the current configuration
  65. * is copied, otherwise a blank one is created.
  66. */
  67. newConfig: function(copyCurrent) {
  68. this._resetDetectorQueue();
  69. var url = OC.generateUrl('apps/user_ldap/ajax/getNewServerConfigPrefix.php');
  70. var params = {};
  71. if(copyCurrent === true) {
  72. params['copyConfig'] = this.configID;
  73. }
  74. params = OC.buildQueryString(params);
  75. var model = this;
  76. copyCurrent = _.isUndefined(copyCurrent) ? false : copyCurrent;
  77. $.post(url, params, function (result) { model._processNewConfigPrefix(model, result, copyCurrent) });
  78. },
  79. /**
  80. * deletes the current configuration. This method will not ask for
  81. * confirmation, if desired it needs to be ensured by the caller.
  82. *
  83. * @param {string} [configID] - the configuration id (or prefix)
  84. */
  85. deleteConfig: function(configID) {
  86. var url = OC.generateUrl('apps/user_ldap/ajax/deleteConfiguration.php');
  87. var params = OC.buildQueryString({ldap_serverconfig_chooser: configID});
  88. var model = this;
  89. $.post(url, params, function (result) { model._processDeleteConfig(model, result, configID) });
  90. },
  91. /**
  92. * @callback wizardCallBack
  93. * @param {ConfigModel} [model]
  94. * @param {OCA.LDAP.Wizard.WizardDetectorGeneric} [detector]
  95. * @param {object} [result] - response from the ajax request
  96. */
  97. /**
  98. * calls an AJAX endpoint at Nextcloud. This method should be called by
  99. * detectors only!
  100. *
  101. * @param {string} [params] - as return by OC.buildQueryString
  102. * @param {wizardCallBack} [callback]
  103. * @param {OCA.LDAP.Wizard.WizardDetectorGeneric} [detector]
  104. * @returns {jqXHR}
  105. */
  106. callWizard: function(params, callback, detector) {
  107. return this.callAjax('wizard.php', params, callback, detector);
  108. },
  109. /**
  110. * calls an AJAX endpoint at Nextcloud. This method should be called by
  111. * detectors only!
  112. *
  113. * @param {string} destination - the desired end point
  114. * @param {string} [params] - as return by OC.buildQueryString
  115. * @param {wizardCallBack} [callback]
  116. * @param {OCA.LDAP.Wizard.WizardDetectorGeneric} [detector]
  117. * @returns {jqXHR}
  118. */
  119. callAjax: function(destination, params, callback, detector) {
  120. var url = OC.generateUrl('apps/user_ldap/ajax/' + destination);
  121. var model = this;
  122. return $.post(url, params, function (result) {
  123. callback(model, detector,result);
  124. });
  125. },
  126. /**
  127. * setRequested Event
  128. *
  129. * @event ConfigModel#setRequested
  130. * @type{object} - empty
  131. */
  132. /**
  133. * modifies a configuration key. If a provided configuration key does
  134. * not exist or the provided value equals the current setting, false is
  135. * returned. Otherwise Nextcloud server will be called to save the new
  136. * value, an event will notify when this is done. True is returned when
  137. * the request is sent, however it does not mean whether saving was
  138. * successful or not.
  139. *
  140. * This method is supposed to be called by views, after the user did a
  141. * change which needs to be saved.
  142. *
  143. * @param {string} [key]
  144. * @param {string|number} [value]
  145. * @returns {boolean}
  146. * @fires {ConfigModel#setRequested}
  147. */
  148. set: function(key, value) {
  149. if(_.isUndefined(this.configuration[key])) {
  150. console.warn('will not save undefined key: ' + key);
  151. return false;
  152. }
  153. if(this.configuration[key] === value) {
  154. return false;
  155. }
  156. this._broadcast('setRequested', {});
  157. var url = OC.generateUrl('apps/user_ldap/ajax/wizard.php');
  158. var objParams = {
  159. ldap_serverconfig_chooser: this.configID,
  160. action: 'save',
  161. cfgkey: key,
  162. cfgval: value
  163. };
  164. var strParams = OC.buildQueryString(objParams);
  165. var model = this;
  166. $.post(url, strParams, function(result) { model._processSetResult(model, result, objParams) });
  167. return true;
  168. },
  169. /**
  170. * configUpdated Event
  171. *
  172. * object property is a key-value-pair of the configuration key as index
  173. * and its value.
  174. *
  175. * @event ConfigModel#configUpdated
  176. * @type{object}
  177. */
  178. /**
  179. * updates the model's configuration data. This should be called only,
  180. * when a new configuration value was received from the Nextcloud server.
  181. * This is typically done by detectors, but never by views.
  182. *
  183. * Cancels with false if old and new values already match.
  184. *
  185. * @param {string} [key]
  186. * @param {string} [value]
  187. * @returns {boolean}
  188. * @fires ConfigModel#configUpdated
  189. */
  190. update: function(key, value) {
  191. if(this.configuration[key] === value) {
  192. return false;
  193. }
  194. if(!_.isUndefined(this.configuration[key])) {
  195. // don't write e.g. count values to the configuration
  196. // they don't go as feature, yet
  197. this.configuration[key] = value;
  198. }
  199. var configPart = {};
  200. configPart[key] = value;
  201. this._broadcast('configUpdated', configPart);
  202. },
  203. /**
  204. * @typedef {object} FeaturePayload
  205. * @property {string} feature
  206. * @property {Array} data
  207. */
  208. /**
  209. * informs about a detected LDAP "feature" (wider sense). For examples,
  210. * the detected object classes for users or groups
  211. *
  212. * @param {FeaturePayload} payload
  213. */
  214. inform: function(payload) {
  215. this._broadcast('receivedLdapFeature', payload);
  216. },
  217. /**
  218. * @typedef {object} ErrorPayload
  219. * @property {string} message
  220. * @property {string} relatedKey
  221. */
  222. /**
  223. * broadcasts an error message, if a wizard reply ended up in an error.
  224. * To be called by detectors.
  225. *
  226. * @param {ErrorPayload} payload
  227. */
  228. gotServerError: function(payload) {
  229. this._broadcast('serverError', payload);
  230. },
  231. /**
  232. * detectionStarted Event
  233. *
  234. * @event ConfigModel#detectionStarted
  235. * @type{string} - the target configuration key that is being
  236. * auto-detected
  237. */
  238. /**
  239. * lets the model broadcast the info that a detector starts to run
  240. *
  241. * supposed to be called by detectors only
  242. *
  243. * @param {string} [key]
  244. * @fires ConfigModel#detectionStarted
  245. */
  246. notifyAboutDetectionStart: function(key) {
  247. this._broadcast('detectionStarted', key);
  248. },
  249. /**
  250. * detectionCompleted Event
  251. *
  252. * @event ConfigModel#detectionCompleted
  253. * @type{string} - the target configuration key that was
  254. * auto-detected
  255. */
  256. /**
  257. * lets the model broadcast the info that a detector run was completed
  258. *
  259. * supposed to be called by detectors only
  260. *
  261. * @param {string} [key]
  262. * @fires ConfigModel#detectionCompleted
  263. */
  264. notifyAboutDetectionCompletion: function(key) {
  265. this._broadcast('detectionCompleted', key);
  266. },
  267. /**
  268. * @callback listenerCallback
  269. * @param {OCA.LDAP.Wizard.WizardTabGeneric|OCA.LDAP.Wizard.WizardView} [view]
  270. * @param {object} [params]
  271. */
  272. /**
  273. * registers a listener to an event
  274. *
  275. * the idea is that only views listen.
  276. *
  277. * @param {string} [name] - the event name
  278. * @param {listenerCallback} [fn]
  279. * @param {OCA.LDAP.Wizard.WizardTabGeneric|OCA.LDAP.Wizard.WizardView} [context]
  280. */
  281. on: function(name, fn, context) {
  282. if(_.isUndefined(this.subscribers[name])) {
  283. this.subscribers[name] = [];
  284. }
  285. this.subscribers[name].push({fn: fn, context: context});
  286. },
  287. /**
  288. * starts a configuration test on the Nextcloud server
  289. */
  290. requestConfigurationTest: function() {
  291. var url = OC.generateUrl('apps/user_ldap/ajax/testConfiguration.php');
  292. var params = OC.buildQueryString({ldap_serverconfig_chooser: this.configID});
  293. var model = this;
  294. $.post(url, params, function(result) { model._processTestResult(model, result) });
  295. //TODO: make sure only one test is running at a time
  296. },
  297. /**
  298. * the view may request a call to the wizard, for instance to fetch
  299. * object classes or groups
  300. *
  301. * @param {string} featureKey
  302. * @param {Object} [additionalParams]
  303. */
  304. requestWizard: function(featureKey, additionalParams) {
  305. var model = this;
  306. var detectorCount = this.detectors.length;
  307. var found = false;
  308. for(var i = 0; i < detectorCount; i++) {
  309. if(this.detectors[i].runsOnFeatureRequest(featureKey)) {
  310. found = true;
  311. (function (detector) {
  312. model.detectorQueue.add(function() {
  313. return detector.run(model, model.configID, additionalParams);
  314. });
  315. })(model.detectors[i]);
  316. }
  317. }
  318. if(!found) {
  319. console.warn('No detector found for feature ' + featureKey);
  320. }
  321. },
  322. /**
  323. * resets the detector queue
  324. *
  325. * @private
  326. */
  327. _resetDetectorQueue: function() {
  328. if(!_.isUndefined(this.detectorQueue)) {
  329. this.detectorQueue.reset();
  330. }
  331. },
  332. /**
  333. * detectors can be registered herewith
  334. *
  335. * @param {OCA.LDAP.Wizard.WizardDetectorGeneric} [detector]
  336. */
  337. registerDetector: function(detector) {
  338. if(detector instanceof OCA.LDAP.Wizard.WizardDetectorGeneric) {
  339. this.detectors.push(detector);
  340. }
  341. },
  342. /**
  343. * emits an event
  344. *
  345. * @param {string} [name] - the event name
  346. * @param {*} [params]
  347. * @private
  348. */
  349. _broadcast: function(name, params) {
  350. if(_.isUndefined(this.subscribers[name])) {
  351. return;
  352. }
  353. var subscribers = this.subscribers[name];
  354. var subscriberCount = subscribers.length;
  355. for(var i = 0; i < subscriberCount; i++) {
  356. if(_.isUndefined(subscribers[i]['fn'])) {
  357. console.warn('callback method is not defined. Event ' + name);
  358. continue;
  359. }
  360. subscribers[i]['fn'](subscribers[i]['context'], params);
  361. }
  362. },
  363. /**
  364. * ConfigModel#configLoaded Event
  365. *
  366. * @event ConfigModel#configLoaded
  367. * @type {object} - LDAP configuration as key-value-pairs
  368. */
  369. /**
  370. * @typedef {object} ConfigLoadResponse
  371. * @property {string} [status]
  372. * @property {object} [configuration] - only present if status equals 'success'
  373. */
  374. /**
  375. * processes the ajax response of a configuration load request
  376. *
  377. * @param {ConfigModel} [model]
  378. * @param {ConfigLoadResponse} [result]
  379. * @fires ConfigModel#configLoaded
  380. * @private
  381. */
  382. _processLoadConfig: function(model, result) {
  383. model.configuration = {};
  384. if(result['status'] === 'success') {
  385. $.each(result['configuration'], function(key, value) {
  386. model.configuration[key] = value;
  387. });
  388. }
  389. model.loadingConfig = false;
  390. model._broadcast('configLoaded', model.configuration);
  391. },
  392. /**
  393. * @typedef {object} ConfigSetPayload
  394. * @property {boolean} [isSuccess]
  395. * @property {string} [key]
  396. * @property {string} [value]
  397. * @property {string} [errorMessage]
  398. */
  399. /**
  400. * ConfigModel#setCompleted Event
  401. *
  402. * @event ConfigModel#setCompleted
  403. * @type {ConfigSetPayload}
  404. */
  405. /**
  406. * @typedef {object} ConfigSetResponse
  407. * @property {string} [status]
  408. * @property {object} [message] - might be present only in error cases
  409. */
  410. /**
  411. * processes the ajax response of a configuration key set request
  412. *
  413. * @param {ConfigModel} [model]
  414. * @param {ConfigSetResponse} [result]
  415. * @param {object} [params] - the original changeSet
  416. * @fires ConfigModel#configLoaded
  417. * @private
  418. */
  419. _processSetResult: function(model, result, params) {
  420. var isSuccess = (result['status'] === 'success');
  421. if(isSuccess) {
  422. model.configuration[params.cfgkey] = params.cfgval;
  423. }
  424. var payload = {
  425. isSuccess: isSuccess,
  426. key: params.cfgkey,
  427. value: model.configuration[params.cfgkey],
  428. errorMessage: _.isUndefined(result['message']) ? '' : result['message']
  429. };
  430. model._broadcast('setCompleted', payload);
  431. // let detectors run
  432. // NOTE: detector's changes will not result in new _processSetResult
  433. // calls, … in case they interfere it is because of this ;)
  434. if(_.isUndefined(model.detectorQueue)) {
  435. console.warn("DetectorQueue was not set, detectors will not be fired");
  436. return;
  437. }
  438. var detectorCount = model.detectors.length;
  439. for(var i = 0; i < detectorCount; i++) {
  440. if(model.detectors[i].triggersOn(params.cfgkey)) {
  441. (function (detector) {
  442. model.detectorQueue.add(function() {
  443. return detector.run(model, model.configID);
  444. });
  445. })(model.detectors[i]);
  446. }
  447. }
  448. },
  449. /**
  450. * @typedef {object} ConfigTestPayload
  451. * @property {boolean} [isSuccess]
  452. */
  453. /**
  454. * ConfigModel#configurationTested Event
  455. *
  456. * @event ConfigModel#configurationTested
  457. * @type {ConfigTestPayload}
  458. */
  459. /**
  460. * @typedef {object} StatusResponse
  461. * @property {string} [status]
  462. */
  463. /**
  464. * processes the ajax response of a configuration test request
  465. *
  466. * @param {ConfigModel} [model]
  467. * @param {StatusResponse} [result]
  468. * @fires ConfigModel#configurationTested
  469. * @private
  470. */
  471. _processTestResult: function(model, result) {
  472. var payload = {
  473. isSuccess: (result['status'] === 'success')
  474. };
  475. model._broadcast('configurationTested', payload);
  476. },
  477. /**
  478. * @typedef {object} BasicConfigPayload
  479. * @property {boolean} [isSuccess]
  480. * @property {string} [configPrefix] - the new config ID
  481. * @property {string} [errorMessage]
  482. */
  483. /**
  484. * ConfigModel#newConfiguration Event
  485. *
  486. * @event ConfigModel#newConfiguration
  487. * @type {BasicConfigPayload}
  488. */
  489. /**
  490. * @typedef {object} NewConfigResponse
  491. * @property {string} [status]
  492. * @property {string} [configPrefix]
  493. * @property {object} [defaults] - default configuration values
  494. * @property {string} [message] - might only appear with status being
  495. * not 'success'
  496. */
  497. /**
  498. * processes the ajax response of a new configuration request
  499. *
  500. * @param {ConfigModel} [model]
  501. * @param {NewConfigResponse} [result]
  502. * @param {boolean} [copyCurrent]
  503. * @fires ConfigModel#newConfiguration
  504. * @fires ConfigModel#configLoaded
  505. * @private
  506. */
  507. _processNewConfigPrefix: function(model, result, copyCurrent) {
  508. var isSuccess = (result['status'] === 'success');
  509. var payload = {
  510. isSuccess: isSuccess,
  511. configPrefix: result['configPrefix'],
  512. errorMessage: _.isUndefined(result['message']) ? '' : result['message']
  513. };
  514. model._broadcast('newConfiguration', payload);
  515. if(isSuccess) {
  516. this.configID = result['configPrefix'];
  517. if(!copyCurrent) {
  518. model.configuration = {};
  519. $.each(result['defaults'], function(key, value) {
  520. model.configuration[key] = value;
  521. });
  522. // view / tabs need to update with new blank config
  523. model._broadcast('configLoaded', model.configuration);
  524. }
  525. }
  526. },
  527. /**
  528. * ConfigModel#deleteConfiguration Event
  529. *
  530. * @event ConfigModel#deleteConfiguration
  531. * @type {BasicConfigPayload}
  532. */
  533. /**
  534. * processes the ajax response of a delete configuration request
  535. *
  536. * @param {ConfigModel} [model]
  537. * @param {StatusResponse} [result]
  538. * @param {string} [configID]
  539. * @fires ConfigModel#deleteConfiguration
  540. * @private
  541. */
  542. _processDeleteConfig: function(model, result, configID) {
  543. var isSuccess = (result['status'] === 'success');
  544. var payload = {
  545. isSuccess: isSuccess,
  546. configPrefix: configID,
  547. errorMessage: _.isUndefined(result['message']) ? '' : result['message']
  548. };
  549. model._broadcast('deleteConfiguration', payload);
  550. }
  551. };
  552. OCA.LDAP.Wizard.ConfigModel = ConfigModel;
  553. })();