I want to do a API with django-graphene, but i want to apply the django default group permission when i user make a request this will have permission if his user-group allow him to CRUD.
Thanks.
This may not be an elegant solution, but it's one that works for mutations:
In my mutation, I have a custom function that returns true if the user pulled from info.context.user is in a group, or false if they are not:
class RelayCreateConversation(relay.ClientIDMutation):
# Set a variable as the field we want to use
conversation = graphene.Field(ConversationNode)
# Create a custom response as a string if the user doesn't have authentication
custom_response = graphene.String()
# What is passed through in the mutation
class Input:
participant_number = graphene.String()
def mutate_and_get_payload(root, info, **input):
submitted_by = info.context.user
# How I manage authentication
if not group_required(submitted_by, "send_and_receive_texts"):
custom_response = "You do not have the correct permissions to do this action"
return RelayCreateConversation(conversation=custom_response)
# mutation code here
And then I have a helper function which I import which is dead simple:
def group_required(user, *group_names):
if bool(user.groups.filter(name__in=group_names)) | user.is_superuser:
return True
return False
Limitations: I currently haven't tried to manage queries with this yet, just functions. If someone gets to that before I do, please comment or update my response.
Related
If I am logged in as user1 and I am accessing a ViewSet called RecipeSubmissionViewSet, the POST takes a recipe_id of the recipe the user wants to submit. How do I ensure that user1 does not submit user2's recipe, assuming the Recipe model has an owner field on it and I can just compare it to request.user? Should I use a permission class for this or is there a better way? I'm speaking from an backend point of view and not taking into account that the front end would of course filter out the recipes that belong to the user and only show them their own recipes.
There can be two ways. You can filter out queryset or define permission class.
If you override get_queryset method like this.
class RecipeSubmissionViewSet(...):
def get_queryset(self):
return Recipe.objects.filter(owner=self.request.user)
# you can also use filtration based on action name like this
# if self.action == 'update':
# return Recipe.objects.filter(owner=self.request.user)
# return Recipe.objects.all()
User will get 404 response and will never be able to access objects other than he owns.
Second choice is permission class. You can define custom permission class and check ownership explicitly like this.
from rest_framework.permissions import BasePermission
class RecipeSubmissionPermission(BasePermission):
def has_object_permission(self, request, view, obj):
# you can also check permission here based on action
# if view.action == 'update':
# pass
return request.user.is_authenticated and obj.owner == request.user
class RecipeSubmissionViewSet(...):
permission_classes=[RecipeSubmissionPermission]
In this case user will get 403 permission error.
If you use both of these methods. 404 will be preferred.
You can use whichever method you want or both of these. Permission class looks more programmatic and structured way of doing it but user will know that object with this id exists but he did not have permission to update it. But if you override queryset, user is not even be able to know if object exists or not thus more secure.
Are you using the django authentication system? Then you should be able to access request.user in the views and set the owner field accordingly.
EDIT: I think I misunderstood the question.
But this could help and Nafees Anwar looks good.
Nafees answer is pretty much the way to go.
I have been developing microservices with multitenancy to users and the rules for them(as per my projecs spec) are:
Users cannot create items on behalf of another company/user
Users cannot view/edit items belonging to another company.
The way I do this is simple.
To prohibit viewing/editing of someone else's stuff
def get_queryset(self):
return self.queryset.filter(user=request.user)
And to prohibit editing of someone else's stuff, this is done on a serializer
class SomeSerializer(...):
def validate(self, data):
data.pop('user', None)
data['user'] = self.context['request'].user
With the above, the get_queryset in the viewset will always return a 404 if user1 requests user2 info.
And the validate function will prevent assignment. Ie. If user1 creates something assigned to user2, it will rather assign user1, this behaviour is what I needed in my application, you can always raise serializers.ValidationError("You cannot assign stuff to user2") instead if thats what you need instead of reassigning the user as I do in my use case. With having this logic in the validate you can be sure that any writable function will always carry the same behaviour for that serializer.
Hope that this helps.
Im following django tutorial and there is additional ideas how to test my code.
Perhaps logged-in admin users should be allowed to see unpublished
Questions, but not ordinary visitors. Again: whatever needs to be
added to the software to accomplish this should be accompanied by a
test,...
How can I create test to check if logged in User can see Questions witout choices and not logged in not?
class QuestionsAndChoices(TestCase):
def test_user_can_see_question_without_choices(self):
"""
A Question without choices should be displayed only for logged in Users
"""
#first make an empty question to use as a test
empty_question = create_question(question_text='Empty question', days=-1)
#so we can create choice but in this case we don't need it
#answer_for_question = past_question.choice_set.create(choice_text='Answer for "Question"', votes=0)
#create a response object to simulate someone using the site
response = self.client.get(reverse('polls:index'))
#if user logged in output should contain the question without choices
self.assertQuerysetEqual(response.context['latest_question_list'], []) #returns empty querylist
In the setUp method of your test class you can create a user.
class QuestionsAndChoices(TestCase):
def setUp(self):
self.user = User.objects.create_user(
username='user', password='top_secret')
Then in your test method, you can use force_login to log the user in, then use self.client.get() and make assertions as usual.
class QuestionsAndChoices(TestCase):
def test_user_can_see_question_without_choices(self):
...
self.client.force_login(self.user)
#create a response object to simulate someone using the site
response = self.client.get(reverse('polls:index'))
#if user logged in output should contain the question without choices
...
This is more a process logic question than a specific language-framework one.
I am developing a mobile app and want the user to be able to use it without having to login (i.e. try it and offer a plus to the logged users), but I don´t want other persons to make post requests from let´s say Postman or any other platform than the app without having some sort of key, so what would be the approach here?
I am thinking on basic auth with some secret username:password for guests, or some kind of token, but as I am totally new on this I am not sure if it´s the correct approach, I´ve read the authentication and permissions Django Rest Framework tutorial but haven´t found a solution
I am learning Django myself and have gotten to the more advanced topics in the subject. What you could do is create a function in your permissions.py file for this. like so:
from rest_framework import permissions
class specialMobileUserPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in request.SAFE_METHODS:
return True
if request.user.id == whatever your mobile users id is:
return false
return obj.id == request.user.id # if the user is a subscribed user and they are logged in return true
return false # because we need a way out if none of the above works
So when dealing with permissions classes the permissions.SAFE_PERMISSIONS is a list of permissions that are non-destructive. So the first if statement asks are you a GET, HEAD, or other non data altering method. If so return true.
The second if statement checks the user id of the user that is making the request. And if that user id is equal to the user id you set for the mobile trail user it would return false, denying permissions to whatever this class is used on.
In your viewset you would need to add the permissions_classes variable like below
from . import permissions # your permissions.py file
class FooViewSet(viewsets.ViewSet):
permission_classes = (permissions.specialMobileUserPermissions,)
Unless you need extra functionality, that should be everything you need, all the way down to the imports. I hope I have helped.
I have setup a basic Django site and have added login to the site. Additionally, I have created a Student (Profile) model that expands upon the built in User one. It has a OneToOne relationship with the User Model.
However, I have yet not come right with forcing the user to automatically create a Profile the first time they log in. How would I make sure they are not able to progress through anything without creating this?
I have tried by defining the following in views:
def UserCheck(request):
current_user = request.user
# Check for or create a Student; set default account type here
try:
profile = Student.objects.get(user = request.user)
if profile == None:
return redirect('/student/profile/update')
return True
except:
return redirect('/student/profile/update')
And thereafter adding the following:
UserCheck(request)
at the top of each of my views. This however, never seems to redirect a user to create a profile.
Is there a best way to ensure that the User is forced to create a profile object above?
Looks like you're attempting to do something similar to Django's user_passes_test decorator (documentation). You can turn the function you have into this:
# Side note: Classes are CamelCase, not functions
def user_check(user):
# Simpler way of seeing if the profile exists
profile_exists = Student.objects.filter(user=user).exists()
if profile_exists:
# The user can continue
return True
else:
# If they don't, they need to be sent elsewhere
return False
Then, you can add a decorator to your views:
from django.contrib.auth.decorators import user_passes_test
# Login URL is where they will be sent if user_check returns False
#user_passes_test(user_check, login_url='/student/profile/update')
def some_view(request):
# Do stuff here
pass
I have django social-auth installed (from omab) and the users have an email address in database that is the one I want to keep but when the users log in from facebook using social-auth, their email gets replaced by the one they have in their facebook account. I am not sure the settings are the one by default or not and cannot find how to stop this behavior.
Have you tried SOCIAL_AUTH_PROTECTED_USER_FIELDS? :)
From the manual:
The update_user_details pipeline processor will set certain fields on
user objects, such as email. Set this to a list of fields you only
want to set for newly created users:
SOCIAL_AUTH_PROTECTED_USER_FIELDS = ['email',]
Also more extra values will be stored if defined. Details about this
setting are listed below in the OpenId and OAuth sections.
I found it, in the pipeline the responsible for that is
social_auth.backends.pipeline.user.update_user_details
I just removed it from the pipeline and now the details like email address and name are left to the user to fill.
I'm posting my solution (update user details, not overwrite them) so it may help someone. Based on pipeline.user.update_user_details I coded the following:
def fill_user_details(backend, details, response, user, is_new=False, *args,
**kwargs):
"""Fills user details using data from provider, without overwriting
existing values.
backend: Current social authentication backend
details: User details given by authentication provider
response: ?
user: User ID given by authentication provider
is_new: flag
source: social_auth.backends.pipeline.user.update_user_details
"""
# Each pipeline entry must return a dict or None, any value in the dict
# will be used in the kwargs argument for the next pipeline entry.
#
# If any function returns something else beside a dict or None, the
# workflow will be cut and the value returned immediately, this is useful
# to return HttpReponse instances like HttpResponseRedirect.
changed = False # flag to track changes
for name, value in details.iteritems():
# do not update username, it was already generated
if name in (USERNAME, 'id', 'pk'):
continue
# set it only if the existing value is not set or is an empty string
existing_value = getattr(user, name, None)
if value is not None and (existing_value is None or
not is_valid_string(existing_value)):
setattr(user, name, value)
changed = True
# Fire a pre-update signal sending current backend instance,
# user instance (created or retrieved from database), service
# response and processed details.
#
# Also fire socialauth_registered signal for newly registered
# users.
#
# Signal handlers must return True or False to signal instance
# changes. Send method returns a list of tuples with receiver
# and it's response.
signal_response = lambda (receiver, response): response
signal_kwargs = {'sender': backend.__class__, 'user': user,
'response': response, 'details': details}
changed |= any(filter(signal_response, pre_update.send(**signal_kwargs)))
# Fire socialauth_registered signal on new user registration
if is_new:
changed |= any(filter(signal_response,
socialauth_registered.send(**signal_kwargs)))
if changed:
user.save()