-
-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Daniel Fernandes Martins
committed
Nov 30, 2014
0 parents
commit 438b7f7
Showing
6 changed files
with
642 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
playground.el* | ||
.elc |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
Copyright (c) 2014, Daniel Fernandes Martins | ||
All rights reserved. | ||
|
||
Redistribution and use in source and binary forms, with or without | ||
modification, are permitted provided that the following conditions are met: | ||
|
||
1. Redistributions of source code must retain the above copyright notice, this | ||
list of conditions and the following disclaimer. | ||
2. Redistributions in binary form must reproduce the above copyright notice, | ||
this list of conditions and the following disclaimer in the documentation | ||
and/or other materials provided with the distribution. | ||
|
||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | ||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | ||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | ||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR | ||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | ||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | ||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND | ||
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | ||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | ||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# Spotify.el | ||
|
||
TODO. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,220 @@ | ||
;;; oauth2.el --- OAuth 2.0 Authorization Protocol | ||
|
||
;; Copyright (C) 2011-2012 Free Software Foundation, Inc | ||
|
||
;; Author: Julien Danjou <julien@danjou.info> | ||
;; Version: 0.8 | ||
;; Keywords: comm | ||
|
||
;; This file is part of GNU Emacs. | ||
|
||
;; GNU Emacs is free software: you can redistribute it and/or modify | ||
;; it under the terms of the GNU General Public License as published by | ||
;; the Free Software Foundation, either version 3 of the License, or | ||
;; (at your option) any later version. | ||
|
||
;; GNU Emacs is distributed in the hope that it will be useful, | ||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
;; GNU General Public License for more details. | ||
|
||
;; You should have received a copy of the GNU General Public License | ||
;; along with GNU Emacs. If not, see <http://www.gnu.org/licenses/>. | ||
|
||
;;; Commentary: | ||
|
||
;; Implementation of the OAuth 2.0 draft. | ||
;; | ||
;; The main entry point is `oauth2-auth-and-store' which will return a token | ||
;; structure. This token structure can be then used with | ||
;; `oauth2-url-retrieve-synchronously' to retrieve any data that need OAuth | ||
;; authentication to be accessed. | ||
;; | ||
;; If the token needs to be refreshed, the code handles it automatically and | ||
;; store the new value of the access token. | ||
|
||
;;; Code: | ||
|
||
(require 'cl) | ||
(require 'plstore) | ||
(require 'json) | ||
|
||
(defun oauth2-start-server () | ||
"Starts an asynchronous Python process that spawns a local HTTP server in | ||
order to capture the Oauth authorization code sent by the Spotify API." | ||
) | ||
|
||
(defun oauth2-kill-server () | ||
"Kills the local Python HTTP server process.") | ||
|
||
(defun oauth2-request-authorization (auth-url client-id &optional scope state redirect-uri) | ||
"Request OAuth authorization at AUTH-URL by launching `browse-url'. | ||
CLIENT-ID is the client id provided by the provider. | ||
It returns the code provided by the service." | ||
(browse-url (concat auth-url | ||
(if (string-match-p "\?" auth-url) "&" "?") | ||
"client_id=" (url-hexify-string client-id) | ||
"&response_type=code" | ||
"&redirect_uri=" (url-hexify-string (or redirect-uri "urn:ietf:wg:oauth:2.0:oob")) | ||
(if scope (concat "&scope=" (url-hexify-string scope)) "") | ||
(if state (concat "&state=" (url-hexify-string state)) ""))) | ||
(first (split-string (shell-command-to-string "python oauth2_callback_server.py") "\n"))) | ||
|
||
(defun oauth2-request-access-parse () | ||
"Parse the result of an OAuth request." | ||
(goto-char (point-min)) | ||
(when (search-forward-regexp "^$" nil t) | ||
(json-read))) | ||
|
||
(defun oauth2-make-access-request (url data) | ||
"Make an access request to URL using DATA in POST." | ||
(let ((url-request-method "POST") | ||
(url-request-data data) | ||
(url-request-extra-headers | ||
'(("Content-Type" . "application/x-www-form-urlencoded")))) | ||
(with-current-buffer (url-retrieve-synchronously url) | ||
(prog1 (oauth2-request-access-parse) | ||
(kill-buffer))))) | ||
|
||
(defstruct oauth2-token | ||
plstore | ||
plstore-id | ||
client-id | ||
client-secret | ||
access-token | ||
refresh-token | ||
token-url | ||
access-response) | ||
|
||
(defun oauth2-request-access (token-url client-id client-secret code &optional redirect-uri) | ||
"Request OAuth access at TOKEN-URL. | ||
The CODE should be obtained with `oauth2-request-authorization'. | ||
Return an `oauth2-token' structure." | ||
(when code | ||
(let ((result | ||
(oauth2-make-access-request | ||
token-url | ||
(concat | ||
"client_id=" client-id | ||
"&client_secret=" client-secret | ||
"&code=" code | ||
"&redirect_uri=" (url-hexify-string (or redirect-uri "urn:ietf:wg:oauth:2.0:oob")) | ||
"&grant_type=authorization_code")))) | ||
(make-oauth2-token :client-id client-id | ||
:client-secret client-secret | ||
:access-token (cdr (assoc 'access_token result)) | ||
:refresh-token (cdr (assoc 'refresh_token result)) | ||
:token-url token-url | ||
:access-response result)))) | ||
|
||
;;;###autoload | ||
(defun oauth2-refresh-access (token) | ||
"Refresh OAuth access TOKEN. | ||
TOKEN should be obtained with `oauth2-request-access'." | ||
(setf (oauth2-token-access-token token) | ||
(cdr (assoc 'access_token | ||
(oauth2-make-access-request | ||
(oauth2-token-token-url token) | ||
(concat "client_id=" (oauth2-token-client-id token) | ||
"&client_secret=" (oauth2-token-client-secret token) | ||
"&refresh_token=" (oauth2-token-refresh-token token) | ||
"&grant_type=refresh_token"))))) | ||
;; If the token has a plstore, update it | ||
(let ((plstore (oauth2-token-plstore token))) | ||
(when plstore | ||
(plstore-put plstore (oauth2-token-plstore-id token) | ||
nil `(:access-token | ||
,(oauth2-token-access-token token) | ||
:refresh-token | ||
,(oauth2-token-refresh-token token) | ||
:access-response | ||
,(oauth2-token-access-response token))) | ||
(plstore-save plstore))) | ||
token) | ||
|
||
;;;###autoload | ||
(defun oauth2-auth (auth-url token-url client-id client-secret &optional scope state redirect-uri) | ||
"Authenticate application via OAuth2." | ||
(oauth2-request-access | ||
token-url | ||
client-id | ||
client-secret | ||
(oauth2-request-authorization | ||
auth-url client-id scope state redirect-uri) | ||
redirect-uri)) | ||
|
||
(defcustom oauth2-token-file (concat user-emacs-directory "oauth2.plstore") | ||
"File path where store OAuth tokens." | ||
:group 'oauth2 | ||
:type 'file) | ||
|
||
(defun oauth2-compute-id (auth-url token-url resource-url) | ||
"Compute an unique id based on URLs. | ||
This allows to store the token in an unique way." | ||
(secure-hash 'md5 (concat auth-url token-url resource-url))) | ||
|
||
;;;###autoload | ||
(defun oauth2-auth-and-store (auth-url token-url resource-url client-id client-secret &optional redirect-uri) | ||
"Request access to a resource and store it using `plstore'." | ||
;; We store a MD5 sum of all URL | ||
(let* ((plstore (plstore-open oauth2-token-file)) | ||
(id (oauth2-compute-id auth-url token-url resource-url)) | ||
(plist (cdr (plstore-get plstore id)))) | ||
;; Check if we found something matching this access | ||
(if plist | ||
;; We did, return the token object | ||
(make-oauth2-token :plstore plstore | ||
:plstore-id id | ||
:client-id client-id | ||
:client-secret client-secret | ||
:access-token (plist-get plist :access-token) | ||
:refresh-token (plist-get plist :refresh-token) | ||
:token-url token-url | ||
:access-response (plist-get plist :access-response)) | ||
(let ((token (oauth2-auth auth-url token-url | ||
client-id client-secret resource-url nil redirect-uri))) | ||
;; Set the plstore | ||
(setf (oauth2-token-plstore token) plstore) | ||
(setf (oauth2-token-plstore-id token) id) | ||
(plstore-put plstore id nil `(:access-token | ||
,(oauth2-token-access-token token) | ||
:refresh-token | ||
,(oauth2-token-refresh-token token) | ||
:access-response | ||
,(oauth2-token-access-response token))) | ||
(plstore-save plstore) | ||
token)))) | ||
|
||
(defun oauth2-url-append-access-token (token url) | ||
"Append access token to URL." | ||
(concat url | ||
(if (string-match-p "\?" url) "&" "?") | ||
"access_token=" (oauth2-token-access-token token))) | ||
|
||
;; Local variable from `url' | ||
;; defined here to avoid compile warning | ||
(defvar success) | ||
|
||
;;;###autoload | ||
(defun oauth2-url-retrieve-synchronously (token url &optional request-method request-data request-extra-headers) | ||
"Retrieve an URL synchronously using TOKENS to access it. | ||
TOKENS can be obtained with `oauth2-auth'." | ||
(let (tokens-need-renew) | ||
(flet ((url-http-handle-authentication (proxy) | ||
(setq tokens-need-renew t) | ||
;; This is to make `url' think | ||
;; it's done. | ||
(setq success t))) | ||
(let ((url-request-method request-method) | ||
(url-request-data request-data) | ||
(url-request-extra-headers request-extra-headers) | ||
(url-buffer)) | ||
(setq url-buffer (url-retrieve-synchronously | ||
(oauth2-url-append-access-token token url))) | ||
(if tokens-need-renew | ||
(oauth2-url-retrieve-synchronously (oauth2-refresh-access token) url request-method request-data request-extra-headers) | ||
url-buffer))))) | ||
|
||
(provide 'oauth2) | ||
|
||
;;; oauth2.el ends here |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
from BaseHTTPServer import BaseHTTPRequestHandler, HTTPServer | ||
from urlparse import urlparse, parse_qs | ||
|
||
class RequestHandler(BaseHTTPRequestHandler): | ||
def log_message(self, format, *args): | ||
return | ||
|
||
def do_GET(self): | ||
print parse_qs(urlparse(self.path).query)["code"][0] | ||
|
||
self.send_response(200) | ||
self.send_header('Content-type','text/html') | ||
self.end_headers() | ||
|
||
self.wfile.write("<h1>Success!</h1><p>You may close this window and go back to Emacs now. :-)</p>") | ||
|
||
try: | ||
server = HTTPServer(('', 8591), RequestHandler) | ||
server.handle_request() | ||
except KeyboardInterrupt: | ||
server.socket.close() |
Oops, something went wrong.