]> code.delx.au - gnu-emacs-elpa/blob - packages/oauth2/oauth2.el
Clean up copyright notices.
[gnu-emacs-elpa] / packages / oauth2 / oauth2.el
1 ;;; oauth2.el --- OAuth 2.0 Authorization Protocol
2
3 ;; Copyright (C) 2011-2012 Free Software Foundation, Inc
4
5 ;; Author: Julien Danjou <julien@danjou.info>
6 ;; Version: 0.8
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 'cl)
39 (require 'plstore)
40 (require 'json)
41
42 (defun oauth2-request-authorization (auth-url client-id &optional scope state redirect-uri)
43 "Request OAuth authorization at AUTH-URL by launching `browse-url'.
44 CLIENT-ID is the client id provided by the provider.
45 It returns the code provided by the service."
46 (browse-url (concat auth-url
47 (if (string-match-p "\?" auth-url) "&" "?")
48 "client_id=" (url-hexify-string client-id)
49 "&response_type=code"
50 "&redirect_uri=" (url-hexify-string (or redirect-uri "urn:ietf:wg:oauth:2.0:oob"))
51 (if scope (concat "&scope=" (url-hexify-string scope)) "")
52 (if state (concat "&state=" (url-hexify-string state)) "")))
53 (read-string "Enter the code your browser displayed: "))
54
55 (defun oauth2-request-access-parse ()
56 "Parse the result of an OAuth request."
57 (goto-char (point-min))
58 (when (search-forward-regexp "^$" nil t)
59 (json-read)))
60
61 (defun oauth2-make-access-request (url data)
62 "Make an access request to URL using DATA in POST."
63 (let ((url-request-method "POST")
64 (url-request-data data)
65 (url-request-extra-headers
66 '(("Content-Type" . "application/x-www-form-urlencoded"))))
67 (with-current-buffer (url-retrieve-synchronously url)
68 (let ((data (oauth2-request-access-parse)))
69 (kill-buffer (current-buffer))
70 data))))
71
72 (defstruct oauth2-token
73 plstore
74 plstore-id
75 client-id
76 client-secret
77 access-token
78 refresh-token
79 token-url
80 access-response)
81
82 (defun oauth2-request-access (token-url client-id client-secret code &optional redirect-uri)
83 "Request OAuth access at TOKEN-URL.
84 The CODE should be obtained with `oauth2-request-authorization'.
85 Return an `oauth2-token' structure."
86 (when code
87 (let ((result
88 (oauth2-make-access-request
89 token-url
90 (concat
91 "client_id=" client-id
92 "&client_secret=" client-secret
93 "&code=" code
94 "&redirect_uri=" (url-hexify-string (or redirect-uri "urn:ietf:wg:oauth:2.0:oob"))
95 "&grant_type=authorization_code"))))
96 (make-oauth2-token :client-id client-id
97 :client-secret client-secret
98 :access-token (cdr (assoc 'access_token result))
99 :refresh-token (cdr (assoc 'refresh_token result))
100 :token-url token-url
101 :access-response result))))
102
103 ;;;###autoload
104 (defun oauth2-refresh-access (token)
105 "Refresh OAuth access TOKEN.
106 TOKEN should be obtained with `oauth2-request-access'."
107 (setf (oauth2-token-access-token token)
108 (cdr (assoc 'access_token
109 (oauth2-make-access-request
110 (oauth2-token-token-url token)
111 (concat "client_id=" (oauth2-token-client-id token)
112 "&client_secret=" (oauth2-token-client-secret token)
113 "&refresh_token=" (oauth2-token-refresh-token token)
114 "&grant_type=refresh_token")))))
115 ;; If the token has a plstore, update it
116 (let ((plstore (oauth2-token-plstore token)))
117 (when plstore
118 (plstore-put plstore (oauth2-token-plstore-id token)
119 nil `(:access-token
120 ,(oauth2-token-access-token token)
121 :refresh-token
122 ,(oauth2-token-refresh-token token)
123 :access-response
124 ,(oauth2-token-access-response token)
125 ))
126 (plstore-save plstore)))
127 token)
128
129 ;;;###autoload
130 (defun oauth2-auth (auth-url token-url client-id client-secret &optional scope state redirect-uri)
131 "Authenticate application via OAuth2."
132 (oauth2-request-access
133 token-url
134 client-id
135 client-secret
136 (oauth2-request-authorization
137 auth-url client-id scope state redirect-uri)
138 redirect-uri))
139
140 (defcustom oauth2-token-file (concat user-emacs-directory "oauth2.plstore")
141 "File path where store OAuth tokens."
142 :group 'oauth2
143 :type 'file)
144
145 (defun oauth2-compute-id (auth-url token-url resource-url)
146 "Compute an unique id based on URLs.
147 This allows to store the token in an unique way."
148 (secure-hash 'md5 (concat auth-url token-url resource-url)))
149
150 ;;;###autoload
151 (defun oauth2-auth-and-store (auth-url token-url resource-url client-id client-secret &optional redirect-uri)
152 "Request access to a resource and store it using `plstore'."
153 ;; We store a MD5 sum of all URL
154 (let* ((plstore (plstore-open oauth2-token-file))
155 (id (oauth2-compute-id auth-url token-url resource-url))
156 (plist (cdr (plstore-get plstore id))))
157 ;; Check if we found something matching this access
158 (if plist
159 ;; We did, return the token object
160 (make-oauth2-token :plstore plstore
161 :plstore-id id
162 :client-id client-id
163 :client-secret client-secret
164 :access-token (plist-get plist :access-token)
165 :refresh-token (plist-get plist :refresh-token)
166 :token-url token-url
167 :access-response (plist-get plist :access-response))
168 (let ((token (oauth2-auth auth-url token-url
169 client-id client-secret resource-url nil redirect-uri)))
170 ;; Set the plstore
171 (setf (oauth2-token-plstore token) plstore)
172 (setf (oauth2-token-plstore-id token) id)
173 (plstore-put plstore id nil `(:access-token
174 ,(oauth2-token-access-token token)
175 :refresh-token
176 ,(oauth2-token-refresh-token token)
177 :access-response
178 ,(oauth2-token-access-response token)))
179 (plstore-save plstore)
180 token))))
181
182 (defun oauth2-url-append-access-token (token url)
183 "Append access token to URL."
184 (concat url
185 (if (string-match-p "\?" url) "&" "?")
186 "access_token=" (oauth2-token-access-token token)))
187
188 ;; Local variable from `url'
189 ;; defined here to avoid compile warning
190 (defvar success)
191
192 ;;;###autoload
193 (defun oauth2-url-retrieve-synchronously (token url &optional request-method request-data request-extra-headers)
194 "Retrieve an URL synchronously using TOKENS to access it.
195 TOKENS can be obtained with `oauth2-auth'."
196 (let (tokens-need-renew)
197 (flet ((url-http-handle-authentication (proxy)
198 (setq tokens-need-renew t)
199 ;; This is to make `url' think
200 ;; it's done.
201 (setq success t)))
202 (let ((url-request-method request-method)
203 (url-request-data request-data)
204 (url-request-extra-headers request-extra-headers)
205 (url-buffer))
206 (setq url-buffer (url-retrieve-synchronously
207 (oauth2-url-append-access-token token url)))
208 (if tokens-need-renew
209 (oauth2-url-retrieve-synchronously (oauth2-refresh-access token) url request-method request-data request-extra-headers)
210 url-buffer)))))
211
212 (provide 'oauth2)
213
214 ;;; oauth2.el ends here