first commit
This commit is contained in:
@@ -0,0 +1,350 @@
|
||||
from authlib.common.urls import add_params_to_uri
|
||||
from authlib.common.urls import is_valid_url
|
||||
|
||||
from .base_server import BaseServer
|
||||
from .errors import AccessDeniedError
|
||||
from .errors import InvalidClientError
|
||||
from .errors import InvalidRequestError
|
||||
from .errors import InvalidTokenError
|
||||
from .errors import MethodNotAllowedError
|
||||
from .errors import MissingRequiredParameterError
|
||||
from .errors import OAuth1Error
|
||||
|
||||
|
||||
class AuthorizationServer(BaseServer):
|
||||
TOKEN_RESPONSE_HEADER = [
|
||||
("Content-Type", "application/x-www-form-urlencoded"),
|
||||
("Cache-Control", "no-store"),
|
||||
("Pragma", "no-cache"),
|
||||
]
|
||||
|
||||
TEMPORARY_CREDENTIALS_METHOD = "POST"
|
||||
|
||||
def _get_client(self, request):
|
||||
client = self.get_client_by_id(request.client_id)
|
||||
request.client = client
|
||||
return client
|
||||
|
||||
def create_oauth1_request(self, request):
|
||||
raise NotImplementedError()
|
||||
|
||||
def handle_response(self, status_code, payload, headers):
|
||||
raise NotImplementedError()
|
||||
|
||||
def handle_error_response(self, error):
|
||||
return self.handle_response(
|
||||
error.status_code, error.get_body(), error.get_headers()
|
||||
)
|
||||
|
||||
def validate_temporary_credentials_request(self, request):
|
||||
"""Validate HTTP request for temporary credentials."""
|
||||
# The client obtains a set of temporary credentials from the server by
|
||||
# making an authenticated (Section 3) HTTP "POST" request to the
|
||||
# Temporary Credential Request endpoint (unless the server advertises
|
||||
# another HTTP request method for the client to use).
|
||||
if request.method.upper() != self.TEMPORARY_CREDENTIALS_METHOD:
|
||||
raise MethodNotAllowedError()
|
||||
|
||||
# REQUIRED parameter
|
||||
if not request.client_id:
|
||||
raise MissingRequiredParameterError("oauth_consumer_key")
|
||||
|
||||
# REQUIRED parameter
|
||||
oauth_callback = request.redirect_uri
|
||||
if not request.redirect_uri:
|
||||
raise MissingRequiredParameterError("oauth_callback")
|
||||
|
||||
# An absolute URI or
|
||||
# other means (the parameter value MUST be set to "oob"
|
||||
if oauth_callback != "oob" and not is_valid_url(oauth_callback):
|
||||
raise InvalidRequestError('Invalid "oauth_callback" value')
|
||||
|
||||
client = self._get_client(request)
|
||||
if not client:
|
||||
raise InvalidClientError()
|
||||
|
||||
self.validate_timestamp_and_nonce(request)
|
||||
self.validate_oauth_signature(request)
|
||||
return request
|
||||
|
||||
def create_temporary_credentials_response(self, request=None):
|
||||
"""Validate temporary credentials token request and create response
|
||||
for temporary credentials token. Assume the endpoint of temporary
|
||||
credentials request is ``https://photos.example.net/initiate``:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /initiate HTTP/1.1
|
||||
Host: photos.example.net
|
||||
Authorization: OAuth realm="Photos",
|
||||
oauth_consumer_key="dpf43f3p2l4k3l03",
|
||||
oauth_signature_method="HMAC-SHA1",
|
||||
oauth_timestamp="137131200",
|
||||
oauth_nonce="wIjqoS",
|
||||
oauth_callback="http%3A%2F%2Fprinter.example.com%2Fready",
|
||||
oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"
|
||||
|
||||
The server validates the request and replies with a set of temporary
|
||||
credentials in the body of the HTTP response:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
oauth_token=hh5s93j4hdidpola&oauth_token_secret=hdhd0244k9j7ao03&
|
||||
oauth_callback_confirmed=true
|
||||
|
||||
:param request: OAuth1Request instance.
|
||||
:returns: (status_code, body, headers)
|
||||
"""
|
||||
try:
|
||||
request = self.create_oauth1_request(request)
|
||||
self.validate_temporary_credentials_request(request)
|
||||
except OAuth1Error as error:
|
||||
return self.handle_error_response(error)
|
||||
|
||||
credential = self.create_temporary_credential(request)
|
||||
payload = [
|
||||
("oauth_token", credential.get_oauth_token()),
|
||||
("oauth_token_secret", credential.get_oauth_token_secret()),
|
||||
("oauth_callback_confirmed", True),
|
||||
]
|
||||
return self.handle_response(200, payload, self.TOKEN_RESPONSE_HEADER)
|
||||
|
||||
def validate_authorization_request(self, request):
|
||||
"""Validate the request for resource owner authorization."""
|
||||
if not request.token:
|
||||
raise MissingRequiredParameterError("oauth_token")
|
||||
|
||||
credential = self.get_temporary_credential(request)
|
||||
if not credential:
|
||||
raise InvalidTokenError()
|
||||
|
||||
# assign credential for later use
|
||||
request.credential = credential
|
||||
return request
|
||||
|
||||
def create_authorization_response(self, request, grant_user=None):
|
||||
"""Validate authorization request and create authorization response.
|
||||
Assume the endpoint for authorization request is
|
||||
``https://photos.example.net/authorize``, the client redirects Jane's
|
||||
user-agent to the server's Resource Owner Authorization endpoint to
|
||||
obtain Jane's approval for accessing her private photos::
|
||||
|
||||
https://photos.example.net/authorize?oauth_token=hh5s93j4hdidpola
|
||||
|
||||
The server requests Jane to sign in using her username and password
|
||||
and if successful, asks her to approve granting 'printer.example.com'
|
||||
access to her private photos. Jane approves the request and her
|
||||
user-agent is redirected to the callback URI provided by the client
|
||||
in the previous request (line breaks are for display purposes only)::
|
||||
|
||||
http://printer.example.com/ready?
|
||||
oauth_token=hh5s93j4hdidpola&oauth_verifier=hfdp7dh39dks9884
|
||||
|
||||
:param request: OAuth1Request instance.
|
||||
:param grant_user: if granted, pass the grant user, otherwise None.
|
||||
:returns: (status_code, body, headers)
|
||||
"""
|
||||
request = self.create_oauth1_request(request)
|
||||
# authorize endpoint should try catch this error
|
||||
self.validate_authorization_request(request)
|
||||
|
||||
temporary_credentials = request.credential
|
||||
redirect_uri = temporary_credentials.get_redirect_uri()
|
||||
if not redirect_uri or redirect_uri == "oob":
|
||||
client_id = temporary_credentials.get_client_id()
|
||||
client = self.get_client_by_id(client_id)
|
||||
redirect_uri = client.get_default_redirect_uri()
|
||||
|
||||
if grant_user is None:
|
||||
error = AccessDeniedError()
|
||||
location = add_params_to_uri(redirect_uri, error.get_body())
|
||||
return self.handle_response(302, "", [("Location", location)])
|
||||
|
||||
request.user = grant_user
|
||||
verifier = self.create_authorization_verifier(request)
|
||||
|
||||
params = [("oauth_token", request.token), ("oauth_verifier", verifier)]
|
||||
location = add_params_to_uri(redirect_uri, params)
|
||||
return self.handle_response(302, "", [("Location", location)])
|
||||
|
||||
def validate_token_request(self, request):
|
||||
"""Validate request for issuing token."""
|
||||
if not request.client_id:
|
||||
raise MissingRequiredParameterError("oauth_consumer_key")
|
||||
|
||||
client = self._get_client(request)
|
||||
if not client:
|
||||
raise InvalidClientError()
|
||||
|
||||
if not request.token:
|
||||
raise MissingRequiredParameterError("oauth_token")
|
||||
|
||||
token = self.get_temporary_credential(request)
|
||||
if not token:
|
||||
raise InvalidTokenError()
|
||||
|
||||
verifier = request.oauth_params.get("oauth_verifier")
|
||||
if not verifier:
|
||||
raise MissingRequiredParameterError("oauth_verifier")
|
||||
|
||||
if not token.check_verifier(verifier):
|
||||
raise InvalidRequestError('Invalid "oauth_verifier"')
|
||||
|
||||
request.credential = token
|
||||
self.validate_timestamp_and_nonce(request)
|
||||
self.validate_oauth_signature(request)
|
||||
return request
|
||||
|
||||
def create_token_response(self, request):
|
||||
"""Validate token request and create token response. Assuming the
|
||||
endpoint of token request is ``https://photos.example.net/token``,
|
||||
the callback request informs the client that Jane completed the
|
||||
authorization process. The client then requests a set of token
|
||||
credentials using its temporary credentials (over a secure Transport
|
||||
Layer Security (TLS) channel):
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
POST /token HTTP/1.1
|
||||
Host: photos.example.net
|
||||
Authorization: OAuth realm="Photos",
|
||||
oauth_consumer_key="dpf43f3p2l4k3l03",
|
||||
oauth_token="hh5s93j4hdidpola",
|
||||
oauth_signature_method="HMAC-SHA1",
|
||||
oauth_timestamp="137131201",
|
||||
oauth_nonce="walatlh",
|
||||
oauth_verifier="hfdp7dh39dks9884",
|
||||
oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"
|
||||
|
||||
The server validates the request and replies with a set of token
|
||||
credentials in the body of the HTTP response:
|
||||
|
||||
.. code-block:: http
|
||||
|
||||
HTTP/1.1 200 OK
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
oauth_token=nnch734d00sl2jdk&oauth_token_secret=pfkkdhi9sl3r4s00
|
||||
|
||||
:param request: OAuth1Request instance.
|
||||
:returns: (status_code, body, headers)
|
||||
"""
|
||||
try:
|
||||
request = self.create_oauth1_request(request)
|
||||
except OAuth1Error as error:
|
||||
return self.handle_error_response(error)
|
||||
|
||||
try:
|
||||
self.validate_token_request(request)
|
||||
except OAuth1Error as error:
|
||||
self.delete_temporary_credential(request)
|
||||
return self.handle_error_response(error)
|
||||
|
||||
credential = self.create_token_credential(request)
|
||||
payload = [
|
||||
("oauth_token", credential.get_oauth_token()),
|
||||
("oauth_token_secret", credential.get_oauth_token_secret()),
|
||||
]
|
||||
self.delete_temporary_credential(request)
|
||||
return self.handle_response(200, payload, self.TOKEN_RESPONSE_HEADER)
|
||||
|
||||
def create_temporary_credential(self, request):
|
||||
"""Generate and save a temporary credential into database or cache.
|
||||
A temporary credential is used for exchanging token credential. This
|
||||
method should be re-implemented::
|
||||
|
||||
def create_temporary_credential(self, request):
|
||||
oauth_token = generate_token(36)
|
||||
oauth_token_secret = generate_token(48)
|
||||
temporary_credential = TemporaryCredential(
|
||||
oauth_token=oauth_token,
|
||||
oauth_token_secret=oauth_token_secret,
|
||||
client_id=request.client_id,
|
||||
redirect_uri=request.redirect_uri,
|
||||
)
|
||||
# if the credential has a save method
|
||||
temporary_credential.save()
|
||||
return temporary_credential
|
||||
|
||||
:param request: OAuth1Request instance
|
||||
:return: TemporaryCredential instance
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_temporary_credential(self, request):
|
||||
"""Get the temporary credential from database or cache. A temporary
|
||||
credential should share the same methods as described in models of
|
||||
``TemporaryCredentialMixin``::
|
||||
|
||||
def get_temporary_credential(self, request):
|
||||
key = "a-key-prefix:{}".format(request.token)
|
||||
data = cache.get(key)
|
||||
# TemporaryCredential shares methods from TemporaryCredentialMixin
|
||||
return TemporaryCredential(data)
|
||||
|
||||
:param request: OAuth1Request instance
|
||||
:return: TemporaryCredential instance
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def delete_temporary_credential(self, request):
|
||||
"""Delete temporary credential from database or cache. For instance,
|
||||
if temporary credential is saved in cache::
|
||||
|
||||
def delete_temporary_credential(self, request):
|
||||
key = "a-key-prefix:{}".format(request.token)
|
||||
cache.delete(key)
|
||||
|
||||
:param request: OAuth1Request instance
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_authorization_verifier(self, request):
|
||||
"""Create and bind ``oauth_verifier`` to temporary credential. It
|
||||
could be re-implemented in this way::
|
||||
|
||||
def create_authorization_verifier(self, request):
|
||||
verifier = generate_token(36)
|
||||
|
||||
temporary_credential = request.credential
|
||||
user_id = request.user.id
|
||||
|
||||
temporary_credential.user_id = user_id
|
||||
temporary_credential.oauth_verifier = verifier
|
||||
# if the credential has a save method
|
||||
temporary_credential.save()
|
||||
|
||||
# remember to return the verifier
|
||||
return verifier
|
||||
|
||||
:param request: OAuth1Request instance
|
||||
:return: A string of ``oauth_verifier``
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def create_token_credential(self, request):
|
||||
"""Create and save token credential into database. This method would
|
||||
be re-implemented like this::
|
||||
|
||||
def create_token_credential(self, request):
|
||||
oauth_token = generate_token(36)
|
||||
oauth_token_secret = generate_token(48)
|
||||
temporary_credential = request.credential
|
||||
|
||||
token_credential = TokenCredential(
|
||||
oauth_token=oauth_token,
|
||||
oauth_token_secret=oauth_token_secret,
|
||||
client_id=temporary_credential.get_client_id(),
|
||||
user_id=temporary_credential.get_user_id(),
|
||||
)
|
||||
# if the credential has a save method
|
||||
token_credential.save()
|
||||
return token_credential
|
||||
|
||||
:param request: OAuth1Request instance
|
||||
:return: TokenCredential instance
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
Reference in New Issue
Block a user