Return-Path: X-Original-To: apmail-couchdb-commits-archive@www.apache.org Delivered-To: apmail-couchdb-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id B701EDFF5 for ; Wed, 15 May 2013 08:29:10 +0000 (UTC) Received: (qmail 19554 invoked by uid 500); 15 May 2013 08:29:08 -0000 Delivered-To: apmail-couchdb-commits-archive@couchdb.apache.org Received: (qmail 19369 invoked by uid 500); 15 May 2013 08:29:08 -0000 Mailing-List: contact commits-help@couchdb.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@couchdb.apache.org Delivered-To: mailing list commits@couchdb.apache.org Received: (qmail 18043 invoked by uid 99); 15 May 2013 08:29:02 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Wed, 15 May 2013 08:29:02 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id 85C75A6BC; Wed, 15 May 2013 08:29:02 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: garren@apache.org To: commits@couchdb.apache.org Date: Wed, 15 May 2013 08:29:08 -0000 Message-Id: <03349b0e61ce4f4fadeb7bbc5534b856@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [08/13] git commit: updated refs/heads/master to 0cd82b9 Add authentication plugin, example module and couchdb admin authentication Project: http://git-wip-us.apache.org/repos/asf/couchdb/repo Commit: http://git-wip-us.apache.org/repos/asf/couchdb/commit/1fec8045 Tree: http://git-wip-us.apache.org/repos/asf/couchdb/tree/1fec8045 Diff: http://git-wip-us.apache.org/repos/asf/couchdb/diff/1fec8045 Branch: refs/heads/master Commit: 1fec804556cd1d145fb44167b44843e4795beed2 Parents: 78a2a22 Author: Garren Smith Authored: Wed May 8 17:11:42 2013 +0200 Committer: Garren Smith Committed: Wed May 15 10:26:27 2013 +0200 ---------------------------------------------------------------------- .gitignore | 1 + src/fauxton/app/addons/auth/base.js | 28 ++++++- src/fauxton/app/addons/auth/resources.js | 64 ++++++++++---- .../app/addons/auth/templates/nav_link.html | 2 +- .../app/addons/auth/templates/noAccess.html | 19 ++++ src/fauxton/app/addons/config/routes.js | 2 + src/fauxton/app/addons/exampleAuth/base.js | 59 ++++++++++++ .../app/addons/exampleAuth/templates/noAccess.html | 19 ++++ src/fauxton/app/addons/logs/routes.js | 2 + src/fauxton/app/api.js | 70 ++++++++++++++- src/fauxton/app/modules/documents/routes.js | 7 +- src/fauxton/app/router.js | 13 ++- 12 files changed, 260 insertions(+), 26 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/.gitignore ---------------------------------------------------------------------- diff --git a/.gitignore b/.gitignore index 67da1c8..c1ece09 100644 --- a/.gitignore +++ b/.gitignore @@ -92,6 +92,7 @@ src/fauxton/app/addons/* !src/fauxton/app/addons/stats !src/fauxton/app/addons/contribute !src/fauxton/app/addons/auth +!src/fauxton/app/addons/exampleAuth src/fauxton/settings.json* !src/fauxton/settings.json.default share/www/fauxton http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/src/fauxton/app/addons/auth/base.js ---------------------------------------------------------------------- diff --git a/src/fauxton/app/addons/auth/base.js b/src/fauxton/app/addons/auth/base.js index 4cf1b83..9f40308 100644 --- a/src/fauxton/app/addons/auth/base.js +++ b/src/fauxton/app/addons/auth/base.js @@ -19,7 +19,7 @@ define([ function(app, FauxtonAPI, Auth) { Auth.initialize = function() { - Auth.session = new Auth.Session(); + var session = Auth.session = new Auth.Session(); Auth.navLink = new Auth.NavLink({model: Auth.session}); FauxtonAPI.addHeaderLink({ @@ -28,6 +28,32 @@ function(app, FauxtonAPI, Auth) { view: Auth.navLink, establish: [Auth.session.fetchOnce()] }); + + var auth = function (roles, layout) { + var deferred = $.Deferred(); + + var sessionDeferred = session.fetchOnce().then(function () { + console.log(session); + + if (session.isAdminParty()) { + deferred.resolve(); + } else if(session.matchesRoles(roles)) { + deferred.resolve(); + } else { + deferred.reject(); + } + }); + + return [sessionDeferred, deferred]; + }; + + var authDenied = function () { + app.masterLayout.setView('#dashboard', new Auth.NoAccessView()); + app.masterLayout.renderView('#dashboard'); + }; + + FauxtonAPI.auth.registerAuth(auth); + FauxtonAPI.auth.registerAuthDenied(authDenied); }; return Auth; http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/src/fauxton/app/addons/auth/resources.js ---------------------------------------------------------------------- diff --git a/src/fauxton/app/addons/auth/resources.js b/src/fauxton/app/addons/auth/resources.js index 64dd254..92cff91 100644 --- a/src/fauxton/app/addons/auth/resources.js +++ b/src/fauxton/app/addons/auth/resources.js @@ -17,6 +17,8 @@ define([ function (app, FauxtonAPI) { + var Auth = new FauxtonAPI.addon(); + var Admin = Backbone.Model.extend({ url: function () { @@ -44,12 +46,10 @@ function (app, FauxtonAPI) { } }); - var Auth = new FauxtonAPI.addon(); - Auth.Session = Backbone.Model.extend({ url: '/_session', - is_admin_party: function () { + isAdminParty: function () { var userCtx = this.get('userCtx'); if (!userCtx.name && userCtx.roles.indexOf("_admin") > -1) { @@ -70,6 +70,30 @@ function (app, FauxtonAPI) { }; }, + userRoles: function () { + var user = this.user(); + + if (user && user.roles) { + return user.roles; + } + + return []; + }, + + matchesRoles: function (roles) { + if (roles.length === 0) { + return true; + } + + var numberMatchingRoles = _.intersection(this.userRoles(), roles).length; + + if (numberMatchingRoles > 0) { + return true; + } + + return false; + }, + fetchOnce: function (opt) { var options = _.extend({}, opt); @@ -80,7 +104,7 @@ function (app, FauxtonAPI) { return this._deferred; }, - validate_user: function (username, password, msg) { + validateUser: function (username, password, msg) { if (_.isEmpty(username) || _.isEmpty(password)) { var deferred = $.Deferred(); @@ -89,7 +113,7 @@ function (app, FauxtonAPI) { } }, - validate_passwords: function (password, password_confirm, msg) { + validatePasswords: function (password, password_confirm, msg) { if (_.isEmpty(password) || _.isEmpty(password_confirm) || (password !== password_confirm)) { var deferred = $.Deferred(); @@ -99,9 +123,9 @@ function (app, FauxtonAPI) { }, - create_admin: function (username, password, login) { + createAdmin: function (username, password, login) { var self = this, - error_promise = this.validate_user(username, password, 'Authname or password cannot be blank.'); + error_promise = this.validateUser(username, password, 'Authname or password cannot be blank.'); if (error_promise) { return error_promise; } @@ -120,7 +144,7 @@ function (app, FauxtonAPI) { }, login: function (username, password) { - var error_promise = this.validate_user(username, password, 'Authname or password cannot be blank.'); + var error_promise = this.validateUser(username, password, 'Authname or password cannot be blank.'); if (error_promise) { return error_promise; } @@ -150,8 +174,8 @@ function (app, FauxtonAPI) { }); }, - change_password: function (password, password_confirm) { - var error_promise = this.validate_passwords(password, password_confirm, 'Passwords do not match.'); + changePassword: function (password, password_confirm) { + var error_promise = this.validatePasswords(password, password_confirm, 'Passwords do not match.'); if (error_promise) { return error_promise; } @@ -208,10 +232,10 @@ function (app, FauxtonAPI) { }, events: { - "click #create-admin": "create_admin" + "click #create-admin": "createAdmin" }, - create_admin: function (event) { + createAdmin: function (event) { event.preventDefault(); this.clear_error_msg(); @@ -219,7 +243,7 @@ function (app, FauxtonAPI) { username = this.$('#username').val(), password = this.$('#password').val(); - var promise = this.model.create_admin(username, password, this.login_after); + var promise = this.model.createAdmin(username, password, this.login_after); promise.then(function () { self.$('.modal').modal('hide'); @@ -265,10 +289,10 @@ function (app, FauxtonAPI) { template: 'addons/auth/templates/change_password_modal', events: { - "click #change-password": "change_password" + "click #change-password": "changePassword" }, - change_password: function () { + changePassword: function () { event.preventDefault(); this.clear_error_msg(); @@ -276,7 +300,7 @@ function (app, FauxtonAPI) { new_password = this.$('#password').val(), password_confirm = this.$('#password-confirm').val(); - var promise = this.model.change_password(new_password, password_confirm); + var promise = this.model.changePassword(new_password, password_confirm); promise.done(function () { self.hide_modal(); @@ -299,7 +323,7 @@ function (app, FauxtonAPI) { serialize: function () { return { - admin_party: this.model.is_admin_party(), + admin_party: this.model.isAdminParty(), user: this.model.user() }; }, @@ -346,5 +370,11 @@ function (app, FauxtonAPI) { } }); + Auth.NoAccessView = FauxtonAPI.View.extend({ + template: "addons/auth/templates/noAccess" + + }); + + return Auth; }); http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/src/fauxton/app/addons/auth/templates/nav_link.html ---------------------------------------------------------------------- diff --git a/src/fauxton/app/addons/auth/templates/nav_link.html b/src/fauxton/app/addons/auth/templates/nav_link.html index 2f1462f..ba0a6f9 100644 --- a/src/fauxton/app/addons/auth/templates/nav_link.html +++ b/src/fauxton/app/addons/auth/templates/nav_link.html @@ -32,7 +32,7 @@ the License.
  • Logout
  • <% } else { %>
  • Login
  • -
  • Sign up
  • + <% } %> http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/src/fauxton/app/addons/auth/templates/noAccess.html ---------------------------------------------------------------------- diff --git a/src/fauxton/app/addons/auth/templates/noAccess.html b/src/fauxton/app/addons/auth/templates/noAccess.html new file mode 100644 index 0000000..f1a9506 --- /dev/null +++ b/src/fauxton/app/addons/auth/templates/noAccess.html @@ -0,0 +1,19 @@ + + +
    +
    +

    You do not have permission to view this page

    +
    +
    http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/src/fauxton/app/addons/config/routes.js ---------------------------------------------------------------------- diff --git a/src/fauxton/app/addons/config/routes.js b/src/fauxton/app/addons/config/routes.js index 42934a8..d86715f 100644 --- a/src/fauxton/app/addons/config/routes.js +++ b/src/fauxton/app/addons/config/routes.js @@ -28,6 +28,8 @@ function(app, FauxtonAPI, Config) { this.configs = new Config.Collection(); }, + roles: ["_admin"], + crumbs: [ {"name": "Config","link": "_config"} ], http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/src/fauxton/app/addons/exampleAuth/base.js ---------------------------------------------------------------------- diff --git a/src/fauxton/app/addons/exampleAuth/base.js b/src/fauxton/app/addons/exampleAuth/base.js new file mode 100644 index 0000000..aa99670 --- /dev/null +++ b/src/fauxton/app/addons/exampleAuth/base.js @@ -0,0 +1,59 @@ +// Licensed under the Apache License, Version 2.0 (the "License"); you may not +// use this file except in compliance with the License. You may obtain a copy of +// the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +// License for the specific language governing permissions and limitations under +// the License. + +define([ + "app", + + "api" +], + +function(app, FauxtonAPI) { + // This is an example module of using the new auth module. + + var noAccessView = FauxtonAPI.View.extend({ + template: "addons/exampleAuth/templates/noAccess" + + }); + + // To utilise the authentication - all that is required, is one callback + // that is registered with the auth api. This function can return an array + // of deferred objects. + // The roles argument that is passed in is the required roles for the current user + // to be allowed to access the current page. + // The layout is the main layout for use when you want to render a view onto the page + var auth = function (roles) { + var deferred = $.Deferred(); + + if (roles.indexOf('_admin') > -1) { + deferred.reject(); + } else { + deferred.resolve(); + } + + return [deferred]; + }; + + // If you would like to do something with when access is denied you can register this callback. + // It will be called is access has been denied on the previous page. + var authFail = function () { + app.masterLayout.setView('#dashboard', new noAccessView()); + app.masterLayout.renderView('#dashboard'); + }; + + // Register the auth call back. This will be called before new route rendered + FauxtonAPI.auth.registerAuth(auth); + // Register a failed route request callback. This is called if access is denied. + FauxtonAPI.auth.registerAuthDenied(authFail); + + + +}); http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/src/fauxton/app/addons/exampleAuth/templates/noAccess.html ---------------------------------------------------------------------- diff --git a/src/fauxton/app/addons/exampleAuth/templates/noAccess.html b/src/fauxton/app/addons/exampleAuth/templates/noAccess.html new file mode 100644 index 0000000..f1a9506 --- /dev/null +++ b/src/fauxton/app/addons/exampleAuth/templates/noAccess.html @@ -0,0 +1,19 @@ + + +
    +
    +

    You do not have permission to view this page

    +
    +
    http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/src/fauxton/app/addons/logs/routes.js ---------------------------------------------------------------------- diff --git a/src/fauxton/app/addons/logs/routes.js b/src/fauxton/app/addons/logs/routes.js index 1ce260c..ae0a6b6 100644 --- a/src/fauxton/app/addons/logs/routes.js +++ b/src/fauxton/app/addons/logs/routes.js @@ -32,6 +32,8 @@ function(app, FauxtonAPI, Log) { "_log": "showLog" }, + roles: ["_admin"], + apiUrl: function() { return this.logs.url(); }, http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/src/fauxton/app/api.js ---------------------------------------------------------------------- diff --git a/src/fauxton/app/api.js b/src/fauxton/app/api.js index 1c0b8f8..9c4ecef 100644 --- a/src/fauxton/app/api.js +++ b/src/fauxton/app/api.js @@ -108,6 +108,57 @@ function(app, Fauxton) { } }); + // This is not exposed externally as it should not need to be accessed or overridden + var Auth = function (options) { + this._options = options; + this.initialize.apply(this, arguments); + }; + + // Piggy-back on Backbone's self-propagating extend function, + Auth.extend = Backbone.Model.extend; + + _.extend(Auth.prototype, Backbone.Events, { + authDeniedCb: function() {}, + + initialize: function() { + var self = this; + + $(document).ajaxError(function(event, jqxhr, settings, exception) { + console.log("UNAUTH"); + console.log(arguments); + if (exception === "Unauthorized" || exception === "Forbidden") { + self.authDeniedCb(); + } + }); + }, + + authHandlerCb : function (roles, layout) { + var deferred = $.Deferred(); + deferred.resolve(); + return deferred; + }, + + registerAuth: function (authHandlerCb) { + this.authHandlerCb = authHandlerCb; + }, + + registerAuthDenied: function (authDeniedCb) { + this.authDeniedCb = authDeniedCb; + }, + + checkAccess: function (roles) { + var requiredRoles = roles || [], + authDeniedCb = this.authDeniedCb, + promise = $.when.apply(null, this.authHandlerCb(requiredRoles)); + + promise.fail(function () { authDeniedCb();}); + + return promise; + } + }); + + FauxtonAPI.auth = new Auth(); + FauxtonAPI.RouteObject = function(options) { this._options = options; @@ -133,6 +184,7 @@ function(app, Fauxton) { renderedState: false, establish: function() {}, route: function() {}, + roles: [], initialize: function() {} }, { @@ -248,7 +300,23 @@ function(app, Fauxton) { routeCallback: function (route) { var routes = this.get('routes'); - return this[routes[route]]; + var routeObj = routes[route]; + + if (typeof routeObj === 'object') { + return this[routeObj.route]; + } else { + return this[routeObj]; + } + }, + + getRouteRoles: function (routeUrl) { + var route = this.get('routes')[routeUrl]; + + if ((typeof route === 'object') && route.roles) { + return route.roles; + } + + return this.roles; } }); http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/src/fauxton/app/modules/documents/routes.js ---------------------------------------------------------------------- diff --git a/src/fauxton/app/modules/documents/routes.js b/src/fauxton/app/modules/documents/routes.js index 103d904..bb8b193 100644 --- a/src/fauxton/app/modules/documents/routes.js +++ b/src/fauxton/app/modules/documents/routes.js @@ -173,7 +173,10 @@ function(app, FauxtonAPI, Documents, Databases) { routes: { "database/:database/_all_docs(:extra)": "allDocs", - "database/:database/_design/:ddoc/_view/:view": "viewFn", + "database/:database/_design/:ddoc/_view/:view": { + route: "viewFn", + roles: ['_admin'] + }, "database/:database/new_view": "newViewEditor" }, @@ -281,8 +284,6 @@ function(app, FauxtonAPI, Documents, Databases) { }); - - var ChangesRouteObject = FauxtonAPI.RouteObject.extend({ layout: "with_tabs", http://git-wip-us.apache.org/repos/asf/couchdb/blob/1fec8045/src/fauxton/app/router.js ---------------------------------------------------------------------- diff --git a/src/fauxton/app/router.js b/src/fauxton/app/router.js index 45cc8b2..4a79ff1 100644 --- a/src/fauxton/app/router.js +++ b/src/fauxton/app/router.js @@ -64,10 +64,16 @@ function(req, app, Initialize, FauxtonAPI, Fauxton, Layout, Databases, Documents } var routeObject = self.activeRouteObject, - routeCallback = routeObject.routeCallback(route); + routeCallback = routeObject.routeCallback(route), + roles = routeObject.getRouteRoles(route); + + var authPromise = app.auth.checkAccess(roles); + + authPromise.then(function () { + routeCallback.apply(routeObject, args); + routeObject.render(route, masterLayout, args); + }); - routeCallback.apply(routeObject, args); - routeObject.render(route, masterLayout, args); }); }, this); }, @@ -108,6 +114,7 @@ function(req, app, Initialize, FauxtonAPI, Fauxton, Layout, Databases, Documents //TODO: It would be nice to handle this with a router this.navBar = app.navBar = new Fauxton.NavBar(); this.apiBar = app.apiBar = new Fauxton.ApiBar(); + this.auth = app.auth = FauxtonAPI.auth; app.masterLayout = this.masterLayout = new Layout(this.navBar, this.apiBar); app.footer = new Fauxton.Footer({el: "#footer-content"});