deriva 1.7.10__py3-none-any.whl → 1.7.11__py3-none-any.whl

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.
deriva/core/__init__.py CHANGED
@@ -1,4 +1,4 @@
1
- __version__ = "1.7.10"
1
+ __version__ = "1.7.11"
2
2
 
3
3
  from deriva.core.utils.core_utils import *
4
4
  from deriva.core.base_cli import BaseCLI, KeyValuePairArgs
@@ -0,0 +1,299 @@
1
+ import sys
2
+ import json
3
+ import logging
4
+ import traceback
5
+ from pprint import pprint
6
+ from requests.exceptions import HTTPError, ConnectionError
7
+ from bdbag.fetch.auth import keychain as bdbkc
8
+ from deriva.core import DEFAULT_CREDENTIAL_FILE, read_credential, write_credential, format_exception, BaseCLI, \
9
+ get_new_requests_session, urlparse, urljoin
10
+ from deriva.core.utils import eprint
11
+
12
+ logger = logging.getLogger(__name__)
13
+
14
+
15
+ def host_to_url(host, path="/", protocol="https"):
16
+ if not host:
17
+ return None
18
+ upr = urlparse(host)
19
+ if upr.scheme and upr.netloc:
20
+ url = urljoin(host, path)
21
+ else:
22
+ url = "%s://%s%s" % (protocol, host, path if not host.endswith("/") else "")
23
+ return url.lower()
24
+
25
+ class UsageException(ValueError):
26
+ """Usage exception.
27
+ """
28
+ def __init__(self, message):
29
+ """Initializes the exception.
30
+ """
31
+ super(UsageException, self).__init__(message)
32
+
33
+ class CredenzaAuthUtilCLI(BaseCLI):
34
+ """CredenzaAuthUtil Command-line Interface.
35
+ """
36
+ def __init__(self, *args, **kwargs):
37
+ super(CredenzaAuthUtilCLI, self).__init__(*args, **kwargs)
38
+ self.remove_options(['--token', '--oauth2-token'])
39
+ self.parser.add_argument("--pretty", "-p", action="store_true",
40
+ help="Pretty-print all result output.")
41
+ self.credentials = None
42
+
43
+ # init subparsers and corresponding functions
44
+ self.subparsers = self.parser.add_subparsers(title='sub-commands', dest='subcmd')
45
+ self.login_init()
46
+ self.logout_init()
47
+ self.get_session_init()
48
+ self.put_session_init()
49
+
50
+ @staticmethod
51
+ def update_bdbag_keychain(token=None, host=None, keychain_file=None, allow_redirects=False, delete=False):
52
+ if (token is None) or (host is None):
53
+ return
54
+ keychain_file = keychain_file or bdbkc.DEFAULT_KEYCHAIN_FILE
55
+ entry = {
56
+ "uri": host_to_url(host),
57
+ "auth_type": "bearer-token",
58
+ "auth_params": {
59
+ "token": token,
60
+ "allow_redirects_with_token": True if allow_redirects else False
61
+ }
62
+ }
63
+ bdbkc.update_keychain(entry, keychain_file=keychain_file, delete=delete)
64
+
65
+ def load_credential(self, host, credential_file=None):
66
+ if not self.credentials:
67
+ self.credentials = read_credential(credential_file or DEFAULT_CREDENTIAL_FILE, create_default=True)
68
+ credential = self.credentials.get(host, self.credentials.get(host.lower()))
69
+ if not credential:
70
+ return None
71
+ return credential.get("bearer-token")
72
+
73
+ def save_credential(self, host, credential_file=None, credential=None):
74
+ if not self.credentials:
75
+ self.credentials = read_credential(credential_file or DEFAULT_CREDENTIAL_FILE, create_default=True)
76
+
77
+ if credential is not None:
78
+ self.credentials[host] = {"bearer-token": credential}
79
+ else:
80
+ self.credentials.pop(host, None)
81
+
82
+ write_credential(credential_file or DEFAULT_CREDENTIAL_FILE, self.credentials)
83
+
84
+ def get_session(self, args, check_only=False):
85
+ credential = self.load_credential(args.host, args.credential_file)
86
+ if not credential:
87
+ return None if check_only else "Credential not found. Login required."
88
+
89
+ url = host_to_url(args.host, "/authn/session")
90
+ session = get_new_requests_session(url)
91
+ session.headers.update({"Authorization": f"Bearer {credential}"})
92
+ response = session.get(url)
93
+
94
+ if response.status_code == 200:
95
+ return response.json()
96
+ elif response.status_code == 404:
97
+ return None if check_only else f"No valid session found for host '{args.host}'."
98
+ else:
99
+ response.raise_for_status()
100
+ return None
101
+
102
+ def put_session(self, args):
103
+ credential = self.load_credential(args.host, args.credential_file)
104
+ if not credential:
105
+ return "Credential not found. Login required."
106
+
107
+ path = "/authn/session"
108
+ if args.refresh_upstream:
109
+ path += "?refresh_upstream=true"
110
+ url = host_to_url(args.host, path)
111
+ session = get_new_requests_session(url)
112
+ session.headers.update({"Authorization": f"Bearer {credential}"})
113
+ response = session.put(url)
114
+
115
+ if response.status_code == 200:
116
+ return response.json()
117
+ elif response.status_code == 404:
118
+ return f"No valid session found for host '{args.host}'."
119
+ else:
120
+ response.raise_for_status()
121
+ return None
122
+
123
+ def login(self, args):
124
+
125
+ if not sys.stdin.isatty():
126
+ raise RuntimeError("Interactive TTY required for device login.")
127
+
128
+ if not args.force:
129
+ resp = self.get_session(args, check_only=True)
130
+ if resp:
131
+ return f"You are already logged in to host '{args.host}'"
132
+
133
+ path = "/authn/device/start"
134
+ if args.refresh:
135
+ path += "?refresh=true"
136
+ url = host_to_url(args.host, path)
137
+ session = get_new_requests_session(url)
138
+ response = session.post(url)
139
+ response.raise_for_status()
140
+ body = response.json()
141
+ verification_url = body["verification_uri"]
142
+
143
+ login_prompt = f"""
144
+
145
+ Device login initiated to {args.host}.
146
+
147
+ 1. Please visit {verification_url} in a browser to complete authentication.
148
+ 2. After that, return here and enter "y" or "yes" at the prompt below to proceed.
149
+
150
+ """
151
+ sys.stdout.write(login_prompt)
152
+ sys.stdout.flush()
153
+ try:
154
+ while True:
155
+ sys.stdout.write("\nProceed? (y/N): ")
156
+ sys.stdout.flush()
157
+ response = sys.stdin.readline()
158
+
159
+ ans = response.strip().lower()
160
+ if ans in {"y", "yes"}:
161
+ break
162
+ elif ans in {"n", "no", ""}: # default is No on empty/enter
163
+ return f"Login cancelled for '{args.host}'."
164
+ except KeyboardInterrupt:
165
+ sys.stdout.write("\nCancelled by user (Ctrl-C).\n")
166
+ return f"Login cancelled for '{args.host}'."
167
+
168
+ token_response = session.post(
169
+ f"https://{args.host}/authn/device/token",
170
+ json={"device_code": body["device_code"]}
171
+ )
172
+ token_response.raise_for_status()
173
+ body = token_response.json()
174
+ token = body["access_token"]
175
+ self.save_credential(args.host, args.credential_file, token)
176
+ if not args.no_bdbag_keychain:
177
+ self.update_bdbag_keychain(host=args.host,
178
+ token=token,
179
+ keychain_file=args.bdbag_keychain_file or bdbkc.DEFAULT_KEYCHAIN_FILE)
180
+ token_display = f"Access token: {token}" if args.show_token else ""
181
+ return f"You have been successfully logged in to host '{args.host}'. {token_display}"
182
+
183
+ def logout(self, args):
184
+ credential = self.load_credential(args.host, args.credential_file)
185
+ if not credential:
186
+ return "Credential not found. Not logged in."
187
+
188
+ url = host_to_url(args.host, "/authn/device/logout")
189
+ session = get_new_requests_session(url)
190
+ session.headers.update({"Authorization": f"Bearer {credential}"})
191
+ response = session.post(url)
192
+ response.raise_for_status()
193
+
194
+ self.save_credential(args.host, args.credential_file)
195
+ if not args.no_bdbag_keychain:
196
+ self.update_bdbag_keychain(host=args.host,
197
+ token=credential,
198
+ delete=True,
199
+ keychain_file=args.bdbag_keychain_file or bdbkc.DEFAULT_KEYCHAIN_FILE)
200
+
201
+ return f"You have been successfully logged out of host '{args.host}'."
202
+
203
+ def login_init(self):
204
+ parser = self.subparsers.add_parser('login',
205
+ help="Login with Globus Auth and get OAuth tokens for resource access.")
206
+
207
+ parser.add_argument("--no-bdbag-keychain", action="store_true",
208
+ help="Do not update the bdbag keychain file with result access tokens. Default false.")
209
+ parser.add_argument('--bdbag-keychain-file', metavar='<file>',
210
+ help="Non-default path to a bdbag keychain file.")
211
+ parser.add_argument("--refresh", action="store_true",
212
+ help="Allow the session manager to automatically refresh access tokens on the user's behalf "
213
+ "until either the refresh token expires or the user logs out.")
214
+ parser.add_argument("--force", action="store_true",
215
+ help="Force a login flow even if the current access token is valid.")
216
+ parser.add_argument("--show-token", action="store_true",
217
+ help="Display the token from the authorization response.")
218
+ parser.set_defaults(func=self.login)
219
+
220
+ def logout_init(self):
221
+ parser = self.subparsers.add_parser("logout", help="Logout and revoke all access and refresh tokens.")
222
+ parser.add_argument("--no-bdbag-keychain", action="store_true",
223
+ help="Do not update the bdbag keychain file by removing access tokens on logout. Default false.")
224
+ parser.add_argument('--bdbag-keychain-file', metavar='<file>',
225
+ help="Non-default path to a bdbag keychain file.")
226
+ parser.set_defaults(func=self.logout)
227
+
228
+ def get_session_init(self):
229
+ parser = self.subparsers.add_parser("get-session",
230
+ help="Retrieve information about the currently logged-in user.")
231
+ parser.set_defaults(func=self.get_session)
232
+
233
+ def put_session_init(self):
234
+ parser = self.subparsers.add_parser("put-session",
235
+ help="Extend the current logged-in user's session.")
236
+ parser.add_argument("--refresh-upstream", action="store_true",
237
+ help="Attempt to refresh access tokens, other dependent tokens, and claims from the "
238
+ "upstream identity provider.")
239
+ parser.set_defaults(func=self.put_session)
240
+
241
+ def main(self):
242
+ args = self.parse_cli()
243
+
244
+ def _cmd_error_message(emsg):
245
+ return "{prog} {subcmd}: {msg}".format(
246
+ prog=self.parser.prog, subcmd=args.subcmd, msg=emsg)
247
+
248
+ try:
249
+ if not hasattr(args, 'func'):
250
+ self.parser.print_usage()
251
+ return 2
252
+
253
+ if args.subcmd == "login" or args.subcmd == "logout" or args.subcmd == "session":
254
+ pass
255
+ else:
256
+ pass
257
+
258
+ response = args.func(args)
259
+ if args.pretty:
260
+ if isinstance(response, dict) or isinstance(response, list):
261
+ try:
262
+ print(json.dumps(response, indent=2))
263
+ return 0
264
+ except:
265
+ pprint(response)
266
+ return 0
267
+ elif not isinstance(response, str):
268
+ pprint(response)
269
+ return 0
270
+ print(response)
271
+ return 0
272
+
273
+ except UsageException as e:
274
+ eprint("{prog} {subcmd}: {msg}".format(prog=self.parser.prog, subcmd=args.subcmd, msg=e))
275
+ except ConnectionError as e:
276
+ eprint("{prog}: Connection error occurred".format(prog=self.parser.prog))
277
+ except HTTPError as e:
278
+ if 401 == e.response.status_code:
279
+ msg = 'Authentication required: %s' % format_exception(e)
280
+ elif 403 == e.response.status_code:
281
+ msg = 'Permission denied: %s' % format_exception(e)
282
+ else:
283
+ msg = e
284
+ eprint(_cmd_error_message(msg))
285
+ except RuntimeError as e:
286
+ logging.debug(format_exception(e))
287
+ eprint('An unexpected runtime error occurred')
288
+ except:
289
+ eprint('An unexpected error occurred')
290
+ traceback.print_exc()
291
+ return 1
292
+
293
+ def main():
294
+ desc = "Credenza Auth Utilities"
295
+ info = "For more information see: https://github.com/informatics-isi-edu/deriva-py"
296
+ return CredenzaAuthUtilCLI(desc, info).main()
297
+
298
+ if __name__ == '__main__':
299
+ sys.exit(main())
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deriva
3
- Version: 1.7.10
3
+ Version: 1.7.11
4
4
  Summary: Python APIs and CLIs (Command-Line Interfaces) for the DERIVA platform.
