|
42 | 42 | from .scratch import setup_scratch |
43 | 43 | from .updates import CliEventRenderer |
44 | 44 |
|
| 45 | +P = ParamSpec("P") |
| 46 | +T = TypeVar("T") |
| 47 | + |
| 48 | + |
| 49 | +def check_connection(func: Callable[P, T]) -> Callable[P, T]: |
| 50 | + @wraps(func) |
| 51 | + def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: |
| 52 | + try: |
| 53 | + return func(*args, **kwargs) |
| 54 | + except ConnectionError as ce: |
| 55 | + raise ClickException( |
| 56 | + "Failed to establish connection to blueapi server." |
| 57 | + ) from ce |
| 58 | + except BlueskyRemoteControlError as e: |
| 59 | + if str(e) == "<Response [401]>": |
| 60 | + raise ClickException( |
| 61 | + "Access denied. Please check your login status and try again." |
| 62 | + ) from e |
| 63 | + else: |
| 64 | + raise e |
| 65 | + |
| 66 | + return wrapper |
| 67 | + |
45 | 68 |
|
46 | 69 | def _load_config(ctx: click.Context, config: Path | None | tuple[Path, ...]) -> None: |
47 | 70 | config_loader = ConfigLoader(ApplicationConfig) |
@@ -156,6 +179,52 @@ def start_application(ctx: click.Context, config: Path | None | tuple[Path, ...] |
156 | 179 | start(loaded_config) |
157 | 180 |
|
158 | 181 |
|
| 182 | +@main.command(name="login") |
| 183 | +@click.pass_obj |
| 184 | +@check_connection |
| 185 | +def login(obj: dict) -> None: |
| 186 | + """ |
| 187 | + Authenticate with the blueapi using the OIDC (OpenID Connect) flow. |
| 188 | + """ |
| 189 | + config: ApplicationConfig = obj["config"] |
| 190 | + try: |
| 191 | + auth: SessionManager = SessionManager.from_cache(config.auth_token_path) |
| 192 | + access_token = auth.get_valid_access_token() |
| 193 | + assert access_token |
| 194 | + print("Logged in") |
| 195 | + except Exception: |
| 196 | + client = BlueapiClient.from_config(config) |
| 197 | + oidc_config = client.get_oidc_config() |
| 198 | + if oidc_config is None: |
| 199 | + print("Server is not configured to use authentication!") |
| 200 | + return |
| 201 | + auth = SessionManager( |
| 202 | + oidc_config, cache_manager=SessionCacheManager(config.auth_token_path) |
| 203 | + ) |
| 204 | + auth.start_device_flow() |
| 205 | + |
| 206 | + |
| 207 | +@main.command(name="logout") |
| 208 | +@click.pass_obj |
| 209 | +def logout(obj: dict) -> None: |
| 210 | + """ |
| 211 | + Logs out from the OIDC provider and removes the cached access token. |
| 212 | + """ |
| 213 | + config: ApplicationConfig = obj["config"] |
| 214 | + try: |
| 215 | + auth: SessionManager = SessionManager.from_cache(config.auth_token_path) |
| 216 | + auth.logout() |
| 217 | + except FileNotFoundError: |
| 218 | + print("Logged out") |
| 219 | + except ValueError as e: |
| 220 | + logging.debug("Invalid login token: %s", e) |
| 221 | + raise ClickException( |
| 222 | + "Login token is not valid - remove before trying again" |
| 223 | + ) from e |
| 224 | + except Exception as e: |
| 225 | + raise ClickException(f"Error logging out: {e}") from e |
| 226 | + |
| 227 | + |
159 | 228 | @main.group() |
160 | 229 | @click.option( |
161 | 230 | "-o", |
@@ -197,30 +266,6 @@ def controller( |
197 | 266 | ctx.obj["client"] = BlueapiClient.from_config(config) |
198 | 267 |
|
199 | 268 |
|
200 | | -P = ParamSpec("P") |
201 | | -T = TypeVar("T") |
202 | | - |
203 | | - |
204 | | -def check_connection(func: Callable[P, T]) -> Callable[P, T]: |
205 | | - @wraps(func) |
206 | | - def wrapper(*args: P.args, **kwargs: P.kwargs) -> T: |
207 | | - try: |
208 | | - return func(*args, **kwargs) |
209 | | - except ConnectionError as ce: |
210 | | - raise ClickException( |
211 | | - "Failed to establish connection to blueapi server." |
212 | | - ) from ce |
213 | | - except BlueskyRemoteControlError as e: |
214 | | - if str(e) == "<Response [401]>": |
215 | | - raise ClickException( |
216 | | - "Access denied. Please check your login status and try again." |
217 | | - ) from e |
218 | | - else: |
219 | | - raise e |
220 | | - |
221 | | - return wrapper |
222 | | - |
223 | | - |
224 | 269 | @controller.command(name="plans") |
225 | 270 | @click.pass_obj |
226 | 271 | @check_connection |
@@ -476,49 +521,3 @@ def get_python_env(obj: dict, name: str, source: SourceInfo) -> None: |
476 | 521 | """ |
477 | 522 | client: BlueapiClient = obj["client"] |
478 | 523 | obj["fmt"].display(client.get_python_env(name=name, source=source)) |
479 | | - |
480 | | - |
481 | | -@main.command(name="login") |
482 | | -@click.pass_obj |
483 | | -@check_connection |
484 | | -def login(obj: dict) -> None: |
485 | | - """ |
486 | | - Authenticate with the blueapi using the OIDC (OpenID Connect) flow. |
487 | | - """ |
488 | | - config: ApplicationConfig = obj["config"] |
489 | | - try: |
490 | | - auth: SessionManager = SessionManager.from_cache(config.auth_token_path) |
491 | | - access_token = auth.get_valid_access_token() |
492 | | - assert access_token |
493 | | - print("Logged in") |
494 | | - except Exception: |
495 | | - client = BlueapiClient.from_config(config) |
496 | | - oidc_config = client.get_oidc_config() |
497 | | - if oidc_config is None: |
498 | | - print("Server is not configured to use authentication!") |
499 | | - return |
500 | | - auth = SessionManager( |
501 | | - oidc_config, cache_manager=SessionCacheManager(config.auth_token_path) |
502 | | - ) |
503 | | - auth.start_device_flow() |
504 | | - |
505 | | - |
506 | | -@main.command(name="logout") |
507 | | -@click.pass_obj |
508 | | -def logout(obj: dict) -> None: |
509 | | - """ |
510 | | - Logs out from the OIDC provider and removes the cached access token. |
511 | | - """ |
512 | | - config: ApplicationConfig = obj["config"] |
513 | | - try: |
514 | | - auth: SessionManager = SessionManager.from_cache(config.auth_token_path) |
515 | | - auth.logout() |
516 | | - except FileNotFoundError: |
517 | | - print("Logged out") |
518 | | - except ValueError as e: |
519 | | - logging.debug("Invalid login token: %s", e) |
520 | | - raise ClickException( |
521 | | - "Login token is not valid - remove before trying again" |
522 | | - ) from e |
523 | | - except Exception as e: |
524 | | - raise ClickException(f"Error logging out: {e}") from e |
0 commit comments