hs-admin/hsadmin/cmd.py

146 lines
4.5 KiB
Python

import os
from pkg_resources import resource_filename, cleanup_resources, resource_string
import atexit
from pathlib import Path
atexit.register(cleanup_resources, force=True)
os.environ["KRB5_CONFIG"] = str(
Path(resource_filename(__name__, "krb5.conf")).resolve()
)
import kadmin
import getpass
import argparse
import secrets
import string
import shutil
import logging
from ldap3 import Server, Connection, LEVEL
from ldap3.utils.conv import escape_filter_chars
from ldap3.utils.dn import escape_rdn
import subprocess
import smtplib
from jinja2 import Template
from email.message import EmailMessage
FROM_LDAP = object()
parser = argparse.ArgumentParser()
parser.add_argument("--admin", default=getpass.getuser())
parser.add_argument("--verbose", action="store_true")
subparsers = parser.add_subparsers(dest="cmd", help="command")
reset_password = subparsers.add_parser(
"reset_password",
help="change user password to newly generated one and send it to his email address from LDAP",
)
reset_password.add_argument("user")
reset_password.add_argument(
"--show-password", action="store_true", help="print generated password"
)
reset_password.add_argument("email_address", default=FROM_LDAP, nargs="?")
APG_CMD = shutil.which("apg")
def generage_password(length=15):
if APG_CMD is None:
logging.warning("apg command not found. Using built in password generator")
pool = string.ascii_lowercase + string.ascii_uppercase + string.digits
password = "".join([secrets.choice(pool) for _ in range(length)])
else:
password = (
subprocess.run(
[APG_CMD, "-m", str(length), "-n", "1", "-M", "NCL"],
check=True,
capture_output=True,
)
.stdout.decode()
.strip()
)
if len(password) != length:
raise Exception("Password generation failed")
return password
def main():
args = parser.parse_args()
logging.basicConfig(level=logging.DEBUG if args.verbose else logging.INFO)
if args.cmd == "reset_password":
admin_pass = getpass.getpass(f"{args.admin}@HACKERSPACE.PL password: ")
logging.debug("initializing kadmin")
k = kadmin.init_with_password(f"{args.admin}/admin", admin_pass)
logging.debug("kadmin initialized")
user = args.user
if user is None:
user = input("User: ")
p = k.get_principal(user)
password = generage_password()
if args.email_address is not FROM_LDAP:
address = args.email_address
else:
address = get_email_address(args.admin, admin_pass, user)
if args.show_password:
print(f'password: "{password}"')
i = input(
f"Type yes to reset {user}'s password and send email to {address}\n"
).strip()
if i != "yes":
print("Aborted")
return
p.change_password(password)
print("password changed")
send_mail(args.admin, admin_pass, password, user, address)
print("email sent")
else:
parser.print_help()
def get_email_address(admin, admin_pass, uid):
logging.debug("fetching email address from LDAP")
s = Server("ldap.hackerspace.pl", use_ssl=True)
with Connection(
s,
user=f"uid={escape_rdn(admin)},ou=People,dc=hackerspace,dc=pl",
password=admin_pass,
raise_exceptions=True,
) as c:
logging.debug("connected to LDAP server")
c.search(
search_base="ou=People,dc=hackerspace,dc=pl",
search_filter=f"(uid={escape_filter_chars(uid)})",
search_scope=LEVEL,
attributes=["mailRoutingAddress"],
)
if not c.entries:
raise Exception("empty response")
if len(c.entries) > 1:
raise Exception("too many responses")
address = c.entries[0]["mailRoutingAddress"]
logging.debug(f"got mail address from LDAP: {address}")
return address
def send_mail(admin, admin_password, password, user, address):
mail_template = Template(
resource_string(__name__, "password_reset.jinja2").decode()
)
msg = EmailMessage()
config = {"password": password, "user": user, "admin": admin}
msg.set_content(mail_template.render(config))
msg["Subject"] = f"Password reset for {user}@hackerspace.pl"
msg["From"] = f"{admin}@hackerspace.pl"
msg["To"] = address
with smtplib.SMTP_SSL("mail.hackerspace.pl") as s:
s.login(admin, admin_password)
s.send_message(msg)