How to block/override djoser user/me endpoint? - python

I would like to know if there is a chance to block the djoser's user/me endpoint for specific request methods. I don't see this in the docs.
Problem: Now when I have two users { email: 'a#exa.com' }, and { email: 'b#exa.com' }. I can use the first user to change his email to match the 2nd user's email, so both will have the same email, and the second one will be blocked cos of that.
Is there an elegant way to check if the email exists from the djoser's level?

Djoser maintainer here.
I would like to know if there is a chance to block the djoser's user/me endpoint for specific request methods. I don't see this in the docs.
You'd need to subclass UserViewSet and change me action or add custom permissions in get_permissions.
So if you wanted to disable/limit PUT
class MyCustomUserViewSet(UserViewSet):
def get_permissions(self):
if self.action == "me" and self.request.method == "PUT":
# do something
return super().get_permissions()
or
class MyCustomUserViewSet(UserViewSet):
#action(["get", "patch", "delete"], detail=False)
def me(self, request, *args, **kwargs):
return super().me(request, *args, **kwargs)
Problem: Now when I have two users { email: 'a#exa.com' }, and { email: 'b#exa.com' }. I can use the first user to change his email to match the 2nd user's email, so both will have the same email, and the second one will be blocked cos of that.
You should always have unique or pk constraint on user email. It's not djoser's responsibility to guarantee unique email in your DB.
Is there an elegant way to check if the email exists from the djoser's level?
If you use unique then it won't be 2xx and you will know something went wrong. There's no way to "check" if email exists from the djoser level and there will never be as its purpose is to be a generic REST auth for Django.

Related

Login system with Django, Kivy and SHA256

I've created a simple REST API to list and create users with Django REST Framework and I'm trying to integrate it with an Kivy app. I've used the django.contrib.auth.models.User as my user class, and passwords are being created as show below:
serializers.py
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
class Meta:
model = User
fields = ('username', 'first_name', 'last_name', 'email', 'password')
def create(self, validated_data):
user = super(UserSerializer, self).create(validated_data)
user.set_password(validated_data['password'])
user.save()
return user
As I'm using the set_password function, my REST API gives me SHA256 hashed passwords when I list my users:
GET /list/
HTTP 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/json
Vary: Accept
[
{
"username": "user1",
"first_name": "Katy",
"last_name": "Smith",
"email": "katysmith#domain.com",
"password": "pbkdf2_sha256$216000$rV8FoNw98RYu$0pdfnA4HO+15o4ov4GZVMXiq0tLNJopfLDV++iPvC5E="
},
{
"username": "user2",
"first_name": "John",
"last_name": "Smith",
"email": "johnsmith#domain.com",
"password": "pbkdf2_sha256$216000$q4wfz8tiFnnF$gmOuN7HJurbEqHykZ221UX8STcG9pAQ8WQTKF+qDtbw="
},
With that backend, I'm creating an app frontend with Kivy. I have a login screen that asks users to input their username and password. My thinking about how should I create a login system (as I'm just a student in programming) is:
Use urllib.request to get the user list;
Loop and check if the username provided is in the list;
Check if the password provided is the password store for that user in that list.
Concerning the logic of this operation, if passwords were stored in plain text, I could simple compare string with the password given by the user. But as they're hashed... how can I do it? How can I check if this hashed password 'match' with the one provided by the user on the login screen?
Also, I would consider a bonus if you can answer this: my login "strategy" is correct? There's a better way to create a login system using this tools I have? If so, how?
When you have a web service that you are using to handle login info then it should be the only one that does hashing and authenticating.
You should NEVER send a list of users and their hashed passwords to the client and then have the client authenticate there password against that list. This is a huge security issue.
Here's how a username and password authentication system works.
Client sends their username and un-hashed password to the server via https to a certain endpoint. eg. mywebsite.com/login
The server receives this request and and retrieves a user with that username if it exists from the database.
You then grab the hashed password for that user from the database as well and you then hash the password sent via https request the same way. And compare the two to see if they match.
If they match you need to send back an api key/authentication token to the client that they will use for all future requests. That api key/authentication token should be stored in the server's database to later be retrieved for requests.
From now on the client sends that api key/authentication token to the server for any requests it needs and the server checks to make sure the key/token is valid and then ignores or processes the request further.
This is a simplified version of the whole process cause there is also salt and peppering a password before hashing. There is expiration times that need to be added to key/tokens so that they dont last forever and other things.
Google around for how authentication systems work and the proper workflow for an authentication system. Hope this helps and #CyberSrikanth referenced a good article to use for getting started.

