change password Core
This commit is contained in:
@@ -348,6 +348,69 @@ setup_core() {
|
||||
return $STATUS
|
||||
}
|
||||
|
||||
# Check an app password this computer keeps for the Core
|
||||
app_password_works() {
|
||||
local title="$1" app_password="$2"
|
||||
[ -n "$app_password" ] || return 0
|
||||
case "$(nextcloud_sign_in "$app_password"; echo $?)" in
|
||||
0) echo " $title: working" ;;
|
||||
2) echo -e " \e[31m$title: Nextcloud doesn't accept its app password.\e[0m"
|
||||
echo " It may have been revoked in Nextcloud under Settings > Security."
|
||||
return 1 ;;
|
||||
*) echo " $title: couldn't reach Nextcloud to check" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
# After your Core password changes, e.g. in Superpanel. Email keeps your Core
|
||||
# password, so it gets the new one. Nextcloud, files and backups use app
|
||||
# passwords, which stay the same; Nextcloud turns them off until you sign in
|
||||
# with the new password, and checking the password here does that.
|
||||
change_password() {
|
||||
local tries reached=1
|
||||
load_core "$1"
|
||||
STATUS=0
|
||||
for tries in 1 2 3; do
|
||||
read -rsp "Your new Core password: " PASSWORD
|
||||
echo
|
||||
nextcloud_sign_in
|
||||
case $? in
|
||||
0) break ;;
|
||||
2) echo "Nextcloud says that password is wrong."; PASSWORD="" ;;
|
||||
*) echo "Couldn't reach Nextcloud at $NEXTCLOUD; trying the password for email anyway."
|
||||
reached=0; break ;;
|
||||
esac
|
||||
done
|
||||
[ -n "$PASSWORD" ] || return 1
|
||||
|
||||
echo
|
||||
if email_done; then
|
||||
jq -n --arg password "$PASSWORD" '{"imap-password": $password, "smtp-password": $password}' \
|
||||
| "$GOA_ACCOUNT" update imap_smtp "$CORE_EMAIL" 2> /dev/null
|
||||
case $? in
|
||||
0) echo " Email: has your new password" ;;
|
||||
3) echo -e " \e[31mEmail: saved your new password, but the mail server didn't accept it.\e[0m"
|
||||
STATUS=1 ;;
|
||||
*) echo -e " \e[31mEmail: couldn't save your new password.\e[0m"; STATUS=1 ;;
|
||||
esac
|
||||
fi
|
||||
unset PASSWORD
|
||||
[ $reached = 1 ] || return 1
|
||||
|
||||
if nextcloud_done; then
|
||||
if "$GOA_ACCOUNT" check owncloud "$CORE_USER@nextcloud.$CORE_DOMAIN" 2> /dev/null; then
|
||||
echo " Nextcloud in Online Accounts: working"
|
||||
else
|
||||
echo -e " \e[31mNextcloud in Online Accounts: can't sign in.\e[0m"
|
||||
echo " Remove it in Settings > Online Accounts, then run: configure core"
|
||||
STATUS=1
|
||||
fi
|
||||
fi
|
||||
files_done && { app_password_works "Files at $MOUNT" "$(files_app_password)" || STATUS=1; }
|
||||
app_password_works "Backups" \
|
||||
"$(secret-tool lookup coredesktop backup item nextcloud url "$WEBDAV" 2> /dev/null)" || STATUS=1
|
||||
return $STATUS
|
||||
}
|
||||
|
||||
# Tailscale and Backup point at one Core, which can't be removed while they do
|
||||
tailscale_on() {
|
||||
[ "$(source "$TAILSCALE_CONF" 2> /dev/null && echo "$TAILSCALE_DOMAIN")" = "$1" ] \
|
||||
@@ -473,6 +536,7 @@ run_configure() {
|
||||
echo " Choose a number to finish or change its setup, or:"
|
||||
echo " a) Add a Core"
|
||||
[ ${#DOMAINS[@]} -gt 1 ] && echo " m) Make another Core the main one"
|
||||
echo " p) Your Core password changed"
|
||||
echo " r) Remove a Core"
|
||||
echo " q) Leave it as it is"
|
||||
echo
|
||||
@@ -485,6 +549,9 @@ run_configure() {
|
||||
[ ${#DOMAINS[@]} -gt 1 ] || return 0
|
||||
pick_core "Which should be the main one?" "$MAIN_DOMAIN" || { echo "Choose a number from the list."; return 1; }
|
||||
make_main "$PICKED" && main_changed ;;
|
||||
p|P)
|
||||
pick_core "Which Core's password changed?" || { echo "Choose a number from the list."; return 1; }
|
||||
change_password "$PICKED" ;;
|
||||
r|R)
|
||||
pick_core "Which Core should be removed?" || { echo "Choose a number from the list."; return 1; }
|
||||
remove_core "$PICKED" ;;
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Add or remove an account in GNOME Online Accounts, the way Settings does.
|
||||
"""Add, change or remove an account in GNOME Online Accounts, the way Settings does.
|
||||
|
||||
Settings adds accounts by calling AddAccount on the Online Accounts service,
|
||||
which saves the account and keeps its password in the GNOME keyring. This
|
||||
calls the same method. The account comes on stdin rather than as arguments,
|
||||
so its password doesn't show up in the process list.
|
||||
calls the same method. The service can't change a password, so when you sign
|
||||
in again, Settings saves the new one in the keyring itself and asks the
|
||||
service to check it (EnsureCredentials); update does the same. Passwords come
|
||||
on stdin rather than as arguments, so they don't show up in the process list.
|
||||
|
||||
Usage:
|
||||
goa-account exists <provider> <presentation-identity>
|
||||
goa-account add < account.json
|
||||
goa-account update <provider> <presentation-identity> < credentials.json
|
||||
goa-account check <provider> <presentation-identity>
|
||||
goa-account remove <provider> <presentation-identity>
|
||||
|
||||
account.json:
|
||||
{"provider": "owncloud", "identity": "...", "presentation_identity": "...",
|
||||
"credentials": {"password": "..."}, "details": {"Uri": "...", ...}}
|
||||
|
||||
credentials.json is the account's "credentials", e.g. {"password": "..."}.
|
||||
check signs in with what's saved, and fails if the server says no. update
|
||||
exits with 3 if it saved the password but the server didn't accept it.
|
||||
"""
|
||||
|
||||
import json
|
||||
@@ -22,10 +30,16 @@ import sys
|
||||
import gi
|
||||
|
||||
gi.require_version("Gio", "2.0")
|
||||
from gi.repository import Gio, GLib # noqa: E402
|
||||
gi.require_version("Secret", "1")
|
||||
from gi.repository import Gio, GLib, Secret # noqa: E402
|
||||
|
||||
BUS_NAME = "org.gnome.OnlineAccounts"
|
||||
|
||||
# How Online Accounts keeps passwords in the keyring: the credentials printed
|
||||
# as a GVariant, under "<provider>:gen<N>:<account id>"
|
||||
SCHEMA = Secret.Schema.new("org.gnome.OnlineAccounts", Secret.SchemaFlags.DONT_MATCH_NAME,
|
||||
{"goa-identity": Secret.SchemaAttributeType.STRING})
|
||||
|
||||
|
||||
def call(bus, path, interface, method, parameters, reply_type):
|
||||
return bus.call_sync(BUS_NAME, path, interface, method, parameters,
|
||||
@@ -43,12 +57,16 @@ def accounts(bus):
|
||||
yield path, account
|
||||
|
||||
|
||||
def find(bus, provider, presentation_identity):
|
||||
return [path for path, account in accounts(bus)
|
||||
def find_accounts(bus, provider, presentation_identity):
|
||||
return [(path, account) for path, account in accounts(bus)
|
||||
if account.get("ProviderType") == provider
|
||||
and account.get("PresentationIdentity") == presentation_identity]
|
||||
|
||||
|
||||
def find(bus, provider, presentation_identity):
|
||||
return [path for path, _ in find_accounts(bus, provider, presentation_identity)]
|
||||
|
||||
|
||||
def exists(bus, provider, presentation_identity):
|
||||
return bool(find(bus, provider, presentation_identity))
|
||||
|
||||
@@ -63,6 +81,52 @@ def add(bus, account):
|
||||
"org.gnome.OnlineAccounts.Manager", "AddAccount", parameters, "(o)")
|
||||
|
||||
|
||||
def ensure_credentials(bus, path):
|
||||
call(bus, path, "org.gnome.OnlineAccounts.Account", "EnsureCredentials", None, "(i)")
|
||||
|
||||
|
||||
# N in the keyring's name comes from the provider, so this saves over the
|
||||
# account's own item rather than making one
|
||||
def store_credentials(provider, account_id, credentials):
|
||||
items = Secret.password_search_sync(
|
||||
None, {"xdg:schema": "org.gnome.OnlineAccounts"},
|
||||
Secret.SearchFlags.ALL | Secret.SearchFlags.UNLOCK, None)
|
||||
identities = [item.get_attributes().get("goa-identity", "") for item in items]
|
||||
identities = [identity for identity in identities
|
||||
if identity.startswith(f"{provider}:gen") and identity.endswith(f":{account_id}")]
|
||||
if not identities:
|
||||
raise LookupError(f"there's no saved password for {account_id} in the keyring")
|
||||
secret = GLib.Variant("a{sv}", {key: GLib.Variant("s", value)
|
||||
for key, value in credentials.items()}).print_(True)
|
||||
for identity in identities:
|
||||
Secret.password_store_sync(SCHEMA, {"goa-identity": identity}, Secret.COLLECTION_DEFAULT,
|
||||
f"GOA {provider} credentials for identity {account_id}",
|
||||
secret, None)
|
||||
|
||||
|
||||
def update(bus, provider, presentation_identity, credentials):
|
||||
found = find_accounts(bus, provider, presentation_identity)
|
||||
if not found:
|
||||
raise LookupError(f"there's no {provider} account for {presentation_identity}")
|
||||
for path, account in found:
|
||||
store_credentials(provider, account["Id"], credentials)
|
||||
try:
|
||||
for path, _ in found:
|
||||
ensure_credentials(bus, path)
|
||||
except GLib.Error as error:
|
||||
print(f"GNOME Online Accounts: {error.message}", file=sys.stderr)
|
||||
return 3
|
||||
return 0
|
||||
|
||||
|
||||
def check(bus, provider, presentation_identity):
|
||||
found = find(bus, provider, presentation_identity)
|
||||
if not found:
|
||||
raise LookupError(f"there's no {provider} account for {presentation_identity}")
|
||||
for path in found:
|
||||
ensure_credentials(bus, path)
|
||||
|
||||
|
||||
def remove(bus, provider, presentation_identity):
|
||||
for path in find(bus, provider, presentation_identity):
|
||||
call(bus, path, "org.gnome.OnlineAccounts.Account", "Remove", None, "()")
|
||||
@@ -78,6 +142,11 @@ def main():
|
||||
return 0
|
||||
add(bus, account)
|
||||
return 0
|
||||
if len(sys.argv) == 4 and sys.argv[1] == "update":
|
||||
return update(bus, sys.argv[2], sys.argv[3], json.load(sys.stdin))
|
||||
if len(sys.argv) == 4 and sys.argv[1] == "check":
|
||||
check(bus, sys.argv[2], sys.argv[3])
|
||||
return 0
|
||||
if len(sys.argv) == 4 and sys.argv[1] == "remove":
|
||||
remove(bus, sys.argv[2], sys.argv[3])
|
||||
return 0
|
||||
@@ -91,3 +160,6 @@ if __name__ == "__main__":
|
||||
except GLib.Error as error:
|
||||
print(f"GNOME Online Accounts: {error.message}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except LookupError as error:
|
||||
print(f"GNOME Online Accounts: {error}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
@@ -31,3 +31,18 @@ nextcloud_revoke_app_password() {
|
||||
auth_header "$CORE_USER" "$1" | curl -sf -o /dev/null -X DELETE -H @- \
|
||||
-H 'OCS-APIRequest: true' "$NEXTCLOUD/ocs/v2.php/core/apppassword"
|
||||
}
|
||||
|
||||
# Sign in to Nextcloud as CORE_USER with a password ($1), or PASSWORD if not
|
||||
# given. Signing in with your Core password (not an app password) also turns
|
||||
# back on the app passwords Nextcloud turned off when the Core password
|
||||
# changed. Uses NEXTCLOUD. Returns 2 if Nextcloud says the password is wrong.
|
||||
nextcloud_sign_in() {
|
||||
local code
|
||||
code="$(auth_header "$CORE_USER" "${1:-$PASSWORD}" | curl -s -o /dev/null -w '%{http_code}' -H @- \
|
||||
-H 'OCS-APIRequest: true' "$NEXTCLOUD/ocs/v2.php/cloud/user?format=json")"
|
||||
case "$code" in
|
||||
200) return 0 ;;
|
||||
401) return 2 ;;
|
||||
*) return 1 ;;
|
||||
esac
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user