I am creating a mobile app where I need to use authentication. How can I achieve the following:
I need to create a user. After creating the user it needs to send Api_client and a secret as a response to the user.
I have a function to perform verification. After creating the user it needs to call the function for mobile verification.
Importantly, how can I stop a user who uses a for loop and starts adding users?
I tried this:
models.signals.post_save.connect(create_api_key, sender=User)
That created an API key but is not sending it as a response when creating the user is successful.
Here's what I understand from your question :
You want any user of your mobile app to register himself,
anonymously, as a user to your Django application.
This request must trigger a Tastypie api_key creation, and then return it.
You want to prevent this request from being spammed.
I don't understand this :
"I have a function for mobile without verification. After creating the user it needs to call the function for mobile verification."
To answer the points I get :
See this SO question regarding user registration with Tastypie How to create or register User using django-tastypie API programmatically?, notably this part :
def obj_create(self, bundle, request=None, **kwargs):
username, password = bundle.data['username'], bundle.data['password']
try:
bundle.obj = User.objects.create_user(username, '', password)
except IntegrityError:
raise BadRequest('That username already exists')
return bundle
For a complete walkthrough, check this article : http://psjinx.com/programming/2013/06/07/so-you-want-to-create-users-using-djangotastypie/
You're on the right track regarding the api_key creation, except you have to tell the api to actually send it back. You can use the regular way (it requires another request, though) :
i.e make it accessible from UserResource, as described in the article linked above, specifically :
def dehydrate(self, bundle):
bundle.data['key'] = bundle.obj.api_key.key
try:
# Don't return `raw_password` in response.
del bundle.data["raw_password"]
except KeyError:
pass
return bundle
If you want to send it right after a User's registration, don't forget to set "always_return_data" to True and add the api_key to the response.
Spam / loop registration :
You should look into your server's capabilities regarding this matter. For example, assuming you're using Nginx : http://wiki.nginx.org/NginxHttpLimitReqModule
Another option might be to use this : http://django-ratelimit-backend.readthedocs.org/en/latest/
Hope this helps !
Regards,
Related
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 am working on a project in angularjs and django 1.6
login(request, user)
print(request.user, request.user.id)
The login is done using standard login from django.contrib.auth in the LoginResource. Inside that it successfully prints user and its id.
Then after logging in i tried another api request from client side.Say there is a MessageResource model. And it has archive function.
def archive(self, request, **kwargs):
self.method_check(request, allowed=['post'])
data = self.deserialize(request, request.body,format=request.META.get('CONTENT_TYPE', 'application/json'))
arch_data = data.get('arch_list', '')
print(request.user, request.user.id)
So i tried to track the user inside this function.But
the last print line prints
AnonymousUser None
Means the user data is not stored in it. Can someone tell me what i'm doing wrong ? How can i get the login user from request?
Cause one - authentication that doesn't attach user to request
It depends of authorization in particular resource.
Let say if you are using ApiKeyAuthorization the user is fetched from credentials and assigned to the request during execution of authentication's is_authenticated method here: https://github.com/toastdriven/django-tastypie/blob/master/tastypie/authentication.py#L206
But if you are using standard Authentication, the method is_authenticated doesn't check or assign anything: https://github.com/toastdriven/django-tastypie/blob/master/tastypie/authentication.py#L47. So event if there are credentials in request they wont be checked or assigned.
Cause two - using resource extended methods doesn't provide authentication etc.
Yes in this case you have to check on your own: allowed http methods authorization, authentication, serialize request.body etc.
If your authentication assign user to request in is_authenticated method like ApiKeyAuthentication does for instance. You just add one line:
def archive(self, request, **kwargs):
self.method_check(request, allowed=['post'])
self.is_authenticated(request)
data = self.deserialize(request, request.body,format=request.META.get('CONTENT_TYPE', 'application/json'))
arch_data = data.get('arch_list', '')
print(request.user, request.user.id)
I have an Adobe Air mobile application that communicates with Django via TastyPie. To use the app people have to register first. Therefore they have to supply their email and password. Afterwards they will be able to "login". I thought it would be the best idea that after entering a successful username/password combination, the api-key will be sent back to the mobile app where it will be cached, so the user is "logged in".
Please tell me if you think there is a better way for registering and "logging in" users.
Inside Django I have a UserRessource class that I use to register new users when sending data via POST:
class UserResource(ModelResource):
class Meta:
allowed_methods = ['get', 'post']
queryset = User.objects.all()
resource_name = 'auth'
authentication = Authentication()
authorization = Authorization()
fields = ['username', 'email']
def obj_create(self, bundle, request=None, **kwargs):
username, email, password = bundle.data['username'], bundle.data['password'], bundle.data['password'],
try:
bundle.obj = User.objects.create_user(username, email, password)
except IntegrityError:
raise BadRequest('That username already exists')
return bundle
That works very well.
But now I'm struggling with the actual login process. In my opinion it would be best to send username and password via GET (and https) to this ressource and if those are valid, return the users api key. But would that be possible? And is it clean? Usually TastyPie would show all users currently in the DB if you send a GET request to that ressource. But I dont need that data, so I could overwrite that somehow. I already checked http://django-tastypie.readthedocs.org/en/v0.9.9/resources.html but I don't get it to work. Is it even possible to overwrite that behaviour?
So the actual questions are Whats the best way to "sign in" a user using ApiKeyAuthentication?
And Is my approach right and clean or do you have a better method? and Do you have any examples for this case?
Thanks alot in advance!
I'm using BasicAuth so it may be slightly different. But my solution is basicaly an empty resource that requires authentication. If the authentication is a success the service returns response code 200 and the authenticated user, I override obj_get_list and stuff the authenticated user in there. If the credentials are wrong the service returns response code 401.
class LoginResource(ModelResource):
class Meta:
allowed_methods = ['get']
resource_name = 'login'
include_resource_uri = False
object_class = User
authentication = BasicAuthentication()
authorization = DjangoAuthorization()
def obj_get_list(self, bundle, **kwargs):
return [bundle.request.user]
Okay I'll try to explain my point of view on the topic:
First the UserResource example on the tastypie page for me has one significant issue:
The User Objects should not be presented at any time to the single User, they should be able to see they're own "profile" or whatever but never browse and see others. Of course that can be done and with UserResource by clearing the main "list view" of that resource and applying the APIKeyAuth to the individual profiles, but still I don't like the idea of UserResource.
Second in the form when you are developing an API(such as tastypie usage) the APIKey is the actual "password" so what should be send on request is not the username and password but the username and APIKey, which is obtained in other manners(normally an e-mail or some kind of website based UI). Than they are recommended to be send via Authorization Header and not in GET parameters.
Third when we are talking about API there is no such thing as sign-in - at least not in the RESTFULL APIs - it is in some sense connectionless, so you actually going to send the Authorization header with each request.
As to the question yes you can overwrite the data. Look at the hydrate/dehydrate cycle in the Tastypie docs to understand how does it renders the content and if you have more question go ahead and ask.
I am relatively new with Django and it's ecosystem. I am writing REST api for our mobile client using django-tastypie. I have gone through almost all the examples on the web about how to use tastypie for creating REST interfaces. but none of them are specific to POSTing the data from client and how would you authorize a client.
I used the from tastypie.authentication.BasicAuthentication as show in the example. It opens a pop up asking username and password and works fine on the browser. But I am not sure, if it will do the same thing on mobile (to be specific, native IOS app). I am not quite getting when a user will make a request to login how this popup will be shown there on his/her mobile device if he or she is not using the browser but the native app.
I am totally lost on this, I would really appreciate your help.
You can check out source and use for example ApiKeyAuthentication.
You just have to POST username and api key to authentificate user.
It looks like usable for ios app.
Here is the part of the checking code.
def is_authenticated(self, request, **kwargs):
"""
Finds the user and checks their API key.
Should return either ``True`` if allowed, ``False`` if not or an
``HttpResponse`` if you need something custom.
"""
from django.contrib.auth.models import User
username = request.GET.get('username') or request.POST.get('username')
api_key = request.GET.get('api_key') or request.POST.get('api_key')
if not username or not api_key:
return self._unauthorized()
try:
user = User.objects.get(username=username)
except (User.DoesNotExist, User.MultipleObjectsReturned):
return self._unauthorized()
request.user = user
return self.get_key(user, api_key)
https://github.com/toastdriven/django-tastypie/blob/master/tastypie/authentication.py#L128
https://github.com/toastdriven/django-tastypie/blob/master/tastypie/authorization.py#L42
Thanks for the help.
I used similar approach mentioned by #Iurii. Here is my solution.
I wrote a class for handling the authentication and override is_authenticated method. and then I can use this class in Meta definition of tastypie resource classes.
from tastypie.authentication import BasicAuthentication
from tastypie.resources import Resource, ModelResource
# class for handling authentication
class MyAuthentication(BasicAuthentication):
def is_authenticated(self, request, **kwargs):
# put here the logic to check username and password from request object
# if the user is authenticated then return True otherwise return False
# tastypie resource class
class MyResource(ModelResource):
class Meta:
authentication = MyAuthentication()
this will ensure a request to access the resource will go through your authentication code.
I'm trying to set up a custom backend that queries another database, for which I have created a model in the system. It uses its own rules (email instead of username, and a differently salted/hashed password) so I can't use built in authentication. I've set up a custom authentication backend like so:
class BlahBlahBackend:
def check_password():
# check password code here
return true
def authenticate(self, email=None, password=None):
import myapp.models.loginmodel
try:
person = myapp.models.loginmodel.People.objects.get(email=email)
if check_password(password, person.password):
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
username=person.first_name + person.last_name
name_count = User.objects.filter(username__startswith = username).count()
if name_count:
username = '%s%s'%(username, name_count + 1)
user = User.objects.create_user(username,email)
else:
user = User.objects.create_user(username,email)
except People.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I've added BlahBlahBackend as an authentication backend:
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
'socialauth.auth_backends.OpenIdBackend',
'socialauth.auth_backends.TwitterBackend',
'socialauth.auth_backends.FacebookBackend',
'socialauth.auth_backends.BlahBlahBackend',
)
As you can see, I'm also using some pre-existing auth backends that are also in socialauth.
I have a submission form that points to the following view:
def blahblah_login_complete(request):
email = request.POST.get('email')
password = request.POST.get('password')
user = authenticate(email,password)
# if user is authenticated then login user
if user:
login(request, user)
else:
return HttpResponseRedirect(reverse('socialauth_login_page'))
However, when I try to login in this way, it seems like one or more of the other backends are acting as if I'm trying to log in using their method.
I read that backends are cached and so ran
Session.objects.all().delete()
to clear out the backends cache.
My main questions are:
Does the order in which items are listed in AUTHENTICATION_BACKENDS
How does the system decide/know which Backend to use? This was never made clear by any of the documentation, and I find it a bit confusing.
Is there any way to force the use of a specific authorization based on the request. In other words, if someone submits a form, is there a way to force them to use the form-login-based authentication as opposed to the login via openid or Twitter?
Update:
It works! This is very cool, thanks. I guess it just seemed like the django doc was saying "You don't have to do anything else, it just sort of works like magic" and it turns out this is absolutely the case. So long as the backend is there and the credentials are set up correctly, the authentication will work. As it turns out the real problem was a misconfiguration in the urls.py file that wasn't sending the post from the login form to the correct handler, which is why it kept trying to use another authentication method.
You're supposed to use keyword arguments to django.contrib.auth.authenticate() The names should match the names of the arguments in your backend's authenticate method. The default backend handles the names 'username' & 'password'.
Your backend can use a different name for the keyword arguments e.g.: blahblah_email and blahblah_password, and then call authenticate(blahblah_email=..., blahblah_password=...).
It's clearly described here -
django tries each backend in order
defined, if first fails to
authenticate it goes to second etc.
I believe you can load backend class dynamically and authenticate
directly through it. Look at django authenticate() function sources on how to do that.
I guess django-cas will be a good reference for you :)
And yes, the order of AUTHENTICATION_BACKENDS matters.
Django loops over the backends list and stop at the first backend that has a authenticate method accepting the credential parameters you passed to it.