Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add admin-cli #99

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open

Add admin-cli #99

wants to merge 6 commits into from

Conversation

martinmake
Copy link

I've added a simple way to manage users and groups from the command line.

Now you should be able to do almost everything, if not everything, that you could do from the admin website. However, now you won't need to worry about superuser.

The most useful usecase would be creating the same users for multiple services with one script.

@tasn
Copy link
Member

tasn commented Jun 3, 2021

Thanks for the contribution! Let me know once it's ready to review and I'll review it.
Btw, we don't have group management in etebase-server, so no need for all of that. I guess you did that as part of an effort on your end to unify group/user creation across different django services you have? I sthat right?

@martinmake
Copy link
Author

Oh, I made support for groups and permissions just because I saw them on the admin website and I just wanted to be fully compatible. I can remove it if there are no future plans for it.

Btw, I'm completely new to django, so I only know how to add commands to manage.py. So by different services I meant mainly email on dovecot and postfix, so I have script which adds user to email, another script which adds user to etebase and a master script which uses both scripts. There is probably a way to share userdb across django services.

I know about one more addition that I could implement.
Currently when you run ./manage.py users-list, you only get usernames and there is no other command to get other information or to filter the list to only superusers/staff/active users or based on date_joined/last_login, which would be usefull for auto-kicking or auto-emailing, but those could be even their own commands which just use the python API on their own.

If the underwhelming users-list isn't an issue, I think that it's ready for review, but I haven't tested all use cases. I'd have to make a script for that, but I don't see you doing any automated tests either, so I think it's good enough and it will just get better with time.

@martinmake
Copy link
Author

I've just found the docker/test-server, but I guess that's just for testing client-app libraries (e.g. etebase-go).

@tasn
Copy link
Member

tasn commented Jun 3, 2021

Yeah, only the users- ones are needed. To be honest, even the delete ones won't really work due to how deletion blocking is currently implemented. So maybe start with users-create and list?

@martinmake martinmake marked this pull request as draft June 3, 2021 20:55
@martinmake
Copy link
Author

It seems my approach was more TDD than BDD...

I was testing User.delete when users weren't in any collection.
Isn't it just the matter of changing django.db.models.deletion.PROTECT to CASCADE?

And I've also found out that:

user = User.objects.get(username='username')
user.set_password('new password')
user.save()

will "just" change the password in myauth_user table (maybe something more) but clients are still able to sync and login only with the old password. I guess that it is somehow tied with encryption and only updating that table won't do the trick. I'd like to know what's the correct way to do this.

@tasn
Copy link
Member

tasn commented Jun 4, 2021

It seems my approach was more TDD than BDD...

BDD?

I was testing User.delete when users weren't in any collection.
Isn't it just the matter of changing django.db.models.deletion.PROTECT to CASCADE?

Yeah, but we are not doing it on purpose to protect accidental data loss (think that a single User.delete() can wipe your data!).

And I've also found out that:

user = User.objects.get(username='username')
user.set_password('new password')
user.save()

will "just" change the password in myauth_user table (maybe something more) but clients are still able to sync and login only with the old password. I guess that it is somehow tied with encryption and only updating that table won't do the trick. I'd like to know what's the correct way to do this.

Yeah, that's on purpose. The password field is actually unused by Etebase. We use a zero knowledge proof for auth. So password has to change client side...

Is this ready for review or still WIP?

@martinmake
Copy link
Author

martinmake commented Jun 4, 2021

BDD?

It's not important. I was just doing wrong tests.

We are doing it on purpose to protect accidental data loss (think that a single User.delete() can wipe your data!).

Ok, any hints on how to do it properly? Should I first remove the user from all collections (and delete them, if he was the only user in them) and then delete him? Anyways, it can always be abstracted into one function call.

The password field is actually unused by Etebase. We use a zero knowledge proof for auth. So password has to change client side...

Nice, but if I lose access to authorized client, will I be able to recover my encrypted data (e.g. through email password reset)? If so, there is probably a way to reset password from python on the server, which would be nice, but it could be used to compromise all accounts by the admin, which isn't the goal I guess.
Could you give me more insight on it, or should I just drop everything password related?

Is this ready for review or still WIP?

It's still WIP, I've changed it to draft.

@tasn
Copy link
Member

tasn commented Jun 10, 2021

Sorry for the slow reply, I missed your comment.

We are doing it on purpose to protect accidental data loss (think that a single User.delete() can wipe your data!).

Ok, any hints on how to do it properly? Should I first remove the user from all collections (and delete them, if he was the only user in them) and then delete him? Anyways, it can always be abstracted into one function call.

Yeah, the right way is to first remove the collections and everything that depends on it. You'll see, it's quite easy.

Nice, but if I lose access to authorized client, will I be able to recover my encrypted data (e.g. through email password reset)? If so, there is probably a way to reset password from python on the server, which would be nice, but it could be used to compromise all accounts by the admin, which isn't the goal I guess.
Could you give me more insight on it, or should I just drop everything password related?

No way for the server to access the data nor modify the authentication password. The authentication is done using cryptography and we don't actually use the Django password field. So yeah, drop all of these.

It's still WIP, I've changed it to draft.

Cool, let me know when ready.

@martinmake martinmake marked this pull request as ready for review June 14, 2021 19:24
@martinmake
Copy link
Author

Okay, I think it's ready now.

Here is a quick reference on how to use it:
python manage.py users-create <username> --staff create a user which can log into admin website
python manage.py users-modify <username> --active false make user unable to log in but keep his data
python manage.py users-list get a \n separated list of all users
python manage.py users-delete <username> --delete-user-data completely delete user and his data
python manage.py help users-<command> print command's options
python manage.py help print all commands

@martinmake
Copy link
Author

I've also found a hacky way to set predefined password for a user.
After you create a user, just etebase.Account.login(...) to it with the predefined password.

This is useful if you're automatically creating users for many people.
You can generate random password for each user and send it to them, so they can change it.
This fixes the problem of other users setting password to your account, when you use the same derivable pattern for all usernames.

However, I didn't find this feature essential and it would add dependency to etebase which is OK I guess.
The real problem was knowing the port number that the service was running on (for etebase.Client("http:https://localhost:<port>")), but I'm sure there is a way to get it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants