diff options
| author | martianh <martianh@noreply.codeberg.org> | 2022-03-23 08:04:07 +0100 | 
|---|---|---|
| committer | martianh <martianh@noreply.codeberg.org> | 2022-03-23 08:04:07 +0100 | 
| commit | cecd5de060a56f13f7f7eb4528b341027812faab (patch) | |
| tree | b612438cd9e5b46c0300c2ae1dc1b007698226bb | |
| parent | c7b475160d2e7712e339e15adf168529f71b52c6 (diff) | |
| parent | 56fa25df379623e79261b535cd724db3ed979d44 (diff) | |
Merge pull request '2FA login support' (#255) from Red_Starfish/mastodon-up.el:login into develop
Reviewed-on: https://codeberg.org/martianh/mastodon.el/pulls/255
| -rw-r--r-- | README.org | 32 | ||||
| -rw-r--r-- | fixture/client.plstore | 5 | ||||
| -rw-r--r-- | lisp/mastodon-auth.el | 201 | ||||
| -rw-r--r-- | lisp/mastodon-client.el | 110 | ||||
| -rw-r--r-- | lisp/mastodon-http.el | 8 | ||||
| -rw-r--r-- | lisp/mastodon.el | 30 | ||||
| -rw-r--r-- | test/ert-helper.el | 1 | ||||
| -rw-r--r-- | test/mastodon-auth-tests.el | 52 | ||||
| -rw-r--r-- | test/mastodon-client-tests.el | 101 | 
9 files changed, 392 insertions, 148 deletions
| @@ -94,10 +94,6 @@ This repo also incorporates fixes for two bugs that were never merged into the u  - https://github.com/jdenen/mastodon.el/issues/227 (and https://github.com/jdenen/mastodon.el/issues/234)  - https://github.com/jdenen/mastodon.el/issues/228 -** 2FA - -It looks like 2-factor auth was never completed in the original repo. It's not a priority for me, auth ain't my thing. If you want to hack on it, its on the develop branch in the original repo. -  ** contributing  Contributions are welcome. Feel free to open an issue or get in touch with me on mastodon: @@ -166,21 +162,31 @@ Or, with =use-package=:  #+END_SRC  ** Usage -*** 2 Factor Auth -2FA is not supported yet. It is in the [[https://github.com/jdenen/mastodon.el/milestone/2][plans]] for the =1.0.0= release. - -If you have 2FA enabled and try to use mastodon.el, your Emacs client will hang until you `C-g` your way out.  *** Instance -Set =mastodon-instance-url= in your =.emacs= or =customize=. Defaults to the [[https://mastodon.social][flagship]]. +You need to set 2 variables in your init file to get started: + +1. mastodon-instance-url +2. mastodon-active-user + +(see their doc strings for details). For example If you want to post +toots as "example_user@social.instance.org", then put this in your init +file:  #+BEGIN_SRC emacs-lisp -    (setq mastodon-instance-url "https://my.instance.url") +    (setq mastodon-instance-url "https://social.instance.org" +          mastodon-active-user "example_user")  #+END_SRC -There is an option to have your user credentials (email address and password) saved to disk so you don't have to re-enter them on every restart. -The default is not to do this because if not properly configured it would save these unencrypted which is not a good default to have. -Customize the variable =mastodon-auth-source-file= if you want to enable this feature. +Then *restart* Emacs and run =M-x mastodon=. Make sure you are connected +to internet before you do this. If you have multiple mastodon accounts +you can activate one at a time by changing those two variables and +restarting Emacs. + +If you have been using mastodon.el before this change and the above +steps do not work it's advisable that you delete the old file specified +by =mastodon-client--token-file= and restart Emacs and follow the steps +again.  *** Timelines diff --git a/fixture/client.plstore b/fixture/client.plstore index e050018..48d951c 100644 --- a/fixture/client.plstore +++ b/fixture/client.plstore @@ -1,3 +1,6 @@  ;;; public entries -*- mode: plstore -*-  (("mastodon-http://other.example" :client_id "id1" :client_secret "secret1") - ("mastodon-http://mastodon.example" :client_id "id2" :client_secret "secret2")) + ("mastodon-http://mastodon.example" :client_id "id2" :client_secret "secret2") + ("user-test8000@mastodon.example" :username "test8000@mastodon.example" :instance "http://mastodon.example" :client_id "id2" :client_secret "secret2" :access_token "token2") + ("active-user" :username "test9000@other.example" :instance "http://other.example" :client_id "id1" :client_secret "secret1" :access_token "token1") + ("user-test9000@other.example" :username "test9000@other.example" :instance "http://other.example" :client_id "id1" :client_secret "secret1" :access_token "token1")) diff --git a/lisp/mastodon-auth.el b/lisp/mastodon-auth.el index e582e4f..582f7bb 100644 --- a/lisp/mastodon-auth.el +++ b/lisp/mastodon-auth.el @@ -1,6 +1,7 @@  ;;; mastodon-auth.el --- Auth functions for mastodon.el  -*- lexical-binding: t -*-  ;; Copyright (C) 2017-2019 Johnson Denen +;; Copyright (C) 2021 Abhiseck Paira <abhiseckpaira@disroot.org>  ;; Author: Johnson Denen <johnson.denen@gmail.com>  ;; Maintainer: Marty Hiatt <martianhiatus@riseup.net>  ;; Version: 0.10.0 @@ -39,21 +40,25 @@  (autoload 'mastodon-http--api "mastodon-http")  (autoload 'mastodon-http--get-json "mastodon-http")  (autoload 'mastodon-http--post "mastodon-http") +(autoload 'mastodon-http--append-query-string "mastodon-http") +(autoload 'mastodon-client--store-access-token "mastodon-client") +(autoload 'mastodon-client--active-user "mastodon-client") +(autoload 'mastodon-client--make-user-active "mastodon-client") +(autoload 'mastodon-client--form-user-from-vars "mastodon-client")  (defvar mastodon-instance-url) +(defvar mastodon-client-scopes) +(defvar mastodon-client-redirect-uri) +(defvar mastodon-active-user)  (defgroup mastodon-auth nil    "Authenticate with Mastodon."    :prefix "mastodon-auth-"    :group 'mastodon) -(defcustom mastodon-auth-source-file "" -  "Filename to use to store user names and passwords. - -Leave empty to not permanently store any secrets. -Otherwise set to e.g. \"~/.authinfo.gpg\" to have encrypted storage, or -if you are happy with unencryped storage use e.g. \"~/authinfo\"." -  :group 'mastodon-auth -  :type 'string) +(defvar mastodon-auth-source-file nil +  "This variable is obsolete. +This variable currently serves no purpose and will be removed in +the future.")  (defvar mastodon-auth--token-alist nil    "Alist of User access tokens keyed by instance url.") @@ -61,60 +66,95 @@ if you are happy with unencryped storage use e.g. \"~/authinfo\"."  (defvar mastodon-auth--acct-alist nil    "Alist of account accts (name@domain) keyed by instance url.") +(defvar mastodon-auth--user-unaware +  "          ** MASTODON.EL - NOTICE ** + +It appears that you are not aware of the recent developments in +mastodon.el.  In short we now require that you also set the +variable `mastodon-active-user' in your init file in addition to +`mastodon-instance-url'. + +Please see its documentation to understand what value it accepts +by running M-x describe-variable on it or visiting our web page: +https://codeberg.org/martianh/mastodon.el + +We apologize for the inconvenience. +") + +(defun mastodon-auth--get-browser-login-url () +  "Return properly formed browser login url." +  (mastodon-http--append-query-string +   (concat mastodon-instance-url "/oauth/authorize/") +   `(("response_type" "code") +     ("redirect_uri" ,mastodon-client-redirect-uri) +     ("scope" ,mastodon-client-scopes) +     ("client_id" ,(plist-get (mastodon-client) :client_id))))) + +(defvar mastodon-auth--explanation +  (format +   " +1. A URL has been copied to your clipboard.  Open this URL in a +javascript capable browser and your browser will take you to your +Mastodon instance's login page. + +2. Login to your account (%s) and authorize \"mastodon.el\". + +3. After authorization you will be presented an authorization +code. Copy this code and paste it in the minibuffer prompt." +   (mastodon-client--form-user-from-vars))) + +(defun mastodon-auth--show-notice (notice buffer-name &optional ask) +  "Display NOTICE to user. +NOTICE is displayed in vertical split occupying 50% of total +width.  The buffer name of the buffer being displayed in the +window is BUFFER-NAME. + +When optional argument ASK is given which should be a string, use +ASK as the minibuffer prompt.  Return whatever user types in +response to the prompt. + +When ASK is absent return nil." +  (let ((buffer (get-buffer-create buffer-name)) +        (inhibit-read-only t) +        ask-value window) +    (set-buffer buffer) +    (erase-buffer) +    (insert notice) +    (fill-region (point-min) (point-max)) +    (read-only-mode) +    (setq window (select-window +                  (split-window (frame-root-window) nil 'left) +                  t)) +    (switch-to-buffer buffer t) +    (when ask +      (setq ask-value (read-string ask)) +      (kill-buffer buffer) +      (delete-window window)) +    ask-value)) + +(defun mastodon-auth--request-authorization-code () +  "Ask authorization code and return it." +  (let ((url (mastodon-auth--get-browser-login-url)) +        authorization-code) +    (kill-new url) +    (setq authorization-code +          (mastodon-auth--show-notice mastodon-auth--explanation +                                      "*mastodon-notice*" +                                      "Authorization Code: ")) +    authorization-code)) +  (defun mastodon-auth--generate-token () -  "Make POST to generate auth token. - -If no auth-sources file, runs -`mastodon-auth--generate-token-no-storing-credentials'. If -auth-sources file exists, runs -`mastodon-auth--generate-token-and-store'." -  (if (or (null mastodon-auth-source-file) -	  (string= "" mastodon-auth-source-file)) -      (mastodon-auth--generate-token-no-storing-credentials) -    (mastodon-auth--generate-token-and-store))) - -(defun mastodon-auth--generate-token-no-storing-credentials () -  "Make POST to generate auth token, without using auth-sources file." -  (mastodon-http--post -   (concat mastodon-instance-url "/oauth/token") -   `(("client_id" . ,(plist-get (mastodon-client) :client_id)) -     ("client_secret" . ,(plist-get (mastodon-client) :client_secret)) -     ("grant_type" . "password") -     ("username" . ,(read-string "Email: " user-mail-address)) -     ("password" . ,(read-passwd "Password: ")) -     ("scope" . "read write follow")) -   nil -   :unauthenticated)) - -(defun mastodon-auth--generate-token-and-store () -  "Make POST to generate auth token. - -Reads and/or stores secrets in `MASTODON-AUTH-SOURCE-FILE'." -  (let* ((auth-sources (list mastodon-auth-source-file)) -	 (auth-source-creation-prompts -          '((user . "Enter email for %h: ") -            (secret . "Password: "))) -         (credentials-plist (nth 0 (auth-source-search -                                    :create t -                                    :host mastodon-instance-url -                                    :port 443 -                                    :require '(:user :secret))))) -    (prog1 -        (mastodon-http--post -         (concat mastodon-instance-url "/oauth/token") -         `(("client_id" . ,(plist-get (mastodon-client) :client_id)) -           ("client_secret" . ,(plist-get (mastodon-client) :client_secret)) -           ("grant_type" . "password") -           ("username" . ,(plist-get credentials-plist :user)) -           ("password" . ,(let ((secret (plist-get credentials-plist :secret))) -                            (if (functionp secret) -                                (funcall secret) -                              secret))) -           ("scope" . "read write follow")) -         nil -	 :unauthenticated) -      (when (functionp (plist-get credentials-plist :save-function)) -        (funcall (plist-get credentials-plist :save-function)))))) +  "Generate access_token for the user.  Return response buffer." +  (let ((authorization-code (mastodon-auth--request-authorization-code))) +    (mastodon-http--post +     (concat mastodon-instance-url "/oauth/token") +     `(("grant_type" . "authorization_code") +       ("client_secret" . ,(plist-get (mastodon-client) :client_secret)) +       ("client_id" . ,(plist-get (mastodon-client) :client_id)) +       ("code" . ,authorization-code) +       ("redirect_uri" . ,mastodon-client-redirect-uri)) +     nil +     :unauthenticated)))  (defun mastodon-auth--get-token ()    "Make a request to generate an auth token and return JSON response." @@ -128,16 +168,33 @@ Reads and/or stores secrets in `MASTODON-AUTH-SOURCE-FILE'."        (json-read-from-string json-string))))  (defun mastodon-auth--access-token () -  "Return exiting or generate new access token. - -If an access token for `mastodon-instance-url' is in -`mastodon-auth--token-alist', return it. - -Otherwise, generate a token and pass it to -`mastodon-auth--handle-token-reponse'." -  (if-let ((token (cdr (assoc mastodon-instance-url mastodon-auth--token-alist)))) -      token -    (mastodon-auth--handle-token-response (mastodon-auth--get-token)))) +  "Return the access token to use with `mastodon-instance-url'. + +Generate/save token if none known yet." +  (cond (mastodon-auth--token-alist +         ;; user variables are known and +         ;; initialised already. +         (alist-get mastodon-instance-url mastodon-auth--token-alist +                    nil nil 'equal)) +        ((plist-get (mastodon-client--active-user) :access_token) +         ;; user variables needs to initialised by reading from +         ;; plstore. +         (push (cons mastodon-instance-url +                     (plist-get (mastodon-client--active-user) :access_token)) +               mastodon-auth--token-alist) +         (alist-get mastodon-instance-url mastodon-auth--token-alist +                    nil nil 'equal)) +        ((null mastodon-active-user) +         ;; user not aware of 2FA related changes and has not set the +         ;; `mastodon-active-user' properly. Make user aware and error +         ;; out. +         (mastodon-auth--show-notice mastodon-auth--user-unaware +                                     "*mastodon-notice*") +         (error "Variables not set properly")) +        (t +         ;; user access-token needs to fetched from the server and +         ;; stored and variables initialised. +         (mastodon-auth--handle-token-response (mastodon-auth--get-token)))))  (defun mastodon-auth--handle-token-response (response)    "Add token RESPONSE to `mastodon-auth--token-alist'. @@ -148,6 +205,8 @@ Handle any errors from the server."    (pcase response      ((and (let token (plist-get response :access_token))            (guard token)) +     (mastodon-client--make-user-active +      (mastodon-client--store-access-token token))       (cdar (push (cons mastodon-instance-url token)                   mastodon-auth--token-alist))) diff --git a/lisp/mastodon-client.el b/lisp/mastodon-client.el index 42e8b1f..4fc8db7 100644 --- a/lisp/mastodon-client.el +++ b/lisp/mastodon-client.el @@ -1,6 +1,7 @@  ;;; mastodon-client.el --- Client functions for mastodon.el  -*- lexical-binding: t -*-  ;; Copyright (C) 2017-2019 Johnson Denen +;; Copyright (C) 2021 Abhiseck Paira <abhiseckpaira@disroot.org>  ;; Author: Johnson Denen <johnson.denen@gmail.com>  ;; Maintainer: Marty Hiatt <martianhiatus@riseup.net>  ;; Version: 0.10.0 @@ -32,8 +33,10 @@  (require 'plstore)  (require 'json) +(require 'url)  (defvar mastodon-instance-url) +(defvar mastodon-active-user)  (autoload 'mastodon-http--api "mastodon-http")  (autoload 'mastodon-http--post "mastodon-http") @@ -46,14 +49,26 @@  (defvar mastodon-client--client-details-alist nil    "An alist of Client id and secrets keyed by the instance url.") +(defvar mastodon-client--active-user-details-plist nil +  "A plist of active user details.") + +(defvar mastodon-client-scopes "read write follow" +  "Scopes to pass to oauth during registration.") + +(defvar mastodon-client-website "https://codeberg.org/martianh/mastodon.el" +  "Website of mastodon.el.") + +(defvar mastodon-client-redirect-uri "urn:ietf:wg:oauth:2.0:oob" +  "Redirect_uri as required by oauth.") +  (defun mastodon-client--register ()    "POST client to Mastodon."    (mastodon-http--post     (mastodon-http--api "apps") -   '(("client_name" . "mastodon.el") -     ("redirect_uris" . "urn:ietf:wg:oauth:2.0:oob") -     ("scopes" . "read write follow") -     ("website" . "https://github.com/jdenen/mastodon.el")) +   `(("client_name" . "mastodon.el") +     ("redirect_uris" . ,mastodon-client-redirect-uri) +     ("scopes" . ,mastodon-client-scopes) +     ("website" . ,mastodon-client-website))     nil     :unauthenticated)) @@ -88,11 +103,96 @@ Make `mastodon-client--fetch' call to determine client values."      (plstore-close plstore)      client)) +(defun mastodon-client--remove-key-from-plstore (plstore) +  "Remove KEY from PLSTORE." +  (cdr plstore)) + +;; Actually it returns a plist with client-details if such details are +;; already stored in mastodon.plstore  (defun mastodon-client--read ()    "Retrieve client_id and client_secret from `mastodon-client--token-file'."    (let* ((plstore (plstore-open (mastodon-client--token-file)))           (mastodon (plstore-get plstore (concat "mastodon-" mastodon-instance-url)))) -    (cdr mastodon))) +    (mastodon-client--remove-key-from-plstore mastodon))) + +(defun mastodon-client--general-read (key) +  "Retrieve the plstore item keyed by KEY. +Return plist without the KEY." +  (let* ((plstore (plstore-open (mastodon-client--token-file))) +         (plstore-item (plstore-get plstore key))) +    (mastodon-client--remove-key-from-plstore plstore-item))) + +(defun mastodon-client--make-user-details-plist () +  "Make a plist with current user details.  Return it." +  `(:username ,(mastodon-client--form-user-from-vars) +              :instance ,mastodon-instance-url +              :client_id ,(plist-get (mastodon-client) :client_id) +              :client_secret ,(plist-get (mastodon-client) :client_secret))) + +(defun mastodon-client--store-access-token (token) +  "Save TOKEN as :access_token in plstore of the current user. +Return the plist after the operation." +  (let* ((user-details (mastodon-client--make-user-details-plist)) +         (plstore (plstore-open (mastodon-client--token-file))) +         (username (plist-get user-details :username)) +         (plstore-value (setq user-details +                              (plist-put user-details :access_token token))) +         (print-length nil) +         (print-level nil)) +    (plstore-put plstore (concat "user-" username) plstore-value nil) +    (plstore-save plstore) +    (plstore-close plstore) +    plstore-value)) + +(defun mastodon-client--make-user-active (user-details) +  "USER-DETAILS is a plist consisting of user details." +  (let ((plstore (plstore-open (mastodon-client--token-file))) +        (print-length nil) +        (print-level nil)) +    (plstore-put plstore "active-user" user-details nil) +    (plstore-save plstore) +    (plstore-close plstore))) + +(defun mastodon-client--form-user-from-vars () +  "Create a username from user variable.  Return that username. + +Username in the form user@instance.com is formed from the +variables `mastodon-instance-url' and `mastodon-active-user'." +  (concat mastodon-active-user +          "@" +          (url-host (url-generic-parse-url mastodon-instance-url)))) + +(defun mastodon-client--make-current-user-active () +  "Make the user specified by user variables active user. +Return the details (plist)." +  (let ((username (mastodon-client--form-user-from-vars)) +        user-plist) +    (when (setq user-plist +                (mastodon-client--general-read (concat "user-" username))) +      (mastodon-client--make-user-active user-plist)) +    user-plist)) + +(defun mastodon-client--current-user-active-p () +  "Return user-details if the current user is active. +Otherwise return nil." +  (let ((username (mastodon-client--form-user-from-vars)) +        (user-details (mastodon-client--general-read "active-user"))) +    (when (and user-details +               (equal (plist-get user-details :username) username)) +      user-details))) + +(defun mastodon-client--active-user () +  "Return the details of the currently active user. + +Details is a plist." +  (let ((active-user-details mastodon-client--active-user-details-plist)) +    (unless active-user-details +      (setq active-user-details +            (or (mastodon-client--current-user-active-p) +                (mastodon-client--make-current-user-active))) +      (setq mastodon-client--active-user-details-plist +            active-user-details)) +    active-user-details))  (defun mastodon-client ()    "Return variable client secrets to use for `mastodon-instance-url'. diff --git a/lisp/mastodon-http.el b/lisp/mastodon-http.el index e288c18..35fd070 100644 --- a/lisp/mastodon-http.el +++ b/lisp/mastodon-http.el @@ -32,6 +32,7 @@  (require 'json)  (require 'request) ; for attachments upload +(require 'url)  (defvar mastodon-instance-url)  (defvar mastodon-toot--media-attachment-ids) @@ -156,6 +157,13 @@ Pass response buffer to CALLBACK function."      (with-temp-buffer        (mastodon-http--url-retrieve-synchronously url)))) +(defun mastodon-http--append-query-string (url params) +  "Append PARAMS to URL as query strings and return it. + +PARAMS should be an alist as required `url-build-query-string'." +  (let ((query-string (url-build-query-string params))) +    (concat url "?" query-string))) +  ;; search functions:  (defun mastodon-http--process-json-search ()    "Process JSON returned by a search query to the server." diff --git a/lisp/mastodon.el b/lisp/mastodon.el index 30fc2d3..49abe26 100644 --- a/lisp/mastodon.el +++ b/lisp/mastodon.el @@ -1,6 +1,7 @@  ;;; mastodon.el --- Client for Mastodon  -*- lexical-binding: t -*-  ;; Copyright (C) 2017-2019 Johnson Denen +;; Copyright (C) 2021 Abhiseck Paira <abhiseckpaira@disroot.org>  ;; Author: Johnson Denen <johnson.denen@gmail.com>  ;; Maintainer: Marty Hiatt <martianhiatus@riseup.net>  ;; Version: 0.10.0 @@ -97,7 +98,34 @@    :group 'external)  (defcustom mastodon-instance-url "https://mastodon.social" -  "Base URL for the Masto instance from which you toot." +  "Base URL for the Mastodon instance you want to be active. + +For example, if your mastodon username is +\"example_user@social.instance.org\", and you want this account +to be active, the value of this variable should be +\"https://social.instance.org\". + +Also for completeness, the value of `mastodon-active-user' should +be \"example_user\". + +After setting these variables you should restart Emacs for these +changes to take effect." +  :group 'mastodon +  :type 'string) + +(defcustom mastodon-active-user nil +  "Username of the active user. + +For example, if your mastodon username is +\"example_user@social.instance.org\", and you want this account +to be active, the value of this variable should be +\"example_user\". + +Also for completeness, the value of `mastodon-instance-url' +should be \"https://social.instance.org\". + +After setting these variables you should restart Emacs for these +changes to take effect."    :group 'mastodon    :type 'string) diff --git a/test/ert-helper.el b/test/ert-helper.el index c2fd5a6..f65649f 100644 --- a/test/ert-helper.el +++ b/test/ert-helper.el @@ -1,3 +1,4 @@ +(load-file "lisp/mastodon.el")  (load-file "lisp/mastodon-search.el")  (load-file "lisp/mastodon-async.el")  (load-file "lisp/mastodon-http.el") diff --git a/test/mastodon-auth-tests.el b/test/mastodon-auth-tests.el index 6a090b7..2d9d6df 100644 --- a/test/mastodon-auth-tests.el +++ b/test/mastodon-auth-tests.el @@ -32,47 +32,6 @@             `(:error "invalid_grant" :error_description ,error-message))          (t error)))))) -(ert-deftest mastodon-auth--generate-token--no-storing-credentials () -  "Should make `mastdon-http--post' request to generate auth token." -  (with-mock -    (let ((mastodon-auth-source-file "") -	  (mastodon-instance-url "https://instance.url")) -      (mock (mastodon-client) => '(:client_id "id" :client_secret "secret")) -      (mock (read-string "Email: " user-mail-address) => "foo@bar.com") -      (mock (read-passwd "Password: ") => "password") -      (mock (mastodon-http--post "https://instance.url/oauth/token" -                                 '(("client_id" . "id") -                                   ("client_secret" . "secret") -                                   ("grant_type" . "password") -                                   ("username" . "foo@bar.com") -                                   ("password" . "password") -                                   ("scope" . "read write follow")) -                                 nil -                                 :unauthenticated)) -      (mastodon-auth--generate-token)))) - -(ert-deftest mastodon-auth--generate-token--storing-credentials () -  "Should make `mastdon-http--post' request to generate auth token." -  (with-mock -    (let ((mastodon-auth-source-file "~/.authinfo") -	  (mastodon-instance-url "https://instance.url")) -      (mock (mastodon-client) => '(:client_id "id" :client_secret "secret")) -      (mock (auth-source-search :create t -                                :host "https://instance.url" -                                :port 443 -                                :require '(:user :secret)) -            => '((:user "foo@bar.com" :secret (lambda () "password")))) -      (mock (mastodon-http--post "https://instance.url/oauth/token" -                                 '(("client_id" . "id") -                                   ("client_secret" . "secret") -                                   ("grant_type" . "password") -                                   ("username" . "foo@bar.com") -                                   ("password" . "password") -                                   ("scope" . "read write follow")) -                                 nil -				 :unauthenticated)) -      (mastodon-auth--generate-token)))) -  (ert-deftest mastodon-auth--get-token ()    "Should generate token and return JSON response."    (with-temp-buffer @@ -94,12 +53,23 @@  (ert-deftest mastodon-auth--access-token-not-found ()    "Should set and return `mastodon-auth--token' if nil."    (let ((mastodon-instance-url "https://instance.url") +        (mastodon-active-user "user")          (mastodon-auth--token-alist nil))      (with-mock        (mock (mastodon-auth--get-token) => '(:access_token "foobaz")) +      (mock (mastodon-client--store-access-token "foobaz")) +      (stub mastodon-client--make-user-active)        (should         (string= (mastodon-auth--access-token)                  "foobaz"))        (should         (equal mastodon-auth--token-alist                '(("https://instance.url" . "foobaz"))))))) + +(ert-deftest mastodon-auth--user-unaware () +  (let ((mastodon-instance-url "https://instance.url") +        (mastodon-active-user nil) +        (mastodon-auth--token-alist nil)) +    (with-mock +      (mock (mastodon-client--active-user)) +      (should-error (mastodon-auth--access-token))))) diff --git a/test/mastodon-client-tests.el b/test/mastodon-client-tests.el index 9123286..b302ed6 100644 --- a/test/mastodon-client-tests.el +++ b/test/mastodon-client-tests.el @@ -10,7 +10,7 @@                                 '(("client_name" . "mastodon.el")                                   ("redirect_uris" . "urn:ietf:wg:oauth:2.0:oob")                                   ("scopes" . "read write follow") -                                 ("website" . "https://github.com/jdenen/mastodon.el")) +                                 ("website" . "https://codeberg.org/martianh/mastodon.el"))                                 nil                                 :unauthenticated))      (mastodon-client--register))) @@ -24,25 +24,22 @@                                               (current-buffer)))        (should (equal (mastodon-client--fetch) '(:foo "bar")))))) -(ert-deftest mastodon-client--store-1 () -  "Should return the client plist." +(ert-deftest mastodon-client--store () +  "Test the value `mastodon-client--store' returns/stores."    (let ((mastodon-instance-url "http://mastodon.example")          (plist '(:client_id "id" :client_secret "secret")))      (with-mock        (mock (mastodon-client--token-file) => "stubfile.plstore") -      (mock (mastodon-client--fetch) => '(:client_id "id" :client_secret "secret")) -      (let* ((plstore (plstore-open "stubfile.plstore")) -             (client (cdr (plstore-get plstore "mastodon-http://mastodon.example")))) -        (should (equal (mastodon-client--store) plist)))))) - -(ert-deftest mastodon-client--store-2 () -  "Should store client in `mastodon-client--token-file'." -  (let* ((mastodon-instance-url "http://mastodon.example") -         (plstore (plstore-open "stubfile.plstore")) -         (client (cdr (plstore-get plstore "mastodon-http://mastodon.example")))) -    (plstore-close plstore) -    (should (string= (plist-get client :client_id) "id")) -    (should (string= (plist-get client :client_secret) "secret")))) +      (mock (mastodon-client--fetch) => plist) +      (should (equal (mastodon-client--store) plist))) +    (let* ((plstore (plstore-open "stubfile.plstore")) +           (client (mastodon-client--remove-key-from-plstore +                    (plstore-get plstore "mastodon-http://mastodon.example")))) +      (plstore-close plstore) +      (should (equal client plist)) +      ;; clean up - delete the stubfile +      (delete-file "stubfile.plstore")))) +  (ert-deftest mastodon-client--read-finds-match ()    "Should return mastodon client from `mastodon-token-file' if it exists." @@ -52,6 +49,27 @@        (should (equal (mastodon-client--read)                       '(:client_id "id2" :client_secret "secret2")))))) +(ert-deftest mastodon-client--general-read-finds-match () +  (with-mock +    (mock (mastodon-client--token-file) => "fixture/client.plstore") +    (should (equal (mastodon-client--general-read "user-test8000@mastodon.example") +                   '(:username "test8000@mastodon.example" +                               :instance "http://mastodon.example" +                               :client_id "id2" :client_secret "secret2" +                               :access_token "token2"))))) + +(ert-deftest mastodon-client--general-read-finds-no-match () +  (with-mock +    (mock (mastodon-client--token-file) => "fixture/client.plstore") +    (should (equal (mastodon-client--general-read "nonexistant-key") +                   nil)))) + +(ert-deftest mastodon-client--general-read-empty-store () +  (with-mock +    (mock (mastodon-client--token-file) => "fixture/empty.plstore") +    (should (equal (mastodon-client--general-read "something") +                   nil)))) +  (ert-deftest mastodon-client--read-finds-no-match ()    "Should return mastodon client from `mastodon-token-file' if it exists."    (let ((mastodon-instance-url "http://mastodon.social")) @@ -103,3 +121,54 @@        (should (equal (mastodon-client) '(:client_id "foo" :client_secret "baz")))        (should (equal mastodon-client--client-details-alist                       '(("http://mastodon.example" :client_id "foo" :client_secret "baz"))))))) + +(ert-deftest mastodon-client--form-user-from-vars () +  (let ((mastodon-active-user "test9000") +        (mastodon-instance-url "https://mastodon.example")) +    (should +     (equal (mastodon-client--form-user-from-vars) +            "test9000@mastodon.example")))) + +(ert-deftest mastodon-client--current-user-active-p () +  (let ((mastodon-active-user "test9000") +        (mastodon-instance-url "https://mastodon.example")) +    ;; when the current user /is/ the active user +    (with-mock +      (mock (mastodon-client--general-read "active-user") => '(:username "test9000@mastodon.example" :client_id "id1")) +      (should (equal (mastodon-client--current-user-active-p) +                     '(:username "test9000@mastodon.example" :client_id "id1")))) +    ;; when the current user is /not/ the active user +    (with-mock +      (mock (mastodon-client--general-read "active-user") => '(:username "user@other.example" :client_id "id1")) +      (should (null (mastodon-client--current-user-active-p)))))) + +(ert-deftest mastodon-client--store-access-token () +  (let ((mastodon-instance-url "https://mastodon.example") +        (mastodon-active-user "test8000") +        (user-details +         '(:username "test8000@mastodon.example" +                     :instance "https://mastodon.example" +                     :client_id "id" :client_secret "secret" +                     :access_token "token"))) +    ;; test if mastodon-client--store-access-token /returns/ right +    ;; value +    (with-mock +      (mock (mastodon-client) => '(:client_id "id" :client_secret "secret")) +      (mock (mastodon-client--token-file) => "stubfile.plstore") +      (should (equal (mastodon-client--store-access-token "token") +                     user-details))) +    ;; test if mastodon-client--store-access-token /stores/ right value +    (with-mock +      (mock (mastodon-client--token-file) => "stubfile.plstore") +      (should (equal (mastodon-client--general-read +                      "user-test8000@mastodon.example") +                     user-details))) +    (delete-file "stubfile.plstore"))) + +(ert-deftest mastodon-client--make-user-active () +  (let ((user-details '(:username "test@mastodon.example"))) +    (with-mock +      (mock (mastodon-client--token-file) => "stubfile.plstore") +      (mastodon-client--make-user-active user-details) +      (should (equal (mastodon-client--general-read "active-user") +                     user-details))))) | 
