Make token scan read-only
This commit is contained in:
@@ -156,11 +156,13 @@ Podstawowe komendy tokenow:
|
|||||||
./rvctl tokens stats --repo ~/dev/workspace/rv/series/inf/03
|
./rvctl tokens stats --repo ~/dev/workspace/rv/series/inf/03
|
||||||
```
|
```
|
||||||
|
|
||||||
`tokens scan` wypisuje tabele `tokens`: jeden wiersz na logiczny token.
|
`tokens scan` wypisuje tabele `tokens` i niczego nie zapisuje: jeden wiersz na
|
||||||
|
logiczny remote tokena.
|
||||||
`token_ref` laczy nazwe tokena z markerem po prawej stronie: `*` oznacza, ze
|
`token_ref` laczy nazwe tokena z markerem po prawej stronie: `*` oznacza, ze
|
||||||
remote i `tokens.json` sa zgodne oraz uprawnienia zostaly wczytane, `R` oznacza
|
remote i `tokens.json` sa zgodne oraz uprawnienia zostaly wczytane, `R` oznacza
|
||||||
token tylko w remote, a `S` token tylko w `tokens.json`. Kolumny `scope`, `org`
|
token tylko w remote, a `S` token tylko w `tokens.json`. Kolumny `scope`, `org`
|
||||||
i `repo` sa maskami uprawnien; bez `*` maja wartosc `?????` albo `????`.
|
i `repo` sa maskami uprawnien; bez `*` maja wartosc `?????` albo `????`.
|
||||||
|
Zapis z remote do `tokens.json` robi dopiero `tokens update --from remotes`.
|
||||||
Kolumna `valid` oznacza, czy token zostal zaakceptowany przez API teraz.
|
Kolumna `valid` oznacza, czy token zostal zaakceptowany przez API teraz.
|
||||||
Jesli przy tokenie w `tokens.json` zapiszesz `expires_at`, `valid` pokaze date
|
Jesli przy tokenie w `tokens.json` zapiszesz `expires_at`, `valid` pokaze date
|
||||||
wygasniecia; bez daty poprawny token pokazuje `forever`.
|
wygasniecia; bez daty poprawny token pokazuje `forever`.
|
||||||
|
|||||||
+25
-11
@@ -10,7 +10,8 @@ Sa dwa miejsca, w ktorych moga byc zapisane tokeny:
|
|||||||
- lokalny plik `~/dev/workspace/rv/tokens/tokens.json`
|
- lokalny plik `~/dev/workspace/rv/tokens/tokens.json`
|
||||||
|
|
||||||
Jesli remote URL zawiera `LOGIN:TOKEN@...`, to remote jest zrodlem prawdy.
|
Jesli remote URL zawiera `LOGIN:TOKEN@...`, to remote jest zrodlem prawdy.
|
||||||
Launcher moze wtedy zeskanowac repo i zapisac ten token do `tokens.json`.
|
Launcher moze wtedy pokazac stan przez `tokens scan` albo jawnie zapisac token
|
||||||
|
do `tokens.json` przez `tokens update --from remotes`.
|
||||||
|
|
||||||
Jesli remote nie ma tokena, `tokens.json` moze byc uzyty jako lokalny store
|
Jesli remote nie ma tokena, `tokens.json` moze byc uzyty jako lokalny store
|
||||||
przy operacjach `tokens write` i `tokens update --from store`.
|
przy operacjach `tokens write` i `tokens update --from store`.
|
||||||
@@ -34,6 +35,9 @@ Tokeny sa trzymane per endpoint serwera, a dopiero pod nim per user i token:
|
|||||||
"t1": "SECRET",
|
"t1": "SECRET",
|
||||||
"t2": {
|
"t2": {
|
||||||
"value": "SECRET",
|
"value": "SECRET",
|
||||||
|
"remote": "r1",
|
||||||
|
"org": "edu-tools",
|
||||||
|
"repo": "rv-launcher",
|
||||||
"expires_at": "2026-05-01T12:00:00"
|
"expires_at": "2026-05-01T12:00:00"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,6 +54,8 @@ To pozwala odroznic:
|
|||||||
- endpoint, czyli `scheme + host + port`
|
- endpoint, czyli `scheme + host + port`
|
||||||
- uzytkownikow na danym serwerze
|
- uzytkownikow na danym serwerze
|
||||||
- wiele tokenow dla jednego usera
|
- wiele tokenow dla jednego usera
|
||||||
|
- nazwe remota, ktora jest identyfikatorem parowania z repo
|
||||||
|
- opcjonalne `org` i `repo` zapamietane z remote URL-a
|
||||||
- opcjonalna date wygasniecia `expires_at` dla tokena
|
- opcjonalna date wygasniecia `expires_at` dla tokena
|
||||||
|
|
||||||
## Skanowanie remota
|
## Skanowanie remota
|
||||||
@@ -58,10 +64,10 @@ Przy `tokens scan` launcher:
|
|||||||
|
|
||||||
- czyta wszystkie remote URL-e w repo
|
- czyta wszystkie remote URL-e w repo
|
||||||
- czyta `tokens.json`
|
- czyta `tokens.json`
|
||||||
- laczy remote i store w pary po endpoincie serwera
|
- laczy remote i store w pary po endpoincie serwera i nazwie remota
|
||||||
- wybiera tylko `http` i `https`
|
- wybiera tylko `http` i `https`
|
||||||
- jesli URL ma `LOGIN:TOKEN@...`, wyciaga login i token
|
- jesli URL ma `LOGIN:TOKEN@...`, wyciaga login i token
|
||||||
- zapisuje je pod odpowiednim endpointem w `tokens.json`
|
- niczego nie zapisuje do `tokens.json`
|
||||||
|
|
||||||
Endpoint jest liczony z:
|
Endpoint jest liczony z:
|
||||||
|
|
||||||
@@ -69,7 +75,7 @@ Endpoint jest liczony z:
|
|||||||
- host
|
- host
|
||||||
- port
|
- port
|
||||||
|
|
||||||
Przy skanowaniu launcher zapisuje tez metadane serwera:
|
Przy `tokens update --from remotes` launcher zapisuje tez metadane serwera:
|
||||||
|
|
||||||
- `type`
|
- `type`
|
||||||
- `scheme`
|
- `scheme`
|
||||||
@@ -90,17 +96,15 @@ Dzialanie:
|
|||||||
|
|
||||||
- skanuje remote URL-e w repo
|
- skanuje remote URL-e w repo
|
||||||
- czyta wpisy z `tokens.json`
|
- czyta wpisy z `tokens.json`
|
||||||
- laczy oba zrodla po endpoincie serwera
|
- laczy oba zrodla po endpoincie serwera i nazwie remota
|
||||||
- zapisuje znalezione tokeny do `tokens.json`
|
|
||||||
- zapisuje je per endpoint serwera
|
|
||||||
- wypisuje tabele `tokens`
|
- wypisuje tabele `tokens`
|
||||||
|
|
||||||
Tabela `tokens` pokazuje jeden logiczny wiersz na token. Remote i `tokens.json`
|
Tabela `tokens` pokazuje jeden logiczny wiersz na remote tokena. Remote i
|
||||||
sa laczone po endpoincie, userze i wartosci tokena.
|
`tokens.json` sa laczone po endpoincie serwera oraz kolumnie `remote`.
|
||||||
|
|
||||||
Znacznik w `token_ref`:
|
Znacznik w `token_ref`:
|
||||||
|
|
||||||
- `*` - token jest w remote i `tokens.json`, uprawnienia zostaly wczytane
|
- `*` - token jest w remote i `tokens.json`, remote/user/token sa zgodne, uprawnienia zostaly wczytane
|
||||||
- `R` - token jest tylko w remote
|
- `R` - token jest tylko w remote
|
||||||
- `S` - token jest tylko w `tokens.json`
|
- `S` - token jest tylko w `tokens.json`
|
||||||
- `!` - blad wczytania uprawnien dla sparowanego tokena
|
- `!` - blad wczytania uprawnien dla sparowanego tokena
|
||||||
@@ -140,6 +144,10 @@ przyklad `e59cc...13be`.
|
|||||||
- `?` - token nie jest sparowany jako `*`, wiec nie sprawdzamy uprawnien
|
- `?` - token nie jest sparowany jako `*`, wiec nie sprawdzamy uprawnien
|
||||||
- `!` - blad sprawdzania API
|
- `!` - blad sprawdzania API
|
||||||
|
|
||||||
|
`tokens scan` jest read-only. Jezeli token jest tylko w remote, tabela pokaze
|
||||||
|
`R` i maski `?????`/`????`. Dopiero jawne `tokens update --from remotes`
|
||||||
|
zapisuje token oraz metadane `remote`, `org` i `repo` do `tokens.json`.
|
||||||
|
|
||||||
Przyklad:
|
Przyklad:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -160,6 +168,7 @@ Wynik zawiera:
|
|||||||
- port
|
- port
|
||||||
- users
|
- users
|
||||||
- tokens
|
- tokens
|
||||||
|
- remote/org/repo przy tokenie, jezeli sa zapisane
|
||||||
|
|
||||||
Oraz liste userow i nazw tokenow dla kazdego endpointu.
|
Oraz liste userow i nazw tokenow dla kazdego endpointu.
|
||||||
|
|
||||||
@@ -226,6 +235,10 @@ Dozwolone kierunki:
|
|||||||
- `tokens update --from remotes`
|
- `tokens update --from remotes`
|
||||||
- `tokens update --from store`
|
- `tokens update --from store`
|
||||||
|
|
||||||
|
`tokens update --from remotes` kopiuje tokeny z remote URL-i repo do
|
||||||
|
`tokens.json`. `tokens update --from store` kopiuje wybrany token z
|
||||||
|
`tokens.json` do remote URL-a repo.
|
||||||
|
|
||||||
Przyklad:
|
Przyklad:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
@@ -240,7 +253,7 @@ Domyslnie launcher nie zgaduje przy konflikcie.
|
|||||||
Jesli:
|
Jesli:
|
||||||
|
|
||||||
- token jest w repo, ale nie ma go w `tokens.json`
|
- token jest w repo, ale nie ma go w `tokens.json`
|
||||||
uzyj `tokens scan`
|
uzyj `tokens update --from remotes`
|
||||||
- token jest w `tokens.json`, ale nie ma go w repo
|
- token jest w `tokens.json`, ale nie ma go w repo
|
||||||
uzyj `tokens write`
|
uzyj `tokens write`
|
||||||
- token jest i tu, i tu, ale wartosci sa rozne
|
- token jest i tu, i tu, ale wartosci sa rozne
|
||||||
@@ -256,6 +269,7 @@ Przy `tokens write` i `tokens update --from store` mozna uzyc:
|
|||||||
Najbezpieczniejszy model pracy:
|
Najbezpieczniejszy model pracy:
|
||||||
|
|
||||||
- `tokens scan` do zczytywania danych z remote'ow
|
- `tokens scan` do zczytywania danych z remote'ow
|
||||||
|
- `tokens update --from remotes` do jawnego zapisania tokenow z remote'ow w `tokens.json`
|
||||||
- `tokens read` do podgladu store
|
- `tokens read` do podgladu store
|
||||||
- `tokens stats` do zbiorczego przegladu per endpoint
|
- `tokens stats` do zbiorczego przegladu per endpoint
|
||||||
- `tokens write` do jawnego wpisania auth do remota
|
- `tokens write` do jawnego wpisania auth do remota
|
||||||
|
|||||||
@@ -303,6 +303,10 @@ def normalize_token_entry(raw_token_entry) -> str | dict | None:
|
|||||||
expires_at = raw_token_entry.get("expires_at")
|
expires_at = raw_token_entry.get("expires_at")
|
||||||
if isinstance(expires_at, str) and expires_at:
|
if isinstance(expires_at, str) and expires_at:
|
||||||
token_entry["expires_at"] = expires_at
|
token_entry["expires_at"] = expires_at
|
||||||
|
for field_name in ["remote", "org", "repo"]:
|
||||||
|
field_value = raw_token_entry.get(field_name)
|
||||||
|
if isinstance(field_value, str) and field_value:
|
||||||
|
token_entry[field_name] = field_value
|
||||||
return token_entry
|
return token_entry
|
||||||
|
|
||||||
|
|
||||||
@@ -320,6 +324,12 @@ def token_entry_expires_at(token_entry) -> str:
|
|||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|
||||||
|
def token_entry_field(token_entry, field_name: str) -> str:
|
||||||
|
if isinstance(token_entry, dict) and isinstance(token_entry.get(field_name), str):
|
||||||
|
return token_entry[field_name]
|
||||||
|
return ""
|
||||||
|
|
||||||
|
|
||||||
def token_value_exists(tokens: dict, token_value: str) -> bool:
|
def token_value_exists(tokens: dict, token_value: str) -> bool:
|
||||||
return any(token_entry_value(existing_token) == token_value for existing_token in tokens.values())
|
return any(token_entry_value(existing_token) == token_value for existing_token in tokens.values())
|
||||||
|
|
||||||
@@ -431,7 +441,7 @@ def normalize_token_store(config: WorkspaceConfig, raw_data: dict) -> dict:
|
|||||||
return token_data
|
return token_data
|
||||||
|
|
||||||
|
|
||||||
def load_token_store(config: WorkspaceConfig) -> dict:
|
def load_token_store(config: WorkspaceConfig, write_normalized: bool = True) -> dict:
|
||||||
if not config.token_path.exists():
|
if not config.token_path.exists():
|
||||||
return empty_token_store()
|
return empty_token_store()
|
||||||
|
|
||||||
@@ -439,7 +449,7 @@ def load_token_store(config: WorkspaceConfig) -> dict:
|
|||||||
if not isinstance(raw_data, dict):
|
if not isinstance(raw_data, dict):
|
||||||
raise SystemExit(f"Invalid token store format: {config.token_path}")
|
raise SystemExit(f"Invalid token store format: {config.token_path}")
|
||||||
normalized_data = normalize_token_store(config, raw_data)
|
normalized_data = normalize_token_store(config, raw_data)
|
||||||
if raw_data != normalized_data:
|
if write_normalized and raw_data != normalized_data:
|
||||||
write_token_store(config, normalized_data)
|
write_token_store(config, normalized_data)
|
||||||
return normalized_data
|
return normalized_data
|
||||||
|
|
||||||
@@ -455,17 +465,58 @@ def write_token_store(config: WorkspaceConfig, token_data: dict) -> None:
|
|||||||
os.chmod(config.token_path, 0o600)
|
os.chmod(config.token_path, 0o600)
|
||||||
|
|
||||||
|
|
||||||
def register_token(token_data: dict, server_info: dict, user_name: str, token_value: str) -> bool:
|
def token_entry_with_metadata(token_value: str, server_info: dict, remote_name: str) -> dict[str, str]:
|
||||||
|
token_entry = {"value": token_value}
|
||||||
|
for source_name, target_name in [("org", "org"), ("repo", "repo")]:
|
||||||
|
source_value = server_info.get(source_name)
|
||||||
|
if isinstance(source_value, str) and source_value:
|
||||||
|
token_entry[target_name] = source_value
|
||||||
|
if remote_name:
|
||||||
|
token_entry["remote"] = remote_name
|
||||||
|
return token_entry
|
||||||
|
|
||||||
|
|
||||||
|
def update_token_entry_metadata(token_entry, token_value: str, server_info: dict, remote_name: str) -> tuple[dict, bool]:
|
||||||
|
if isinstance(token_entry, dict):
|
||||||
|
updated_entry = dict(token_entry)
|
||||||
|
else:
|
||||||
|
updated_entry = {"value": token_value}
|
||||||
|
|
||||||
|
changed = updated_entry.get("value") != token_value
|
||||||
|
updated_entry["value"] = token_value
|
||||||
|
for source_name, target_name in [("org", "org"), ("repo", "repo")]:
|
||||||
|
source_value = server_info.get(source_name)
|
||||||
|
if isinstance(source_value, str) and source_value and updated_entry.get(target_name) != source_value:
|
||||||
|
updated_entry[target_name] = source_value
|
||||||
|
changed = True
|
||||||
|
if remote_name and updated_entry.get("remote") != remote_name:
|
||||||
|
updated_entry["remote"] = remote_name
|
||||||
|
changed = True
|
||||||
|
return updated_entry, changed
|
||||||
|
|
||||||
|
|
||||||
|
def register_token(token_data: dict, server_info: dict, remote_name: str, user_name: str, token_value: str) -> str:
|
||||||
server_entry = ensure_server_entry(token_data, server_info)
|
server_entry = ensure_server_entry(token_data, server_info)
|
||||||
users = server_entry.setdefault("users", {})
|
users = server_entry.setdefault("users", {})
|
||||||
user_entry = users.setdefault(user_name, {"tokens": {}})
|
user_entry = users.setdefault(user_name, {"tokens": {}})
|
||||||
tokens = user_entry.setdefault("tokens", {})
|
tokens = user_entry.setdefault("tokens", {})
|
||||||
|
|
||||||
if token_value_exists(tokens, token_value):
|
for token_name, token_entry in list(tokens.items()):
|
||||||
return False
|
if token_entry_field(token_entry, "remote") != remote_name:
|
||||||
|
continue
|
||||||
|
updated_entry, changed = update_token_entry_metadata(token_entry, token_value, server_info, remote_name)
|
||||||
|
tokens[token_name] = updated_entry
|
||||||
|
return "updated" if changed else "existing"
|
||||||
|
|
||||||
tokens[next_token_name(tokens)] = token_value
|
for token_name, token_entry in list(tokens.items()):
|
||||||
return True
|
if token_entry_value(token_entry) != token_value:
|
||||||
|
continue
|
||||||
|
updated_entry, changed = update_token_entry_metadata(token_entry, token_value, server_info, remote_name)
|
||||||
|
tokens[token_name] = updated_entry
|
||||||
|
return "updated" if changed else "existing"
|
||||||
|
|
||||||
|
tokens[next_token_name(tokens)] = token_entry_with_metadata(token_value, server_info, remote_name)
|
||||||
|
return "added"
|
||||||
|
|
||||||
|
|
||||||
def remote_urls(repo_path: Path) -> list[str]:
|
def remote_urls(repo_path: Path) -> list[str]:
|
||||||
@@ -502,7 +553,7 @@ def remote_credentials(remote_url: str) -> tuple[str, str] | None:
|
|||||||
return unquote(split_url.username), unquote(split_url.password)
|
return unquote(split_url.username), unquote(split_url.password)
|
||||||
|
|
||||||
|
|
||||||
def sync_tokens_from_repo(config: WorkspaceConfig, repo_path: Path) -> dict:
|
def scan_repo_remotes(config: WorkspaceConfig, repo_path: Path) -> dict:
|
||||||
repo_root = git_repo_root(repo_path)
|
repo_root = git_repo_root(repo_path)
|
||||||
if repo_root is None:
|
if repo_root is None:
|
||||||
return {
|
return {
|
||||||
@@ -515,12 +566,14 @@ def sync_tokens_from_repo(config: WorkspaceConfig, repo_path: Path) -> dict:
|
|||||||
"unsupported_urls": 0,
|
"unsupported_urls": 0,
|
||||||
"found": 0,
|
"found": 0,
|
||||||
"added": 0,
|
"added": 0,
|
||||||
|
"updated": 0,
|
||||||
"servers": 0,
|
"servers": 0,
|
||||||
"remote_rows": [],
|
"remote_rows": [],
|
||||||
|
"found_credentials": [],
|
||||||
"changed": False,
|
"changed": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
found_credentials: list[tuple[int, dict, str, str]] = []
|
found_credentials: list[tuple[int, dict, str, str, str]] = []
|
||||||
remote_entries = remote_name_urls(repo_root)
|
remote_entries = remote_name_urls(repo_root)
|
||||||
remote_names = {remote_name for remote_name, _ in remote_entries}
|
remote_names = {remote_name for remote_name, _ in remote_entries}
|
||||||
touched_servers: set[str] = set()
|
touched_servers: set[str] = set()
|
||||||
@@ -582,19 +635,7 @@ def sync_tokens_from_repo(config: WorkspaceConfig, repo_path: Path) -> dict:
|
|||||||
"result": "found",
|
"result": "found",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
found_credentials.append((row_index, server_info, user_name, token_value))
|
found_credentials.append((row_index, server_info, remote_name, user_name, token_value))
|
||||||
|
|
||||||
token_data = load_token_store(config)
|
|
||||||
added = 0
|
|
||||||
for row_index, server_info, user_name, token_value in found_credentials:
|
|
||||||
if register_token(token_data, server_info, user_name, token_value):
|
|
||||||
added += 1
|
|
||||||
remote_rows[row_index]["result"] = "added"
|
|
||||||
else:
|
|
||||||
remote_rows[row_index]["result"] = "existing"
|
|
||||||
|
|
||||||
if found_credentials and (added or not config.token_path.exists()):
|
|
||||||
write_token_store(config, token_data)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
"repo_root": repo_root,
|
"repo_root": repo_root,
|
||||||
@@ -605,13 +646,42 @@ def sync_tokens_from_repo(config: WorkspaceConfig, repo_path: Path) -> dict:
|
|||||||
"plain_urls": plain_urls,
|
"plain_urls": plain_urls,
|
||||||
"unsupported_urls": unsupported_urls,
|
"unsupported_urls": unsupported_urls,
|
||||||
"found": len(found_credentials),
|
"found": len(found_credentials),
|
||||||
"added": added,
|
"added": 0,
|
||||||
|
"updated": 0,
|
||||||
"servers": len(touched_servers),
|
"servers": len(touched_servers),
|
||||||
"remote_rows": remote_rows,
|
"remote_rows": remote_rows,
|
||||||
"changed": added > 0,
|
"found_credentials": found_credentials,
|
||||||
|
"changed": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def sync_tokens_from_repo(config: WorkspaceConfig, repo_path: Path) -> dict:
|
||||||
|
report = scan_repo_remotes(config, repo_path)
|
||||||
|
found_credentials = report.get("found_credentials", [])
|
||||||
|
if not found_credentials:
|
||||||
|
return report
|
||||||
|
|
||||||
|
token_data = load_token_store(config)
|
||||||
|
added = 0
|
||||||
|
updated = 0
|
||||||
|
for row_index, server_info, remote_name, user_name, token_value in found_credentials:
|
||||||
|
result = register_token(token_data, server_info, remote_name, user_name, token_value)
|
||||||
|
if result == "added":
|
||||||
|
added += 1
|
||||||
|
elif result == "updated":
|
||||||
|
updated += 1
|
||||||
|
report["remote_rows"][row_index]["result"] = result
|
||||||
|
|
||||||
|
changed = added > 0 or updated > 0
|
||||||
|
if changed or not config.token_path.exists():
|
||||||
|
write_token_store(config, token_data)
|
||||||
|
|
||||||
|
report["added"] = added
|
||||||
|
report["updated"] = updated
|
||||||
|
report["changed"] = changed
|
||||||
|
return report
|
||||||
|
|
||||||
|
|
||||||
def count_server_tokens(server_entry: dict) -> int:
|
def count_server_tokens(server_entry: dict) -> int:
|
||||||
return sum(len(user_entry.get("tokens", {})) for user_entry in server_entry.get("users", {}).values())
|
return sum(len(user_entry.get("tokens", {})) for user_entry in server_entry.get("users", {}).values())
|
||||||
|
|
||||||
@@ -627,15 +697,15 @@ def token_store_servers(token_data: dict) -> list[tuple[str, dict]]:
|
|||||||
return sorted(servers.items())
|
return sorted(servers.items())
|
||||||
|
|
||||||
|
|
||||||
def token_pairs_from_users(users_data: dict) -> set[tuple[str, str]]:
|
def token_pairs_from_users(users_data: dict) -> set[tuple[str, str, str]]:
|
||||||
token_pairs: set[tuple[str, str]] = set()
|
token_pairs: set[tuple[str, str, str]] = set()
|
||||||
for user_name, user_entry in users_data.items():
|
for user_name, user_entry in users_data.items():
|
||||||
if not isinstance(user_entry, dict):
|
if not isinstance(user_entry, dict):
|
||||||
continue
|
continue
|
||||||
for token_entry in user_entry.get("tokens", {}).values():
|
for token_entry in user_entry.get("tokens", {}).values():
|
||||||
token_value = token_entry_value(token_entry)
|
token_value = token_entry_value(token_entry)
|
||||||
if token_value:
|
if token_value:
|
||||||
token_pairs.add((str(user_name), token_value))
|
token_pairs.add((token_entry_field(token_entry, "remote"), str(user_name), token_value))
|
||||||
return token_pairs
|
return token_pairs
|
||||||
|
|
||||||
|
|
||||||
@@ -703,7 +773,7 @@ def collect_repo_server_entries(config: WorkspaceConfig, repo_path: Path) -> tup
|
|||||||
user_entry = entry["users"].setdefault(user_name, {"tokens": {}})
|
user_entry = entry["users"].setdefault(user_name, {"tokens": {}})
|
||||||
tokens = user_entry.setdefault("tokens", {})
|
tokens = user_entry.setdefault("tokens", {})
|
||||||
if not token_value_exists(tokens, token_value):
|
if not token_value_exists(tokens, token_value):
|
||||||
tokens[next_token_name(tokens)] = token_value
|
tokens[next_token_name(tokens)] = token_entry_with_metadata(token_value, server_info, remote_name)
|
||||||
|
|
||||||
return repo_root, repo_servers
|
return repo_root, repo_servers
|
||||||
|
|
||||||
@@ -878,6 +948,9 @@ def token_store_rows(store_servers: dict[str, dict]) -> list[dict[str, str]]:
|
|||||||
"endpoint": endpoint,
|
"endpoint": endpoint,
|
||||||
"user": str(user_name),
|
"user": str(user_name),
|
||||||
"token_name": str(token_name),
|
"token_name": str(token_name),
|
||||||
|
"remote": token_entry_field(token_entry, "remote"),
|
||||||
|
"owner": token_entry_field(token_entry, "org"),
|
||||||
|
"repo_name": token_entry_field(token_entry, "repo"),
|
||||||
"token_value": token_value,
|
"token_value": token_value,
|
||||||
"expires_at": token_entry_expires_at(token_entry),
|
"expires_at": token_entry_expires_at(token_entry),
|
||||||
}
|
}
|
||||||
@@ -1078,9 +1151,10 @@ def print_token_item_rows(
|
|||||||
status_counts[status_name] += 1
|
status_counts[status_name] += 1
|
||||||
|
|
||||||
store_rows = token_store_rows(store_servers)
|
store_rows = token_store_rows(store_servers)
|
||||||
store_by_value: dict[tuple[str, str, str], dict[str, str]] = {}
|
store_by_remote: dict[tuple[str, str], dict[str, str]] = {}
|
||||||
for row in store_rows:
|
for row in store_rows:
|
||||||
store_by_value.setdefault((row["endpoint"], row["user"], row["token_value"]), row)
|
if row["remote"]:
|
||||||
|
store_by_remote.setdefault((row["endpoint"], row["remote"]), row)
|
||||||
|
|
||||||
endpoint_values_by_name = {
|
endpoint_values_by_name = {
|
||||||
endpoint: endpoint_column_values(endpoint, repo_servers.get(endpoint), store_servers.get(endpoint))
|
endpoint: endpoint_column_values(endpoint, repo_servers.get(endpoint), store_servers.get(endpoint))
|
||||||
@@ -1125,13 +1199,14 @@ def print_token_item_rows(
|
|||||||
for row in repo_token_rows(endpoint_names, repo_servers, scan_rows):
|
for row in repo_token_rows(endpoint_names, repo_servers, scan_rows):
|
||||||
endpoint = row["endpoint"]
|
endpoint = row["endpoint"]
|
||||||
token_value = row["token_value"]
|
token_value = row["token_value"]
|
||||||
store_row = store_by_value.get((endpoint, row["user"], token_value))
|
store_row = store_by_remote.get((endpoint, row["remote"]))
|
||||||
token_name = store_row["token_name"] if store_row else ""
|
token_name = store_row["token_name"] if store_row else ""
|
||||||
expires_at = store_row.get("expires_at", "") if store_row else ""
|
expires_at = store_row.get("expires_at", "") if store_row else ""
|
||||||
marker = "R"
|
marker = "R"
|
||||||
authz = None
|
authz = None
|
||||||
if store_row:
|
if store_row:
|
||||||
used_store_keys.add((endpoint, store_row["user"], store_row["token_name"], token_value))
|
used_store_keys.add((endpoint, store_row["user"], store_row["token_name"], store_row["token_value"]))
|
||||||
|
if store_row["user"] == row["user"] and store_row["token_value"] == token_value:
|
||||||
authz_key = (endpoint, row["user"], token_value, expires_at, row["owner"], row["repo_name"])
|
authz_key = (endpoint, row["user"], token_value, expires_at, row["owner"], row["repo_name"])
|
||||||
authz = authz_cache.get(authz_key)
|
authz = authz_cache.get(authz_key)
|
||||||
if authz is None:
|
if authz is None:
|
||||||
@@ -1166,12 +1241,12 @@ def print_token_item_rows(
|
|||||||
append_token_row(
|
append_token_row(
|
||||||
row["endpoint"],
|
row["endpoint"],
|
||||||
row["user"],
|
row["user"],
|
||||||
"",
|
row["remote"],
|
||||||
row["token_name"],
|
row["token_name"],
|
||||||
row["token_value"],
|
row["token_value"],
|
||||||
"S",
|
"S",
|
||||||
fallback.get("owner", ""),
|
row["owner"] or fallback.get("owner", ""),
|
||||||
fallback.get("repo_name", ""),
|
row["repo_name"] or fallback.get("repo_name", ""),
|
||||||
)
|
)
|
||||||
|
|
||||||
print_fixed_table("tokens", TOKEN_COLUMNS, token_rows)
|
print_fixed_table("tokens", TOKEN_COLUMNS, token_rows)
|
||||||
@@ -1268,16 +1343,16 @@ def resolve_repo_argument(repo_arg: str | None) -> Path:
|
|||||||
|
|
||||||
def run_tokens_scan(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
def run_tokens_scan(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
||||||
repo_path = resolve_repo_argument(args.repo)
|
repo_path = resolve_repo_argument(args.repo)
|
||||||
report = sync_tokens_from_repo(config, repo_path)
|
token_data = load_token_store(config, write_normalized=False)
|
||||||
repo_root, repo_servers = collect_repo_server_entries(config, repo_path)
|
|
||||||
token_data = load_token_store(config)
|
|
||||||
store_servers = token_data.get("servers", {})
|
store_servers = token_data.get("servers", {})
|
||||||
|
report = scan_repo_remotes(config, repo_path)
|
||||||
|
repo_root, repo_servers = collect_repo_server_entries(config, repo_path)
|
||||||
endpoint_names = sorted(set(repo_servers) | set(store_servers))
|
endpoint_names = sorted(set(repo_servers) | set(store_servers))
|
||||||
print_token_item_rows(endpoint_names, repo_servers, store_servers, report["remote_rows"])
|
print_token_item_rows(endpoint_names, repo_servers, store_servers, report["remote_rows"])
|
||||||
|
|
||||||
|
|
||||||
def run_tokens_read(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
def run_tokens_read(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
||||||
token_data = load_token_store(config)
|
token_data = load_token_store(config, write_normalized=False)
|
||||||
servers = token_store_servers(token_data)
|
servers = token_store_servers(token_data)
|
||||||
if args.server:
|
if args.server:
|
||||||
server_entry = token_data.get("servers", {}).get(args.server)
|
server_entry = token_data.get("servers", {}).get(args.server)
|
||||||
@@ -1309,6 +1384,10 @@ def run_tokens_read(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
|||||||
token_value = token_entry_value(token_entry)
|
token_value = token_entry_value(token_entry)
|
||||||
secret_value = token_value if args.show_secrets else mask_secret(token_value)
|
secret_value = token_value if args.show_secrets else mask_secret(token_value)
|
||||||
print(f"token\t{token_name}\t{secret_value}")
|
print(f"token\t{token_name}\t{secret_value}")
|
||||||
|
for field_name in ["remote", "org", "repo"]:
|
||||||
|
field_value = token_entry_field(token_entry, field_name)
|
||||||
|
if field_value:
|
||||||
|
print(f"{field_name}\t{token_name}\t{field_value}")
|
||||||
expires_at = token_entry_expires_at(token_entry)
|
expires_at = token_entry_expires_at(token_entry)
|
||||||
if expires_at:
|
if expires_at:
|
||||||
print(f"expires_at\t{token_name}\t{expires_at}")
|
print(f"expires_at\t{token_name}\t{expires_at}")
|
||||||
@@ -1316,7 +1395,7 @@ def run_tokens_read(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
def run_tokens_stats(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
def run_tokens_stats(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
||||||
repo_root, repo_servers = collect_repo_server_entries(config, resolve_repo_argument(args.repo))
|
repo_root, repo_servers = collect_repo_server_entries(config, resolve_repo_argument(args.repo))
|
||||||
token_data = load_token_store(config)
|
token_data = load_token_store(config, write_normalized=False)
|
||||||
store_servers = token_data.get("servers", {})
|
store_servers = token_data.get("servers", {})
|
||||||
endpoint_names = sorted(set(repo_servers) | set(store_servers))
|
endpoint_names = sorted(set(repo_servers) | set(store_servers))
|
||||||
if args.server:
|
if args.server:
|
||||||
@@ -1381,7 +1460,13 @@ def run_tokens_write(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
|||||||
|
|
||||||
def run_tokens_update(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
def run_tokens_update(config: WorkspaceConfig, args: argparse.Namespace) -> None:
|
||||||
if args.from_source == "remotes":
|
if args.from_source == "remotes":
|
||||||
run_tokens_scan(config, args)
|
repo_path = resolve_repo_argument(args.repo)
|
||||||
|
report = sync_tokens_from_repo(config, repo_path)
|
||||||
|
repo_root, repo_servers = collect_repo_server_entries(config, repo_path)
|
||||||
|
token_data = load_token_store(config, write_normalized=False)
|
||||||
|
store_servers = token_data.get("servers", {})
|
||||||
|
endpoint_names = sorted(set(repo_servers) | set(store_servers))
|
||||||
|
print_token_item_rows(endpoint_names, repo_servers, store_servers, report["remote_rows"])
|
||||||
return
|
return
|
||||||
if args.from_source == "store":
|
if args.from_source == "store":
|
||||||
run_tokens_write(config, args)
|
run_tokens_write(config, args)
|
||||||
@@ -1702,11 +1787,11 @@ def print_tokens_overview() -> None:
|
|||||||
print_table(
|
print_table(
|
||||||
["command", "common options", "direction", "purpose"],
|
["command", "common options", "direction", "purpose"],
|
||||||
[
|
[
|
||||||
["scan", "[--repo PATH]", "repo + tokens.json", "print token table with remote/store marker and auth masks"],
|
["scan", "[--repo PATH]", "read-only", "print token table with remote/store marker and auth masks"],
|
||||||
["read", "[--server ENDPOINT]", "tokens.json", "show servers, users and token names"],
|
["read", "[--server ENDPOINT]", "tokens.json", "show servers, users and token names"],
|
||||||
["stats", "[--repo PATH]", "repo + tokens.json", "compare remote URLs with local token store"],
|
["stats", "[--repo PATH]", "repo + tokens.json", "compare remote URLs with local token store"],
|
||||||
["write", "--remote R [--replace]", "tokens.json -> repo", "write selected token into a remote URL"],
|
["write", "--remote R [--replace]", "tokens.json -> repo", "write selected token into a remote URL"],
|
||||||
["update", "--from remotes|store", "selected", "run scan or write with explicit direction"],
|
["update", "--from remotes|store", "selected", "copy token data in an explicit direction"],
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
print()
|
print()
|
||||||
@@ -1810,7 +1895,7 @@ def build_parser() -> argparse.ArgumentParser:
|
|||||||
|
|
||||||
tokens_scan_parser = tokens_subparsers.add_parser(
|
tokens_scan_parser = tokens_subparsers.add_parser(
|
||||||
"scan",
|
"scan",
|
||||||
help="Scan repo remotes and tokens.json, then print paired endpoint rows.",
|
help="Read repo remotes and tokens.json, then print paired token rows.",
|
||||||
)
|
)
|
||||||
add_repo_option(tokens_scan_parser)
|
add_repo_option(tokens_scan_parser)
|
||||||
|
|
||||||
@@ -1872,7 +1957,6 @@ def main() -> int:
|
|||||||
parser = build_parser()
|
parser = build_parser()
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
config = load_config(args.config)
|
config = load_config(args.config)
|
||||||
sync_tokens_from_repo(config, Path.cwd())
|
|
||||||
|
|
||||||
if args.command == "show-config":
|
if args.command == "show-config":
|
||||||
print_config(config)
|
print_config(config)
|
||||||
|
|||||||
Reference in New Issue
Block a user