From 274d1f02dfa0ac7a28f5318fe8c4eb6a28702bbe Mon Sep 17 00:00:00 2001 From: "Danilo M." Date: Fri, 16 Oct 2026 09:54:42 +0200 Subject: feat(user): userctl reads and writes the user's details One writer for the stores other programs read: AccountsService (and with it the passwd GECOS LibreOffice prefills from), KDE's emaildefaults and the global git config, where the surname goes in as an initial. busctl is asked for JSON because its plain output escapes non-ASCII as octal. Co-Authored-By: Claude Sonnet 5.5 --- desktop/modules/user/test-userctl.sh | 143 +++++++++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+) create mode 100755 desktop/modules/user/test-userctl.sh (limited to 'desktop/modules/user/test-userctl.sh') diff --git a/desktop/modules/user/test-userctl.sh b/desktop/modules/user/test-userctl.sh new file mode 100755 index 0000000..cc90550 --- /dev/null +++ b/desktop/modules/user/test-userctl.sh @@ -0,0 +1,143 @@ +#!/bin/bash +# +# Copyright (C) 2026 Danilo M. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# The one runnable check for userctl. busctl, kwriteconfig6, git and gpg are +# stubs on PATH that log their arguments, so nothing here reaches the real +# AccountsService, emaildefaults or git config. jq is the real one. +# +# Usage: ./test-userctl.sh (exit 0 = all passed) + +set -u + +here="$(cd "$(dirname "$0")" && pwd)" +ctl="$here/userctl" +tmp="$(mktemp -d)" +trap 'rm -rf "$tmp"' EXIT + +mkdir "$tmp/bin" +cat > "$tmp/bin/stub" <<'EOF' +#!/bin/bash +n="${0##*/}" +{ printf '%s' "$n"; printf ' [%s]' "$@"; echo; } >> "$LOG" +[[ "${FAIL:-}" == "$n" ]] && { echo boom >&2; exit 1; } +case "$n" in + busctl) [[ " $* " == *" get-property "* ]] && printf '{"type":"s","data":"%s"}\n' "${@: -1}" ;; + git) [[ " $* " == *" --get "* ]] && { [[ -n "${GIT_VALUE:-}" ]] && echo "$GIT_VALUE" || exit 1; } ;; + gpg) cat "$GPG_FIXTURE" ;; +esac +exit 0 +EOF +chmod +x "$tmp/bin/stub" +for n in busctl kwriteconfig6 git gpg; do ln -s stub "$tmp/bin/$n"; done +export PATH="$tmp/bin:$PATH" HOME="$tmp" LOG="$tmp/log" + +obj="/org/freedesktop/Accounts/User$(id -u)" +acc="busctl [--system] [call] [org.freedesktop.Accounts] [$obj] [org.freedesktop.Accounts.User]" + +pass=0 +fail=0 +check() { + local label="$1" want="$2" got="$3" + if [[ "$want" == "$got" ]]; then + printf 'ok %s\n' "$label"; pass=$((pass + 1)) + else + printf 'FAIL %s:\n want %q\n got %q\n' "$label" "$want" "$got"; fail=$((fail + 1)) + fi +} +reset() { : > "$LOG"; unset FAIL GIT_VALUE; } + +# get: one line per field, AccountsService through --json, the key from git. +reset +export GIT_VALUE=ABCDEF +check "get" "name=RealName +email=Email +avatar=IconFile +language=Language +location=Location +signingkey=ABCDEF" "$("$ctl" get)" + +# keys: the primary fingerprint and first uid of each secret key; subkey +# fingerprints and later uids are skipped. +reset +cat > "$tmp/gpg.txt" <<'EOF' +sec:u:255:22:AAAA1111:1700000000:::u:::scESC:::+:::23::0: +fpr:::::::::1111222233334444555566667777AAAA1111: +grp:::::::::0000: +uid:u::::1700000000::HASH::Jane Doèr ::::::::::0: +uid:u::::1700000000::HASH::Jane D. ::::::::::0: +ssb:u:255:18:SUB1:1700000000::::::e:::+:::23: +fpr:::::::::9999888877776666555544443333SUB1: +sec:u:255:22:BBBB2222:1700000000:::u:::scESC:::#:::23::0: +fpr:::::::::AAAABBBBCCCCDDDDEEEEFFFF0000BBBB2222: +uid:u::::1700000000::HASH::Work Key ::::::::::0: +EOF +export GPG_FIXTURE="$tmp/gpg.txt" +check "keys" "1111222233334444555566667777AAAA1111 Jane Doèr +AAAABBBBCCCCDDDDEEEEFFFF0000BBBB2222 Work Key " "$("$ctl" keys)" + +# set name: AccountsService, emaildefaults, git with the surname shortened. +reset +out="$("$ctl" set name "Jane Doèr")" +check "set name prints nothing" "" "$out" +check "set name writes" "$acc [SetRealName] [s] [Jane Doèr] +kwriteconfig6 [--file] [emaildefaults] [--group] [PROFILE_Default] [--key] [FullName] [Jane Doèr] +git [config] [--global] [user.name] [Jane D.]" "$(cat "$LOG")" + +gitname() { reset; "$ctl" set name "$1" >/dev/null; grep '^git' "$LOG"; } +check "three words" "git [config] [--global] [user.name] [Anna Maria R.]" "$(gitname "Anna Maria Rossi")" +check "one word" "git [config] [--global] [user.name] [Cher]" "$(gitname "Cher")" +check "accented initial" "git [config] [--global] [user.name] [Ève À.]" "$(gitname "Ève Àlba")" +check "extra spaces" "git [config] [--global] [user.name] [Jane D.]" "$(gitname " Jane Doèr ")" + +# set email: the same three stores. +reset +"$ctl" set email jane@example.org >/dev/null +check "set email writes" "$acc [SetEmail] [s] [jane@example.org] +kwriteconfig6 [--file] [emaildefaults] [--group] [PROFILE_Default] [--key] [EmailAddress] [jane@example.org] +git [config] [--global] [user.email] [jane@example.org]" "$(cat "$LOG")" + +# AccountsService only. +for pair in avatar:SetIconFile language:SetLanguage location:SetLocation; do + reset + "$ctl" set "${pair%%:*}" v >/dev/null + check "set ${pair%%:*} writes" "$acc [${pair#*:}] [s] [v]" "$(cat "$LOG")" +done + +# signingkey: a fingerprint is written; empty unsets, but only if one is set. +reset +"$ctl" set signingkey ABCD >/dev/null +check "set signingkey" "git [config] [--global] [user.signingkey] [ABCD]" "$(cat "$LOG")" +reset +export GIT_VALUE=OLD +"$ctl" set signingkey "" >/dev/null +check "unset signingkey" "git [config] [--global] [--get] [user.signingkey] +git [config] [--global] [--unset] [user.signingkey]" "$(cat "$LOG")" +reset +"$ctl" set signingkey "" >/dev/null +check "unset when none is set" "git [config] [--global] [--get] [user.signingkey]" "$(cat "$LOG")" + +# A failing write stops there, says why on stdout, and exits 1. +reset +export FAIL=busctl +out="$("$ctl" set name "Jane Doèr")"; rc=$? +check "failure exit" "1" "$rc" +check "failure message" "busctl: boom" "$out" +check "failure stops" "1" "$(wc -l < "$LOG" | tr -d ' ')" + +# An unknown field is refused. +reset +"$ctl" set shoesize 42 >/dev/null 2>&1; rc=$? +check "unknown field" "2" "$rc" + +printf '\n%d passed, %d failed\n' "$pass" "$fail" +[[ $fail -eq 0 ]] -- cgit v1.2.3