#!/bin/sh # Gestion des ayants droit de la bibliotheque. # # users.json est la SEULE source de verite ; la map nginx en est deduite et # se reecrit entierement a chaque geste. Retirer quelqu un de ce fichier et # recharger, c est tuer tous ses liens dans la seconde : c est la revocation. # L expiration inscrite dans les URL n est qu un filet. # # Configuration : /etc/li/li.env (ou le fichier nomme par LI_CONF), partage # avec li-gen. Voir li.env.example. set -eu LI_CONF=${LI_CONF:-/etc/li/li.env} # shellcheck disable=SC1090 [ -f "$LI_CONF" ] && . "$LI_CONF" : "${LI_BASE_URL:?LI_BASE_URL n est pas defini (voir li.env.example)}" LI_DATA=${LI_DATA:-/srv/appdata/li} LI_EXPIRES=${LI_EXPIRES:-2145916800} # 2038 — doit valoir celui de li-gen LI_NGINX=${LI_NGINX:-docker exec li nginx} # li-gen lit les memes variables : on les lui transmet telles que chargees. export LI_CONF LI_BASE_URL LI_DATA LI_EXPIRES BASE=${LI_BASE_URL%/} AYANTS=$LI_DATA/users.json MAP=$LI_DATA/conf/users.conf LI_GEN=${LI_GEN:-$(dirname "$0")/li-gen} usage() { echo "usage: li-user add|del|lien|list [nom]" >&2 exit 2 } verifie_nom() { # Le nom voyage dans l URL et devient un nom de fichier : on le tient # court et sans surprise plutot que d avoir a l echapper partout. case "$1" in *[!A-Za-z0-9_-]*|"") echo "Nom invalide : lettres, chiffres, - et _ seulement." >&2; exit 1 ;; esac } rendre_map() { mkdir -p "$(dirname "$MAP")" umask 077 { echo "# Engendre par li-user — ne pas editer a la main." echo "# Un identifiant absent d ici obtient un secret VIDE, et le vhost" echo "# refuse avant meme de calculer la moindre signature." echo 'map $arg_u $li_secret {' echo ' default "";' jq -r 'to_entries | sort_by(.key)[] | " \"\(.key)\" \"\(.value)\";"' "$AYANTS" echo '}' } > "$MAP.tmp" mv "$MAP.tmp" "$MAP" chmod 644 "$MAP" # lu par le conteneur, qui le monte en lecture seule # shellcheck disable=SC2086 # LI_NGINX est une commande, mots compris $LI_NGINX -t >/dev/null 2>&1 || { echo "La configuration nginx ne passe plus le controle — rien recharge." >&2 $LI_NGINX -t exit 1 } # shellcheck disable=SC2086 $LI_NGINX -s reload } signe_uri() { # $1 = uri, $2 = secret printf '%s' "$LI_EXPIRES$1$2" \ | openssl md5 -binary | openssl base64 | tr '+/' '-_' | tr -d '=' } lien() { secret=$(jq -er --arg n "$1" '.[$n]' "$AYANTS") || { echo "$1 : inconnu." >&2; exit 1 } # Les deux documents portent les MEMES liens de telechargement : la # signature ne depend que du chemin et du secret, jamais du document # qui la transporte. for ext in html xspf; do uri="/playlists/$1.$ext" printf ' %-5s %s\n' "$ext" "$BASE$uri?u=$1&e=$LI_EXPIRES&s=$(signe_uri "$uri" "$secret")" done } [ -f "$AYANTS" ] || { mkdir -p "$LI_DATA"; umask 077; echo '{}' > "$AYANTS"; } case "${1:-}" in add) [ $# -eq 2 ] || usage verifie_nom "$2" if jq -e --arg n "$2" 'has($n)' "$AYANTS" >/dev/null; then echo "$2 existe deja — « li-user lien $2 » pour retrouver son URL." >&2 exit 1 fi secret=$(LC_ALL=C tr -dc 'A-Za-z0-9' < /dev/urandom | head -c 24) tmp=$(mktemp) jq --arg n "$2" --arg s "$secret" '.[$n] = $s' "$AYANTS" > "$tmp" mv "$tmp" "$AYANTS" chmod 600 "$AYANTS" rendre_map "$LI_GEN" echo echo "Liens a transmettre a $2 :" lien "$2" ;; del) [ $# -eq 2 ] || usage jq -e --arg n "$2" 'has($n)' "$AYANTS" >/dev/null || { echo "$2 : inconnu." >&2; exit 1 } tmp=$(mktemp) jq --arg n "$2" 'del(.[$n])' "$AYANTS" > "$tmp" mv "$tmp" "$AYANTS" chmod 600 "$AYANTS" # Les documents partent AVANT le rechargement : un worker en cours de # vidange a encore l ancienne map, et servirait un 404 la ou la # reponse juste est 403. rm -f "$LI_DATA/playlists/$2.xspf" "$LI_DATA/playlists/$2.html" rendre_map echo "$2 revoque — tous ses liens sont morts." ;; lien) [ $# -eq 2 ] || usage lien "$2" ;; list) jq -r 'to_entries | sort_by(.key)[] | .key' "$AYANTS" ;; *) usage ;; esac