Django security and authentication - django

I have a few questions regarding tokens and username/pass pairs.
I have a django rest API set up which uses tokens once a user has registered. However I do not know how to return the token to the user in a safe matter? Currently I use:
response_data = UserSerializer(instance=new_user).data
response_data['token'] = token.key
return Response(response_data, status=status.HTTP_201_CREATED)
But in this way i can clearly see all of the details in my Response body in the browser? Even my password. How should I return it to the client ?
When registering a User I do it this way:
serialized = UserSerializer(data=request.DATA)
if serialized.is_valid():
print(serialized.validated_data)
new_user = get_user_model().objects.create(**serialized.validated_data)
token = Token.objects.create(user=new_user)
Will this create my user properly ? Will the password be hashed?
Thank you
P.S. here is the whole method:
#api_view(['POST'])
def register_user(request):
print (request)
serialized = UserSerializer(data=request.DATA)
if serialized.is_valid():
print(serialized.validated_data)
new_user = get_user_model().objects.create(**serialized.validated_data)
token = Token.objects.create(user=new_user)
response_data = UserSerializer(instance=new_user).data
response_data['token'] = token.key
return Response(response_data, status=status.HTTP_201_CREATED)
else:
return Response(serialized._errors, status=status.HTTP_400_BAD_REQUEST)

I would handle #1 by setting a cookie, if that works for your use case. Relevant SO Post: How to set cookie in Django view and then render template.
For #2, I believe you should use create_user rather than create. Check the Django docs here. A quick way to check and see if your passwords are getting hashed properly is to pop open a shell, grab a user object, and see what the password looks like:
>>u = User.objects.get(id=1)
>>u.password
u'pbkdf2_sha256$12000$e30c2ea7a76f83b7c1a975ddc24286b675e714ebbbc72ccd5f0401730231ab57'
You will easily be able to tell whether or not the password has been hashed.

Related

Django request.POST returning NONE

I have been working on a Django REST API and one of the views is as follows :
#api_view(['POST'])
def create(request):
key = request.POST.get('key')
name = request.POST.get("name")
email = request.POST.get("email")
password = request.POST.get("password")
print(key)
if(key=='0'):
user = Users(name=name,email=email,password=password)
user.save()
return Response("User Created Successfully")
else:
return Response("Invalid Key")
When I send a POST request with all the proper parameters I get the key printed as NONE, but I tried replacing POST with GET every where as below and then sending a GET request actually works normally, but POST request isn't working :
#api_view(['GET'])
def create(request):
key = request.GET.get('key')
name = request.GET.get("name")
email = request.GET.get("email")
password = request.GET.get("password")
print(key)
if(key=='0'):
user = Users(name=name,email=email,password=password)
user.save()
return Response("User Created Successfully")
else:
return Response("Invalid Key")
Thanks in advance !!
Tried GET instead of POST and that works, but since this is a method to enter value in the DB so this should be POST request.
[Edit 1]
I have tried using request.data but that isn't working, it is returning empty request like this {} and the same is the case with request.POST.
I am sending the request from Postman.
The request I am sending is like this :
In your screenshot, you are passing query parameters in POST request. You should POST data though data tab in postman (formdata,json,etc). Based on your screenshot, you can get the data passed through request.query_params, but this is not recommended for POST requests.
In Django REST Framework, request.POST or data submitted through POST is restructured into request.data and data through GET requests is passed into request.query_params. Both of these are QueryDict, so normal dictionary methods are applicable.
Reference: https://www.django-rest-framework.org/api-guide/requests/#request-parsing

Is it possible to make email confirmation key shorter with dj-rest-auth?

