I would like to create a profile page for every account created. Once created the user profile can be accessed like
http://example.com/username
But before creation I should validate that the url is already available and not taken by existing url patterns. For ex: There will be a page like
http://example.com/about
Now "about" is not a valid user. But it is a valid url pattern. I should be able to prevent a user creation with a name 'about'. For that I need to check the url patterns in addition to check whether a user with that name already exists. How to do this?
An easy way out would be to have a url pattern like the following for the profile page:
http://example.com/user/username
But I've a strong requirement to have profile page like the following
http://example.com/username
You can simply try to resolve the address to a view:
from django.core.urlresolvers import resolve
from myapp.views import user_profile_view
try:
my_view = resolve("/%s/" % user_name)
if my_view == user_profile_view:
# We match the user_profile_view, so that's OK.
else:
# oops, we have another view that is mapped on that URL
# you already have something mapped on this address
except:
# app doesn't have such path
EDIT:
you can also make the check in a different way:
def user_profile_view(request, user_name):
# some code here
user_profile_view.name = "User Profile View"
and then the check above could be:
if getattr(my_view, "name", None) == "User Profile View":
...
you can add custom form field validation. Look at this post.
Django and Custom Form validation
raise forms.ValidationError(u'please choose another username')
check and raise errors.
Or you can try setting the following as url for your users,
example.com/user/<username>/
edit 1 :
you can use this as a list of all invalid usernames
I don't think you can check this with django.core.urlresolvers.resolve. Note that it just checks for the pattern for fixed url and not variable part of your url. In your case, you will most likely have 'username' as variable parameter that is passed to view. Pattern for this will match for non-existing username also. So the checking patterns is not good solution.
Better method will be separating out static or other pages in different namespace. e.g. http://example.com/static/about
Or you can have predefined keywords/reserved words for your site e.g. [ "about", ... ] and check it against username while creating user.
Put your username view at the end of urls.py, so that other url rules will be checked first.
Then the easiest way is to have a list of invalid user names which should be used in user registration validation.
def clean_username(self):
INVALID_USERNAMES = ('about', 'admin', 'register', '...')
username = self.cleaned_data['username']
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
pass
else:
raise forms.ValidationError(u'%s already exists' % username )
if username in INVALID_USERNAMES:
raise forms.ValidationError(u'%s is not a valid username' % username )
return username
firstly, a url rule for usernames:
url(r'^(?P<username>[-\w]+)/$', 'membership.views.profile', name='profile'),
making sure that a username doesn't conflict with an existing url rule is a little harder.
the way I usually handle this is by adding uniqueness to the url:
url(r'^user/(?P<username>[-\w]+)/$', 'membership.views.profile', name='profile'),
if you absolutely must have the url for profiles start with the username then you can try to rake the urls using a method like this one: https://stackoverflow.com/a/2094270/884453 and then make sure that username is both unique against other usernames and against routes
EDIT
as i was writing this someone posted a cool idea for a validator that makes a bunch more sense.
using from django.core.urlresolvers import resolve to check it for uniqueness is a great solution
from django.core.exceptions import ValidationError
from django.core.urlresolvers import resolve
def validate_unique_resolve(value):
urlroute = True
try:
urlroute = resolve(path, urlconf)
except urlresolvers.Resolver404:
urlroute = False
if urlroute != False :
raise ValidationError(u'%s is a reserved url pattern' % value)
Related
I'm using Django 1.8.4 on Python 3, and attempting to create an auth backend which validates a cookie from a legacy ColdFusion web site and create / log the Django user in after checking the value in a database. In settings, I am including the backend:
AUTHENTICATION_BACKENDS = (
'site_classroom.cf_auth_backend.ColdFusionBackend',
)
And the code for the backend itself; SiteCFUser is a model against the SQL Server database user model which contains the active cookie token value:
from django.contrib.auth.backends import ModelBackend
from django.contrib.auth import get_user_model
from users.models import SiteCFUser
class ColdFusionBackend(ModelBackend):
"""
Authenticates and logs in a Django user if they have a valid ColdFusion created cookie.
ColdFusion sets a cookie called "site_web_auth"
Example cookie: authenticated#site+username+domain+8E375588B1AAA9A13BE03E401A02BC46
We verify this cookie in the MS SQL database 'site', table site_users, column user_last_cookie_token
"""
def authenticate(self, request):
User = get_user_model()
print('Hello!')
token=request.COOKIES.get('site_web_auth', None)
print('Token: ' + token)
cookie_bites = token.split('+')
if cookie_bites[0] != "authenticated#site":
# Reality check: not a valid site auth cookie
return None
username = cookie_bites[1]
cf_token = cookie_bites[3]
try:
site_user = SiteCFUser.objects.using('mssqlsite').filter(cf_username=username)
except:
# No user found; redirect to login page
return None
if site_user[0].cftoken == cf_token:
try:
# Does the user exist in Django?
user = User.objects.get(username=username)
except:
# User does not exist, has a valid cookie, create the User.
user = User(username=username)
user.first_name = site_user[0].cf_first_name
user.last_name = site_user[0].cf_last_name
user.email = site_user[0].cf_email
user.save()
else:
return None
def get_user(self, user_id):
User = get_user_model()
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
The problem is, the backend doesn't seem to be called when hitting a URL with a view with #login_required, or even trying to log in through a form with username and password. If I force an error by changing the name of the class in settings, or change the name of the class in cf_auth_backend.py, I do get an error. However, none of the print statements show up in the console. I'm clearly missing something here: any idea what I'm not doing right?
While the accepted answer might have helped the OP, it's not a general answer to the question's title.
Authentication back ends do work simply by listing them in AUTHENTICATION_BACKENDS. But they may appear to be ignored
for various reasons, e.g.:
urls.py needs to point to something like django.contrib.auth.views.login
url(r'^accounts/login/$', django.contrib.auth.views.login)
if it's pointing to some other authentication app. AUTHENTICATION_BACKENDS
may not work.
the authenticate() method must accept a password keyword, either through
password=None or **kwargs. Probably true for username too. It won't
be called if it doesn't accept that keyword argument.
Authentication backends doesn't work that way. They won't be called on each request or on requests where authentication is required.
If you want to log in user based on some cookie, you should call authentication in middleware.
I'm currently using Python / App Engine / SimpleAuth to provide OAuth login to my application. The current workflow is that users login with OAuth, and they can later create a unique username for themselves in the app.
I'm having problems creating the unique username after the webapp2 User entity has already been created. I see that in the webapp2 model there is a way to enable a unique username within the application Entity group, but I don't know how to set it for myself. (I'm using SimpleAuth to set everything for other OAuth providers.)
I want to check to see if the user-submitted 'username' exists, and if it doesn't to add it as a property to the currently logged-in user. I'd appreciate any help/pointers on this!
I think you could extend webapp2_extras.appengine.auth.models.User and add username property, e.g.
from webapp2_extras.appengine.auth.models import User as Webapp2User
class User(Webapp2User):
username = ndb.StringProperty(required=True)
Then, to create a webapp2 app you'd need a config which includes this:
APP_CFG = {
'webapp2_extras.auth': {
'user_model': User, # default is webapp2_extras.appengine.auth.models.User
'user_attributes': ['username'] # list of User model properties
}
}
app = webapp2.WSGIApplication(config=APP_CFG)
Havign the above, creating a new user using the following code will ensure username is unique (ensured by Unique model):
auth_id = 'some-auth-id' # e.g. 'google:123456789', see simpleauth example.
ok, props = User.create_user(auth_id, unique_properties=['username'],
username='some-username',
...)
if not ok:
# props list will contain 'username', indicating that
# another entity with the same username already exists
...
Problem is, with this configuration you are bound to set username during the creation time.
If you wanted to make username optional, or let users set/change it later on, you would probably want to change the above code to something like this:
class User(Webapp2User):
username = ndb.StringProperty() # note, there's no required=True
# when creating a new user:
auth_id = 'some-auth-id' # e.g. 'google:123456789', see simpleauth example.
ok, props = User.create_user(auth_id, unique_properties=[], ...)
Basically, unique_properties will be empty list (or you can just skip it). Also, you could temporarily assign username property to something like user.key.id() until the user decides to change their username to something more meaningful. Take, for instance, Google+ profile links: mine is currently https://plus.google.com/114517983826182834234, but if they let me change it, I would try something like https://plus.google.com/+IamNotANumberAnymore
Then, in a "change/set username" form handler, you could check if a username already exists and update User entity (if it doesn't):
def handle_change_username(self):
user = ... # get the user who wants to change their username
username = self.request.get('username')
uniq = 'User.username:%s' % username
ok = User.unique_model.create(uniq)
if ok:
user.username = username
user.put()
else:
# notify them that this username
# is already taken
...
User.unique_model.create(uniq) will create a Unique entity with the given value if it didn't exist. In this case ok will be True. Otherwise, ok will be False which indicates that an entity with that value (a unique username in this case) already exists.
Also, you might want to put User.unique_model.create() and user.put() in the same transaction (it'll be XG because they are in different entity groups).
Hope this helps!
I want to add user in a Django app. here's my code, it fires an exception in this line
user = User.objects.create(userName, userMail, userPass)
Here's the whole code:
def createUser(request):
userName = request.REQUEST.get('username', None)
userPass = request.REQUEST.get('password', None)
userMail = request.REQUEST.get('email', None)
# TODO: check if already existed
user = User.objects.create(userName, userMail, userPass)
user.save()
return render_to_response('home.html', context_instance=RequestContext(request))
Any help?
Use the create_user helper function instead of the create method. It takes care of hashing the password for you, amongst other things.
user = User.objects.create_user(userName, userMail, userPass)
As an aside, fetching the values out of the request data dictionary isn't best practice. It's a good idea to learn about django forms and model forms, which will validate input data for you. Once you understand that, there's a UserCreationForm included with the django auth app which you could use.
When using the standard authentication module in django, a failed user authentication is ambiguous. Namely, there seems to be no way of distinguishing between the following 2 scenarios:
Username was valid, password was invalid
Username was invalid
I am thinking that I would like to display the appropriate messages to the user in these 2 cases, rather than a single "username or password was invalid...".
Anyone have any experience with simple ways to do this. The crux of the matter seems to go right to the lowest level - in the django.contrib.auth.backends.ModelBackend class. The authenticate() method of this class, which takes the username and password as arguments, simply returns the User object, if authentication was successful, or None, if authentication failed. Given that this code is at the lowest level (well, lowest level that is above the database code), bypassing it seems like a lot of code is being thrown away.
Is the best way simply to implement a new authentication backend and add it to the AUTHENTICATION_BACKENDS setting? A backend could be implemented that returns a (User, Bool) tuple, where the User object is only None if the username did not exist and the Bool is only True if the password was correct. This, however, would break the contract that the backend has with the django.contrib.auth.authenticate() method (which is documented to return the User object on successful authentication and None otherwise).
Maybe, this is all a worry over nothing? Regardless of whether the username or password was incorrect, the user is probably going to have to head on over to the "Lost password" page anyway, so maybe this is all academic. I just can't help feeling, though...
EDIT:
A comment regarding the answer that I have selected:
The answer I have selected is the way to implement this feature. There is another answer, below, that discusses the potential security implications of doing this, which I also considered as the nominated answer. However, the answer I have nominated explains how this feature could be implemented. The security based answer discusses whether one should implement this feature which is, really, a different question.
You really don't want to distinguish between these two cases. Otherwise, you are giving a potential hacker a clue as to whether or not a username is valid - a significant help towards gaining a fraudulent login.
This is not a function of the backend simply the authentication form. Just rewrite the form to display the errors you want for each field. Write a login view that use your new form and make that the default login url. (Actually I just saw in a recent commit of Django you can now pass a custom form to the login view, so this is even easier to accomplish). This should take about 5 minutes of effort. Everything you need is in django.contrib.auth.
To clarify here is the current form:
class AuthenticationForm(forms.Form):
"""
Base class for authenticating users. Extend this to get a form that accepts
username/password logins.
"""
username = forms.CharField(label=_("Username"), max_length=30)
password = forms.CharField(label=_("Password"), widget=forms.PasswordInput)
def __init__(self, request=None, *args, **kwargs):
"""
If request is passed in, the form will validate that cookies are
enabled. Note that the request (a HttpRequest object) must have set a
cookie with the key TEST_COOKIE_NAME and value TEST_COOKIE_VALUE before
running this validation.
"""
self.request = request
self.user_cache = None
super(AuthenticationForm, self).__init__(*args, **kwargs)
def clean(self):
username = self.cleaned_data.get('username')
password = self.cleaned_data.get('password')
if username and password:
self.user_cache = authenticate(username=username, password=password)
if self.user_cache is None:
raise forms.ValidationError(_("Please enter a correct username and password. Note that both fields are case-sensitive."))
elif not self.user_cache.is_active:
raise forms.ValidationError(_("This account is inactive."))
# TODO: determine whether this should move to its own method.
if self.request:
if not self.request.session.test_cookie_worked():
raise forms.ValidationError(_("Your Web browser doesn't appear to have cookies enabled. Cookies are required for logging in."))
return self.cleaned_data
def get_user_id(self):
if self.user_cache:
return self.user_cache.id
return None
def get_user(self):
return self.user_cache
Add:
def clean_username(self):
username = self.cleaned_data['username']
try:
User.objects.get(username=username)
except User.DoesNotExist:
raise forms.ValidationError("The username you have entered does not exist.")
return username
We had to deal with this on a site that used an external membership subscription service. Basically you do
from django.contrib.auth.models import User
try:
user = User.objects.get(username=whatever)
# if you get here the username exists and you can do a normal authentication
except:
pass # no such username
In our case, if the username didn't exist, then we had to go check an HTPASSWD file that was updated by a Perl script from the external site. If the name existed in the file then we would create the user, set the password, and then do the auth.
This answer is not specific to Django, but this is the pseudo-code I would use to accomplish this:
//Query if user exists who's username=<username> and password=<password>
//If true
//successful login!
//If false
//Query if user exists who's username=<username>
//If true
//This means the user typed in the wrong password
//If false
//This means the user typed in the wrong username
def clean_username(self):
"""
Verifies that the username is available.
"""
username = self.cleaned_data["username"]
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
return username
else:
raise forms.ValidationError(u"""\
This username is already registered,
please choose another one.\
""")
I'm trying to validate that a submitted URL doesn't already exist in the database.
The relevant parts of the Form class look like this:
from django.contrib.sites.models import Site
class SignUpForm(forms.Form):
# ... Other fields ...
url = forms.URLField(label='URL for new site, eg: example.com')
def clean_url(self):
url = self.cleaned_data['url']
try:
a = Site.objects.get(domain=url)
except Site.DoesNotExist:
return url
else:
raise forms.ValidationError("That URL is already in the database. Please submit a unique URL.")
def clean(self):
# Other form cleaning stuff. I don't *think* this is causing the grief
The problem is, regardless of what value I submit, I can't raise the ValidationError. And if I do something like this in the clean_url() method:
if Site.objects.get(domain=url):
raise forms.ValidationError("That URL is already in the database. Please submit a unique URL.")
then I get a DoesNotExist error, even for URLs that already exist in the Database. Any ideas?
django channel in IRC saved me here. The problem was that the URLField.clean() does two things I wasn't expecting:
If no URL scheme is present (eg, http://) the method prepends 'http://' to the url
the method also appends a trailing slash.
The results are returned and stored in the form's cleaned_data. So I was checking cleaned_data['url'] expecting something like example.com and actually getting http://example.com/. Suffice to say, changing my clean_url() method to the following works:
def clean_url(self):
url = self.cleaned_data['url']
bits = urlparse(url)
dom = bits[1]
try:
site=Site.objects.get(domain__iexact=dom)
except Site.DoesNotExist:
return dom
raise forms.ValidationError(u'That domain is already taken. Please choose another')
I do it this way. It's slightly simpler.
try:
a = Site.objects.get(domain=url)
raise forms.ValidationError("That URL is already in the database. Please submit a unique URL.")
except Site.DoesNotExist:
pass
return url
I think, you can return '' and fill _errors.
msg = u"That URL is already in the database. Please submit a unique URL."
self._errors["url"]=ErrorList([msg])
return ''
or
from django.contrib.sites.models import Site
class SignUpForm(forms.Form):
# ... Other fields ...
url = forms.URLField(label='URL for new site, eg: example.com')
def clean_url(self):
url = self.cleaned_data['url']
try:
a = Site.objects.get(domain=url)
raise forms.ValidationError("That URL is already in the database. Please submit a unique URL.")
except Site.DoesNotExist:
return url
return ''
def clean(self):
# Other form cleaning stuff. I don't *think* this is causing the grief
Well I logged in cause I found this via Google with a similar issue and wanted to add a comment to Carl Meyers post noting that using self._errors is totally valid as per the Django docs:
http://docs.djangoproject.com/en/1.2/ref/forms/validation/#cleaning-and-validating-fields-that-depend-on-each-other