You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

authtoken_view.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. /* global Handlebars, moment */
  2. /**
  3. * @author Christoph Wurst <christoph@owncloud.com>
  4. *
  5. * @copyright Copyright (c) 2016, ownCloud, Inc.
  6. * @license AGPL-3.0
  7. *
  8. * This code is free software: you can redistribute it and/or modify
  9. * it under the terms of the GNU Affero General Public License, version 3,
  10. * as published by the Free Software Foundation.
  11. *
  12. * This program is distributed in the hope that it will be useful,
  13. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  14. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. * GNU Affero General Public License for more details.
  16. *
  17. * You should have received a copy of the GNU Affero General Public License, version 3,
  18. * along with this program. If not, see <http://www.gnu.org/licenses/>
  19. *
  20. */
  21. (function (OC, _, $, Handlebars, moment) {
  22. 'use strict';
  23. OC.Settings = OC.Settings || {};
  24. var TEMPLATE_TOKEN =
  25. '<tr data-id="{{id}}">'
  26. + '<td class="has-tooltip" title="{{title}}"><span class="token-name">{{name}}</span></td>'
  27. + '<td><span class="last-activity has-tooltip" title="{{lastActivityTime}}">{{lastActivity}}</span></td>'
  28. + '{{#if canDelete}}'
  29. + '<td><a class="icon-delete has-tooltip" title="' + t('core', 'Disconnect') + '"></a></td>'
  30. + '{{else}}'
  31. + '<td></td>'
  32. + '{{/if}}'
  33. + '<tr>';
  34. var SubView = OC.Backbone.View.extend({
  35. collection: null,
  36. /**
  37. * token type
  38. * - 0: browser
  39. * - 1: device
  40. *
  41. * @see OC\Authentication\Token\IToken
  42. */
  43. type: 0,
  44. _template: undefined,
  45. template: function (data) {
  46. if (_.isUndefined(this._template)) {
  47. this._template = Handlebars.compile(TEMPLATE_TOKEN);
  48. }
  49. return this._template(data);
  50. },
  51. initialize: function (options) {
  52. this.type = options.type;
  53. this.collection = options.collection;
  54. this.on(this.collection, 'change', this.render);
  55. },
  56. render: function () {
  57. var _this = this;
  58. var list = this.$('.token-list');
  59. var tokens = this.collection.filter(function (token) {
  60. return parseInt(token.get('type'), 10) === _this.type;
  61. });
  62. list.html('');
  63. // Show header only if there are tokens to show
  64. this._toggleHeader(tokens.length > 0);
  65. tokens.forEach(function (token) {
  66. var viewData = this._formatViewData(token.toJSON());
  67. var html = _this.template(viewData);
  68. var $html = $(html);
  69. $html.find('.has-tooltip').tooltip({container: 'body'});
  70. list.append($html);
  71. }.bind(this));
  72. },
  73. toggleLoading: function (state) {
  74. this.$('.token-list').toggleClass('icon-loading', state);
  75. },
  76. _toggleHeader: function (show) {
  77. this.$('.hidden-when-empty').toggleClass('hidden', !show);
  78. },
  79. _formatViewData: function (viewData) {
  80. var ts = viewData.lastActivity * 1000;
  81. viewData.lastActivity = OC.Util.relativeModifiedDate(ts);
  82. viewData.lastActivityTime = OC.Util.formatDate(ts, 'LLL');
  83. // preserve title for cases where we format it further
  84. viewData.title = viewData.name;
  85. // pretty format sync client user agent
  86. var matches = viewData.name.match(/Mozilla\/5\.0 \((\w+)\) (?:mirall|csyncoC)\/(\d+\.\d+\.\d+)/);
  87. var userAgentMap = {
  88. ie: /(?:MSIE|Trident) (\d+)/,
  89. // Microsoft Edge User Agent from https://msdn.microsoft.com/en-us/library/hh869301(v=vs.85).aspx
  90. edge: /^Mozilla\/5\.0 \([^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+ Edge\/[0-9.]+$/,
  91. // Firefox User Agent from https://developer.mozilla.org/en-US/docs/Web/HTTP/Gecko_user_agent_string_reference
  92. firefox: /^Mozilla\/5\.0 \([^)]*(Windows|OS X|Linux)[^)]+\) Gecko\/[0-9.]+ Firefox\/(\d+)(?:\.\d)?$/,
  93. // Chrome User Agent from https://developer.chrome.com/multidevice/user-agent
  94. chrome: /^Mozilla\/5\.0 \([^)]*(Windows|OS X|Linux)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\) Chrome\/(\d+)[0-9.]+ (?:Mobile Safari|Safari)\/[0-9.]+$/,
  95. // Safari User Agent from http://www.useragentstring.com/pages/Safari/
  96. safari: /^Mozilla\/5\.0 \([^)]*(Windows|OS X)[^)]+\) AppleWebKit\/[0-9.]+ \(KHTML, like Gecko\)(?: Version\/([0-9]+)[0-9.]+)? Safari\/[0-9.A-Z]+$/,
  97. // Android Chrome user agent: https://developers.google.com/chrome/mobile/docs/user-agent
  98. androidChrome: /Android.*(?:; (.*) Build\/).*Chrome\/(\d+)[0-9.]+/,
  99. iphone: / *CPU +iPhone +OS +(\d+)_\d+ +like +Mac +OS +X */,
  100. iosClient: /^Mozilla\/5\.0 \(iOS\) ownCloud\-iOS.*$/,
  101. androidClient:/^Mozilla\/5\.0 \(Android\) ownCloud\-android.*$/,
  102. // DAVdroid/1.2 (2016/07/03; dav4android; okhttp3) Android/6.0.1
  103. davDroid: /DAVdroid\/([0-9.]+)/,
  104. // Mozilla/5.0 (U; Linux; Maemo; Jolla; Sailfish; like Android 4.3) AppleWebKit/538.1 (KHTML, like Gecko) WebPirate/2.0 like Mobile Safari/538.1 (compatible)
  105. webPirate: /(Sailfish).*WebPirate\/(\d+)/,
  106. // Mozilla/5.0 (Maemo; Linux; U; Jolla; Sailfish; Mobile; rv:31.0) Gecko/31.0 Firefox/31.0 SailfishBrowser/1.0
  107. sailfishBrowser: /(Sailfish).*SailfishBrowser\/(\d+)/
  108. };
  109. var nameMap = {
  110. ie: t('setting', 'Internet Explorer'),
  111. edge: t('setting', 'Edge'),
  112. firefox: t('setting', 'Firefox'),
  113. chrome: t('setting', 'Google Chrome'),
  114. safari: t('setting', 'Safari'),
  115. androidChrome: t('setting', 'Google Chrome for Android'),
  116. iphone: t('setting', 'iPhone'),
  117. iosClient: t('setting', 'iOS Client'),
  118. androidClient: t('setting', 'Android Client'),
  119. davDroid: 'DAVdroid',
  120. webPirate: 'WebPirate',
  121. sailfishBrowser: 'SailfishBrowser'
  122. };
  123. if (matches) {
  124. viewData.name = t('settings', 'Sync client - {os}', {
  125. os: matches[1],
  126. version: matches[2]
  127. });
  128. }
  129. for (var client in userAgentMap) {
  130. if (matches = viewData.title.match(userAgentMap[client])) {
  131. if (matches[2] && matches[1]) { // version number and os
  132. viewData.name = nameMap[client] + ' ' + matches[2] + ' - ' + matches[1];
  133. }else if (matches[1]) { // only version number
  134. viewData.name = nameMap[client] + ' ' + matches[1];
  135. } else {
  136. viewData.name = nameMap[client];
  137. }
  138. }
  139. }
  140. if (viewData.current) {
  141. viewData.name = t('settings', 'This session');
  142. }
  143. return viewData;
  144. }
  145. });
  146. var AuthTokenView = OC.Backbone.View.extend({
  147. collection: null,
  148. _views: [],
  149. _form: undefined,
  150. _tokenName: undefined,
  151. _addAppPasswordBtn: undefined,
  152. _result: undefined,
  153. _newAppLoginName: undefined,
  154. _newAppPassword: undefined,
  155. _newAppId: undefined,
  156. _hideAppPasswordBtn: undefined,
  157. _addingToken: false,
  158. initialize: function (options) {
  159. this.collection = options.collection;
  160. var tokenTypes = [0, 1];
  161. var _this = this;
  162. _.each(tokenTypes, function (type) {
  163. var el = type === 0 ? '#sessions' : '#apppasswords';
  164. _this._views.push(new SubView({
  165. el: el,
  166. type: type,
  167. collection: _this.collection
  168. }));
  169. var $el = $(el);
  170. $el.on('click', 'a.icon-delete', _.bind(_this._onDeleteToken, _this));
  171. });
  172. this._form = $('#app-password-form');
  173. this._tokenName = $('#app-password-name');
  174. this._addAppPasswordBtn = $('#add-app-password');
  175. this._addAppPasswordBtn.click(_.bind(this._addAppPassword, this));
  176. this._result = $('#app-password-result');
  177. this._newAppLoginName = $('#new-app-login-name');
  178. this._newAppLoginName.on('focus', _.bind(this._onNewTokenLoginNameFocus, this));
  179. this._newAppPassword = $('#new-app-password');
  180. this._newAppPassword.on('focus', _.bind(this._onNewTokenFocus, this));
  181. this._hideAppPasswordBtn = $('#app-password-hide');
  182. this._hideAppPasswordBtn.click(_.bind(this._hideToken, this));
  183. // Clipboard!
  184. var clipboard = new Clipboard('.clipboardButton');
  185. clipboard.on('success', function(e) {
  186. var $input = $(e.trigger);
  187. $input.tooltip({placement: 'bottom', trigger: 'manual', title: t('core', 'Copied!')});
  188. $input.tooltip('show');
  189. _.delay(function() {
  190. $input.tooltip('hide');
  191. }, 3000);
  192. });
  193. clipboard.on('error', function (e) {
  194. var $input = $(e.trigger);
  195. var actionMsg = '';
  196. if (/iPhone|iPad/i.test(navigator.userAgent)) {
  197. actionMsg = t('core', 'Not supported!');
  198. } else if (/Mac/i.test(navigator.userAgent)) {
  199. actionMsg = t('core', 'Press ⌘-C to copy.');
  200. } else {
  201. actionMsg = t('core', 'Press Ctrl-C to copy.');
  202. }
  203. $input.tooltip({
  204. placement: 'bottom',
  205. trigger: 'manual',
  206. title: actionMsg
  207. });
  208. $input.tooltip('show');
  209. _.delay(function () {
  210. $input.tooltip('hide');
  211. }, 3000);
  212. });
  213. },
  214. render: function () {
  215. _.each(this._views, function (view) {
  216. view.render();
  217. view.toggleLoading(false);
  218. });
  219. },
  220. reload: function () {
  221. var _this = this;
  222. _.each(this._views, function (view) {
  223. view.toggleLoading(true);
  224. });
  225. var loadingTokens = this.collection.fetch();
  226. $.when(loadingTokens).done(function () {
  227. _this.render();
  228. });
  229. $.when(loadingTokens).fail(function () {
  230. OC.Notification.showTemporary(t('core', 'Error while loading browser sessions and device tokens'));
  231. });
  232. },
  233. _addAppPassword: function () {
  234. var _this = this;
  235. this._toggleAddingToken(true);
  236. var deviceName = this._tokenName.val();
  237. var creatingToken = $.ajax(OC.generateUrl('/settings/personal/authtokens'), {
  238. method: 'POST',
  239. data: {
  240. name: deviceName
  241. }
  242. });
  243. $.when(creatingToken).done(function (resp) {
  244. // We can delete token we add
  245. resp.deviceToken.canDelete = true;
  246. _this.collection.add(resp.deviceToken);
  247. _this.render();
  248. _this._newAppLoginName.val(resp.loginName);
  249. _this._newAppPassword.val(resp.token);
  250. _this._newAppId = resp.deviceToken.id;
  251. _this._toggleFormResult(false);
  252. _this._newAppPassword.select();
  253. _this._tokenName.val('');
  254. });
  255. $.when(creatingToken).fail(function () {
  256. OC.Notification.showTemporary(t('core', 'Error while creating device token'));
  257. });
  258. $.when(creatingToken).always(function () {
  259. _this._toggleAddingToken(false);
  260. });
  261. },
  262. _onNewTokenLoginNameFocus: function () {
  263. this._newAppLoginName.select();
  264. },
  265. _onNewTokenFocus: function () {
  266. this._newAppPassword.select();
  267. },
  268. _hideToken: function () {
  269. this._toggleFormResult(true);
  270. },
  271. _toggleAddingToken: function (state) {
  272. this._addingToken = state;
  273. this._addAppPasswordBtn.toggleClass('icon-loading-small', state);
  274. },
  275. _onDeleteToken: function (event) {
  276. var $target = $(event.target);
  277. var $row = $target.closest('tr');
  278. var id = $row.data('id');
  279. if (id === this._newAppId) {
  280. this._toggleFormResult(true);
  281. }
  282. var token = this.collection.get(id);
  283. if (_.isUndefined(token)) {
  284. // Ignore event
  285. return;
  286. }
  287. var destroyingToken = token.destroy();
  288. $row.find('.icon-delete').tooltip('hide');
  289. var _this = this;
  290. $.when(destroyingToken).fail(function () {
  291. OC.Notification.showTemporary(t('core', 'Error while deleting the token'));
  292. });
  293. $.when(destroyingToken).always(function () {
  294. _this.render();
  295. });
  296. },
  297. _toggleFormResult: function (showForm) {
  298. if (showForm) {
  299. this._result.slideUp();
  300. this._form.slideDown();
  301. } else {
  302. this._form.slideUp();
  303. this._result.slideDown();
  304. }
  305. }
  306. });
  307. OC.Settings.AuthTokenView = AuthTokenView;
  308. })(OC, _, $, Handlebars, moment);