PageRenderTime 45ms CodeModel.GetById 24ms RepoModel.GetById 1ms app.codeStats 0ms

/src/core/service-provider.lisp

http://github.com/skypher/cl-oauth
Lisp | 264 lines | 195 code | 43 blank | 26 comment | 2 complexity | 5dc6aca298ce5fd99076bdd16cbeaf61 MD5 | raw file
Possible License(s): LGPL-3.0
  1. (in-package :oauth)
  2. (defvar *protocol-version* :1.0)
  3. ;;;; Service provider infrastructure
  4. ;;;; TODO: need to store application-specific data somewhere.
  5. (defun finalize-callback-uri (request-token)
  6. "Prepares the callback URI of REQUEST-TOKEN for
  7. redirection."
  8. (let ((uri (request-token-callback-uri request-token)))
  9. (setf (puri:uri-query uri)
  10. (concatenate 'string (or (puri:uri-query uri) "")
  11. (if (puri:uri-query uri) "&" "")
  12. "oauth_token="
  13. (url-encode (token-key request-token))
  14. "&oauth_verifier="
  15. (url-encode (request-token-verification-code request-token))))
  16. uri))
  17. ;;; Consumer management
  18. (defvar *registered-consumers* (make-hash-table :test #'equalp))
  19. (defmethod register-token ((token consumer-token))
  20. (setf (gethash (token-key token) *registered-consumers*) token)
  21. token)
  22. (defmethod unregister-token ((token consumer-token))
  23. (remhash (token-key token) *registered-consumers*))
  24. (defun get-consumer-token (key)
  25. (gethash key *registered-consumers*))
  26. (defmacro ignore-oauth-errors (&body body)
  27. `(handler-case (progn ,@body)
  28. (http-error (condition) (values nil condition))))
  29. ;;; signature checking
  30. (defun check-signature ()
  31. (unless (equalp (parameter "oauth_signature_method") "HMAC-SHA1")
  32. (raise-error 'bad-request "Signature method not passed or different from HMAC-SHA1"))
  33. (let* ((supplied-signature (gethash (request) *signature-cache*))
  34. ;; TODO: do not bluntly ignore all errors. Factor out into GET-TOKEN
  35. (consumer-secret (ignore-errors
  36. (token-secret
  37. (get-consumer-token (parameter "oauth_consumer_key")))))
  38. (token-secret (ignore-errors
  39. (token-secret (or (ignore-oauth-errors (get-supplied-request-token))
  40. (ignore-oauth-errors (get-supplied-access-token)))))))
  41. (unless supplied-signature
  42. (raise-error 'bad-request "This request is not signed"))
  43. (unless consumer-secret
  44. (raise-error 'unauthorized "Invalid consumer"))
  45. ;; now calculate the signature and check for match
  46. (let* ((signature-base-string (signature-base-string))
  47. (hmac-key (hmac-key consumer-secret token-secret))
  48. (signature (hmac-sha1 signature-base-string hmac-key))
  49. (encoded-signature (encode-signature signature nil)))
  50. (unless (equal encoded-signature supplied-signature)
  51. (format t "calculated: ~S / supplied: ~S~%" encoded-signature supplied-signature)
  52. (raise-error 'unauthorized "Invalid signature")))
  53. t))
  54. ;;; nonce and timestamp checking
  55. (defun check-nonce-and-timestamp (consumer-token)
  56. ;; TODO: nonce checking
  57. (unless (parameter "oauth_timestamp")
  58. (raise-error 'bad-request "Missing Timestamp"))
  59. (let ((timestamp (ignore-errors (parse-integer (parameter "oauth_timestamp"))))
  60. (nonce (parameter "oauth_nonce")))
  61. (unless timestamp
  62. (raise-error 'unauthorized "Malformed Timestamp"))
  63. (unless nonce
  64. (raise-error 'bad-request "Missing nonce"))
  65. (unless (>= timestamp (consumer-token-last-timestamp consumer-token))
  66. (raise-error 'unauthorized "Invalid timestamp"))
  67. t))
  68. ;;; version checking
  69. (defun check-version ()
  70. (let ((version (parameter "oauth_version")))
  71. (unless (member version '("1.0" nil) :test #'equalp)
  72. (raise-error 'bad-request "Not prepared to handle OAuth version other than 1.0" version))
  73. t))
  74. ;;; verification code checking
  75. (defun check-verification-code ()
  76. (unless (equal (parameter "oauth_verifier")
  77. (request-token-verification-code (get-supplied-request-token)))
  78. (raise-error 'unauthorized "Invalid verification code"))
  79. t)
  80. ;;; misc
  81. (defun get-supplied-consumer-token ()
  82. (let ((consumer-key (parameter "oauth_consumer_key")))
  83. (unless consumer-key
  84. (raise-error 'bad-request "Consumer key not supplied"))
  85. (let ((consumer-token (get-consumer-token consumer-key)))
  86. (unless consumer-token
  87. (raise-error 'unauthorized "Can't identify Consumer"))
  88. consumer-token)))
  89. (defun get-supplied-callback-uri (&key allow-oob-callback-p
  90. (allow-none (eq *protocol-version* :1.0)))
  91. (let ((callback (parameter "oauth_callback")))
  92. (cond
  93. ((and (not allow-none) (not callback))
  94. (raise-error 'bad-request "No callback supplied"))
  95. ((and (not allow-oob-callback-p) (equal callback "oob"))
  96. (raise-error 'bad-request "Not prepared for an OOB callback setup!"))
  97. (t
  98. callback))))
  99. ;;; request token management
  100. (defvar *issued-request-tokens* (make-hash-table :test #'equalp))
  101. (defmethod register-token ((token request-token))
  102. ;; TODO: already registered?
  103. (setf (gethash (token-key token) *issued-request-tokens*) token))
  104. (defmethod unregister-token ((token request-token))
  105. (remhash (token-key token) *issued-request-tokens*))
  106. (defun invalidate-request-token (request-token)
  107. (remhash (token-key request-token) *issued-request-tokens*))
  108. (defun make-response (alist)
  109. "[5.3]"
  110. (alist->query-string
  111. (mapcar (lambda (cons)
  112. (cons (url-encode (car cons))
  113. (url-encode (cdr cons))))
  114. alist)
  115. :include-leading-ampersand nil))
  116. (defun request-token-response (request-token &rest additional-parameters)
  117. "Respond to a valid request token request. [6.1.2]"
  118. (assert (notany #'oauth-parameter-p additional-parameters))
  119. (make-response
  120. (append
  121. `(("oauth_token" . ,(token-key request-token))
  122. ("oauth_token_secret" . ,(token-secret request-token))
  123. ("oauth_callback_confirmed" . "true"))
  124. additional-parameters)))
  125. (defun validate-request-token-request (&key (request-token-ctor #'make-request-token)
  126. allow-oob-callback-p)
  127. "Check whether REQUEST is a valid request token request.
  128. Returns the supplied Consumer callback (a PURI:URI) or NIL if
  129. the callback is supposed to be transferred oob. [6.1.1]"
  130. (protocol-assert (>= (length (normalized-parameters))
  131. (case *protocol-version*
  132. ;; callbacks were introduced in 1.0a
  133. (1.0 4)
  134. (t 6 5))))
  135. (check-version)
  136. (check-signature)
  137. (let ((consumer-token (get-supplied-consumer-token)))
  138. (check-nonce-and-timestamp consumer-token)
  139. (let* ((callback-uri (get-supplied-callback-uri :allow-oob-callback-p allow-oob-callback-p
  140. :allow-none t))
  141. (request-token (funcall request-token-ctor :consumer consumer-token
  142. :callback-uri (when callback-uri
  143. (puri:parse-uri callback-uri))
  144. :user-data (remove-oauth-parameters (normalized-parameters)))))
  145. (register-token request-token)
  146. request-token)))
  147. (defun get-supplied-request-token (&key check-verification-code-p)
  148. "Utility function that extracts the Consumer-supplied request token
  149. from a list of normalized parameters. Guards against non-existing
  150. and unknown tokens. Returns the request token on success."
  151. ;; TODO: check whether the supplied token matches the Consumer key
  152. (let ((request-token-key (parameter "oauth_token")))
  153. ;; check if the Consumer supplied a request token
  154. (unless request-token-key
  155. (raise-error 'bad-request "Missing request token"))
  156. ;; check if the supplied request token is known to us
  157. (let ((request-token (gethash request-token-key *issued-request-tokens*)))
  158. (unless request-token
  159. (raise-error 'unauthorized "Invalid request token"))
  160. (when check-verification-code-p
  161. (check-verification-code))
  162. ;; everything's looking good
  163. request-token)))
  164. ;;; access token management
  165. (defvar *issued-access-tokens* (make-hash-table :test #'equalp))
  166. (defmethod register-token ((token access-token))
  167. (setf (gethash (token-key token) *issued-access-tokens*) token))
  168. (defmethod unregister-token ((token access-token))
  169. (remhash (token-key token) *issued-access-tokens*))
  170. (defun validate-access-token-request (&key (access-token-ctor #'make-access-token))
  171. ;; no user-supplied parameters allowed here, and the
  172. ;; spec forbids duplicate oauth args per section 5.
  173. ;; moreover we don't count the oauth_signature parameter as it isn't
  174. ;; part of the normalized parameter list.
  175. (protocol-assert (multiple-value-call #'between (length (normalized-parameters))
  176. (case *protocol-version*
  177. (1.0 (values 5 6))
  178. (t 6 (values 6 7)))))
  179. (format t "foo~%")
  180. (protocol-assert (null (remove-oauth-parameters (normalized-parameters))))
  181. (format t "bar~%")
  182. (check-version)
  183. (check-signature)
  184. (let* ((request-token (get-supplied-request-token
  185. :check-verification-code-p (not (eq *protocol-version* :1.0))))
  186. (consumer (token-consumer request-token)))
  187. (check-nonce-and-timestamp consumer)
  188. (let ((access-token (funcall access-token-ctor :consumer consumer)))
  189. (register-token access-token)
  190. (prog1
  191. access-token
  192. (invalidate-request-token request-token)))))
  193. (defun access-token-response (access-token &rest additional-parameters)
  194. (declare (ignore additional-parameters)) ; TODO not supported yet
  195. (url-encode (alist->query-string
  196. `(("oauth_token" . ,(token-key access-token))
  197. ("oauth_token_secret" . ,(token-secret access-token))))))
  198. ;;; protected resource access management [7]
  199. (defun get-supplied-access-token ()
  200. "Utility function that extracts the Consumer-supplied request token
  201. from a list of normalized parameters. Guards against non-existing
  202. and unknown tokens. Returns the request token on success."
  203. ;; TODO: check whether the supplied token matches the Consumer key
  204. (let ((access-token-key (parameter "oauth_token")))
  205. (unless access-token-key
  206. (raise-error 'bad-request "Missing access token"))
  207. ;; check if the supplied access token is known to us
  208. (let ((access-token (gethash access-token-key *issued-access-tokens*)))
  209. (unless access-token
  210. (raise-error 'unauthorized "Invalid access token"))
  211. access-token)))
  212. (defun validate-access-token ()
  213. (protocol-assert (>= (length (normalized-parameters)) 6))
  214. (check-version)
  215. (check-signature)
  216. (let ((consumer-token (get-supplied-consumer-token)))
  217. (check-nonce-and-timestamp consumer-token)
  218. (let ((access-token (get-supplied-access-token)))
  219. (unless (eq consumer-token (token-consumer access-token))
  220. (raise-error 'unauthorized "Access token ~S wasn't issued for Consumer ~S" access-token consumer-token))
  221. t)))