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/
Related
O.K. I have a headache with this problem. I have to different sites(non django) with login option and I would like to join in it into one new website based on django.
Each of these two user databases consist of table with columns:(username, password, e-mail).
The problem is, I just can not copy it to User table in Django as we all know django is very rigid about it, so I am trying to think a way, existing users would be able to login to site as nothing has changed.
Is there any django/pythonic way to do so?
I was thinking to create an app, which would take a foreign key to User model. Within User model I would create two users (database_1, database_2), so whenever a user from database 1 would login, e.g. JohnSmith, he would be connected as database_1 user, but so would JessicaSimpson if she would be in database 1. I am just thing to create Authentication and Authorization app as system app in some way... Is this a right way thinking? Would love to hear from professionals. Thanks
in models:
from django.db import models
from django.contrib.auth.models import User, Group
# Create your models here.
class New_users(models.Model):
new_user_id = models.ForeignKey(User, unique=False)
username = models.CharField(max_length=25)
password = models.CharField(max_length=25)
email = models.CharField(max_length=25)
in views:
from django.shortcuts import render
from django.http import HttpResponse
# Create your views here.
def home(request):
if request.method == 'POST':
#if username and password from New_users are ok...
login()#User where id is the same blah blah....
I'm a professional, and I would add the old users to the new DB and put in random passwords. I would also make a table of old_users with their old hashed passwords.
I would flag these old users such that, when they visit the new app, they would be forced to enter their old pw (you'd need to know the hash method) and, if successful, then set the old pw to the new user, and log them in.
If that's too much trouble, you could write a script that sends all the old users an email (naturally, you have their email address) and a link to a change_password form. It's pretty easy to extend the django reset password functionality. And it's a good thing to have.
Could you just migrate the existing users into the new database by looping through the existing users and calling the create_user function? This would take care of the password hashing and everything, assuming that you can decrypt your current passwords back to plaintext.
I have a very old Perl CGI system with a table of users. The passwords are stored as computed hashes using the crypt function. I am looking to migrate the system to Django, and I would like to keep the user data while using the Django table structure.
One option would be to copy all the user/password data an auth_user table and use a custom authentication function since I have existing password hashes.
Do I have a better option?
If I go with this option then how can it be implemented?
The documentation for writing up a custom authentication backend are in
Customizing authentication in Django
Since Borodin asked and since you might find it handy to have a sample more specific to your request, I went ahead and wrote up an example that authenticates against a crypt-based file (e.g., htpasswd).
If the backend finds a user with a matching password in the crypt file, it looks for a standard Django user and returns it. If it can't find one it creates one. Obviously you would need to decide how you want to handle the actual details of your implementation.
./authbackend/__init__.py
import crypt
from django.conf import settings
from django.contrib.auth.models import User
class CryptBackend(object):
def authenticate(self, request, username=None, password=None):
crypt_file = getattr(settings, "CRYPT_DB", None)
if crypt_file is None:
return None
password_match = False
with open(crypt_file,"r") as f:
for line in f:
(user, crypted_pass) = line.rstrip().split(":")
if user == username:
password_match = crypt.crypt(password, crypted_pass) == crypted_pass
break
if not password_match:
return None
# found a match in our crypt database
try:
django_user = User.objects.get(username=username)
except User.DoesNotExist:
django_user = User.objects.create_user(username=username, email='', password=password)
django_user.is_staff = True
django_user.save()
return django_user
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
That new custom backend is loaded based on additions to settings.py. In my example, I'm keeping the default Django backend and simply adding my new custom one. Django checks them in order, so it will try a standard Django authentication and if that doesn't work moves on to my custom one. The CRYPT_DB parameter is the path to the htpasswd file.
settings.py
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'authbackend.CryptBackend',
]
CRYPT_DB = '/path/to/your/passwd.file'
And for completeness, an example of the format (htpasswd) that the above is checking against.
passwd.file
jill:C.1oP2DOot4MY
jack:qJn7lPS/VNssM
Is there any way to configure Mezzanine so that the admin user gets an email when a new (regular) user signs up? I have ACCOUNTS_VERIFICATION_REQUIRED=True, so the would-be user gets an email, but I don't want to have to approve accounts myself (ACCOUNTS_APPROVAL_REQUIRED).
If this isn't possible out of the box, do I need to customize the accounts app? Or monkey-patch UserProfileAdmin.save_model? What is the best approach?
For the sake of closure, here is the solution I was more or less handed from Steve MacDonald himself on the mezzanine-users mailing list. The setting ACCOUNTS_PROFILE_FORM_CLASS allows one to specify a custom form class for user profile signup/update. So, in settings.py set:
ACCOUNTS_PROFILE_FORM_CLASS = "myapp.forms.MyCustomProfileForm"
And in myapp.forms.py send the email on save:
from mezzanine.accounts.forms import ProfileForm
class MyCustomProfileForm(ProfileForm):
def save(self, *args, **kwargs):
user = super(MyCustomProfileForm, self).save(*args, **kwargs)
if self._signup:
# send email here
return user
This worked very well for me.
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)
i have an application where we allow users to use Oauth2 for authentication and even Custom User Registrations. All the Users are saved into the default User entity in the datastore. If the user is logging in using Oauth2 for the first time a new record in the default User entity is created like this:
"""Check if user is already logged in"""
if self.logged_in:
logging.info('User Already Logged In. Updating User Login Information')
u = self.current_user
u.auth_ids.append(auth_id)
u.populate(**self._to_user_model_attrs(data, self.USER_ATTRS[provider]))
u.put()
else:
"""Create a New User"""
logging.info('Creating a New User')
ok, user = self.auth.store.user_model.create_user(auth_id, **self._to_user_model_attrs(data, self.USER_ATTRS[provider]))
if ok:
self.auth.set_session(
self.auth.store.user_to_dict(user)
)
self.redirect(continue_url)
for custom registrations records are inserted through the following handler.
class RegistrationHandler(TemplateHandler, SimpleAuthHandler):
def get(self):
self.render('register.html')
def post(self):
"""Process registration form."""
user = 'appname:%s' % self.request.get('email')
name = '%s %s' % (self.request.get('first_name'), self.request.get('last_name'))
password = self.request.get('password')
avatar = self.request.get('avatar')
act_url = user_activation.Activate(self.request.get('first_name'), self.request.get('email'))
ok, user = User.create_user(auth_id=user, name=name, password_raw=password, email=self.request.get('email'))
if ok:
self.auth.set_session(self.auth.store.user_to_dict(user))
acc = models.Account(display_name=self.request.get('first_name'), act_url=act_url, act_key=act_url.split('activate/')[1], user=users.User(User.get_by_auth_id(self.current_user.auth_ids[0]).email))
acc.put()
if avatar:
avt = models.Picture(is_avatar=True, is_approved=True, image=avatar, user=users.User(User.get_by_auth_id(self.current_user.auth_ids[0]).email))
avt.put()
self.redirect('/')
Now we are using webapp2_extras.sessions for session handling. We have different models like, Comments, Images, Reviews etc in which we want to use db.UserProperty() as the author field. However, the author field shows blank or None whenever we enter a record into any of these models using 'users.get_current_user()'. I think this is because we are handling the sessions through webapp2 sessions.
What we want to achieve is to be able to use the db.UserProperty field in various models and link appropriately to the current user using webapp2 sessions ?
the UserProperty() has to be passed with a User Object in order for it to properly insert the records. Even though we are able to enter the records using the following code :
user = users.User(User.get_by_auth_id(self.current_user.auth_ids[0]).email)
or
user = users.User(User.get_by_auth_id(self.current_user.auth_ids[0]).name)
but then we are not able to get the whole user object by referencing to model.author
Any ideas how we should achieve this ?
OAuth 2.0 is not currently supported by Users service. Supported options are
Google Accounts
OpenId
OAuth 1.0
I don't frankly understand what you're trying to accomplish with introducing db.User in to the codebase. Given there's self.current_user, I assume you're already handling authentication process.
When you do self.auth.store.user_model.create_user - that already gives you a webapp2's user object/entity (it has nothing to do with db.User though). I believe that's what you'll have to use as your author field given OAuth 2.0 constraint.
users.get_current_user() relies on a special cookie (App Engine internal). In fact, it has nothing to do with webapp2's session (or any other "custom" session for that matter). You could hack it by setting the cookie to a value that App Engine internals can understand and be tricked as if a user were logged in with one of the methods I mentioned, but I wouldn't recommend this approach. It is not documented (cookie name, format, etc.) and might be changed at any time.
Instead of using UserProperty to store references to the webapp2 user objects, you should instead store the auth_id as a StringProperty and add a convenience method for fetching the corresponding webapp2 user entity.
Something like this
from webapp2_extras.appengine.auth.models import User
class Comment(db.model):
text = db.StringProperty()
author = db.StringProperty()
def get_author(self):
return User.get_by_auth_id(self.author)