diff --git a/scripts/get_taiga_token.py b/scripts/get_taiga_token.py new file mode 100644 index 0000000000000000000000000000000000000000..ae71f51294ba02ae8bcbebea74791a14355ac04f --- /dev/null +++ b/scripts/get_taiga_token.py @@ -0,0 +1,127 @@ +#!/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}")