5
5
  Home-page: https://github.com/informatics-isi-edu/deriva-py
6
6
  Author: USC Information Sciences Institute, Informatics Systems Research Division
@@ -15,7 +15,6 @@ Classifier: Operating System :: POSIX
15
15
  Classifier: Operating System :: MacOS :: MacOS X
16
16
  Classifier: Operating System :: Microsoft :: Windows
17
17
  Classifier: Programming Language :: Python
18
- Classifier: Programming Language :: Python :: 3.8
19
18
  Classifier: Programming Language :: Python :: 3.9
20
19
  Classifier: Programming Language :: Python :: 3.10
21
20
  Classifier: Programming Language :: Python :: 3.11
@@ -28,7 +27,7 @@ Requires-Dist: requests
28
27
  Requires-Dist: pika
29
28
  Requires-Dist: urllib3<3,>=1.26.20
30
29
  Requires-Dist: portalocker>=1.2.1
31
- Requires-Dist: bdbag>=1.7.5
30
+ Requires-Dist: bdbag>=1.8.0
32
31
  Requires-Dist: globus_sdk<4,>=3
33
32
  Requires-Dist: fair-research-login>=0.3.1
34
33
  Requires-Dist: fair-identifiers-client>=0.5.1
@@ -8,7 +8,7 @@ deriva/config/dump_catalog_annotations.py,sha256=QzaWDLfWIAQ0eWVV11zeceWgwDBOYIe
8
8
  deriva/config/rollback_annotation.py,sha256=vqrIcen-KZX8LDpu2OVNivzIHpQoQgWkZAChZJctvtk,3015
