summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGarren Smith <garren.smith@gmail.com>2013-05-08 17:11:42 +0200
committerGarren Smith <garren.smith@gmail.com>2013-05-15 10:26:27 +0200
commit1fec804556cd1d145fb44167b44843e4795beed2 (patch)
tree2783f896bea52569d5adfe79088e839ba333a5e5
parent78a2a223c01dd26c4aa3ef15a958b17032eb6724 (diff)
downloadcouchdb-1fec804556cd1d145fb44167b44843e4795beed2.tar.gz
Add authentication plugin, example module and couchdb admin
authentication
-rw-r--r--.gitignore1
-rw-r--r--src/fauxton/app/addons/auth/base.js28
-rw-r--r--src/fauxton/app/addons/auth/resources.js64
-rw-r--r--src/fauxton/app/addons/auth/templates/nav_link.html2
-rw-r--r--src/fauxton/app/addons/auth/templates/noAccess.html19
-rw-r--r--src/fauxton/app/addons/config/routes.js2
-rw-r--r--src/fauxton/app/addons/exampleAuth/base.js59
-rw-r--r--src/fauxton/app/addons/exampleAuth/templates/noAccess.html19
-rw-r--r--src/fauxton/app/addons/logs/routes.js2
-rw-r--r--src/fauxton/app/api.js70
-rw-r--r--src/fauxton/app/modules/documents/routes.js7
-rw-r--r--src/fauxton/app/router.js13
12 files changed, 260 insertions, 26 deletions
diff --git a/.gitignore b/.gitignore
index 67da1c81c..c1ece0940 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
diff --git a/src/fauxton/app/addons/auth/base.js b/src/fauxton/app/addons/auth/base.js
index 4cf1b8312..9f4030804 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;
diff --git a/src/fauxton/app/addons/auth/resources.js b/src/fauxton/app/addons/auth/resources.js
index 64dd2547a..92cff9170 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;
});
diff --git a/src/fauxton/app/addons/auth/templates/nav_link.html b/src/fauxton/app/addons/auth/templates/nav_link.html
index 2f1462fa4..ba0a6f918 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.
<li> <a id="user-logout" href="#"> Logout </a> </li>
<% } else { %>
<li> <a id="user-login" href="#"> Login </a> </li>
- <li> <a id="user-sign-up"> Sign up </a> </li>
+ <!--<li> <a id="user-sign-up"> Sign up </a> </li>-->
<% } %>
</ul>
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 000000000..f1a950642
--- /dev/null
+++ b/src/fauxton/app/addons/auth/templates/noAccess.html
@@ -0,0 +1,19 @@
+<!--
+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.
+-->
+
+<div class="row-fluid" >
+ <div class="span6 offset4">
+ <h3> You do not have permission to view this page </h3>
+</div>
+</div>
diff --git a/src/fauxton/app/addons/config/routes.js b/src/fauxton/app/addons/config/routes.js
index 42934a82c..d86715fb8 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"}
],
diff --git a/src/fauxton/app/addons/exampleAuth/base.js b/src/fauxton/app/addons/exampleAuth/base.js
new file mode 100644
index 000000000..aa9967067
--- /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);
+
+
+
+});
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 000000000..f1a950642
--- /dev/null
+++ b/src/fauxton/app/addons/exampleAuth/templates/noAccess.html
@@ -0,0 +1,19 @@
+<!--
+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.
+-->
+
+<div class="row-fluid" >
+ <div class="span6 offset4">
+ <h3> You do not have permission to view this page </h3>
+</div>
+</div>
diff --git a/src/fauxton/app/addons/logs/routes.js b/src/fauxton/app/addons/logs/routes.js
index 1ce260c0e..ae0a6b66a 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();
},
diff --git a/src/fauxton/app/api.js b/src/fauxton/app/api.js
index 1c0b8f89b..9c4ecef20 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;
}
});
diff --git a/src/fauxton/app/modules/documents/routes.js b/src/fauxton/app/modules/documents/routes.js
index 103d90491..bb8b19383 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",
diff --git a/src/fauxton/app/router.js b/src/fauxton/app/router.js
index 45cc8b2c2..4a79ff144 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"});