diff --git a/Algorithmia/CLI.py b/Algorithmia/CLI.py index 3368915..4551a68 100644 --- a/Algorithmia/CLI.py +++ b/Algorithmia/CLI.py @@ -12,8 +12,7 @@ class CLI: def __init__(self): self.client = Algorithmia.client() # algo auth - - def auth(self, apikey, apiaddress, cacert="", profile="default"): + def auth(self, apiaddress, apikey="", cacert="", profile="default", bearer=""): # store api key in local config file and read from it each time a client needs to be created key = self.getconfigfile() @@ -24,20 +23,17 @@ def auth(self, apikey, apiaddress, cacert="", profile="default"): config['profiles'][profile]['api_key'] = apikey config['profiles'][profile]['api_server'] = apiaddress config['profiles'][profile]['ca_cert'] = cacert + config['profiles'][profile]['bearer_token'] = bearer else: - config['profiles'][profile] = {'api_key': apikey, 'api_server': apiaddress, 'ca_cert': cacert} + config['profiles'][profile] = {'api_key':apikey,'api_server':apiaddress,'ca_cert':cacert,'bearer_token':bearer} else: - config['profiles'] = {profile: {'api_key': apikey, 'api_server': apiaddress, 'ca_cert': cacert}} + config['profiles'] = {profile:{'api_key':apikey,'api_server':apiaddress,'ca_cert':cacert,'bearer_token':bearer }} with open(key, "w") as key: - toml.dump(config, key) - client = Algorithmia.client( - api_key=self.getAPIkey(profile), - api_address=self.getAPIaddress(profile), - ca_cert=self.getCert(profile) - ) - self.ls(path=None, client=client) + toml.dump(config,key) + + self.ls(path = None,client = CLI().getClient(profile)) # algo run run the the specified algo def runalgo(self, options, client): @@ -366,6 +362,7 @@ def getconfigfile(self): file.write("api_key = ''\n") file.write("api_server = ''\n") file.write("ca_cert = ''\n") + file.write("bearer_token = ''\n") key = keyPath + keyFile @@ -383,6 +380,16 @@ def getAPIkey(self, profile): return config_dict['profiles'][profile]['api_key'] else: return None + + def getBearerToken(self,profile): + key = self.getconfigfile() + config_dict = toml.load(key) + if 'profiles' in config_dict and profile in config_dict['profiles'] and \ + config_dict['profiles'][profile]['bearer_token'] != "": + return config_dict['profiles'][profile]['bearer_token'] + else: + return None + def getAPIaddress(self, profile): key = self.getconfigfile() @@ -401,3 +408,14 @@ def getCert(self, profile): return config_dict['profiles'][profile]['ca_cert'] else: return None + + def getClient(self,profile): + apiAddress = self.getAPIaddress(profile) + apiKey = self.getAPIkey(profile) + caCert = self.getCert(profile) + bearer = None + + if apiKey is None: + bearer = self.getBearerToken(profile) + + return Algorithmia.client(api_key=apiKey,api_address=apiAddress,ca_cert=caCert,bearer_token = bearer) diff --git a/Algorithmia/__init__.py b/Algorithmia/__init__.py index 05ed6dc..38e7ed6 100644 --- a/Algorithmia/__init__.py +++ b/Algorithmia/__init__.py @@ -23,8 +23,8 @@ def file(dataUrl): def dir(dataUrl): return getDefaultClient().dir(dataUrl) -def client(api_key=None, api_address=None, ca_cert=None): - return Client(api_key, api_address, ca_cert) +def client(api_key=None, api_address=None, ca_cert=None, bearer_token=None): + return Client(api_key, api_address, ca_cert, bearer_token) def handler(apply_func, load_func=lambda: None): return Handler(apply_func, load_func) diff --git a/Algorithmia/__main__.py b/Algorithmia/__main__.py index 9e67c5c..1b5f7b5 100644 --- a/Algorithmia/__main__.py +++ b/Algorithmia/__main__.py @@ -6,6 +6,7 @@ import six from Algorithmia.CLI import CLI import argparse +import re #bind input to raw input try: @@ -145,27 +146,26 @@ def main(): APIkey = input("enter API key: ") CACert = input('(optional) enter path to custom CA certificate: ') - if len(APIkey) == 28 and APIkey.startswith("sim"): - if APIaddress == "" or not APIaddress.startswith("https://api."): - APIaddress = "https://api.algorithmia.com" - - CLI().auth(apikey=APIkey, apiaddress=APIaddress, cacert=CACert, profile=args.profile) + if APIaddress == "" or not APIaddress.startswith("https://api."): + print("invalid API address") else: - print("invalid api key") - + if len(APIkey) == 28 and APIkey.startswith("sim"): + CLI().auth(apikey=APIkey, apiaddress=APIaddress, cacert=CACert, profile=args.profile) + else: + jwt = re.compile(r"^([a-zA-Z0-9_=]+)\.([a-zA-Z0-9_=]+)\.([a-zA-Z0-9_\-\+\/=]*)") + Bearer = input("enter JWT token: ") + if jwt.match(Bearer): + CLI().auth(apikey=APIkey, bearer=Bearer, apiaddress=APIaddress, cacert=CACert, profile=args.profile) + else: + print("invalid authentication") + + + if args.cmd == 'help': parser.parse_args(['-h']) #create a client with the appropreate api address and key - client = Algorithmia.client() - if len(CLI().getAPIaddress(args.profile)) > 1: - client = Algorithmia.client(CLI().getAPIkey(args.profile), CLI().getAPIaddress(args.profile)) - elif len(CLI().getAPIaddress(args.profile)) > 1 and len(CLI().getCert(args.profile)) > 1: - client = Algorithmia.client(CLI().getAPIkey(args.profile), CLI().getAPIaddress(args.profile),CLI().getCert(args.profile)) - elif len(CLI().getAPIaddress(args.profile)) < 1 and len(CLI().getCert(args.profile)) > 1: - client = Algorithmia.client(CLI().getAPIkey(args.profile), CLI().getAPIaddress(args.profile),CLI().getCert(args.profile)) - else: - client = Algorithmia.client(CLI().getAPIkey(args.profile)) + client = CLI().getClient(args.profile) if args.cmd == 'run': diff --git a/Algorithmia/client.py b/Algorithmia/client.py index ccea0ca..451d6dd 100644 --- a/Algorithmia/client.py +++ b/Algorithmia/client.py @@ -21,13 +21,20 @@ class Client(object): apiKey = None apiAddress = None requestSession = None + bearerToken = None - def __init__(self, apiKey=None, apiAddress=None, caCert=None): + + def __init__(self, apiKey = None, apiAddress = None, caCert = None, bearerToken=None): # Override apiKey with environment variable config = None self.requestSession = requests.Session() if apiKey is None and 'ALGORITHMIA_API_KEY' in os.environ: apiKey = os.environ['ALGORITHMIA_API_KEY'] + if apiKey is None: + if bearerToken is None and 'ALGORITHMIA_BEARER_TOKEN' in os.environ: + bearerToken = os.environ['ALGORITHMIA_BEARER_TOKEN'] + self.bearerToken = bearerToken + self.apiKey = apiKey if apiAddress is not None: self.apiAddress = apiAddress @@ -217,6 +224,8 @@ def postJsonHelper(self, url, input_object, parse_response_as_json=True, **query headers = {} if self.apiKey is not None: headers['Authorization'] = self.apiKey + else: + headers['Authorization'] = "Bearer "+ self.bearerToken input_json = None if input_object is None: @@ -244,18 +253,24 @@ def getHelper(self, url, **query_parameters): headers = {} if self.apiKey is not None: headers['Authorization'] = self.apiKey + else: + headers['Authorization'] = 'Bearer '+ self.bearerToken return self.requestSession.get(self.apiAddress + url, headers=headers, params=query_parameters) def getStreamHelper(self, url, **query_parameters): headers = {} if self.apiKey is not None: headers['Authorization'] = self.apiKey + else: + headers['Authorization'] = 'Bearer '+ self.bearerToken return self.requestSession.get(self.apiAddress + url, headers=headers, params=query_parameters, stream=True) def patchHelper(self, url, params): headers = {'content-type': 'application/json'} if self.apiKey is not None: headers['Authorization'] = self.apiKey + else: + headers['Authorization'] = 'Bearer '+ self.bearerToken return self.requestSession.patch(self.apiAddress + url, headers=headers, data=json.dumps(params)) # Used internally to get http head result @@ -263,6 +278,8 @@ def headHelper(self, url): headers = {} if self.apiKey is not None: headers['Authorization'] = self.apiKey + else: + headers['Authorization'] = 'Bearer '+ self.bearerToken return self.requestSession.head(self.apiAddress + url, headers=headers) # Used internally to http put a file @@ -270,6 +287,8 @@ def putHelper(self, url, data): headers = {} if self.apiKey is not None: headers['Authorization'] = self.apiKey + else: + headers['Authorization'] = 'Bearer '+ self.bearerToken if isJson(data): headers['Content-Type'] = 'application/json' @@ -283,6 +302,8 @@ def deleteHelper(self, url): headers = {} if self.apiKey is not None: headers['Authorization'] = self.apiKey + else: + headers['Authorization'] = 'Bearer '+ self.bearerToken response = self.requestSession.delete(self.apiAddress + url, headers=headers) if response.reason == "No Content": return response diff --git a/Test/CLI_test.py b/Test/CLI_test.py index ae1d546..99f662e 100644 --- a/Test/CLI_test.py +++ b/Test/CLI_test.py @@ -17,6 +17,7 @@ class CLIDummyTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.client = Algorithmia.client(api_address="http://localhost:8080", api_key="simabcd123") + cls.bearerClient = Algorithmia.client(api_address="http://localhost:8080", bearer_token="simabcd123.token.token") def test_run(self): name = "util/Echo" @@ -52,6 +53,40 @@ def test_run(self): result = CLI().runalgo(args, self.client) self.assertEqual(result, inputs) + def test_run_token(self): + name = "util/Echo" + inputs = "test" + + parser = argparse.ArgumentParser('CLI for interacting with Algorithmia') + + subparsers = parser.add_subparsers(help='sub cmd', dest='subparser_name') + parser_run = subparsers.add_parser('run', help='algo run [input options] [output options]') + + parser_run.add_argument('algo') + parser_run.add_argument('-d', '--data', action='store', help='detect input type', default=None) + parser_run.add_argument('-t', '--text', action='store', help='treat input as text', default=None) + parser_run.add_argument('-j', '--json', action='store', help='treat input as json data', default=None) + parser_run.add_argument('-b', '--binary', action='store', help='treat input as binary data', default=None) + parser_run.add_argument('-D', '--data-file', action='store', help='specify a path to an input file', + default=None) + parser_run.add_argument('-T', '--text-file', action='store', help='specify a path to a text file', + default=None) + parser_run.add_argument('-J', '--json-file', action='store', help='specify a path to a json file', + default=None) + parser_run.add_argument('-B', '--binary-file', action='store', help='specify a path to a binary file', + default=None) + parser_run.add_argument('--timeout', action='store', type=int, default=300, + help='specify a timeout (seconds)') + parser_run.add_argument('--debug', action='store_true', + help='print the stdout from the algo ') + parser_run.add_argument('--profile', action='store', type=str, default='default') + parser_run.add_argument('-o', '--output', action='store', default=None, type=str) + + args = parser.parse_args(['run', name, '-d', inputs]) + + result = CLI().runalgo(args, self.bearerClient) + self.assertEqual(result, inputs) + class CLIMainTest(unittest.TestCase): def setUp(self): @@ -156,7 +191,7 @@ def test_auth(self): key = os.getenv('ALGORITHMIA_API_KEY') address = 'https://api.algorithmia.com' profile = 'default' - CLI().auth(key, address, profile=profile) + CLI().auth(address, key, profile=profile) resultK = CLI().getAPIkey(profile) resultA = CLI().getAPIaddress(profile) self.assertEqual(resultK, key) @@ -176,13 +211,24 @@ def test_auth_cert(self): cacert = localfile profile = 'test' - CLI().auth(key, address, cacert, profile) + CLI().auth(address, key, cacert=cacert, profile=profile) resultK = CLI().getAPIkey(profile) resultA = CLI().getAPIaddress(profile) resultC = CLI().getCert(profile) self.assertEqual(resultK, key) self.assertEqual(resultA, address) self.assertEqual(resultC, cacert) + + def test_auth_token(self): + address = 'https://api.algorithmia.com' + bearer = 'testtokenabcd' + profile = 'test' + + CLI().auth(apiaddress=address, bearer=bearer, profile=profile) + resultA = CLI().getAPIaddress(profile) + resultT = CLI().getBearerToken(profile) + self.assertEqual(resultA, address) + self.assertEqual(resultT, bearer) def test_get_environment(self): result = CLI().get_environment_by_language("python2", self.client) @@ -230,7 +276,7 @@ def test_get_template(self): def test_api_address_auth(self): api_key = os.getenv('ALGORITHMIA_TEST_API_KEY') api_address = "https://api.test.algorithmia.com" - CLI().auth(api_key, api_address) + CLI().auth(api_address, api_key) profile = "default" client = Algorithmia.client(CLI().getAPIkey(profile), CLI().getAPIaddress(profile), CLI().getCert(profile))