emacs-elpa-diffs
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

[elpa] externals/ement 03d411a177: Add: SSO support


From: ELPA Syncer
Subject: [elpa] externals/ement 03d411a177: Add: SSO support
Date: Tue, 30 May 2023 03:57:48 -0400 (EDT)

branch: externals/ement
commit 03d411a1778179dd1b68b404059e84508a2c1fd8
Author: Adam Porter <adam@alphapapa.net>
Commit: Adam Porter <adam@alphapapa.net>

    Add: SSO support
    
    Closes <https://github.com/alphapapa/ement.el/issues/24>.
    
    Co-developed-by: Jeffrey Stoffers <jstoffers@uberpurple.com>
---
 README.org |   3 ++
 ement.el   | 164 +++++++++++++++++++++++++++++++++++++++----------------------
 2 files changed, 107 insertions(+), 60 deletions(-)

diff --git a/README.org b/README.org
index 6397dbcdc6..1570f19c08 100644
--- a/README.org
+++ b/README.org
@@ -298,6 +298,9 @@ Note that, while ~matrix-client~ remains usable, and 
probably will for some time
 
 ** 0.10-pre
 
+*Additions*
++ Support for Single Sign-On (SSO) authentication.  
([[https://github.com/alphapapa/ement.el/issues/24][#24]].  Thanks to 
[[https://github.com/Necronian][Jeffrey Stoffers]] for development, and to 
[[https://github.com/phil-s][Phil Sainty]], [[https://github.com/FrostyX][Jakub 
Kadlčík]], and [[https://github.com/oneingan][Juanjo Presa]] for testing.)
+
 *Changes*
 
 + Activating a space in the room list uses ~ement-view-space~ (which shows a 
directory of rooms in the space) instead of ~ement-view-room~ (which shows 
events in the space, which is generally not useful).
diff --git a/ement.el b/ement.el
index aed01dfd13..10b958021b 100644
--- a/ement.el
+++ b/ement.el
@@ -184,6 +184,11 @@ handled (e.g. how to be notified)."
   :type 'hook
   :options '(ement-interrupted-sync-message ement-interrupted-sync-warning))
 
+(defcustom ement-sso-server-port 4567
+  "TCP port used for local HTTP server for SSO logins.
+It shouldn't usually be necessary to change this."
+  :type 'integer)
+
 ;;;; Commands
 
 ;;;###autoload
@@ -205,8 +210,7 @@ the port, e.g.
   \"http://localhost:8080\"";
   (interactive (if current-prefix-arg
                    ;; Force new session.
-                   (list :user-id (read-string "User ID: ")
-                         :password (read-passwd "Password: "))
+                   (list :user-id (read-string "User ID: "))
                  ;; Use known session.
                  (unless ement-sessions
                    ;; Read sessions from disk.
@@ -215,66 +219,106 @@ the port, e.g.
                      (error (display-warning 'ement (format "Unable to read 
session data from disk (%s).  Prompting to log in again."
                                                             
(error-message-string err))))))
                  (cl-case (length ement-sessions)
-                   (0 (list :user-id (read-string "User ID: ")
-                            :password (read-passwd "Password: ")))
+                   (0 (list :user-id (read-string "User ID: ")))
                    (1 (list :session (cdar ement-sessions)))
                    (otherwise (list :session (ement-complete-session))))))
-  (cl-labels ((new-session
-               () (unless (string-match (rx bos "@" (group (1+ (not (any 
":")))) ; Username
-                                            ":" (group (optional (1+ (not (any 
blank)))))) ; Server name
-                                        user-id)
-                    (user-error "Invalid user ID format: use 
@USERNAME:SERVER"))
-               (let* ((username (match-string 1 user-id))
-                      (server-name (match-string 2 user-id))
-                      (uri-prefix (or uri-prefix (ement--hostname-uri 
server-name)))
-                      (user (make-ement-user :id user-id :username username))
-                      (server (make-ement-server :name server-name :uri-prefix 
uri-prefix))
-                      (transaction-id (ement--initial-transaction-id))
-                      (initial-device-display-name (format "Ement.el: %s@%s"
-                                                           ;; Just to be extra 
careful:
-                                                           (or user-login-name 
"[unknown user-login-name]")
-                                                           (or (system-name) 
"[unknown system-name]")))
-                      (device-id (secure-hash 'sha256 
initial-device-display-name)))
-                 (make-ement-session :user user :server server :transaction-id 
transaction-id
-                                     :device-id device-id 
:initial-device-display-name initial-device-display-name
-                                     :events (make-hash-table :test #'equal))))
-              (password-login
-               () (pcase-let* (((cl-struct ement-session user device-id 
initial-device-display-name) session)
-                               ((cl-struct ement-user id) user)
-                               (data (ement-alist "type" "m.login.password"
-                                                  "identifier"
-                                                  (ement-alist "type" 
"m.id.user"
-                                                               "user" id)
-                                                  "password" password
-                                                  "device_id" device-id
-                                                  
"initial_device_display_name" initial-device-display-name)))
-                    ;; TODO: Clear password in callback (if we decide to hold 
on to it for retrying login timeouts).
-                    (ement-api session "login" :method 'post :data 
(json-encode data)
-                      :then (apply-partially #'ement--login-callback 
session))))
-              (flows-callback
-               (data) (if (cl-loop for flow across (map-elt data 'flows)
-                                   thereis (equal (map-elt flow 'type) 
"m.login.password"))
-                          (progn
-                            (message "Ement: Logging in with password...")
-                            (password-login))
-                        (error "Matrix server doesn't support m.login.password 
login flow.  Supported flows: %s"
-                               (cl-loop for flow in (map-elt data 'flows)
-                                        collect (map-elt flow 'type))))))
-    (if session
-        ;; Start syncing given session.
-        (let ((user-id (ement-user-id (ement-session-user session))))
-          ;; HACK: If session is already in ement-sessions, this replaces it.  
I think that's okay...
-          (setf (alist-get user-id ement-sessions nil nil #'equal) session)
-          (ement--sync session :timeout ement-initial-sync-timeout))
-      ;; Start password login flow.  Prompt for user ID and password
-      ;; if not given (i.e. if not called interactively.)
-      (unless user-id
-        (setf user-id (read-string "User ID: ")))
-      (unless password
-        (setf password (read-passwd (format "Password for %s: " user-id))))
-      (setf session (new-session))
-      (when (ement-api session "login" :then #'flows-callback)
-        (message "Ement: Checking server's login flows...")))))
+  (let (sso-server-process)
+    (cl-labels ((new-session
+                 () (unless (string-match (rx bos "@" (group (1+ (not (any 
":")))) ; Username
+                                              ":" (group (optional (1+ (not 
(any blank)))))) ; Server name
+                                          user-id)
+                      (user-error "Invalid user ID format: use 
@USERNAME:SERVER"))
+                 (let* ((username (match-string 1 user-id))
+                        (server-name (match-string 2 user-id))
+                        (uri-prefix (or uri-prefix (ement--hostname-uri 
server-name)))
+                        (user (make-ement-user :id user-id :username username))
+                        (server (make-ement-server :name server-name 
:uri-prefix uri-prefix))
+                        (transaction-id (ement--initial-transaction-id))
+                        (initial-device-display-name (format "Ement.el: %s@%s"
+                                                             ;; Just to be 
extra careful:
+                                                             (or 
user-login-name "[unknown user-login-name]")
+                                                             (or (system-name) 
"[unknown system-name]")))
+                        (device-id (secure-hash 'sha256 
initial-device-display-name)))
+                   (make-ement-session :user user :server server 
:transaction-id transaction-id
+                                       :device-id device-id 
:initial-device-display-name initial-device-display-name
+                                       :events (make-hash-table :test 
#'equal))))
+                (password-login
+                 () (pcase-let* (((cl-struct ement-session user device-id 
initial-device-display-name) session)
+                                 ((cl-struct ement-user id) user)
+                                 (data (ement-alist "type" "m.login.password"
+                                                    "identifier"
+                                                    (ement-alist "type" 
"m.id.user"
+                                                                 "user" id)
+                                                    "password" (or password
+                                                                   
(read-passwd (format "Password for %s: " id)))
+                                                    "device_id" device-id
+                                                    
"initial_device_display_name" initial-device-display-name)))
+                      ;; TODO: Clear password in callback (if we decide to 
hold on to it for retrying login timeouts).
+                      (ement-api session "login" :method 'post :data 
(json-encode data)
+                        :then (apply-partially #'ement--login-callback 
session))
+                      (ement-message "Logging in with password...")))
+                (sso-filter
+                 (process string)
+                 ;; NOTE: This is technically wrong, because it's not 
guaranteed that the
+                 ;; string will be a complete request--it could just be a 
chunk.  But in
+                 ;; practice, if this works, it's much simpler than setting up 
process log
+                 ;; functions and per-client buffers for this throwaway, 
pretend HTTP server.
+                 (when (string-match (rx "GET /?loginToken=" (group (0+ nonl)) 
" " (0+ nonl)) string)
+                   (unwind-protect
+                       (pcase-let* ((token (match-string 1 string))
+                                    ((cl-struct ement-session user device-id 
initial-device-display-name)
+                                     session)
+                                    ((cl-struct ement-user id) user)
+                                    (data (ement-alist
+                                           "type" "m.login.token"
+                                           "identifier" (ement-alist "type" 
"m.id.user"
+                                                                     "user" id)
+                                           "token" token
+                                           "device_id" device-id
+                                           "initial_device_display_name" 
initial-device-display-name)))
+                         (ement-api session "login" :method 'post
+                           :data (json-encode data)
+                           :then (apply-partially #'ement--login-callback 
session)))
+                     (delete-process sso-server-process)
+                     (delete-process process))))
+                (sso-login ()
+                           (setf sso-server-process
+                                 (make-network-process
+                                  :name "ement-sso" :family 'ipv4 :host 'local 
:service ement-sso-server-port
+                                  :filter #'sso-filter :server t :noquery t))
+                           (browse-url
+                            (concat (ement-server-uri-prefix 
(ement-session-server session))
+                                    
"/_matrix/client/r0/login/sso/redirect?redirectUrl=http://localhost:";
+                                    (number-to-string ement-sso-server-port))))
+                (flows-callback
+                 (data) (let ((flows (cl-loop for flow across (map-elt data 
'flows)
+                                              collect (map-elt flow 'type))))
+                          (pcase (length flows)
+                            (1 (pcase (car flows)
+                                 ("m.login.password" (password-login))
+                                 ("m.login.sso" (sso-login))
+                                 (_ (error "Ement: Unsupported login flow: %s  
Server:%s"
+                                           (car flows) 
(ement-server-uri-prefix (ement-session-server session))))))
+                            (_ (pcase (completing-read "Select authentication 
method: "
+                                                       (cl-loop for flow in 
flows
+                                                                collect 
(string-trim-left flow (rx "m.login."))))
+                                 ("password" (password-login))
+                                 ("sso" (sso-login))
+                                 (else (error "Ement: Unsupported login 
flow:%S  Server:%S  Supported flows:%S"
+                                              else (ement-server-uri-prefix 
(ement-session-server session)) flows))))))))
+      (if session
+          ;; Start syncing given session.
+          (let ((user-id (ement-user-id (ement-session-user session))))
+            ;; HACK: If session is already in ement-sessions, this replaces 
it.  I think that's okay...
+            (setf (alist-get user-id ement-sessions nil nil #'equal) session)
+            (ement--sync session :timeout ement-initial-sync-timeout))
+        ;; Start password login flow.  Prompt for user ID and password
+        ;; if not given (i.e. if not called interactively.)
+        (unless user-id
+          (setf user-id (read-string "User ID: ")))
+        (setf session (new-session))
+        (when (ement-api session "login" :then #'flows-callback)
+          (message "Ement: Checking server's login flows..."))))))
 
 (defun ement-disconnect (sessions)
   "Disconnect from SESSIONS.



reply via email to

[Prev in Thread] Current Thread [Next in Thread]