oscar-python 1.3.0__tar.gz → 1.3.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. {oscar_python-1.3.0 → oscar_python-1.3.2}/PKG-INFO +41 -5
  2. {oscar_python-1.3.0 → oscar_python-1.3.2}/README.md +38 -3
  3. oscar_python-1.3.2/oscar_python/_oidc.py +96 -0
  4. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/_utils.py +1 -1
  5. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/client.py +28 -13
  6. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/storage.py +21 -5
  7. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python.egg-info/PKG-INFO +41 -5
  8. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python.egg-info/SOURCES.txt +2 -0
  9. {oscar_python-1.3.0 → oscar_python-1.3.2}/tests/test_client.py +9 -0
  10. oscar_python-1.3.2/tests/test_oidc.py +38 -0
  11. {oscar_python-1.3.0 → oscar_python-1.3.2}/tests/test_storage.py +2 -2
  12. {oscar_python-1.3.0 → oscar_python-1.3.2}/tests/test_utils.py +3 -1
  13. {oscar_python-1.3.0 → oscar_python-1.3.2}/LICENSE +0 -0
  14. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/__init__.py +0 -0
  15. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/_providers/_minio.py +0 -0
  16. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/_providers/_onedata.py +0 -0
  17. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/_providers/_providers_base.py +0 -0
  18. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/_providers/_s3.py +0 -0
  19. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/_providers/_webdav.py +0 -0
  20. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/client_anon.py +0 -0
  21. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/default_client.py +0 -0
  22. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python/local_test.py +0 -0
  23. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python.egg-info/dependency_links.txt +0 -0
  24. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python.egg-info/not-zip-safe +0 -0
  25. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python.egg-info/requires.txt +0 -0
  26. {oscar_python-1.3.0 → oscar_python-1.3.2}/oscar_python.egg-info/top_level.txt +0 -0
  27. {oscar_python-1.3.0 → oscar_python-1.3.2}/setup.cfg +0 -0
  28. {oscar_python-1.3.0 → oscar_python-1.3.2}/setup.py +0 -0
  29. {oscar_python-1.3.0 → oscar_python-1.3.2}/tests/test_default_client.py +0 -0
  30. {oscar_python-1.3.0 → oscar_python-1.3.2}/tests/test_onedata.py +0 -0
  31. {oscar_python-1.3.0 → oscar_python-1.3.2}/tests/test_s3.py +0 -0
  32. {oscar_python-1.3.0 → oscar_python-1.3.2}/tests/test_webdav.py +0 -0
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: oscar_python
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Summary: OSCAR API for python
5
5
  Home-page: https://github.com/grycap/oscar_python
6
6
  Author: GRyCAP - Universitat Politecnica de Valencia
@@ -24,12 +24,13 @@ Dynamic: description
24
24
  Dynamic: description-content-type
25
25
  Dynamic: home-page
26
26
  Dynamic: license
27
+ Dynamic: license-file
27
28
  Dynamic: requires-dist
28
29
  Dynamic: summary
29
30
 
30
31
  ## Python OSCAR client
31
32
 
