initial commit
This commit is contained in:
commit
756f6cbe66
8 changed files with 234 additions and 0 deletions
2
README.rst
Normal file
2
README.rst
Normal file
|
@ -0,0 +1,2 @@
|
|||
Command line tool for managing user accounts at Warsaw Hackerspace. For now only
|
||||
password reset is implemented.
|
3
build.nix
Normal file
3
build.nix
Normal file
|
@ -0,0 +1,3 @@
|
|||
{ pkgs ? import <nixpkgs> {} }:
|
||||
|
||||
pkgs.python3Packages.callPackage (import ./default.nix) {}
|
16
default.nix
Normal file
16
default.nix
Normal file
|
@ -0,0 +1,16 @@
|
|||
{ buildPythonPackage, setuptools, jinja2, ldap3, apg, callPackage }:
|
||||
|
||||
let
|
||||
python-kadmin = callPackage (import ./python-kadmin.nix) {};
|
||||
in buildPythonPackage rec {
|
||||
pname = "hs-admin-${version}";
|
||||
version = "0.0.1";
|
||||
propagatedBuildInputs = [
|
||||
python-kadmin
|
||||
setuptools
|
||||
jinja2
|
||||
ldap3
|
||||
apg
|
||||
];
|
||||
src = ./.;
|
||||
}
|
145
hsadmin/cmd.py
Normal file
145
hsadmin/cmd.py
Normal file
|
@ -0,0 +1,145 @@
|
|||
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)
|
13
hsadmin/krb5.conf
Normal file
13
hsadmin/krb5.conf
Normal file
|
@ -0,0 +1,13 @@
|
|||
[libdefaults]
|
||||
default_realm = HACKERSPACE.PL
|
||||
[realms]
|
||||
HACKERSPACE.PL = {
|
||||
admin_server = hackerspace.pl
|
||||
kdc = kerberos.hackerspace.pl
|
||||
default_domain = hackerspace.pl
|
||||
sasl-realm = HACKERSPACE.PL
|
||||
}
|
||||
|
||||
[domain_realm]
|
||||
.hackerspace.pl = HACKERSPACE.PL
|
||||
hackerspace.pl = HACKERSPACE.PL
|
8
hsadmin/password_reset.jinja2
Normal file
8
hsadmin/password_reset.jinja2
Normal file
|
@ -0,0 +1,8 @@
|
|||
Hi,
|
||||
|
||||
Password for your Warsaw Hackerspace account has been resetted.
|
||||
|
||||
user: {{ user }}
|
||||
password: {{ password }}
|
||||
|
||||
You can change it here: https://profile.hackerspace.pl
|
24
python-kadmin.nix
Normal file
24
python-kadmin.nix
Normal file
|
@ -0,0 +1,24 @@
|
|||
{ buildPythonPackage, bison, krb5, fetchFromGitHub }:
|
||||
|
||||
buildPythonPackage rec {
|
||||
pname = "python-kadmin-${version}";
|
||||
version = "0.0.2";
|
||||
nativeBuildInputs = [ bison ];
|
||||
buildInputs = [ krb5 ];
|
||||
src = fetchFromGitHub {
|
||||
owner = "nightfly19";
|
||||
repo = "python-kadmin";
|
||||
#rev = "c1acec9d197b79e3f51928aad6df0f99e86283c2";
|
||||
#sha256 = "0jb0k998624scy204im068kwhnwa2l6vag69qi3hnmlpr1q3wh0z";
|
||||
rev = "31d25f734b926b71e15d4c2f3a2e68decf8a465b";
|
||||
sha256 = "1vnmrd9sz08sr3nsg1n1rgwrqai802hba8gqybgabblbza4kg4x1";
|
||||
};
|
||||
preConfigure = ''
|
||||
substituteInPlace setup.py --replace '["/usr/include/", "/usr/include/et/"]' '["${krb5.dev}/include"]'
|
||||
'';
|
||||
postInstall = ''
|
||||
ln -s $src $out
|
||||
'';
|
||||
#doCheck=false;
|
||||
doCheck=true;
|
||||
}
|
23
setup.py
Normal file
23
setup.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
name="hsadmin",
|
||||
version="0.1",
|
||||
description="Warsaw Hackerspace account managment cli",
|
||||
author="vuko",
|
||||
author_email="vuko@hackerspace.pl",
|
||||
classifiers=[
|
||||
"Development Status :: 3 - Alpha",
|
||||
"License :: OSI Approved :: zlib/libpng License",
|
||||
"Programming Language :: Python :: 3.8",
|
||||
],
|
||||
packages=["hsadmin"],
|
||||
python_requires=">=3.8,",
|
||||
install_requires=["kadmin", "setuptools", "jinja2", "ldap3"],
|
||||
package_data={"hsadmin": ["krb5.conf", "password_reset.jinja2"]},
|
||||
entry_points={
|
||||
"console_scripts": [
|
||||
"hs-admin=hsadmin.cmd:main",
|
||||
]
|
||||
},
|
||||
)
|
Loading…
Reference in a new issue