Repository: globus/globus-sdk-python (python)

Issue:
BasicAuthorizer won't accept non-ascii username or password
Under Python 2.7.12:

```python
from globus_sdk.authorizers import BasicAuthorizer
nonascii = 'テ'
BasicAuthorizer('myusername', nonascii)
# UnicodeDecodeError: 'ascii' codec can't decode byte 0xe3 in position 11: ordinal not in range(128)
```

Pull request description:
Better handling of base64 encoding non-ascii chars
I think this is what we want for handling python 2 strings with non-ascii characters? 

What this does is tries to encode the `username:password` string with an encoding of `utf-8` which will work on an ascii str in python 2 or any string in python 3. If a python 2 string has a non-ascii character in it it'll hit the `except` block and just run it through `b64encode()`, which is (should?) be fine since it'll be in the correct format without needing an `encode()`.

Closes #250 



Patch:
diff --git a/globus_sdk/authorizers/basic.py b/globus_sdk/authorizers/basic.py
index 83b49204..23475f24 100644
--- a/globus_sdk/authorizers/basic.py
+++ b/globus_sdk/authorizers/basic.py
@@ -1,7 +1,7 @@
 import logging
-import base64
 
 from globus_sdk.authorizers.base import GlobusAuthorizer
+from globus_sdk.utils import safe_b64encode
 
 logger = logging.getLogger(__name__)
 
@@ -27,9 +27,8 @@ class BasicAuthorizer(GlobusAuthorizer):
         self.username = username
         self.password = password
 
-        encoded = base64.b64encode(
-            bytes("{0}:{1}".format(username, password).encode("utf-8")))
-        self.header_val = "Basic %s" % encoded.decode('utf-8')
+        to_b64 = '{0}:{1}'.format(username, password)
+        self.header_val = "Basic %s" % safe_b64encode(to_b64)
 
     def set_authorization_header(self, header_dict):
         """
diff --git a/globus_sdk/utils/__init__.py b/globus_sdk/utils/__init__.py
new file mode 100644
index 00000000..52c09c7b
--- /dev/null
+++ b/globus_sdk/utils/__init__.py
@@ -0,0 +1,6 @@
+from globus_sdk.utils.string_handling import safe_b64encode
+
+
+__all__ = [
+    'safe_b64encode'
+]
diff --git a/globus_sdk/utils/string_handling.py b/globus_sdk/utils/string_handling.py
new file mode 100644
index 00000000..c2a9648d
--- /dev/null
+++ b/globus_sdk/utils/string_handling.py
@@ -0,0 +1,10 @@
+from base64 import b64encode
+
+
+def safe_b64encode(s):
+    try:
+        encoded = b64encode(s.encode('utf-8'))
+    except UnicodeDecodeError:
+        encoded = b64encode(s)
+
+    return encoded.decode('utf-8')


Test patch:
diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py
new file mode 100644
index 00000000..c459e6aa
--- /dev/null
+++ b/tests/unit/test_utils.py
@@ -0,0 +1,19 @@
+# coding=utf-8
+import unittest
+
+from globus_sdk import utils
+
+
+class TestUtils(unittest.TestCase):
+
+    def test_safe_b64encode_non_ascii(self):
+        test_string = 'ⓤⓢⓔⓡⓝⓐⓜⓔ'
+        expected_b64 = '4pOk4pOi4pOU4pOh4pOd4pOQ4pOc4pOU'
+
+        self.assertEqual(utils.safe_b64encode(test_string), expected_b64)
+
+    def test_safe_b64encode_ascii(self):
+        test_string = 'username'
+        expected_b64 = 'dXNlcm5hbWU='
+
+        self.assertEqual(utils.safe_b64encode(test_string), expected_b64)