Skip to content

Commit

Permalink
First commit.
Browse files Browse the repository at this point in the history
  • Loading branch information
Daniel Fernandes Martins committed Nov 30, 2014
0 parents commit 438b7f7
Show file tree
Hide file tree
Showing 6 changed files with 642 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
playground.el*
.elc
22 changes: 22 additions & 0 deletions COPYING
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.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Spotify.el

TODO.
220 changes: 220 additions & 0 deletions oauth2.el
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
21 changes: 21 additions & 0 deletions oauth2_callback_server.py
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()
Loading

0 comments on commit 438b7f7

Please sign in to comment.