mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-11 03:52:59 +00:00
add JupyterHub.admin_access
optionally allow admin users to login to user servers by visiting a special admin-only URL that sets the relevant cookie - disabled by default - an 'access server' button is added to the admin panel, which sets the necessary cookie to log in to the user server
This commit is contained in:
@@ -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):
|
||||||
@@ -147,8 +147,30 @@ class UserServerAPIHandler(BaseUserHandler):
|
|||||||
yield self.stop_single_user(user)
|
yield self.stop_single_user(user)
|
||||||
self.set_status(204)
|
self.set_status(204)
|
||||||
|
|
||||||
|
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),
|
||||||
]
|
]
|
||||||
|
@@ -321,6 +321,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
|
||||||
|
|
||||||
@@ -708,6 +714,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,
|
||||||
|
@@ -244,6 +244,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'],
|
||||||
|
@@ -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)
|
||||||
|
|
||||||
|
@@ -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) {
|
||||||
@@ -31,7 +33,24 @@ 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);
|
||||||
|
@@ -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});
|
||||||
|
@@ -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 %}
|
||||||
|
@@ -54,6 +54,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 %}
|
||||||
|
Reference in New Issue
Block a user