diff --git a/README.md b/README.md index 714f8c1..bcfcdde 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,7 @@ Create yourself an admin user: ``` At this stage you need to create accounts to be used with the EteSync apps. To do that, please go to: -`www.your-etesync-install.com/admin` and create a new user to be used with the service. No need to set +`www.your-etesync-install.com/admin` or use CLI `./manage.py createuser ` and create a new user to be used with the service. No need to set a password, as Etebase uses a zero-knowledge proof for authentication, so the user will just create a password when creating the account from the apps. @@ -161,7 +161,7 @@ A quick summary can be found [on tldrlegal](https://tldrlegal.com/license/gnu-af ## Commercial licensing -For commercial licensing options, contact license@etebase.com +For commercial licensing options, contact license@etebase.com # Financially Supporting Etebase diff --git a/django_etebase/admin-cli/management/__init__.py b/django_etebase/admin-cli/management/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_etebase/admin-cli/management/commands/__init__.py b/django_etebase/admin-cli/management/commands/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/django_etebase/admin-cli/management/commands/_utils.py b/django_etebase/admin-cli/management/commands/_utils.py new file mode 100644 index 0000000..5aa8dfd --- /dev/null +++ b/django_etebase/admin-cli/management/commands/_utils.py @@ -0,0 +1,5 @@ +from distutils.util import strtobool + +def argbool(arg): + if arg == None: return None + return bool(strtobool(arg)) diff --git a/django_etebase/admin-cli/management/commands/users-create.py b/django_etebase/admin-cli/management/commands/users-create.py new file mode 100755 index 0000000..abc4268 --- /dev/null +++ b/django_etebase/admin-cli/management/commands/users-create.py @@ -0,0 +1,68 @@ +from django.core.management.base import BaseCommand +from ._utils import argbool +from myauth.models import User +from django.db.utils import IntegrityError + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument( 'username' + , type=str + , help="New user's login username." ) + parser.add_argument( '-f' + , '--first_name' + , '--first' + , type=str + , default='' + , help="New user's first name." ) + parser.add_argument( '-l' + , '--last_name' + , '--last' + , type=str + , default='' + , help="New user's last name." ) + parser.add_argument( '-e' + , '--email' + , type=str + , default='' + , help="New user's email address." ) + parser.add_argument( '-a' + , '--is_active' + , '--active' + , nargs='?' + , type=argbool + , const=True + , default=True + , help="Enable login. [YES]" ) + parser.add_argument( '-s' + , '--is_staff' + , '--staff' + , nargs='?' + , type=argbool + , const=True + , default=False + , help="Mark user as staff. [NO]" ) + parser.add_argument( '-S' + , '--is_superuser' + , '--superuser' + , nargs='?' + , type=argbool + , const=True + , default=False + , help="Mark user as superuser. [NO]" ) + + def handle(self, *args, **options): + try: + User.objects.create_user( username = options["username" ] + , email = options["email" ] + , first_name = options["first_name" ] + , last_name = options["last_name" ] + , is_active = options["is_active" ] + , is_staff = options["is_staff" ] + , is_superuser = options["is_superuser"] ) + except IntegrityError as exception: + self.stdout.write(self.style.ERROR(f'Unable to create user "{options["username"]}": ' + str(exception))) + exit(1) + + self.stdout.write(self.style.SUCCESS(f'User "{options["username"]}" has been created.')) + exit(0) diff --git a/django_etebase/admin-cli/management/commands/users-delete.py b/django_etebase/admin-cli/management/commands/users-delete.py new file mode 100755 index 0000000..bc5c56b --- /dev/null +++ b/django_etebase/admin-cli/management/commands/users-delete.py @@ -0,0 +1,33 @@ +from django.core.management.base import BaseCommand +from myauth.models import User +from django.db.models.deletion import ProtectedError + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument( 'username' + , type=str + , help="Login username of the user to be deleted." ) + parser.add_argument( '--delete-user-data' + , action='store_true' + , default=False + , help="Delete all user's collections!" ) + + def handle(self, *args, **options): + try: + user = User.objects.get(username = options["username"]) + if options["delete_user_data"]: + collections = user.collection_set.all() + for collection in collections: + collection.delete() + user.delete() + except User.DoesNotExist as exception: + self.stdout.write(self.style.ERROR(f'Unable to delete user "{options["username"]}": ' + str(exception))) + exit(1) + except ProtectedError as exception: + self.stdout.write(self.style.ERROR(f'Unable to delete user "{options["username"]}": ' + str(exception))) + self.stdout.write(self.style.NOTICE('Use --delete-user-data to overcome this protection.')) + exit(2) + + self.stdout.write(self.style.SUCCESS(f'User "{options["username"]}" has been deleted.')) + exit(0) diff --git a/django_etebase/admin-cli/management/commands/users-list.py b/django_etebase/admin-cli/management/commands/users-list.py new file mode 100755 index 0000000..f4e1402 --- /dev/null +++ b/django_etebase/admin-cli/management/commands/users-list.py @@ -0,0 +1,9 @@ +from django.core.management.base import BaseCommand +from myauth.models import User + +class Command(BaseCommand): + + def handle(self, *args, **options): + for user in User.objects.all(): + print(user.username) + exit(0) diff --git a/django_etebase/admin-cli/management/commands/users-modify.py b/django_etebase/admin-cli/management/commands/users-modify.py new file mode 100755 index 0000000..2f0f686 --- /dev/null +++ b/django_etebase/admin-cli/management/commands/users-modify.py @@ -0,0 +1,81 @@ +from django.core.management.base import BaseCommand +from ._utils import argbool +from myauth.models import User +from django.db.utils import IntegrityError + +class Command(BaseCommand): + + def add_arguments(self, parser): + parser.add_argument( 'username' + , type=str + , help="User's login username." ) + parser.add_argument( '-u' + , '--new_username' + , '--new-username' + , type=str + , default=None + , help="User's new login username." ) + parser.add_argument( '-f' + , '--first_name' + , '--first-name' + , '--first' + , type=str + , default=None + , help="User's new first name." ) + parser.add_argument( '-l' + , '--last_name' + , '--last-name' + , '--last' + , type=str + , default=None + , help="User's new last name." ) + parser.add_argument( '-e' + , '--email' + , type=str + , default=None + , help="User's new email address." ) + parser.add_argument( '-a' + , '--is_active' + , '--is-active' + , '--active' + , nargs='?' + , type=argbool + , const=True + , default=None + , help="Enable/Disable login." ) + parser.add_argument( '-s' + , '--is_staff' + , '--is-staff' + , '--staff' + , nargs='?' + , type=argbool + , const=True + , default=None + , help="Mark/Unmark user as staff." ) + parser.add_argument( '-S' + , '--is_superuser' + , '--is-superuser' + , '--superuser' + , nargs='?' + , type=argbool + , const=True + , default=None + , help="Mark/Unmark user as superuser." ) + + def handle(self, *args, **options): + try: + user = User.objects.get(username = options["username"]) + if options["new_username"] != None: user.username = options["new_username"] + if options["email" ] != None: user.email = options["email" ] + if options["first_name" ] != None: user.first_name = options["first_name" ] + if options["last_name" ] != None: user.last_name = options["last_name" ] + if options["is_active" ] != None: user.is_active = options["is_active" ] + if options["is_staff" ] != None: user.is_staff = options["is_staff" ] + if options["is_superuser"] != None: user.is_superuser = options["is_superuser"] + user.save() + except (User.DoesNotExist, ValueError) as exception: + self.stdout.write(self.style.ERROR(f'Unable to modify user "{options["username"]}": ' + str(exception))) + exit(1) + + self.stdout.write(self.style.SUCCESS(f'User "{options["username"]}" has been modified.')) + exit(0) diff --git a/etebase_server/settings.py b/etebase_server/settings.py index 42ded0b..6e34d84 100644 --- a/etebase_server/settings.py +++ b/etebase_server/settings.py @@ -57,6 +57,7 @@ INSTALLED_APPS = [ "myauth.apps.MyauthConfig", "django_etebase.apps.DjangoEtebaseConfig", "django_etebase.token_auth.apps.TokenAuthConfig", + "django_etebase.admin-cli", ] MIDDLEWARE = [