Welcome to the CHICKEN Scheme pasting service
dynamic scoping woes added by andyjpb on Mon Oct 29 20:05:07 2012
(use uri-common http-client intarweb) (use data-structures) ;export make-oauth-service-provider ; make-oauth-service ; authenticated-call-with-input-request ; with-oauth ; acquire-temporary-credential ; authorize-resource-owner ; acquire-token-credential (define empty-credential '("" . "")) (define (make-oauth-credential identifier secret) (cons identifier secret)) (define token car) (define secret cdr) (define supported-signatures '(plaintext)) ; '(plaintext hmac-sha1 rsa-sha1) (define supported-methods '(POST)) ; '(POST GET) (define supported-transmission '(authorization-header)) ; '(authorization-header request-entity-body query-string) ; Differences between 1.0 and 1.0a ; http://code.google.com/p/oauth/source/diff?spec=svn1058&old=991&r=1058&format=unidiff&path=%2Fspec%2Fcore%2F1.0a%2Foauth-core-1_0a.xml ; + oauth_callback in temporary credential acquisition rather than owner auth ; + oauth_callback_confirmed in temporary credential response ; + presence of oauth_verifier in owner auth callback query string ; + in absence of callback, display of verification code rather than assertion that auth has completed. ; Differences between 1.0a and rfc5849 ; http://tools.ietf.org/html/rfc5849#appendix-A ; + MUST use TLS/SSL with PLAINTEXT ; + nonce and timestamp parameters OPTIONAL when using PLAINTEXT ; + permitted omitting empty oauth_token ; + various other things only relevant for non PLAINTEXT signatures (define supported-versions '(1.0 1.0a rfc5849)) (define (memv? obj lst) (and (memv obj lst) #t)) ; Particulars specified by RFC5849, Section 2 (define (make-oauth-service-provider #!key protocol-version signature-method owner-auth-url credential-request-url (credential-request-method 'POST) token-request-url (token-request-method 'POST) (transmission-method 'authorization-header)) (assert (memv protocol-version supported-versions)) (assert (string? credential-request-url)) (assert (string? owner-auth-url)) (assert (string? token-request-url)) (assert (memv signature-method supported-signatures)) (assert (memv credential-request-method supported-methods)) (assert (memv token-request-method supported-methods)) (assert (memv transmission-method supported-transmission)) (let ((credential-request-url (uri-reference credential-request-url)) (owner-auth-url (uri-reference owner-auth-url)) (token-request-url (uri-reference token-request-url))) (assert (eqv? 'https (uri-scheme credential-request-url))) (assert (eqv? 'https (uri-scheme token-request-url))) `((protocol-version . ,protocol-version) (signature-method . ,signature-method) (credential-request-req . ,(make-request uri: credential-request-url method: credential-request-method)) (owner-auth-url . ,owner-auth-url) (token-request-req . ,(make-request uri: token-request-url method: token-request-method)) (confirms-callback . ,(memv? protocol-version '(1.0a rfc5849))) (callback-on-credential . ,(memv? protocol-version '(1.0a rfc5849))) (callback-on-owner-auth . ,(memv? protocol-version '(1.0))) (sends-oauth-verifier . ,(memv? protocol-version '(1.0a rfc5849))) ))) (define (make-oauth-service #!key service client-credential) (alist-cons 'client-credential client-credential service)) (define (authenticated-call-with-input-request service protocol-parameters token-credential uri-or-request writer reader) (let* ((uri (cond ((uri? uri-or-request) uri-or-request) ; stolen from http-client ((string? uri-or-request) (uri-reference uri-or-request)) (else (request-uri uri-or-request)))) (req (if (request? uri-or-request) ; stolen from http-client uri-or-request (make-request uri: uri method: (if writer 'POST 'GET)))) (body (if (list? writer) writer #f)) ; RFC5849: Section 3.4.1.3.1. Parameter Sources (signature-method (alist-ref 'signature-method service)) (protocol-parameters (append protocol-parameters `((oauth_consumer_key . ,(token (alist-ref 'client-credential service))) (oauth_token . ,(token token-credential)) (oauth_signature_method . ,(string-upcase (symbol->string signature-method))) (oauth_version . ,(exact->inexact (alist-ref 'protocol-version service))) ,@(if (not (eqv? 'plaintext signature-method)) `((oauth_timestamp . "bar") (oauth_nonce . "bar")) '())))) (signature (sign-request req protocol-parameters body service token-credential)) (protocol-parameters (alist-cons 'oauth_signature signature protocol-parameters))) (call-with-input-request (update-request req headers: (headers `((authorization . (#(OAuth ,protocol-parameters)))))) (or body writer) reader))) (define %encode uri-encode-string) (define (sign-request request protocol-parameters body service token-credential) ; For hmac-sha1 and rsa-sha1 we must assert the http request entity-body conditions of RFC5849: Section 3.4.1.3.1 (let ((signature-method (alist-ref 'signature-method service))) (case signature-method ((plaintext) ; RFC5849: Section 3.4.4 - MUST be used with TLS/SSL. ;(assert (eqv? 'https (uri-scheme (request-uri request)))) (let ((client-credential (alist-ref 'client-credential service))) (conc (%encode (secret client-credential)) "&" (%encode (secret token-credential))))) (else (abort (conc signature-method " signature method not implemented!")))))) ; This generates an "Authorization: Oauth ..." header. The OAuth spec calls for ; "Authorization: OAuth ..." but HTTP declares it as case-insensitive: ; ; "HTTP provides a simple challenge-response authentication mechanism that MAY ; be used by a server to challenge a client request and by a client to provide ; authentication information. It uses an extensible, case-insensitive token to ; identify the authentication scheme, followed by a comma-separated list of ; attribute-value pairs which carry the parameters necessary for achieving ; authentication via that scheme." ; -- RFC2617, section 1.2 ; (authorization-param-subunparsers (alist-cons 'OAuth (lambda (params) (string-intersperse (map (lambda (p) (conc (%encode (symbol->string (car p))) "=\"" (%encode (->string (cdr p))) "\"")) params) ", ")) (authorization-param-subunparsers))) (define (with-oauth service token-credential thunk) (fluid-let ((call-with-input-request (cut authenticated-call-with-input-request service '() token-credential <> <> <>))) (thunk))) (define (acquire-temporary-credential service #!optional callback-url) (let* ((callback-on-credential (alist-ref 'callback-on-credential service)) (_ (if (and (not callback-on-credential) callback-url) (abort "This service does not accept callback-url during temporary credential acquisition."))) (resp (nth-value 0 (authenticated-call-with-input-request service `(,@(if (and callback-on-credential callback-url) `((oauth_callback . ,callback-url)) '())) empty-credential (alist-ref 'credential-request-req service) "" ; forces POST even if credential-request-req isn't a request object. (lambda (p) (form-urldecode (read-string #f p)))))) ; string that stray \r ??? (credential (make-oauth-credential (alist-ref 'oauth_token resp eqv? "") (alist-ref 'oauth_token_secret resp eqv? ""))) (_ (if (alist-ref 'confirms-callback service) (assert (alist-ref 'oauth_callback_confirmed resp)))) ; RFC5849: Section 2.1 (rest (remove (lambda (e) (memv (car e) '(oauth_token_secret oauth_token oauth_callback_confirmed))) resp))) (values credential rest))) (define (authorize-resource-owner service temporary-credential #!optional callback-url) (let* ((callback-on-owner-auth (alist-ref 'callback-on-owner-auth service)) (_ (if (and (not callback-on-owner-auth) callback-url) (abort "This service does not accept callback-url during resource owner authorization."))) (callback-url (cond ((uri? callback-url) (uri->string callback-url)) (else callback-url))) (uri (alist-ref 'owner-auth-url service)) (query (uri-query uri)) (query (alist-cons 'oauth_token (token temporary-credential) query)) (query (if (and callback-on-owner-auth callback-url) (alist-cons 'oauth_callback callback-url query) query)) (uri (update-uri uri query: query))) uri)) ; return a uri object that the user can be redirected to. (define (acquire-token-credential service temporary-credential #!optional verifier) (let* ((_ (if (alist-ref 'sends-oauth-verifier service) (abort "oauth_verifier MUST be supplied!"))) (resp (nth-value 0 (authenticated-call-with-input-request service (if verifier `((oauth_verifier . ,verifier)) '()) temporary-credential (alist-ref 'token-request-req service) ""; forces POST even if token-request-req isn't a request object. (lambda (p) (form-urldecode (read-string #f p)))))) ; string that stray \r ??? (credential (make-oauth-credential (alist-ref 'oauth_token resp eqv? "") (alist-ref 'oauth_token_secret resp eqv? ""))) (rest (remove (lambda (e) (memv (car e) '(oauth_token_secret oauth_token))) resp))) (values credential rest))) (define dropbox (make-oauth-service-provider protocol-version: 1.0 credential-request-url: "https://api.dropbox.com/1/oauth/request_token" owner-auth-url: "https://www.dropbox.com/1/oauth/authorize" token-request-url: "https://api.dropbox.com/1/oauth/access_token" signature-method: 'plaintext)) ;(define mydropbox (make-oauth-service service: dropbox client-credential: (make-oauth-credential "p" "q"))) ; (with-oauth mydropbox '("a" . "b") (lambda () (call-with-input-request "http://www.google.com" #f (cut read-string #f <>))