]> code.delx.au - gnu-emacs-elpa/blob - packages/oauth2/oauth2.el
New package oauth2
[gnu-emacs-elpa] / packages / oauth2 / oauth2.el
1 ;;; oauth2.el --- OAuth 2.0 Authorization Protocol
2
3 ;; Copyright (C) 2011 Free Software Foundation, Inc
4
5 ;; Author: Julien Danjou <julien@danjou.info>
6 ;; Version: 0.1
7 ;; Keywords: comm
8
9 ;; This file is part of GNU Emacs.
10
11 ;; GNU Emacs is free software: you can redistribute it and/or modify
12 ;; it under the terms of the GNU General Public License as published by
13 ;; the Free Software Foundation, either version 3 of the License, or
14 ;; (at your option) any later version.
15
16 ;; GNU Emacs is distributed in the hope that it will be useful,
17 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
18 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19 ;; GNU General Public License for more details.
20
21 ;; You should have received a copy of the GNU General Public License
22 ;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
23
24 ;;; Commentary:
25
26 ;; Implementation of the OAuth 2.0 draft.
27 ;;
28 ;; The main entry point is `oauth2-auth-and-store' which will return a token
29 ;; structure. This token structure can be then used with
30 ;; `oauth2-url-retrieve-synchronously' to retrieve any data that need OAuth
31 ;; authentication to be accessed.
32 ;;
33 ;; If the token needs to be refreshed, the code handles it automatically and
34 ;; store the new value of the access token.
35
36 ;;; Code:
37
38 (require 'plstore)
39
40 (defun oauth2-request-authorization (auth-url client-id &optional scope state)
41 "Request OAuth authorization at AUTH-URL by launching `browse-url'.
42 CLIENT-ID is the client id provided by the provider.
43 It returns the code provided by the service."
44 (browse-url (concat auth-url
45 (if (string-match-p "\?" auth-url) "&" "?")
46 "client_id=" client-id
47 "&response_type=code&redirect_uri=urn:ietf:wg:oauth:2.0:oob"
48 (if scope (concat "&scope=" (url-hexify-string scope)) "")
49 (if state (concat "&state=" state) "")))
50 (read-string "Enter the code your browser displayed: "))
51
52 (defun oauth2-request-access-parse ()
53 "Parse the result of an OAuth request."
54 (goto-char (point-min))
55 (when (search-forward-regexp "^$" nil t)
56 (json-read)))
57
58 (defun oauth2-make-access-request (url data)
59 "Make an access request to URL using DATA in POST."
60 (let ((url-request-method "POST")
61 (url-request-data data)
62 (url-request-extra-headers '(("Content-Type" . "application/x-www-form-urlencoded"))))
63 (with-current-buffer (url-retrieve-synchronously url)
64 (let ((data (oauth2-request-access-parse)))
65 (kill-buffer (current-buffer))
66 data))))
67
68 (defstruct oauth2-token
69 plstore
70 plstore-id
71 client-id
72 client-secret
73 access-token
74 refresh-token
75 token-url)
76
77 (defun oauth2-request-access (token-url client-id client-secret code)
78 "Request OAuth access at TOKEN-URL.
79 The CODE should be obtained with `oauth2-request-authorization'.
80 Return an `oauth2-token' structure."
81 (when code
82 (let ((result
83 (oauth2-make-access-request token-url
84 (concat "client_id=" client-id
85 "&client_secret=" client-secret
86 "&code=" code
87 "&redirect_uri=urn:ietf:wg:oauth:2.0:oob&grant_type=authorization_code"))))
88 (make-oauth2-token :client-id client-id
89 :client-secret client-secret
90 :access-token (aget result 'access_token)
91 :refresh-token (aget result 'refresh_token)
92 :token-url token-url))))
93
94 ;;;###autoload
95 (defun oauth2-refresh-access (token)
96 "Refresh OAuth access TOKEN.
97 TOKEN should be obtained with `oauth2-request-access'."
98 (setf (oauth2-token-access-token token)
99 (aget (oauth2-make-access-request (oauth2-token-token-url token)
100 (concat "client_id=" (oauth2-token-client-id token)
101 "&client_secret=" (oauth2-token-client-secret token)
102 "&refresh_token=" (oauth2-token-refresh-token token)
103 "&grant_type=refresh_token"))
104 'access_token))
105 ;; If the token has a plstore, update it
106 (let ((plstore (oauth2-token-plstore token)))
107 (when plstore
108 (plstore-put plstore (oauth2-token-plstore-id token)
109 nil `(:access-token
110 ,(oauth2-token-access-token token)
111 :refresh-token
112 ,(oauth2-token-refresh-token token)))
113 (plstore-save plstore)))
114 token)
115
116 ;;;###autoload
117 (defun oauth2-auth (auth-url token-url client-id client-secret &optional scope state)
118 "Authenticate application via OAuth2."
119 (oauth2-request-access
120 token-url
121 client-id
122 client-secret
123 (oauth2-request-authorization
124 auth-url client-id scope state)))
125
126 (defcustom oauth2-token-file (concat user-emacs-directory "oauth2.plstore")
127 "File path where store OAuth tokens."
128 :group 'oauth2
129 :type 'file)
130
131 (defun oauth2-compute-id (auth-url token-url resource-url)
132 "Compute an unique id based on URLs.
133 This allows to store the token in an unique way."
134 (secure-hash 'md5 (concat auth-url token-url resource-url)))
135
136 ;;;###autoload
137 (defun oauth2-auth-and-store (auth-url token-url resource-url client-id client-secret)
138 "Request access to a resource and store it using `plstore'."
139 ;; We store a MD5 sum of all URL
140 (let* ((plstore (plstore-open oauth2-token-file))
141 (id (oauth2-compute-id auth-url token-url resource-url))
142 (plist (cdr (plstore-get plstore id))))
143 ;; Check if we found something matching this access
144 (if plist
145 ;; We did, return the token object
146 (make-oauth2-token :plstore plstore
147 :plstore-id id
148 :client-id client-id
149 :client-secret client-secret
150 :access-token (plist-get plist :access-token)
151 :refresh-token (plist-get plist :refresh-token)
152 :token-url token-url)
153 (let ((token (oauth2-auth auth-url token-url
154 client-id client-secret resource-url)))
155 ;; Set the plstore
156 (setf (oauth2-token-plstore token) plstore)
157 (setf (oauth2-token-plstore-id token) id)
158 (plstore-put plstore id nil `(:access-token
159 ,(oauth2-token-access-token token)
160 :refresh-token
161 ,(oauth2-token-refresh-token token)))
162 (plstore-save plstore)
163 token))))
164
165 (defun oauth2-url-append-access-token (token url)
166 "Append access token to URL."
167 (concat url
168 (if (string-match-p "\?" url) "&" "?")
169 "access_token=" (oauth2-token-access-token token)))
170
171 ;;;###autoload
172 (defun oauth2-url-retrieve-synchronously (token url)
173 "Retrieve an URL synchronously using TOKENS to access it.
174 TOKENS can be obtained with `oauth2-auth'."
175 (let (tokens-need-renew)
176 (flet ((url-http-handle-authentication (proxy)
177 (setq tokens-need-renew t)
178 ;; This is to make `url' think it's done.
179 (setq success t)))
180 (let ((url-buffer (url-retrieve-synchronously
181 (oauth2-url-append-access-token token url))))
182 (if tokens-need-renew
183 (oauth2-url-retrieve-synchronously (oauth2-refresh-access token) url)
184 url-buffer)))))
185
186 (provide 'oauth2)
187
188 ;;; oauth2.el ends here