How can you customize the response message of the post in django?

I am trying to make a membership API using django rest frameworks.I made a code and checked that the function was working properly. In my current code, if the data of the email, password, and username are empty, the message is given as follows.
{
"email": [
"This field is required."
],
"username": [
"This field is required."
],
}
But after talking about this with my team's client developers, they said it was better to give a unified message as below.
{
"message": "email field is required."
}
How can I customize the value like this? Here's my code.
class customSignUpView (GenericAPIView) :
serializer_class = customRegisterSerializer
def post (self, request) :
user = request.data
serializer = self.serializer_class(data=user)
serializer.is_valid(raise_exception=True)
serializer.save()
user = User.objects.get(email=serializer.data['email'])
token = RefreshToken.for_user(user).access_token
current_site = get_current_site(request).domain
relativeLink = reverse('emailVerify')
absurls = F'http://{current_site}{relativeLink}?token={token}'
email_body = F'Hi {user.username} Use link below to verify your email \n{absurls}'
data = {'email_body': email_body, 'to_email': user.email, 'email_subject': 'Verify your email'}
Util.send_email(data)
return Response({'message': 'check your email.'}, status=201)
you need to customize customRegisterSerializer further by adding a custome validate method to it, just try to do something like this
class YourSerializer(serializers.Serializer):
field1 = serializers.CharField(args)
...
fieldn = serializers.CharField(args)
def validate(self, data):
error = {}
if 'some_field' in data:
test field is valid here
if data['some_field'] is not valid:
error['some_field'] = 'your message'
.... ad nauseam
if error:
raise serializers.ValidationError(error)
return data
pass the arguments as the data parameter, and you should be able to customize everything however you want
First of all would like to say that the standard DRF approach to error messages is more universal, as it allows sending several messages for several fields in a unified way. I.e. that in the returned JSON key is the field name and value - the list of messages. Which also allows FE to display the messages next to the appropriate field.
Cause with the format you're trying to achieve comes the question of what kind of message to send if both email and username were not provided but are required (for e.g. should it be one message string or a list of "{field_name} is required" strings?).
But if you really need to achieve the approach you mentioned, let me elaborate on the answer by #vencaslac. So in your case the serializer should roughly look like:
class CustomRegisterSerializer(serializers.ModelSerializer):
...
def validate(self, data):
if not data.get("email"):
raise serializers.ValidationError({"message": "email field is required."})
return data
The validate() method is the same for both Serializer and ModelSerializer. You can find more info in the docs. But again, with this approach you need to figure out an answer to the question I mentioned above.

How do you call a user defined function in views.py DJANGO

The requirement is to display the logged in username and his role in the organization in all the pages at the top right corner.
The role can be identified by the permissions he is given.
My approach:
I thought of creating a user-defined function in views.py and call it in each and every other function. The function that I create should check the current user's permissions and based on them his role should be decided. All these details must be sent to base.html so that it will render and display the username and his role every time the page is loaded.
Any suggestions on other approaches are welcomed and if my approach is entirely wrong please let me know.
In the views you could do something like:
context = {
'username': username,
'role': role,
}
return render('template.html', context)
and in template.html you would then render it like:
{username} {role}
You can find your answer in the comments section of: Can I call a view from within another view?
btw, yes you can create a method to validate user/department. below is the sample of how I have done in my app.
A better approach I feel is to save such details in DB as a boolean flag and use django admin's capabilities to ease your work.
def validate_details(request):
user = request.GET['username']
user_details = User.objects.filter(name=user).values_list("name", "department", flat=True)
# Call validation method defined in view
is_ok = validate_user(user_details)
if is_ok:
return render_to_response("main/landing.html", {"user": user_details["name"],"department":user_details["department"]}, context_instance=RequestContext(request))
else:
return render_to_response("main/landing.html", {"user":"NA","department":"NA"}, context_instance=RequestContext(request))

