diff --git a/openid-connect-server/src/main/webapp/WEB-INF/tags/footer.tag b/openid-connect-server/src/main/webapp/WEB-INF/tags/footer.tag index a25b014da..0b7b2e197 100644 --- a/openid-connect-server/src/main/webapp/WEB-INF/tags/footer.tag +++ b/openid-connect-server/src/main/webapp/WEB-INF/tags/footer.tag @@ -5,9 +5,9 @@ ================================================== --> - - - + + + diff --git a/openid-connect-server/src/main/webapp/WEB-INF/tags/header.tag b/openid-connect-server/src/main/webapp/WEB-INF/tags/header.tag index aa5673b26..fa826a689 100644 --- a/openid-connect-server/src/main/webapp/WEB-INF/tags/header.tag +++ b/openid-connect-server/src/main/webapp/WEB-INF/tags/header.tag @@ -52,7 +52,7 @@ - + diff --git a/openid-connect-server/src/main/webapp/resources/js/admin.js b/openid-connect-server/src/main/webapp/resources/js/admin.js index ee21472c9..20088fee7 100644 --- a/openid-connect-server/src/main/webapp/resources/js/admin.js +++ b/openid-connect-server/src/main/webapp/resources/js/admin.js @@ -1,1645 +1,577 @@ - var URIModel = Backbone.Model.extend({ +var URIModel = Backbone.Model.extend({ - validate: function(attrs){ + validate: function(attrs){ - var expression = /^(?:([a-z0-9+.-]+:\/\/)((?:(?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*)@)?((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*)(:(?:\d*))?(\/(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*)?|([a-z0-9+.-]+:)(\/?(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9A-F]{2})+(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*)?)(\?(?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*)?(#(?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*)?$/i; - var regex = new RegExp(expression); + var expression = /^(?:([a-z0-9+.-]+:\/\/)((?:(?:[a-z0-9-._~!$&'()*+,;=:]|%[0-9A-F]{2})*)@)?((?:[a-z0-9-._~!$&'()*+,;=]|%[0-9A-F]{2})*)(:(?:\d*))?(\/(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*)?|([a-z0-9+.-]+:)(\/?(?:[a-z0-9-._~!$&'()*+,;=:@]|%[0-9A-F]{2})+(?:[a-z0-9-._~!$&'()*+,;=:@\/]|%[0-9A-F]{2})*)?)(\?(?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*)?(#(?:[a-z0-9-._~!$&'()*+,;=:\/?@]|%[0-9A-F]{2})*)?$/i; + var regex = new RegExp(expression); - if (attrs.item == null || !attrs.item.match(regex)) { - return "Invalid URI"; - } + if (attrs.item == null || !attrs.item.match(regex)) { + return "Invalid URI"; } + } - }); +}); - /* - * Backbone JS Reusable ListWidget - * Options - * { - * collection: Backbone JS Collection - * type: ('uri'|'default') - * autocomplete: ['item1','item2'] List of auto complete items - * } - * - */ - var ListWidgetChildView = Backbone.View.extend({ +/* +* Backbone JS Reusable ListWidget +* Options +* { +* collection: Backbone JS Collection +* type: ('uri'|'default') +* autocomplete: ['item1','item2'] List of auto complete items +* } +* + */ +var ListWidgetChildView = Backbone.View.extend({ - tagName: 'tr', + tagName: 'tr', - events:{ - "click .btn-delete":function (e) { - e.preventDefault(); - //this.$el.tooltip('delete'); - this.model.destroy(); - } - }, - - initialize:function () { - - if (!this.template) { - this.template = _.template($('#tmpl-list-widget-child').html()); - } - - this.model.bind('destroy', this.remove, this); - - }, - - render:function () { - this.$el.html(this.template(this.model.toJSON())); - - if (this.model.get('item').length > 30) { - this.$el.tooltip({title:this.model.get('item')}); - } - return this; - } - }); - - var ListWidgetView = Backbone.View.extend({ - - tagName: "table", - - childView:ListWidgetChildView, - - events:{ - "click .btn-add":"addItem", - "keypress input":function (e) { - // trap the enter key - if (e.which == 13) { - this.addItem(); - e.preventDefault(); - $("input", this.$el).focus(); - } - } - }, - - initialize:function () { - - if (!this.template) { - this.template = _.template($('#tmpl-list-widget').html()); - } - - this.$el.addClass("table table-condensed table-hover table-striped span4"); - this.collection.bind('add', this.render, this); - - }, - - addItem:function(e) { + events:{ + "click .btn-delete":function (e) { e.preventDefault(); + //this.$el.tooltip('delete'); + this.model.destroy(); + } + }, - var input_value = $("input", this.el).val().trim(); + initialize:function () { - var model; + if (!this.template) { + this.template = _.template($('#tmpl-list-widget-child').html()); + } - if (this.options.type == 'uri') { - model = new URIModel({item:input_value}); + this.model.bind('destroy', this.remove, this); + + }, + + render:function () { + this.$el.html(this.template(this.model.toJSON())); + + if (this.model.get('item').length > 30) { + this.$el.tooltip({title:this.model.get('item')}); + } + return this; + } +}); + +var ListWidgetView = Backbone.View.extend({ + + tagName: "table", + + childView:ListWidgetChildView, + + events:{ + "click .btn-add":"addItem", + "keypress input":function (e) { + // trap the enter key + if (e.which == 13) { + this.addItem(); + e.preventDefault(); + $("input", this.$el).focus(); + } + } + }, + + initialize:function () { + + if (!this.template) { + this.template = _.template($('#tmpl-list-widget').html()); + } + + this.$el.addClass("table table-condensed table-hover table-striped span4"); + this.collection.bind('add', this.render, this); + + }, + + addItem:function(e) { + e.preventDefault(); + + var input_value = $("input", this.el).val().trim(); + + var model; + + if (this.options.type == 'uri') { + model = new URIModel({item:input_value}); + } else { + model = new Backbone.Model({item:input_value}); + model.validate = function(attrs) { + if(!attrs.item) { + return "value can't be null"; + } + }; + } + + // if it's valid and doesn't already exist + if (model.get("item") != null && this.collection.where({item: input_value}).length < 1) { + this.collection.add(model); + } else { + // else add a visual error indicator + $(".control-group", this.el).addClass('error'); + } + }, + + render:function (eventName) { + + this.$el.html(this.template({placeholder:this.options.placeholder})); + + // bind autocomplete options + if (this.options.autocomplete) { + $('input', this.$el).typeahead({source:this.options.autocomplete}); + } + + _self = this; + + _.each(this.collection.models, function (model) { + var el = new this.childView({model:model}).render().el; + $("tbody", _self.el).append(el); + }, this); + + return this; + } + +}); + +var BlackListModel = Backbone.Model.extend({ + idAttribute: 'id', + + urlRoot: 'api/blacklist' +}); + +var BlackListCollection = Backbone.Collection.extend({ + initialize: function() { }, + + url: "api/blacklist" +}); + +var BreadCrumbView = Backbone.View.extend({ + + tagName: 'ul', + + initialize:function () { + + if (!this.template) { + this.template = _.template($('#tmpl-breadcrumbs').html()); + } + + this.$el.addClass('breadcrumb'); + + this.collection.bind('add', this.render, this); + }, + + render:function () { + + this.$el.empty(); + var parent = this; + + // go through each of the breadcrumb models + _.each(this.collection.models, function (crumb, index) { + + // if it's the last index in the crumbs then render the link inactive + if (index == parent.collection.size() - 1) { + crumb.set({active:true}, {silent:true}); } else { - model = new Backbone.Model({item:input_value}); - model.validate = function(attrs) { - if(!attrs.item) { - return "value can't be null"; - } - }; + crumb.set({active:false}, {silent:true}); } - // if it's valid and doesn't already exist - if (model.get("item") != null && this.collection.where({item: input_value}).length < 1) { - this.collection.add(model); - } else { - // else add a visual error indicator - $(".control-group", this.el).addClass('error'); - } - }, + this.$el.append(this.template(crumb.toJSON())); + }, this); - render:function (eventName) { + $('#breadcrumbs').html(this.el); + } +}); - this.$el.html(this.template({placeholder:this.options.placeholder})); - // bind autocomplete options - if (this.options.autocomplete) { - $('input', this.$el).typeahead({source:this.options.autocomplete}); - } - - _self = this; - - _.each(this.collection.models, function (model) { - var el = new this.childView({model:model}).render().el; - $("tbody", _self.el).append(el); - }, this); - - return this; - } - - }); - - var BlackListModel = Backbone.Model.extend({ - idAttribute: 'id', - - urlRoot: 'api/blacklist' - }); - - var BlackListCollection = Backbone.Collection.extend({ - initialize: function() { }, - - url: "api/blacklist" - }); - - var WhiteListModel = Backbone.Model.extend({ - - idAttribute: "id", - - initialize: function () { }, - - urlRoot: "api/whitelist" - - }); - - var WhiteListCollection = Backbone.Collection.extend({ - initialize: function() { - //this.fetch(); - }, - - getByClientId: function(clientId) { - var clients = this.where({clientId: clientId}); - if (clients.length == 1) { - return clients[0]; - } else { - return null; - } - }, - - model: WhiteListModel, - url: "api/whitelist" - - }); - - var ApprovedSiteModel = Backbone.Model.extend({ - idAttribute: 'id', - - initialize: function() { }, - - urlRoot: 'api/approved' - - }); - - var ApprovedSiteCollection = Backbone.Collection.extend({ - initialize: function() { }, - - model: ApprovedSiteModel, - url: 'api/approved' - }); - - - var SystemScopeModel = Backbone.Model.extend({ - idAttribute: 'id', - - defaults:{ - id:null, - description:null, - icon:null, - value:null, - defaultScope:false, - allowDynReg:false - }, - - urlRoot: 'api/scopes' - }); - - var SystemScopeCollection = Backbone.Collection.extend({ - idAttribute: 'id', - - model: SystemScopeModel, - - url: 'api/scopes', - - defaultScopes: function() { - filtered = this.filter(function(scope) { - return scope.get("defaultScope") === true; - }); - return new SystemScopeCollection(filtered); - }, - - getByValue: function(value) { - var scopes = this.where({value: value}); - if (scopes.length == 1) { - return scopes[0]; - } else { - return null; +var BlackListListView = Backbone.View.extend({ + tagName: 'span', + + initialize:function() { + if (!this.template) { + this.template = _.template($('#tmpl-blacklist-form').html()); + } + }, + + events: { + "click .refresh-table":"refreshTable" + }, + + refreshTable:function() { + var _self = this; + this.model.fetch({ + success: function() { + _self.render(); } - } - - }); - - var ClientModel = Backbone.Model.extend({ + }); + }, + + render:function (eventName) { + + $(this.el).html(this.template(this.model.toJSON())); + + $('#blacklist .controls', this.el).html(new BlackListWidgetView({ + placeholder: 'http://', + collection: this.model + }).render().el); + + return this; + } +}); - idAttribute: "id", +var BlackListWidgetView = ListWidgetView.extend({ + + childView: ListWidgetChildView.extend({ + render:function() { + var uri = this.model.get('uri'); + + this.$el.html(this.template({item: uri})); - initialize: function () { - - // bind validation errors to dom elements - // this will display form elements in red if they are not valid - this.bind('error', function(model, errs) { - _.map(errs, function (val, elID) { - $('#' + elID).addClass('error'); - }); - }); - - }, - - // We can pass it default values. - defaults:{ - id:null, - idTokenValiditySeconds: 600, - clientName:"", - clientSecret:"", - registeredRedirectUri:[], - authorizedGrantTypes:["authorization_code"], - scope:[], - authorities:[], - clientDescription:"", - logoUrl:"", - clientId:"", - allowRefresh:false, - accessTokenValiditySeconds: 3600, - refreshTokenValiditySeconds: 604800, - displayClientSecret: false, - generateClientSecret: false, - requireClientSecret: true, - allowIntrospection: false - }, - - urlRoot:"api/clients" - - }); - - var ClientCollection = Backbone.Collection.extend({ - - initialize: function() { - //this.fetch(); - }, - - model:ClientModel, - url:"api/clients", - - getByClientId: function(clientId) { - var clients = this.where({clientId: clientId}); - if (clients.length == 1) { - return clients[0]; - } else { - return null; - } - } - }); - - var BreadCrumbView = Backbone.View.extend({ - - tagName: 'ul', - - initialize:function () { - - if (!this.template) { - this.template = _.template($('#tmpl-breadcrumbs').html()); + if (uri.length > 30) { + this.$el.tooltip({title:uri}); } - - this.$el.addClass('breadcrumb'); - - this.collection.bind('add', this.render, this); - }, - - render:function () { - - this.$el.empty(); - var parent = this; - - // go through each of the breadcrumb models - _.each(this.collection.models, function (crumb, index) { - - // if it's the last index in the crumbs then render the link inactive - if (index == parent.collection.size() - 1) { - crumb.set({active:true}, {silent:true}); - } else { - crumb.set({active:false}, {silent:true}); - } - - this.$el.append(this.template(crumb.toJSON())); - }, this); - - $('#breadcrumbs').html(this.el); - } - }); - - - var ClientView = Backbone.View.extend({ - - tagName: 'tr', - - initialize:function () { - - if (!this.template) { - this.template = _.template($('#tmpl-client').html()); - } - - if (!this.scopeTemplate) { - this.scopeTemplate = _.template($('#tmpl-scope-list').html()); - } - - this.model.bind('change', this.render, this); - - }, - - render:function (eventName) { - this.$el.html(this.template(this.model.toJSON())); - - $('.scope-list', this.el).html(this.scopeTemplate({scopes: this.model.get('scope'), systemScopes: app.systemScopeList})); - - this.$('.dynamically-registered').tooltip({title: 'This client was dynamically registered'}); - return this; - }, + + } + }), + + addItem:function(e) { + e.preventDefault(); - events:{ - "click .btn-edit":"editClient", - "click .btn-delete":"deleteClient", - "click .btn-whitelist":"whiteListClient" - }, + var input_value = $("input", this.el).val().trim(); + + // TODO: URI/pattern validation, check against existing clients + + var item = new BlackListModel({ + uri: input_value + }); + + var _self = this; // closures... + + item.save({}, { + success:function() { + _self.collection.add(item); + } + }); - editClient:function () { - app.navigate('admin/client/' + this.model.id, {trigger: true}); - }, + } + +}); - whiteListClient:function() { - var whiteList = app.whiteListList.getByClientId(this.model.get('clientId')); - if (whiteList == null) { - // create a new one - app.navigate('admin/whitelist/new/' + this.model.id, {trigger: true}); - } else { - // edit the existing one - app.navigate('admin/whitelist/' + whiteList.id, {trigger: true}); +// Router +var AppRouter = Backbone.Router.extend({ + + routes:{ + "admin/clients":"listClients", + "admin/client/new":"newClient", + "admin/client/:id":"editClient", + + "admin/whitelists":"whiteList", + "admin/whitelist/new/:cid":"newWhitelist", + "admin/whitelist/:id":"editWhitelist", + + "admin/blacklist":"blackList", + + "admin/scope":"siteScope", + "admin/scope/new":"newScope", + "admin/scope/:id":"editScope", + + "user/approved":"approvedSites", + + "": "root" + + }, + + root:function() { + this.navigate('user/approved', {trigger: true}); + }, + + initialize:function () { + + this.clientList = new ClientCollection(); + this.whiteListList = new WhiteListCollection(); + this.blackListList = new BlackListCollection(); + this.approvedSiteList = new ApprovedSiteCollection(); + this.systemScopeList = new SystemScopeCollection(); + + this.clientListView = new ClientListView({model:this.clientList}); + this.whiteListListView = new WhiteListListView({model:this.whiteListList}); + this.approvedSiteListView = new ApprovedSiteListView({model:this.approvedSiteList}); + this.blackListListView = new BlackListListView({model:this.blackListList}); + this.systemScopeListView = new SystemScopeListView({model:this.systemScopeList}); + + this.breadCrumbView = new BreadCrumbView({ + collection:new Backbone.Collection() + }); + + this.breadCrumbView.render(); + + // + // Several items depend on the clients and whitelists being loaded, so we're going to pre-fetch them here + // and not start the app router until they're loaded. + // + + // load things in the right order: + this.systemScopeList.fetch({ + success: function(collection, response) { + app.clientList.fetch({ + success: function(collection, response) { + app.whiteListList.fetch({ + success: function(collection, response) { + var baseUrl = $.url($('base').attr('href')); + Backbone.history.start({pushState: true, root: baseUrl.attr('relative') + 'manage/'}); + } + }); + } + }); } - }, - - deleteClient:function () { + }); - if (confirm("Are you sure sure you would like to delete this client?")) { - var self = this; + }, - this.model.destroy({ - success:function () { - self.$el.fadeTo("fast", 0.00, function () { //fade - $(this).slideUp("fast", function () { //slide up - $(this).remove(); //then remove from the DOM - app.clientListView.togglePlaceholder(); - }); - }); - } - }); + listClients:function () { - app.clientListView.delegateEvents(); - } + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:"Home", href:""}, + {text:"Manage Clients", href:"manage/#admin/clients"} + ]); - return false; - }, + $('#content').html(this.clientListView.render().el); + this.clientListView.delegateEvents(); - close:function () { - $(this.el).unbind(); - $(this.el).empty(); - } - }); + }, - var ClientListView = Backbone.View.extend({ + newClient:function() { - tagName: 'span', + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:"Home", href:""}, + {text:"Manage Clients", href:"manage/#admin/clients"}, + {text:"New", href:""} + ]); - initialize:function () { - //this.model.bind("reset", this.render, this); - }, - - events:{ - "click .new-client":"newClient", - "click .refresh-table":"refreshTable" - }, - - newClient:function () { - this.remove(); - app.navigate('admin/client/new', {trigger: true}); - }, - - render:function (eventName) { - - // append and render table structure - $(this.el).html($('#tmpl-client-table').html()); - - _.each(this.model.models, function (client) { - $("#client-table",this.el).append(new ClientView({model:client}).render().el); - }, this); - - this.togglePlaceholder(); - - return this; - }, - - togglePlaceholder:function() { - if (this.model.length > 0) { - $('#client-table', this.el).show(); - $('#client-table-empty', this.el).hide(); - } else { - $('#client-table', this.el).hide(); - $('#client-table-empty', this.el).show(); - } - }, + var client = new ClientModel(); - refreshTable:function() { - var _self = this; - this.model.fetch({ - success: function() { - _self.render(); - } - }); - } - }); - - var ClientFormView = Backbone.View.extend({ - - tagName:"span", - - initialize:function () { - - if (!this.template) { - this.template = _.template($('#tmpl-client-form').html()); - } - - this.registeredRedirectUriCollection = new Backbone.Collection(); - this.scopeCollection = new Backbone.Collection(); - }, - - events:{ - "click .btn-save":"saveClient", - "click #allowRefresh" : "toggleRefreshTokenTimeout", - "click #disableAccessTokenTimeout" : function(){ $("#access-token-timeout-seconds", this.$el).prop('disabled',!$("#access-token-timeout-seconds", this.$el).prop('disabled')); }, - "click #disableIDTokenTimeout" : function(){ $("#id-token-timeout-seconds", this.$el).prop('disabled',!$("#id-token-timeout-seconds", this.$el).prop('disabled')); }, - "click #disableRefreshTokenTimeout" : function(){ $("#refresh-token-timeout-seconds", this.$el).prop('disabled',!$("#refresh-token-timeout-seconds", this.$el).prop('disabled')); }, - "click .btn-cancel": function() { window.history.back(); return false; }, - "change #requireClientSecret":"toggleRequireClientSecret", - "change #displayClientSecret":"toggleDisplayClientSecret", - "change #generateClientSecret":"toggleGenerateClientSecret", - "change #logoUrl input":"previewLogo" - }, - - toggleRefreshTokenTimeout:function () { - $("#refreshTokenValiditySeconds", this.$el).toggle(); - }, - - previewLogo:function(event) { - if ($('#logoUrl input', this.el).val()) { - $('#logoPreview', this.el).empty(); - $('#logoPreview', this.el).attr('src', $('#logoUrl input').val()); - } else { - $('#logoBlock', this.el).hide(); - } - }, - - /** - * Set up the form based on the current state of the requireClientSecret checkbox parameter - * @param event - */ - toggleRequireClientSecret:function(event) { - - if ($('#requireClientSecret input', this.el).is(':checked')) { - // client secret is required, show all the bits - $('#clientSecretPanel', this.el).show(); - // this function sets up the display portions - this.toggleGenerateClientSecret(); - } else { - // no client secret, hide all the bits - $('#clientSecretPanel', this.el).hide(); - } - }, - - /** - * Set up the form based on the "Generate" checkbox - * @param event - */ - toggleGenerateClientSecret:function(event) { - - if ($('#generateClientSecret input', this.el).is(':checked')) { - // show the "generated" block, hide the "display" checkbox - $('#displayClientSecret', this.el).hide(); - $('#clientSecret', this.el).hide(); - $('#clientSecretGenerated', this.el).show(); - $('#clientSecretHidden', this.el).hide(); - } else { - // show the display checkbox, fall back to the "display" logic - $('#displayClientSecret', this.el).show(); - this.toggleDisplayClientSecret(event); - } - }, - - /** - * Handle whether or not to display the client secret - * @param event - */ - toggleDisplayClientSecret:function(event) { - - if ($('#displayClientSecret input').is(':checked')) { - // want to display it - $('#clientSecret', this.el).show(); - $('#clientSecretHidden', this.el).hide(); - $('#clientSecretGenerated', this.el).hide(); - } else { - // want to hide it - $('#clientSecret', this.el).hide(); - $('#clientSecretHidden', this.el).show(); - $('#clientSecretGenerated', this.el).hide(); - } - }, - - getFormTokenValue:function(value) { - if (value == "") return null; - else return value; - }, - - // maps from a form-friendly name to the real grant parameter name - authorizedGrantMap:{ - "authorization_code": "authorization_code", - "password": "password", - "implicit": "implicit", - "client_credentials": "client_credentials", - "redelegate": "urn:ietf:params:oauth:grant_type:redelegate", - "refresh_token": "refresh_token" - }, - - saveClient:function (event) { - - $('.control-group').removeClass('error'); - - // build the scope object - var scopes = this.scopeCollection.pluck("item"); - - // build the grant type object - var authorizedGrantTypes = []; - $.each(this.authorizedGrantMap, function(index,type) { - if ($('#authorizedGrantTypes-' + index).is(':checked')) { - authorizedGrantTypes.push(type); - } - }); - - var requireClientSecret = $('#requireClientSecret input').is(':checked'); - var generateClientSecret = $('#generateClientSecret input').is(':checked'); - var clientSecret = null; - - if (requireClientSecret && !generateClientSecret) { - // if it's required but we're not generating it, send the value - clientSecret = $('#clientSecret input').val(); - } - - var accessTokenValiditySeconds = null; - if (!$('disableAccessTokenTimeout').is(':checked')) { - accessTokenValiditySeconds = this.getFormTokenValue($('#accessTokenValiditySeconds input[type=text]').val()); - } - - var idTokenValiditySeconds = null; - if (!$('disableIDTokenTimeout').is(':checked')) { - idTokenValiditySeconds = this.getFormTokenValue($('#idTokenValiditySeconds input[type=text]').val()); - } - - var refreshTokenValiditySeconds = null; - if ($('#allowRefresh').is(':checked')) { - - if ($.inArray('refresh_token', authorizedGrantTypes) == -1) { - authorizedGrantTypes.push('refresh_token'); - } - - if ($.inArray('offline_access', scopes) == -1) { - scopes.push("offline_access"); - } - - if (!$('disableRefreshTokenTimeout').is(':checked')) { - refreshTokenValiditySeconds = this.getFormTokenValue($('#refreshTokenValiditySeconds input[type=text]').val()); - } - } - - var valid = this.model.set({ - clientName:$('#clientName input').val(), - clientId:$('#clientId input').val(), - clientSecret: clientSecret, - generateClientSecret:generateClientSecret, - registeredRedirectUri: this.registeredRedirectUriCollection.pluck("item"), - clientDescription:$('#clientDescription textarea').val(), - logoUrl:$('#logoUrl input').val(), - authorizedGrantTypes: authorizedGrantTypes, - accessTokenValiditySeconds: accessTokenValiditySeconds, - refreshTokenValiditySeconds: refreshTokenValiditySeconds, - idTokenValiditySeconds: idTokenValiditySeconds, - allowRefresh: $('#allowRefresh').is(':checked'), - allowIntrospection: $('#allowIntrospection input').is(':checked'), - scope: scopes - }); - - // post-validate - // TODO: move these into the validation function somehow? - if (this.model.get("allowRefresh") == false) { - this.model.set("refreshTokenValiditySeconds",null); - } - - if ($('#disableIDTokenTimeout').is(':checked')) { - this.model.set("idTokenValiditySeconds",null); - } - - if ($('#disableAccessTokenTimeout').is(':checked')) { - this.model.set("accessTokenValiditySeconds",null); - } - - if ($('#disableRefreshTokenTimeout').is(':checked')) { - this.model.set("refreshTokenValiditySeconds",null); - } - - if (valid) { - - var _self = this; - this.model.save({}, { - success:function () { - app.clientList.add(_self.model); - app.navigate('admin/clients', {trigger:true}); - }, - error:function (model,resp) { - console.error("Oops! The object didn't save correctly.",resp); - } - }); - } - - return false; - }, - - render:function (eventName) { - - $(this.el).html(this.template(this.model.toJSON())); - - - var _self = this; - - // build and bind registered redirect URI collection and view - _.each(this.model.get("registeredRedirectUri"), function (registeredRedirectUri) { - _self.registeredRedirectUriCollection.add(new URIModel({item:registeredRedirectUri})); - }); - - $("#registeredRedirectUri .controls",this.el).html(new ListWidgetView({type:'uri', placeholder: 'http://', - collection: this.registeredRedirectUriCollection}).render().el); - - _self = this; - // build and bind scopes - _.each(this.model.get("scope"), function (scope) { - _self.scopeCollection.add(new Backbone.Model({item:scope})); - }); - - $("#scope .controls",this.el).html(new ListWidgetView({placeholder: 'new scope here' - , autocomplete: _.uniq(_.flatten(app.systemScopeList.pluck("value"))) // TODO: load from default scopes - , collection: this.scopeCollection}).render().el); - - if (!this.model.get("allowRefresh")) { - $("#refreshTokenValiditySeconds", this.$el).hide(); - } - - if (this.model.get("accessTokenValiditySeconds") == null) { - $("#access-token-timeout-seconds", this.$el).prop('disabled',true); - } - - if (this.model.get("refreshTokenValiditySeconds") == null) { - $("#refresh-token-timeout-seconds", this.$el).prop('disabled',true); - } - - if (this.model.get("idTokenValiditySeconds") == null) { - $("#id-token-timeout-seconds", this.$el).prop('disabled',true); - } - - this.toggleRequireClientSecret(); - this.previewLogo(); - - return this; - } - }); - - - var ApprovedSiteListView = Backbone.View.extend({ - tagName: 'span', + // set up this new client to require a secret and have us autogenerate one + client.set({ + requireClientSecret:true, + generateClientSecret:true, + displayClientSecret:false, + scope: _.uniq(_.flatten(this.systemScopeList.defaultScopes().pluck("value"))), + }, { silent: true }); - initialize:function() { }, - - events: { - "click .refresh-table":"refreshTable" - }, - - render:function (eventName) { - $(this.el).html($('#tmpl-grant-table').html()); - - _.each(this.model.models, function(approvedSite) { - // look up client - var client = app.clientList.getByClientId(approvedSite.get('clientId')); - - if (client != null) { - - if (approvedSite.get('whitelistedSite') != null) { - $('#grant-whitelist-table', this.el).append(new ApprovedSiteView({model: approvedSite, client: client}).render().el); - } else { - $('#grant-table', this.el).append(new ApprovedSiteView({model: approvedSite, client: client}).render().el); - } - - } - - }, this); - - this.togglePlaceholder(); - - return this; - }, - - togglePlaceholder:function() { - // count the whitelisted and non-whitelisted entries - var wl = 0; - var gr = 0; - for (var i = 0; i < this.model.length; i++) { - if (this.model.at(i).get('whitelistedSite') != null) { - wl += 1; - } else { - gr += 1; - } - } - - if (wl > 0) { - $('#grant-whitelist-table', this.el).show(); - $('#grant-whitelist-table-empty', this.el).hide(); - } else { - $('#grant-whitelist-table', this.el).hide(); - $('#grant-whitelist-table-empty', this.el).show(); - } - if (gr > 0) { - $('#grant-table', this.el).show(); - $('#grant-table-empty', this.el).hide(); - } else { - $('#grant-table', this.el).hide(); - $('#grant-table-empty', this.el).show(); - } - }, - - refreshTable:function() { - var _self = this; - this.model.fetch({ - success: function() { - _self.render(); - } - }); - } + this.clientFormView = new ClientFormView({model:client}); + $('#content').html(this.clientFormView.render().el); + }, - }); - - var ApprovedSiteView = Backbone.View.extend({ - tagName: 'tr', - - initialize: function() { - if (!this.template) { - this.template = _.template($('#tmpl-grant').html()); - } - if (!this.scopeTemplate) { - this.scopeTemplate = _.template($('#tmpl-scope-list').html()); - } + editClient:function(id) { - }, - - render: function() { - var json = {grant: this.model.toJSON(), client: this.options.client.toJSON()}; - - this.$el.html(this.template(json)); + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:"Home", href:""}, + {text:"Manage Clients", href:"manage/#admin/clients"}, + {text:"Edit", href:"manage/#admin/client/" + id} + ]); - $('.scope-list', this.el).html(this.scopeTemplate({scopes: this.options.client.get('scope'), systemScopes: app.systemScopeList})); - - this.$('.dynamically-registered').tooltip({title: 'This client was dynamically registered'}); - this.$('.whitelisted-site').tooltip({title: 'This site was whitelisted by an adminstrator'}); - - return this; - }, - - events: { - 'click .btn-delete': 'deleteApprovedSite' - }, - - deleteApprovedSite:function() { - if (confirm("Are you sure you want to revoke access to this site?")) { - var self = this; - - this.model.destroy({ - success:function () { - self.$el.fadeTo("fast", 0.00, function () { //fade - $(this).slideUp("fast", function () { //slide up - $(this).remove(); //then remove from the DOM - app.approvedSiteListView.togglePlaceholder(); - }); - }); - } - }); - - app.approvedSiteListView.delegateEvents(); - } - - return false; - }, - - close:function() { - $(this.el).unbind(); - $(this.el).empty(); - } - }); - - var WhiteListListView = Backbone.View.extend({ - tagName: 'span', - - initialize:function () { - //this.model.bind("reset", this.render, this); - }, - - events:{ - "click .refresh-table":"refreshTable" - }, - - render:function (eventName) { - $(this.el).html($('#tmpl-whitelist-table').html()); - - _.each(this.model.models, function (whiteList) { - - // look up client - var client = app.clientList.getByClientId(whiteList.get('clientId')); - - // if there's no client ID, this is an error! - if (client != null) { - $('#whitelist-table', this.el).append(new WhiteListView({model: whiteList, client: client}).render().el); - } - - }, this); + var client = this.clientList.get(id); - this.togglePlaceholder(); - - return this; - }, - - togglePlaceholder:function() { - if (this.model.length > 0) { - $('#whitelist-table', this.el).show(); - $('#whitelist-table-empty', this.el).hide(); - } else { - $('#whitelist-table', this.el).hide(); - $('#whitelist-table-empty', this.el).show(); - } - }, - - refreshTable:function() { - var _self = this; - this.model.fetch({ - success: function() { - _self.render(); - } - }); - } - }); - - var WhiteListView = Backbone.View.extend({ - tagName: 'tr', - - initialize:function() { - if (!this.template) { - this.template = _.template($('#tmpl-whitelist').html()); - } - - if (!this.scopeTemplate) { - this.scopeTemplate = _.template($('#tmpl-scope-list').html()); - } - - this.model.bind('change', this.render, this); - }, - - render:function(eventName) { - - var json = {whiteList: this.model.toJSON(), client: this.options.client.toJSON()}; - - this.$el.html(this.template(json)); - - $('.scope-list', this.el).html(this.scopeTemplate({scopes: this.model.get('allowedScopes'), systemScopes: app.systemScopeList})); - - this.$('.dynamically-registered').tooltip({title: 'This client was dynamically registered'}); - - return this; - }, - - events:{ - 'click .btn-edit': 'editWhitelist', - 'click .btn-delete': 'deleteWhitelist' - }, - - editWhitelist:function() { - app.navigate('admin/whitelist/' + this.model.id, {trigger: true}); - }, - - deleteWhitelist:function() { - - if (confirm("Are you sure you want to delete this whitelist entry?")) { - var self = this; - - this.model.destroy({ - success:function () { - self.$el.fadeTo("fast", 0.00, function () { //fade - $(this).slideUp("fast", function () { //slide up - $(this).remove(); //then remove from the DOM - // check the placeholder in case it's empty now - app.whiteListListView.togglePlaceholder(); - }); - }); - } - }); - - app.whiteListListView.delegateEvents(); - } - - return false; - }, - - close:function() { - $(this.el).unbind(); - $(this.el).empty(); - } - }); - - var WhiteListFormView = Backbone.View.extend({ - tagName: 'span', - - initialize:function () { - if (!this.template) { - this.template = _.template($('#tmpl-whitelist-form').html()); - } - - this.scopeCollection = new Backbone.Collection(); - }, - - events:{ - 'click .btn-save':'saveWhiteList', - 'click .btn-cancel':'cancelWhiteList', - - }, - - saveWhiteList:function (event) { - $('.control-group').removeClass('error'); - - // process allowed scopes - var allowedScopes = this.scopeCollection.pluck("item"); - - if (this.model.get('id') == null) { - this.model.set({clientId:$('#clientId input').val()}); - } - - var valid = this.model.set({ - allowedScopes: allowedScopes - }); - - if (valid) { - var _self = this; - this.model.save({}, { - success:function () { - app.whiteListList.add(_self.model); - app.navigate('admin/whitelists', {trigger:true}); - }, - error:function (model,resp) { - console.error("Oops! The object didn't save correctly.",resp); - } - }); - } - - return false; - - }, - - cancelWhiteList:function(event) { - app.navigate('admin/whitelists', {trigger:true}); - }, - - render:function (eventName) { - - var json = {whiteList: this.model.toJSON(), client: this.options.client.toJSON()}; - - this.$el.html(this.template(json)); - - - var _self = this; - // build and bind scopes - _.each(this.model.get("allowedScopes"), function (scope) { - _self.scopeCollection.add(new Backbone.Model({item:scope})); - }); - - $("#scope .controls",this.el).html(new ListWidgetView({ - placeholder: 'new scope here', - autocomplete: this.options.client.scope, - collection: this.scopeCollection}).render().el); - - - return this; - - } - - }); - - var BlackListListView = Backbone.View.extend({ - tagName: 'span', - - initialize:function() { - if (!this.template) { - this.template = _.template($('#tmpl-blacklist-form').html()); - } - }, - - events: { - "click .refresh-table":"refreshTable" - }, - - refreshTable:function() { - var _self = this; - this.model.fetch({ - success: function() { - _self.render(); - } - }); - }, - - render:function (eventName) { - - $(this.el).html(this.template(this.model.toJSON())); - - $('#blacklist .controls', this.el).html(new BlackListWidgetView({ - placeholder: 'http://', - collection: this.model - }).render().el); - - return this; - } - }); - - var BlackListWidgetView = ListWidgetView.extend({ - - childView: ListWidgetChildView.extend({ - render:function() { - var uri = this.model.get('uri'); - - this.$el.html(this.template({item: uri})); - - if (uri.length > 30) { - this.$el.tooltip({title:uri}); - } - return this; - - } - }), - - addItem:function(e) { - e.preventDefault(); - - var input_value = $("input", this.el).val().trim(); - - // TODO: URI/pattern validation, check against existing clients - - var item = new BlackListModel({ - uri: input_value - }); - - var _self = this; // closures... - - item.save({}, { - success:function() { - _self.collection.add(item); - } - }); - - } - - }); - - var SystemScopeView = Backbone.View.extend({ - - tagName: 'tr', - - initialize:function () { - - if (!this.template) { - this.template = _.template($('#tmpl-system-scope').html()); - } - - this.model.bind('change', this.render, this); - - }, - - events: { - 'click .btn-edit':'editScope', - 'click .btn-delete':'deleteScope' - }, - - editScope:function() { - app.navigate('admin/scope/' + this.model.id, {trigger: true}); - }, - - render:function (eventName) { - this.$el.html(this.template(this.model.toJSON())); - - this.$('.dynamically-registered').tooltip({title: 'This client was dynamically registered'}); - - return this; - }, - - deleteScope:function () { - - if (confirm("Are you sure sure you would like to delete this scope? Clients that have this scope will still be able to ask for it.")) { - var self = this; - - this.model.destroy({ - success:function () { - self.$el.fadeTo("fast", 0.00, function () { //fade - $(this).slideUp("fast", function () { //slide up - $(this).remove(); //then remove from the DOM - app.systemScopeListView.togglePlaceholder(); - }); - }); - } - }); - - app.systemScopeListView.delegateEvents(); - } - - return false; - }, - - close:function () { - $(this.el).unbind(); - $(this.el).empty(); - } - }); - - var SystemScopeListView = Backbone.View.extend({ - tagName: 'span', - - events:{ - "click .new-scope":"newScope", - "click .refresh-table":"refreshTable" - }, - - newScope:function() { - this.remove(); - app.navigate('admin/scope/new', {trigger: true}); - }, - - refreshTable:function() { - var _self = this; - this.model.fetch({ - success: function() { - _self.render(); - } - }); - }, - - togglePlaceholder:function() { - if (this.model.length > 0) { - $('#scope-table', this.el).show(); - $('#scope-table-empty', this.el).hide(); - } else { - $('#scope-table', this.el).hide(); - $('#scope-table-empty', this.el).show(); - } - }, - - render: function (eventName) { - - // append and render the table structure - $(this.el).html($('#tmpl-system-scope-table').html()); - - _.each(this.model.models, function (scope) { - $("#scope-table", this.el).append(new SystemScopeView({model: scope}).render().el); - }, this); - - this.togglePlaceholder(); - - return this; - } - }); - - var SystemScopeFormView = Backbone.View.extend({ - tagName: 'span', - - initialize:function() { - if (!this.template) { - this.template = _.template($('#tmpl-system-scope-form').html()); - } - if (!this.iconTemplate) { - this.iconTemplate = _.template($('#tmpl-system-scope-icon').html()); - } - - // initialize our icon set into slices for the selector - if (!this.bootstrapIcons) { - this.bootstrapIcons = []; - - var iconList = ['glass', 'music', 'search', 'envelope', 'heart', 'star', - 'star-empty', 'user', 'film', 'th-large', 'th', 'th-list', 'ok', - 'remove', 'zoom-in', 'zoom-out', 'off', 'signal', 'cog', 'trash', - 'home', 'file', 'time', 'road', 'download-alt', 'download', - 'upload', 'inbox', 'play-circle', 'repeat', 'refresh', 'list-alt', - 'lock', 'flag', 'headphones', 'volume-off', 'volume-down', - 'volume-up', 'qrcode', 'barcode', 'tag', 'tags', 'book', - 'bookmark', 'print', 'camera', 'font', 'bold', 'italic', - 'text-height', 'text-width', 'align-left', 'align-center', - 'align-right', 'align-justify', 'list', 'indent-left', - 'indent-right', 'facetime-video', 'picture', 'pencil', - 'map-marker', 'tint', 'share', 'move', 'fast-backward', 'backward', - 'pause', 'stop', 'forward', 'step-forward', 'eject', - 'chevron-right', 'plus-sign', 'minus-sign', 'remove-sign', - 'ok-sign', 'question-sign', 'info-sign', 'screenshot', - 'remove-circle', 'ok-circle', 'ban-circle', 'arrow-left', - 'arrow-right', 'arrow-down', 'share-alt', 'resize-full', - 'resize-small', 'plus', 'asterisk', 'exclamation-sign', 'gift', - 'leaf', 'fire', 'eye-close', 'plane', 'random', 'magnet', - 'chevron-up', 'chevron-down', 'retweet', 'shopping-cart', - 'folder-close', 'folder-open', 'resize-vertical', - 'resize-horizontal', 'hdd', 'bell', 'thumbs-up', 'hand-right', - 'hand-left', 'hand-down', 'circle-arrow-left', 'circle-arrow-up', - 'circle-arrow-down', 'globe', 'tasks', 'briefcase' ]; - - var size = 3; - while (iconList.length > 0) { - this.bootstrapIcons.push(iconList.splice(0, size)); - } - - } - }, - - events:{ - 'click .btn-save':'saveScope', - 'click .btn-cancel': function() {app.navigate('admin/scope', {trigger: true}); }, - 'click .btn-icon':'selectIcon' - }, - - saveScope:function(event) { - - var value = $('#value input').val() - - if (value == null || value.trim() == "") { - // error: can't have a blank scope - return false - } - - var valid = this.model.set({ - value:value, - description:$('#description textarea').val(), - icon:$('#iconDisplay input').val(), - defaultScope:$('#defaultScope input').is(':checked'), - allowDynReg:$('#allowDynReg input').is(':checked') - }); - - if (valid) { - var _self = this; - this.model.save({}, { - success:function() { - app.systemScopeList.add(_self.model); - app.navigate('admin/scope', {trigger: true}); - }, - error:function(model,resp) { - console.error("The scope didn't save correctly.", resp); - } - }); - } - - return false; - }, - - selectIcon:function(event) { - - var icon = event.target.value; - - $('#iconDisplay input').val(icon); - $('#iconDisplay span').html(icon); - $('#iconDisplay i').removeClass(); - $('#iconDisplay i').addClass('icon-' + icon); - - $('#iconSelector').modal('hide'); - - return false; - }, - - render: function(eventName) { - this.$el.html(this.template(this.model.toJSON())); - - _.each(this.bootstrapIcons, function (items) { - $(".modal-body", this.el).append(this.iconTemplate({items:items})); - }, this); - - - return this; - } - }); - - // Router - var AppRouter = Backbone.Router.extend({ - - routes:{ - "admin/clients":"listClients", - "admin/client/new":"newClient", - "admin/client/:id":"editClient", - - "admin/whitelists":"whiteList", - "admin/whitelist/new/:cid":"newWhitelist", - "admin/whitelist/:id":"editWhitelist", - - "admin/blacklist":"blackList", - - "admin/scope":"siteScope", - "admin/scope/new":"newScope", - "admin/scope/:id":"editScope", - - "user/approved":"approvedSites", - - "": "root" - - }, - - root:function() { - this.navigate('user/approved', {trigger: true}); - }, - - initialize:function () { - - this.clientList = new ClientCollection(); - this.whiteListList = new WhiteListCollection(); - this.blackListList = new BlackListCollection(); - this.approvedSiteList = new ApprovedSiteCollection(); - this.systemScopeList = new SystemScopeCollection(); - - this.clientListView = new ClientListView({model:this.clientList}); - this.whiteListListView = new WhiteListListView({model:this.whiteListList}); - this.approvedSiteListView = new ApprovedSiteListView({model:this.approvedSiteList}); - this.blackListListView = new BlackListListView({model:this.blackListList}); - this.systemScopeListView = new SystemScopeListView({model:this.systemScopeList}); - - this.breadCrumbView = new BreadCrumbView({ - collection:new Backbone.Collection() - }); - - this.breadCrumbView.render(); - - // - // Several items depend on the clients and whitelists being loaded, so we're going to pre-fetch them here - // and not start the app router until they're loaded. - // - - // load things in the right order: - this.systemScopeList.fetch({ - success: function(collection, response) { - app.clientList.fetch({ - success: function(collection, response) { - app.whiteListList.fetch({ - success: function(collection, response) { - var baseUrl = $.url($('base').attr('href')); - Backbone.history.start({pushState: true, root: baseUrl.attr('relative') + 'manage/'}); - } - }); - } - }); - } - }); - - }, - - listClients:function () { - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:"Home", href:""}, - {text:"Manage Clients", href:"manage/#admin/clients"} - ]); - - $('#content').html(this.clientListView.render().el); - this.clientListView.delegateEvents(); - - }, - - newClient:function() { - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:"Home", href:""}, - {text:"Manage Clients", href:"manage/#admin/clients"}, - {text:"New", href:""} - ]); - - var client = new ClientModel(); - - // set up this new client to require a secret and have us autogenerate one + if (client.get("clientSecret") == null) { client.set({ - requireClientSecret:true, - generateClientSecret:true, - displayClientSecret:false, - scope: _.uniq(_.flatten(this.systemScopeList.defaultScopes().pluck("value"))), + requireClientSecret:false }, { silent: true }); - - this.clientFormView = new ClientFormView({model:client}); - $('#content').html(this.clientFormView.render().el); - }, - - editClient:function(id) { - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:"Home", href:""}, - {text:"Manage Clients", href:"manage/#admin/clients"}, - {text:"Edit", href:"manage/#admin/client/" + id} - ]); - - var client = this.clientList.get(id); - - if (client.get("clientSecret") == null) { - client.set({ - requireClientSecret:false - }, { silent: true }); - } - - if ($.inArray("refresh_token", client.get("authorizedGrantTypes")) != -1) { - client.set({ - allowRefresh: true - }, { silent: true }); - } - + } + + if ($.inArray("refresh_token", client.get("authorizedGrantTypes")) != -1) { client.set({ - generateClientSecret:false, - displayClientSecret:false + allowRefresh: true }, { silent: true }); - - this.clientFormView = new ClientFormView({model:client}); - $('#content').html(this.clientFormView.render().el); - }, - - whiteList:function () { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:"Home", href:""}, - {text:"Manage Whitelisted Sites", href:"manage/#admin/whitelists"} - ]); - - $('#content').html(this.whiteListListView.render().el); - this.whiteListListView.delegateEvents(); - }, + } - newWhitelist:function(cid) { - var client = this.clientList.get(cid); - - // if there's no client this is an error - if (client != null) { - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:"Home", href:""}, - {text:"Manage Whitelisted Sites", href:"manage/#admin/whitelists"}, - {text:"Manage Whitelisted Sites", href:"manage/#admin/whitelist/new/" + cid} - ]); - - var whiteList = new WhiteListModel(); - whiteList.set({ - clientId: client.get('clientId'), - allowedScopes: client.get('scope') - }, { silent: true }); - - this.whiteListFormView = new WhiteListFormView({model: whiteList, client: client}); - $('#content').html(this.whiteListFormView.render().el); - } else { - console.log('ERROR: no client found for ' + cid); - } - - - }, + client.set({ + generateClientSecret:false, + displayClientSecret:false + }, { silent: true }); - editWhitelist:function(id) { - this.breadCrumbView.collection.reset(); + this.clientFormView = new ClientFormView({model:client}); + $('#content').html(this.clientFormView.render().el); + }, + + whiteList:function () { + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:"Home", href:""}, + {text:"Manage Whitelisted Sites", href:"manage/#admin/whitelists"} + ]); + + $('#content').html(this.whiteListListView.render().el); + this.whiteListListView.delegateEvents(); + }, + + newWhitelist:function(cid) { + var client = this.clientList.get(cid); + + // if there's no client this is an error + if (client != null) { + + this.breadCrumbView.collection.reset(); this.breadCrumbView.collection.add([ {text:"Home", href:""}, {text:"Manage Whitelisted Sites", href:"manage/#admin/whitelists"}, - {text:"Manage Whitelisted Sites", href:"manage/#admin/whitelist/" + id} + {text:"Manage Whitelisted Sites", href:"manage/#admin/whitelist/new/" + cid} ]); - var whiteList = this.whiteListList.get(id); - if (whiteList != null) { - var client = app.clientList.getByClientId(whiteList.get('clientId')); - - // if there's no client, this is an error - if (client != null) { - this.whiteListFormView = new WhiteListFormView({model: whiteList, client: client}); - $('#content').html(this.whiteListFormView.render().el); - } else { - console.log('ERROR: no client found for ' + whiteList.get('clientId')); - } - } else { - console.error('ERROR: no whitelist found for id ' + id); - } - }, - - approvedSites:function() { - - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:"Home", href:""}, - {text:"Manage Approved Sites", href:"manage/#user/approve"} - ]); - - var view = this.approvedSiteListView; - - this.approvedSiteList.fetch({success: - function(collection, response, options) { - $('#content').html(view.render().el); - } - }); - - }, - - blackList:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:"Home", href:""}, - {text:"Manage Blacklisted Sites", href:"manage/#admin/blacklist"} - ]); + var whiteList = new WhiteListModel(); + whiteList.set({ + clientId: client.get('clientId'), + allowedScopes: client.get('scope') + }, { silent: true }); - var view = this.blackListListView; - - this.blackListList.fetch({success: - function(collection, response, options) { - $('#content').html(view.render().el); - } - }); - }, - - siteScope:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:"Home", href:""}, - {text:"Mange System Scopes", href:"manage/#admin/scope"} - ]); - - $('#content').html(this.systemScopeListView.render().el); - this.systemScopeListView.delegateEvents(); - }, - - newScope:function() { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:"Home", href:""}, - {text:"Mange System Scopes", href:"manage/#admin/scope"}, - {text:"New", href:"manage/#admin/scope/new"} - ]); - - var scope = new SystemScopeModel(); - - this.systemScopeFormView = new SystemScopeFormView({model:scope}); - $('#content').html(this.systemScopeFormView.render().el); - }, - - editScope:function(sid) { - this.breadCrumbView.collection.reset(); - this.breadCrumbView.collection.add([ - {text:"Home", href:""}, - {text:"Mange System Scopes", href:"manage/#admin/scope"}, - {text:"Edit", href:"manage/#admin/scope/" + sid} - ]); - - var scope = this.systemScopeList.get(sid); - - this.systemScopeFormView = new SystemScopeFormView({model:scope}); - $('#content').html(this.systemScopeFormView.render().el); - + this.whiteListFormView = new WhiteListFormView({model: whiteList, client: client}); + $('#content').html(this.whiteListFormView.render().el); + } else { + console.log('ERROR: no client found for ' + cid); } - - - }); - - // holds the global app. - // this gets init after the templates load - var app = null; - - // main - $(function () { - - jQuery.ajaxSetup({async:false}); - - var _load = function (templates) { - $('body').append(templates); - }; - - // load templates and append them to the body - $.get('resources/template/admin.html', _load); - - jQuery.ajaxSetup({async:true}); - app = new AppRouter(); - - // grab all hashed URLs and send them through the app router instead - $('a[href*="#"]').on('click', function(event) { - event.preventDefault(); - app.navigate(this.hash.slice(1), {trigger: true}); - }); + + }, + + editWhitelist:function(id) { + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:"Home", href:""}, + {text:"Manage Whitelisted Sites", href:"manage/#admin/whitelists"}, + {text:"Manage Whitelisted Sites", href:"manage/#admin/whitelist/" + id} + ]); + + var whiteList = this.whiteListList.get(id); + if (whiteList != null) { + var client = app.clientList.getByClientId(whiteList.get('clientId')); + + // if there's no client, this is an error + if (client != null) { + this.whiteListFormView = new WhiteListFormView({model: whiteList, client: client}); + $('#content').html(this.whiteListFormView.render().el); + } else { + console.log('ERROR: no client found for ' + whiteList.get('clientId')); + } + } else { + console.error('ERROR: no whitelist found for id ' + id); + } + }, + + approvedSites:function() { + + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:"Home", href:""}, + {text:"Manage Approved Sites", href:"manage/#user/approve"} + ]); + + var view = this.approvedSiteListView; + + this.approvedSiteList.fetch({success: + function(collection, response, options) { + $('#content').html(view.render().el); + } + }); + + }, + + blackList:function() { + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:"Home", href:""}, + {text:"Manage Blacklisted Sites", href:"manage/#admin/blacklist"} + ]); + + var view = this.blackListListView; + + this.blackListList.fetch({success: + function(collection, response, options) { + $('#content').html(view.render().el); + } + }); + }, + + siteScope:function() { + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:"Home", href:""}, + {text:"Mange System Scopes", href:"manage/#admin/scope"} + ]); + + $('#content').html(this.systemScopeListView.render().el); + this.systemScopeListView.delegateEvents(); + }, + + newScope:function() { + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:"Home", href:""}, + {text:"Mange System Scopes", href:"manage/#admin/scope"}, + {text:"New", href:"manage/#admin/scope/new"} + ]); + + var scope = new SystemScopeModel(); + + this.systemScopeFormView = new SystemScopeFormView({model:scope}); + $('#content').html(this.systemScopeFormView.render().el); + }, + + editScope:function(sid) { + this.breadCrumbView.collection.reset(); + this.breadCrumbView.collection.add([ + {text:"Home", href:""}, + {text:"Mange System Scopes", href:"manage/#admin/scope"}, + {text:"Edit", href:"manage/#admin/scope/" + sid} + ]); + + var scope = this.systemScopeList.get(sid); + + this.systemScopeFormView = new SystemScopeFormView({model:scope}); + $('#content').html(this.systemScopeFormView.render().el); + + } + + +}); + +// holds the global app. +// this gets init after the templates load +var app = null; + +// main +$(function () { + + jQuery.ajaxSetup({async:false}); + + // load up our model and collection classes + $.get('resources/js/client.js', _load); + $.get('resources/js/grant.js', _load); + $.get('resources/js/scope.js', _load); + $.get('resources/js/whitelist.js', _load); + + + var _load = function (templates) { + $('body').append(templates); + }; + + // load templates and append them to the body + $.get('resources/template/admin.html', _load); + $.get('resources/template/client.html', _load); + $.get('resources/template/grant.html', _load); + $.get('resources/template/scope.html', _load); + $.get('resources/template/whitelist.html', _load); + + jQuery.ajaxSetup({async:true}); + app = new AppRouter(); + + // grab all hashed URLs and send them through the app router instead + $('a[href*="#"]').on('click', function(event) { + event.preventDefault(); + app.navigate(this.hash.slice(1), {trigger: true}); }); + +}); diff --git a/openid-connect-server/src/main/webapp/resources/js/client.js b/openid-connect-server/src/main/webapp/resources/js/client.js new file mode 100644 index 000000000..4f363fac3 --- /dev/null +++ b/openid-connect-server/src/main/webapp/resources/js/client.js @@ -0,0 +1,451 @@ +var ClientModel = Backbone.Model.extend({ + + idAttribute: "id", + + initialize: function () { + + // bind validation errors to dom elements + // this will display form elements in red if they are not valid + this.bind('error', function(model, errs) { + _.map(errs, function (val, elID) { + $('#' + elID).addClass('error'); + }); + }); + + }, + + // We can pass it default values. + defaults:{ + id:null, + idTokenValiditySeconds: 600, + clientName:"", + clientSecret:"", + registeredRedirectUri:[], + authorizedGrantTypes:["authorization_code"], + scope:[], + authorities:[], + clientDescription:"", + logoUrl:"", + clientId:"", + allowRefresh:false, + accessTokenValiditySeconds: 3600, + refreshTokenValiditySeconds: 604800, + displayClientSecret: false, + generateClientSecret: false, + requireClientSecret: true, + allowIntrospection: false + }, + + urlRoot:"api/clients" + +}); + +var ClientCollection = Backbone.Collection.extend({ + + initialize: function() { + //this.fetch(); + }, + + model:ClientModel, + url:"api/clients", + + getByClientId: function(clientId) { + var clients = this.where({clientId: clientId}); + if (clients.length == 1) { + return clients[0]; + } else { + return null; + } + } +}); + +var ClientView = Backbone.View.extend({ + + tagName: 'tr', + + initialize:function () { + + if (!this.template) { + this.template = _.template($('#tmpl-client').html()); + } + + if (!this.scopeTemplate) { + this.scopeTemplate = _.template($('#tmpl-scope-list').html()); + } + + this.model.bind('change', this.render, this); + + }, + + render:function (eventName) { + this.$el.html(this.template(this.model.toJSON())); + + $('.scope-list', this.el).html(this.scopeTemplate({scopes: this.model.get('scope'), systemScopes: app.systemScopeList})); + + this.$('.dynamically-registered').tooltip({title: 'This client was dynamically registered'}); + + return this; + }, + + events:{ + "click .btn-edit":"editClient", + "click .btn-delete":"deleteClient", + "click .btn-whitelist":"whiteListClient" + }, + + editClient:function () { + app.navigate('admin/client/' + this.model.id, {trigger: true}); + }, + + whiteListClient:function() { + var whiteList = app.whiteListList.getByClientId(this.model.get('clientId')); + if (whiteList == null) { + // create a new one + app.navigate('admin/whitelist/new/' + this.model.id, {trigger: true}); + } else { + // edit the existing one + app.navigate('admin/whitelist/' + whiteList.id, {trigger: true}); + } + }, + + deleteClient:function () { + + if (confirm("Are you sure sure you would like to delete this client?")) { + var self = this; + + this.model.destroy({ + success:function () { + self.$el.fadeTo("fast", 0.00, function () { //fade + $(this).slideUp("fast", function () { //slide up + $(this).remove(); //then remove from the DOM + app.clientListView.togglePlaceholder(); + }); + }); + } + }); + + app.clientListView.delegateEvents(); + } + + return false; + }, + + close:function () { + $(this.el).unbind(); + $(this.el).empty(); + } +}); + +var ClientListView = Backbone.View.extend({ + + tagName: 'span', + + initialize:function () { + //this.model.bind("reset", this.render, this); + }, + + events:{ + "click .new-client":"newClient", + "click .refresh-table":"refreshTable" + }, + + newClient:function () { + this.remove(); + app.navigate('admin/client/new', {trigger: true}); + }, + + render:function (eventName) { + + // append and render table structure + $(this.el).html($('#tmpl-client-table').html()); + + _.each(this.model.models, function (client) { + $("#client-table",this.el).append(new ClientView({model:client}).render().el); + }, this); + + this.togglePlaceholder(); + + return this; + }, + + togglePlaceholder:function() { + if (this.model.length > 0) { + $('#client-table', this.el).show(); + $('#client-table-empty', this.el).hide(); + } else { + $('#client-table', this.el).hide(); + $('#client-table-empty', this.el).show(); + } + }, + + refreshTable:function() { + var _self = this; + this.model.fetch({ + success: function() { + _self.render(); + } + }); + } +}); + +var ClientFormView = Backbone.View.extend({ + + tagName:"span", + + initialize:function () { + + if (!this.template) { + this.template = _.template($('#tmpl-client-form').html()); + } + + this.registeredRedirectUriCollection = new Backbone.Collection(); + this.scopeCollection = new Backbone.Collection(); + }, + + events:{ + "click .btn-save":"saveClient", + "click #allowRefresh" : "toggleRefreshTokenTimeout", + "click #disableAccessTokenTimeout" : function(){ $("#access-token-timeout-seconds", this.$el).prop('disabled',!$("#access-token-timeout-seconds", this.$el).prop('disabled')); }, + "click #disableIDTokenTimeout" : function(){ $("#id-token-timeout-seconds", this.$el).prop('disabled',!$("#id-token-timeout-seconds", this.$el).prop('disabled')); }, + "click #disableRefreshTokenTimeout" : function(){ $("#refresh-token-timeout-seconds", this.$el).prop('disabled',!$("#refresh-token-timeout-seconds", this.$el).prop('disabled')); }, + "click .btn-cancel": function() { window.history.back(); return false; }, + "change #requireClientSecret":"toggleRequireClientSecret", + "change #displayClientSecret":"toggleDisplayClientSecret", + "change #generateClientSecret":"toggleGenerateClientSecret", + "change #logoUrl input":"previewLogo" + }, + + toggleRefreshTokenTimeout:function () { + $("#refreshTokenValiditySeconds", this.$el).toggle(); + }, + + previewLogo:function(event) { + if ($('#logoUrl input', this.el).val()) { + $('#logoPreview', this.el).empty(); + $('#logoPreview', this.el).attr('src', $('#logoUrl input').val()); + } else { + $('#logoBlock', this.el).hide(); + } + }, + + /** + * Set up the form based on the current state of the requireClientSecret checkbox parameter + * @param event + */ + toggleRequireClientSecret:function(event) { + + if ($('#requireClientSecret input', this.el).is(':checked')) { + // client secret is required, show all the bits + $('#clientSecretPanel', this.el).show(); + // this function sets up the display portions + this.toggleGenerateClientSecret(); + } else { + // no client secret, hide all the bits + $('#clientSecretPanel', this.el).hide(); + } + }, + + /** + * Set up the form based on the "Generate" checkbox + * @param event + */ + toggleGenerateClientSecret:function(event) { + + if ($('#generateClientSecret input', this.el).is(':checked')) { + // show the "generated" block, hide the "display" checkbox + $('#displayClientSecret', this.el).hide(); + $('#clientSecret', this.el).hide(); + $('#clientSecretGenerated', this.el).show(); + $('#clientSecretHidden', this.el).hide(); + } else { + // show the display checkbox, fall back to the "display" logic + $('#displayClientSecret', this.el).show(); + this.toggleDisplayClientSecret(event); + } + }, + + /** + * Handle whether or not to display the client secret + * @param event + */ + toggleDisplayClientSecret:function(event) { + + if ($('#displayClientSecret input').is(':checked')) { + // want to display it + $('#clientSecret', this.el).show(); + $('#clientSecretHidden', this.el).hide(); + $('#clientSecretGenerated', this.el).hide(); + } else { + // want to hide it + $('#clientSecret', this.el).hide(); + $('#clientSecretHidden', this.el).show(); + $('#clientSecretGenerated', this.el).hide(); + } + }, + + getFormTokenValue:function(value) { + if (value == "") return null; + else return value; + }, + + // maps from a form-friendly name to the real grant parameter name + authorizedGrantMap:{ + "authorization_code": "authorization_code", + "password": "password", + "implicit": "implicit", + "client_credentials": "client_credentials", + "redelegate": "urn:ietf:params:oauth:grant_type:redelegate", + "refresh_token": "refresh_token" + }, + + saveClient:function (event) { + + $('.control-group').removeClass('error'); + + // build the scope object + var scopes = this.scopeCollection.pluck("item"); + + // build the grant type object + var authorizedGrantTypes = []; + $.each(this.authorizedGrantMap, function(index,type) { + if ($('#authorizedGrantTypes-' + index).is(':checked')) { + authorizedGrantTypes.push(type); + } + }); + + var requireClientSecret = $('#requireClientSecret input').is(':checked'); + var generateClientSecret = $('#generateClientSecret input').is(':checked'); + var clientSecret = null; + + if (requireClientSecret && !generateClientSecret) { + // if it's required but we're not generating it, send the value + clientSecret = $('#clientSecret input').val(); + } + + var accessTokenValiditySeconds = null; + if (!$('disableAccessTokenTimeout').is(':checked')) { + accessTokenValiditySeconds = this.getFormTokenValue($('#accessTokenValiditySeconds input[type=text]').val()); + } + + var idTokenValiditySeconds = null; + if (!$('disableIDTokenTimeout').is(':checked')) { + idTokenValiditySeconds = this.getFormTokenValue($('#idTokenValiditySeconds input[type=text]').val()); + } + + var refreshTokenValiditySeconds = null; + if ($('#allowRefresh').is(':checked')) { + + if ($.inArray('refresh_token', authorizedGrantTypes) == -1) { + authorizedGrantTypes.push('refresh_token'); + } + + if ($.inArray('offline_access', scopes) == -1) { + scopes.push("offline_access"); + } + + if (!$('disableRefreshTokenTimeout').is(':checked')) { + refreshTokenValiditySeconds = this.getFormTokenValue($('#refreshTokenValiditySeconds input[type=text]').val()); + } + } + + var valid = this.model.set({ + clientName:$('#clientName input').val(), + clientId:$('#clientId input').val(), + clientSecret: clientSecret, + generateClientSecret:generateClientSecret, + registeredRedirectUri: this.registeredRedirectUriCollection.pluck("item"), + clientDescription:$('#clientDescription textarea').val(), + logoUrl:$('#logoUrl input').val(), + authorizedGrantTypes: authorizedGrantTypes, + accessTokenValiditySeconds: accessTokenValiditySeconds, + refreshTokenValiditySeconds: refreshTokenValiditySeconds, + idTokenValiditySeconds: idTokenValiditySeconds, + allowRefresh: $('#allowRefresh').is(':checked'), + allowIntrospection: $('#allowIntrospection input').is(':checked'), + scope: scopes + }); + + // post-validate + // TODO: move these into the validation function somehow? + if (this.model.get("allowRefresh") == false) { + this.model.set("refreshTokenValiditySeconds",null); + } + + if ($('#disableIDTokenTimeout').is(':checked')) { + this.model.set("idTokenValiditySeconds",null); + } + + if ($('#disableAccessTokenTimeout').is(':checked')) { + this.model.set("accessTokenValiditySeconds",null); + } + + if ($('#disableRefreshTokenTimeout').is(':checked')) { + this.model.set("refreshTokenValiditySeconds",null); + } + + if (valid) { + + var _self = this; + this.model.save({}, { + success:function () { + app.clientList.add(_self.model); + app.navigate('admin/clients', {trigger:true}); + }, + error:function (model,resp) { + console.error("Oops! The object didn't save correctly.",resp); + } + }); + } + + return false; + }, + + render:function (eventName) { + + $(this.el).html(this.template(this.model.toJSON())); + + + var _self = this; + + // build and bind registered redirect URI collection and view + _.each(this.model.get("registeredRedirectUri"), function (registeredRedirectUri) { + _self.registeredRedirectUriCollection.add(new URIModel({item:registeredRedirectUri})); + }); + + $("#registeredRedirectUri .controls",this.el).html(new ListWidgetView({type:'uri', placeholder: 'http://', + collection: this.registeredRedirectUriCollection}).render().el); + + _self = this; + // build and bind scopes + _.each(this.model.get("scope"), function (scope) { + _self.scopeCollection.add(new Backbone.Model({item:scope})); + }); + + $("#scope .controls",this.el).html(new ListWidgetView({placeholder: 'new scope here' + , autocomplete: _.uniq(_.flatten(app.systemScopeList.pluck("value"))) // TODO: load from default scopes + , collection: this.scopeCollection}).render().el); + + if (!this.model.get("allowRefresh")) { + $("#refreshTokenValiditySeconds", this.$el).hide(); + } + + if (this.model.get("accessTokenValiditySeconds") == null) { + $("#access-token-timeout-seconds", this.$el).prop('disabled',true); + } + + if (this.model.get("refreshTokenValiditySeconds") == null) { + $("#refresh-token-timeout-seconds", this.$el).prop('disabled',true); + } + + if (this.model.get("idTokenValiditySeconds") == null) { + $("#id-token-timeout-seconds", this.$el).prop('disabled',true); + } + + this.toggleRequireClientSecret(); + this.previewLogo(); + + return this; + } +}); + + diff --git a/openid-connect-server/src/main/webapp/resources/js/grant.js b/openid-connect-server/src/main/webapp/resources/js/grant.js new file mode 100644 index 000000000..483cf0a0e --- /dev/null +++ b/openid-connect-server/src/main/webapp/resources/js/grant.js @@ -0,0 +1,146 @@ +var ApprovedSiteModel = Backbone.Model.extend({ + idAttribute: 'id', + + initialize: function() { }, + + urlRoot: 'api/approved' + +}); + +var ApprovedSiteCollection = Backbone.Collection.extend({ + initialize: function() { }, + + model: ApprovedSiteModel, + url: 'api/approved' +}); + + +var ApprovedSiteListView = Backbone.View.extend({ + tagName: 'span', + + initialize:function() { }, + + events: { + "click .refresh-table":"refreshTable" + }, + + render:function (eventName) { + $(this.el).html($('#tmpl-grant-table').html()); + + _.each(this.model.models, function(approvedSite) { + // look up client + var client = app.clientList.getByClientId(approvedSite.get('clientId')); + + if (client != null) { + + if (approvedSite.get('whitelistedSite') != null) { + $('#grant-whitelist-table', this.el).append(new ApprovedSiteView({model: approvedSite, client: client}).render().el); + } else { + $('#grant-table', this.el).append(new ApprovedSiteView({model: approvedSite, client: client}).render().el); + } + + } + + }, this); + + this.togglePlaceholder(); + + return this; + }, + + togglePlaceholder:function() { + // count the whitelisted and non-whitelisted entries + var wl = 0; + var gr = 0; + for (var i = 0; i < this.model.length; i++) { + if (this.model.at(i).get('whitelistedSite') != null) { + wl += 1; + } else { + gr += 1; + } + } + + if (wl > 0) { + $('#grant-whitelist-table', this.el).show(); + $('#grant-whitelist-table-empty', this.el).hide(); + } else { + $('#grant-whitelist-table', this.el).hide(); + $('#grant-whitelist-table-empty', this.el).show(); + } + if (gr > 0) { + $('#grant-table', this.el).show(); + $('#grant-table-empty', this.el).hide(); + } else { + $('#grant-table', this.el).hide(); + $('#grant-table-empty', this.el).show(); + } + }, + + refreshTable:function() { + var _self = this; + this.model.fetch({ + success: function() { + _self.render(); + } + }); + } + +}); + +var ApprovedSiteView = Backbone.View.extend({ + tagName: 'tr', + + initialize: function() { + if (!this.template) { + this.template = _.template($('#tmpl-grant').html()); + } + if (!this.scopeTemplate) { + this.scopeTemplate = _.template($('#tmpl-scope-list').html()); + } + + }, + + render: function() { + var json = {grant: this.model.toJSON(), client: this.options.client.toJSON()}; + + this.$el.html(this.template(json)); + + $('.scope-list', this.el).html(this.scopeTemplate({scopes: this.options.client.get('scope'), systemScopes: app.systemScopeList})); + + this.$('.dynamically-registered').tooltip({title: 'This client was dynamically registered'}); + this.$('.whitelisted-site').tooltip({title: 'This site was whitelisted by an adminstrator'}); + + return this; + }, + + events: { + 'click .btn-delete': 'deleteApprovedSite' + }, + + deleteApprovedSite:function() { + if (confirm("Are you sure you want to revoke access to this site?")) { + var self = this; + + this.model.destroy({ + success:function () { + self.$el.fadeTo("fast", 0.00, function () { //fade + $(this).slideUp("fast", function () { //slide up + $(this).remove(); //then remove from the DOM + app.approvedSiteListView.togglePlaceholder(); + }); + }); + } + }); + + app.approvedSiteListView.delegateEvents(); + } + + return false; + }, + + close:function() { + $(this.el).unbind(); + $(this.el).empty(); + } +}); + diff --git a/openid-connect-server/src/main/webapp/resources/js/backbone.js b/openid-connect-server/src/main/webapp/resources/js/lib/backbone.js similarity index 100% rename from openid-connect-server/src/main/webapp/resources/js/backbone.js rename to openid-connect-server/src/main/webapp/resources/js/lib/backbone.js diff --git a/openid-connect-server/src/main/webapp/resources/js/backbone.validations.js b/openid-connect-server/src/main/webapp/resources/js/lib/backbone.validations.js similarity index 100% rename from openid-connect-server/src/main/webapp/resources/js/backbone.validations.js rename to openid-connect-server/src/main/webapp/resources/js/lib/backbone.validations.js diff --git a/openid-connect-server/src/main/webapp/resources/js/jquery.js b/openid-connect-server/src/main/webapp/resources/js/lib/jquery.js similarity index 100% rename from openid-connect-server/src/main/webapp/resources/js/jquery.js rename to openid-connect-server/src/main/webapp/resources/js/lib/jquery.js diff --git a/openid-connect-server/src/main/webapp/resources/js/purl.js b/openid-connect-server/src/main/webapp/resources/js/lib/purl.js similarity index 100% rename from openid-connect-server/src/main/webapp/resources/js/purl.js rename to openid-connect-server/src/main/webapp/resources/js/lib/purl.js diff --git a/openid-connect-server/src/main/webapp/resources/js/underscore.js b/openid-connect-server/src/main/webapp/resources/js/lib/underscore.js similarity index 100% rename from openid-connect-server/src/main/webapp/resources/js/underscore.js rename to openid-connect-server/src/main/webapp/resources/js/lib/underscore.js diff --git a/openid-connect-server/src/main/webapp/resources/js/scope.js b/openid-connect-server/src/main/webapp/resources/js/scope.js new file mode 100644 index 000000000..47478faa9 --- /dev/null +++ b/openid-connect-server/src/main/webapp/resources/js/scope.js @@ -0,0 +1,259 @@ +var SystemScopeModel = Backbone.Model.extend({ + idAttribute: 'id', + + defaults:{ + id:null, + description:null, + icon:null, + value:null, + defaultScope:false, + allowDynReg:false + }, + + urlRoot: 'api/scopes' +}); + +var SystemScopeCollection = Backbone.Collection.extend({ + idAttribute: 'id', + + model: SystemScopeModel, + + url: 'api/scopes', + + defaultScopes: function() { + filtered = this.filter(function(scope) { + return scope.get("defaultScope") === true; + }); + return new SystemScopeCollection(filtered); + }, + + getByValue: function(value) { + var scopes = this.where({value: value}); + if (scopes.length == 1) { + return scopes[0]; + } else { + return null; + } + } + +}); + +var SystemScopeView = Backbone.View.extend({ + + tagName: 'tr', + + initialize:function () { + + if (!this.template) { + this.template = _.template($('#tmpl-system-scope').html()); + } + + this.model.bind('change', this.render, this); + + }, + + events: { + 'click .btn-edit':'editScope', + 'click .btn-delete':'deleteScope' + }, + + editScope:function() { + app.navigate('admin/scope/' + this.model.id, {trigger: true}); + }, + + render:function (eventName) { + this.$el.html(this.template(this.model.toJSON())); + + this.$('.dynamically-registered').tooltip({title: 'This client was dynamically registered'}); + + return this; + }, + + deleteScope:function () { + + if (confirm("Are you sure sure you would like to delete this scope? Clients that have this scope will still be able to ask for it.")) { + var self = this; + + this.model.destroy({ + success:function () { + self.$el.fadeTo("fast", 0.00, function () { //fade + $(this).slideUp("fast", function () { //slide up + $(this).remove(); //then remove from the DOM + app.systemScopeListView.togglePlaceholder(); + }); + }); + } + }); + + app.systemScopeListView.delegateEvents(); + } + + return false; + }, + + close:function () { + $(this.el).unbind(); + $(this.el).empty(); + } +}); + +var SystemScopeListView = Backbone.View.extend({ + tagName: 'span', + + events:{ + "click .new-scope":"newScope", + "click .refresh-table":"refreshTable" + }, + + newScope:function() { + this.remove(); + app.navigate('admin/scope/new', {trigger: true}); + }, + + refreshTable:function() { + var _self = this; + this.model.fetch({ + success: function() { + _self.render(); + } + }); + }, + + togglePlaceholder:function() { + if (this.model.length > 0) { + $('#scope-table', this.el).show(); + $('#scope-table-empty', this.el).hide(); + } else { + $('#scope-table', this.el).hide(); + $('#scope-table-empty', this.el).show(); + } + }, + + render: function (eventName) { + + // append and render the table structure + $(this.el).html($('#tmpl-system-scope-table').html()); + + _.each(this.model.models, function (scope) { + $("#scope-table", this.el).append(new SystemScopeView({model: scope}).render().el); + }, this); + + this.togglePlaceholder(); + + return this; + } +}); + +var SystemScopeFormView = Backbone.View.extend({ + tagName: 'span', + + initialize:function() { + if (!this.template) { + this.template = _.template($('#tmpl-system-scope-form').html()); + } + if (!this.iconTemplate) { + this.iconTemplate = _.template($('#tmpl-system-scope-icon').html()); + } + + // initialize our icon set into slices for the selector + if (!this.bootstrapIcons) { + this.bootstrapIcons = []; + + var iconList = ['glass', 'music', 'search', 'envelope', 'heart', 'star', + 'star-empty', 'user', 'film', 'th-large', 'th', 'th-list', 'ok', + 'remove', 'zoom-in', 'zoom-out', 'off', 'signal', 'cog', 'trash', + 'home', 'file', 'time', 'road', 'download-alt', 'download', + 'upload', 'inbox', 'play-circle', 'repeat', 'refresh', 'list-alt', + 'lock', 'flag', 'headphones', 'volume-off', 'volume-down', + 'volume-up', 'qrcode', 'barcode', 'tag', 'tags', 'book', + 'bookmark', 'print', 'camera', 'font', 'bold', 'italic', + 'text-height', 'text-width', 'align-left', 'align-center', + 'align-right', 'align-justify', 'list', 'indent-left', + 'indent-right', 'facetime-video', 'picture', 'pencil', + 'map-marker', 'tint', 'share', 'move', 'fast-backward', 'backward', + 'pause', 'stop', 'forward', 'step-forward', 'eject', + 'chevron-right', 'plus-sign', 'minus-sign', 'remove-sign', + 'ok-sign', 'question-sign', 'info-sign', 'screenshot', + 'remove-circle', 'ok-circle', 'ban-circle', 'arrow-left', + 'arrow-right', 'arrow-down', 'share-alt', 'resize-full', + 'resize-small', 'plus', 'asterisk', 'exclamation-sign', 'gift', + 'leaf', 'fire', 'eye-close', 'plane', 'random', 'magnet', + 'chevron-up', 'chevron-down', 'retweet', 'shopping-cart', + 'folder-close', 'folder-open', 'resize-vertical', + 'resize-horizontal', 'hdd', 'bell', 'thumbs-up', 'hand-right', + 'hand-left', 'hand-down', 'circle-arrow-left', 'circle-arrow-up', + 'circle-arrow-down', 'globe', 'tasks', 'briefcase' ]; + + var size = 3; + while (iconList.length > 0) { + this.bootstrapIcons.push(iconList.splice(0, size)); + } + + } + }, + + events:{ + 'click .btn-save':'saveScope', + 'click .btn-cancel': function() {app.navigate('admin/scope', {trigger: true}); }, + 'click .btn-icon':'selectIcon' + }, + + saveScope:function(event) { + + var value = $('#value input').val(); + + if (value == null || value.trim() == "") { + // error: can't have a blank scope + return false; + } + + var valid = this.model.set({ + value:value, + description:$('#description textarea').val(), + icon:$('#iconDisplay input').val(), + defaultScope:$('#defaultScope input').is(':checked'), + allowDynReg:$('#allowDynReg input').is(':checked') + }); + + if (valid) { + var _self = this; + this.model.save({}, { + success:function() { + app.systemScopeList.add(_self.model); + app.navigate('admin/scope', {trigger: true}); + }, + error:function(model,resp) { + console.error("The scope didn't save correctly.", resp); + } + }); + } + + return false; + }, + + selectIcon:function(event) { + + var icon = event.target.value; + + $('#iconDisplay input').val(icon); + $('#iconDisplay span').html(icon); + $('#iconDisplay i').removeClass(); + $('#iconDisplay i').addClass('icon-' + icon); + + $('#iconSelector').modal('hide'); + + return false; + }, + + render: function(eventName) { + this.$el.html(this.template(this.model.toJSON())); + + _.each(this.bootstrapIcons, function (items) { + $(".modal-body", this.el).append(this.iconTemplate({items:items})); + }, this); + + + return this; + } +}); + diff --git a/openid-connect-server/src/main/webapp/resources/js/whitelist.js b/openid-connect-server/src/main/webapp/resources/js/whitelist.js new file mode 100644 index 000000000..e25c1ecfb --- /dev/null +++ b/openid-connect-server/src/main/webapp/resources/js/whitelist.js @@ -0,0 +1,223 @@ +var WhiteListModel = Backbone.Model.extend({ + + idAttribute: "id", + + initialize: function () { }, + + urlRoot: "api/whitelist" + +}); + +var WhiteListCollection = Backbone.Collection.extend({ + initialize: function() { + //this.fetch(); + }, + + getByClientId: function(clientId) { + var clients = this.where({clientId: clientId}); + if (clients.length == 1) { + return clients[0]; + } else { + return null; + } + }, + + model: WhiteListModel, + url: "api/whitelist" + +}); + +var WhiteListListView = Backbone.View.extend({ + tagName: 'span', + + initialize:function () { + //this.model.bind("reset", this.render, this); + }, + + events:{ + "click .refresh-table":"refreshTable" + }, + + render:function (eventName) { + $(this.el).html($('#tmpl-whitelist-table').html()); + + _.each(this.model.models, function (whiteList) { + + // look up client + var client = app.clientList.getByClientId(whiteList.get('clientId')); + + // if there's no client ID, this is an error! + if (client != null) { + $('#whitelist-table', this.el).append(new WhiteListView({model: whiteList, client: client}).render().el); + } + + }, this); + + this.togglePlaceholder(); + + return this; + }, + + togglePlaceholder:function() { + if (this.model.length > 0) { + $('#whitelist-table', this.el).show(); + $('#whitelist-table-empty', this.el).hide(); + } else { + $('#whitelist-table', this.el).hide(); + $('#whitelist-table-empty', this.el).show(); + } + }, + + refreshTable:function() { + var _self = this; + this.model.fetch({ + success: function() { + _self.render(); + } + }); + } +}); + +var WhiteListView = Backbone.View.extend({ + tagName: 'tr', + + initialize:function() { + if (!this.template) { + this.template = _.template($('#tmpl-whitelist').html()); + } + + if (!this.scopeTemplate) { + this.scopeTemplate = _.template($('#tmpl-scope-list').html()); + } + + this.model.bind('change', this.render, this); + }, + + render:function(eventName) { + + var json = {whiteList: this.model.toJSON(), client: this.options.client.toJSON()}; + + this.$el.html(this.template(json)); + + $('.scope-list', this.el).html(this.scopeTemplate({scopes: this.model.get('allowedScopes'), systemScopes: app.systemScopeList})); + + this.$('.dynamically-registered').tooltip({title: 'This client was dynamically registered'}); + + return this; + }, + + events:{ + 'click .btn-edit': 'editWhitelist', + 'click .btn-delete': 'deleteWhitelist' + }, + + editWhitelist:function() { + app.navigate('admin/whitelist/' + this.model.id, {trigger: true}); + }, + + deleteWhitelist:function() { + + if (confirm("Are you sure you want to delete this whitelist entry?")) { + var self = this; + + this.model.destroy({ + success:function () { + self.$el.fadeTo("fast", 0.00, function () { //fade + $(this).slideUp("fast", function () { //slide up + $(this).remove(); //then remove from the DOM + // check the placeholder in case it's empty now + app.whiteListListView.togglePlaceholder(); + }); + }); + } + }); + + app.whiteListListView.delegateEvents(); + } + + return false; + }, + + close:function() { + $(this.el).unbind(); + $(this.el).empty(); + } +}); + +var WhiteListFormView = Backbone.View.extend({ + tagName: 'span', + + initialize:function () { + if (!this.template) { + this.template = _.template($('#tmpl-whitelist-form').html()); + } + + this.scopeCollection = new Backbone.Collection(); + }, + + events:{ + 'click .btn-save':'saveWhiteList', + 'click .btn-cancel':'cancelWhiteList', + + }, + + saveWhiteList:function (event) { + $('.control-group').removeClass('error'); + + // process allowed scopes + var allowedScopes = this.scopeCollection.pluck("item"); + + if (this.model.get('id') == null) { + this.model.set({clientId:$('#clientId input').val()}); + } + + var valid = this.model.set({ + allowedScopes: allowedScopes + }); + + if (valid) { + var _self = this; + this.model.save({}, { + success:function () { + app.whiteListList.add(_self.model); + app.navigate('admin/whitelists', {trigger:true}); + }, + error:function (model,resp) { + console.error("Oops! The object didn't save correctly.",resp); + } + }); + } + + return false; + + }, + + cancelWhiteList:function(event) { + app.navigate('admin/whitelists', {trigger:true}); + }, + + render:function (eventName) { + + var json = {whiteList: this.model.toJSON(), client: this.options.client.toJSON()}; + + this.$el.html(this.template(json)); + + + var _self = this; + // build and bind scopes + _.each(this.model.get("allowedScopes"), function (scope) { + _self.scopeCollection.add(new Backbone.Model({item:scope})); + }); + + $("#scope .controls",this.el).html(new ListWidgetView({ + placeholder: 'new scope here', + autocomplete: this.options.client.scope, + collection: this.scopeCollection}).render().el); + + + return this; + + } + +}); + diff --git a/openid-connect-server/src/main/webapp/resources/template/admin.html b/openid-connect-server/src/main/webapp/resources/template/admin.html index 8c4091325..0681d4954 100644 --- a/openid-connect-server/src/main/webapp/resources/template/admin.html +++ b/openid-connect-server/src/main/webapp/resources/template/admin.html @@ -1,314 +1,3 @@ - - - - - - - - - - - - - - - - - - - - - - - + - - - - - - - - - - \ No newline at end of file diff --git a/openid-connect-server/src/main/webapp/resources/template/client.html b/openid-connect-server/src/main/webapp/resources/template/client.html new file mode 100644 index 000000000..6c2aabfc0 --- /dev/null +++ b/openid-connect-server/src/main/webapp/resources/template/client.html @@ -0,0 +1,310 @@ + + + + + + + + \ No newline at end of file diff --git a/openid-connect-server/src/main/webapp/resources/template/grant.html b/openid-connect-server/src/main/webapp/resources/template/grant.html new file mode 100644 index 000000000..524b2bf9d --- /dev/null +++ b/openid-connect-server/src/main/webapp/resources/template/grant.html @@ -0,0 +1,99 @@ + + + + + + diff --git a/openid-connect-server/src/main/webapp/resources/template/scope.html b/openid-connect-server/src/main/webapp/resources/template/scope.html new file mode 100644 index 000000000..c97d9959a --- /dev/null +++ b/openid-connect-server/src/main/webapp/resources/template/scope.html @@ -0,0 +1,175 @@ + + + + + + + + + + \ No newline at end of file diff --git a/openid-connect-server/src/main/webapp/resources/template/whitelist.html b/openid-connect-server/src/main/webapp/resources/template/whitelist.html new file mode 100644 index 000000000..898a5fe81 --- /dev/null +++ b/openid-connect-server/src/main/webapp/resources/template/whitelist.html @@ -0,0 +1,92 @@ + + + + + + +