PageRenderTime 28ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/openid-connect-server-webapp/src/main/webapp/resources/js/client.js

https://gitlab.com/jslee1/OpenID-Connect-Java-Spring-Server
JavaScript | 1229 lines | 913 code | 220 blank | 96 comment | 159 complexity | 9329766482282ce3bc3c5a222ddcf986 MD5 | raw file
  1. /*******************************************************************************
  2. * Copyright 2016 The MITRE Corporation
  3. * and the MIT Internet Trust Consortium
  4. *
  5. * Licensed under the Apache License, Version 2.0 (the "License");
  6. * you may not use this file except in compliance with the License.
  7. * You may obtain a copy of the License at
  8. *
  9. * http://www.apache.org/licenses/LICENSE-2.0
  10. *
  11. * Unless required by applicable law or agreed to in writing, software
  12. * distributed under the License is distributed on an "AS IS" BASIS,
  13. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. * See the License for the specific language governing permissions and
  15. * limitations under the License.
  16. *******************************************************************************/
  17. var ClientModel = Backbone.Model.extend({
  18. idAttribute: "id",
  19. initialize: function () {
  20. // bind validation errors to dom elements
  21. // this will display form elements in red if they are not valid
  22. this.bind('error', function(model, errs) {
  23. _.map(errs, function (val, elID) {
  24. $('#' + elID).addClass('error');
  25. });
  26. });
  27. },
  28. // We can pass it default values.
  29. defaults:{
  30. id:null,
  31. clientId:"",
  32. clientSecret:"",
  33. redirectUris:[],
  34. clientName:null,
  35. clientUri:"",
  36. logoUri:"",
  37. contacts:[],
  38. tosUri:"",
  39. tokenEndpointAuthMethod:null,
  40. scope:[],
  41. grantTypes:[],
  42. responseTypes:[],
  43. policyUri:"",
  44. jwksUri:"",
  45. jwks:null,
  46. jwksType:"URI",
  47. applicationType:null,
  48. sectorIdentifierUri:"",
  49. subjectType:null,
  50. requestObjectSigningAlg:null,
  51. userInfoSignedResponseAlg:null,
  52. userInfoEncryptedResponseAlg:null,
  53. userInfoEncryptedResponseEnc:null,
  54. idTokenSignedResponseAlg:null,
  55. idTokenEncryptedResponseAlg:null,
  56. idTokenEncryptedResponseEnc:null,
  57. tokenEndpointAuthSigningAlg:null,
  58. defaultMaxAge:null,
  59. requireAuthTime:false,
  60. defaultACRvalues:null,
  61. initiateLoginUri:"",
  62. postLogoutRedirectUris:[],
  63. requestUris:[],
  64. authorities:[],
  65. accessTokenValiditySeconds: null,
  66. refreshTokenValiditySeconds: null,
  67. resourceIds:[],
  68. //additionalInformation?
  69. claimsRedirectUris:[],
  70. clientDescription:"",
  71. reuseRefreshToken:true,
  72. clearAccessTokensOnRefresh:true,
  73. dynamicallyRegistered:false,
  74. allowIntrospection:false,
  75. idTokenValiditySeconds: null,
  76. createdAt:null,
  77. allowRefresh:false,
  78. displayClientSecret: false,
  79. generateClientSecret: false,
  80. },
  81. urlRoot:"api/clients",
  82. matches:function(term) {
  83. var matches = [];
  84. if (term) {
  85. if (this.get('clientId').toLowerCase().indexOf(term.toLowerCase()) != -1) {
  86. matches.push($.t('client.client-table.match.id'));
  87. }
  88. if (this.get('clientName') != null && this.get('clientName').toLowerCase().indexOf(term.toLowerCase()) != -1) {
  89. matches.push($.t('client.client-table.match.name'));
  90. }
  91. if (this.get('clientDescription') != null && this.get('clientDescription').toLowerCase().indexOf(term.toLowerCase()) != -1) {
  92. matches.push($.t('client.client-table.match.description'));
  93. }
  94. if (this.get('clientUri') != null && this.get('clientUri').toLowerCase().indexOf(term.toLowerCase()) != -1) {
  95. matches.push($.t('client.client-table.match.homepage'));
  96. }
  97. if (this.get('policyUri') != null && this.get('policyUri').toLowerCase().indexOf(term.toLowerCase()) != -1) {
  98. matches.push($.t('client.client-table.match.policy'));
  99. }
  100. if (this.get('tosUri') != null && this.get('tosUri').toLowerCase().indexOf(term.toLowerCase()) != -1) {
  101. matches.push($.t('client.client-table.match.terms'));
  102. }
  103. if (this.get('logoUri') != null && this.get('logoUri').toLowerCase().indexOf(term.toLowerCase()) != -1) {
  104. matches.push($.t('client.client-table.match.logo'));
  105. }
  106. if (this.get('contacts') != null) {
  107. var f = _.filter(this.get('contacts'), function(item) {
  108. return item.toLowerCase().indexOf(term.toLowerCase()) != -1;
  109. });
  110. if (f.length > 0) {
  111. matches.push($.t('client.client-table.match.contacts'));
  112. }
  113. }
  114. if (this.get('redirectUris') != null) {
  115. var f = _.filter(this.get('redirectUris'), function (item) {
  116. return item.toLowerCase().indexOf(term.toLowerCase()) != -1;
  117. });
  118. if (f.length > 0) {
  119. matches.push($.t('client.client-table.match.redirect'));
  120. }
  121. }
  122. if (this.get('scope') != null) {
  123. var f = _.filter(this.get('scope'), function (item) {
  124. return item.toLowerCase().indexOf(term.toLowerCase()) != -1;
  125. });
  126. if (f.length > 0) {
  127. matches.push($.t('client.client-table.match.scope'));
  128. }
  129. }
  130. } else {
  131. // there's no search term, we always match
  132. this.unset('matches', {silent: true});
  133. //console.log('no term');
  134. return true;
  135. }
  136. var matchString = matches.join(' | ');
  137. if (matches.length > 0) {
  138. this.set({
  139. matches: matchString
  140. }, {silent: true});
  141. return true;
  142. } else {
  143. this.unset('matches', {silent: true});
  144. return false;
  145. }
  146. }
  147. });
  148. var RegistrationTokenModel = Backbone.Model.extend({
  149. idAttribute: 'clientId',
  150. urlRoot: 'api/tokens/registration'
  151. });
  152. var ClientCollection = Backbone.Collection.extend({
  153. initialize: function() {
  154. //this.fetch();
  155. },
  156. model:ClientModel,
  157. url:"api/clients",
  158. getByClientId: function(clientId) {
  159. var clients = this.where({clientId: clientId});
  160. if (clients.length == 1) {
  161. return clients[0];
  162. } else {
  163. return null;
  164. }
  165. }
  166. });
  167. var ClientView = Backbone.View.extend({
  168. tagName: 'tr',
  169. initialize:function (options) {
  170. this.options = options;
  171. if (!this.template) {
  172. this.template = _.template($('#tmpl-client-table-item').html());
  173. }
  174. if (!this.scopeTemplate) {
  175. this.scopeTemplate = _.template($('#tmpl-scope-list').html());
  176. }
  177. if (!this.moreInfoTemplate) {
  178. this.moreInfoTemplate = _.template($('#tmpl-client-more-info-block').html());
  179. }
  180. if (!this.registrationTokenTemplate) {
  181. this.registrationTokenTemplate = _.template($('#tmpl-client-registration-token').html());
  182. }
  183. this.model.bind('change', this.render, this);
  184. },
  185. render:function (eventName) {
  186. var creationDate = this.model.get('createdAt');
  187. var displayCreationDate = $.t('client.client-table.unknown');
  188. var hoverCreationDate = "";
  189. if (creationDate == null || !moment(creationDate).isValid()) {
  190. displayCreationDate = $.t('client.client-table.unknown');
  191. hoverCreationDate = "";
  192. } else {
  193. creationDate = moment(creationDate);
  194. if (moment().diff(creationDate, 'months') < 6) {
  195. displayCreationDate = creationDate.fromNow();
  196. } else {
  197. displayCreationDate = "on " + creationDate.format("LL");
  198. }
  199. hoverCreationDate = creationDate.format("LLL");
  200. }
  201. var json = {client: this.model.toJSON(), count: this.options.count, whiteList: this.options.whiteList,
  202. displayCreationDate: displayCreationDate, hoverCreationDate: hoverCreationDate};
  203. this.$el.html(this.template(json));
  204. $('.scope-list', this.el).html(this.scopeTemplate({scopes: this.model.get('scope'), systemScopes: this.options.systemScopeList}));
  205. $('.client-more-info-block', this.el).html(this.moreInfoTemplate({client: this.model.toJSON()}));
  206. $('.clientid-full', this.el).hide();
  207. this.$('.dynamically-registered').tooltip({title: $.t('client.client-table.dynamically-registered-tooltip')});
  208. this.$('.allow-introspection').tooltip({title: $.t('client.client-table.allow-introspection-tooltip')});
  209. this.updateMatched();
  210. $(this.el).i18n();
  211. return this;
  212. },
  213. showRegistrationToken:function(e) {
  214. e.preventDefault();
  215. $('#modalAlertLabel').html($.t('client.client-form.registration-access-token'));
  216. var token = new RegistrationTokenModel({clientId: this.model.get('clientId')});
  217. var _self = this;
  218. token.fetch({success:function() {
  219. var savedModel = {
  220. clientId: _self.model.get('clientId'),
  221. registrationToken: token.get('value')
  222. };
  223. $('#modalAlert .modal-body').html(_self.registrationTokenTemplate(savedModel));
  224. $('#modalAlert .modal-body #rotate-token').click(function(e) {
  225. if (confirm($.t('client.client-form.rotate-registration-token-confirm'))) {
  226. token.save(null, {success: function() {
  227. console.log('token:' + token.get('value'));
  228. $('#modalAlert .modal-body #registrationToken').val(token.get('value'));
  229. },
  230. error: function() {
  231. $('#modalAlert').i18n();
  232. $('#modalAlert .modal-body').html($t('client.client-form.rotate-registration-token-error'));
  233. $('#modalAlert').modal({
  234. 'backdrop': 'static',
  235. 'keyboard': true,
  236. 'show': true
  237. });
  238. }
  239. });
  240. }
  241. });
  242. $('#modalAlert').i18n();
  243. $('#modalAlert').modal({
  244. 'backdrop': 'static',
  245. 'keyboard': true,
  246. 'show': true
  247. });
  248. },
  249. error:function() {
  250. $('#modalAlert').i18n();
  251. $('#modalAlert .modal-body').html($t('client.client-form.registration-token-error'));
  252. $('#modalAlert').modal({
  253. 'backdrop': 'static',
  254. 'keyboard': true,
  255. 'show': true
  256. });
  257. }
  258. });
  259. },
  260. updateMatched:function() {
  261. //console.log(this.model.get('matches'));
  262. if (this.model.get('matches')) {
  263. $('.matched', this.el).show();
  264. $('.matched span', this.el).html(this.model.get('matches'));
  265. } else {
  266. $('.matched', this.el).hide();
  267. }
  268. },
  269. events:{
  270. "click .btn-edit":"editClient",
  271. "click .btn-delete":"deleteClient",
  272. "click .btn-whitelist":"whiteListClient",
  273. 'click .toggleMoreInformation': 'toggleMoreInformation',
  274. "click .clientid-substring":"showClientId",
  275. "click .dynamically-registered": 'showRegistrationToken'
  276. },
  277. editClient:function (e) {
  278. e.preventDefault();
  279. app.navigate('admin/client/' + this.model.id, {trigger: true});
  280. },
  281. whiteListClient:function(e) {
  282. e.preventDefault();
  283. if (this.options.whiteList == null) {
  284. // create a new one
  285. app.navigate('admin/whitelist/new/' + this.model.get('id'), {trigger: true});
  286. } else {
  287. // edit the existing one
  288. app.navigate('admin/whitelist/' + this.options.whiteList.get('id'), {trigger: true});
  289. }
  290. },
  291. deleteClient:function (e) {
  292. e.preventDefault();
  293. if (confirm($.t('client.client-table.confirm'))) {
  294. var _self = this;
  295. this.model.destroy({
  296. dataType: false, processData: false,
  297. success:function () {
  298. _self.$el.fadeTo("fast", 0.00, function () { //fade
  299. $(this).slideUp("fast", function () { //slide up
  300. $(this).remove(); //then remove from the DOM
  301. _self.parentView.togglePlaceholder();
  302. });
  303. });
  304. },
  305. error:function (error, response) {
  306. console.log("An error occurred when deleting a client");
  307. //Pull out the response text.
  308. var responseJson = JSON.parse(response.responseText);
  309. //Display an alert with an error message
  310. $('#modalAlert div.modal-header').html(responseJson.error);
  311. $('#modalAlert div.modal-body').html(responseJson.error_description);
  312. $("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
  313. "backdrop" : "static",
  314. "keyboard" : true,
  315. "show" : true // ensure the modal is shown immediately
  316. });
  317. }
  318. });
  319. _self.parentView.delegateEvents();
  320. }
  321. return false;
  322. },
  323. toggleMoreInformation:function(e) {
  324. e.preventDefault();
  325. if ($('.moreInformation', this.el).is(':visible')) {
  326. // hide it
  327. $('.moreInformationContainer', this.el).removeClass('alert').removeClass('alert-info').addClass('muted');
  328. $('.moreInformation', this.el).hide('fast');
  329. $('.toggleMoreInformation i', this.el).attr('class', 'icon-chevron-right');
  330. } else {
  331. // show it
  332. $('.moreInformationContainer', this.el).addClass('alert').addClass('alert-info').removeClass('muted');
  333. $('.moreInformation', this.el).show('fast');
  334. $('.toggleMoreInformation i', this.el).attr('class', 'icon-chevron-down');
  335. }
  336. },
  337. showClientId:function(e) {
  338. e.preventDefault();
  339. $('.clientid-full', this.el).show();
  340. },
  341. close:function () {
  342. $(this.el).unbind();
  343. $(this.el).empty();
  344. }
  345. });
  346. var ClientListView = Backbone.View.extend({
  347. tagName: 'span',
  348. initialize:function (options) {
  349. this.options = options;
  350. this.filteredModel = this.model;
  351. },
  352. load:function(callback) {
  353. if (this.model.isFetched &&
  354. this.options.whiteListList.isFetched &&
  355. this.options.stats.isFetched &&
  356. this.options.systemScopeList.isFetched) {
  357. callback();
  358. return;
  359. }
  360. $('#loadingbox').sheet('show');
  361. $('#loading').html(
  362. '<span class="label" id="loading-clients">' + $.t("common.clients") + '</span> ' +
  363. '<span class="label" id="loading-whitelist">' + $.t("whitelist.whitelist") + '</span> ' +
  364. '<span class="label" id="loading-scopes">' + $.t("common.scopes") + '</span> ' +
  365. '<span class="label" id="loading-stats">' + $.t("common.statistics") + '</span> '
  366. );
  367. $.when(this.model.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}}),
  368. this.options.whiteListList.fetchIfNeeded({success:function(e) {$('#loading-whitelist').addClass('label-success');}}),
  369. this.options.stats.fetchIfNeeded({success:function(e) {$('#loading-stats').addClass('label-success');}}),
  370. this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
  371. .done(function() {
  372. $('#loadingbox').sheet('hide');
  373. callback();
  374. });
  375. },
  376. events:{
  377. "click .new-client":"newClient",
  378. "click .refresh-table":"refreshTable",
  379. 'keyup .search-query':'searchTable',
  380. 'click .form-search button':'clearSearch',
  381. 'page .paginator':'changePage'
  382. },
  383. newClient:function (e) {
  384. e.preventDefault();
  385. this.remove();
  386. app.navigate('admin/client/new', {trigger: true});
  387. },
  388. render:function (eventName) {
  389. // append and render table structure
  390. $(this.el).html($('#tmpl-client-table').html());
  391. this.renderInner();
  392. $(this.el).i18n();
  393. return this;
  394. },
  395. renderInner:function(eventName) {
  396. // render the rows
  397. _.each(this.filteredModel.models, function (client, index) {
  398. var view = new ClientView({
  399. model:client,
  400. count:this.options.stats.get(client.get('id')),
  401. systemScopeList: this.options.systemScopeList,
  402. whiteList: this.options.whiteListList.getByClientId(client.get('clientId'))
  403. });
  404. view.parentView = this;
  405. var element = view.render().el;
  406. $("#client-table",this.el).append(element);
  407. if (Math.ceil((index + 1) / 10) != 1) {
  408. $(element).hide();
  409. }
  410. }, this);
  411. this.togglePlaceholder();
  412. },
  413. togglePlaceholder:function() {
  414. // set up pagination
  415. var numPages = Math.ceil(this.filteredModel.length / 10);
  416. if (numPages > 1) {
  417. $('.paginator', this.el).show();
  418. $('.paginator', this.el).bootpag({
  419. total: numPages,
  420. maxVisible: 10,
  421. leaps: false,
  422. page: 1
  423. });
  424. } else {
  425. $('.paginator', this.el).hide();
  426. }
  427. if (this.filteredModel.length > 0) {
  428. $('#client-table', this.el).show();
  429. $('#client-table-empty', this.el).hide();
  430. $('#client-table-search-empty', this.el).hide();
  431. } else {
  432. if (this.model.length > 0) {
  433. // there's stuff in the model but it's been filtered out
  434. $('#client-table', this.el).hide();
  435. $('#client-table-empty', this.el).hide();
  436. $('#client-table-search-empty', this.el).show();
  437. } else {
  438. // we're empty
  439. $('#client-table', this.el).hide();
  440. $('#client-table-empty', this.el).show();
  441. $('#client-table-search-empty', this.el).hide();
  442. }
  443. }
  444. },
  445. changePage:function(event, num) {
  446. $('.paginator', this.el).bootpag({page:num});
  447. $('#client-table tbody tr', this.el).each(function(index, element) {
  448. if (Math.ceil((index + 1) / 10) != num) {
  449. $(element).hide();
  450. } else {
  451. $(element).show();
  452. }
  453. });
  454. },
  455. refreshTable:function(e) {
  456. e.preventDefault();
  457. $('#loadingbox').sheet('show');
  458. $('#loading').html(
  459. '<span class="label" id="loading-clients">' + $.t("common.clients") + '</span> ' +
  460. '<span class="label" id="loading-whitelist">' + $.t("whitelist.whitelist") + '</span> ' +
  461. '<span class="label" id="loading-scopes">' + $.t("common.scopes") + '</span> ' +
  462. '<span class="label" id="loading-stats">' + $.t("common.statistics") + '</span> '
  463. );
  464. var _self = this;
  465. $.when(this.model.fetch({success:function(e) {$('#loading-clients').addClass('label-success');}}),
  466. this.options.whiteListList.fetch({success:function(e) {$('#loading-whitelist').addClass('label-success');}}),
  467. this.options.stats.fetch({success:function(e) {$('#loading-stats').addClass('label-success');}}),
  468. this.options.systemScopeList.fetch({success:function(e) {$('#loading-scopes').addClass('label-success');}}))
  469. .done(function() {
  470. $('#loadingbox').sheet('hide');
  471. _self.render();
  472. });
  473. },
  474. searchTable:function(e) {
  475. var term = $('.search-query', this.el).val();
  476. this.filteredModel = new ClientCollection(this.model.filter(function(client) {
  477. return client.matches(term);
  478. }));
  479. // clear out the table
  480. $('tbody', this.el).html('');
  481. // re-render the table
  482. this.renderInner();
  483. },
  484. clearSearch:function(e) {
  485. $('.search-query', this.el).val('');
  486. this.searchTable();
  487. }
  488. });
  489. var ClientFormView = Backbone.View.extend({
  490. tagName:"span",
  491. initialize:function (options) {
  492. this.options = options;
  493. if (!this.template) {
  494. this.template = _.template($('#tmpl-client-form').html());
  495. }
  496. if (!this.clientSavedTemplate) {
  497. this.clientSavedTemplate = _.template($('#tmpl-client-saved').html());
  498. }
  499. this.redirectUrisCollection = new Backbone.Collection();
  500. this.scopeCollection = new Backbone.Collection();
  501. this.contactsCollection = new Backbone.Collection();
  502. this.defaultAcrValuesCollection = new Backbone.Collection();
  503. this.requestUrisCollection = new Backbone.Collection();
  504. this.postLogoutRedirectUrisCollection = new Backbone.Collection();
  505. this.claimsRedirectUrisCollection = new Backbone.Collection();
  506. // TODO: add Spring authorities collection and resource IDs collection?
  507. // collection of sub-views that need to be sync'd on save
  508. this.listWidgetViews = [];
  509. },
  510. events:{
  511. "click .btn-save":"saveClient",
  512. "click #allowRefresh" : "toggleRefreshTokenTimeout",
  513. "click #disableAccessTokenTimeout" : function() {
  514. $("#access-token-timeout-time", this.$el).prop('disabled',!$("#access-token-timeout-time", this.$el).prop('disabled'));
  515. $("#access-token-timeout-unit", this.$el).prop('disabled',!$("#access-token-timeout-unit", this.$el).prop('disabled'));
  516. document.getElementById("access-token-timeout-time").value = '';
  517. },
  518. "click #disableRefreshTokenTimeout" : function() {
  519. $("#refresh-token-timeout-time", this.$el).prop('disabled',!$("#refresh-token-timeout-time", this.$el).prop('disabled'));
  520. $("#refresh-token-timeout-unit", this.$el).prop('disabled',!$("#refresh-token-timeout-unit", this.$el).prop('disabled'));
  521. document.getElementById("refresh-token-timeout-time").value = '';
  522. },
  523. "click .btn-cancel":"cancel",
  524. "change #tokenEndpointAuthMethod input:radio":"toggleClientCredentials",
  525. "change #displayClientSecret":"toggleDisplayClientSecret",
  526. "change #generateClientSecret":"toggleGenerateClientSecret",
  527. "change #logoUri input":"previewLogo",
  528. "change #jwkSelector input:radio":"toggleJWKSetType"
  529. },
  530. cancel:function(e) {
  531. e.preventDefault();
  532. app.navigate('admin/clients', {trigger: true});
  533. },
  534. load:function(callback) {
  535. if (this.model.isFetched &&
  536. this.options.systemScopeList.isFetched) {
  537. callback();
  538. return;
  539. }
  540. $('#loadingbox').sheet('show');
  541. $('#loading').html(
  542. '<span class="label" id="loading-clients">' + $.t('common.clients') + '</span> ' +
  543. '<span class="label" id="loading-scopes">' + $.t("common.scopes") + '</span> '
  544. );
  545. $.when(this.options.systemScopeList.fetchIfNeeded({success:function(e) {$('#loading-scopes').addClass('label-success');}}),
  546. this.model.fetchIfNeeded({success:function(e) {$('#loading-clients').addClass('label-success');}}))
  547. .done(function() {
  548. $('#loadingbox').sheet('hide');
  549. callback();
  550. });
  551. },
  552. toggleRefreshTokenTimeout:function () {
  553. $("#refreshTokenValidityTime", this.$el).toggle();
  554. },
  555. previewLogo:function() {
  556. if ($('#logoUri input', this.el).val()) {
  557. $('#logoPreview', this.el).empty();
  558. $('#logoPreview', this.el).attr('src', $('#logoUri input', this.el).val());
  559. } else {
  560. //$('#logoBlock', this.el).hide();
  561. $('#logoPreview', this.el).attr('src', 'resources/images/logo_placeholder.gif');
  562. }
  563. },
  564. /**
  565. * Set up the form based on the current state of the tokenEndpointAuthMethod parameter
  566. * @param event
  567. */
  568. toggleClientCredentials:function() {
  569. var tokenEndpointAuthMethod = $('#tokenEndpointAuthMethod input', this.el).filter(':checked').val();
  570. if (tokenEndpointAuthMethod == 'SECRET_BASIC'
  571. || tokenEndpointAuthMethod == 'SECRET_POST'
  572. || tokenEndpointAuthMethod == 'SECRET_JWT') {
  573. // client secret is required, show all the bits
  574. $('#clientSecretPanel', this.el).show();
  575. // this function sets up the display portions
  576. this.toggleGenerateClientSecret();
  577. } else {
  578. // no client secret, hide all the bits
  579. $('#clientSecretPanel', this.el).hide();
  580. }
  581. // show or hide the signing algorithm method depending on what's selected
  582. if (tokenEndpointAuthMethod == 'PRIVATE_KEY'
  583. || tokenEndpointAuthMethod == 'SECRET_JWT') {
  584. $('#tokenEndpointAuthSigningAlg', this.el).show();
  585. } else {
  586. $('#tokenEndpointAuthSigningAlg', this.el).hide();
  587. }
  588. },
  589. /**
  590. * Set up the form based on the JWK Set selector
  591. */
  592. toggleJWKSetType:function() {
  593. var jwkSelector = $('#jwkSelector input:radio', this.el).filter(':checked').val();
  594. if (jwkSelector == 'URI') {
  595. $('#jwksUri', this.el).show();
  596. $('#jwks', this.el).hide();
  597. } else if (jwkSelector == 'VAL') {
  598. $('#jwksUri', this.el).hide();
  599. $('#jwks', this.el).show();
  600. } else {
  601. $('#jwksUri', this.el).hide();
  602. $('#jwks', this.el).hide();
  603. }
  604. },
  605. /**
  606. * Set up the form based on the "Generate" checkbox
  607. * @param event
  608. */
  609. toggleGenerateClientSecret:function() {
  610. if ($('#generateClientSecret input', this.el).is(':checked')) {
  611. // show the "generated" block, hide the "display" checkbox
  612. $('#displayClientSecret', this.el).hide();
  613. $('#clientSecret', this.el).hide();
  614. $('#clientSecretGenerated', this.el).show();
  615. $('#clientSecretHidden', this.el).hide();
  616. } else {
  617. // show the display checkbox, fall back to the "display" logic
  618. $('#displayClientSecret', this.el).show();
  619. this.toggleDisplayClientSecret();
  620. }
  621. },
  622. /**
  623. * Handle whether or not to display the client secret
  624. * @param event
  625. */
  626. toggleDisplayClientSecret:function() {
  627. if ($('#displayClientSecret input').is(':checked')) {
  628. // want to display it
  629. $('#clientSecret', this.el).show();
  630. $('#clientSecretHidden', this.el).hide();
  631. $('#clientSecretGenerated', this.el).hide();
  632. } else {
  633. // want to hide it
  634. $('#clientSecret', this.el).hide();
  635. $('#clientSecretHidden', this.el).show();
  636. $('#clientSecretGenerated', this.el).hide();
  637. }
  638. },
  639. // rounds down to the nearest integer value in seconds.
  640. getFormTokenNumberValue:function(value, timeUnit) {
  641. if (value == "") {
  642. return null;
  643. } else if (timeUnit == 'hours') {
  644. return parseInt(parseFloat(value) * 3600);
  645. } else if (timeUnit == 'minutes') {
  646. return parseInt(parseFloat(value) * 60);
  647. } else { // seconds
  648. return parseInt(value);
  649. }
  650. },
  651. // returns "null" if given the value "default" as a string, otherwise returns input value. useful for parsing the JOSE algorithm dropdowns
  652. defaultToNull:function(value) {
  653. if (value == 'default') {
  654. return null;
  655. } else {
  656. return value;
  657. }
  658. },
  659. disableUnsupportedJOSEItems:function(serverSupported, query) {
  660. var supported = ['default'];
  661. if (serverSupported) {
  662. supported = _.union(supported, serverSupported);
  663. }
  664. $(query, this.$el).each(function(idx) {
  665. if(_.contains(supported, $(this).val())) {
  666. $(this).prop('disabled', false);
  667. } else {
  668. $(this).prop('disabled', true);
  669. }
  670. });
  671. },
  672. // maps from a form-friendly name to the real grant parameter name
  673. grantMap:{
  674. 'authorization_code': 'authorization_code',
  675. 'password': 'password',
  676. 'implicit': 'implicit',
  677. 'client_credentials': 'client_credentials',
  678. 'redelegate': 'urn:ietf:params:oauth:grant_type:redelegate',
  679. 'refresh_token': 'refresh_token'
  680. },
  681. // maps from a form-friendly name to the real response type parameter name
  682. responseMap:{
  683. 'code': 'code',
  684. 'token': 'token',
  685. 'idtoken': 'id_token',
  686. 'token-idtoken': 'token id_token',
  687. 'code-idtoken': 'code id_token',
  688. 'code-token': 'code token',
  689. 'code-token-idtoken': 'code token id_token'
  690. },
  691. saveClient:function (event) {
  692. $('.control-group').removeClass('error');
  693. // sync any leftover collection items
  694. _.each(this.listWidgetViews, function(v) {
  695. v.addItem($.Event('click'));
  696. });
  697. // build the scope object
  698. var scopes = this.scopeCollection.pluck("item");
  699. // build the grant type object
  700. var grantTypes = [];
  701. $.each(this.grantMap, function(index,type) {
  702. if ($('#grantTypes-' + index).is(':checked')) {
  703. grantTypes.push(type);
  704. }
  705. });
  706. // build the response type object
  707. var responseTypes = [];
  708. $.each(this.responseMap, function(index,type) {
  709. if ($('#responseTypes-' + index).is(':checked')) {
  710. responseTypes.push(type);
  711. }
  712. });
  713. var generateClientSecret = $('#generateClientSecret input').is(':checked');
  714. var clientSecret = null;
  715. var tokenEndpointAuthMethod = $('#tokenEndpointAuthMethod input').filter(':checked').val();
  716. // whether or not the client secret changed
  717. var secretChanged = false;
  718. if (tokenEndpointAuthMethod == 'SECRET_BASIC'
  719. || tokenEndpointAuthMethod == 'SECRET_POST'
  720. || tokenEndpointAuthMethod == 'SECRET_JWT') {
  721. if (!generateClientSecret) {
  722. // if it's required but we're not generating it, send the value to preserve it
  723. clientSecret = $('#clientSecret input').val();
  724. // if it's not the same as before, offer to display it
  725. if (clientSecret != this.model.get('clientSecret')) {
  726. secretChanged = true;
  727. }
  728. } else {
  729. // it's being generated anew
  730. secretChanged = true;
  731. }
  732. }
  733. var accessTokenValiditySeconds = null;
  734. if (!$('disableAccessTokenTimeout').is(':checked')) {
  735. accessTokenValiditySeconds = this.getFormTokenNumberValue($('#accessTokenValidityTime input[type=text]').val(), $('#accessTokenValidityTime select').val());
  736. }
  737. var idTokenValiditySeconds = this.getFormTokenNumberValue($('#idTokenValidityTime input[type=text]').val(), $('#idTokenValidityTime select').val());
  738. var refreshTokenValiditySeconds = null;
  739. if ($('#allowRefresh').is(':checked')) {
  740. if ($.inArray('refresh_token', grantTypes) == -1) {
  741. grantTypes.push('refresh_token');
  742. }
  743. if ($.inArray('offline_access', scopes) == -1) {
  744. scopes.push("offline_access");
  745. }
  746. if (!$('disableRefreshTokenTimeout').is(':checked')) {
  747. refreshTokenValiditySeconds = this.getFormTokenNumberValue($('#refreshTokenValidityTime input[type=text]').val(), $('#refreshTokenValidityTime select').val());
  748. }
  749. }
  750. // make sure that the subject identifier is consistent with the redirect URIs
  751. var subjectType = $('#subjectType input').filter(':checked').val();
  752. var redirectUris = this.redirectUrisCollection.pluck("item");
  753. var sectorIdentifierUri = $('#sectorIdentifierUri input').val();
  754. if (subjectType == 'PAIRWISE' && redirectUris.length > 1 && sectorIdentifierUri == '') {
  755. //Display an alert with an error message
  756. $('#modalAlert div.modal-header').html("Consistency error");
  757. $('#modalAlert div.modal-body').html("Pairwise identifiers cannot be used with multiple redirect URIs unless a sector identifier URI is also registered.");
  758. $("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
  759. "backdrop" : "static",
  760. "keyboard" : true,
  761. "show" : true // ensure the modal is shown immediately
  762. });
  763. return false;
  764. }
  765. // process the JWKS
  766. var jwksUri = null;
  767. var jwks = null;
  768. var jwkSelector = $('#jwkSelector input:radio', this.el).filter(':checked').val();
  769. if (jwkSelector == 'URI') {
  770. jwksUri = $('#jwksUri input').val();
  771. jwks = null;
  772. } else if (jwkSelector == 'VAL') {
  773. jwksUri = null;
  774. try {
  775. jwks = JSON.parse($('#jwks textarea').val());
  776. } catch (e) {
  777. console.log("An error occurred when parsing the JWK Set");
  778. //Display an alert with an error message
  779. $('#modalAlert div.modal-header').html("JWK Set Error");
  780. $('#modalAlert div.modal-body').html("There was an error parsing the public key from the JSON Web Key set. Check the value and try again.");
  781. $("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
  782. "backdrop" : "static",
  783. "keyboard" : true,
  784. "show" : true // ensure the modal is shown immediately
  785. });
  786. return false;
  787. }
  788. } else {
  789. jwksUri = null;
  790. jwks = null;
  791. }
  792. var attrs = {
  793. clientName:$('#clientName input').val(),
  794. clientId:$('#clientId input').val(),
  795. clientSecret: clientSecret,
  796. generateClientSecret:generateClientSecret,
  797. redirectUris: redirectUris,
  798. clientDescription:$('#clientDescription textarea').val(),
  799. logoUri:$('#logoUri input').val(),
  800. grantTypes: grantTypes,
  801. accessTokenValiditySeconds: accessTokenValiditySeconds,
  802. refreshTokenValiditySeconds: refreshTokenValiditySeconds,
  803. idTokenValiditySeconds: idTokenValiditySeconds,
  804. allowRefresh: $('#allowRefresh').is(':checked'),
  805. allowIntrospection: $('#allowIntrospection input').is(':checked'), // <-- And here? --^
  806. scope: scopes,
  807. tosUri: $('#tosUri input').val(),
  808. policyUri: $('#policyUri input').val(),
  809. clientUri: $('#clientUri input').val(),
  810. applicationType: $('#applicationType input').filter(':checked').val(),
  811. jwksUri: jwksUri,
  812. jwks: jwks,
  813. subjectType: subjectType,
  814. tokenEndpointAuthMethod: tokenEndpointAuthMethod,
  815. responseTypes: responseTypes,
  816. sectorIdentifierUri: sectorIdentifierUri,
  817. initiateLoginUri: $('#initiateLoginUri input').val(),
  818. postLogoutRedirectUris: this.postLogoutRedirectUrisCollection.pluck('item'),
  819. claimsRedirectUris: this.claimsRedirectUrisCollection.pluck('item'),
  820. reuseRefreshToken: $('#reuseRefreshToken').is(':checked'),
  821. clearAccessTokensOnRefresh: $('#clearAccessTokensOnRefresh').is(':checked'),
  822. requireAuthTime: $('#requireAuthTime input').is(':checked'),
  823. defaultMaxAge: parseInt($('#defaultMaxAge input').val()),
  824. contacts: this.contactsCollection.pluck('item'),
  825. requestUris: this.requestUrisCollection.pluck('item'),
  826. defaultAcrValues: this.defaultAcrValuesCollection.pluck('item'),
  827. requestObjectSigningAlg: this.defaultToNull($('#requestObjectSigningAlg select').val()),
  828. userInfoSignedResponseAlg: this.defaultToNull($('#userInfoSignedResponseAlg select').val()),
  829. userInfoEncryptedResponseAlg: this.defaultToNull($('#userInfoEncryptedResponseAlg select').val()),
  830. userInfoEncryptedResponseEnc: this.defaultToNull($('#userInfoEncryptedResponseEnc select').val()),
  831. idTokenSignedResponseAlg: this.defaultToNull($('#idTokenSignedResponseAlg select').val()),
  832. idTokenEncryptedResponseAlg: this.defaultToNull($('#idTokenEncryptedResponseAlg select').val()),
  833. idTokenEncryptedResponseEnc: this.defaultToNull($('#idTokenEncryptedResponseEnc select').val()),
  834. tokenEndpointAuthSigningAlg: this.defaultToNull($('#tokenEndpointAuthSigningAlg select').val())
  835. };
  836. // post-validate
  837. if (attrs["allowRefresh"] == false) {
  838. attrs["refreshTokenValiditySeconds"] = null;
  839. }
  840. if ($('#disableAccessTokenTimeout').is(':checked')) {
  841. attrs["accessTokenValiditySeconds"] = null;
  842. }
  843. if ($('#disableRefreshTokenTimeout').is(':checked')) {
  844. attrs["refreshTokenValiditySeconds"] = null;
  845. }
  846. // set all empty strings to nulls
  847. for (var key in attrs) {
  848. if (attrs[key] === "") {
  849. attrs[key] = null;
  850. }
  851. }
  852. var _self = this;
  853. this.model.save(attrs, {
  854. success:function () {
  855. $('#modalAlertLabel').html($.t('client.client-form.saved.saved'));
  856. var savedModel = {
  857. clientId: _self.model.get('clientId'),
  858. clientSecret: _self.model.get('clientSecret'),
  859. secretChanged: secretChanged
  860. };
  861. $('#modalAlert .modal-body').html(_self.clientSavedTemplate(savedModel));
  862. $('#modalAlert .modal-body #savedClientSecret').hide();
  863. $('#modalAlert').on('click', '#clientSaveShow', function(event) {
  864. event.preventDefault();
  865. $('#clientSaveShow').hide();
  866. $('#savedClientSecret').show();
  867. });
  868. $('#modalAlert').i18n();
  869. $('#modalAlert').modal({
  870. 'backdrop': 'static',
  871. 'keyboard': true,
  872. 'show': true
  873. });
  874. app.clientList.add(_self.model);
  875. app.navigate('admin/clients', {trigger:true});
  876. },
  877. error:function (error, response) {
  878. console.log("An error occurred when saving a client");
  879. //Pull out the response text.
  880. var responseJson = JSON.parse(response.responseText);
  881. //Display an alert with an error message
  882. $('#modalAlert div.modal-header').html(responseJson.error);
  883. $('#modalAlert div.modal-body').html(responseJson.error_description);
  884. $("#modalAlert").modal({ // wire up the actual modal functionality and show the dialog
  885. "backdrop" : "static",
  886. "keyboard" : true,
  887. "show" : true // ensure the modal is shown immediately
  888. });
  889. }
  890. });
  891. return false;
  892. },
  893. render:function (eventName) {
  894. var data = {client: this.model.toJSON(), heartMode: heartMode};
  895. $(this.el).html(this.template(data));
  896. var _self = this;
  897. // clear the sub-view collection
  898. this.listWidgetViews = [];
  899. // build and bind registered redirect URI collection and view
  900. _.each(this.model.get("redirectUris"), function (redirectUri) {
  901. _self.redirectUrisCollection.add(new URIModel({item:redirectUri}));
  902. });
  903. var redirUriView = new ListWidgetView({
  904. type:'uri',
  905. placeholder: 'https://',
  906. helpBlockText: $.t('client.client-form.redirect-uris-help'),
  907. collection: this.redirectUrisCollection});
  908. $("#redirectUris .controls",this.el).html(redirUriView.render().el);
  909. this.listWidgetViews.push(redirUriView);
  910. // build and bind scopes
  911. _.each(this.model.get("scope"), function (scope) {
  912. _self.scopeCollection.add(new Backbone.Model({item:scope}));
  913. });
  914. var scopeView = new ListWidgetView({
  915. placeholder: $.t('client.client-form.scope-placeholder'),
  916. autocomplete: _.uniq(_.flatten(this.options.systemScopeList.pluck("value"))),
  917. helpBlockText: $.t('client.client-form.scope-help'),
  918. collection: this.scopeCollection});
  919. $("#scope .controls",this.el).html(scopeView.render().el);
  920. this.listWidgetViews.push(scopeView);
  921. // build and bind contacts
  922. _.each(this.model.get('contacts'), function (contact) {
  923. _self.contactsCollection.add(new Backbone.Model({item:contact}));
  924. });
  925. var contactsView = new ListWidgetView({
  926. placeholder: $.t("client.client-form.contacts-placeholder"),
  927. helpBlockText: $.t("client.client-form.contacts-help"),
  928. collection: this.contactsCollection});
  929. $("#contacts .controls", this.el).html(contactsView.render().el);
  930. this.listWidgetViews.push(contactsView);
  931. // build and bind post-logout redirect URIs
  932. _.each(this.model.get('postLogoutRedirectUris'), function(postLogoutRedirectUri) {
  933. _self.postLogoutRedirectUrisCollection.add(new URIModel({item:postLogoutRedirectUri}));
  934. });
  935. var postLogoutRedirectUrisView = new ListWidgetView({
  936. type: 'uri',
  937. placeholder: 'https://',
  938. helpBlockText: $.t('client.client-form.post-logout-help'),
  939. collection: this.postLogoutRedirectUrisCollection});
  940. $('#postLogoutRedirectUris .controls', this.el).html(postLogoutRedirectUrisView.render().el);
  941. this.listWidgetViews.push(postLogoutRedirectUrisView);
  942. // build and bind claims redirect URIs
  943. _.each(this.model.get('claimsRedirectUris'), function(claimsRedirectUri) {
  944. _self.claimsRedirectUrisCollection.add(new URIModel({item:claimsRedirectUri}));
  945. });
  946. var claimsRedirectUrisView = new ListWidgetView({
  947. type: 'uri',
  948. placeholder: 'https://',
  949. helpBlockText: $.t('client.client-form.claims-redirect-uris-help'),
  950. collection: this.claimsRedirectUrisCollection});
  951. $('#claimsRedirectUris .controls', this.el).html(claimsRedirectUrisView.render().el);
  952. this.listWidgetViews.push(claimsRedirectUrisView);
  953. // build and bind request URIs
  954. _.each(this.model.get('requestUris'), function (requestUri) {
  955. _self.requestUrisCollection.add(new URIModel({item:requestUri}));
  956. });
  957. var requestUriView = new ListWidgetView({
  958. type: 'uri',
  959. placeholder: 'https://',
  960. helpBlockText: $.t('client.client-form.request-uri-help'),
  961. collection: this.requestUrisCollection});
  962. $('#requestUris .controls', this.el).html(requestUriView.render().el);
  963. this.listWidgetViews.push(requestUriView);
  964. // build and bind default ACR values
  965. _.each(this.model.get('defaultAcrValues'), function (defaultAcrValue) {
  966. _self.defaultAcrValuesCollection.add(new Backbone.Model({item:defaultAcrValue}));
  967. });
  968. var defaultAcrView = new ListWidgetView({
  969. placeholder: $.t('client.client-form.acr-values-placeholder'),
  970. // TODO: autocomplete from spec
  971. helpBlockText: $.t('client.client-form.acr-values-help'),
  972. collection: this.defaultAcrValuesCollection});
  973. $('#defaultAcrValues .controls', this.el).html(defaultAcrView.render().el);
  974. this.listWidgetViews.push(defaultAcrView);
  975. // build and bind
  976. // set up token fields
  977. if (!this.model.get("allowRefresh")) {
  978. $("#refreshTokenValidityTime", this.$el).hide();
  979. }
  980. if (this.model.get("accessTokenValiditySeconds") == null) {
  981. $("#access-token-timeout-time", this.$el).prop('disabled',true);
  982. $("#access-token-timeout-unit", this.$el).prop('disabled',true);
  983. }
  984. if (this.model.get("refreshTokenValiditySeconds") == null) {
  985. $("#refresh-token-timeout-time", this.$el).prop('disabled',true);
  986. $("#refresh-token-timeout-unit", this.$el).prop('disabled',true);
  987. }
  988. // toggle other dynamic fields
  989. this.toggleClientCredentials();
  990. this.previewLogo();
  991. this.toggleJWKSetType();
  992. // disable unsupported JOSE algorithms
  993. this.disableUnsupportedJOSEItems(app.serverConfiguration.request_object_signing_alg_values_supported, '#requestObjectSigningAlg option');
  994. this.disableUnsupportedJOSEItems(app.serverConfiguration.userinfo_signing_alg_values_supported, '#userInfoSignedResponseAlg option');
  995. this.disableUnsupportedJOSEItems(app.serverConfiguration.userinfo_encryption_alg_values_supported, '#userInfoEncryptedResponseAlg option');
  996. this.disableUnsupportedJOSEItems(app.serverConfiguration.userinfo_encryption_enc_values_supported, '#userInfoEncryptedResponseEnc option');
  997. this.disableUnsupportedJOSEItems(app.serverConfiguration.id_token_signing_alg_values_supported, '#idTokenSignedResponseAlg option');
  998. this.disableUnsupportedJOSEItems(app.serverConfiguration.id_token_encryption_alg_values_supported, '#idTokenEncryptedResponseAlg option');
  999. this.disableUnsupportedJOSEItems(app.serverConfiguration.id_token_encryption_enc_values_supported, '#idTokenEncryptedResponseEnc option');
  1000. this.disableUnsupportedJOSEItems(app.serverConfiguration.token_endpoint_auth_signing_alg_values_supported, '#tokenEndpointAuthSigningAlg option');
  1001. this.$('.nyi').clickover({
  1002. placement: 'right',
  1003. title: $.t('common.not-yet-implemented'),
  1004. content: $.t('common.not-yet-implemented-content')
  1005. });
  1006. $(this.el).i18n();
  1007. return this;
  1008. }
  1009. });