aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authormartianh <martianh@noreply.codeberg.org>2022-03-23 08:04:07 +0100
committermartianh <martianh@noreply.codeberg.org>2022-03-23 08:04:07 +0100
commitcecd5de060a56f13f7f7eb4528b341027812faab (patch)
treeb612438cd9e5b46c0300c2ae1dc1b007698226bb
parentc7b475160d2e7712e339e15adf168529f71b52c6 (diff)
parent56fa25df379623e79261b535cd724db3ed979d44 (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.org32
-rw-r--r--fixture/client.plstore5
-rw-r--r--lisp/mastodon-auth.el201
-rw-r--r--lisp/mastodon-client.el110
-rw-r--r--lisp/mastodon-http.el8
-rw-r--r--lisp/mastodon.el30
-rw-r--r--test/ert-helper.el1
-rw-r--r--test/mastodon-auth-tests.el52
-rw-r--r--test/mastodon-client-tests.el101
9 files changed, 392 insertions, 148 deletions
diff --git a/README.org b/README.org
index 929f5ea..f11ba61 100644
--- a/README.org
+++ b/README.org
@@ -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)))))