Commits

Paul Tan committed 0499c31

Implemented workarounds system.

  • Participants
  • Parent commits 0219744

Comments (0)

Files changed (1)

oauth1client/__init__.py

     return apply_url_query_to_req(req, oauth_params)
 
 
-""" Signature Base String Functions (For Signature Methods) """
+""" Signature Base String Functions. These are implemented according to
+the RFC spec. """
 
 
 def base_string_uri(uri):
     return urlunsplit((scheme, netloc, path, "", ""))
 
 
-def base_string_request_params(uri, oauth_params = None, data = None):
+def base_string_req_params(uri, oauth_params = None, data = None):
     from urllib.parse import urlsplit, parse_qs, urlencode 
     from collections import Mapping
     query = dict((k, v[0]) for k, v in parse_qs(urlsplit(uri).query).items())
     return '&'.join(['{0}={1}'.format(k, v) for k, v in params])
 
 
-def signature_base_string(method, uri, oauth_params = None, data = None):
+def signature_base_string(method, uri, oauth_params = None, data = None, *,
+        base_string_uri=base_string_uri,
+        base_string_req_params=base_string_req_params):
     x = '{0}&{1}&{2}'.format(urlquote(method.upper()), urlquote(base_string_uri(uri)),
-            urlquote(base_string_request_params(uri, oauth_params, data)))
+            urlquote(base_string_req_params(uri, oauth_params, data)))
     return x
 
 
 """ Signature Methods """
 
+class SignatureMethod:
+    """Base Signature Method class. This provides several utility methods
+    which can be useful for implementing Signature Methods. Library users
+    can also override these methods to customize the behaviour of the
+    Signature Methods."""
 
-class PLAINTEXTSignatureMethod:
+    signature_base_string = staticmethod(signature_base_string)
+
+    def __call__(self, req:Request, oauth_params:dict, token_secret:str="") -> str:
+        raise NotImplementedError()
+
+class PLAINTEXTSignatureMethod(SignatureMethod):
     name = "PLAINTEXT"
+
     def __init__(self, client_secret: str):
         self.client_secret = client_secret
+
     def __call__(self, req: Request, oauth_params:dict, token_secret:str = "") -> str:
         return "{0}&{1}".format(urlquote(self.client_secret), urlquote(token_secret))
 
-class HMACSHA1SignatureMethod:
+class HMACSHA1SignatureMethod(SignatureMethod):
     name = "HMAC-SHA1"
+
     def __init__(self, client_secret: str):
         self.client_secret = client_secret
+
     def __call__(self, req: Request, oauth_params:dict, token_secret:str = "") -> str:
         #digest = HMAC-SHA1(key, text)
-        text = signature_base_string(req.method, req.url, oauth_params, req.data)
+        text = self.signature_base_string(req.method, req.url, oauth_params, req.data)
         key = "{0}&{1}".format(urlquote(self.client_secret), urlquote(token_secret))
         x = hmac.new(key.encode('ascii'), text.encode('ascii'), hashlib.sha1)
         return b2a_base64(x.digest())[:-1].decode('ascii')
 
+
 #TODO: RSASHA1SignatureMethod: Use the RSA module?
 
 
 class Config(UserDict):
     def server(self, name, **kwargs):
         """Returns the server with name"""
+        global workarounds
         x = self[name].copy()
+        # Handle Signature Method
         if "signature_method" in x:
             signature_method = x["signature_method"]
         else:
             version = x["version"]
         else:
             version = "rfc"
+        #Workaround system
+        if "workarounds" in x:
+            _workarounds = x["workarounds"].split() #Space separated (don't want spaces in names)
+        else:
+            _workarounds = []
         if "_pyoauth1client_class" in x:
             module, _, cls = x["_pyoauth1client_class"].rpartition(".")
             module = __import__(module, fromlist = [cls])
         else:
             raise ValueError("Unknown oAuth version {!r}".format(version))
         x = dict((k, v) for k, v in x.items() if not (k.startswith("_") or 
-            k in ["client_secret", "resources", "version"]))
+            k in ["client_secret", "resources", "version", "workarounds"]))
         x["name"] = name
         x.update(kwargs)
         y = cls(**x)
+        #Apply workarounds
+        for x in _workarounds:
+            workarounds[x](y)
         return y
 
     def url(self, url, **kwargs):
         """Returns the server which has a resource under URL"""
         def check(x, url):
             if "resources" in x:
-                y = x["resources"].split("|")
+                y = x["resources"].split() #Whitespace delimeted (whitespace is illegal character in URLs)
                 return any(url.startswith(x) for x in y)
             else:
                 return False
 
 
 
+""" Workaround System """
+
+def workaround_base_string_req_params_keys_no_encode(provider):
+    """Some providers misread the oAuth 1.0a spec and implement
+    base string request params construction such that the
+    KEYS are NOT PERCENT ENCODED"""
+    def base_string_req_params(uri, oauth_params = None, data = None):
+        query = dict((k, v[0]) for k, v in parse_qs(urlsplit(uri).query).items())
+        if oauth_params: 
+            query.update(oauth_params)
+        if data and isinstance(data, Mapping):
+            query.update(data)
+        if "oauth_signature" in query: 
+            del query["oauth_signature"]
+        #Parameter normalization
+        def handle_val(x):
+            return x if isinstance(x, bytes) else str(x).encode("utf-8")
+        def handle_val2(x):
+            return x if isinstance(x, bytes) else str(x)
+        params = [(handle_val2(k), urlquote(handle_val(v))) for k, v in query.items()]
+        params.sort()
+        return '&'.join(['{0}={1}'.format(k, v) for k, v in params])
+    provider.signature_method.signature_base_string = _partial(
+            signature_base_string, base_string_req_params=base_string_req_params)
+
+
+workarounds = {
+        "base_string_req_params_keys_no_encode": workaround_base_string_req_params_keys_no_encode
+        }
+
+
 """ Workarounds """
 class ProtocolSignatureWorkaround(OAuth1Server):
     def oauth(self, req, credentials = None, params = {}):