9
9
  deriva/config/examples/group_owner_policy.json,sha256=8v3GWM1F_BWnYD9x_f6Eo4kBDvyy8g7mRqujfoEKLNc,2408
10
10
  deriva/config/examples/self_serve_policy.json,sha256=pW-cqWz4rJNNXwY4eVZFkQ8gKCHclC9yDa22ylfcDqY,1676
11
- deriva/core/__init__.py,sha256=2YJow_azmav6XbSDKH_fIO7or9_VyHdN1Od8_H-T964,4976
11
+ deriva/core/__init__.py,sha256=u3pzQuTb0mKvl0GenoQWGuiUMDDdzjVnoPXHmX5pdwY,4976
12
12
  deriva/core/annotation.py,sha256=PkAkPkxX1brQsb8_drR1Qj5QjQA5mjkpXhkq9NuZ1g8,13432
13
13
  deriva/core/base_cli.py,sha256=78Ilf3_f2xREQb3IIj6q0jwWAiXSObZszG0JURs36lA,2902
14
14
  deriva/core/catalog_cli.py,sha256=CwfTf7C81SpU1J_aPsWiIbPOBAyekkIh384KUivq5H8,23550
@@ -45,6 +45,7 @@ deriva/core/schemas/visible_foreign_keys.schema.json,sha256=K-oa2qzj5EbmJCEyN6mN
45
45
  deriva/core/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
