oof, get user creation working
parent
2b46bef460
commit
55562572ae
|
@ -0,0 +1,4 @@
|
||||||
|
foobar
|
||||||
|
some stuff
|
||||||
|
yeahh
|
||||||
|
hm
|
|
@ -0,0 +1,14 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
import sys
|
||||||
|
|
||||||
|
KEYFILE_PATH = '/home/{}/.ssh/authorized_keys2'
|
||||||
|
|
||||||
|
|
||||||
|
def main(argv):
|
||||||
|
username = argv[1]
|
||||||
|
with open(KEYFILE_PATH.format(username), 'w') as f:
|
||||||
|
f.write(sys.stdin.read())
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
exit(main(sys.argv))
|
|
@ -1,7 +1,10 @@
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
|
from subprocess import run, CalledProcessError, PIPE
|
||||||
|
from tempfile import TemporaryFile
|
||||||
|
|
||||||
from django.db.models import Model
|
from django.db.models import Model
|
||||||
from django.db.models.signals import pre_save
|
from django.db.models.signals import pre_save, post_save, post_delete
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from django.db.models import TextField, BooleanField, CharField, ForeignKey
|
from django.db.models import TextField, BooleanField, CharField, ForeignKey
|
||||||
|
@ -15,6 +18,23 @@ SSH_TYPE_CHOICES = (
|
||||||
('ssh-dss', 'ssh-dss',),
|
('ssh-dss', 'ssh-dss',),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
KEYFILE_HEADER = """Hi! This file is automatically managed by tilde.town. You
|
||||||
|
probably shouldn't change it. If you want to add more public keys that's
|
||||||
|
totally fine: you can put them in ~/.ssh/authorized_keys"""
|
||||||
|
TMP_PATH = '/tmp/ttadmin'
|
||||||
|
|
||||||
|
ENSURE_PRESENT = 'present'
|
||||||
|
ENSURE_ABSENT = 'absent'
|
||||||
|
|
||||||
|
def user_in_passwd(username):
|
||||||
|
"""Given a username, returns either the user's line in passwd or None.
|
||||||
|
Opens and reads passwd every time. Memoize or something if this becomes an
|
||||||
|
issue."""
|
||||||
|
with open('/etc/passwd') as passwd:
|
||||||
|
for line in passwd:
|
||||||
|
if username == line.split(':')[0]:
|
||||||
|
return line.rstrip()
|
||||||
|
|
||||||
|
|
||||||
class Townie(User):
|
class Townie(User):
|
||||||
"""Both an almost normal Django User as well as an abstraction over a
|
"""Both an almost normal Django User as well as an abstraction over a
|
||||||
|
@ -44,6 +64,58 @@ class Townie(User):
|
||||||
self.username,
|
self.username,
|
||||||
self.email))
|
self.email))
|
||||||
|
|
||||||
|
# managing concrete system state
|
||||||
|
|
||||||
|
def reconcile(self, ensure):
|
||||||
|
assert(self.reviewed)
|
||||||
|
dot_ssh_path = '/home/{}/.ssh'.format(self.username)
|
||||||
|
if ensure == ENSURE_ABSENT:
|
||||||
|
# TODO delete
|
||||||
|
# This is manual for now because it very rarely comes up and I want
|
||||||
|
# the present case to work.
|
||||||
|
pass
|
||||||
|
|
||||||
|
if ensure == ENSURE_PRESENT:
|
||||||
|
# TODO handle rename case either with update fields or a rename action
|
||||||
|
# Add the user
|
||||||
|
result = run(['sudo',
|
||||||
|
'adduser',
|
||||||
|
'--quiet',
|
||||||
|
'--shell={}'.format(self.shell),
|
||||||
|
'--gecos="{}"'.format(self.displayname),
|
||||||
|
'--disabled-password',
|
||||||
|
self.username,],
|
||||||
|
check=True,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create .ssh
|
||||||
|
run(['sudo', '--user={}'.format(self.username), 'mkdir', dot_ssh_path])
|
||||||
|
|
||||||
|
# Write out authorized_keys file
|
||||||
|
with TemporaryFile(dir="/tmp") as fp:
|
||||||
|
fp.write(self.generate_authorized_keys().encode('utf-8'))
|
||||||
|
fp.seek(0)
|
||||||
|
run(['sudo',
|
||||||
|
'--user={}'.format(self.username),
|
||||||
|
'/opt/bin/create_keyfile.py',
|
||||||
|
self.username],
|
||||||
|
stdin=fp
|
||||||
|
)
|
||||||
|
|
||||||
|
def generate_authorized_keys(self):
|
||||||
|
"""returns a string suitable for writing out to an authorized_keys
|
||||||
|
file"""
|
||||||
|
content = KEYFILE_HEADER
|
||||||
|
pubkeys = Pubkey.objects.filter(townie=self)
|
||||||
|
for key in pubkeys:
|
||||||
|
if key.startswith('ssh-'):
|
||||||
|
content += '\n {}'.format(key.key)
|
||||||
|
else:
|
||||||
|
content += '\n{} {}'.format(key.key_type, key.key)
|
||||||
|
|
||||||
|
return content
|
||||||
|
|
||||||
|
|
||||||
class Pubkey(Model):
|
class Pubkey(Model):
|
||||||
key_type = CharField(max_length=50,
|
key_type = CharField(max_length=50,
|
||||||
blank=False,
|
blank=False,
|
||||||
|
@ -62,3 +134,62 @@ def on_townie_pre_save(sender, instance, **kwargs):
|
||||||
|
|
||||||
if not existing[0].reviewed and instance.reviewed == True:
|
if not existing[0].reviewed and instance.reviewed == True:
|
||||||
instance.send_welcome_email()
|
instance.send_welcome_email()
|
||||||
|
|
||||||
|
@receiver(post_save, sender=Townie)
|
||||||
|
def post_save_reconcile(sender, instance, **kwargs):
|
||||||
|
if not instance.reviewed:
|
||||||
|
return
|
||||||
|
instance.reconcile(ENSURE_PRESENT)
|
||||||
|
|
||||||
|
@receiver(post_delete, sender=Townie)
|
||||||
|
def post_delete_reconcile(sender, instance, **kwargs):
|
||||||
|
if not instance.reviewed:
|
||||||
|
# TODO should i actually do this check?
|
||||||
|
# I might want to make it such that users can never become un-reviewed.
|
||||||
|
return
|
||||||
|
instance.reconcile(ENSURE_ABSENT)
|
||||||
|
|
||||||
|
# what the puppet module does:
|
||||||
|
# * creates user account
|
||||||
|
# * creates home directory
|
||||||
|
# * creates authorized_keys2
|
||||||
|
# * adds user to group 'tilde' (why?)
|
||||||
|
# * sets shell
|
||||||
|
# * creates .ssh directory
|
||||||
|
# * creates .irssi directory
|
||||||
|
# * creates templatized .irssi config file (irssi isn't even default anymore...)
|
||||||
|
# * creates hardcoded .twurlrc file (why not skel?)
|
||||||
|
#
|
||||||
|
# some of this stuff is pointless and the actually required stuff is:
|
||||||
|
# * create user account (useradd)
|
||||||
|
# * create home dir (useradd)
|
||||||
|
# * create .ssh/authorized_keys2 (need functions for this
|
||||||
|
# * set shell (chsh)
|
||||||
|
# * create .twurlrc (just use /etc/skel)
|
||||||
|
|
||||||
|
# other things to consider:
|
||||||
|
# * what happens when a user wants their name changed?
|
||||||
|
# * it looks like usermod -l and a mv of the home dir can change a user's username.
|
||||||
|
# * would hook this into the pre_save signal to note a username change
|
||||||
|
# * what happens when a user is marked as not reviewed?
|
||||||
|
# * does this signal user deletion? Or does literal Townie deletion signal
|
||||||
|
# "needs to be removed from disk"? I think it makes the most sense for the
|
||||||
|
# latter to imply full user deletion.
|
||||||
|
# * I honestly can't even think of a reason to revert a user to "not reviewed"
|
||||||
|
# and perhaps it's best to just not make that possible. for now, though, I
|
||||||
|
# think I can ignore it.
|
||||||
|
# * what happens when a user needs to be banned?
|
||||||
|
# * the Townie should be deleted via post_delete signal
|
||||||
|
# * what are things about a user that might change in django and require changes on disk?
|
||||||
|
# * username
|
||||||
|
# * displayname (only if i start using this?)
|
||||||
|
# * ssh key
|
||||||
|
#
|
||||||
|
# how should this code be structured?
|
||||||
|
# * within the Townie model, hardcoded
|
||||||
|
# * outside the Townie model, procedurally
|
||||||
|
# * within an abstract class
|
||||||
|
#
|
||||||
|
# for now my gut says to implement stuff hardcoded in the Townie class but with
|
||||||
|
# an eye towards generalizing the pattern in some base class for other
|
||||||
|
# resources as needed.
|
||||||
|
|
Loading…
Reference in New Issue