Skip to content

Commit 9496d1a

Browse files
authored
Merge pull request #97 from ssl-hep:oauth
Authenticate with ServiceX API token
2 parents 5c657ae + 1071be6 commit 9496d1a

12 files changed

+110
-158
lines changed

.vscode/settings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"SXPASS",
1919
"SXTYPE",
2020
"SXUSER",
21+
"SXTOKEN",
2122
"Servivce",
2223
"Topo",
2324
"accesskey",

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,13 +33,14 @@ The API access information is normally placed in a `.servicex` file (to keep thi
3333
directory on Windows).
3434
1. The `config_defaults.yaml` file distributed with the `servicex` package.
3535

36-
Create a `.servicex` file, in the `yaml` format, in the appropriate place for your work that contains the following:
36+
If no endpoint is specified, then the library defaults to the developer endpoint, which is `http://localhost:5000` for the web-service API, and `localhost:9000` for the `minio` endpoint. No passwords are required.
37+
38+
Create a `.servicex` file, in the `yaml` format, in the appropriate place for your work that contains the following (for the `xaod` backend; use `uproot` for the uproot backend):
3739

3840
```yaml
3941
api_endpoints:
40-
- endpoint: <your-endpoint-url>
41-
email: <api-email>
42-
password: <api-password>
42+
- endpoint: <your-endpoint>
43+
token: <api-token>
4344
type: xaod
4445
```
4546

servicex/config_default.yaml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@
33

44
api_endpoints:
55
- endpoint: http://localhost:5000
6-
# email: xxx
7-
# password: yyy
6+
# token: xxx
87

98
# These are default settings, depreciated, and should not be used.
109
# They will be removed in the next version.

servicex/minio_adaptor.py

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,19 @@ class MinioAdaptor:
4444
# uses blocking http requests, so we can't use asyncio to interleave them.
4545
_download_executor = ThreadPoolExecutor(max_workers=5)
4646

47-
def __init__(self, mino_endpoint: str,
47+
def __init__(self, minio_endpoint: str,
48+
secured: bool = False,
4849
access_key: str = 'miniouser',
4950
secretkey: str = 'leftfoot1'):
50-
self._endpoint = mino_endpoint
51+
self._endpoint = minio_endpoint
52+
self._secured = secured
5153
self._access_key = access_key
5254
self._secretkey = secretkey
5355

5456
self._client = Minio(self._endpoint,
5557
access_key=self._access_key,
5658
secret_key=self._secretkey,
57-
secure=False)
59+
secure=self._secured)
5860