How to Create A User Model Only Including Username in Django?

Well, this is a bit complicated, but let me explain myself. I want to create a RESTful service. This service will contain users, but not in a classic way.
I want to create users based on random hashes, I will use uuid to do that. And the most important thing is that I will not need username, password, email or full_name (?). This type of user will authenticate via a GET parameter on a view, only using its username, not anything else.
I read some articles on extending Django user, yet I couldn't find satisfying explanation especially for this case.
Further Explanations
Now, I can hear questions like "Why would anyone ever need especially passwordless User model, and especially thinking that it is quite insecure.". So, this part is especially for the ones who needs a logical explanation to understand such a request.
In service, I want to have three group of users:
anonymous users: the ones who do request some data on server
uuid users: the ones who have a unique id. Why do I need this type? Because I want to track those users' requests and response special data for them. These kind of users will also be removed if they are inactive for specific several days. I think I can do it by cron jobs.
admin: This is me, reaching admin panel. That is all.
I think this explains enough.
Environment
django 1.9.5
python 3.5.1
Django supports multiple backend authentications. As Luis Masuelli suggested you can extend the model to add more fields. But in your scenario, specifically you want to implement a custom authentication method. I woudl go about treating uuid as username and setting password as None.
So, in my settings.py:
AUTH_USER_MODEL = 'app_name.MyUUIDModel'
# REMOVE ALL PASSWORD Validators
In my app_name/models.py:
from django.contrib.auth.models import BaseUserManager
class MyUUIDManager(BaseUserManager):
def create_user(self, uuid):
user = self.model(
uuid=self.normalize_email(email),
)
user.set_password(None)
user.save(using=self._db)
return user
def create_superuser(self, uuid):
user = self.create_user(uuid)
user.save(using=self._db)
return user
def get_by_natural_key(self, email):
return self.get(**{self.model.USERNAME_FIELD: email})
class MyUUIDModel(AbstractBaseUser):
uuid = models.CharField(max_length=36, unique=True)
USERNAME_FIELD = 'uuid'
objects = UUIDModelManager()
def save(self, *args, **kwargs):
super(MyUUIDModel, self).save(*args, **kwargs)
At this point, if you run createuser or createsuperuser Django command, you may be able to create the user. The next bit is where the authentication needs to be done. You can simply check if the UUID exists in your DB and return true when authenticate() is called from the view.
Add authentication backend in the settings.py file:
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'app_name.auth.MyBackend'
]
Create a file app_name/auth.py with contents SIMILAR to below:
class MyBackend(object):
def authenticate(self, username=None, password=None):
# Check if username i.e. UUID exists
try:
my_uuid = MyUUIDModel.objects.get(uuid=username)
except MyUUIDModel.DoesNotExist:
return None
return my_uuid
More more details refer to: https://docs.djangoproject.com/en/1.9/topics/auth/customizing/

Admin(only) registration of users, Flask-Security