32
- [![Build](https://github.com/grycap/oscar_python/actions/workflows/main.yaml/badge.svg)](https://github.com/grycap/oscar_python/actions/workflows/main.yaml)
33
+ [![Build](https://github.com/grycap/oscar_python/actions/workflows/release.yaml/badge.svg)](https://github.com/grycap/oscar_python/actions/workflows/main.yaml)
33
34
  ![PyPI](https://img.shields.io/pypi/v/oscar_python)
34
35
 
35
36
  This package provides a client to interact with OSCAR (https://oscar.grycap.net) clusters and services. It is available on Pypi with the name [oscar-python](https://pypi.org/project/oscar-python/).
@@ -84,6 +85,33 @@ If you already have a valid token, you can use the parameter `oidc_token` instea
84
85
  ```
85
86
  An example of using a generated token is if you want to use EGI Notebooks. Since you can't use oidc-agent on the Notebook, you can make use of the generated token that EGI provides on path `/var/run/secrets/egi.eu/access_token`.
86
87
 
88
+ If you have a valid refresh token (long live token), you can use the parameter `refresh_token` instead.
89
+
90
+ ``` python
91
+ options_oidc_auth = {'cluster_id':'cluster-id',
92
+ 'endpoint':'https://cluster-endpoint',
93
+ 'refresh_token':'token',
94
+ 'ssl':'True'}
95
+
96
+ client = Client(options = options_oidc_auth)
97
+ ```
98
+
99
+ You can get a refresh token from EGI Check-In using the [Token Portal](https://aai.egi.eu/token).
100
+
101
+ In case of using other OIDC provider you must provide two additional parameters `token_endpoint`
102
+ and `scopes`:
103
+
104
+ ``` python
105
+ options_oidc_auth = {'cluster_id':'cluster-id',
106
+ 'endpoint':'https://cluster-endpoint',
107
+ 'refresh_token':'token',
108
+ 'scopes': ["openid", "profile", "email"],
109
+ 'token_endpoint': "http://issuer.com/token",
110
+ 'ssl':'True'}
111
+
112
+ client = Client(options = options_oidc_auth)
113
+ ```
114
+
87
115
  ### Sample usage
88
116
 
89
117
  - Sample code that creates a client and gets information about the cluster
@@ -219,11 +247,19 @@ response = client.remove_all_jobs("service_name") # returns an http response
219
247
 
220
248
  #### Storage usage
221
249
 
222
- You can create a storage object to operate over the different storage providers defined on a service with the method `create_storage_client` as follows:
250
+ You can create a storage object to operate over the different storage providers defined on a service with the method `create_storage_client`. This constructor returns a storage object with methos to interact with the storage providers.
251
+
252
+ The default constructor, seen as follows, will create a provider to interact with the default MinIO instance through the user's credentials.
253
+
254
+ ``` python
255
+ storage_service = client.create_storage_client() # returns a storage object
256
+ ```
257
+ Additionally, if you need to interact with specific storage providers defined on a service, the constructor accepts a `svc` parameter where you can state the service name from which to search for additional credentials.
223
258
 
224
259
  ``` python
225
260
  storage_service = client.create_storage_client("service_name") # returns a storage object
226
261
  ```
262
+
227
263
  > _Note_ : The `storage_provider` parameter on the storage methods follows the format: `["storage_provider_type"].["storage_provider_name"]` where `storage_provider_type` is one of the suported storage providers (minIO, S3, Onedata or webdav) and `storage_provider_name` is the identifier _(ex: minio.default)_
228
264
 
229
265
  **list_files_from_path**
@@ -232,7 +268,7 @@ This method returns a JSON with the info except for OneData, which returns an HT
232
268
 
233
269
  ``` python
234
270
  # get a list of the files of one of the service storage provider
235
- files = storage_service.list_files_from_path("storage_provider") # returns json
271
+ files = storage_service.list_files_from_path("storage_provider", "remote_path") # returns json
236
272
  ```
237
273
 
238
274
  **upload_file**
@@ -1,6 +1,6 @@
1
1
  ## Python OSCAR client
2
2
 
3
- [![Build](https://github.com/grycap/oscar_python/actions/workflows/main.yaml/badge.svg)](https://github.com/grycap/oscar_python/actions/workflows/main.yaml)
3
+ [![Build](https://github.com/grycap/oscar_python/actions/workflows/release.yaml/badge.svg)](https://github.com/grycap/oscar_python/actions/workflows/main.yaml)
4
4
  ![PyPI](https://img.shields.io/pypi/v/oscar_python)
5
5
 
6
6
  This package provides a client to interact with OSCAR (https://oscar.grycap.net) clusters and services. It is available on Pypi with the name [oscar-python](https://pypi.org/project/oscar-python/).
@@ -55,6 +55,33 @@ If you already have a valid token, you can use the parameter `oidc_token` instea
55
55
  ```
56
56
  An example of using a generated token is if you want to use EGI Notebooks. Since you can't use oidc-agent on the Notebook, you can make use of the generated token that EGI provides on path `/var/run/secrets/egi.eu/access_token`.
57
57
 
58
+ If you have a valid refresh token (long live token), you can use the parameter `refresh_token` instead.
59
+
60
+ ``` python
61
+ options_oidc_auth = {'cluster_id':'cluster-id',
62
+ 'endpoint':'https://cluster-endpoint',
63
+ 'refresh_token':'token',
64
+ 'ssl':'True'}
65
+
66
+ client = Client(options = options_oidc_auth)
67
+ ```
68
+
69
+ You can get a refresh token from EGI Check-In using the [Token Portal](https://aai.egi.eu/token).
70
+
71
+ In case of using other OIDC provider you must provide two additional parameters `token_endpoint`
72
+ and `scopes`:
73
+
74
+ ``` python
75
+ options_oidc_auth = {'cluster_id':'cluster-id',
76
+ 'endpoint':'https://cluster-endpoint',
77
+ 'refresh_token':'token',
78
+ 'scopes': ["openid", "profile", "email"],
79
+ 'token_endpoint': "http://issuer.com/token",
80
+ 'ssl':'True'}
81
+
82
+ client = Client(options = options_oidc_auth)
83
+ ```
84
+
58
85
  ### Sample usage
59
86
 
60
87
  - Sample code that creates a client and gets information about the cluster
@@ -190,11 +217,19 @@ response = client.remove_all_jobs("service_name") # returns an http response
190
217
 
191
218
  #### Storage usage
192
219
 
193
- You can create a storage object to operate over the different storage providers defined on a service with the method `create_storage_client` as follows:
220
+ You can create a storage object to operate over the different storage providers defined on a service with the method `create_storage_client`. This constructor returns a storage object with methos to interact with the storage providers.
221
+
222
+ The default constructor, seen as follows, will create a provider to interact with the default MinIO instance through the user's credentials.
223
+
224
+ ``` python
225
+ storage_service = client.create_storage_client() # returns a storage object
226
+ ```
227
+ Additionally, if you need to interact with specific storage providers defined on a service, the constructor accepts a `svc` parameter where you can state the service name from which to search for additional credentials.
194
228
 
195
229
  ``` python
196
230
  storage_service = client.create_storage_client("service_name") # returns a storage object
197
231
  ```
232
+
198
233
  > _Note_ : The `storage_provider` parameter on the storage methods follows the format: `["storage_provider_type"].["storage_provider_name"]` where `storage_provider_type` is one of the suported storage providers (minIO, S3, Onedata or webdav) and `storage_provider_name` is the identifier _(ex: minio.default)_
199
234
 
200
235
  **list_files_from_path**
@@ -203,7 +238,7 @@ This method returns a JSON with the info except for OneData, which returns an HT
203
238
 
204
239
  ``` python
205
240
  # get a list of the files of one of the service storage provider
206
- files = storage_service.list_files_from_path("storage_provider") # returns json
241
+ files = storage_service.list_files_from_path("storage_provider", "remote_path") # returns json
207
242
  ```
208
243
 
209
244
  **upload_file**
@@ -0,0 +1,96 @@
1
+ """
2
+ Class to manage OIDC JWT tokens
3
+ """
4
+ import json
5
+ import base64
6
+ import re
7
+ import time
8
+ import requests
9
+
10
+
11
+ class OIDC(object):
12
+
13
+ @staticmethod
14
+ def _b64d(b):
15
+ """Decode some base64-encoded bytes.
16
+
17
+ Raises Exception if the string contains invalid characters or padding.
18
+
19
+ :param b: bytes
20
+ """
21
+
22
+ cb = b.rstrip(b"=") # shouldn't but there you are
23
+
24
+ # Python's base64 functions ignore invalid characters, so we need to
25
+ # check for them explicitly.
26
+ b64_re = re.compile(b"^[A-Za-z0-9_-]*$")
27
+ if not b64_re.match(cb):
28
+ raise Exception(cb, "base64-encoded data contains illegal characters")
29
+
30
+ if cb == b:
31
+ b = OIDC._add_padding(b)
32
+
33
+ return base64.urlsafe_b64decode(b)
34
+
35
+ @staticmethod
36
+ def _add_padding(b):
37
+ # add padding chars
38
+ m = len(b) % 4
39
+ if m == 1:
40
+ # NOTE: for some reason b64decode raises *TypeError* if the
41
+ # padding is incorrect.
42
+ raise Exception(b, "incorrect padding")
43
+ elif m == 2:
44
+ b += b"=="
45
+ elif m == 3:
46
+ b += b"="
47
+ return b
48
+
49
+ @staticmethod
50
+ def get_info(token):
51
+ """
52
+ Unpacks a JWT into its parts and base64 decodes the parts
53
+ individually, returning the part 1 json decoded, where the
54
+ token info is stored.
55
+
56
+ :param token: The JWT token
57
+ """
58
+ part = tuple(token.encode("utf-8").split(b"."))
59
+ part = [OIDC._b64d(p) for p in part]
60
+ return json.loads(part[1].decode("utf-8"))
61
+
62
+ @staticmethod
63
+ def is_access_token_expired(token):
64
+ """
65
+ Check if the current access token is expired
66
+ """
67
+ try:
68
+ decoded_token = OIDC.get_info(token)
69
+ now = int(time.time())
70
+ expires = int(decoded_token['exp'])
71
+ validity = expires - now
72
+ if validity < 0:
73
+ return True
74
+ else:
75
+ return False
76
+ except Exception:
77
+ return True
78
+
79
+ @staticmethod
80
+ def refresh_access_token(refresh_token, scopes, token_endpoint):
81
+ """
82
+ Refresh the access token using the refresh token
83
+ """
84
+ data = {
85
+ 'grant_type': 'refresh_token',
86
+ 'refresh_token': refresh_token,
87
+ 'client_id': 'token-portal',
88
+ 'scope': ' '.join(scopes)
89
+ }
90
+
91
+ response = requests.post(token_endpoint, data=data)
92
+
93
+ if response.status_code == 200:
94
+ return response.json()['access_token']
95
+ else:
96
+ return None
@@ -56,7 +56,7 @@ def get_headers(c):
56
56
  token = agent.get_access_token(c.shortname)
57
57
  return get_headers_with_token(token)
58
58
  if c._AUTH_TYPE == "oidc":
59
- return get_headers_with_token(c.oidc_token)
59
+ return get_headers_with_token(c.get_access_token())
60
60
 
61
61
 
62
62
  def get_headers_with_token(token):
@@ -18,6 +18,7 @@ import os
18
18
  import yaml
19
19
  import liboidcagent as agent
20
20
  import oscar_python._utils as utils
21
+ from oscar_python._oidc import OIDC
21
22
  from oscar_python.default_client import DefaultClient
22
23
  from oscar_python.storage import Storage
23
24
 
@@ -37,10 +38,16 @@ _POST = "post"
37
38
  _PUT = "put"
38
39
  _DELETE = "delete"
39
40
 
41
+ # Default values for OIDC refresh token using EGI CheckIn
42
+ _DEFAULT_SCOPES = ['openid', 'email', 'profile', 'voperson_id', 'eduperson_entitlement']
43
+ _DEFAULT_TOKEN_ENDPOINT = 'https://aai.egi.eu/auth/realms/egi/protocol/openid-connect/token'
44
+
40
45
 
41
46
  class Client(DefaultClient):
42
47
  # Cluster info
43
48
  def __init__(self, options) -> None:
49
+ self.id = options['cluster_id']
50
+ self.endpoint = options['endpoint']
44
51
  self.set_auth_type(options)
45
52
  if self._AUTH_TYPE == 'basicauth':
46
53
  self.basic_auth_client(options)
@@ -50,22 +57,20 @@ class Client(DefaultClient):
50
57
  self.oidc_client(options)
51
58
 
52
59
  def basic_auth_client(self, options):
53
- self.id = options['cluster_id']
54
- self.endpoint = options['endpoint']
55
60
  self.user = options['user']
56
61
  self.password = options['password']
57
62
  self.ssl = bool(options['ssl'])
58
63
 
59
64
  def oidc_agent_client(self, options):
60
- self.id = options['cluster_id']
61
- self.endpoint = options['endpoint']
62
65
  self.shortname = options['shortname']
63
66
  self.ssl = bool(options['ssl'])
64
67
 
65
68
  def oidc_client(self, options):
66
- self.id = options['cluster_id']
67
- self.endpoint = options['endpoint']
68
- self.oidc_token = options['oidc_token']
69
+ self.oidc_token = options.get('oidc_token')
70
+ self.refresh_token = options.get('refresh_token')
71
+ self.scopes = options.get('scopes', _DEFAULT_SCOPES)
72
+ self.token_endpoint = options.get('token_endpoint',
73
+ _DEFAULT_TOKEN_ENDPOINT)
69
74
  self.ssl = bool(options['ssl'])
70
75
 
71
76
  def set_auth_type(self, options):
@@ -77,18 +82,28 @@ class Client(DefaultClient):
77
82
  agent.get_access_token(options['shortname'])
78
83
  except agent.OidcAgentError as e:
79
84
  print("ERROR oidc-agent: {}".format(e))
80
- elif 'oidc_token' in options:
85
+ elif 'oidc_token' in options or 'refresh_token' in options:
81
86
  self._AUTH_TYPE = "oidc"
82
87
  else:
83
88
  raise ValueError("Unrecognized authentication credentials in options")
84
89
 
90
+ def get_access_token(self):
91
+ if self.refresh_token and OIDC.is_access_token_expired(self.oidc_token):
92
+ self.oidc_token = OIDC.refresh_access_token(self.refresh_token,
93
+ self.scopes,
94
+ self.token_endpoint)
95
+ return self.oidc_token
96
+
85
97
  """ Creates a generic storage client to interact with the storage providers
86
98
  defined on a specific service of the refered OSCAR cluster """
87
- def create_storage_client(self, svc):
88
- return Storage(
89
- client_obj=self,
90
- svc_name=svc)
91
-
99
+ def create_storage_client(self, svc=None):
100
+ if svc != None:
101
+ return Storage(
102
+ client_obj=self, svc_name=svc)
103
+ else:
104
+ return Storage(
105
+ client_obj=self)
106
+
92
107
  """ Function to get cluster info """
93
108
  def get_cluster_info(self):
94
109
  return utils.make_request(self, _INFO_PATH, _GET)
@@ -23,21 +23,37 @@ _MINIO = "minio"
23
23
  _S3 = "s3"
24
24
  _ONE_DATA = "onedata"
25
25
  _WEBDAV = "webdav"
26
+
27
+ _GET = "get"
28
+ _CONFIG_PATH = "/system/config"
26
29
  _SVC_PATH = "/system/services"
27
30
 
28
31
 
29
32
  # TODO check returns from functions
30
33
  class Storage:
31
- def __init__(self, client_obj, svc_name) -> None:
34
+ def __init__(self, client_obj, svc_name = None) -> None:
32
35
  self.client_obj = client_obj
33
- self.svc_name = svc_name
34
- self._store_providers()
36
+ self.storage_providers = {}
37
+ if svc_name != None:
38
+ self.svc_name = svc_name
39
+ self._store_provider_from_service()
40
+ self._store_default_minio_provider()
35
41
 
36
42
  """ Function to store all the providers of the service """
37
- def _store_providers(self):
38
- svc = utils.make_request(self.client_obj, _SVC_PATH + "/" + self.svc_name, "get")
43
+ def _store_provider_from_service(self):
44
+ svc = utils.make_request(self.client_obj, _SVC_PATH + "/" + self.svc_name, _GET)
39
45
  self.storage_providers = json.loads(svc.text)["storage_providers"]
40
46
 
47
+ """ Function to store the user credentials for the default MinIO provider """
48
+ def _store_default_minio_provider(self):
49
+ config = utils.make_request(self.client_obj, _CONFIG_PATH + "/" , _GET)
50
+ if _MINIO in self.storage_providers:
51
+ self.storage_providers[_MINIO]["default"] = json.loads(config.text)["minio_provider"]
52
+ else:
53
+ default = {"default": json.loads(config.text)["minio_provider"]}
54
+ self.storage_providers[_MINIO] = default
55
+
56
+
41
57
  """ Function to retreive credentials of a specific storage provider """
42
58
  def _get_provider_creds(self, provider, provider_name):
43
59
  return self.storage_providers[provider][provider_name]
@@ -1,6 +1,6 @@
1
- Metadata-Version: 2.2
1
+ Metadata-Version: 2.4
2
2
  Name: oscar_python
3
- Version: 1.3.0
3
+ Version: 1.3.2
4
4
  Summary: OSCAR API for python
5
5
  Home-page: https://github.com/grycap/oscar_python
6
6
  Author: GRyCAP - Universitat Politecnica de Valencia
@@ -24,12 +24,13 @@ Dynamic: description
24
24
  Dynamic: description-content-type
25
25
  Dynamic: home-page
26
26
  Dynamic: license
27
+ Dynamic: license-file
27
28
  Dynamic: requires-dist
28
29
  Dynamic: summary
29
30
 
30
31
  ## Python OSCAR client
31
32
 
32
- [![Build](https://github.com/grycap/oscar_python/actions/workflows/main.yaml/badge.svg)](https://github.com/grycap/oscar_python/actions/workflows/main.yaml)
33
+ [![Build](https://github.com/grycap/oscar_python/actions/workflows/release.yaml/badge.svg)](https://github.com/grycap/oscar_python/actions/workflows/main.yaml)
33
34
  ![PyPI](https://img.shields.io/pypi/v/oscar_python)
34
35
 
35
36
  This package provides a client to interact with OSCAR (https://oscar.grycap.net) clusters and services. It is available on Pypi with the name [oscar-python](https://pypi.org/project/oscar-python/).
@@ -84,6 +85,33 @@ If you already have a valid token, you can use the parameter `oidc_token` instea
84
85
  ```
85
86
  An example of using a generated token is if you want to use EGI Notebooks. Since you can't use oidc-agent on the Notebook, you can make use of the generated token that EGI provides on path `/var/run/secrets/egi.eu/access_token`.
86
87
 
88
+ If you have a valid refresh token (long live token), you can use the parameter `refresh_token` instead.
89
+
90
+ ``` python
91
+ options_oidc_auth = {'cluster_id':'cluster-id',
92
+ 'endpoint':'https://cluster-endpoint',
93
+ 'refresh_token':'token',
94
+ 'ssl':'True'}
95
+
96
+ client = Client(options = options_oidc_auth)
97
+ ```
98
+
99
+ You can get a refresh token from EGI Check-In using the [Token Portal](https://aai.egi.eu/token).
100
+
101
+ In case of using other OIDC provider you must provide two additional parameters `token_endpoint`
102
+ and `scopes`:
103
+
104
+ ``` python
105
+ options_oidc_auth = {'cluster_id':'cluster-id',
106
+ 'endpoint':'https://cluster-endpoint',
107
+ 'refresh_token':'token',
108
+ 'scopes': ["openid", "profile", "email"],
109
+ 'token_endpoint': "http://issuer.com/token",
110
+ 'ssl':'True'}
111
+
112
+ client = Client(options = options_oidc_auth)
113
+ ```
114
+
87
115
  ### Sample usage
88
116
 
89
117
  - Sample code that creates a client and gets information about the cluster
@@ -219,11 +247,19 @@ response = client.remove_all_jobs("service_name") # returns an http response
219
247
 
220
248
  #### Storage usage
221
249
 
222
- You can create a storage object to operate over the different storage providers defined on a service with the method `create_storage_client` as follows:
250
+ You can create a storage object to operate over the different storage providers defined on a service with the method `create_storage_client`. This constructor returns a storage object with methos to interact with the storage providers.
251
+
252
+ The default constructor, seen as follows, will create a provider to interact with the default MinIO instance through the user's credentials.
253
+
254
+ ``` python
255
+ storage_service = client.create_storage_client() # returns a storage object
256
+ ```
257
+ Additionally, if you need to interact with specific storage providers defined on a service, the constructor accepts a `svc` parameter where you can state the service name from which to search for additional credentials.
223
258
 
224
259
  ``` python
225
260
  storage_service = client.create_storage_client("service_name") # returns a storage object
226
261
  ```
262
+
227
263
  > _Note_ : The `storage_provider` parameter on the storage methods follows the format: `["storage_provider_type"].["storage_provider_name"]` where `storage_provider_type` is one of the suported storage providers (minIO, S3, Onedata or webdav) and `storage_provider_name` is the identifier _(ex: minio.default)_
228
264
 
229
265
  **list_files_from_path**
@@ -232,7 +268,7 @@ This method returns a JSON with the info except for OneData, which returns an HT
232
268
 
233
269
  ``` python
234
270
  # get a list of the files of one of the service storage provider
235
- files = storage_service.list_files_from_path("storage_provider") # returns json
271
+ files = storage_service.list_files_from_path("storage_provider", "remote_path") # returns json
236
272
  ```
237
273
 
238
274
  **upload_file**
@@ -2,6 +2,7 @@ LICENSE
2
2
  README.md
3
3
  setup.py
4
4
  oscar_python/__init__.py
5
+ oscar_python/_oidc.py
5
6
  oscar_python/_utils.py
6
7
  oscar_python/client.py
7
8
  oscar_python/client_anon.py
@@ -21,6 +22,7 @@ oscar_python/_providers/_s3.py
21
22
  oscar_python/_providers/_webdav.py
22
23
  tests/test_client.py
23
24
  tests/test_default_client.py
25
+ tests/test_oidc.py
24
26
  tests/test_onedata.py
25
27
  tests/test_s3.py
26
28
  tests/test_storage.py
@@ -44,6 +44,15 @@ def test_oidc_client(options):
44
44
  assert client.oidc_token == options['oidc_token']
45
45
  assert client.ssl == options['ssl']
46
46
 
47
+ del options['oidc_token']
48
+ options['refresh_token'] = 'test_refresh_token'
49
+ options['scopes'] = ['openid', 'profile', 'email']
50
+ options['token_endpoint'] = 'test_token_endpoint'
51
+ client = Client(options)
52
+ assert client.refresh_token == options['refresh_token']
53
+ assert client.scopes == ['openid', 'profile', 'email']
54
+ assert client.token_endpoint == options['token_endpoint']
55
+
47
56
 
48
57
  def test_set_auth_type(options):
49
58
  client = Client(options)
@@ -0,0 +1,38 @@
1
+ from unittest.mock import patch, MagicMock
2
+ from oscar_python._oidc import OIDC
3
+
4
+
5
+ def test_is_access_token_expired():
6
+ token = ("eyJraWQiOiJyc2ExIiwiYWxnIjoiUlMyNTYifQ.eyJzdWIiOiJkYzVkNWFiNy02ZGI5LTQwNzktOTg1Yy04MGFjMDUwMTcw"
7
+ "NjYiLCJpc3MiOiJodHRwczpcL1wvaWFtLXRlc3QuaW5kaWdvLWRhdGFjbG91ZC5ldVwvIiwiZXhwIjoxNDY2MDkzOTE3LCJ"
8
+ "pYXQiOjE0NjYwOTAzMTcsImp0aSI6IjE1OTU2N2U2LTdiYzItNDUzOC1hYzNhLWJjNGU5MmE1NjlhMCJ9.eINKxJa2J--xd"
9
+ "GAZWIOKtx9Wi0Vz3xHzaSJWWY-UHWy044TQ5xYtt0VTvmY5Af-ngwAMGfyaqAAvNn1VEP-_fMYQZdwMqcXLsND4KkDi1ygiC"
10
+ "IwQ3JBz9azBT1o_oAHE5BsPsE2BjfDoVRasZxxW5UoXCmBslonYd8HK2tUVjz0")
11
+ assert OIDC.is_access_token_expired(token)
12
+
13
+
14
+ def test_refresh_access_token():
15
+ mock_response = MagicMock()
16
+ mock_response.status_code = 200
17
+ mock_response.json.return_value = {
18
+ "access_token": "new_access_token",
19
+ "expires_in": 3600,
20
+ "refresh_token": "new_refresh_token"
21
+ }
22
+
23
+ with patch("requests.post") as mock_post:
24
+ mock_post.return_value = mock_response
25
+ access_token = OIDC.refresh_access_token("old_refresh_token",
26
+ ["openid", "profile", "email"],
27
+ "http://test.com/token")
28
+
29
+ assert access_token == "new_access_token"
30
+ mock_post.assert_called_once_with(
31
+ "http://test.com/token",
32
+ data={
33
+ "grant_type": "refresh_token",
34
+ "refresh_token": "old_refresh_token",
35
+ "client_id": "token-portal",
36
+ "scope": "openid profile email"
37
+ }
38
+ )
@@ -11,9 +11,9 @@ def mock_client_obj():
11
11
  @pytest.fixture
12
12
  def storage(mock_client_obj):
13
13
  mock_response = MagicMock()
14
- mock_response.text = '{"storage_providers": {"minio": {"default": {"access_key": "key","secret_key": "secret", "endpoint": "http://test.endpoint", "region": "us-east-1", "verify": false}}}}'
14
+ mock_response.text = '{"minio_provider": {"access_key": "key","secret_key": "secret", "endpoint": "http://test.endpoint", "region": "us-east-1", "verify": false}}'
15
15
  with patch('oscar_python._utils.make_request', return_value=mock_response):
16
- return Storage(mock_client_obj, "test_service")
16
+ return Storage(mock_client_obj)
17
17
 
18
18
 
19
19
  def test_get_provider_creds(storage):
@@ -29,7 +29,9 @@ def test_get_headers_with_oidc_agent():
29
29
  def test_get_headers_with_oidc():
30
30
  class MockClient:
31
31
  _AUTH_TYPE = "oidc"
32
- oidc_token = "test_oidc_token"
32
+
33
+ def get_access_token(self):
34
+ return "test_oidc_token"
33
35
 
34
36
  c = MockClient()
35
37
  headers = utils.get_headers(c)
File without changes
File without changes
File without changes