lumos-identity-cli 0.0.0.dev1__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.
- lumos/__init__.py +4 -0
- lumos/__main__.py +9 -0
- lumos/cli.py +96 -0
- lumos/common/__init__.py +1 -0
- lumos/common/client.py +479 -0
- lumos/common/client_helpers.py +40 -0
- lumos/common/helpers.py +90 -0
- lumos/common/keyhelpers.py +41 -0
- lumos/common/logging.py +30 -0
- lumos/common/models.py +236 -0
- lumos/list_collections/__init__.py +1 -0
- lumos/list_collections/cli.py +289 -0
- lumos/request/__init__.py +1 -0
- lumos/request/cli.py +523 -0
- lumos_identity_cli-0.0.0.dev1.dist-info/METADATA +77 -0
- lumos_identity_cli-0.0.0.dev1.dist-info/RECORD +18 -0
- lumos_identity_cli-0.0.0.dev1.dist-info/WHEEL +4 -0
- lumos_identity_cli-0.0.0.dev1.dist-info/entry_points.txt +2 -0
lumos/__init__.py
ADDED
lumos/__main__.py
ADDED
lumos/cli.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from click_extra import Context, echo, group, option, pass_context
|
|
4
|
+
|
|
5
|
+
from lumos import __version__
|
|
6
|
+
from lumos.common.client import ApiClient
|
|
7
|
+
from lumos.common.helpers import authenticate
|
|
8
|
+
from lumos.common.helpers import login as _login
|
|
9
|
+
from lumos.common.helpers import logout as _logout
|
|
10
|
+
from lumos.common.helpers import setup as _setup
|
|
11
|
+
from lumos.common.logging import logdebug
|
|
12
|
+
|
|
13
|
+
client = ApiClient()
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@group(
|
|
17
|
+
context_settings={"help_option_names": ["-h", "--help"]},
|
|
18
|
+
version=__version__,
|
|
19
|
+
)
|
|
20
|
+
@option("--debug", is_flag=True, help="Enable debug mode", hidden=True)
|
|
21
|
+
@pass_context
|
|
22
|
+
def lumos(ctx: Context, debug: bool) -> None:
|
|
23
|
+
"""Lumos CLI - Command line interface for Lumos"""
|
|
24
|
+
if debug:
|
|
25
|
+
os.environ["DEBUG"] = "1"
|
|
26
|
+
logdebug("🐞 Debug mode enabled")
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@lumos.command("whoami", help="Show information about the currently logged in user.")
|
|
30
|
+
@option("--username", is_flag=True, help="Show the current user's username only")
|
|
31
|
+
@option("--id", "show_id", is_flag=True, help="Show the current user's ID only")
|
|
32
|
+
@authenticate
|
|
33
|
+
def whoami(username: bool, show_id: bool) -> None:
|
|
34
|
+
user = client.get_current_user()
|
|
35
|
+
if username:
|
|
36
|
+
echo(user.email)
|
|
37
|
+
return
|
|
38
|
+
if show_id:
|
|
39
|
+
echo(user.id)
|
|
40
|
+
return
|
|
41
|
+
msg = f" 💻 Logged in as {user.given_name} {user.family_name} ({user.email})"
|
|
42
|
+
if scope := os.environ.get("SCOPE"):
|
|
43
|
+
msg += f" as {scope}"
|
|
44
|
+
echo(msg)
|
|
45
|
+
echo(f"Your ID is {user.id}, if you need to reference it")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@lumos.command(
|
|
49
|
+
"setup",
|
|
50
|
+
help="Setup your Lumos CLI. Can be used to login or change your authentication method.",
|
|
51
|
+
)
|
|
52
|
+
def setup():
|
|
53
|
+
_setup(show_overwrite_prompt=True)
|
|
54
|
+
# Call whoami command programmatically
|
|
55
|
+
ctx = Context(whoami)
|
|
56
|
+
ctx.invoke(whoami, username=False, show_id=False)
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
@lumos.command(
|
|
60
|
+
"login",
|
|
61
|
+
help="Login to your Lumos account via OAuth. You must be logged in to Lumos on your browser.",
|
|
62
|
+
)
|
|
63
|
+
@option(
|
|
64
|
+
"--admin",
|
|
65
|
+
is_flag=True,
|
|
66
|
+
help="Log in as an admin, if you have the permission to do so",
|
|
67
|
+
)
|
|
68
|
+
def login(admin: bool):
|
|
69
|
+
_logout()
|
|
70
|
+
_login(admin)
|
|
71
|
+
# Call whoami command programmatically
|
|
72
|
+
ctx = Context(whoami)
|
|
73
|
+
ctx.invoke(whoami, username=False, show_id=False)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@lumos.command("logout", help="Logout of your Lumos account.")
|
|
77
|
+
def logout():
|
|
78
|
+
_logout()
|
|
79
|
+
echo(" 👋 Logged out!")
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
# Import and register subcommands
|
|
83
|
+
def register_subcommands():
|
|
84
|
+
"""Register all subcommands after the main CLI group is defined"""
|
|
85
|
+
from lumos.list_collections.cli import list_group
|
|
86
|
+
from lumos.request.cli import request
|
|
87
|
+
|
|
88
|
+
lumos.add_command(request)
|
|
89
|
+
lumos.add_command(list_group, name="list")
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
# Register subcommands at module load time
|
|
93
|
+
register_subcommands()
|
|
94
|
+
|
|
95
|
+
if __name__ == "__main__":
|
|
96
|
+
lumos()
|
lumos/common/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# Common utilities for Lumos CLI
|
lumos/common/client.py
ADDED
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import webbrowser
|
|
4
|
+
from abc import abstractmethod
|
|
5
|
+
from typing import Any, ClassVar
|
|
6
|
+
from uuid import UUID
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
from click_extra import confirm, echo
|
|
10
|
+
|
|
11
|
+
from lumos import __version__
|
|
12
|
+
from lumos.common.client_helpers import check_version_header
|
|
13
|
+
from lumos.common.keyhelpers import write_key
|
|
14
|
+
from lumos.common.logging import logdebug_request, logdebug_response
|
|
15
|
+
from lumos.common.models import (
|
|
16
|
+
AccessRequest,
|
|
17
|
+
App,
|
|
18
|
+
AppSetting,
|
|
19
|
+
Group,
|
|
20
|
+
Permission,
|
|
21
|
+
SupportRequestStatus,
|
|
22
|
+
User,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BaseClient:
|
|
27
|
+
url: str
|
|
28
|
+
|
|
29
|
+
def __init__(self, url: str):
|
|
30
|
+
self.url = url
|
|
31
|
+
|
|
32
|
+
def get(self, endpoint: str, params: dict | None = None):
|
|
33
|
+
"""Function to call an API endpoint and return the response."""
|
|
34
|
+
if params is None:
|
|
35
|
+
params = {}
|
|
36
|
+
return self._send_request("GET", endpoint, params=params)
|
|
37
|
+
|
|
38
|
+
def get_paged(
|
|
39
|
+
self,
|
|
40
|
+
endpoint: str,
|
|
41
|
+
params: dict | None = None,
|
|
42
|
+
page_size: int = 100,
|
|
43
|
+
page: int = 1,
|
|
44
|
+
) -> tuple[list[dict[str, Any]], int, int, int, int]:
|
|
45
|
+
"""Function to call an API endpoint and return the paged response."""
|
|
46
|
+
if params is None:
|
|
47
|
+
params = {}
|
|
48
|
+
params = {**params, "size": page_size, "page": page}
|
|
49
|
+
response = self.get(endpoint, params=params)
|
|
50
|
+
if response:
|
|
51
|
+
return (
|
|
52
|
+
response["items"],
|
|
53
|
+
len(response["items"]),
|
|
54
|
+
int(response["total"]),
|
|
55
|
+
int(response["page"]),
|
|
56
|
+
int(response["pages"]),
|
|
57
|
+
)
|
|
58
|
+
return [], 0, 0, 0, 0
|
|
59
|
+
|
|
60
|
+
def get_all(
|
|
61
|
+
self,
|
|
62
|
+
endpoint: str,
|
|
63
|
+
params: dict | None = None,
|
|
64
|
+
) -> tuple[list[dict[str, Any]], int, int, int, int]:
|
|
65
|
+
"""Function to call an API endpoint and return all the results."""
|
|
66
|
+
if params is None:
|
|
67
|
+
params = {}
|
|
68
|
+
all_results: list[Any] = []
|
|
69
|
+
page = 1
|
|
70
|
+
while True:
|
|
71
|
+
results, _, total, _, pages = self.get_paged(endpoint, params=params, page=page, page_size=100)
|
|
72
|
+
if total == 0:
|
|
73
|
+
break
|
|
74
|
+
all_results.extend(results)
|
|
75
|
+
if page == pages:
|
|
76
|
+
break
|
|
77
|
+
if (
|
|
78
|
+
page == 1
|
|
79
|
+
and total > 5000
|
|
80
|
+
and not confirm(
|
|
81
|
+
f"Warning: {total} results found. This may take a while. Do you want to continue?",
|
|
82
|
+
default=True,
|
|
83
|
+
)
|
|
84
|
+
):
|
|
85
|
+
raise SystemExit(1)
|
|
86
|
+
page += 1
|
|
87
|
+
time.sleep(0.15)
|
|
88
|
+
count = len(all_results)
|
|
89
|
+
return all_results, count, count, 1, 1
|
|
90
|
+
|
|
91
|
+
def get_all_or_paged(
|
|
92
|
+
self,
|
|
93
|
+
endpoint: str,
|
|
94
|
+
params: dict | None = None,
|
|
95
|
+
all: bool = False,
|
|
96
|
+
page_size: int = 100,
|
|
97
|
+
page: int = 1,
|
|
98
|
+
) -> tuple[list[dict[str, Any]], int, int, int, int]:
|
|
99
|
+
if params is None:
|
|
100
|
+
params = {}
|
|
101
|
+
"""Function to call an API endpoint and return all the results."""
|
|
102
|
+
if all:
|
|
103
|
+
return self.get_all(endpoint, params=params)
|
|
104
|
+
return self.get_paged(endpoint, params=params, page_size=page_size, page=page)
|
|
105
|
+
|
|
106
|
+
def post(self, endpoint: str, body: dict[str, Any]):
|
|
107
|
+
"""Function to call an API endpoint and return the response."""
|
|
108
|
+
return self._send_request("POST", endpoint, body=body)
|
|
109
|
+
|
|
110
|
+
def delete(self, endpoint: str, params: dict | None = None):
|
|
111
|
+
if params is None:
|
|
112
|
+
params = {}
|
|
113
|
+
return self._send_request("DELETE", endpoint, params=params)
|
|
114
|
+
|
|
115
|
+
def _send_request(
|
|
116
|
+
self,
|
|
117
|
+
method: str,
|
|
118
|
+
endpoint: str,
|
|
119
|
+
body: dict | None = None,
|
|
120
|
+
params: dict | None = None,
|
|
121
|
+
retry: int = 0,
|
|
122
|
+
):
|
|
123
|
+
if params is None:
|
|
124
|
+
params = {}
|
|
125
|
+
if retry > 3:
|
|
126
|
+
echo("Too many retries. Exiting.", err=True)
|
|
127
|
+
raise SystemExit(1)
|
|
128
|
+
url, headers = self._get_url_and_headers(endpoint)
|
|
129
|
+
logdebug_request(url, headers, body, params, method)
|
|
130
|
+
|
|
131
|
+
response = requests.request(method, url, headers=headers, json=body, params=params)
|
|
132
|
+
|
|
133
|
+
logdebug_response(response)
|
|
134
|
+
|
|
135
|
+
if not check_version_header(response):
|
|
136
|
+
raise SystemExit(1)
|
|
137
|
+
|
|
138
|
+
if response.ok:
|
|
139
|
+
if response.status_code == 204:
|
|
140
|
+
return None
|
|
141
|
+
return response.json()
|
|
142
|
+
if response.status_code == 429:
|
|
143
|
+
echo("We're being rate limited. Waiting a sec.", err=True)
|
|
144
|
+
retry += 1
|
|
145
|
+
time.sleep(retry)
|
|
146
|
+
return self._send_request(method, endpoint, body, params, retry)
|
|
147
|
+
if response.status_code == 401:
|
|
148
|
+
if retry > 1 or not (scope := os.environ.get("SCOPE")):
|
|
149
|
+
echo(
|
|
150
|
+
"Something went wrong with authorization. Try logging in again.",
|
|
151
|
+
err=True,
|
|
152
|
+
)
|
|
153
|
+
raise SystemExit(1)
|
|
154
|
+
echo(
|
|
155
|
+
"Something went wrong with authorization. Trying to log in again.",
|
|
156
|
+
err=True,
|
|
157
|
+
)
|
|
158
|
+
AuthClient().authenticate(scope == "admin")
|
|
159
|
+
return self._send_request(method, endpoint, body, params, retry + 1)
|
|
160
|
+
if response.status_code == 403:
|
|
161
|
+
echo("You don't have permission to do that.", err=True)
|
|
162
|
+
raise SystemExit(1)
|
|
163
|
+
|
|
164
|
+
if response.status_code == 404:
|
|
165
|
+
echo("Not found.", err=True)
|
|
166
|
+
raise SystemExit(1)
|
|
167
|
+
|
|
168
|
+
echo(f"An error occurred (status code {response.status_code})", err=True)
|
|
169
|
+
if response.status_code == 409 or response.status_code == 422:
|
|
170
|
+
response_json = response.json()
|
|
171
|
+
if detail := response_json.get("detail"):
|
|
172
|
+
echo(detail, err=True)
|
|
173
|
+
|
|
174
|
+
raise SystemExit(1)
|
|
175
|
+
|
|
176
|
+
@abstractmethod
|
|
177
|
+
def _get_url_and_headers(self, endpoint: str) -> tuple[str, dict[str, str]]:
|
|
178
|
+
pass
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
class AuthClient(BaseClient):
|
|
182
|
+
GRANT_TYPE = "urn:ietf:params:oauth:grant-type:device_code"
|
|
183
|
+
HEADERS: ClassVar[dict[str, str]] = {
|
|
184
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
185
|
+
"User-Agent": f"lumos-cli/{__version__}",
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
def __init__(self):
|
|
189
|
+
auth_url: str | None = None
|
|
190
|
+
if os.environ.get("DEV_MODE"):
|
|
191
|
+
auth_url = os.environ.get("AUTH_URL")
|
|
192
|
+
super().__init__(auth_url or "https://b.app.lumosidentity.com")
|
|
193
|
+
|
|
194
|
+
def _get_url_and_headers(self, endpoint: str) -> tuple[str, dict[str, str]]:
|
|
195
|
+
return f"{self.url}/b/oauth/{endpoint}", self.HEADERS
|
|
196
|
+
|
|
197
|
+
def _get_client_id(self) -> str:
|
|
198
|
+
if auth_url := os.environ.get("AUTH_URL"):
|
|
199
|
+
if "localhost" in auth_url:
|
|
200
|
+
return "IHcwQtVXJH8RVrPag3Tqi17KDdI6X9Ja"
|
|
201
|
+
if "qa" in auth_url:
|
|
202
|
+
return "37o8paGpj1BvV6NOFmVO21cG8L9ePSCs"
|
|
203
|
+
if "staging" in auth_url:
|
|
204
|
+
return "tLPbUcyJZZyEvuxTo5deyFQXkO9iXZyx"
|
|
205
|
+
return "XfsajAwB6pl2XyNYwzrVkTI15ISbQ2dR"
|
|
206
|
+
|
|
207
|
+
def authenticate(self, admin: bool = False):
|
|
208
|
+
url, headers = self._get_url_and_headers("device/code")
|
|
209
|
+
client_id = self._get_client_id()
|
|
210
|
+
scope = "admin" if admin else "user"
|
|
211
|
+
params = {
|
|
212
|
+
"client_id": client_id,
|
|
213
|
+
"scope": scope,
|
|
214
|
+
}
|
|
215
|
+
logdebug_request(url, headers, None, params, "POST")
|
|
216
|
+
|
|
217
|
+
response = requests.post(url, headers=headers, params=params)
|
|
218
|
+
if not check_version_header(response):
|
|
219
|
+
raise SystemExit(1)
|
|
220
|
+
|
|
221
|
+
if not response.ok:
|
|
222
|
+
echo("Something went wrong.")
|
|
223
|
+
logdebug_response(response)
|
|
224
|
+
raise SystemExit(1)
|
|
225
|
+
|
|
226
|
+
device_auth_data = response.json()
|
|
227
|
+
|
|
228
|
+
verification_uri_complete = device_auth_data["verification_uri_complete"]
|
|
229
|
+
if os.environ.get("DEV_MODE"):
|
|
230
|
+
if url.startswith("http://"):
|
|
231
|
+
verification_uri_complete = verification_uri_complete.replace("https://", "http://")
|
|
232
|
+
if "localhost:3000" in verification_uri_complete:
|
|
233
|
+
verification_uri_complete = verification_uri_complete.replace("localhost:3000", "localhost:8080")
|
|
234
|
+
|
|
235
|
+
webbrowser.open(verification_uri_complete)
|
|
236
|
+
|
|
237
|
+
token_data = {
|
|
238
|
+
"client_id": client_id,
|
|
239
|
+
"device_code": device_auth_data["device_code"],
|
|
240
|
+
"grant_type": self.GRANT_TYPE,
|
|
241
|
+
}
|
|
242
|
+
token_url, _ = self._get_url_and_headers("token")
|
|
243
|
+
echo(
|
|
244
|
+
f" 🔑 Please go to {verification_uri_complete} to authenticate. You must already be logged in to Lumos on your browser."
|
|
245
|
+
)
|
|
246
|
+
|
|
247
|
+
logdebug_request(token_url, headers, token_data, None, "POST")
|
|
248
|
+
|
|
249
|
+
wait = 0
|
|
250
|
+
while True:
|
|
251
|
+
print(
|
|
252
|
+
" ⏰ Waiting" + ("." * (wait % 10)) + (" " * (10 - (wait % 10))),
|
|
253
|
+
end="\r",
|
|
254
|
+
)
|
|
255
|
+
token_response = requests.post(token_url, headers=headers, data=token_data)
|
|
256
|
+
if token_response.status_code == 400:
|
|
257
|
+
echo(f"Bad request: {token_response.json()}")
|
|
258
|
+
raise SystemExit(1)
|
|
259
|
+
logdebug_response(token_response)
|
|
260
|
+
if token_response.status_code != 200:
|
|
261
|
+
time.sleep(1)
|
|
262
|
+
wait += 1
|
|
263
|
+
else:
|
|
264
|
+
token_data = token_response.json()
|
|
265
|
+
token = token_data["access_token"]
|
|
266
|
+
break
|
|
267
|
+
if wait > 60:
|
|
268
|
+
echo("Timed out waiting for authentication.")
|
|
269
|
+
raise SystemExit(1)
|
|
270
|
+
echo(" ✅ Authenticated!")
|
|
271
|
+
write_key(token, scope)
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
class ApiClient(BaseClient):
|
|
275
|
+
def __init__(self):
|
|
276
|
+
api_url: str | None = None
|
|
277
|
+
if os.environ.get("DEV_MODE"):
|
|
278
|
+
api_url = os.environ.get("API_URL")
|
|
279
|
+
super().__init__(api_url or "https://api.lumos.com")
|
|
280
|
+
|
|
281
|
+
def _get_url_and_headers(self, endpoint: str) -> tuple[str, dict[str, str]]:
|
|
282
|
+
api_key = os.environ.get("API_KEY")
|
|
283
|
+
headers = {
|
|
284
|
+
"Authorization": f"Bearer {api_key}",
|
|
285
|
+
"Accept": "application/json",
|
|
286
|
+
"User-Agent": f"lumos-cli/{__version__}",
|
|
287
|
+
}
|
|
288
|
+
return f"{self.url}/{endpoint}", headers
|
|
289
|
+
|
|
290
|
+
def get_current_user_id(self) -> UUID:
|
|
291
|
+
if not os.environ.get("USER_ID"):
|
|
292
|
+
user = self.get_current_user()
|
|
293
|
+
os.environ["USER_ID"] = str(user.id)
|
|
294
|
+
return user.id
|
|
295
|
+
return UUID(os.environ["USER_ID"])
|
|
296
|
+
|
|
297
|
+
def get_current_user(self) -> User:
|
|
298
|
+
user = self.get("users/current")
|
|
299
|
+
if user:
|
|
300
|
+
os.environ["USER_ID"] = user["id"]
|
|
301
|
+
return User(**user)
|
|
302
|
+
echo("You are not logged in", err=True)
|
|
303
|
+
raise SystemExit(1)
|
|
304
|
+
|
|
305
|
+
def get_appstore_app(self, id: UUID) -> App | None:
|
|
306
|
+
raw_app = self.get(f"appstore/apps/{id}")
|
|
307
|
+
if raw_app:
|
|
308
|
+
return App(**raw_app)
|
|
309
|
+
return None
|
|
310
|
+
|
|
311
|
+
def get_appstore_app_setting(self, id: UUID) -> AppSetting:
|
|
312
|
+
raw_setting = self.get(f"appstore/apps/{id}/settings")
|
|
313
|
+
return AppSetting(**raw_setting)
|
|
314
|
+
|
|
315
|
+
def get_request_status(self, id: UUID) -> AccessRequest | None:
|
|
316
|
+
raw_request = self.get(f"appstore/access_requests/{id}")
|
|
317
|
+
return self._create_access_request(raw_request)
|
|
318
|
+
|
|
319
|
+
def cancel_access_request(self, id: UUID, reason: str | None) -> None:
|
|
320
|
+
params = {}
|
|
321
|
+
if reason:
|
|
322
|
+
params["reason"] = reason
|
|
323
|
+
self.delete(f"appstore/access_requests/{id}", params)
|
|
324
|
+
|
|
325
|
+
def _create_access_request(self, raw_request: dict | None) -> AccessRequest | None:
|
|
326
|
+
if not raw_request:
|
|
327
|
+
return None
|
|
328
|
+
return AccessRequest(**raw_request)
|
|
329
|
+
|
|
330
|
+
def get_appstore_apps(
|
|
331
|
+
self,
|
|
332
|
+
name_search: str | None = None,
|
|
333
|
+
all: bool = False,
|
|
334
|
+
page_size: int = 100,
|
|
335
|
+
page: int = 1,
|
|
336
|
+
) -> tuple[list[App], int, int]:
|
|
337
|
+
endpoint = "appstore/apps"
|
|
338
|
+
params: dict[str, Any] = {}
|
|
339
|
+
if name_search:
|
|
340
|
+
params["name_search"] = name_search
|
|
341
|
+
raw_apps, count, total, _, _ = self.get_all_or_paged(
|
|
342
|
+
endpoint, params=params, all=all, page_size=page_size, page=page
|
|
343
|
+
)
|
|
344
|
+
apps: list[App] = []
|
|
345
|
+
for item in raw_apps:
|
|
346
|
+
apps.append(App(**item))
|
|
347
|
+
return apps, count, total
|
|
348
|
+
|
|
349
|
+
def get_my_apps(self, for_user: UUID | None = None) -> list[App]:
|
|
350
|
+
user = for_user or self.get_current_user_id()
|
|
351
|
+
statuses = SupportRequestStatus.PENDING_STATUSES + SupportRequestStatus.SUCCESS_STATUSES
|
|
352
|
+
access_requests, _, _, _, _ = self.get_access_requests(
|
|
353
|
+
target_user_id=user,
|
|
354
|
+
status=statuses,
|
|
355
|
+
all=True,
|
|
356
|
+
)
|
|
357
|
+
return access_requests
|
|
358
|
+
|
|
359
|
+
def get_access_requests(
|
|
360
|
+
self,
|
|
361
|
+
target_user_id: UUID | None = None,
|
|
362
|
+
status: list[str] | None = None,
|
|
363
|
+
all: bool = False,
|
|
364
|
+
page_size: int = 100,
|
|
365
|
+
page: int = 1,
|
|
366
|
+
) -> tuple[list[AccessRequest], int, int, int, int]:
|
|
367
|
+
params: dict[str, Any] = {"sort": "desc"}
|
|
368
|
+
if target_user_id:
|
|
369
|
+
params["target_user_id"] = str(target_user_id)
|
|
370
|
+
if status and len(status) > 0:
|
|
371
|
+
params["statuses"] = [str(s) for s in status]
|
|
372
|
+
|
|
373
|
+
endpoint = "appstore/access_requests"
|
|
374
|
+
raw_access_requests, count, total, page, pages = self.get_all_or_paged(
|
|
375
|
+
endpoint, params=params, all=all, page=page, page_size=page_size
|
|
376
|
+
)
|
|
377
|
+
access_requests: list[AccessRequest] = []
|
|
378
|
+
for item in raw_access_requests:
|
|
379
|
+
access_request = self._create_access_request(item)
|
|
380
|
+
if access_request:
|
|
381
|
+
access_requests.append(access_request)
|
|
382
|
+
return access_requests, count, total, page, pages
|
|
383
|
+
|
|
384
|
+
def get_users(
|
|
385
|
+
self,
|
|
386
|
+
like: str | None = None,
|
|
387
|
+
all: bool = False,
|
|
388
|
+
page_size: int = 100,
|
|
389
|
+
page: int = 1,
|
|
390
|
+
) -> tuple[list[User], int, int]:
|
|
391
|
+
endpoint = "users"
|
|
392
|
+
params: dict[str, Any] = {}
|
|
393
|
+
if like:
|
|
394
|
+
params["search_term"] = like
|
|
395
|
+
raw_users, count, total, _, _ = self.get_all_or_paged(
|
|
396
|
+
endpoint, params=params, all=all, page_size=page_size, page=page
|
|
397
|
+
)
|
|
398
|
+
|
|
399
|
+
users: list[User] = []
|
|
400
|
+
for item in raw_users:
|
|
401
|
+
users.append(User(**item))
|
|
402
|
+
return users, count, total
|
|
403
|
+
|
|
404
|
+
def get_app_requestable_permissions(
|
|
405
|
+
self,
|
|
406
|
+
app_id: UUID,
|
|
407
|
+
search_term: str | None = None,
|
|
408
|
+
all: bool = False,
|
|
409
|
+
page_size: int = 100,
|
|
410
|
+
page: int = 1,
|
|
411
|
+
) -> tuple[list[Permission], int, int]:
|
|
412
|
+
endpoint = "appstore/requestable_permissions"
|
|
413
|
+
params: dict[str, Any] = {
|
|
414
|
+
"app_id": str(app_id),
|
|
415
|
+
"in_app_store": True,
|
|
416
|
+
}
|
|
417
|
+
if search_term:
|
|
418
|
+
params["search_term"] = search_term
|
|
419
|
+
|
|
420
|
+
raw_permissions, count, total, _, _ = self.get_all_or_paged(
|
|
421
|
+
endpoint, params=params, all=all, page_size=page_size, page=page
|
|
422
|
+
)
|
|
423
|
+
|
|
424
|
+
return [self._create_permission(item) for item in raw_permissions], count, total
|
|
425
|
+
|
|
426
|
+
def get_groups(
|
|
427
|
+
self,
|
|
428
|
+
app_id: UUID | None,
|
|
429
|
+
search_term: str | None = None,
|
|
430
|
+
all: bool = False,
|
|
431
|
+
page_size: int = 25,
|
|
432
|
+
page: int = 1,
|
|
433
|
+
) -> tuple[list[Group], int, int]:
|
|
434
|
+
endpoint = "groups"
|
|
435
|
+
params: dict[str, Any] = {}
|
|
436
|
+
if app_id:
|
|
437
|
+
params["app_id"] = str(app_id)
|
|
438
|
+
if search_term:
|
|
439
|
+
params["name"] = search_term
|
|
440
|
+
|
|
441
|
+
raw_permissions, count, total, _, _ = self.get_all_or_paged(
|
|
442
|
+
endpoint, params=params, all=all, page_size=page_size, page=page
|
|
443
|
+
)
|
|
444
|
+
|
|
445
|
+
return [Group(**item) for item in raw_permissions], count, total
|
|
446
|
+
|
|
447
|
+
def get_app_requestable_permission(self, permission_id: UUID) -> Permission | None:
|
|
448
|
+
item = self.get(f"appstore/requestable_permissions/{permission_id}")
|
|
449
|
+
if not item:
|
|
450
|
+
return None
|
|
451
|
+
return self._create_permission(item)
|
|
452
|
+
|
|
453
|
+
def _create_permission(self, raw_permission: dict) -> Permission:
|
|
454
|
+
permission = Permission(**raw_permission)
|
|
455
|
+
durations = raw_permission["request_config"]["request_fulfillment_config"]["time_based_access"]
|
|
456
|
+
permission.duration_options = durations
|
|
457
|
+
return permission
|
|
458
|
+
|
|
459
|
+
def create_access_request(
|
|
460
|
+
self,
|
|
461
|
+
app_id: UUID,
|
|
462
|
+
note: str,
|
|
463
|
+
expiration_in_seconds: int | None,
|
|
464
|
+
permission_ids: list[UUID] | None = None,
|
|
465
|
+
target_user_id: UUID | None = None,
|
|
466
|
+
) -> AccessRequest | None:
|
|
467
|
+
body: dict[str, Any] = {
|
|
468
|
+
"app_id": str(app_id),
|
|
469
|
+
"note": note,
|
|
470
|
+
}
|
|
471
|
+
if permission_ids:
|
|
472
|
+
body["requestable_permission_ids"] = [str(p) for p in permission_ids]
|
|
473
|
+
if expiration_in_seconds:
|
|
474
|
+
body["expiration_in_seconds"] = expiration_in_seconds
|
|
475
|
+
if target_user_id:
|
|
476
|
+
body["target_user_id"] = str(target_user_id)
|
|
477
|
+
response = self.post("appstore/access_request", body)
|
|
478
|
+
|
|
479
|
+
return AccessRequest(**response[0]) if response else None
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import requests
|
|
4
|
+
from click_extra import secho
|
|
5
|
+
|
|
6
|
+
from lumos import __version__
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def check_version_header(response: requests.Response) -> bool:
|
|
10
|
+
if os.environ.get("WARNED"):
|
|
11
|
+
return True
|
|
12
|
+
if not (version_header_string := response.headers.get("X-CLI-Version")):
|
|
13
|
+
return True
|
|
14
|
+
|
|
15
|
+
current_version = parse_version(__version__)
|
|
16
|
+
header_version = parse_version(version_header_string)
|
|
17
|
+
cont, error_message = check_version(current_version, header_version)
|
|
18
|
+
|
|
19
|
+
if error_message:
|
|
20
|
+
secho(error_message, fg="cyan" if cont else "red")
|
|
21
|
+
return cont
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def check_version(current_version: list[int], header_version: list[int]) -> tuple[bool, str | None]:
|
|
25
|
+
if current_version[0] < header_version[0]:
|
|
26
|
+
return (
|
|
27
|
+
False,
|
|
28
|
+
"A new version of the CLI is available. Please run `brew upgrade lumos` to proceed.",
|
|
29
|
+
)
|
|
30
|
+
if current_version[0] == header_version[0] and current_version[1] < header_version[1]:
|
|
31
|
+
os.environ["WARNED"] = "1"
|
|
32
|
+
return (
|
|
33
|
+
True,
|
|
34
|
+
"There's an update available to the CLI. Please run `brew upgrade lumos`.",
|
|
35
|
+
)
|
|
36
|
+
return True, None
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def parse_version(version: str) -> list[int]:
|
|
40
|
+
return [int(v) for v in version.split(".")]
|