I'm currently building a login for a webapp using Flask-Security (which includes Flask-WTForms, Flask-SQLalchemy and Flask-Login). I've been able to fairly painlessly set up the majority of my login flow, including forgotten password; however I want to make it so that the only way users can be registered is through a page only accessible to the admins. I've managed to configure Roles (Admin, User) and set up the following view:
#app.route('/adminregister')
#roles_accepted('admin')
def adminregister():
return render_template('*')
This creates the portal that is successfully limited to accounts with an Admin role. I'm unsure how to proceed for here however, as Flask-security has no built in means to enable what I'm trying to do.
I've overridden RegisterForm already to enforce password rules through a regexp:
# fixed register form
class ExtendedRegisterForm(RegisterForm):
password = TextField('Password', [validators.Required(), validators.Regexp(r'(?=.*?[0-9])(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[$-/:-?{-~!"^_`\[\]])')])
Basically I want a form, located at /adminregister, that when visited by an admin allows for the entry of an email address, at which point first the user is created in the database with a random and secure password, and then a similar process to a forgotten password happens and a 1 time password code is created to reset the password.
Useful things I've looked at:
Within flask-security/views.py there is the forgotten passsword code:
def forgot_password():
"""View function that handles a forgotten password request."""
form_class = _security.forgot_password_form
if request.json:
form = form_class(MultiDict(request.json))
else:
form = form_class()
if form.validate_on_submit():
send_reset_password_instructions(form.user)
if request.json is None:
do_flash(*get_message('PASSWORD_RESET_REQUEST', email=form.user.email))
if request.json:
return _render_json(form, include_user=False)
return _security.render_template(config_value('FORGOT_PASSWORD_TEMPLATE'),
forgot_password_form=form,
**_ctx('forgot_password'))
Within flask_security/registerable.py there is the code for register_user
def register_user(**kwargs):
confirmation_link, token = None, None
kwargs['password'] = encrypt_password(kwargs['password'])
user = _datastore.create_user(**kwargs)
_datastore.commit()
if _security.confirmable:
confirmation_link, token = generate_confirmation_link(user)
do_flash(*get_message('CONFIRM_REGISTRATION', email=user.email))
user_registered.send(app._get_current_object(),
user=user, confirm_token=token)
if config_value('SEND_REGISTER_EMAIL'):
send_mail(config_value('EMAIL_SUBJECT_REGISTER'), user.email, 'welcome',
user=user, confirmation_link=confirmation_link)
return user
I want to somehow combine these two, so that upon submission of a form with the sole field "Email" at '/adminregister' the email is added with a secure, random password in the database and the email address is sent an email with a link to change there password (and ideally a message explaining). I'm not even sure where I would add such code, as there is nothing to specifically override, especially as I can't find a way to override RegisterForm to have FEWER fields and the same functionality.
The structure of my code is in line with the flask-security documentation's quickstart.
Thank you in advance for any guidance you can offer.
I ended up using a work around as follows:
I enabled registration but limited registration view to users with an admin role.
I used del form.password in views -> register to no longer send the form with a password field.
I did the following in .registerable, generating a random password to fill the table.
kwargs['password'] = encrypt_password(os.urandom(24))
Upon admin entry of an email in the registration form, I had confimable enabled. This means the user would immediatly get an email to confirm their account and explaining they'd been registered. Upon confirmation they are redirected to the forgotten password page and asked to change their password (which is limited based on security).
If anyone comes up with a more direct way I'd appreciate it. I'm leaving this here in case anyone has the same problem.
The register process creates a signal with blinker that you can access like this:
from flask.ext.security.signals import user_registered
#user_registered.connect_via(app)
def user_registered_sighandler(app, user, confirm_token):
user_datastore.deactivate_user(user)
db.session.commit()
Which will deactivate any newly registered users.
I know this is an ancient question, but I think I have an elegant answer.
first import register_user
from flask_security.registerable import register_user
Then since you do not want just anyone to register ensure registerable is disabled (though disabled is the default so you can omit this) and since you want to send confirmation email, enable confirmable, and changeable for users to change their paswords
app.config['SECURITY_CONFIRMABLE'] = True
app.config['SECURITY_REGISTERABLE'] = False
app.config['SECURITY_RECOVERABLE'] = True
Then, you can do your create your user registration view and decorate it with role required. I have used my own custom registration form so I have had to go an extra mile to check if user already exists and return an error accourdingly
#app.route('/admin/create/user', methods=['GET', 'POST'])
#roles_required('admin')
def admin_create_user():
form = RegistrationForm(request.form)
if request.method == 'POST' and form.validate_on_submit():
email = form.email.data
password = form.password.data
user_exists = session.query(User).filter_by(email=email).first()
if user_exists:
form.email.errors.append(email + ' is already associated with another user')
form.email.data = email
email = ''
return render_template('create-user.html', form = form)
else:
register_user(
email=email,
password = password)
flash('User added successfully')
return render_template('create-user.html', form = form)
Also see flask-security - admin create user, force user to choose password
Here's another solution I found after poking through flask-security-too. I made an admin create user form, and simply add the following code after creating the user in the database:
from flask_security.recoverable import send_reset_password_instructions
# my code is maintains self.created_id after creating the user record
# this is due to some complex class involved which handles my crudapi stuff
# your code may vary
user = User.query.filter_by(id=self.created_id).one()
send_reset_password_instructions(user)

Categories

Resources