#!/usr/bin/env python
#
# kpasswd.py
#
# Set password for kofa instances.
#
# (c) 2024 WAeUP Germany
# Licensed under AGPL 3.0 or newer
#
# This script sets the username and password for a principal of a `kofa`
# instance.
#
#    $ python kpasswd.py -l foo -c parts/etc/site.zcml not-secret
#
# sets the credentials for the superuser principal (`zope.manager`) to `foo`
# (username) and `not-scret` (password). The given site.zcml will be updated
# accordingly.
#
# Not giving a site.zcml path will print the hashed form of the given password:
#
#    $ python kpasswd.py also-not-secret
#    {SSHA}EkqqJb8opEe1QxhETB57Ps2O62HS63Vu
#
from __future__ import print_function
import argparse
import os
import sys
import xml.etree.ElementTree as ET
from zope.password.password import managers as ZOPE_PW_MANAGERS


def parse_args(argv):
    p = argparse.ArgumentParser(description=(
        "Set kofa password. If path to local `site.zcml` is given, also "
        "updates this file. If not, the hashed password is displayed and "
        "can be set manually in `site.zcml`"))
    p.add_argument("password", nargs=1, help="Password to set")
    p.add_argument(
        "-c",
        "--config",
        help=(
            "path to the site.zcml configuration file (optional). If "
            "given, the respective file will be modified. Otherwise "
            "the hashed password is displayed on stdout."
        ),
    )
    p.add_argument(
        "--id",
        help=(
            "Principal id found in site.zcml. `zope.manager? by default. "
            "Only considered if a config file was given."
        ),
        default="zope.manager",
    )
    p.add_argument(
        "-l",
        "--login",
        help=(
            "Principal login name. `grok` by default. Only considered if a "
            "config file was given."
        ),
        default="grok",
    )
    options = p.parse_args(argv[1:])
    return options


def main(argv=None):
    if argv is None:
        argv = sys.argv
    try:
        options = parse_args(argv)
        app = Application(options)
        app.run()
    except SystemExit as e:
        if e.code:
            return 2
    return 0


class Application(object):
    def __init__(self, options):
        self.options = options

    def run(self):
        hashed = self.get_hashed_password(self.options.password[0]).decode("utf-8")
        if self.options.config is None:
            print(hashed)
            return
        path = os.path.abspath(self.options.config)
        xmltree = ET.parse(path).getroot()
        doc = open(path).read()
        for entry in xmltree.iter("{http://namespaces.zope.org/zope}principal"):
            if entry.get("id") != self.options.id:
                continue
            # Replace hashed password with new value
            doc = doc.replace(entry.get("password"), hashed)
            # Replace old login (username) with new one
            doc = doc.replace(
                'login="%s"' % entry.get("login"), 'login="%s"' % self.options.login
            )
        # Create a backup of the file we are going to modify
        os.rename(path, path + ".bak")
        with open(path, "w") as fd:
            fd.write(doc)
        print("Updated: %s" % path)

    def get_hashed_password(self, password, salt=None):
        manager = dict(ZOPE_PW_MANAGERS)["SSHA"]
        return manager.encodePassword(password, salt)
