#!/usr/bin/env python3 import requests import secrets from getpass import getpass from typing import Any TAIGA_BASE = "https://todo.fachschaft.cl.uni-heidelberg.de" # https://docs.taiga.io/api.html#object-auth-user-detail UserAuthDetail = dict[str, Any] class AuthenticationFailedError(Exception): pass def json_or_error(r: requests.Response) -> dict[str, Any]: if r.status_code == 401: raise AuthenticationFailedError(r.json()) elif r.status_code != 200: raise RuntimeError(r.json()) return r.json() def __login_with_type(username: str, psasword: str, type_: str) -> UserAuthDetail: # See https://docs.taiga.io/api.html#auth-normal-login r = requests.post( f"{TAIGA_BASE}/api/v1/auth", json={ "type": type_, "username": username, "password": password, }, ) return json_or_error(r) def login(username: str, password: str) -> UserAuthDetail: """Log in to the Taiga instance at TAIGA_BASE with the given username and password. If successful, return an auth_detail dictionary, which (among other things) contains an "auth_token" that can be used to authorize API operations. This first treats the account as an LDAP account. If this doesn't work, it falls back to normal authentication. :return: auth_detail dictionary, token stored with key "auth_token" :raise AuthenticationFailedError: When authentication is not possible (HTTP 401) """ try: auth_detail = __login_with_type(username, password, "ldap") except AuthenticationFailedError: auth_detail = __login_with_type(username, password, "normal") return auth_detail def get_auth_code_dict(application_id: str, user_auth_token: str) -> dict[str, Any]: """ https://docs.taiga.io/api.html#external-app-authorization """ r = requests.post( f"{TAIGA_BASE}/api/v1/application-tokens/authorize", json={"application": application_id, "state": secrets.token_hex(48)}, headers={"Authorization": f"Bearer {user_auth_token}"}, ) return json_or_error(r) def get_token_dict( application_id: str, user_auth_token: str, auth_code_dict: dict[str, Any] ) -> dict[str, Any]: r = requests.post( f"{TAIGA_BASE}/api/v1/application-tokens/validate", json={ "application": application_id, "auth_code": auth_code_dict["auth_code"], "state": auth_code_dict["state"], }, headers={"Authorization": f"Bearer {user_auth_token}"}, ) return json_or_error(r) def get_application_auth_token(application_id: str, user_auth_token: str) -> str: auth_code_dict = get_auth_code_dict(application_id, user_auth_token) token_dict = get_token_dict(application_id, user_auth_token, auth_code_dict) return token_dict["token"] if __name__ == "__main__": print( f"Tool to request an application access token from the Taiga instance {TAIGA_BASE}" ) print() username = input("[?] Username: ") password = getpass("[?] Password: ") try: auth_detail = login(username, password) print("[✓] Successfully logged in.") print() except AuthenticationFailedError: print( "[✗] Login failed: Maybe password or username were wrong, or Taiga made an error." ) exit(1) print( f"[!] Now, create an application under {TAIGA_BASE}/admin/external_apps/application " f"(you will need an admin account for this)." ) print() application_id = input("[?] Application id: ") application_auth_token = get_application_auth_token( application_id, auth_detail["auth_token"] ) print(f"[✓] Your application auth token is: {application_auth_token}")