2019-11-30 08:46:49 +01:00
;;; ein-jupyterhub.el --- Interface to Jupyterhub -*- lexical-binding: t -*-
;; Copyright (C) 2016 - John Miller
;; Authors: Takafumi Arakaki <aka.tkf at gmail.com>
;; John M. Miller <millejoh at mac.com>
;; This file is NOT part of GNU Emacs.
;; ein-jupyterhub.el is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; ein-jupyterhub.el is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with ein-jupyter.el. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
2020-02-03 19:45:34 +01:00
;; Deprecated.
2019-11-30 08:46:49 +01:00
;;;
;;; An interface to the Jupyterhub login and management API as described in
;;; http://jupyterhub.readthedocs.io/en/latest/api/index.html
;;;
;;
;;; Code:
( require 'ein-query )
( require 'ein-websocket )
( require 'ein-notebooklist )
2020-02-03 19:45:34 +01:00
( require 'anaphora )
2019-11-30 08:46:49 +01:00
( defvar *ein:jupyterhub-connections* ( make-hash-table :test #' equal ) )
2020-02-03 19:45:34 +01:00
( defstruct ein:$jh-conn
2019-11-30 08:46:49 +01:00
" Data representing a connection to a jupyterhub server. "
url-or-port
version
user
token )
2020-02-03 19:45:34 +01:00
( defstruct ein:$jh-user
2019-11-30 08:46:49 +01:00
" A jupyterhub user, per https://jupyterhub.readthedocs.io/en/latest/_static/rest-api/index.html#/definitions/User "
name
admin
groups
server
pending
last-activity )
( defsubst ein:jupyterhub-user-path ( url-or-port &rest paths )
" Goes from URL-OR-PORT/PATHS to URL-OR-PORT/user/someone/PATHS "
2020-02-03 19:45:34 +01:00
( let ( ( user-base ( aif ( gethash url-or-port *ein:jupyterhub-connections* )
2019-11-30 08:46:49 +01:00
( ein:$jh-user-server ( ein:$jh-conn-user it ) ) ) ) )
( apply #' ein:url url-or-port user-base paths ) ) )
( defsubst ein:jupyterhub-api-path ( url-or-port &rest paths )
( apply #' ein:url url-or-port " hub/api " paths ) )
( defun ein:jupyterhub--store-cookies ( conn )
" Websockets use the url-cookie API "
( let* ( ( url-or-port ( ein:$jh-conn-url-or-port conn ) )
( parsed-url ( url-generic-parse-url url-or-port ) )
( host-port ( if ( url-port-if-non-default parsed-url )
( format " %s:%s " ( url-host parsed-url ) ( url-port parsed-url ) )
( url-host parsed-url ) ) )
( securep ( string= ( url-type parsed-url ) " https " ) )
( cookies ( append
( request-cookie-alist ( url-host parsed-url ) " /hub/ " securep )
( ein:aand ( ein:$jh-conn-user conn ) ( ein:$jh-user-server it )
( request-cookie-alist ( url-host parsed-url ) it securep ) ) ) ) )
( dolist ( c cookies )
( ein:websocket-store-cookie c host-port
( car ( url-path-and-query parsed-url ) ) securep ) ) ) )
( cl-defun ein:jupyterhub--login-complete ( dobj conn &key response &allow-other-keys )
( deferred:callback-post dobj ( list conn response ) ) )
( defmacro ein:jupyterhub--add-header ( header )
` ( setq my-settings
( plist-put my-settings :headers
( append ( plist-get my-settings :headers ) ( list , header ) ) ) ) )
( defmacro ein:jupyterhub-query ( conn-key url cb cbargs &rest settings )
` ( let ( ( my-settings ( list ,@ settings ) ) )
( ein:and-let* ( ( conn ( gethash , conn-key *ein:jupyterhub-connections* ) ) )
( ein:jupyterhub--add-header
( cons " Referer " ( ein:url ( ein:$jh-conn-url-or-port conn ) " hub/login " ) ) )
2020-02-03 19:45:34 +01:00
( aif ( ein:$jh-conn-token conn )
2019-11-30 08:46:49 +01:00
( ein:jupyterhub--add-header
( cons " Authorization " ( format " token %s " it ) ) ) ) )
( apply #' ein:query-singleton-ajax
2020-02-03 19:45:34 +01:00
, url
2019-11-30 08:46:49 +01:00
:error
( lambda ( &rest args )
( ein:log 'error " ein:jupyterhub-query--error (%s) %s (%s) " , url
( request-response-status-code ( plist-get args :response ) )
( plist-get args :symbol-status ) ) )
:complete
( lambda ( &rest args )
( ein:log 'debug " ein:jupyterhub-query--complete (%s) %s (%s) " , url
( request-response-status-code ( plist-get args :response ) )
( plist-get args :symbol-status ) ) )
:success
( lambda ( &rest args )
( apply , cb ( request-response-data ( plist-get args :response ) ) , cbargs ) )
my-settings ) ) )
( defsubst ein:jupyterhub--query-login ( callback username password conn )
( ein:jupyterhub-query
( ein:$jh-conn-url-or-port conn )
( ein:url ( ein:$jh-conn-url-or-port conn ) " hub/login " )
#' ein:jupyterhub--receive-login
` ( , callback , username , password , conn )
;; :type "POST" ;; no type here else redirect will use POST
:parser #' ignore
:data ` ( ( " username " . , username )
( " password " . , password ) ) ) )
( defun ein:jupyterhub--receive-version ( data url-or-port callback username password )
( let ( ( conn ( make-ein:$jh-conn
:url-or-port url-or-port
:version ( plist-get data :version ) ) ) )
( setf ( gethash url-or-port *ein:jupyterhub-connections* ) conn )
( ein:jupyterhub--query-login callback username password conn ) ) )
( defun ein:jupyterhub--receive-user ( data callback username password conn iteration )
( let ( ( user ( make-ein:$jh-user :name ( plist-get data :name )
:admin ( plist-get data :admin )
:groups ( plist-get data :groups )
:server ( plist-get data :server )
:pending ( plist-get data :pending )
:last-activity ( plist-get data :last_activity ) ) ) )
( setf ( ein:$jh-conn-user conn ) user )
( ein:jupyterhub--store-cookies conn )
( if ( not ( ein:$jh-user-server user ) )
( if ( <= iteration 0 )
( ein:jupyterhub--query-token callback username password conn )
( ein:display-warning " jupyterhub cannot start single-user server " :error ) )
( ein:notebooklist-open*
( ein:jupyterhub-user-path ( ein:$jh-conn-url-or-port conn ) )
nil nil callback ) ) ) )
( defsubst ein:jupyterhub--query-user ( callback username password conn iteration )
( ein:jupyterhub-query
( ein:$jh-conn-url-or-port conn )
( ein:jupyterhub-api-path ( ein:$jh-conn-url-or-port conn ) " users " username )
#' ein:jupyterhub--receive-user
` ( , callback , username , password , conn , iteration )
:type " GET "
:parser #' ein:json-read ) )
( defun ein:jupyterhub--receive-login ( _data callback username password conn )
( ein:jupyterhub--store-cookies conn )
( ein:jupyterhub--query-user callback username password conn 0 ) )
( defsubst ein:jupyterhub--query-server ( callback username password conn )
( ein:jupyterhub-query
( ein:$jh-conn-url-or-port conn )
( ein:jupyterhub-api-path ( ein:$jh-conn-url-or-port conn )
" users " username " server " )
#' ein:jupyterhub--receive-server
` ( , callback , username , password , conn )
:type " POST "
:parser #' ein:json-read ) )
( defun ein:jupyterhub--receive-token ( data callback username password conn )
( setf ( ein:$jh-conn-token conn ) ( plist-get data :token ) )
( ein:jupyterhub--query-server callback username password conn ) )
2020-02-03 19:45:34 +01:00
( defun ein:jupyterhub--receive-server ( _data callback username password conn )
( ein:jupyterhub--query-user callback username password conn 1 ) )
2019-11-30 08:46:49 +01:00
( defun ein:jupyterhub--query-token ( callback username password conn )
( ein:jupyterhub-query
( ein:$jh-conn-url-or-port conn )
( ein:jupyterhub-api-path ( ein:$jh-conn-url-or-port conn )
" authorizations/token " )
#' ein:jupyterhub--receive-token
` ( , callback , username , password , conn )
:type " POST "
:data ( json-encode ` ( ( :username . , username )
( :password . , password ) ) )
:parser #' ein:json-read ) )
( defsubst ein:jupyterhub--query-version ( url-or-port callback username password )
( ein:jupyterhub-query
url-or-port
( ein:jupyterhub-api-path url-or-port )
#' ein:jupyterhub--receive-version
` ( , url-or-port , callback , username , password )
:type " GET "
:parser #' ein:json-read ) )
;;;###autoload
( defun ein:jupyterhub-connect ( url-or-port username password callback )
" Log on to a jupyterhub server using PAM authentication. Requires jupyterhub version 0.8 or greater. CALLBACK takes two arguments, the resulting buffer and the singleuser url-or-port "
( interactive ( let ( ( url-or-port ( ein:notebooklist-ask-url-or-port ) )
( pam-plist ( ein:notebooklist-ask-user-pw-pair " User " " Password " ) ) )
( cl-loop for ( user pw ) on pam-plist by ( function cddr )
2020-02-03 19:45:34 +01:00
return ( list url-or-port ( symbol-name user ) pw ( lambda ( buffer _url-or-port ) ( pop-to-buffer buffer ) ) ) ) ) )
2019-11-30 08:46:49 +01:00
( ein:jupyterhub--query-version url-or-port callback username password ) )
( provide 'ein-jupyterhub )