5961
@on_exception(backoff.constant, ResponseError, interval=0.1)
6062
def get_files(self, request_id):
@@ -118,7 +120,7 @@ def __init__(self, c: Optional[ConfigView] = None,
118120
if self._always is None and c is not None:
119121
self._config_adaptor = self._from_config(c)
120122

121-
def from_best(self, transation_info: Optional[Dict[str, str]] = None) -> MinioAdaptor:
123+
def from_best(self, transaction_info: Optional[Dict[str, str]] = None) -> MinioAdaptor:
122124
'''Using the information we have, create the proper Minio Adaptor with the correct
123125
endpoint and login information. Order of adaptor generation:
124126
@@ -136,14 +138,14 @@ def from_best(self, transation_info: Optional[Dict[str, str]] = None) -> MinioAd
136138
if self._always is not None:
137139
logging.getLogger(__name__).debug('Using the pre-defined minio_adaptor')
138140
return self._always
139-
if transation_info is not None:
140-
if 'minio-endpoint' in transation_info \
141-
and 'minio-access-key' in transation_info \
142-
and 'minio-secret-key' in transation_info:
141+
if transaction_info is not None:
142+
keys = ['minio-endpoint', 'minio-secured', 'minio-access-key', 'minio-secret-key']
143+
if all(k in transaction_info for k in keys):
143144
logging.getLogger(__name__).debug('Using the request-specific minio_adaptor')
144-
return MinioAdaptor(transation_info['minio-endpoint'],
145-
transation_info['minio-access-key'],
146-
transation_info['minio-secret-key'])
145+
return MinioAdaptor(transaction_info['minio-endpoint'],
146+
transaction_info['minio-secured'],
147+
transaction_info['minio-access-key'],
148+
transaction_info['minio-secret-key'])
147149
if self._config_adaptor is not None:
148150
logging.getLogger(__name__).debug('Using the config-file minio_adaptor')
149151
return self._config_adaptor

servicex/servicex.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -107,8 +107,8 @@ def __init__(self,
107107

108108
if not servicex_adaptor:
109109
# Given servicex adaptor is none, this should be ok. Fixes type checkers
110-
end_point, email, password = config.get_servicex_adaptor_config(backend_type)
111-
servicex_adaptor = ServiceXAdaptor(end_point, email, password)
110+
end_point, token = config.get_servicex_adaptor_config(backend_type)
111+
servicex_adaptor = ServiceXAdaptor(end_point, token)
112112
self._servicex_adaptor = servicex_adaptor
113113

114114
if not minio_adaptor:

servicex/servicex_adaptor.py

Lines changed: 13 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,41 +21,32 @@
2121

2222
# Low level routines for interacting with a ServiceX instance via the WebAPI
2323
class ServiceXAdaptor:
24-
def __init__(self, endpoint, email=None, password=None):
24+
def __init__(self, endpoint, refresh_token=None):
2525
'''
2626
Authenticated access to ServiceX
2727
'''
2828
self._endpoint = endpoint
29-
self._email = email
30-
self._password = password
31-
3229
self._token = None
33-
self._refresh_token = None
34-
35-
async def _login(self, client: aiohttp.ClientSession):
36-
url = f'{self._endpoint}/login'
37-
async with client.post(url, json={
38-
'email': self._email,
39-
'password': self._password
40-
}) as response:
30+
self._refresh_token = refresh_token
31+
32+
async def _get_token(self, client: aiohttp.ClientSession):
33+
url = f'{self._endpoint}/token/refresh'
34+
headers = {'Authorization': f'Bearer {self._refresh_token}'}
35+
async with client.post(url, headers=headers, json=None) as response:
4136
status = response.status
4237
if status == 200:
4338
j = await response.json()
4439
self._token = j['access_token']
45-
self._refresh_token = j['refresh_token']
4640
else:
47-
raise ServiceXException(f'ServiceX login request rejected: {status}')
41+
raise ServiceXException(f'ServiceX access token request rejected: {status}')
4842

4943
async def _get_authorization(self, client: aiohttp.ClientSession):
50-
if self._email:
51-
now = datetime.utcnow().timestamp()
52-
if not self._token or jwt.decode(self._token, verify=False)['exp'] - now < 0:
53-
await self._login(client)
54-
return {
55-
'Authorization': f'Bearer {self._token}'
56-
}
57-
else:
44+
if not self._refresh_token:
5845
return {}
46+
now = datetime.utcnow().timestamp()
47+
if not self._token or jwt.decode(self._token, verify=False)['exp'] - now < 0:
48+
await self._get_token(client)
49+
return {'Authorization': f'Bearer {self._token}'}
5950

6051
async def submit_query(self, client: aiohttp.ClientSession,
6152
json_query: Dict[str, str]) -> Dict[str, str]:

servicex/servicex_config.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def find_in_list(c, key) -> Optional[str]:
8282
return a
8383

8484
def get_servicex_adaptor_config(self, backend_type: Optional[str] = None) -> \
85-
Tuple[str, Optional[str], Optional[str]]:
85+
Tuple[str, Optional[str]]:
8686
'''Return the servicex (endpoint, username, email) from a given backend configuration.
8787
8888
Args:
@@ -97,14 +97,12 @@ def get_servicex_adaptor_config(self, backend_type: Optional[str] = None) -> \
9797
# It is an error if this is not specified somewhere.
9898
endpoints = self._settings['api_endpoints']
9999

100-
def extract_info(ep) -> Tuple[str, Optional[str], Optional[str]]:
100+
def extract_info(ep) -> Tuple[str, Optional[str]]:
101101
endpoint = ep['endpoint'].as_str_expanded()
102-
email = ep['email'].as_str_expanded() if 'email' in ep else None
103-
password = ep['password'].as_str_expanded() if 'password' in ep \
104-
else None
102+
token = ep['token'].as_str_expanded() if 'token' in ep else None
105103

106104
# We can default these to "None"
107-
return (endpoint, email, password) # type: ignore
105+
return (endpoint, token) # type: ignore
108106

109107
# If we have a good name, look for exact match
110108
if backend_type is not None:

tests/test_minio_adaptor.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -231,10 +231,12 @@ def test_factory_from_request():
231231
info = {
232232
'minio-access-key': 'miniouser',
233233
'minio-endpoint': 'minio.servicex.com:9000',
234+
'minio-secured': False,
234235
'minio-secret-key': 'leftfoot1',
235236
}
236237
m = MinioAdaptorFactory().from_best(info)
237238
assert m._endpoint == 'minio.servicex.com:9000'
239+
assert not m._secured
238240
assert m._access_key == "miniouser"
239241
assert m._secretkey == "leftfoot1"
240242

tests/test_servicex.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ def test_default_ctor(mocker):
3232
'''
3333
config = mocker.MagicMock(spec=ServiceXConfigAdaptor)
3434
config.settings = Configuration('servicex', 'servicex')
35-
config.get_servicex_adaptor_config.return_value = ('http://no-way.dude', 'j@yaol.com',
35+
config.get_servicex_adaptor_config.return_value = ('http://no-way.dude',
3636
'no_spoon_there_is')
3737

3838
fe.ServiceXDataset('localds://dude', "uproot-ftw", config_adaptor=config)
@@ -47,7 +47,7 @@ def test_default_ctor_no_type(mocker):
4747
'''
4848
config = mocker.MagicMock(spec=ServiceXConfigAdaptor)
4949
config.settings = Configuration('servicex', 'servicex')
50-
config.get_servicex_adaptor_config.return_value = ('http://no-way.dude', 'j@yaol.com',
50+
config.get_servicex_adaptor_config.return_value = ('http://no-way.dude',
5151
'no_spoon_there_is')
5252

5353
fe.ServiceXDataset('localds://dude', config_adaptor=config)

0 commit comments

Comments
 (0)