Adding permissions when user is saved in Django Rest Framework - django

I'm creating an instance of a User object. The creation itself is a standard User.objects.create_user call and that works ok - user is created. After that, I'm trying to add a few permissions to him or her:
for name in ('view_restaurant', 'change_restaurant', 'delete_restaurant',
'view_meal', 'add_meal', 'change_meal', 'delete_meal',
'view_order', 'delete_order',
'view_historicalorder', 'add_historicalorder', 'change_historicalorder',
'view_orderitem',
'view_historicalorderitem',
'view_restaurantemployee', 'add_restaurantemployee', 'change_restaurantemployee', 'delete_restaurantemployee'):
permission = Permission.objects.get(codename=name)
print(permission is None)
user.user_permissions.add(permission)
user.save()
print(user.has_perm(permission))
As you can see, in the last line I'm checking whether the user was assigned with an appropriate permission, and few lines above I'm checking if permission is None. The result is that the permission object is never none, but user.has_perm call always returns false. What am I doing wrong here?

You're calling has_perm incorrectly. It expects a string in this format
"<app label>.<permission codename>"
As an aside, may I recommend to simplify your code like so:
codenames = 'view_restaurant', 'change_restaurant', ...
perms = Permission.objects.filter(codename__in=codenames)
user.user_permissions.add(*perms)

Related

Efficient way of checking if a User has permissions from a list and converting results to a dictionary

i wish to be able to do the following :
Pass in a list of permissions , for example: ['can_change_xxx' , 'can_add_xxx',...]
The function would convert it to {'can_change_xxx':True,'can_add_xxx':False,...}
as efficiently as possible.
The reason for doing so would be to create a permissions field serializer whereby i would pass the boolean values to the front end to do some conditional rendering.
You can use the method on the User model to get the objects permissions. It returns a set of strings. With a little dict comprehension and checking to see if the
def get_perm_dict(user, perm_list):
// Gives me a set of user permissions
user_perms = User.get_user_permissions(obj=user)
// Iterates through the passed in permission list and see if the user has them
perms_dict = { permission: True if permission in user_perms else False for permission in perms_list }
This removes the need for manually checking that a user has each permission in the list via user.has_perm
You can make use dict comprehensions like this:
def get_perm_dict(user, perm_list):
return { p: user.has_perm(p) for p in perm_list}
Thats a fast way to generate dictionaries, but I'm not sure if django provides a more efficient way to get all user permissions at once intead of checking each individually.

Way to pass information from a GET parameter to a POST form in django?

I have a mailing list system in which a user can click a link to unsubscribe. This link contains the user's email in a GET parameter and the page it points to contains a short form to ask for feedback. This feedback needs to point to the email of the user who submitted it.
The way I tried to achieve this is:
take the email from the GET parameter
put it as initial value in a hidden field on the feedback form
retrieve it from form data when the form is sent
The problem is that if this hidden field is not disabled, the user can meddle with its value and dissimulate his own identity or even claim that the feedback came from another user. But if I set the field as disabled, the request.POST dictionary does not contain the field at all.
I also tried keeping the field enabled and checking for its presence in form.changed_data, but it seems to always be present there even if its value does not change.
This is the form class:
class UnsubscribeForm(forms.Form):
reason = forms.ChoiceField(choices=UnsubscribeFeedback.Reasons.choices)
comment = forms.CharField(widget=forms.Textarea, required=False)
user_email = forms.CharField(widget=forms.HiddenInput, required=False, disabled=False)
This is how I populate user_email in the view when the method is GET:
email = request.GET.get("email", "")
# ...
context["form"] = UnsubscribeForm(initial={"user_email": email})
Note that I also tried disabling the field manually after this line, as well as in the form's init method. The result is the same: if the field is disabled, the value does not get passed.
After setting the initial value, I print()ed it to make sure it was being set correctly, and it is. I also checked the page's source code, which showed the value correctly.
And this is how I check for the value in the POST part of the view, when the data-bound form is being received:
form = UnsubscribeForm(request.POST)
if form.is_valid(): # This passes whether I change the value or not.
if "user_email" in form.changed_data: # This too passes whether I change the value or not.
print("Changed!")
email = form.cleaned_data["user_email"] # This is "" if user_email is disabled, else the correct value.
I have no idea why the initial value I set is being ignored when the field is disabled. As far as I know, a disabled field passes the initial value over regardless of any changes, but here the initial value isn't being passed at all. And as I outlined above, I can't afford to keep this field editable by the user, even if it's hidden.
Django is version 3.0.3, if that matters.
Any solution? Is this a bug?
I found a solution to my problem, though it doesn't quite answer the question of why disabled fields ignore runtime initial values, so in a sense, the question is still open to answers.
In the original question, I crucially neglected to specify (in an effort to make the code minimal and reproducible) that the GET request that includes the user's email address also contains a token I generate with unpredictable data to verify that the email is authentic and corresponds to a subscribed user. In order to successfully meddle with the email, a user would also have to forge a valid token, which is unlikely (and not worth the effort) unless they have access to both my database and codebase (in which case I have worse problems than a feedback form).
I will simply keep the hidden field not disabled and also pass the token along, to verify that the address is indeed valid.

Cannot obtain user by using filter with username and password

