Merge pull request #132 from minrk/admin-access

add JupyterHub.admin_access
This commit is contained in:
Min RK
2015-01-31 16:48:32 -08:00
8 changed files with 90 additions and 15 deletions

View File

@@ -8,7 +8,7 @@ import json
from tornado import gen, web from tornado import gen, web
from .. import orm from .. import orm
from ..utils import admin_only, authenticated_403 from ..utils import admin_only
from .base import APIHandler from .base import APIHandler
class BaseUserHandler(APIHandler): class BaseUserHandler(APIHandler):
@@ -159,8 +159,30 @@ class UserServerAPIHandler(BaseUserHandler):
status = 202 if user.stop_pending else 204 status = 202 if user.stop_pending else 204
self.set_status(status) self.set_status(status)
class UserAdminAccessAPIHandler(BaseUserHandler):
"""Grant admins access to single-user servers
This handler sets the necessary cookie for an admin to login to a single-user server.
"""
@admin_only
def post(self, name):
current = self.get_current_user()
self.log.warn("Admin user %s has requested access to %s's server",
current.name, name,
)
if not self.settings.get('admin_access', False):
raise web.HTTPError(403, "admin access to user servers disabled")
user = self.find_user(name)
if user is None:
raise web.HTTPError(404)
if user.server is None:
raise web.HTTPError(400, "%s has no server running" % name)
self.set_server_cookie(user)
default_handlers = [ default_handlers = [
(r"/api/users", UserListAPIHandler), (r"/api/users", UserListAPIHandler),
(r"/api/users/([^/]+)", UserAPIHandler), (r"/api/users/([^/]+)", UserAPIHandler),
(r"/api/users/([^/]+)/server", UserServerAPIHandler), (r"/api/users/([^/]+)/server", UserServerAPIHandler),
(r"/api/users/([^/]+)/admin-access", UserAdminAccessAPIHandler),
] ]

View File

