Skip to content

Commit d09e265

Browse files
authored
Merge pull request #161 from descarteslabs/feat/auth-injection
Support multiple authenticated users in the same environment
2 parents 4c8760f + 0bd0880 commit d09e265

File tree

8 files changed

+61
-39
lines changed

8 files changed

+61
-39
lines changed

descarteslabs/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616

1717
__version__ = "0.4.4"
1818
from .auth import Auth
19-
descartes_auth = Auth()
19+
descartes_auth = Auth.from_environment_or_token_json()
2020

2121
from .services.metadata import Metadata
2222
from .services.places import Places

descarteslabs/auth/__init__.py

Lines changed: 43 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727

2828
from descarteslabs.exceptions import AuthError, OauthError
2929

30+
DEFAULT_TOKEN_INFO_PATH = os.path.join(
31+
os.path.expanduser("~"), '.descarteslabs', 'token_info.json')
32+
3033

3134
def base64url_decode(input):
3235
"""Helper method to base64url_decode a string.
@@ -42,34 +45,55 @@ def base64url_decode(input):
4245

4346
class Auth:
4447
def __init__(self, domain="https://iam.descarteslabs.com",
45-
scope=None, leeway=500):
48+
scope=None, leeway=500, token_info_path=DEFAULT_TOKEN_INFO_PATH,
49+
client_id=None, client_secret=None, jwt_token=None):
4650
"""
4751
Helps retrieve JWT from a client id and refresh token for cli usage.
48-
:param client_id: str
49-
:param refresh_token: str generated through IAM interface
50-
:param url: endpoint for auth0
52+
:param domain: endpoint for auth0
5153
:param scope: the JWT fields to be included
5254
:param leeway: JWT expiration leeway
55+
:param token_info_path: path to a JSON file optionally holding auth information
56+
:param client_id: JWT client id
57+
:param client_secret: JWT client secret
58+
:param jwt_token: the JWT token, if we already have one
5359
"""
60+
self.token_info_path = token_info_path
61+
self.client_id = client_id
62+
self.client_secret = client_secret
63+
self._token = jwt_token
5464

55-
token_info = {}
65+
self.domain = domain
66+
self.scope = scope
67+
self.leeway = leeway
5668

69+
if self.scope is None:
70+
self.scope = ['openid', 'name', 'groups']
71+
72+
@classmethod
73+
def from_environment_or_token_json(cls, domain="https://iam.descarteslabs.com",
74+
scope=None, leeway=500,
75+
token_info_path=DEFAULT_TOKEN_INFO_PATH):
76+
"""
77+
Creates an Auth object from environment variables CLIENT_ID, CLIENT_SECRET,
78+
JWT_TOKEN if they are set, or else from a JSON file at the given path.
79+
:param domain: endpoint for auth0
80+
:param scope: the JWT fields to be included
81+
:param leeway: JWT expiration leeway
82+
:param token_info_path: path to a JSON file optionally holding auth information
83+
"""
84+
token_info = {}
5785
try:
58-
with open(os.path.join(os.path.expanduser("~"), '.descarteslabs', 'token_info.json')) as fp:
86+
with open(token_info_path) as fp:
5987
token_info = json.load(fp)
6088
except:
6189
pass
6290

63-
self.client_id = os.environ.get('CLIENT_ID', token_info.get('client_id', None))
64-
self.client_secret = os.environ.get('CLIENT_SECRET', token_info.get('client_secret', None))
65-
self._token = os.environ.get('JWT_TOKEN', token_info.get('jwt_token', None))
66-
67-
self.domain = domain
68-
self.scope = scope
69-
self.leeway = leeway
91+
client_id = os.environ.get('CLIENT_ID', token_info.get('client_id', None))
92+
client_secret = os.environ.get('CLIENT_SECRET', token_info.get('client_secret', None))
93+
jwt_token = os.environ.get('JWT_TOKEN', token_info.get('jwt_token', None))
7094

71-
if self.scope is None:
72-
self.scope = ['openid', 'name', 'groups']
95+
return cls(domain=domain, scope=scope, leeway=leeway, token_info_path=token_info_path,
96+
client_id=client_id, client_secret=client_secret, jwt_token=jwt_token)
7397

7498
@property
7599
def token(self):
@@ -134,7 +158,7 @@ def _get_token(self, timeout=100):
134158
token_info = {}
135159

136160
try:
137-
with open(os.path.join(os.path.expanduser("~"), '.descarteslabs', 'token_info.json')) as fp:
161+
with open(self.token_info_path) as fp:
138162
token_info = json.load(fp)
139163
except:
140164
pass
@@ -148,15 +172,13 @@ def _get_token(self, timeout=100):
148172

149173
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
150174

151-
file = os.path.join(os.path.expanduser("~"), '.descarteslabs', 'token_info.json')
152-
153-
with open(file, 'w+') as fp:
175+
with open(self.token_info_path, 'w+') as fp:
154176
json.dump(token_info, fp)
155177

156-
os.chmod(file, stat.S_IRUSR | stat.S_IWUSR)
178+
os.chmod(self.token_info_path, stat.S_IRUSR | stat.S_IWUSR)
157179

158180

159181
if __name__ == '__main__':
160-
auth = Auth()
182+
auth = Auth.from_environment_or_token_json()
161183

162184
print(auth.token)

descarteslabs/scripts/parser/auth.py

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,13 @@
2020
import six
2121
from six.moves import input
2222

23-
from descarteslabs.auth import Auth, base64url_decode
23+
from descarteslabs.auth import Auth, base64url_decode, DEFAULT_TOKEN_INFO_PATH
2424

2525
import descarteslabs as dl
2626

2727

2828
def auth_handler(args):
29-
auth = Auth()
29+
auth = Auth.from_environment_or_token_json()
3030

3131
if args.command == 'login':
3232

@@ -41,22 +41,20 @@ def auth_handler(args):
4141

4242
token_info = json.loads(base64url_decode(s).decode('utf-8'))
4343

44-
path = os.path.join(os.path.expanduser("~"), '.descarteslabs')
44+
path = os.path.dirname(DEFAULT_TOKEN_INFO_PATH)
4545

4646
if not os.path.exists(path):
4747
os.makedirs(path)
4848

4949
os.chmod(path, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR)
5050

51-
file = os.path.join(os.path.expanduser("~"), '.descarteslabs', 'token_info.json')
52-
53-
with open(file, 'w+') as fp:
51+
with open(DEFAULT_TOKEN_INFO_PATH, 'w+') as fp:
5452
json.dump(token_info, fp)
5553

56-
os.chmod(file, stat.S_IRUSR | stat.S_IWUSR)
54+
os.chmod(DEFAULT_TOKEN_INFO_PATH, stat.S_IRUSR | stat.S_IWUSR)
5755

5856
# Get a fresh Auth token
59-
auth = dl.Auth()
57+
auth = dl.Auth.from_environment_or_token_json()
6058
dl.metadata.auth = auth
6159
keys = dl.metadata.keys()
6260

descarteslabs/services/metadata.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class Metadata(Service):
3232
TIMEOUT = (9.5, 120)
3333
"""Image Metadata Service"""
3434

35-
def __init__(self, url=None, token=None):
35+
def __init__(self, url=None, token=None, auth=dl.descartes_auth):
3636
"""The parent Service class implements authentication and exponential
3737
backoff/retry. Override the url parameter to use a different instance
3838
of the backing service.
@@ -42,7 +42,7 @@ def __init__(self, url=None, token=None):
4242
url = os.environ.get("DESCARTESLABS_METADATA_URL",
4343
"https://platform-services.descarteslabs.com/metadata/v1")
4444