I am making a rest api for mobile application. I am using dj-rest-auth package for the overall authentication process. Overall auth functionality works fine but One thing I want to modify is to make the email confirmation key shorter.
User will get email like this.
Please use this key to confirm your email.
MjE:1l7ZhR:f6U2RWlx2kEJY2jXzFuAuKpKclNyc3MpaKmeiEFGp3Y
In my email verify api user need to enter this whole key for verification.
Is there any way to make this key shorter so that it will be good from user perspective(I think) ?
I have made my custom adapter here.
class MyAdapter(DefaultAccountAdapter):
def send_confirmation_mail(self, request, emailconfirmation, signup):
current_site = get_current_site(request)
activate_url = self.get_email_confirmation_url(
request,
emailconfirmation)
ctx = {
"user": emailconfirmation.email_address.user,
"activate_url": activate_url,
"current_site": current_site,
"key": emailconfirmation.key,
}
Looking at your code I guess you are using django-allauth (https://github.com/pennersr/django-allauth).
In this example I assumed that user is authenticated and verifcation code doesn't have to be unique.
If you want to create a shorter key and store it in db you should create custom model where key fits your needs (4 digit in this example - I assumed your user put in manually):
class ActivationKey(models.Model):
user = models.ForeignKey(User, verbose_name=_("user"), on_delete=models.CASCADE)
key = models.PositiveSmallIntegerField(_("key"))
created_at = models.DateTimeField(auto_now_add=True)
#classmethod
def create(cls, user):
key = random.randint(1000, 9999)
return cls._default_manager.create(user=user, key=key)
We can generate key after user creation (override save() or use signal or create before sending email). As you pointed you can override send_confirmation_mail so for simplicity in this example I use it:
class MyAdapter(DefaultAccountAdapter):
def send_confirmation_mail(self, request, emailconfirmation, signup):
current_site = get_current_site(request)
ctx = {
"user": emailconfirmation.email_address.user,
"activate_url": reverse("account_confirm_key"), # depends on you - if you need it at all
"current_site": current_site,
"key": ActivationKey.objects.create(user=user) # create key
}
if signup:
email_template = "account/email/email_confirmation_signup"
else:
email_template = "account/email/email_confirmation"
self.send_mail(email_template, emailconfirmation.email_address.email, ctx)
Next you should create custom endpoint for confirm code and activate user:
class VerifyUserView(APIView):
permission_classes = [permissions.IsAuthenticated]
def post(self, request): # or change to get if more proper to you
key = request.data.get("key")
if not key:
return Response({"error": _("Key is missing")}, status=status.HTTP_400_BAD_REQUEST)
if not ActivationCode.objects.filter(user=request.user, key=key).exists():
return Response({"error": _("Wrong activation key")}, status=status.HTTP_400_BAD_REQUEST)
get_adapter(request).confirm_email(request, user.email) # confirm method from adapter
return Response({"status": "ok"})
And remember about settings:
ACCOUNT_ADAPTER = MyAdapter
About django-allauth:
Default configuration (https://dj-rest-auth.readthedocs.io/en/latest/configuration.html) for email verification is ACCOUNT_EMAIL_CONFIRMATION_HMAC = True which means django would generate HMAC (https://en.wikipedia.org/wiki/HMAC) key.
If you change option to False key will be generated and stored in EmailConfirmation model: https://github.com/pennersr/django-allauth/blob/da5ccdcf171e32ab1a438add3af38957f5a0659a/allauth/account/models.py#L100 but probably too long for you - 64 chars
Another option is that you create short link (e.g bit.ly) for user without changing anything. Or maybe if this is for mobile application you can create deeplink, so after clicking confirmation link (even long) user will be redirect to mobile app and then mobile app send request to backend?

Login user with a login link

I want to send a login link to the users.
I know there are some OneTimePassword apps out there with thousands of features. But I just want some easy and barebon way to login user via login link.
My question is if this is a correct way to go about this. Like best practice and DRY code.
So I've set up a table that stores three rows.
1. 'user' The user
2. 'autogeneratedkey' A autogenerated key
3. 'created_at' A Timestamp
When they login, the'll be sent a mail containing a login link valid for nn minutes.
So the login would be something like
https://example.net/login/?username=USERNAME&autogeneratedkey=KEY
The tricky part for me is to figure out a good way to check this and log in the user.
I'm just guessing here. But would this be a good approach?
class login(generic.CreateView):
def get(self, request, *args, **kwargs):
try:
autgeneratedkey = self.request.GET.get('autgeneratedkey', '')
username = self.request.GET.get('username', '')
obj_key = Login.objects.filter(autgeneratedkey=autgeneratedkey)[0]
obj_user = Login.objects.filter(userusername=username)[0]
try:
if obj_user == obj_key: #Compare the objects if same
if datetime.datetime.now() < (obj_key.created_at + datetime.timedelta(minutes=10)): #Check so the key is not older than 10min
u = CustomUser.objects.get(pk=obj_user.user_id)
login(request, u)
Login.objects.filter(autgeneratedkey=autgeneratedkey).delete()
else:
return login_fail
else:
return login_fail
except:
return login_fail
return redirect('index')
def login_fail(self, request, *args, **kwargs):
return render(request, 'login/invalid_login.html')
It feels sloppy to call the same post using first the autogeneratedkey then using the username. Also stacking if-else feels tacky.
I would not send the username in the get request. Just send an autogenerated key.
http://example.com/login?key=random-long-string
Then this db schema (it's a new table because I don't know if Login is already being used.
LoginKey ( id [PK], user [FK(CustomUser)], key [Unique], expiry )
When a user provides an email, you create a new LoginKey.
Then do something like this:
def get(self, request, *args, **kwargs):
key = request.GET.get('key', '')
if not key:
return login_fail
login_key = LoginKey.objects.get(key=key)
if login_key is None or datetime.datetime.now() > login_key.expiry:
return login_fail
u = login_key.user
login(request, u)
login_key.delete()
return redirect('index')
Probably you can optimize the code like this:
First assuming you have relationship between User and Login Model like this:
class Login(models.Model):
user = models.ForeignKey(CustomUser, on_delete=models.CASCADE)
Then you can use a view like this:
class LoginView(generic.View):
def get(self, request, *args, **kwargs):
try:
autgeneratedkey = self.request.GET.get('autgeneratedkey', '')
username = self.request.GET.get('username', '')
user = CustomUser.objects.get(login__autgeneratedkey=autgeneratedkey, username=username, login__created_at__gte=datetime.now()-datetime.timedelta(minutes=10))
login(request, user)
user.login_set.all().delete() # delete all login objects
except CustomUser.DoesNotExist:
return login_fail
return redirect('index')
Just another thing, it is not a good practice to use GET method where the database is updated. GET methods should be idempotent. Its better to use a post method here. Just allow user to click the link(which will be handled by a different template view), then from that template, use ajax to make a POST request to this view.

How can i capture the secret token to post users tweet - KeyError at / 'oauth_token_secret'

I have built a Django webpage with a text area and I would like a user of the site to be able to tweet the content of the text area.
I have been able to post on my own account (with developer credentials), however I'm having trouble extracting the user's secret token (gained after logging in with python social auth), and without this they cant make a post.
The problem seems to be that the secret token cannot be found in the dictionary that holds this information. Bearing in mind that login via OAuth works correctly; what is it past this point, that I am not doing, or doing incorrectly that means that 'oauth_secret_token' cannot be found/ does not exist?
views.py
def form_handle(request):
form = MyForm()
if request.method=='POST':
form = MyForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
text = cd.get('text')
if 'tweet' in request.POST:
user = request.user
soc = user.social_auth.get(provider='twitter')
access_token = soc.extra_data['access_token']
access_secret = soc.extra_data['oauth_token_secret'] <-- error generated here.
post_tweets().post_my_tweet(text, access_token, access_secret)
form = MyForm()
return render(request, 'hello.html',{'form':form})
else:
form = MyForm()
return render(request, 'hello.html',{'form':form})
twitter_posting.py
from twitter import *
class post_tweets():
ckey= "XXXXXXXXXXXXX"
csecret= "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX"
def post_my_tweet(self, tweet, atoken, asecret):
t = Twitter(auth=OAuth(atoken, asecret, self.ckey, self.csecret))
t.statuses.update(status= str(tweet))
EDIT:
I have read the Tweepy documentation here: http://docs.tweepy.org/en/v3.2.0/auth_tutorial.html
And Twython documentation here: https://twython.readthedocs.org/en/latest/usage/basic_usage.html#updating-status
I am convinced that both of these libraries would be able to help me, however cant work out how to apply it to my code. Would someone from one of these comunities be able to give me an example of how I can modify this so it tweets from the users account?
The answer is that the oauth token and secret are inside the second dimension of a 2d dictionary. The key to the dictionary is called 'access_token' it holds five KV pairs with keys: 'oauth_token', 'oauth_token_secret', 'x_auth_expires', 'user_id', and 'screen_name'.
This therefore is how you post to twitter from a users account using django:
views.py
def form_handle(request):
form = MyForm()
if request.method=='POST':
form = MyForm(request.POST)
if form.is_valid():
cd = form.cleaned_data
text = cd.get('text')
if 'tweet' in request.POST:
user = request.user
soc = user.social_auth.get(provider='twitter')
access_token = soc.extra_data['access_token'] ['oauth_token'] <-- changed
access_secret = soc.extra_data['access_token']['oauth_token_secret'] <-- changed
post_tweets().post_my_tweet(text, access_token, access_secret)
form = MyForm()
return render(request, 'hello.html',{'form':form})
else:
form = MyForm()
return render(request, 'hello.html',{'form':form})
twitter_posting.py is unchanged from the question above. If you would like to view the twitter data returned upon login you can create a custom pipeline that prints out all the data (this is how I solved the problem). Without turning this into a pipeline tutorial I've added some helpful links below.
http://psa.matiasaguirre.net/docs/pipeline.html
https://github.com/omab/python-social-auth/tree/master/social/pipeline

Show recaptcha after 3 wrong attemps and process it in Django?

I am using Class-based view with Django: I want to show the captcha only after 3 times and process captcha only after it is shown. Till now, I can only show the captcha after one wrong attempt:
def post(self, request):
response = captcha.submit(
request.POST.get('recaptcha_challenge_field'),
request.POST.get('recaptcha_response_field'),
'[[ MY PRIVATE KEY ]]',
request.META['REMOTE_ADDR'],)
username = request.POST['username']
password = request.POST['password']
user = authenticate(username=username, password=password)
state = "The username or password is incorrect."
if user is not None:
login(request, user)
return HttpResponseRedirect('/index/')
else:
#captcha = CaptchaField()
public_key = settings.RECAPTCHA_PUBLIC_KEY
script = displayhtml(public_key=public_key)
return render_to_response('index.html', {'state':state, 'captcha':'captcha', 'script':script}, context_instance=RequestContext(request))
I want to show the captcha after three times and process it using response.is_valid. How can I do this?
The simplest solution is probably to store the number of attempts in a cookie, incrementing it on each failed attempt. Obviously this can be tampered with, which brings me to signed cookies:
https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpRequest.get_signed_cookie
https://docs.djangoproject.com/en/dev/ref/request-response/#django.http.HttpResponse.set_signed_cookie
Essentially a signed cookie will raise an exception if the value has been tampered with.
You can also store the counter in the session, but I don't see any point in prematurely creating a session object when a cookie will do the trick.