Is the AuthToken generator unique enough? - django

What will happen once I generate say a million AuthTokens for users (on multiple devices).. should I be worried about uniqueness down the line?
The line that generates the token in the API is:
def generate_key(self):
unique = uuid.uuid4()
return hmac.new(unique.bytes, digestmod=sha1).hexdigest()
It seems as though it's SHA1 so I'm assuming it should be OK but perhaps I should validate not only the token but perhaps an incoming device id or e-mail?
Any thoughts?

Related

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().

How to generate hash in django 1.9 like Invitation Link

I want to send the email to the user that will contains url+hash
like this bleow
www.mywebsite.com/user/verify/121#$%3h2%^1kj3#$h2kj1h%$3kj%$21h
and save this hash against the user in the Database like this
ID | Email |Hash
1 | youremail#gmail.com |121#$%3h2%^1kj3#$h2kj1h%$3kj%$21h
When the user received the email it should check and compare the hash with it and perform the action as per situation.
My question is simple how to generate a unique hash for each user and how to store them in the Database.
If by "hash", you mean a unique string, you can just use uuid.uuid4 for that purpose.
>>> import uuid
>>> unique_id = str(uuid.uuid4())
>>> print unique_id
d8814205-f11e-46e1-925e-a878fc75cb8d
>>> # replace dashes, if you like
>>> unique_id.replace("-", "")
I've used this for projects where I need to verify a user's email.
P.S.: It's not called a hash, it's called a unique ID. Hashing is something else, where you generate a value from a given string. See this question for more explanation.
Django has a Cryptographic Signing module, which helps produce unique and verifiable signatures for any data you need. If you are trying to do this to verify that the request is done by the appropriate user or not, you can use the library to verify requests, without storing the hash in the database.

python code for directory api to batch retrieve all users from domain

Currently I have a method that retrieves all ~119,000 gmail accounts and writes them to a csv file using python code below and the enabled admin.sdk + auth 2.0:
def get_accounts(self):
students = []
page_token = None
params = {'customer': 'my_customer'}
while True:
try:
if page_token:
params['pageToken'] = page_token
current_page = self.dir_api.users().list(**params).execute()
students.extend(current_page['users'])
# write each page of data to a file
csv_file = CSVWriter(students, self.output_file)
csv_file.write_file()
# clear the list for the next page of data
del students[:]
page_token = current_page.get('nextPageToken')
if not page_token:
break
except errors.HttpError as error:
break
I would like to retrieve all 119,000 as a lump sum, that is, without having to loop or as a batch call. Is this possible and if so, can you provide example python code? I have run into communication issues and have to rerun the process multiple times to obtain the ~119,000 accts successfully (takes about 10 minutes to download). Would like to minimize communication errors. Please advise if better method exists or non-looping method also is possible.
There's no way to do this as a batch because you need to know each pageToken and those are only given as the page is retrieved. However, you can increase your performance somewhat by getting larger pages:
params = {'customer': 'my_customer', 'maxResults': 500}
since the default page size when maxResults is not set is 100, adding maxResults: 500 will reduce the number of API calls by an order of 5. While each call may take slightly longer, you should notice performance increases because you're making far fewer API calls and HTTP round trips.
You should also look at using the fields parameter to only specify user attributes you need to read in the list. That way you're not wasting time and bandwidth retrieving details about your users that your app never uses. Try something like:
my_fields = 'nextPageToken,users(primaryEmail,name,suspended)'
params = {
'customer': 'my_customer',
maxResults': 500,
fields: my_fields
}
Last of all, if your app retrieves the list of users fairly frequently, turning on caching may help.

FK validation within Django

Good afternoon,
I have my django server running with a REST api on top to serve my mobile devices. Now, at some point, the mobile device will communicate with Django.
Let's say the device is asking Django to add an object in the database, and within that object, I need to set a FK like this:
objectA = ObjectA.objects.create(title=title,
category_id = c_id, order = order, equipment_id = e_id,
info_maintenance = info_m, info_security = info_s,
info_general = info_g, alphabetical_notation = alphabetical_notation,
allow_comments = allow_comments,
added_by_id = user_id,
last_modified_by_id = user_id)
If the e_id and c_id is received from my mobile devices, should I check before calling this creation if they actually still exists in the DB? That is two extra queries... but if they can avoid any problems, I don't mind!
Thanks a lot!
It think that Django creates constraint on Foreign Key by default ( might depend on database though ). This means that if your foreign keys point to something that does not exist, then saving will fail ( resulting in Exception on Python side ).
You can reduce it to a single query (it should be a single query at least, warning I haven't tested the code):
if MyObject.objects.filter(id__in=[e_id, c_id]).distinct().count() == 2:
# create the object
ObjectA.objects.create(...)
else:
# objects corresponding e_id and c_id do not exist, do NOT create ObjectA
You should always validate any information that's coming from a user or that can be altered by a determined user. It wouldn't be difficult for someone to sniff the traffic and start constructing their own REST requests to your server. Always clean and validate external data that's being added to the system.

Django-registation : Error on activate new user

I'm using Django registration inside my project on a development server.
When I register a new user, I use EMAIL_BACKEND = 'django.core.mail.backends.filebased.EmailBackend' to get the activation link.
When I try to put the activation link into the web browser, I have an error, and the account is not activated.
It is said :
Thank you.
This function is used to generate the key.
def create_profile(self, user):
"""
Create a ``RegistrationProfile`` for a given
``User``, and return the ``RegistrationProfile``.
The activation key for the ``RegistrationProfile`` will be a
SHA1 hash, generated from a combination of the ``User``'s
username and a random salt.
"""
salt = hashlib.sha1(str(random.random())).hexdigest()[:5]
username = user.username
if isinstance(username, unicode):
username = username.encode('utf-8')
activation_key = hashlib.sha1(salt+username).hexdigest()
return self.create(user=user,
activation_key=activation_key)
I received that mail. But I use EMAIL_BACKEND'django.core.mail.backends.filebased.EmailBackend'.
I think the problem comes from here. But I can't test in production server.
I solved the problem actually It's because I generate the email to send inside a file thanks to the file email backends provided by django for development purpose. Inside this file, when there is a carriage return, it adds an = characters. And this is the case with the link to active the account.
You shouldn't have a = character in your activation key.
Although sergzach's answer will work, I'd be more interested in finding out why that = is there in the first place.
django-registration usually generates the key as follows:
salt = sha.new(str(random.random())).hexdigest()[:5]
activation_key = sha.new(salt+user.username).hexdigest()
Where are you generating yours?
The character '=' is not in the range of \w+. Use [\w=]+ instead of \w+.
Replace ?P<activation_key>\w+ to ?P<activation_key>[\w=]+