seqslab-cli 3.3.1__tar.gz → 3.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.
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/LICENSE +0 -3
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/PKG-INFO +4 -4
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/README.md +3 -3
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/__init__.py +3 -7
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/auth/azuread.py +50 -37
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/auth/commands.py +138 -84
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/cli.py +5 -6
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/context.py +1 -3
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/__init__.py +1 -3
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/api/azure.py +185 -89
- seqslab-cli-3.3.2/python/seqslab/drs/api/base.py +453 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/api/common.py +1 -1
- seqslab-cli-3.3.2/python/seqslab/drs/api/template.py +149 -0
- seqslab-cli-3.3.2/python/seqslab/drs/commands.py +1457 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/internal/aiocopy.py +240 -158
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/internal/common.py +1 -1
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/internal/utils.py +40 -23
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/storage/azure.py +317 -187
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/storage/base.py +136 -124
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/utils/atgxmetadata.py +63 -58
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/utils/biomimetype.py +9 -9
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/utils/progressbar.py +40 -22
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/exceptions.py +4 -2
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/organization/__init__.py +1 -4
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/organization/commands.py +19 -17
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/organization/resource/base.py +10 -10
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/plugin.py +29 -26
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/role/__init__.py +1 -3
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/role/commands.py +1 -2
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/role/internal/common.py +1 -1
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/role/resource/base.py +18 -13
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/runsheet/runsheet.py +71 -47
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/scr/__init__.py +1 -3
- seqslab-cli-3.3.2/python/seqslab/scr/commands.py +232 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/scr/internal/common.py +2 -2
- {seqslab-cli-3.3.1/python/seqslab/trs → seqslab-cli-3.3.2/python/seqslab/scr}/resource/azure.py +2 -1
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/scr/resource/base.py +37 -30
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/settings.py +2 -2
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/statusbar.py +3 -5
- seqslab-cli-3.3.2/python/seqslab/trs/__init__.py +13 -0
- seqslab-cli-3.3.2/python/seqslab/trs/commands.py +705 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/trs/internal/utils.py +5 -5
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/trs/register/azure.py +11 -7
- seqslab-cli-3.3.2/python/seqslab/trs/register/base.py +300 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/trs/register/common.py +2 -3
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/trs/register/template.py +82 -45
- {seqslab-cli-3.3.1/python/seqslab/workspace → seqslab-cli-3.3.2/python/seqslab/trs}/resource/azure.py +2 -1
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/trs/resource/base.py +69 -56
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/trs/resource/common.py +2 -3
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/trs/template/base.py +82 -44
- seqslab-cli-3.3.2/python/seqslab/trs/template/template.py +85 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/user/__init__.py +1 -3
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/user/commands.py +113 -75
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/user/internal/common.py +2 -2
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/user/resource/azure.py +14 -8
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/user/resource/base.py +34 -23
- seqslab-cli-3.3.2/python/seqslab/wes/__init__.py +13 -0
- seqslab-cli-3.3.2/python/seqslab/wes/commands.py +739 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/wes/internal/common.py +2 -3
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/wes/internal/parameters.py +91 -52
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/wes/resource/azure.py +2 -1
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/wes/resource/base.py +110 -79
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/wes/template/base.py +39 -25
- seqslab-cli-3.3.2/python/seqslab/wes/template/template.py +91 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/workspace/__init__.py +1 -3
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/workspace/commands.py +90 -57
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/workspace/internal/common.py +2 -2
- seqslab-cli-3.3.2/python/seqslab/workspace/resource/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/scr → seqslab-cli-3.3.2/python/seqslab/workspace}/resource/azure.py +2 -1
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/workspace/resource/base.py +33 -23
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab_cli.egg-info/PKG-INFO +4 -4
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/setup.py +37 -29
- seqslab-cli-3.3.1/python/seqslab/drs/api/base.py +0 -379
- seqslab-cli-3.3.1/python/seqslab/drs/api/template.py +0 -126
- seqslab-cli-3.3.1/python/seqslab/drs/commands.py +0 -1109
- seqslab-cli-3.3.1/python/seqslab/drs/utils/__init__.py +0 -2
- seqslab-cli-3.3.1/python/seqslab/scr/commands.py +0 -206
- seqslab-cli-3.3.1/python/seqslab/trs/__init__.py +0 -15
- seqslab-cli-3.3.1/python/seqslab/trs/commands.py +0 -550
- seqslab-cli-3.3.1/python/seqslab/trs/register/base.py +0 -244
- seqslab-cli-3.3.1/python/seqslab/trs/template/template.py +0 -79
- seqslab-cli-3.3.1/python/seqslab/wes/__init__.py +0 -15
- seqslab-cli-3.3.1/python/seqslab/wes/commands.py +0 -569
- seqslab-cli-3.3.1/python/seqslab/wes/template/template.py +0 -88
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/MANIFEST.in +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/auth/__init__.py +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/api/__init__.py +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/internal/__init__.py +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/drs/storage/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/organization/resource → seqslab-cli-3.3.2/python/seqslab/drs/utils}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/role/internal → seqslab-cli-3.3.2/python/seqslab/organization/resource}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/role/resource → seqslab-cli-3.3.2/python/seqslab/role/internal}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/runsheet → seqslab-cli-3.3.2/python/seqslab/role/resource}/__init__.py +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/role/resource/azure.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/scr/internal → seqslab-cli-3.3.2/python/seqslab/runsheet}/__init__.py +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/sample_sheet/__init__.py +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/sample_sheet/_version.py +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/sample_sheet/util.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/scr/resource → seqslab-cli-3.3.2/python/seqslab/scr/internal}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/trs/internal → seqslab-cli-3.3.2/python/seqslab/scr/resource}/__init__.py +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/session_logger.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/trs/register → seqslab-cli-3.3.2/python/seqslab/trs/internal}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/trs/resource → seqslab-cli-3.3.2/python/seqslab/trs/register}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/trs/template → seqslab-cli-3.3.2/python/seqslab/trs/resource}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/user/internal → seqslab-cli-3.3.2/python/seqslab/trs/template}/__init__.py +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab/usage_logger.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/user/resource → seqslab-cli-3.3.2/python/seqslab/user/internal}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/wes/internal → seqslab-cli-3.3.2/python/seqslab/user/resource}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/wes/resource → seqslab-cli-3.3.2/python/seqslab/wes/internal}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/wes/template → seqslab-cli-3.3.2/python/seqslab/wes/resource}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/workspace/internal → seqslab-cli-3.3.2/python/seqslab/wes/template}/__init__.py +0 -0
- {seqslab-cli-3.3.1/python/seqslab/workspace/resource → seqslab-cli-3.3.2/python/seqslab/workspace/internal}/__init__.py +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab_cli.egg-info/SOURCES.txt +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab_cli.egg-info/dependency_links.txt +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab_cli.egg-info/entry_points.txt +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab_cli.egg-info/requires.txt +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab_cli.egg-info/top_level.txt +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/python/seqslab_cli.egg-info/zip-safe +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/requirements.txt +0 -0
- {seqslab-cli-3.3.1 → seqslab-cli-3.3.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: seqslab-cli
|
|
3
|
-
Version: 3.3.
|
|
3
|
+
Version: 3.3.2
|
|
4
4
|
Summary: Atgenomix SeqsLab Command Line Tool
|
|
5
5
|
Home-page: https://github.com/AnomeGAP/seqslab-cli
|
|
6
6
|
Author: Allen Chang
|
|
@@ -43,7 +43,7 @@ Description: <!-- PROJECT SHIELDS -->
|
|
|
43
43
|
### Prerequisites
|
|
44
44
|
|
|
45
45
|
* Python 3.8 or later
|
|
46
|
-
|
|
46
|
+
|
|
47
47
|
* [keyring](https://pypi.org/project/keyring/)
|
|
48
48
|
* [dbus](https://wiki.freedesktop.org/www/Software/dbus/)
|
|
49
49
|
* [glib](https://docs.gtk.org/glib/)
|
|
@@ -75,7 +75,7 @@ Description: <!-- PROJECT SHIELDS -->
|
|
|
75
75
|
```bash
|
|
76
76
|
root> exit
|
|
77
77
|
```
|
|
78
|
-
|
|
78
|
+
|
|
79
79
|
### CLI Mode
|
|
80
80
|
|
|
81
81
|
Through the use of subcommands, you can operate the SeqsLab CLI like any traditional Linux-based command-line utility.
|
|
@@ -89,7 +89,7 @@ Description: <!-- PROJECT SHIELDS -->
|
|
|
89
89
|
### Basic Commands
|
|
90
90
|
#### Authentication with the SeqsLab API
|
|
91
91
|
|
|
92
|
-
Regardless of the mode that you intend to use, you must sign in to authenticate the session. The SeqsLab CLI follows the OAuth 2.0 Device Authorization Grant (external URL) process.
|
|
92
|
+
Regardless of the mode that you intend to use, you must sign in to authenticate the session. The SeqsLab CLI follows the OAuth 2.0 Device Authorization Grant (external URL) process.
|
|
93
93
|
|
|
94
94
|
Example (interactive mode):
|
|
95
95
|
|
|
@@ -33,7 +33,7 @@ This package provides a unified command line interface to Atgenomix SeqsLab, a c
|
|
|
33
33
|
### Prerequisites
|
|
34
34
|
|
|
35
35
|
* Python 3.8 or later
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
* [keyring](https://pypi.org/project/keyring/)
|
|
38
38
|
* [dbus](https://wiki.freedesktop.org/www/Software/dbus/)
|
|
39
39
|
* [glib](https://docs.gtk.org/glib/)
|
|
@@ -65,7 +65,7 @@ This mode provides fish-style auto-completion functionality that is user-friendl
|
|
|
65
65
|
```bash
|
|
66
66
|
root> exit
|
|
67
67
|
```
|
|
68
|
-
|
|
68
|
+
|
|
69
69
|
### CLI Mode
|
|
70
70
|
|
|
71
71
|
Through the use of subcommands, you can operate the SeqsLab CLI like any traditional Linux-based command-line utility.
|
|
@@ -79,7 +79,7 @@ Through the use of subcommands, you can operate the SeqsLab CLI like any traditi
|
|
|
79
79
|
### Basic Commands
|
|
80
80
|
#### Authentication with the SeqsLab API
|
|
81
81
|
|
|
82
|
-
Regardless of the mode that you intend to use, you must sign in to authenticate the session. The SeqsLab CLI follows the OAuth 2.0 Device Authorization Grant (external URL) process.
|
|
82
|
+
Regardless of the mode that you intend to use, you must sign in to authenticate the session. The SeqsLab CLI follows the OAuth 2.0 Device Authorization Grant (external URL) process.
|
|
83
83
|
|
|
84
84
|
Example (interactive mode):
|
|
85
85
|
|
|
@@ -18,13 +18,9 @@ laws and state trade secret laws, punishable by civil and criminal penalties.
|
|
|
18
18
|
|
|
19
19
|
name = "seqslab"
|
|
20
20
|
|
|
21
|
-
__all__ = [
|
|
21
|
+
__all__ = []
|
|
22
22
|
|
|
23
|
-
]
|
|
24
23
|
|
|
24
|
+
__version__ = "3.3.2"
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
LOGGING = {
|
|
29
|
-
"DIR_PATH": "/var/log/seqslab"
|
|
30
|
-
}
|
|
26
|
+
LOGGING = {"DIR_PATH": "/var/log/seqslab"}
|
|
@@ -1,53 +1,64 @@
|
|
|
1
1
|
from __future__ import absolute_import, unicode_literals
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
import msal
|
|
6
|
-
import environ
|
|
3
|
+
# Standard Library
|
|
7
4
|
import atexit
|
|
8
5
|
import pickle
|
|
6
|
+
from typing import TypeVar
|
|
7
|
+
from urllib.parse import parse_qs
|
|
9
8
|
|
|
10
|
-
|
|
11
|
-
|
|
9
|
+
import environ
|
|
10
|
+
import msal
|
|
12
11
|
import seqslab
|
|
13
|
-
from urllib.parse import parse_qs
|
|
14
|
-
from nubia import context
|
|
15
12
|
from msal.oauth2cli.authcode import (
|
|
16
|
-
_AuthCodeHandler,
|
|
17
13
|
AuthCodeReceiver,
|
|
18
|
-
|
|
14
|
+
_AuthCodeHandler,
|
|
19
15
|
_AuthCodeHttpServer,
|
|
20
|
-
|
|
16
|
+
_AuthCodeHttpServer6,
|
|
17
|
+
_qs2kv,
|
|
18
|
+
)
|
|
19
|
+
from nubia import context
|
|
20
|
+
from pydantic import BaseModel
|
|
21
21
|
|
|
22
22
|
env = environ.Env()
|
|
23
23
|
environ.Env.read_env("/etc/seqslab/cli_apps.env")
|
|
24
24
|
|
|
25
25
|
PRIVATE_NAME = env.str("PRIVATE_NAME", "api")
|
|
26
26
|
API_HOSTNAME = f"{PRIVATE_NAME}.seqslab.net"
|
|
27
|
-
SOCIAL_AUTH_AZURE_KEY = env.str(
|
|
27
|
+
SOCIAL_AUTH_AZURE_KEY = env.str(
|
|
28
|
+
"AZUREAD_OAUTH2_KEY", "b10403db-7700-42c2-996e-116578438579"
|
|
29
|
+
)
|
|
28
30
|
SOCIAL_AUTH_AZURE_TENANT_ID = env.str("AZUREAD_OAUTH2_TENANT", "organizations")
|
|
29
|
-
SOCIAL_AUTH_AZURE_SCOPE = env.json(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
SOCIAL_AUTH_AZURE_SCOPE = env.json(
|
|
32
|
+
"AZUREAD_OAUTH2_SCOPE",
|
|
33
|
+
["email offline_access " "https://management.azure.com/user_impersonation"],
|
|
34
|
+
)
|
|
35
|
+
SOCIAL_AUTH_AZURE_SCOPE_APP = env.json(
|
|
36
|
+
"AZUREAD_OAUTH2_SCOPE_APP",
|
|
37
|
+
{
|
|
38
|
+
"management": ["https://management.azure.com/.default"],
|
|
39
|
+
"storage": ["https://storage.azure.com/.default"],
|
|
40
|
+
},
|
|
41
|
+
)
|
|
35
42
|
|
|
36
43
|
http_cache = f"/tmp/{seqslab.name}.{seqslab.__version__}.http_cache"
|
|
37
44
|
try:
|
|
38
45
|
with open(http_cache, "rb") as f:
|
|
39
46
|
persisted_http_cache = pickle.load(f) # Take a snapshot
|
|
40
47
|
except (
|
|
41
|
-
|
|
42
|
-
|
|
48
|
+
FileNotFoundError,
|
|
49
|
+
pickle.UnpicklingError, # A corrupted http cache file
|
|
43
50
|
):
|
|
44
51
|
persisted_http_cache = {} # Recover by starting afresh
|
|
45
52
|
|
|
46
|
-
atexit.register(
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
53
|
+
atexit.register(
|
|
54
|
+
lambda: pickle.dump(
|
|
55
|
+
# When exit, flush it back to the file.
|
|
56
|
+
# It may occasionally overwrite another process's concurrent write,
|
|
57
|
+
# but that is fine. Subsequent runs will reach eventual consistency.
|
|
58
|
+
persisted_http_cache,
|
|
59
|
+
open(http_cache, "wb"),
|
|
60
|
+
)
|
|
61
|
+
)
|
|
51
62
|
|
|
52
63
|
Client = TypeVar("Client", bound=msal.ClientApplication)
|
|
53
64
|
|
|
@@ -72,7 +83,8 @@ class ClientApp(AuthBaseModel):
|
|
|
72
83
|
app_name=seqslab.name,
|
|
73
84
|
app_version=seqslab.__version__,
|
|
74
85
|
proxies=proxies,
|
|
75
|
-
http_cache=persisted_http_cache
|
|
86
|
+
http_cache=persisted_http_cache,
|
|
87
|
+
)
|
|
76
88
|
return self.confidential_app
|
|
77
89
|
else:
|
|
78
90
|
if not self.public_app:
|
|
@@ -82,7 +94,8 @@ class ClientApp(AuthBaseModel):
|
|
|
82
94
|
app_name=seqslab.name,
|
|
83
95
|
app_version=seqslab.__version__,
|
|
84
96
|
proxies=proxies,
|
|
85
|
-
http_cache=persisted_http_cache
|
|
97
|
+
http_cache=persisted_http_cache,
|
|
98
|
+
)
|
|
86
99
|
return self.public_app
|
|
87
100
|
|
|
88
101
|
|
|
@@ -90,24 +103,26 @@ app = ClientApp()
|
|
|
90
103
|
|
|
91
104
|
|
|
92
105
|
class _AuthCodeHandlerEx(_AuthCodeHandler):
|
|
93
|
-
|
|
94
106
|
def do_POST(self):
|
|
95
|
-
clen = int(self.headers.get(
|
|
107
|
+
clen = int(self.headers.get("Content-Length"))
|
|
96
108
|
body = self.rfile.read(clen)
|
|
97
|
-
qs = parse_qs(body.decode(
|
|
98
|
-
if qs.get(
|
|
109
|
+
qs = parse_qs(body.decode("utf-8"))
|
|
110
|
+
if qs.get("code") or qs.get("error"):
|
|
99
111
|
self.server.auth_response = _qs2kv(qs)
|
|
100
|
-
template = (
|
|
101
|
-
|
|
112
|
+
template = (
|
|
113
|
+
self.server.success_template
|
|
114
|
+
if "code" in qs
|
|
115
|
+
else self.server.error_template
|
|
116
|
+
)
|
|
102
117
|
self._send_full_response(
|
|
103
|
-
template.safe_substitute(**self.server.auth_response)
|
|
118
|
+
template.safe_substitute(**self.server.auth_response)
|
|
119
|
+
)
|
|
104
120
|
# NOTE: Don't do self.server.shutdown() here. It'll halt the server.
|
|
105
121
|
else:
|
|
106
122
|
self._send_full_response(self.server.welcome_page)
|
|
107
123
|
|
|
108
124
|
|
|
109
125
|
class AuthCodeReceiverEx(AuthCodeReceiver):
|
|
110
|
-
|
|
111
126
|
def __init__(self, port=None):
|
|
112
127
|
"""
|
|
113
128
|
Create a Receiver waiting for incoming auth response.
|
|
@@ -118,5 +133,3 @@ class AuthCodeReceiverEx(AuthCodeReceiver):
|
|
|
118
133
|
address = "127.0.0.1"
|
|
119
134
|
Server = _AuthCodeHttpServer6 if ":" in address else _AuthCodeHttpServer
|
|
120
135
|
self._server = Server((address, port or 0), _AuthCodeHandlerEx)
|
|
121
|
-
|
|
122
|
-
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
+
# Standard Library
|
|
2
3
|
import errno
|
|
3
4
|
import getpass
|
|
4
5
|
import json
|
|
@@ -13,14 +14,10 @@ import jwt
|
|
|
13
14
|
import keyring
|
|
14
15
|
import requests
|
|
15
16
|
from keyring.errors import PasswordDeleteError
|
|
16
|
-
from nubia import argument
|
|
17
|
-
from
|
|
18
|
-
from nubia import context
|
|
19
|
-
from requests import HTTPError
|
|
20
|
-
from requests import request
|
|
17
|
+
from nubia import argument, command, context
|
|
18
|
+
from requests import HTTPError, request
|
|
21
19
|
from seqslab import settings as api_settings
|
|
22
|
-
from seqslab.auth import aad_app
|
|
23
|
-
from seqslab.auth import azuread, __version__
|
|
20
|
+
from seqslab.auth import __version__, aad_app, azuread
|
|
24
21
|
from seqslab.auth.azuread import API_HOSTNAME
|
|
25
22
|
from termcolor import cprint
|
|
26
23
|
|
|
@@ -55,18 +52,26 @@ class BaseAuth:
|
|
|
55
52
|
return response
|
|
56
53
|
|
|
57
54
|
def _signin_azure_silent(
|
|
58
|
-
self,
|
|
55
|
+
self,
|
|
56
|
+
credential: str,
|
|
57
|
+
assertion: str,
|
|
58
|
+
scope: str,
|
|
59
|
+
backend: str,
|
|
60
|
+
proxy: str = None,
|
|
59
61
|
) -> dict:
|
|
60
62
|
scopes = azuread.SOCIAL_AUTH_AZURE_SCOPE_APP.get(scope)
|
|
61
63
|
client = aad_app.load_client(credential=credential)
|
|
62
64
|
result = client.acquire_token_silent(scopes, account=None)
|
|
63
65
|
if not result:
|
|
64
|
-
logging.info(
|
|
66
|
+
logging.info(
|
|
67
|
+
"No suitable token exists in cache. Let's get a new one from AAD."
|
|
68
|
+
)
|
|
65
69
|
result = client.acquire_token_for_client(scopes=scopes)
|
|
66
70
|
|
|
67
71
|
if "error" in result:
|
|
68
|
-
raise PermissionError(
|
|
69
|
-
|
|
72
|
+
raise PermissionError(
|
|
73
|
+
"{}: {}".format(result["error"], result["error_description"])
|
|
74
|
+
)
|
|
70
75
|
|
|
71
76
|
result.update({"assertion": assertion, "scope": " ".join(scopes)})
|
|
72
77
|
return self._request(
|
|
@@ -74,9 +79,12 @@ class BaseAuth:
|
|
|
74
79
|
method=self.ACCESS_TOKEN_METHOD,
|
|
75
80
|
params={"secure": True},
|
|
76
81
|
data=result,
|
|
77
|
-
headers={
|
|
78
|
-
|
|
79
|
-
|
|
82
|
+
headers={
|
|
83
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
84
|
+
"Accept": "application/json",
|
|
85
|
+
},
|
|
86
|
+
proxies=proxy,
|
|
87
|
+
).json()
|
|
80
88
|
|
|
81
89
|
def _signin_azure(
|
|
82
90
|
self, device_code: bool, daemon: bool, backend: str, proxy: str = None
|
|
@@ -87,7 +95,9 @@ class BaseAuth:
|
|
|
87
95
|
client = aad_app.load_client()
|
|
88
96
|
accounts = client.get_accounts()
|
|
89
97
|
if accounts:
|
|
90
|
-
logging.info(
|
|
98
|
+
logging.info(
|
|
99
|
+
"Azure AD account(s) exists in cache and proceed with token cache."
|
|
100
|
+
)
|
|
91
101
|
|
|
92
102
|
if 1 == len(accounts):
|
|
93
103
|
chosen = accounts[0]
|
|
@@ -102,38 +112,50 @@ class BaseAuth:
|
|
|
102
112
|
result = client.acquire_token_silent(scopes=scopes, account=chosen)
|
|
103
113
|
|
|
104
114
|
if result is None:
|
|
105
|
-
logging.info(
|
|
115
|
+
logging.info(
|
|
116
|
+
"No suitable token exists in cache. You must get a new one from AAD."
|
|
117
|
+
)
|
|
106
118
|
|
|
107
119
|
if not device_code:
|
|
108
120
|
# obtain authorization uri
|
|
109
121
|
auth_uri = (
|
|
110
|
-
self._request(
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
122
|
+
self._request(
|
|
123
|
+
self.AUTHORIZATION_URL.format(backend=backend),
|
|
124
|
+
method=self.ACCESS_TOKEN_METHOD,
|
|
125
|
+
headers={
|
|
126
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
127
|
+
"Accept": "application/json",
|
|
128
|
+
},
|
|
129
|
+
params={"format": "json"},
|
|
130
|
+
proxies=proxy,
|
|
131
|
+
)
|
|
132
|
+
.json()
|
|
133
|
+
.get("url")
|
|
118
134
|
)
|
|
119
135
|
|
|
120
136
|
with azuread.AuthCodeReceiverEx(port=0) as receiver:
|
|
121
137
|
# hardcode http://localhost here as msal also hardcodes that
|
|
122
138
|
redirect_uri = f"http://localhost:{receiver.get_port()}"
|
|
123
|
-
auth_uri = re.sub(
|
|
124
|
-
|
|
125
|
-
|
|
139
|
+
auth_uri = re.sub(
|
|
140
|
+
r"(redirect_uri=)[^&]+",
|
|
141
|
+
r"\1" + quote(redirect_uri, safe=""),
|
|
142
|
+
auth_uri,
|
|
143
|
+
)
|
|
126
144
|
auth_uri = re.sub(r"prompt=[^&]+&", "", auth_uri)
|
|
127
145
|
result = receiver.get_auth_response(
|
|
128
146
|
auth_uri=auth_uri,
|
|
129
147
|
timeout=60,
|
|
130
148
|
success_template="You have signed in with your Microsoft "
|
|
131
|
-
|
|
132
|
-
|
|
149
|
+
"account on your device. "
|
|
150
|
+
"You may now close this window.",
|
|
151
|
+
)
|
|
133
152
|
|
|
134
153
|
if "error" in result:
|
|
135
|
-
raise PermissionError(
|
|
136
|
-
|
|
154
|
+
raise PermissionError(
|
|
155
|
+
"{}: {}".format(
|
|
156
|
+
result["error"], result["error_description"]
|
|
157
|
+
)
|
|
158
|
+
)
|
|
137
159
|
|
|
138
160
|
token_uri = self.AUTHORIZATION_URL.format(backend=backend)
|
|
139
161
|
result.update({"redirect_uri": redirect_uri})
|
|
@@ -141,31 +163,39 @@ class BaseAuth:
|
|
|
141
163
|
flow = client.initiate_device_flow(scopes=scopes)
|
|
142
164
|
if "user_code" not in flow:
|
|
143
165
|
raise ValueError(
|
|
144
|
-
"Unable to create device flow. Err: %s"
|
|
166
|
+
"Unable to create device flow. Err: %s"
|
|
167
|
+
% json.dumps(flow, indent=4)
|
|
168
|
+
)
|
|
145
169
|
|
|
146
170
|
cprint(flow["message"], "red")
|
|
147
171
|
# input("Press Enter after signing in from another device to proceed, CTRL+C to abort.")
|
|
148
172
|
result = client.acquire_token_by_device_flow(flow)
|
|
149
173
|
if "error" in result:
|
|
150
|
-
raise PermissionError(
|
|
151
|
-
result["error"], result["error_description"])
|
|
174
|
+
raise PermissionError(
|
|
175
|
+
"{}: {}".format(result["error"], result["error_description"])
|
|
176
|
+
)
|
|
152
177
|
|
|
153
178
|
return self._request(
|
|
154
179
|
token_uri,
|
|
155
180
|
method=self.ACCESS_TOKEN_METHOD,
|
|
156
181
|
params={"secure": True} if daemon else None,
|
|
157
182
|
data=result,
|
|
158
|
-
headers={
|
|
159
|
-
|
|
160
|
-
|
|
183
|
+
headers={
|
|
184
|
+
"Content-Type": "application/x-www-form-urlencoded",
|
|
185
|
+
"Accept": "application/json",
|
|
186
|
+
},
|
|
187
|
+
proxies=proxy,
|
|
188
|
+
).json()
|
|
161
189
|
|
|
162
190
|
@staticmethod
|
|
163
191
|
def _decode(token) -> dict:
|
|
164
|
-
attrs = jwt.decode(
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
192
|
+
attrs = jwt.decode(
|
|
193
|
+
token,
|
|
194
|
+
key=api_settings.JWT_VERIFYING_KEY,
|
|
195
|
+
algorithms=[api_settings.JWT_ALGORITHM],
|
|
196
|
+
verify=True,
|
|
197
|
+
options={"verify_aud": False, "verify_exp": False},
|
|
198
|
+
)
|
|
169
199
|
if "exp" not in attrs:
|
|
170
200
|
attrs["exp"] = 0
|
|
171
201
|
return attrs
|
|
@@ -184,47 +214,63 @@ class BaseAuth:
|
|
|
184
214
|
try:
|
|
185
215
|
attrs = Auth._decode(access)
|
|
186
216
|
except jwt.exceptions.ExpiredSignatureError:
|
|
187
|
-
Auth.tokens.update(
|
|
188
|
-
|
|
217
|
+
Auth.tokens.update(
|
|
218
|
+
{
|
|
219
|
+
"tokens": {"refresh": refresh, "access": None},
|
|
220
|
+
"attrs": {"exp": 0},
|
|
221
|
+
}
|
|
222
|
+
)
|
|
189
223
|
else:
|
|
190
|
-
Auth.tokens.update(
|
|
191
|
-
|
|
224
|
+
Auth.tokens.update(
|
|
225
|
+
{"tokens": {"refresh": refresh, "access": access}, "attrs": attrs}
|
|
226
|
+
)
|
|
192
227
|
|
|
193
228
|
if arrow.utcnow() >= arrow.get(Auth.tokens["attrs"]["exp"]):
|
|
194
229
|
# expired, refresh token
|
|
195
230
|
proxy = context.get_context().args.proxy
|
|
196
|
-
with Auth._request(
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
231
|
+
with Auth._request(
|
|
232
|
+
Auth.REFRESH_TOKEN_URL,
|
|
233
|
+
method=Auth.ACCESS_TOKEN_METHOD,
|
|
234
|
+
json={"refresh": Auth.tokens["tokens"]["refresh"]},
|
|
235
|
+
headers={
|
|
236
|
+
"Content-Type": "application/json",
|
|
237
|
+
"Accept": "application/json",
|
|
238
|
+
},
|
|
239
|
+
proxies=proxy,
|
|
240
|
+
) as response:
|
|
202
241
|
if response.status_code != requests.codes.ok:
|
|
203
242
|
Auth.tokens.clear()
|
|
204
243
|
return None
|
|
205
244
|
|
|
206
245
|
result = response.json()
|
|
207
|
-
Auth.tokens[
|
|
246
|
+
Auth.tokens["tokens"].update(result)
|
|
208
247
|
Auth.tokens.update({"attrs": Auth._decode(result["access"])})
|
|
209
|
-
keyring.set_password(
|
|
210
|
-
|
|
248
|
+
keyring.set_password(
|
|
249
|
+
"net.seqslab.api.tokens.access", user, result["access"]
|
|
250
|
+
)
|
|
211
251
|
|
|
212
252
|
return Auth.tokens
|
|
213
253
|
|
|
214
254
|
"""Authentication command help"""
|
|
215
255
|
|
|
216
|
-
@command(
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
256
|
+
@command(
|
|
257
|
+
aliases=["login"],
|
|
258
|
+
help="Authenticate to the platform and obtain API access token.",
|
|
259
|
+
)
|
|
260
|
+
@argument(
|
|
261
|
+
"device_code",
|
|
262
|
+
type=bool,
|
|
263
|
+
positional=False,
|
|
264
|
+
description="Use the device authorization grant flow (optional).",
|
|
265
|
+
aliases=["i"],
|
|
266
|
+
)
|
|
267
|
+
@argument(
|
|
268
|
+
"daemon",
|
|
269
|
+
type=bool,
|
|
270
|
+
positional=False,
|
|
271
|
+
description="Sign in for a long-running, non-interactive daemon process (optional).",
|
|
272
|
+
aliases=["d"],
|
|
273
|
+
)
|
|
228
274
|
def signin(self, device_code: bool = False, daemon: bool = False) -> int:
|
|
229
275
|
"""
|
|
230
276
|
Sign in to the configured authentication backend and obtain API access token.
|
|
@@ -244,10 +290,12 @@ class BaseAuth:
|
|
|
244
290
|
result = method(device_code, daemon, backend, proxy)
|
|
245
291
|
# store in keyring secret service
|
|
246
292
|
user = getpass.getuser()
|
|
247
|
-
keyring.set_password(
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
293
|
+
keyring.set_password(
|
|
294
|
+
"net.seqslab.api.tokens.refresh", user, result["tokens"]["refresh"]
|
|
295
|
+
)
|
|
296
|
+
keyring.set_password(
|
|
297
|
+
"net.seqslab.api.tokens.access", user, result["tokens"]["access"]
|
|
298
|
+
)
|
|
251
299
|
return 0
|
|
252
300
|
except ValueError:
|
|
253
301
|
return errno.EINVAL
|
|
@@ -260,14 +308,18 @@ class BaseAuth:
|
|
|
260
308
|
cprint(err, "red")
|
|
261
309
|
return errno.ECANCELED
|
|
262
310
|
|
|
263
|
-
@command(
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
311
|
+
@command(
|
|
312
|
+
aliases=["daemon"],
|
|
313
|
+
help="Authenticate silently to the platform and obtain API access token. "
|
|
314
|
+
"This requires that the SeqsLab API app admin consent has been granted.",
|
|
315
|
+
)
|
|
316
|
+
@argument(
|
|
317
|
+
"scope",
|
|
318
|
+
type=str,
|
|
319
|
+
positional=False,
|
|
320
|
+
description="Specify the scope of the application permission requested (default = management).",
|
|
321
|
+
choices=["management", "storage"],
|
|
322
|
+
)
|
|
271
323
|
def daemon(self, scope: str = "management") -> int:
|
|
272
324
|
"""
|
|
273
325
|
Sign in silently to the configured authentication backend and obtain API access token.
|
|
@@ -284,7 +336,7 @@ class BaseAuth:
|
|
|
284
336
|
return errno.ENODEV
|
|
285
337
|
|
|
286
338
|
if len(Auth.tokens):
|
|
287
|
-
assertion = Auth.tokens["tokens"].get(
|
|
339
|
+
assertion = Auth.tokens["tokens"].get("access")
|
|
288
340
|
credential = Auth.tokens["attrs"].get("scrt")
|
|
289
341
|
else:
|
|
290
342
|
# load from Keyring secret service
|
|
@@ -302,10 +354,12 @@ class BaseAuth:
|
|
|
302
354
|
method = getattr(self, mname)
|
|
303
355
|
result = method(credential, assertion, scope, backend, proxy)
|
|
304
356
|
# store in keyring secret service
|
|
305
|
-
keyring.set_password(
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
357
|
+
keyring.set_password(
|
|
358
|
+
"net.seqslab.api.tokens.refresh", user, result["tokens"]["refresh"]
|
|
359
|
+
)
|
|
360
|
+
keyring.set_password(
|
|
361
|
+
"net.seqslab.api.tokens.access", user, result["tokens"]["access"]
|
|
362
|
+
)
|
|
309
363
|
return 0
|
|
310
364
|
except ValueError:
|
|
311
365
|
return errno.EINVAL
|
|
@@ -16,12 +16,12 @@ copies by sale, rental, lease or lending are violations of federal copyright
|
|
|
16
16
|
laws and state trade secret laws, punishable by civil and criminal penalties.
|
|
17
17
|
"""
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
# Standard Library
|
|
20
20
|
import signal
|
|
21
|
+
import sys
|
|
21
22
|
|
|
22
23
|
from nubia import Nubia, Options
|
|
23
|
-
|
|
24
|
-
from seqslab import auth, drs, wes, trs, workspace, user, role, organization, scr
|
|
24
|
+
from seqslab import auth, drs, organization, role, scr, trs, user, wes, workspace
|
|
25
25
|
from seqslab.plugin import SQLBPlugin
|
|
26
26
|
|
|
27
27
|
|
|
@@ -30,7 +30,7 @@ def signal_handler(sig, frame):
|
|
|
30
30
|
sys.stderr.write(msg)
|
|
31
31
|
ans = input()
|
|
32
32
|
|
|
33
|
-
if ans ==
|
|
33
|
+
if ans == "y":
|
|
34
34
|
print("")
|
|
35
35
|
exit(1)
|
|
36
36
|
else:
|
|
@@ -47,8 +47,7 @@ def main():
|
|
|
47
47
|
command_pkgs=[auth, drs, wes, trs, workspace, user, role, organization, scr],
|
|
48
48
|
plugin=plugin,
|
|
49
49
|
options=Options(
|
|
50
|
-
persistent_history=False,
|
|
51
|
-
auto_execute_single_suggestions=False
|
|
50
|
+
persistent_history=False, auto_execute_single_suggestions=False
|
|
52
51
|
),
|
|
53
52
|
)
|
|
54
53
|
sys.exit(shell.run())
|