Skip to content

Commit

Permalink
Switch to oauth2-auto for Google Calendar OAuth2
Browse files Browse the repository at this point in the history
This allows switching from the deprecated OOB flow
<https://developers.googleblog.com/2022/02/making-oauth-flows-safer.html#disallowed-oob>,
fixing <kidd#191>.

As a side effect, oauth2-auto makes it straightforward to support
multiple accounts (see
<kidd#199>), although this
PR doesn't by itself enable that.
  • Loading branch information
telotortium committed Jul 2, 2022
1 parent 3aa9251 commit 6fd06b9
Showing 1 changed file with 33 additions and 126 deletions.
159 changes: 33 additions & 126 deletions org-gcal.el
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
(require 'alert)
(require 'cl-lib)
(require 'json)
(require 'oauth2-auto)
(require 'ol)
(require 'org)
(require 'org-archive)
Expand Down Expand Up @@ -297,15 +298,6 @@ See: https://developers.google.com/calendar/v3/reference/events/insert."
:group 'org-gcal
:type 'string)

(defconst org-gcal-auth-url "https://accounts.google.com/o/oauth2/auth"
"Google OAuth2 server URL.")

(defconst org-gcal-token-url "https://www.googleapis.com/oauth2/v3/token"
"Google OAuth2 server URL.")

(defconst org-gcal-resource-url "https://www.googleapis.com/auth/calendar"
"URL used to request access to calendar resources.")

(defconst org-gcal-api-url "https://www.googleapis.com/calendar/v3"
"URL relative to which Google Calendar API methods are called.")

Expand Down Expand Up @@ -365,7 +357,6 @@ AIO version: ‘org-gcal-sync-aio'."
(user-error "org-gcal sync locked. If a previous sync has failed, call ‘org-gcal--sync-unlock’ to reset the lock and try again."))
(org-gcal--sync-lock)
(org-generic-id-update-id-locations org-gcal-entry-id-property)
(org-gcal--ensure-token)
(when org-gcal-auto-archive
(dolist (i org-gcal-fetch-file-alist)
(with-current-buffer
Expand Down Expand Up @@ -735,10 +726,10 @@ AIO version: ‘org-gcal--sync-request-events-aio'"
:type "GET"
:headers
`(("Accept" . "application/json")
("Authorization" . ,(format "Bearer %s" (org-gcal--get-access-token))))
("Authorization" . ,(format "Bearer %s" (org-gcal--get-access-token calendar-id))))
:params
(append
`(("access_token" . ,(org-gcal--get-access-token))
`(("access_token" . ,(org-gcal--get-access-token calendar-id))
("singleEvents" . "True"))
(when org-gcal-local-timezone `(("timeZone" . ,org-gcal-local-timezone)))
(seq-let [expires sync-token]
Expand Down Expand Up @@ -804,10 +795,10 @@ AIO version: ‘org-gcal--sync-request-instances-aio'"
:type "GET"
:headers
`(("Accept" . "application/json")
("Authorization" . ,(format "Bearer %s" (org-gcal--get-access-token))))
("Authorization" . ,(format "Bearer %s" (org-gcal--get-access-token calendar-id))))
:params
(append
`(("access_token" . ,(org-gcal--get-access-token))
`(("access_token" . ,(org-gcal--get-access-token calendar-id))
("timeMin" . ,(org-gcal--format-time2iso up-time))
("timeMax" . ,(org-gcal--format-time2iso down-time)))
(when page-token `(("pageToken" . ,page-token))))
Expand Down Expand Up @@ -858,7 +849,7 @@ AIO version: ‘org-gcal--sync-handle-response-aio'"
"Received HTTP 401"
"OAuth token expired. Now trying to refresh-token")
(deferred:$ ; Migrated to AIO
(org-gcal--refresh-token)
(org-gcal--refresh-token calendar-id)
(deferred:nextc it
(lambda (_unused)
(funcall retry-fn)))))
Expand Down Expand Up @@ -1252,7 +1243,6 @@ AIO version: see ‘org-gcal-sync-buffer-aio'."
(when org-gcal--sync-lock
(user-error "org-gcal sync locked. If a previous sync has failed, call ‘org-gcal--sync-unlock’ to reset the lock and try again."))
(org-gcal--sync-lock)
(org-gcal--ensure-token)
(let*
((name (or (buffer-file-name) (buffer-name))))
(deferred:try
Expand Down Expand Up @@ -1833,7 +1823,6 @@ For valid values of EXISTING-MODE see
AIO version: ‘org-gcal-post-at-point-aio'."
(interactive)
(org-gcal--ensure-token)
(save-excursion
;; Post entry at point in org-agenda buffer.
(when (eq major-mode 'org-agenda-mode)
Expand Down Expand Up @@ -2082,7 +2071,6 @@ If called with prefix or with CLEAR-GCAL-INFO non-nil, will clear calendar info
from the entry even if deleting the event from the server fails. Use this to
delete calendar info from events on calendars you no longer have access to."
(interactive "P")
(org-gcal--ensure-token)
(save-excursion
;; Delete entry at point in org-agenda buffer.
(when (eq major-mode 'org-agenda-mode)
Expand Down Expand Up @@ -2198,67 +2186,6 @@ For overall description, including CLEAR-GCAL-INFO, see that."
(error "In org-gcal-delete-at-point: for %s %s: error: %S"
calendar-id event-id delete-error)))))))

(defun org-gcal-request-authorization ()
"Request OAuth authorization at AUTH-URL by launching `browse-url'.
CLIENT-ID is the client id provided by the provider.
Return the code provided by the service."
(let* ((gcal-auth-url
(concat org-gcal-auth-url
"?client_id=" (url-hexify-string org-gcal-client-id)
"&response_type=code"
"&redirect_uri=" (url-hexify-string "urn:ietf:wg:oauth:2.0:oob")
"&scope=" (url-hexify-string org-gcal-resource-url)))
(prompt
(format
"Please visit (if it doesn't open automatically): %s\n\nEnter the code your browser displayed:"
gcal-auth-url)))
(browse-url gcal-auth-url)
(read-string prompt)))

(defun org-gcal-request-token ()
"Refresh OAuth access at TOKEN-URL.
Returns a ‘deferred’ object that can be used to wait for completion."
(interactive)
(deferred:$ ; Migrated to AIO
(request-deferred
org-gcal-token-url
:type "POST"
:data `(("client_id" . ,org-gcal-client-id)
("client_secret" . ,org-gcal-client-secret)
("code" . ,(org-gcal-request-authorization))
("redirect_uri" . "urn:ietf:wg:oauth:2.0:oob")
("grant_type" . "authorization_code"))
:parser 'org-gcal--json-read)
(deferred:nextc it
(lambda (response)
(let
((data (request-response-data response))
(status-code (request-response-status-code response))
(error-thrown (request-response-error-thrown response)))
(cond
;; If there is no network connectivity, the response will not
;; include a status code.
((eq status-code nil)
(org-gcal--notify
"Got Error"
"Could not contact remote service. Please check your network connectivity.")
(error "Network connectivity issue %s: %s" status-code error-thrown))
;; Generic error-handler meant to provide useful information about
;; failure cases not otherwise explicitly specified.
((not (eq error-thrown nil))
(org-gcal--notify
(concat "Status code: " (number-to-string status-code))
(pp-to-string error-thrown))
(error "Got error %S: %S" status-code error-thrown))
;; Fetch was successful.
(t
(when data
(setq org-gcal-token-plist data)
(org-gcal--save-sexp data org-gcal-token-file))
(deferred:succeed nil))))))))

(defun org-gcal-request-token-aio ()
"Refresh OAuth access at ‘org-gcal-token-url'.
Expand Down Expand Up @@ -2307,36 +2234,21 @@ For overall description, see that."
(org-gcal--save-sexp data org-gcal-token-file))
nil))))

(defun org-gcal--refresh-token ()
(defun org-gcal--refresh-token (calendar-id)
"Refresh OAuth access and return the new access token as a deferred object.
AIO version: ‘org-gcal--refresh-token-aio’."
(deferred:$ ; Migrated to AIO
(request-deferred
org-gcal-token-url
:type "POST"
:data `(("client_id" . ,org-gcal-client-id)
("client_secret" . ,org-gcal-client-secret)
("refresh_token" . ,(org-gcal--get-refresh-token))
("grant_type" . "refresh_token"))
:parser 'org-gcal--json-read)
(deferred:nextc it
(lambda (response)
(let ((data (request-response-data response))
(status-code (request-response-status-code response))
(error-thrown (request-response-error-thrown response)))
(cond
((eq error-thrown nil)
(plist-put org-gcal-token-plist
:access_token
(plist-get data :access_token))
(org-gcal--save-sexp org-gcal-token-plist org-gcal-token-file)
(let ((_token (plist-get org-gcal-token-plist :access_token)))
(deferred:succeed nil)))
(t
(error "Got error %S: %S" status-code error-thrown))))))))

(aio-iter2-defun org-gcal--refresh-token-aio ()
;; FIXME: For now, we just synchronously wait for the refresh. Once the
;; project has been rewritten to use aio
;; (https://github.com/kidd/org-gcal.el/issues/191), we can wait for this
;; asynchronously as well.
(let ((token
(aio-wait-for
(oauth2-auto-access-token calendar-id 'org-gcal))))
(deferred:succeed token)))

"Refresh OAuth access and return an ‘aio-promise’ for the new access token."
(let* ((response
(aio-await
Expand Down Expand Up @@ -3189,7 +3101,7 @@ overwrite the event at MARKER if the event has changed on the server.
Returns a ‘deferred’ object that can be used to wait for completion.
AIO version: ‘org-gcal--delete-event-aio’."
(let ((a-token (or a-token (org-gcal--get-access-token))))
(let ((a-token (or a-token (org-gcal--get-access-token calendar-id))))
(deferred:try
(deferred:$ ; Migrated to AIO
(request-deferred
Expand Down Expand Up @@ -3377,22 +3289,6 @@ Returns an ‘aio-promise’ object that can be used to wait for completion."
(with-eval-after-load 'org-refile
(add-hook 'org-after-refile-insert-hook 'org-gcal--refile-post))

(defun org-gcal--ensure-token ()
"Ensure that access, refresh, and sync token variables in expected state."
(unless (org-gcal--sync-tokens-valid)
(persist-load 'org-gcal--sync-tokens)
(unless (org-gcal--sync-tokens-valid)
(org-gcal-sync-tokens-clear)))
(cond
(org-gcal-token-plist t)
((and (file-exists-p org-gcal-token-file)
(ignore-errors
(setq org-gcal-token-plist
(with-temp-buffer
(insert-file-contents org-gcal-token-file)
(plist-get (read (current-buffer)) :token))))) t)
(t (deferred:sync! (org-gcal-request-token)))))

(aio-iter2-defun org-gcal--ensure-token-aio ()
"Ensure that access, refresh, and sync token variables in expected state.
Expand All @@ -3417,11 +3313,6 @@ Returns a promise to wait for completion."
(and (listp org-gcal--sync-tokens)
(json-alist-p org-gcal--sync-tokens)))

(defun org-gcal--clear-access-token ()
"Clear access token in ‘org-gcal-token-plist’.
Primarily for debugging."
(plist-put org-gcal-token-plist :access_token nil))

(defun org-gcal--timestamp-successor ()
"Search for the next timestamp object.
Return value is a cons cell whose CAR is `timestamp' and CDR is
Expand Down Expand Up @@ -3565,6 +3456,22 @@ For URL and SETTINGS see ‘org-gcal--aio-request’."
(org-gcal-tmp-dbgmsg "catch-error: err: %S" err)
(signal (car err) (cdr err)))))

(defun org-gcal-reload-client-id-secret ()
"Setup OAuth2 authentication after setting client ID and secret."
(interactive)
(add-to-list
'oauth2-auto-additional-providers-alist
`(org-gcal
(authorize_url . "https://accounts.google.com/o/oauth2/auth")
(token_url . "https://oauth2.googleapis.com/token")
(scope . "https://www.googleapis.com/auth/calendar")
(client_id . ,org-gcal-client-id)
(client_secret . ,org-gcal-client-secret))))

(if (and org-gcal-client-id org-gcal-client-secret)
(org-gcal-reload-client-id-secret)
(warn "org-gcal: must set ‘org-gcal-client-id’ and ‘org-gcal-client-secret’ for this package to work. Please run ‘org-gcal-reload-client-id-secret’ after setting these variables."))

(provide 'org-gcal)

;;; org-gcal.el ends here
Expand Down

0 comments on commit 6fd06b9

Please sign in to comment.