@@ -322,6 +322,12 @@ class JupyterHub(Application):
db = Any() db = Any()
session_factory = Any() session_factory = Any()
admin_access = Bool(False, config=True,
help="""Grant admin users permission to access single-user servers.
Users should be properly informed if this is enabled.
"""
)
admin_users = Set(config=True, admin_users = Set(config=True,
help="""set of usernames of admin users help="""set of usernames of admin users
@@ -717,6 +723,7 @@ class JupyterHub(Application):
proxy=self.proxy, proxy=self.proxy,
hub=self.hub, hub=self.hub,
admin_users=self.admin_users, admin_users=self.admin_users,
admin_access=self.admin_access,
authenticator=self.authenticator, authenticator=self.authenticator,
spawner_class=self.spawner_class, spawner_class=self.spawner_class,
base_url=self.base_url, base_url=self.base_url,

View File

@@ -132,22 +132,30 @@ class BaseHandler(RequestHandler):
self.clear_cookie(user.server.cookie_name, path=user.server.base_url) self.clear_cookie(user.server.cookie_name, path=user.server.base_url)
self.clear_cookie(self.hub.server.cookie_name, path=self.hub.server.base_url) self.clear_cookie(self.hub.server.cookie_name, path=self.hub.server.base_url)
def set_server_cookie(self, user):
"""set the login cookie for the single-user server"""
self.set_secure_cookie(
user.server.cookie_name,
user.cookie_id,
path=user.server.base_url,
)
def set_hub_cookie(self, user):
"""set the login cookie for the Hub"""
self.set_secure_cookie(
self.hub.server.cookie_name,
user.cookie_id,
path=self.hub.server.base_url)
def set_login_cookie(self, user): def set_login_cookie(self, user):
"""Set login cookies for the Hub and single-user server.""" """Set login cookies for the Hub and single-user server."""
# create and set a new cookie token for the single-user server # create and set a new cookie token for the single-user server
if user.server: if user.server:
self.set_secure_cookie( self.set_server_cookie(user)
user.server.cookie_name,
user.cookie_id,
path=user.server.base_url,
)
# create and set a new cookie token for the hub # create and set a new cookie token for the hub
if not self.get_current_user_cookie(): if not self.get_current_user_cookie():
self.set_secure_cookie( self.set_hub_cookie(user)
self.hub.server.cookie_name,
user.cookie_id,
path=self.hub.server.base_url)
@gen.coroutine @gen.coroutine
def authenticate(self, data): def authenticate(self, data):
@@ -278,6 +286,7 @@ class BaseHandler(RequestHandler):
user = self.get_current_user() user = self.get_current_user()
return dict( return dict(
base_url=self.hub.server.base_url, base_url=self.hub.server.base_url,
prefix=self.base_url,
user=user, user=user,
login_url=self.settings['login_url'], login_url=self.settings['login_url'],
logout_url=self.settings['logout_url'], logout_url=self.settings['logout_url'],

View File

@@ -48,6 +48,7 @@ class AdminHandler(BaseHandler):
html = self.render_template('admin.html', html = self.render_template('admin.html',
user=self.get_current_user(), user=self.get_current_user(),
users=self.db.query(orm.User), users=self.db.query(orm.User),
admin_access=self.settings.get('admin_access', False),
) )
self.finish(html) self.finish(html)

View File

@@ -1,10 +1,12 @@
// Copyright (c) Jupyter Development Team. // Copyright (c) Jupyter Development Team.
// Distributed under the terms of the Modified BSD License. // Distributed under the terms of the Modified BSD License.
require(["jquery", "bootstrap", "moment", "jhapi"], function ($, bs, moment, JHAPI) { require(["jquery", "bootstrap", "moment", "jhapi", "utils"], function ($, bs, moment, JHAPI, utils) {
"use strict"; "use strict";
var base_url = window.jhdata.base_url; var base_url = window.jhdata.base_url;
var prefix = window.jhdata.prefix;
var api = new JHAPI(base_url); var api = new JHAPI(base_url);
var get_row = function (element) { var get_row = function (element) {
@@ -32,6 +34,23 @@ require(["jquery", "bootstrap", "moment", "jhapi"], function ($, bs, moment, JHA
}); });
}); });
$(".access-server").click(function () {
var el = $(this);
var row = get_row(el);
var user = row.data('user');
var w = window.open();
api.admin_access(user, {
async: false,
success: function () {
w.location = utils.url_path_join(prefix, 'user', user);
},
error: function (xhr, err) {
w.close();
console.error("Failed to gain access to server", err);
}
});
});
$(".start-server").click(function () { $(".start-server").click(function () {
var el = $(this); var el = $(this);
var row = get_row(el); var row = get_row(el);

View File

@@ -100,6 +100,19 @@ define(['jquery', 'utils'], function ($, utils) {
); );
}; };
JHAPI.prototype.admin_access = function (user, options) {
options = options || {};
options = update(options, {
type: 'POST',
dataType: null,
});
this.api_request(
utils.url_path_join('users', user, 'admin-access'),
options
);
};
JHAPI.prototype.delete_user = function (user, options) { JHAPI.prototype.delete_user = function (user, options) {
options = options || {}; options = options || {};
options = update(options, {type: 'DELETE', dataType: null}); options = update(options, {type: 'DELETE', dataType: null});

View File

@@ -15,10 +15,13 @@
<tr class="user-row" data-user="{{u.name}}" data-admin="{{u.admin}}"> <tr class="user-row" data-user="{{u.name}}" data-admin="{{u.admin}}">
<td class="name-col col-sm-2">{{u.name}}</td> <td class="name-col col-sm-2">{{u.name}}</td>
<td class="admin-col col-sm-2">{% if u.admin %}admin{% endif %}</td> <td class="admin-col col-sm-2">{% if u.admin %}admin{% endif %}</td>
<td class="time-col col-sm-4">{{u.last_activity.isoformat() + 'Z'}}</td> <td class="time-col col-sm-3">{{u.last_activity.isoformat() + 'Z'}}</td>
<td class="server-col col-sm-2 text-center"> <td class="server-col col-sm-3 text-center">
{% if u.server %} {% if u.server %}
<span class="stop-server btn btn-xs btn-danger">stop server</span> <span class="stop-server btn btn-xs btn-danger">stop server</span>
{% if admin_access %}
<span class="access-server btn btn-xs btn-success">access server</span>
{% endif %}
{% else %} {% else %}
<span class="start-server btn btn-xs btn-success">start server</span> <span class="start-server btn btn-xs btn-success">start server</span>
{% endif %} {% endif %}

View File

@@ -57,6 +57,7 @@
<script type="text/javascript"> <script type="text/javascript">
window.jhdata = { window.jhdata = {
base_url: "{{base_url}}", base_url: "{{base_url}}",
prefix: "{{prefix}}",
{% if user %} {% if user %}
user: "{{user.name}}", user: "{{user.name}}",
{% endif %} {% endif %}