setup-forgejo/forgejo-curl.sh
cascading-pr f9010a64e9
All checks were successful
/ integration-nested (pull_request) Successful in 4m48s
/ integration (map[image:codeberg.org/forgejo-experimental/forgejo version:1.21.0-5-rc2]) (pull_request) Successful in 2m17s
/ integration (map[image:codeberg.org/forgejo/forgejo version:1.20]) (pull_request) Successful in 2m8s
/ cascade (pull_request) Successful in 11s
cascading-pr update
2023-11-03 23:12:32 +00:00

339 lines
8.1 KiB
Bash
Executable file

#!/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 <<EOF
forgejo-curl.sh - thin curl wrapper that helps with Forgejo authentication
COMMON OPTIONS
--verbose display curl commands
--debug equivalent to set -x
LOGIN AND TOKEN
The API endpoints that require authentication will be given the
token provided with the --token argument. If not provided, it will
be generated (and named $TOKEN_NAME), using the --user and
--password credentials.
The web endpoints that require authentication will be given a cookie
and CSRF token created using the the --user and --password credentials
On a successful login the credentials are stored in the $DOT
directory to be used by the web, api, api_json commands. The logout
command removes the $DOT directory.
If the argument of --token starts with @, it is used as a filename
from which the token will be read.
forgejo-curl.sh [--verbose] [--debug]
[--user <user>] [--password <password>]
[--token {<token>|<@tokenfilename>}]
[--scopes <scopes>] login URL
forgejo-curl.sh logout
OPTIONS
--user <user> username
--password <password> password of <user>
--scopes <scopes> scopes of the token to be created (default ["all"])
--token {<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} "${@}"