45-
Service.__init__(self, url, token)
45+
Service.__init__(self, url, token, auth)
4646

4747
def sources(self):
4848
"""Get a list of image sources.

descarteslabs/services/places.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,21 +19,22 @@
1919
from cachetools.keys import hashkey
2020
from .service import Service
2121
from six import string_types
22+
import descarteslabs
2223

2324

2425
class Places(Service):
2526
TIMEOUT = (9.5, 360)
2627
"""Places and statistics service https://iam.descarteslabs.com/service/waldo"""
2728

28-
def __init__(self, url=None, token=None, maxsize=10, ttl=600):
29+
def __init__(self, url=None, token=None, auth=descarteslabs.descartes_auth, maxsize=10, ttl=600):
2930
"""The parent Service class implements authentication and exponential
3031
backoff/retry. Override the url parameter to use a different instance
3132
of the backing service.
3233
"""
3334
if url is None:
3435
url = os.environ.get("DESCARTESLABS_PLACES_URL", "https://platform-services.descarteslabs.com/waldo/v1")
3536

36-
Service.__init__(self, url, token)
37+
Service.__init__(self, url, token, auth)
3738
self.cache = TTLCache(maxsize, ttl)
3839

3940
def placetypes(self):

descarteslabs/services/raster.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import json
1919
import warnings
2020

21+
import descarteslabs
2122
from descarteslabs.addons import numpy as np
2223
from descarteslabs.utilities import as_json_string
2324
from .service import Service
@@ -35,7 +36,7 @@ class Raster(Service):
3536
"""Raster"""
3637
TIMEOUT = (9.5, 300)
3738

38-
def __init__(self, url=None, token=None):
39+
def __init__(self, url=None, token=None, auth=descarteslabs.descartes_auth):
3940
"""The parent Service class implements authentication and exponential
4041
backoff/retry. Override the url parameter to use a different instance
4142
of the backing service.
@@ -44,7 +45,7 @@ def __init__(self, url=None, token=None):
4445
if url is None:
4546
url = os.environ.get("DESCARTESLABS_RASTER_URL", "https://platform-services.descarteslabs.com/raster/v1")
4647

47-
Service.__init__(self, url, token)
48+
Service.__init__(self, url, token, auth)
4849

4950
def get_bands_by_key(self, key):
5051
"""

descarteslabs/services/service.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ class Service:
6565

6666
ADAPTER = HTTPAdapter(max_retries=RETRY_CONFIG)
6767

68-
def __init__(self, url, token):
69-
self.auth = descarteslabs.descartes_auth
68+
def __init__(self, url, token, auth):
69+
self.auth = auth
7070
self.base_url = url
7171
if token:
7272
self.auth._token = token

descarteslabs/tests/test_auth.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121
class TestAuth(unittest.TestCase):
2222
def test_get_token(self):
2323
# get a jwt
24-
auth = Auth()
24+
auth = Auth.from_environment_or_token_json()
2525
self.assertIsNotNone(auth.token)
2626

2727
# validate the jwt

0 commit comments

Comments
 (0)