#!/bin/bash # SPDX-License-Identifier: MIT VERSION=1.0.0 SELF_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" VERBOSE=false DEBUG=false : ${EXIT_ON_ERROR:=true} : ${TOKEN_NAME:=forgejo-curl} : ${DOT:=$HOME/.forgejo-curl} function debug() { DEBUG=true set -x PS4='${BASH_SOURCE[0]}:$LINENO: ${FUNCNAME[0]}: ' } function verbose() { VERBOSE=true } function log() { echo "$@" >&2 } function log_error() { log "$@" } function log_verbose() { if $VERBOSE ; then log "$@" fi } function log_info() { log "$@" } function fatal_error() { log_error "$@" if $EXIT_ON_ERROR ; then exit 1 else return 1 fi } function dot_ensure() { mkdir -p $DOT } HEADER_JSON='-H Content-Type:application/json' HEADER_TOKEN="-H @$DOT/header-token" HEADER_CSRF="-H @$DOT/header-csrf" function api() { client $HEADER_TOKEN "$@" } function api_json() { api $HEADER_JSON "$@" } function login_api() { local user="$1" password="$2" token="$3" scopes="${4:-[\"all\"]}" url="$5" dot_ensure if test -s $DOT/token ; then log_info "already logged in, ignored" return fi if test -z "$token" ; then log_verbose curl -sS -X DELETE --user "${user}:${password}" "${url}/api/v1/users/$user/tokens/${TOKEN_NAME}" -o /dev/null -w "%{http_code}" local basic="${user:-unknown}:${password:-unknown}" local status=$(curl -sS -X DELETE --user "${basic}" "${url}/api/v1/users/$user/tokens/${TOKEN_NAME}" -o /dev/null -w "%{http_code}") if test "${status}" != 404 -a "${status}" != 204 ; then fatal_error permission denied, the user or password are probably incorrect, try again with --verbose return 1 fi token=$(client $HEADER_JSON --user "${basic}" --data-raw '{"name":"'${TOKEN_NAME}'","scopes":'${scopes}'}' "${url}/api/v1/users/${user}/tokens" | jq --raw-output .sha1) fi if [[ "$token" =~ ^@ ]] ; then cp "${token##@}" $DOT/token else echo "$token" > $DOT/token fi ( echo -n "Authorization: token " ; cat $DOT/token ) > $DOT/header-token # # Verify the token works # local status=$(api -w "%{http_code}" -o /dev/null "${url}/api/v1/user") if test "${status}" != 200 ; then fatal_error "${url}/api/v1/user returns status code '${status}', the token is invalid, $0 logout and login again" return 1 fi } function client() { log_verbose curl --cookie $DOT/cookies -f -sS "$@" if ! curl --cookie $DOT/cookies -f -sS "$@" ; then fatal_error fi } function web() { client $HEADER_CSRF "$@" } function client_update_cookies() { log_verbose curl --cookie-jar $DOT/cookies --cookie $DOT/cookies -w "%{http_code}" -f -sS "$@" local status=$(curl --cookie-jar $DOT/cookies --cookie $DOT/cookies -w "%{http_code}" -f -sS "$@") if ! test "${status}" = 200 -o "${status}" = 303 ; then fatal_error fi } function login_client() { local user="$1" password="$2" url="$3" if test -z "$password" ; then log_verbose "no password, web will not be authenticated" return fi dot_ensure # # Get the CSRF required for login # client_update_cookies -o /dev/null "${url}/user/login" # # The login stores a cookie # client_update_cookies -X POST --data "user_name=${user}" --data "password=${password}" "${url}/user/login" -o $DOT/login.html # # Get the CSRF for reuse by other requests # client_update_cookies -o /dev/null "${url}/user/login" local csrf=$(sed -n -e '/csrf/s/.*csrf\t//p' $DOT/cookies) echo "X-Csrf-Token: $csrf" > $DOT/header-csrf # # Verify it works # local status=$(web -o /dev/null -w "%{http_code}" "${url}/user/settings") if test "${status}" != 200 ; then grep -C 1 flash-error $DOT/login.html if ${DEBUG} ; then cat $DOT/login.html fi fatal_error login failed, the user or password are probably incorrect, try again with --verbose fi } function login() { local user="$1" password="$2" token="$3" scope="$4" url="$5" login_client "${user}" "${password}" "${url}" login_api "${user}" "${password}" "${token}" "${scope}" "${url}" } function logout() { rm -f $DOT/* if test -d $DOT ; then rmdir $DOT fi } function usage() { cat >&2 <] [--password ] [--token {|<@tokenfilename>}] [--scopes ] login URL forgejo-curl.sh logout OPTIONS --user username --password password of --scopes scopes of the token to be created (default ["all"]) --token {|<@tokenfilename>} personal access token EXAMPLES forgejo-curl.sh --token ABCD \\ login https://forgejo.example.com web is not authenticated api, api_json use ABCD to authenticate forgejo-curl.sh --token @/tmp/token \\ login https://forgejo.example.com web is not authenticated api, api_json use the content of /tmp/token to authenticate forgejo-curl.sh --user joe --password passw0rd \\ login https://forgejo.example.com web is authenticated api, api_json use a newly generated token that belongs to user joe with scope ["all"] to authenticate forgejo-curl.sh --user joe --password passw0rd --scopes '["write:package","write:issue"]' \\ login https://forgejo.example.com web is authenticated api, api_json use a newly generated token with write permission to packages and issues to authenticate forgejo-curl.sh [--verbose] [--debug] web [curl options]" call curl using the CSRF token generated by the login command EXAMPLES forgejo-curl.sh web --form avatar=@avatar.png https://forgejo.example.com/settings/avatar upload the file avatar.png and update the avatar of the logged in user forgejo-curl.sh [--verbose] [--debug] api|api_json [curl options]" call curl using the token given to (or generated by) the login command. If called using api_json, the Content-Type header is set to application/json. EXAMPLES forgejo-curl.sh api_json --data-raw '{"title":"TITLE"}' \\ https://forgejo.example.com/api/v1/repos/joe/test/issues create a new issue in the repository test forgejo-curl.sh api --form name=image.png --form attachment=@image.png \\ https://forgejo.example.com/api/v1/repos/joe/test/issues/1234/assets add the image.png file as an attachment to the issue 1234 in the test repository forgejo-curl.sh --help - display help forgejo-curl.sh --version - show the version EOF } function main() { local command=login user password token scopes while true; do case "$1" in --verbose) shift verbose ;; --debug) shift debug ;; --user) shift user="$1" shift ;; --password) shift password="$1" shift ;; --token) shift token="$1" shift ;; --scopes) shift scopes="$1" shift ;; login) shift login "$user" "$password" "$token" "$scopes" "$1" return 0 ;; logout) shift logout return 0 ;; web) shift web "$@" return 0 ;; api) shift api "$@" return 0 ;; api_json) shift api_json "$@" return 0 ;; --version) echo "forgejo-curl.sh version $VERSION" return 0 ;; --help|*) usage return 1 ;; esac done } ${MAIN:-main} "${@}"