46
46
  deriva/core/utils/__init__.py,sha256=XSbGaWe44hebxYvoh5huFzZkMY6TSKPOCRSjUOvaY70,124
47
47
  deriva/core/utils/core_utils.py,sha256=bMPVBG0W5-OAPcY7rXYlIfPAofmN1We6h1uyzXYnR9s,19821
48
+ deriva/core/utils/credenza_auth_utils.py,sha256=JX-sOwSltfmXbDWdaWJHP5gCMOOBkELKEsnnhqxoRk8,12435
48
49
  deriva/core/utils/globus_auth_utils.py,sha256=x5Dh4PlAMIKTm4b8nKUeuCMSFn7NMgj_NqtmnS_FM0I,57366
49
50
  deriva/core/utils/hash_utils.py,sha256=JqUYVB3jXusCQYX9fkKmweUKBC0WQi8ZI2N8m-uKygQ,2299
50
51
  deriva/core/utils/mime_utils.py,sha256=ZT7pMjY2kQWBsgsGC3jY6kjfygdsIyiYW3BKNw_pPyg,1128
@@ -97,20 +98,9 @@ deriva/transfer/upload/processors/metadata_update_processor.py,sha256=Hgu5huZf7Z
97
98
  deriva/transfer/upload/processors/rename_processor.py,sha256=UQ-JQuQgyYCGT-fU9kHA53kPdQ20kt-2Bb486od7B14,2423
98
99
  deriva/transfer/upload/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
99
100
  deriva/utils/__init__.py,sha256=jv2YF__bseklT3OWEzlqJ5qE24c4aWd5F4r0TTjOrWQ,65
100
- deriva-1.7.10.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
101
- tests/deriva/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
102
- tests/deriva/core/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
103
- tests/deriva/core/test_datapath.py,sha256=ft7qDp9peomnhtREry5et4QDO4RyfCCBSE6YnNDNYe8,40225
104
- tests/deriva/core/test_ermrest_model.py,sha256=tmWmAgXTcLBueY4iUS14uq-b1SjgKMaFGt2lw9mYvbg,34330
105
- tests/deriva/core/mmo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
106
- tests/deriva/core/mmo/base.py,sha256=3iLqUDNdiJaERB-8-G-5NEvkJr9fz0YUMvPxl5hMINE,9356
107
- tests/deriva/core/mmo/test_mmo_drop.py,sha256=R_yl-PIHGx-3kchT5TH8O9TJktepWjgs6hHISOc6d2k,8930
108
- tests/deriva/core/mmo/test_mmo_find.py,sha256=PcUN76sik68B3XKg0G3wHVpKcPEld_6RtbxeGzkrMQ8,4172
109
- tests/deriva/core/mmo/test_mmo_prune.py,sha256=4pYtYL8g1BgadlewNPVpVA5lT_gV6SPTDYf04ZKzBTA,6851
110
- tests/deriva/core/mmo/test_mmo_rename.py,sha256=4oSR1G3Od701Ss3AnolI1Z7CbMxKuQF2uSr2_IcoR6s,8512
111
- tests/deriva/core/mmo/test_mmo_replace.py,sha256=w-66LWyiQ_ajC7Ipmhc4kAKwIloPdQELeUPsvelTdX8,8439
112
- deriva-1.7.10.dist-info/METADATA,sha256=OrMw19lEt6tH9VyJq5a8wL4WpeIu-Wr1a8rjrJLKNdk,1891
113
- deriva-1.7.10.dist-info/WHEEL,sha256=0CuiUZ_p9E4cD6NyLD6UG80LBXYyiSYZOKDm5lp32xk,91
114
- deriva-1.7.10.dist-info/entry_points.txt,sha256=HmYCHlgbjYQ_aZX_j4_4tApH4tDTbYtS66jKlfytbn8,850
115
- deriva-1.7.10.dist-info/top_level.txt,sha256=_LHDie5-O53wFlexfrxjewpVkf04oydf3CqX5h75DXE,13
116
- deriva-1.7.10.dist-info/RECORD,,
101
+ deriva-1.7.11.dist-info/licenses/LICENSE,sha256=tAkwu8-AdEyGxGoSvJ2gVmQdcicWw3j1ZZueVV74M-E,11357
102
+ deriva-1.7.11.dist-info/METADATA,sha256=8K51AvD17zR_kZpJf2PDUA8ObbFUbSBSuIADf_9yS_M,1840
103
+ deriva-1.7.11.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
104
+ deriva-1.7.11.dist-info/entry_points.txt,sha256=uS-e7FxTP7lVm-QJdq_ErytmAbXvlRkEvrjab33ctRE,922
105
+ deriva-1.7.11.dist-info/top_level.txt,sha256=OoTnCAuRnf-pPKMre5HA7mgLMIPl_2-lAWoZ63y74-U,7
106
+ deriva-1.7.11.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.3.1)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
5
5
 
@@ -6,6 +6,7 @@ deriva-annotation-rollback = deriva.config.rollback_annotation:main
6
6
  deriva-annotation-validate = deriva.config.annotation_validate:main
7
7
  deriva-backup-cli = deriva.transfer.backup.__main__:main
8
8
  deriva-catalog-cli = deriva.core.catalog_cli:main
9
+ deriva-credenza-auth-utils = deriva.core.utils.credenza_auth_utils:main
9
10
  deriva-download-cli = deriva.transfer.download.__main__:main
10
11
  deriva-export-cli = deriva.transfer.download.deriva_export:main
11
12
  deriva-globus-auth-utils = deriva.core.utils.globus_auth_utils:main
tests/deriva/__init__.py DELETED
File without changes
File without changes
File without changes