I did not find anything on the web and so I'm asking here.
Is there a way to create a custom auth wich only requires a username? That means to login to a specific subpage one has only to enter a username, no email and no password etc.?
Or is there a better way to do this? E.g. a subpage can only be accessed if the username (or similar) exists in a db table?
Yes you could do something like this:
(in models file, or at the top of the controller(s), or even better make a function decorator)
Check session.logged_in_user to see if it's None, if None, redirect to /default/login where you present the user with a form:
form = FORM(Field('username'), requires=IS_IN_DB(db, db.users.username))
On form submission (see web2py manual for form processing), if valid (e.g. if username exists in db.users table), set session.logged_in_user = request.vars.username
Here's a completish example (untested):
models/Auth.py
# Could also check whether session.logged_in_user exists in DB, but probably not needed
# If so though, should be renamed zAuth or something to come after db.py file
if not session or not session.logged_in_user:
redirect(URL('default','login', vars={'next':request.vars.url}))
controllers/default.py
#in file: controllers/default.py
...
def login():
form = FORM(Field('username', requires=IS_IN_DB(db, db.users.username))
if form.process().accepted:
session.logged_in_user = form.vars.username
redirect(request.vars.next)
elif form.errors:
session.logged_in_user = None # not necessary, but oh well
response.flash = "Please enter a valid username"
return dict(form=form)
views/default/login.html
{{ extend 'layout.html' }}
{{ =form }}
By placing code in a models file, you can ensure it is executed on every page request.
This will not allow you to use web2py's authentication mechanism (i.e. auth = AUTH()), but I'm not sure that you'd want it for this anyway unless you're interested in using groups and permissions, etc. But if that's the case, adding passwords (even if it's a generic password or a blank one) seems like it wouldn't be too much trouble.
web2py by default allows blank passwords. So simply hide the password fields in the login and registration forms using CSS. You should be able to use the default auth.
if you want to sign-in with only your unique username and password go to db.py and write this code:
auth.define_tables(username=True,signature=True)
db.auth_user.username.requires = IS_NOT_IN_DB(db, 'auth_user.username')
db.auth_user.email.readable = False
db.auth_user.email.writable = False
db.auth_user.first_name.readable = False
db.auth_user.first_name.writable = False
db.auth_user.last_name.readable = False
db.auth_user.last_name.writable = False
for me it worked
Related
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
I'm trying to implement password reset by sending an email to the user with a link which will redirect him/her to a new password form.
I took by example this question and this site.
But my problem is a bit different. I don't have a local database containing the users, so I cannot perform operations over their attributes. I receive user data via an API (user id, user email, user password).
So, which is the best way to generate a unique link to send via email to user so that this link would tell me who the user is and allow me to reset his/her password? And also, how could I redirect it in urls.py?
I wish that this link could be used only a single time.
My views.py is like this:
def password_reset_form(request):
if request.method == 'GET':
form = PasswordResetForm()
else:
form = PasswordResetForm(request.POST)
if form.is_valid():
email = form.cleaned_data['email']
content_dict = {
'email': email,
'domain': temp_data.DOMAIN,
'site_name': temp_data.SITE_NAME,
'protocol': temp_data.PROTOCOL,
}
subject = content_dict.get('site_name')+ ' - Password Reset'
content = render_to_string('portal/password_reset_email.html', content_dict)
send_mail(subject, content, temp_data.FIBRE_CONTACT_EMAIL, [email])
return render(request, 'portal/password_reset_done.html', {'form': form,})
return render(request, 'portal/password_reset_form.html', {'form': form,})
And the template the e-mail I'm sending is:
{% autoescape off %}
You're receiving this e-mail because we got a request to reset the password for your user account at {{ site_name }}.
Please go to the following page and choose a new password:
{% block reset_link %}
{{ protocol }}://{{ domain }}/[***some unique link***]
{% endblock %}
If you didn't request a password reset, let us know.
Thank you.
The {{ site_name }} team.
{% endautoescape %}
Thanks, guys.
Don't reinvent the wheel. You should use django-allauth for this kind of stuff. The library is well maintained and under active development.
The problem is not to generate unique link, you need to store somewhere info about user id or email, and generated token. Otherwise you will not know which user should use which token. After user resets his password you can delete his record (his token).
You can write the most simple model even in sqlite, which basically could look like this:
class UserTokens(model.Models):
email = models.EmailField(max_length=50)
token = models.CharField(max_length=50)
Afterwards when you send mail make something like this:
def password_reset_form(request):
#your logic here
# form post
usr, created = UserToken.objects.get_or_create(email=form.email)
if usr.token:
#send this token to him
else:
usr.token = ''.join(
random.choice(string.ascii_uppercase + string.digits) for _ in range(50))
usr.save()
#also send this token to him
Then you create a new view or api view which searches for that token, if found, let him reset the password. If not, raise a 404 error, or just let him know that there is no such link to reset password.
Please not that, this was written from my phone so care for typos.
PS. you also asked about urls
just make something like this:
url(r'unsubscribe/(?P<quit_hash>[\w\d]+)/$', 'quit_newsletter_or_somethin', name='quit_newsletter_or_somethin')
I’d suggest to borrow the general logic from Django and adapt it to your specific conditions:
PasswordResetTokenGenerator code — pretty well-documented, very useful
password reset docs (probably known information, though)
password reset view implementation
As you can see from PasswordResetTokenGenerator._make_token_with_timestamp(), its algo relies on user’s last login timestamp, so the APIs you consume would need to accommodate that.
You could import the same utility functions used by the above—where cryptography is concerned it’s better to rely on well-tested solutions. Deep internals are prone to change without a release note though when you update to newer Django versions, so take care.
You could also look into simply storing some carefully randomly generated reset codes along with usernames in your local DB and deleting them when user accesses the reset form, but that is less elegant while being more brittle and infosec-issue prone.
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 use the backend solution from django. I just want to get a username from the cookie or the session_key to get to know the user. How I can do it?
from django.contrib.auth.models import User
from django.contrib.sessions.models import Session
def start(request, template_name="registration/my_account.html"):
user_id = request.session.get('session_key')
if user_id:
name = request.user.username
return render_to_response(template_name, locals())
else:
return render_to_response('account/noauth.html')
Only else is coming up. What am I doing wrong?
Am I right then that authenticated means he is logged in?
--> Okay this I got!
Firstly, if you have some clarification to a question, update the question, don't post an answer or (even worse) another question, as you have done. Secondly, if the user is logged out, by definition he doesn't have a username.
I mean the advantage of Cookies is to identify a user again. I just want to place his name on the webpage. Even if he is logged out. Or isnt't it possible?
You can check if a user is authenticated by calling the, apptly named, is_authenticated method. Your code would then look somewhat like this:
def start(request, template_name="registration/my_account.html"):
if request.user.is_authenticated():
name = request.user.username
return render_to_response(template_name, locals())
else:
return render_to_response('account/noauth.html')
No need to access the session yourself, Django handles all of that automatically (provided you use both django.contrib.sessions and django.contrib.auth).
/edit: in order to have a user's username, he needs to be authenticated. There's no good way around that.
piquadrat has absolutely the right answer, but if for some reason you do need to get the user from the session, you call get_decoded() on the session object:
session_data = request.session.get_decoded()
user_id = session_data['_auth_user_id']
You need to enable the AuthenticationMiddleware and SessionMiddleware in your MIDDLEWARE_CLASSES setting in your settings.py to access request.user in your views.
http://docs.djangoproject.com/en/1.2/topics/auth/#authentication-in-web-requests
You can then access the username using request.user.username
I have a flatland form for a proxy website that looks something like this:
class doSomethingForm(Form):
'''Do an action with account credentials'''
username = String.using(default='',
validators=[Present(), UsernameFormat()])
password = String.using(default='',
validators=[Present(), PasswordFormat()])
action = String.using(default='',
validators=[Present(), ActionFormat()])
If any of the parameters are empty, I want the validation error to be that the given elements are empty. If they are non-empty and meet other requirements, then the next step is to ask the other service if the credentials are valid. If they are invalid, then I want to turn that into a validation error.
This looks a lot like flatland's container-level validation, which runs validation on the contained elements first. So I tried this:
class doSomethingForm(Form):
'''Do an action with account credentials'''
account = Dict.of(
username = String.named('username').using(default='',
validators=[Present(), UsernameFormat()])
password = String.named('password').using(default='',
validators=[Present(), PasswordFormat()])
).using(validators=[CheckCredentials()])
action = String.using(default='',
validators=[Present(), ActionFormat()])
This looks like it would work, but now the form is looking for elements named account_username and account_password, which isn't quite the interface I was hoping for.
I see a couple of solutions:
I could attach the CheckCredentials validator to doSomethingForm. But, I don't see a way to do this in the documentation.
I could attach the CheckCredentials validator to username, which can access its sibling password. However, I don't want to bother the other server if the username and password are in a bad format. so I'd have to run the validations on username and password first, which seems like repeating myself.
I could hack a verson of Dict that doesn't do namespacing
Is there right way to add form-level validators in flatland?
After reading a bit of the docs I would assume you can simply do your_form.validate().
The Form class inherits the Dict class so the way you can do validation is identical to how the container-level validation normally works.
Disclaimer: until your question I've never heard of "flatland" so I can be really off here.
It is useful to checkout the flatland code (hg clone http://bitbucket.org/jek/flatland), because the documentation and examples aren't quite there yet.
Form inherits from Dict, which inherits from Container, which inherits from Element. There are two class elements that will help:
descent_validators (from Container) - an iterable of Validators to run on the descent (first) phase.
validators (from Element) - an iterable of Validators to run on the ascent (second) phase.
To get my desired behavior (check account only if the username and password are valid), I need to add my validator to the validators class variable, and check that the child validators passed.
My code now looks something like this:
class CheckCredentials(Validator):
'''Checks credentials and creates an account object'''
def validate(self, element, state):
username = element['username']
password = element['password']
if not (username.all_valid and password.all_valid):
return false
self.account = Account(username.u, password.u)
return self.account.valid()
class doSomethingForm(Form):
'''Do an action with account credentials'''
username = String.using(default='',
validators=[Present(), UsernameFormat()])
password = String.using(default='',
validators=[Present(), PasswordFormat()])
action = String.using(default='',
validators=[Present(), ActionFormat()])
validators = (CheckCredentials(),)
#expose('/do_something')
def doSomething(request):
form = doSomethingForm.from_defaults()
form.set_flat(request.form)
result_html = ''
method = request.method
if method == 'POST' and not form.validate():
method = 'GET'
errors = []
if form.errors: errors.extend(form.errors)
for el in form.all_children:
if el.errors: errors.extend(el.errors)
result_html = errorsToHtml(errors)
if method == 'POST':
result_html = form.account.action(form['action'].u)
return render_template('do_something.html',
result=result_html, **form.value)
I think flatland can do some cooler things to help with generating the HTML forms, but I'm pretty happy with this so far.
Thanks to WoLpH and jek for their help.