Repository: CartoDB/cartoframes

Issue:
add base url validation on `https` to ensure all requests occur as expected
`POST api/v1/imports` forwards to `GET api/v1/imports` if the base url is not `https`. This causes some unexpected behaviors and perplexing errors, so we need to add base url validation to ensure all the services work as expected. We can bundle this into the `is_authenticated` method in CartoContext.

Discussion:

Patch:
diff --git a/README.rst b/README.rst
index a5839a12..7f985b28 100644
--- a/README.rst
+++ b/README.rst
@@ -110,7 +110,7 @@ Get table from CARTO, make changes in pandas, sync updates with CARTO:
 .. code:: python
 
     import cartoframes
-    # `base_url`s are of the form `http://{username}.carto.com/` for most users
+    # `base_url`s are of the form `https://{username}.carto.com/` for most users
     cc = cartoframes.CartoContext(base_url='https://eschbacher.carto.com/',
                                   api_key=APIKEY)
 
@@ -194,7 +194,7 @@ CARTO Credential Management
 Typical usage
 ^^^^^^^^^^^^^
 
-The most common way to input credentials into cartoframes is through the `CartoContext`, as below. Replace `{your_user_name}` with your CARTO username and `{your_api_key}` with your API key, which you can find at ``http://{your_user_name}.carto.com/your_apps``.
+The most common way to input credentials into cartoframes is through the `CartoContext`, as below. Replace `{your_user_name}` with your CARTO username and `{your_api_key}` with your API key, which you can find at ``https://{your_user_name}.carto.com/your_apps``.
 
 .. code:: python
 
diff --git a/cartoframes/context.py b/cartoframes/context.py
index c4147ecb..12696c61 100644
--- a/cartoframes/context.py
+++ b/cartoframes/context.py
@@ -136,6 +136,7 @@ class CartoContext(object):
 
     def _is_authenticated(self):
         """Checks if credentials allow for authenticated carto access"""
+        # check if user is authenticated
         try:
             self.sql_client.send(
                 'select * from information_schema.tables limit 0')
diff --git a/cartoframes/credentials.py b/cartoframes/credentials.py
index 4feaecfb..00e7db5e 100644
--- a/cartoframes/credentials.py
+++ b/cartoframes/credentials.py
@@ -1,7 +1,12 @@
 """Credentials management for cartoframes usage."""
 import os
 import json
+import sys
 import warnings
+if sys.version_info >= (3, 0):
+    from urllib.parse import urlparse
+else:
+    from urlparse import urlparse
 import appdirs
 
 _USER_CONFIG_DIR = appdirs.user_config_dir('cartoframes')
@@ -46,26 +51,32 @@ class Credentials(object):
     """
     def __init__(self, creds=None, key=None, username=None, base_url=None,
                  cred_file=None):
+        self._key = None
+        self._username = None
+        self._base_url = None
         if creds and isinstance(creds, Credentials):
-            self._key = creds.key()
-            self._username = creds.username()
-            self._base_url = creds.base_url()
+            self.key(key=creds.key())
+            self.username(username=creds.username())
+            self.base_url(base_url=creds.base_url())
         elif (key and username) or (key and base_url):
-            self._key = key
-            self._username = username
+            self.key(key=key)
+            self.username(username=username)
             if base_url:
-                self._base_url = base_url
+                self.base_url(base_url=base_url)
             else:
-                self._base_url = 'https://{}.carto.com/'.format(self._username)
+                self.base_url(
+                    base_url='https://{}.carto.com/'.format(self._username)
+                )
         elif cred_file:
             self._retrieve(cred_file)
         else:
             try:
                 self._retrieve(_DEFAULT_PATH)
             except:
-                raise RuntimeError('Could not load CARTO credentials. Try '
-                                   'setting them with the `key` and '
-                                   '`username` arguments.')
+                raise RuntimeError(
+                    'Could not load CARTO credentials. Try setting them with '
+                    'the `key` and `username` arguments.'
+                )
         self._norm_creds()
 
     def __repr__(self):
@@ -77,7 +88,8 @@ class Credentials(object):
 
     def _norm_creds(self):
         """Standardize credentials"""
-        self._base_url = self._base_url.strip('/')
+        if self._base_url:
+            self._base_url = self._base_url.strip('/')
 
     def save(self, config_loc=None):
         """Saves current user credentials to user directory.
@@ -256,6 +268,12 @@ class Credentials(object):
                 >>> creds.base_url('new_base_url')
         """
         if base_url:
+            # POSTs need to be over HTTPS (e.g., Import API reverts to a GET)
+            if urlparse(base_url).scheme != 'https':
+                raise ValueError(
+		    '`base_url`s need to be over `https`. Update your '
+                    '`base_url`.'
+		)
             self._base_url = base_url
         else:
             return self._base_url


Test patch:
diff --git a/test/test_context.py b/test/test_context.py
index dd04ed4c..a8185971 100644
--- a/test/test_context.py
+++ b/test/test_context.py
@@ -167,6 +167,14 @@ class TestCartoContext(unittest.TestCase, _UserUrlLoader):
         cc_saved = cartoframes.CartoContext()
         self.assertEqual(cc_saved.creds.key(), self.apikey)
 
+    @unittest.skipIf(WILL_SKIP, 'no carto credentials, skipping this test')
+    def test_cartocontext_authenticated(self):
+        """context.CartoContext._is_authenticated"""
+        with self.assertRaises(ValueError):
+            cc = cartoframes.CartoContext(
+                base_url=self.baseurl.replace('https', 'http'),
+                api_key=self.apikey)
+
     @unittest.skipIf(WILL_SKIP, 'no carto credentials, skipping this test')
     def test_cartocontext_isorguser(self):
         """context.CartoContext._is_org_user"""
diff --git a/test/test_credentials.py b/test/test_credentials.py
index 5b6f6735..0aeb9ba2 100644
--- a/test/test_credentials.py
+++ b/test/test_credentials.py
@@ -22,10 +22,10 @@ class TestCredentials(unittest.TestCase):
         self.onprem_base_url = 'https://turtleland.com/user/{}'.format(
                 self.username)
         self.default = {
-                'key': 'default_key',
-                'username': 'default_username',
-                'base_url': 'https://default_username.carto.com/'
-                }
+            'key': 'default_key',
+            'username': 'default_username',
+            'base_url': 'https://default_username.carto.com/'
+        }
         self.default_cred = Credentials(**self.default)
         self.default_cred.save()
 
@@ -61,6 +61,12 @@ class TestCredentials(unittest.TestCase):
         self.assertEqual(creds.username(), None)
         self.assertEqual(creds.base_url(), self.base_url.strip('/'))
 
+        with self.assertRaises(ValueError):
+            creds = Credentials(
+                key=self.key,
+                base_url=self.base_url.replace('https', 'http')
+            )
+
     def test_credentials_onprem_baseurl(self):
         """credentials.Credentials on-prem-style base_url"""
         creds = Credentials(key=self.key,