I am using the following code
email = validated_data["login"]
password = validated_data["password"]
user_obj = User.objects.filter(Q(email__exact=email) & Q(password__exact=password))
I changed the password from admin however no user is returned. However if I remove the password check then I get a user object back.The object that I get back if I remove the Q(password__exact=password) condition has _password field as None. This code has been working fine for a while but today it is not returning back the object. Am I missing something here ? I verified that I am receiving the correct username and password from the client.I also tried accessing the admin with that username and password (The account has staff status) and I was able to log in. So the password is correct but for some reason I cant obtain that user by filtering. ? What might I be doing wrong ?
password isn't stored in plain text, but as a hash (and a little more). Get the user by username and check the password:
# assumes there can be only one
user = User.objects.get(email=email)
# this checks the plaintext password against the stored hash
correct = user.check_password(password)
BTW, you don't need Q objects for logical AND. filter(email__exact=email, password__exact=password) would suffice, even though it doesn't make much sense, in this case.
it is because Django doesn't stores password as the simple text they are hashed, you cant perform a password__exact on that it will return none every time unless you are getting the same hash password = validated_data["password"] here

django-channels: differentiate between different `AnonymousUser`s

Unfortunately I'm using django-channels channels 1.1.8, as I missed all the
updates to channels 2.0. Upgrading now is unrealistic as we've just
launched and this will take some time to figure out correctly.
Here's my problem:
I'm using the *message.user.id *to differentiate between authenticated
users that I need to send messages to. However, there are cases where I'll
need to send messages to un-authenticated users as well - and that message
depends on an external API call. I have done this in ws_connect():
#channel_session_user_from_http
def ws_connect(message):
# create group for user
if str(message.user) == "AnonymousUser":
user_group = "AnonymousUser" + str(uuid.uuid4())
else:
user_group = str(message.user.id)
print(f"user group is {user_group}")
Group(user_group).add(message.reply_channel)
Group(user_group).send({"accept": True})
message.channel_session['get_user'] = user_group
This is only the first part of the issue, basically I'm appending a random
string to each AnonymousUser instance. But I can't find a way to access
this string from the request object in a view, in order to determine who
I am sending the message to.
Is this even achievable? Right now I'm not able to access anything set in
the ws_connect in my view.
EDIT: Following kagronick's advice, I tried this:
#channel_session_user_from_http
def ws_connect(message):
# create group for user
if str(message.user) == "AnonymousUser":
user_group = "AnonymousUser" + str(uuid.uuid4())
else:
user_group = str(message.user.id)
Group(user_group).add(message.reply_channel)
Group(user_group).send({"accept": True})
message.channel_session['get_user'] = user_group
message.http_session['get_user'] = user_group
print(message.http_session['get_user'])
message.http_session.save()
However, http_session is None when user is AnonymousUser. Other decorators didn't help.
Yes you can save to the session and access it in the view. But you need to use the http_session and not the channel session. Use the #http_session decorator or #channel_and_http_session. You may need to call message.http_session.save() (I don't remember, I'm on Channels 2 now.). But after that you will be able to see the user's group in the view.
Also, using a group for this is kind of overkill. If the group will only ever have 1 user, put the reply_channel in the session and do something like Channel(request.session['reply_channel']).send() in the view. That way it doesn't need to look up the one user that is in the group and can send directly to the user.
If this solves your problem please mark it as accepted.
EDIT: unfortunately this only works locally but not in production. when AnonymousUser, message.http_sesssion doesn't exist.
user kagronick got me on the right track, where he pointed that message has an http_session attribute. However, it seems http_session is always None in ws_connect when user is AnonymousUser, which defeats our purpose.
I've solved it by checking if the user is Anonymous in the view, and if he is, which means he doesn't have a session (or at least channels can't see it), initialize one, and assign the key get_user the value "AnonymousUser" + str(uuid.uuid4()) (this way previously done in the consumer).
After I did this, every time ws_connect is called message will have an http_session attribute: Either the user ID when one is logged in, or AnonymousUser-uuid.uuid4().

Django Next Parameter Redirect - Adding Additional Parameters

I'm trying to add authentication to my webapp. A user begins the authentication process by going to:
/authorization/v1/new?client_id=X&response_type=Y&redirect_uri=Z
If request.user does not exist, I redirect using the following code:
try:
user_identity = Identity.objects.get(user=request.user)
except:
resource = '%s?next=/authorization/v1/new' % settings.LOGIN_URL
return HttpResponseRedirect(resource)
This takes the user to /login/?next=/authorization/v1/new.
The user logs in successfully, and is redirected back to /authorization/v1/new.
However, now I'm missing the required GET parameters client_id, response_type, and redirect_uri, and this causes the authorization endpoint to throw an error for missing required parameters.
What is the best way to capture the initial GET params, pass them through to login, and attach them to the redirection back to the auth endpoint?
I thought about using sessions or modifying the redirect_uri to attach the other params, but I still need to figure out how to capture a single parameter through this process. I'd like to not have to modify the login code, if possible. Any ideas/suggestions greatly appreciated. Thanks!
If you were using Django's built-in #login_required decorator, I believe this would be taken care of for you as of r2954.
Given that you're not, you just need to replicate the same steps. Just set next to the value of the full path (as Django does in #login_required), like so:
from urllib import quote
...
try:
user_identity = Identity.objects.get(user=request.user)
except Identity.DoesNotExist:
return HttpResponseRedirect('%snext=%s' % (settings.LOGIN_URL, quote(request.get_full_path()))
NB I've also changed your code to avoid your Pokémon exception handling ("Gotta catch 'em all!").