secflow-cli 0.2.4__tar.gz → 0.3.0__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.
- {secflow_cli-0.2.4/src/secflow_cli.egg-info → secflow_cli-0.3.0}/PKG-INFO +15 -14
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/USAGE.md +13 -13
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/pyproject.toml +3 -2
- secflow_cli-0.3.0/src/secflow_cli/__init__.py +1 -0
- secflow_cli-0.3.0/src/secflow_cli/__main__.py +3 -0
- secflow_cli-0.3.0/src/secflow_cli/bootstrap.py +37 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/browser_login.py +52 -14
- secflow_cli-0.3.0/src/secflow_cli/logkit/__init__.py +976 -0
- secflow_cli-0.3.0/src/secflow_cli/logkit/test_logkit.py +1282 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/main.py +10 -1
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/manifest_commands.py +5 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/output.py +4 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/session_commands.py +8 -4
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/token_commands.py +12 -4
- secflow_cli-0.3.0/src/secflow_cli/update_install.py +322 -0
- secflow_cli-0.3.0/src/secflow_cli/update_runtime.py +511 -0
- secflow_cli-0.3.0/src/secflow_cli/update_security.py +170 -0
- secflow_cli-0.3.0/src/secflow_cli/update_state.py +195 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0/src/secflow_cli.egg-info}/PKG-INFO +15 -14
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli.egg-info/SOURCES.txt +8 -1
- secflow_cli-0.3.0/src/secflow_cli.egg-info/entry_points.txt +2 -0
- secflow_cli-0.2.4/src/secflow_cli/__init__.py +0 -0
- secflow_cli-0.2.4/src/secflow_cli/__main__.py +0 -3
- secflow_cli-0.2.4/src/secflow_cli.egg-info/entry_points.txt +0 -2
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/LICENSE +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/setup.cfg +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/access_profile_commands.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/account_commands.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/command_support.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/completion.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/config.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/credentials.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/doctor_commands.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/errors.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/help.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/namespace_commands.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/operator_commands.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/paging.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/policy_audit_commands.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/reference_commands.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/report_commands.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/role_commands.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/runtime_commands.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli/secret_commands.py +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli.egg-info/dependency_links.txt +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli.egg-info/requires.txt +0 -0
- {secflow_cli-0.2.4 → secflow_cli-0.3.0}/src/secflow_cli.egg-info/top_level.txt +0 -0
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: secflow-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: SecFlow operator CLI
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Repository, https://github.com/yeon3724/secflow
|
|
7
7
|
Project-URL: Issues, https://github.com/yeon3724/secflow/issues
|
|
8
|
+
Keywords: intflow-cli-update-protocol-1
|
|
8
9
|
Requires-Python: >=3.10
|
|
9
10
|
Description-Content-Type: text/markdown
|
|
10
11
|
License-File: LICENSE
|
|
@@ -14,11 +15,11 @@ Dynamic: license-file
|
|
|
14
15
|
|
|
15
16
|
# SecFlow CLI
|
|
16
17
|
|
|
17
|
-
Use Intflow browser login or an
|
|
18
|
+
Use Intflow browser login or an Admin session token.
|
|
18
19
|
|
|
19
20
|
## Install and update
|
|
20
21
|
|
|
21
|
-
Choose
|
|
22
|
+
Choose npm or Python; both own `secflowctl` and share profiles and OS credentials.
|
|
22
23
|
|
|
23
24
|
```sh
|
|
24
25
|
pipx install secflow-cli
|
|
@@ -30,19 +31,19 @@ npm install --global @intflow/secflow-cli
|
|
|
30
31
|
secflowctl --version
|
|
31
32
|
```
|
|
32
33
|
|
|
33
|
-
|
|
34
|
+
Python 3.10+ is required; installation includes the pinned Admin-capable SDK.
|
|
34
35
|
|
|
35
|
-
The npm distribution
|
|
36
|
+
The npm distribution bundles Python and dependencies for Windows x64, macOS Intel/Apple Silicon, and Linux x64 glibc 2.35+. Alpine/musl and Linux ARM use the Python distribution. Keep optional dependencies enabled; installation has no postinstall script. Releases require all four same-version native packages. Binaries are not platform-signed or notarized.
|
|
36
37
|
|
|
37
|
-
The
|
|
38
|
+
The bundle includes HTTPS CAs; `SSL_CERT_FILE`/`SSL_CERT_DIR` overrides trust without disabling TLS verification.
|
|
38
39
|
|
|
39
40
|
```sh
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
python -m pip install --upgrade secflow-cli
|
|
43
|
-
npm install --global @intflow/secflow-cli@latest
|
|
41
|
+
secflowctl update --check
|
|
42
|
+
secflowctl update --status
|
|
44
43
|
```
|
|
45
44
|
|
|
45
|
+
Commands check within 3s and stage protocol-1 stable runtimes in a private cache/venv, then run argv once; offline failures keep the last good runtime. TTY/JSON agree; help/version/check/status never install. `update --apply` installs; `--auto on|off` persists policy; `--pin VERSION` freezes an exact runtime; `--unpin` releases only policy. `INTFLOW_CLI_AUTO_UPDATE=0` stops automatic installs, allowing explicit apply/pin. Pins outrank off and fail closed. Internal native binaries update through npm.
|
|
46
|
+
|
|
46
47
|
## Connect
|
|
47
48
|
|
|
48
49
|
```sh
|
|
@@ -55,9 +56,9 @@ secflowctl --help
|
|
|
55
56
|
|
|
56
57
|
`secflowctl` without arguments displays a quick start. `secflowctl login` opens Intflow login and creates the `production` profile for `https://secflow.intflow.dev` on first use. Existing profiles retain their selected server. For another server, first use `secflowctl profile add staging --base-url https://secflow.example.test`, then `secflowctl --profile staging login`. Global options precede the command. Every command supports `--help`, including examples. Account and role administration requires server authorization.
|
|
57
58
|
|
|
58
|
-
`auth login`
|
|
59
|
+
`auth login` prompts without echo for an existing Admin session token. Sessions are stored in Windows Credential Manager, macOS Keychain, or Linux Secret Service. Linux persistent login needs an unlocked Secret Service (for example GNOME Keyring) and a D-Bus session. If secure storage is unavailable, login fails without a plaintext file fallback. Installing the CLI does not create an account or grant access.
|
|
59
60
|
|
|
60
|
-
When browser login is enabled on the server, run `secflowctl auth login --browser`. Sign in to Intflow, compare the code with your terminal, and approve the SecFlow login. Use `--browser --no-browser` to open the displayed URL manually. This requires an interactive terminal, an existing authorized SecFlow account, and OS credential storage. The CLI stores only the resulting SecFlow session. If login expires or the exchange fails,
|
|
61
|
+
When browser login is enabled on the server, run `secflowctl auth login --browser`. Sign in to Intflow, compare the code with your terminal, and approve the SecFlow login. Use `--browser --no-browser` to open the displayed URL manually. This requires an interactive terminal, an existing authorized SecFlow account, and OS credential storage. The CLI stores only the resulting SecFlow session. A busy server is retried for a minute. If login expires or the exchange fails, log in again.
|
|
61
62
|
|
|
62
63
|
For automation use `SECFLOW_ADMIN_SESSION_TOKEN` supplied by your secret manager, or `--token-stdin` with `--non-interactive`. Never put tokens in command arguments, profiles, shell history, or tracked files. SDK tokens cannot authorize these Admin commands. `auth logout` revokes the current server session; `auth forget` only removes the local credential.
|
|
63
64
|
|
|
@@ -128,6 +129,6 @@ For automation, use `--non-interactive` before the command and `--yes` on `put`.
|
|
|
128
129
|
|
|
129
130
|
## Credentials and values
|
|
130
131
|
|
|
131
|
-
`tokens issue
|
|
132
|
+
`tokens issue` uses the server-default lifetime; choose `--ttl-seconds` (1-7776000) or `--no-expiry`, never both. `--save-as NAME` stores an SDK token for `runtime ... --sdk-name NAME`. `auth sessions issue --save-as PROFILE` stores a separate Admin profile. Both need a new destination or `--token-stdout` redirected to a pipe/file; ordinary results contain no raw credentials. Linux/macOS non-interactive calls need an injected credential, not an OS store unlock.
|
|
132
133
|
|
|
133
|
-
Secret create/rotate
|
|
134
|
+
Secret create/rotate accepts exact UTF-8 or base64 via hidden input, `--value-stdin` or `--value-file`, never command arguments. Stdin cannot carry both an Admin token and a value or policy. Runtime uses SDK credentials, never Admin sessions; no plaintext resolve command exists.
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
# SecFlow CLI
|
|
2
2
|
|
|
3
|
-
Use Intflow browser login or an
|
|
3
|
+
Use Intflow browser login or an Admin session token.
|
|
4
4
|
|
|
5
5
|
## Install and update
|
|
6
6
|
|
|
7
|
-
Choose
|
|
7
|
+
Choose npm or Python; both own `secflowctl` and share profiles and OS credentials.
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
pipx install secflow-cli
|
|
@@ -16,19 +16,19 @@ npm install --global @intflow/secflow-cli
|
|
|
16
16
|
secflowctl --version
|
|
17
17
|
```
|
|
18
18
|
|
|
19
|
-
|
|
19
|
+
Python 3.10+ is required; installation includes the pinned Admin-capable SDK.
|
|
20
20
|
|
|
21
|
-
The npm distribution
|
|
21
|
+
The npm distribution bundles Python and dependencies for Windows x64, macOS Intel/Apple Silicon, and Linux x64 glibc 2.35+. Alpine/musl and Linux ARM use the Python distribution. Keep optional dependencies enabled; installation has no postinstall script. Releases require all four same-version native packages. Binaries are not platform-signed or notarized.
|
|
22
22
|
|
|
23
|
-
The
|
|
23
|
+
The bundle includes HTTPS CAs; `SSL_CERT_FILE`/`SSL_CERT_DIR` overrides trust without disabling TLS verification.
|
|
24
24
|
|
|
25
25
|
```sh
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
python -m pip install --upgrade secflow-cli
|
|
29
|
-
npm install --global @intflow/secflow-cli@latest
|
|
26
|
+
secflowctl update --check
|
|
27
|
+
secflowctl update --status
|
|
30
28
|
```
|
|
31
29
|
|
|
30
|
+
Commands check within 3s and stage protocol-1 stable runtimes in a private cache/venv, then run argv once; offline failures keep the last good runtime. TTY/JSON agree; help/version/check/status never install. `update --apply` installs; `--auto on|off` persists policy; `--pin VERSION` freezes an exact runtime; `--unpin` releases only policy. `INTFLOW_CLI_AUTO_UPDATE=0` stops automatic installs, allowing explicit apply/pin. Pins outrank off and fail closed. Internal native binaries update through npm.
|
|
31
|
+
|
|
32
32
|
## Connect
|
|
33
33
|
|
|
34
34
|
```sh
|
|
@@ -41,9 +41,9 @@ secflowctl --help
|
|
|
41
41
|
|
|
42
42
|
`secflowctl` without arguments displays a quick start. `secflowctl login` opens Intflow login and creates the `production` profile for `https://secflow.intflow.dev` on first use. Existing profiles retain their selected server. For another server, first use `secflowctl profile add staging --base-url https://secflow.example.test`, then `secflowctl --profile staging login`. Global options precede the command. Every command supports `--help`, including examples. Account and role administration requires server authorization.
|
|
43
43
|
|
|
44
|
-
`auth login`
|
|
44
|
+
`auth login` prompts without echo for an existing Admin session token. Sessions are stored in Windows Credential Manager, macOS Keychain, or Linux Secret Service. Linux persistent login needs an unlocked Secret Service (for example GNOME Keyring) and a D-Bus session. If secure storage is unavailable, login fails without a plaintext file fallback. Installing the CLI does not create an account or grant access.
|
|
45
45
|
|
|
46
|
-
When browser login is enabled on the server, run `secflowctl auth login --browser`. Sign in to Intflow, compare the code with your terminal, and approve the SecFlow login. Use `--browser --no-browser` to open the displayed URL manually. This requires an interactive terminal, an existing authorized SecFlow account, and OS credential storage. The CLI stores only the resulting SecFlow session. If login expires or the exchange fails,
|
|
46
|
+
When browser login is enabled on the server, run `secflowctl auth login --browser`. Sign in to Intflow, compare the code with your terminal, and approve the SecFlow login. Use `--browser --no-browser` to open the displayed URL manually. This requires an interactive terminal, an existing authorized SecFlow account, and OS credential storage. The CLI stores only the resulting SecFlow session. A busy server is retried for a minute. If login expires or the exchange fails, log in again.
|
|
47
47
|
|
|
48
48
|
For automation use `SECFLOW_ADMIN_SESSION_TOKEN` supplied by your secret manager, or `--token-stdin` with `--non-interactive`. Never put tokens in command arguments, profiles, shell history, or tracked files. SDK tokens cannot authorize these Admin commands. `auth logout` revokes the current server session; `auth forget` only removes the local credential.
|
|
49
49
|
|
|
@@ -114,6 +114,6 @@ For automation, use `--non-interactive` before the command and `--yes` on `put`.
|
|
|
114
114
|
|
|
115
115
|
## Credentials and values
|
|
116
116
|
|
|
117
|
-
`tokens issue
|
|
117
|
+
`tokens issue` uses the server-default lifetime; choose `--ttl-seconds` (1-7776000) or `--no-expiry`, never both. `--save-as NAME` stores an SDK token for `runtime ... --sdk-name NAME`. `auth sessions issue --save-as PROFILE` stores a separate Admin profile. Both need a new destination or `--token-stdout` redirected to a pipe/file; ordinary results contain no raw credentials. Linux/macOS non-interactive calls need an injected credential, not an OS store unlock.
|
|
118
118
|
|
|
119
|
-
Secret create/rotate
|
|
119
|
+
Secret create/rotate accepts exact UTF-8 or base64 via hidden input, `--value-stdin` or `--value-file`, never command arguments. Stdin cannot carry both an Admin token and a value or policy. Runtime uses SDK credentials, never Admin sessions; no plaintext resolve command exists.
|
|
@@ -4,15 +4,16 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "secflow-cli"
|
|
7
|
-
version = "0.
|
|
7
|
+
version = "0.3.0"
|
|
8
8
|
description = "SecFlow operator CLI"
|
|
9
9
|
readme = "USAGE.md"
|
|
10
10
|
license = "MIT"
|
|
11
11
|
requires-python = ">=3.10"
|
|
12
|
+
keywords = ["intflow-cli-update-protocol-1"]
|
|
12
13
|
dependencies = ["secflow-sdk==0.1.10", "keyring>=25,<26"]
|
|
13
14
|
|
|
14
15
|
[project.scripts]
|
|
15
|
-
secflowctl = "secflow_cli.
|
|
16
|
+
secflowctl = "secflow_cli.bootstrap:main"
|
|
16
17
|
|
|
17
18
|
[project.urls]
|
|
18
19
|
Repository = "https://github.com/yeon3724/secflow"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.3.0"
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from typing import Any
|
|
5
|
+
|
|
6
|
+
from . import __version__
|
|
7
|
+
from .update_runtime import bootstrap
|
|
8
|
+
from .update_state import Configuration, default_root
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def execute(arguments: list[str]) -> int:
|
|
12
|
+
from .main import main
|
|
13
|
+
|
|
14
|
+
return main(arguments, configure=True)
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def output(value: dict[str, Any]) -> None:
|
|
18
|
+
from .output import update_result
|
|
19
|
+
|
|
20
|
+
update_result(value)
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def main(argv: list[str] | None = None) -> int:
|
|
24
|
+
config = Configuration(
|
|
25
|
+
"secflow-cli", "secflow_cli", __version__, default_root("secflow-cli")
|
|
26
|
+
)
|
|
27
|
+
return bootstrap(
|
|
28
|
+
config,
|
|
29
|
+
list(sys.argv[1:] if argv is None else argv),
|
|
30
|
+
execute,
|
|
31
|
+
output,
|
|
32
|
+
frozen=bool(getattr(sys, "frozen", False)),
|
|
33
|
+
)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
if __name__ == "__main__":
|
|
37
|
+
raise SystemExit(main())
|
|
@@ -4,6 +4,7 @@ import base64
|
|
|
4
4
|
from datetime import datetime, timezone
|
|
5
5
|
import hashlib
|
|
6
6
|
import json
|
|
7
|
+
import logging
|
|
7
8
|
import os
|
|
8
9
|
import re
|
|
9
10
|
import sys
|
|
@@ -11,12 +12,18 @@ import time
|
|
|
11
12
|
from urllib.error import HTTPError
|
|
12
13
|
from urllib.request import HTTPRedirectHandler, Request, build_opener
|
|
13
14
|
from uuid import UUID
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
LOGGER = logging.getLogger(__name__)
|
|
14
18
|
import webbrowser
|
|
15
19
|
|
|
16
20
|
from secflow_sdk import SecFlowAdminClient
|
|
17
21
|
from secflow_cli.errors import CliError
|
|
18
22
|
|
|
19
23
|
|
|
24
|
+
BUSY_WAIT_SECONDS = 60
|
|
25
|
+
|
|
26
|
+
|
|
20
27
|
def rejected():
|
|
21
28
|
return CliError(3, "BROWSER_LOGIN_FAILED", "Browser login did not complete. Start a new login; uncertain session exchanges are not retried.")
|
|
22
29
|
|
|
@@ -32,9 +39,9 @@ def post(url, body, timeout):
|
|
|
32
39
|
try:
|
|
33
40
|
response = build_opener(NoRedirect).open(request, timeout=timeout)
|
|
34
41
|
except HTTPError as error:
|
|
35
|
-
status = error.code
|
|
42
|
+
status, retry_after = error.code, _retry_after(error.headers.get("Retry-After"))
|
|
36
43
|
error.close()
|
|
37
|
-
return status, {}
|
|
44
|
+
return status, {}, retry_after
|
|
38
45
|
with response:
|
|
39
46
|
raw = response.read(16385)
|
|
40
47
|
if len(raw) > 16384:
|
|
@@ -42,7 +49,13 @@ def post(url, body, timeout):
|
|
|
42
49
|
value = json.loads(raw)
|
|
43
50
|
if not isinstance(value, dict):
|
|
44
51
|
raise rejected()
|
|
45
|
-
return response.status, value
|
|
52
|
+
return response.status, value, None
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _retry_after(value):
|
|
56
|
+
if not isinstance(value, str) or not re.fullmatch(r"[0-9]{1,4}", value):
|
|
57
|
+
return None
|
|
58
|
+
return min(30, max(1, int(value)))
|
|
46
59
|
|
|
47
60
|
|
|
48
61
|
def acquire_session(url, timeout, *, no_browser=False):
|
|
@@ -57,7 +70,8 @@ def acquire_session(url, timeout, *, no_browser=False):
|
|
|
57
70
|
def _open_browser(verification_uri):
|
|
58
71
|
try:
|
|
59
72
|
opened = webbrowser.open(verification_uri)
|
|
60
|
-
except Exception:
|
|
73
|
+
except Exception as caught:
|
|
74
|
+
LOGGER.debug("Browser could not be opened", extra={"attributes": {"error.type": type(caught).__name__}})
|
|
61
75
|
opened = False
|
|
62
76
|
if not opened:
|
|
63
77
|
print("Open the URL above manually to continue.", file=sys.stderr)
|
|
@@ -75,10 +89,26 @@ def _issued_session(result):
|
|
|
75
89
|
return {field: result[field] for field in ("session_token", "session_id", "account_id", "expires_at")}
|
|
76
90
|
|
|
77
91
|
|
|
92
|
+
def _create(url, proof, timeout):
|
|
93
|
+
give_up = time.monotonic() + BUSY_WAIT_SECONDS
|
|
94
|
+
announced = False
|
|
95
|
+
while True:
|
|
96
|
+
started_at = time.monotonic()
|
|
97
|
+
status, created, retry_after = post(url + "/v1/auth/browser-login", {"poll_challenge": hashlib.sha256(proof.encode()).hexdigest()}, timeout)
|
|
98
|
+
if status != 503 or retry_after is None:
|
|
99
|
+
return status, created, started_at
|
|
100
|
+
remaining = give_up - time.monotonic()
|
|
101
|
+
if remaining <= 0:
|
|
102
|
+
raise CliError(3, "BROWSER_LOGIN_BUSY", "The server is too busy to start a browser login. Try again in a few minutes.")
|
|
103
|
+
if not announced:
|
|
104
|
+
print("The server is busy. Retrying for up to " + str(BUSY_WAIT_SECONDS) + " seconds.", file=sys.stderr)
|
|
105
|
+
announced = True
|
|
106
|
+
time.sleep(min(retry_after, remaining))
|
|
107
|
+
|
|
108
|
+
|
|
78
109
|
def _acquire_session(url, timeout, *, no_browser):
|
|
79
110
|
proof = base64.urlsafe_b64encode(os.urandom(32)).rstrip(b"=").decode("ascii")
|
|
80
|
-
started_at =
|
|
81
|
-
status, created = post(url + "/v1/auth/browser-login", {"poll_challenge": hashlib.sha256(proof.encode()).hexdigest()}, timeout)
|
|
111
|
+
status, created, started_at = _create(url, proof, timeout)
|
|
82
112
|
if status == 404:
|
|
83
113
|
raise CliError(3, "BROWSER_LOGIN_UNAVAILABLE", "This server has not enabled browser login. Use a Slack-issued Admin session.")
|
|
84
114
|
if status != 201:
|
|
@@ -97,18 +127,26 @@ def _acquire_session(url, timeout, *, no_browser):
|
|
|
97
127
|
print("Confirm code " + created["user_code"] + " and approve the login in your browser.", file=sys.stderr)
|
|
98
128
|
if not no_browser:
|
|
99
129
|
_open_browser(verification_uri)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
130
|
+
return _poll(url, timeout, identifier, proof, deadline, created["interval"])
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _poll(url, timeout, identifier, proof, deadline, interval):
|
|
134
|
+
wait = interval
|
|
135
|
+
while time.monotonic() + wait < deadline:
|
|
136
|
+
time.sleep(wait)
|
|
137
|
+
wait = interval
|
|
103
138
|
remaining = deadline - time.monotonic()
|
|
104
139
|
if remaining <= 0:
|
|
105
140
|
break
|
|
106
|
-
status, result = post(url + "/v1/auth/browser-login/redeem",
|
|
107
|
-
|
|
141
|
+
status, result, retry_after = post(url + "/v1/auth/browser-login/redeem",
|
|
142
|
+
{"transaction_id": identifier, "poll_secret": proof}, min(timeout, remaining))
|
|
108
143
|
if status == 202 and result == {"status": "pending"}:
|
|
109
144
|
continue
|
|
110
145
|
if status == 429:
|
|
111
|
-
interval = min(30, interval + 2)
|
|
146
|
+
wait = interval = min(30, interval + 2)
|
|
147
|
+
continue
|
|
148
|
+
if status == 503 and retry_after is not None:
|
|
149
|
+
wait = max(interval, retry_after)
|
|
112
150
|
continue
|
|
113
151
|
if status != 200:
|
|
114
152
|
raise rejected()
|
|
@@ -128,7 +166,7 @@ def login(url, timeout, store, reference, *, no_browser=False):
|
|
|
128
166
|
except BaseException:
|
|
129
167
|
try:
|
|
130
168
|
client.revoke_current_session()
|
|
131
|
-
except Exception:
|
|
132
|
-
|
|
169
|
+
except Exception as caught:
|
|
170
|
+
LOGGER.debug("New session revocation failed", extra={"attributes": {"error.type": type(caught).__name__}})
|
|
133
171
|
raise
|
|
134
172
|
return account, session["expires_at"]
|