From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Julien Danjou Newsgroups: gmane.emacs.devel Subject: Re: OAuth2 implementation in Elisp Date: Fri, 23 Sep 2011 00:15:19 +0200 Message-ID: <87k490jkaw.fsf@keller.adm.naquadah.org> References: <87sjnojl7j.fsf@keller.adm.naquadah.org> <4E7BAFA4.8090800@dogan.se> <4E7BAFE2.2090102@dogan.se> NNTP-Posting-Host: lo.gmane.org Mime-Version: 1.0 Content-Type: multipart/signed; boundary="==-=-="; micalg=pgp-sha1; protocol="application/pgp-signature" X-Trace: dough.gmane.org 1316729732 11409 80.91.229.12 (22 Sep 2011 22:15:32 GMT) X-Complaints-To: usenet@dough.gmane.org NNTP-Posting-Date: Thu, 22 Sep 2011 22:15:32 +0000 (UTC) Cc: emacs-devel@gnu.org To: Deniz Dogan Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Fri Sep 23 00:15:28 2011 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([140.186.70.17]) by lo.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1R6rYF-000288-8F for ged-emacs-devel@m.gmane.org; Fri, 23 Sep 2011 00:15:27 +0200 Original-Received: from localhost ([::1]:45700 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1R6rYE-0005c7-CS for ged-emacs-devel@m.gmane.org; Thu, 22 Sep 2011 18:15:26 -0400 Original-Received: from eggs.gnu.org ([140.186.70.92]:60646) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1R6rYB-0005br-FM for emacs-devel@gnu.org; Thu, 22 Sep 2011 18:15:24 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1R6rYA-0006IV-Ei for emacs-devel@gnu.org; Thu, 22 Sep 2011 18:15:23 -0400 Original-Received: from prometheus.naquadah.org ([212.85.154.174]:35483 helo=mx1.naquadah.org) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1R6rYA-0006IK-5m for emacs-devel@gnu.org; Thu, 22 Sep 2011 18:15:22 -0400 Original-Received: from keller.adm.naquadah.org (AMontsouris-651-1-106-83.w83-202.abo.wanadoo.fr [83.202.161.83]) (using TLSv1 with cipher DHE-RSA-AES128-SHA (128/128 bits)) (No client certificate requested) by mx1.naquadah.org (Postfix) with ESMTPSA id E73495C1B9; Fri, 23 Sep 2011 00:15:20 +0200 (CEST) Mail-Followup-To: Deniz Dogan , emacs-devel@gnu.org In-Reply-To: <4E7BAFE2.2090102@dogan.se> (Deniz Dogan's message of "Fri, 23 Sep 2011 00:00:02 +0200") User-Agent: Gnus/5.110018 (No Gnus v0.18) Emacs/24.0.50 (gnu/linux) X-detected-operating-system: by eggs.gnu.org: GNU/Linux 2.6 (newer, 3) X-Received-From: 212.85.154.174 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:144207 Archived-At: --==-=-= Content-Type: multipart/mixed; boundary="=-=-=" --=-=-= Content-Type: text/plain On Fri, Sep 23 2011, Deniz Dogan wrote: > Uh, where is the code by the way? Well, I did not published it yet, since I don't want to set up a Git repository if it's OK to merge this into the trunk. And I usually don't like sending code by e-mail, since there's always a chance someone will grab old code from this to-be-outdated mail in the future. But well, here it is. :) --=-=-= Content-Type: application/emacs-lisp Content-Disposition: attachment; filename=oauth2.el Content-Transfer-Encoding: quoted-printable ;;; oauth2.el --- OAuth 2.0 Authorization Protocol ;; Copyright (C) 2011 Julien Danjou ;; Author: Julien Danjou ;; Keywords: comm ;; This file is NOT 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 . ;;; 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 'plstore) (defun oauth2-request-authorization (auth-url client-id &optional scope sta= te) "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=3D" client-id "&response_type=3Dcode&redirect_uri=3Durn:ietf:wg:oau= th:2.0:oob" (if scope (concat "&scope=3D" (url-hexify-string scop= e)) "") (if state (concat "&state=3D" state) ""))) (read-string "Enter the code your browser displayed: ")) (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-f= orm-urlencoded")))) (with-current-buffer (url-retrieve-synchronously url) (let ((data (oauth2-request-access-parse))) (kill-buffer (current-buffer)) data)))) (defstruct oauth2-token plstore client-id client-secret access-token refresh-token token-url) (defun oauth2-request-access (token-url client-id client-secret code) "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=3D" client-id "&client_secret=3D" client-s= ecret "&code=3D" code "&redirect_uri=3Durn:ietf:wg= :oauth:2.0:oob&grant_type=3Dauthorization_code")))) (make-oauth2-token :client-id client-id :client-secret client-secret :access-token (aget result 'access_token) :refresh-token (aget result 'refresh_token) :token-url token-url)))) ;;;###autoload (defun oauth2-refresh-access (token) "Refresh OAuth access TOKEN. TOKEN should be obtained with `oauth2-request-access'." (setf (oauth2-token-access-token token) (aget (oauth2-make-access-request (oauth2-token-token-url token) (concat "client_id=3D" (oauth2-to= ken-client-id token) "&client_secret=3D" (oaut= h2-token-client-secret token) "&refresh_token=3D" (oaut= h2-token-refresh-token token) "&grant_type=3Drefresh_to= ken")) 'access_token)) ;; If the token has a plstore, update it (let ((plstore (oauth2-token-plstore token))) (when plstore (plstore-put plstore id nil `(:access-token ,(oauth2-token-access-token token) :refresh-token ,(oauth2-token-refresh-token token))) (plstore-save plstore))) token) ;;;###autoload (defun oauth2-auth (auth-url token-url client-id client-secret &optional sc= ope state) "Authenticate application via OAuth2." (oauth2-request-access token-url client-id client-secret (oauth2-request-authorization auth-url client-id scope state))) (defcustom oauth2-token-file (concat user-emacs-directory "oauth2.plstore") "File path where store OAuth tokens." :group 'oauth2 :type 'file) ;;;###autoload (defun oauth2-auth-and-store (auth-url token-url resource-url client-id cli= ent-secret) "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 (secure-hash 'md5 (concat 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 :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) (let ((token (oauth2-auth auth-url token-url client-id client-secret resource-url))) ;; Set the plstore (setf (oauth2-token-plstore token) plstore) (plstore-put plstore id nil `(:access-token ,(oauth2-token-access-token token) :refresh-token ,(oauth2-token-refresh-token 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=3D" (oauth2-token-access-token token))) ;;;###autoload (defun oauth2-url-retrieve-synchronously (token url) "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 for `url' to think it= 's done. (setq success t))) (let ((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) url-buffer))))) (provide 'oauth2) ;;; oauth2.el ends here --=-=-= Content-Type: text/plain Content-Transfer-Encoding: quoted-printable =2D-=20 Julien Danjou --=-=-=-- --==-=-= Content-Type: application/pgp-signature -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.11 (GNU/Linux) iQIcBAEBAgAGBQJOe7N4AAoJEGEbqVCLeKXCXscP/Rr2E2NuBilz2bX0IIoZwcdh Fa0UD2LBSamHmGuoYT3YXtVbq0PSN9RWGXRt6yh1kIGVt5aC17fXngoO2ncuss7K kYFn2eNgeO1otptT+cf3XUZiXKoE1lC7HMnMA72mSeODLXisHQeIU0/cRdeBYtQb ch7xE3C9LZuIMzgI8yRtuvczM0aLWy4Ce9kLljYFdC+DsitjYhBJrmvUzQVsyxVG UHszy9ffaXDKS42Ozdn2EInPLmu8jgyaZwg4LSt2jUj9t4Xpor6U7ce3kvsgvD6w FZFbEhJcXezKtuncD/fER20FxIv6BgzcIBofJuY7Xpwjt/+DkHt8wgSXiIPlgzX3 O9NYE62M6m7YNClZQhzni+LY8KmDFvoraZfleHQntz0M3V/iOBvZEbv6Gq92Vy/q Sms2MCqQufE4xpNZaR+lrBi63OgLjh5AUOkummAtQNULigIIQ+qKZL1DlAKrmpXS 7FNdyTQF9O85R8wmmUVVdij1g1/Qnc7UaKo1AfcLTJna5u9dscS3KAIExwakzYAz PjMHX1sQk4UUn0yQXE3qlACIWeTbxU/pRyLyo7fIhXql8ZQhT51JcegrbRfATQKs fMNmgrMhboQsXtvqinZdtAcNR5J/N5KsUjLCxFU48fYIRcy4YKu3V0VMaOWJ+j3X yQH/cQzZZxPUCQdcG30Q =bSs4 -----END PGP SIGNATURE----- --==-=-=--