Skip to content
Snippets Groups Projects
get_taiga_token.py 3.73 KiB
Newer Older
#!/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}")