I have a Django intranet which is reachable on http(s)://somename/ and http(s)://10.10.0.30/, using the ALLOWED_HOSTS setting:
ALLOWED_HOSTS = [u'10.10.0.30', u'somename',]
Now I'd like to allow certain users to login into the website remotely as well. As a first step I'll have to add my external URL (like somename.com) to the ALLOWED_HOSTS; no problem there. But from that moment on, everyone with an account will be able to log in, which is not what I want.
I was thinking in terms of having some group called PermitRemoteLogin - when a user is part of that group, logging in from host somename.com would be allowed. But I'm unsure about the actual implementation and/or whether this is doable in the first place (?).
When searching e.g. DjangoPackages, no results were found. Any idea whether this has been done before?
I've done similar things in the past, it's quite easy actually. You simply need to replace the normal authentication backend with your own: https://docs.djangoproject.com/en/1.8/topics/auth/customizing/#writing-an-authentication-backend
The default backend looks like this: https://github.com/django/django/blob/master/django/contrib/auth/backends.py#L113-143
class ModelBackend(object):
...
def authenticate(self, remote_user):
"""
The username passed as ``remote_user`` is considered trusted. This
method simply returns the ``User`` object with the given username,
creating a new ``User`` object if ``create_unknown_user`` is ``True``.
Returns None if ``create_unknown_user`` is ``False`` and a ``User``
object with the given username is not found in the database.
"""
if not remote_user:
return
user = None
username = self.clean_username(remote_user)
UserModel = get_user_model()
# Note that this could be accomplished in one try-except clause, but
# instead we use get_or_create when creating unknown users since it has
# built-in safeguards for multiple threads.
if self.create_unknown_user:
user, created = UserModel._default_manager.get_or_create(**{
UserModel.USERNAME_FIELD: username
})
if created:
user = self.configure_user(user)
else:
try:
user = UserModel._default_manager.get_by_natural_key(username)
except UserModel.DoesNotExist:
pass
return user
What you need to do is inherit this class and add the remote host check to it.
Something along the lines of this:
class HostnameAuthenticationBackend(backends.ModelBackend):
def authenticate(self, username=None, password=None,
hostname=None, **kwargs):
user = backends.ModelBackend.authenticate(
username=username, password=password, **kwargs)
if user:
# check the hostname and groups here
if hostname_correct:
return user
The one tiny snag you'll hit is that by default the hostname won't be available, you'll have to pass it along from the login view to the authentication backend.
If you want to allow users from outside of the intranet to access the page, but not to be able to login (except of those with special permissions), then I suggest overriding the default login view and check whether the user that is trying to log in has appropriate permissions.
Related
i have custom user model with email as unique identifier for auth instead of username. i want register a user by email or mobile number. if user enter email address, then register user by activation link, and if user enter phone number then register by SMS OTP.
something like instagram registration:
https://www.instagram.com/accounts/emailsignup/
i found this topic but the answer is not explained well.
A username is required for a User object. But, you can make that username their email too, so it's not a problem (at least in django 2.x, not sure about 1.x). You didn't describe what the app you're making was for, it's purpose, etc., so before you skip to the code, read the following warnings and thought process of why I am giving you a better option.
It's a very bad idea to force the person to have the username equal to their email, because in the future you might want to add some other functionality.
For example: Maybe you'd want to make a message board so people can talk to each other. But because of bad planning from the beginning, everyone would see each other by their email. Technically, you could asign them all a bunch of random usernames they didn't make up, but that's not a good idea cause they're less likely to remember it, and they might not like it. The only good use of a name like sPaRkLe_DaNcEr12 or Poothtaste, is for making people rage quit in video games.
So if you wanted a future ability for users to talk to each other, it would be better to only show their username to other users, but ALLOW people to log in with their email or phone number if they wanted to. This way, now they can login with their (username) or (email) or (phone_number), and they only have to remember one of those. I will show you how soon.
Some downsides of this: It makes more queries to your database, which can make it slower if you have tons of users, but that's your call. Personally, I say it's worth it because it's negligible, and easier for users, and it's them you should cater to. So pay for a faster server, or no??? Ultimately, you always ought to design around having as few queries as possible, while caching certain pages that are heavy on the database so it doesn't have to do the same thing X number of times.
Let's begin:
Remember that the following is an example of what I would do for django 2.x, with a better functionality than you're asking for. If you're using 1.x, just use url() instead of path(), and any other requirements.
Assume we have an app called accounts_app.
Also assume that we put path('accounts/', include('accounts_app.urls')), inside our project level urls.py.
I'm also going to assume you know how to use templates... Now, create a urls.py inside that app:
accounts_app/urls.py:
app_name = 'accounts_app'
urlpatterns = [
...
path('signup/', views.signup, name='signup'),
]
accounts_app/models.py:
The user attribute below enables you to extend the User model, so you can have their phone_number too. In this example, I allowed that be blank in case they don't want to give it. But if they did, you would have make a separate view for it. To make this whole thing much more simple, I'm not going to include that, nor tell you how to overwrite the whole User model. I'm only going to show you how to log in with an email with the regular User model. After that, doing so with a phone number shouldn't be hard at all. A reminder on what you will need is at the very end of this post.
class ExtendedUserExample(models.Model):
user = models.OneToOneField(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
created = models.DateTimeField(auto_now_add=True)
phone_number = models.IntegerField(blank=True)
settings.py.... NOTE THAT THIS IS IN THE PROJECT LEVEL FOLDER
The order of these backends matters. Always do ModelBackend first, or it will break.
AUTHENTICATION_BACKENDS = [
'django.contrib.auth.backends.ModelBackend',
'accounts_app.authentication.EmailAuthBackend', # to be able to login with email, described next
]
accounts_app/authentication.py:
This is an example to be able to login by their email. If you wanted someone to login by their phone number too, the principle is the same, but in this file, you would also need to import the ExtendedUserExample model above, add that to the above settings AUTHENTICATION_BACKENDS at the bottom of it, and make a new class for PhoneAuthBackend, that searches ExtendedUserExample for the phone_number. Again, to keep this more simple, I am not completely overwriting the User model, rather I only extend it, so if a user made an account and wanted to login by phone number later on, they would have to sign up with a username and email first, and once in, they could add a phone number (with this example, you will need another view for that).
So try this email example first until you get the hang of it. You also don't need to import this file anywhere else because the settings.py file takes care of it.
Here's what's happening: On your template for logging in, it will first search for the username field inside your User model, because your settings.py file has the AUTHENTICATION_BACKENDS variable to check the ModelBackend first.
But let's say a user entered their username as aaa#aaa.com. Now since you didn't allow anyone to sign up with an email as their username, when aaa#aaa.com is not found as a username in the User model, your settings file now says to go check the same User object a second time, but search their input by the email field / column instead. If their input exists in that column, authenticate() logs them in by their email if the password is right.
from django.contrib.auth.models import User
class EmailAuthBackend(object):
""" Authenticate using an email address """
def authenticate(self, request, username=None, password=None):
try:
user = User.objects.get(email=username) # gets the email by the 'username' they entered
if user.check_password(password):
return user
return None
except User.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
acounts_app/views.py:
I'll assume you know how to make django forms. If not, check here for model forms.
from .forms import ExtendedUserForm
def signup(request):
form = ExtendedUserForm(request.POST or None)
if request.method == 'POST':
if request.POST['password1'] == request.POST['password2']:
potential_user = request.POST.get('username', False).lower()
try:
user = User.objects.get(username=potential_user)
return render(request, 'accounts_app/signup.html', {'error': 'Username has already been taken. Please try another'})
except User.DoesNotExist:
if form.is_valid():
new_user = User.objects.create_user(username=potential_user, email=request.POST['email'], password=request.POST['password1'])
# backend argument required cause we are making the ability to LOGIN by email.
# Remember, I only extended the User model.
auth.login(request, new_user, backend='django.contrib.auth.backends.ModelBackend')
return redirect('some_app:some_view')
else:
return render(request, 'accounts_app/signup.html', {'error': "Password's must match."})
return render(request, 'accounts_app/signup.html', {'form': form})
Now that your login by email should be working, it's not that hard to follow these principles to create the same ability for logging in by phone. If you continue with this example, you will need to make a new view to save a phone number to ExtendedUserExample.phone_number. After that, add another line at the bottom of AUTHENTICATION_BACKENDS, write a new class in authentication.py, and you'd be set... So long as you have <input type="text" name="username" required> when they use your login view.
I have implemented login form for username/password method, and that works perfect.
I want user to be also able to login using their social accounts.
I am using django-allauth to map social users to django-users.
Now I want to allow only those social accounts to login, that are mapped to django-users and not everyone.
Is there a way to override callback view? or something else can be done?
To simply disable registration, you have to overwrite the default account adaptor. If you also want to support social login, you also need to overwrite the default soculaaccount adapter. Add the following code somewhere in one of your apps (e.g. adapter.py):
from allauth.account.adapter import DefaultAccountAdapter
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
from allauth.exceptions import ImmediateHttpResponse
class NoNewUsersAccountAdapter(DefaultAccountAdapter):
def is_open_for_signup(self, request):
return False
class SocialAccountWhitelist(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
u = sociallogin.user
print('User {0} is trying to login'.format(u.email))
# Write code here to check your whitelist
if not_in_your_list(u):
raise ImmediateHttpResponse(HttpResponseRedirect('/account/login'))
and then add the following to your settings:
ACCOUNT_ADAPTER = 'path_to_app.adapter.NoNewUsersAccountAdapter'
SOCIALACCOUNT_ADAPTER = 'path_to_app.adapters.SocialAccountWhitelist'
After that, all you need to do is manually create an Account from the Admin pages, and manually create an EmailAddress. For the social login, you will need to write code to somehow check if the email is allowed
I would recommend you add a Staff-Only form to make this easy on you, where you can ask for username, email (and even password) and then do
new_user = Account.objects.create_user(email=email, username=username, password=password)
EmailAddress.objects.create(email=email, user=new_user, verified=True, primary=True)
You can also develop an Invitation scheme, but that is a lot more complicated but quickly googled and found the following project, which I have not personally used, but looks like what you need:
https://github.com/bee-keeper/django-invitations
Finally After reading the documents thoroughly and doing a lot of trials and errors I got to what I was looking for.
I had to set following parameters as a part of configuration specified in docs.
ACCOUNT_EMAIL_REQUIRED (=False)
The user is required to hand over an e-mail address when signing up.
and
SOCIALACCOUNT_QUERY_EMAIL (=ACCOUNT_EMAIL_REQUIRED)
Request e-mail address from 3rd party account provider? E.g. using OpenID AX, or the Facebook “email” permission.
I had to set ACCOUNT_EMAIL_REQUIRED = True as it was required to check if that email id is already registerd with us.
and then finally I overridden pre_social_login like below.
from allauth.socialaccount.adapter import DefaultSocialAccountAdapter
class NoNewSocialLogin(DefaultSocialAccountAdapter):
def pre_social_login(self, request, sociallogin):
try:
cr_user = auth_user.objects.get(email=sociallogin.user.email)
if cr_user and cr_user.is_active:
user_login = login(request, cr_user, 'django.contrib.auth.backends.ModelBackend')
raise ImmediateHttpResponse(HttpResponseRedirect(reverse('protect')))
else:
raise ImmediateHttpResponse(render_to_response("account/authentication_error.html"))
except ObjectDoesNotExist as e:
raise ImmediateHttpResponse(render_to_response("socialaccount/authentication_error.html"))
except Exception as e:
raise ImmediateHttpResponse(HttpResponseRedirect(reverse('protect')))
I am creating a mobile app where I need to use authentication. How can I achieve the following:
I need to create a user. After creating the user it needs to send Api_client and a secret as a response to the user.
I have a function to perform verification. After creating the user it needs to call the function for mobile verification.
Importantly, how can I stop a user who uses a for loop and starts adding users?
I tried this:
models.signals.post_save.connect(create_api_key, sender=User)
That created an API key but is not sending it as a response when creating the user is successful.
Here's what I understand from your question :
You want any user of your mobile app to register himself,
anonymously, as a user to your Django application.
This request must trigger a Tastypie api_key creation, and then return it.
You want to prevent this request from being spammed.
I don't understand this :
"I have a function for mobile without verification. After creating the user it needs to call the function for mobile verification."
To answer the points I get :
See this SO question regarding user registration with Tastypie How to create or register User using django-tastypie API programmatically?, notably this part :
def obj_create(self, bundle, request=None, **kwargs):
username, password = bundle.data['username'], bundle.data['password']
try:
bundle.obj = User.objects.create_user(username, '', password)
except IntegrityError:
raise BadRequest('That username already exists')
return bundle
For a complete walkthrough, check this article : http://psjinx.com/programming/2013/06/07/so-you-want-to-create-users-using-djangotastypie/
You're on the right track regarding the api_key creation, except you have to tell the api to actually send it back. You can use the regular way (it requires another request, though) :
i.e make it accessible from UserResource, as described in the article linked above, specifically :
def dehydrate(self, bundle):
bundle.data['key'] = bundle.obj.api_key.key
try:
# Don't return `raw_password` in response.
del bundle.data["raw_password"]
except KeyError:
pass
return bundle
If you want to send it right after a User's registration, don't forget to set "always_return_data" to True and add the api_key to the response.
Spam / loop registration :
You should look into your server's capabilities regarding this matter. For example, assuming you're using Nginx : http://wiki.nginx.org/NginxHttpLimitReqModule
Another option might be to use this : http://django-ratelimit-backend.readthedocs.org/en/latest/
Hope this helps !
Regards,
I am trying to make a website, where people only put their email addresses and they are logged in with cookies and all. At a later stage, i will ask them provide password and names, but NO username will be used. I am trying to do this with django-registraition, but i get errors and i have a few problems.
First to disable usernames as a login feature, i put str(time()) instead of username - i was looking for something that will change every time.
However, when I skip the authentication (which i currently don't need) i get error:
'RegistrationProfile' object has no attribute 'backend'
Alternatively, i can leave the authentication but then i don't know how to authenticate it only with email and no password. Also, i don't know how to make the next line work:
auth.login(request, ProfileUser)
If anyone can get me out of here, it would be awesome. Here is some code:
my form Class:
class RegistrationFormCustom(forms.Form):
email = forms.EmailField()
def do_save(self):
new_u = User(username=str(time()),email= self.cleaned_data.get('email'),)
new_u.save()
new_p = Profile.objects.create_profile(new_u)
new_p.save()
return new_p
my view:
def registerCustom(request, backend, success_url=None, form_class=None,
disallowed_url='registration_disallowed',
template_name='registration/registration_form.html',
extra_context=None,
initial={}):
form = RegistrationFormCustom(initial=initial)
if request.method == 'POST':
form = RegistrationFormCustom(initial=initial, data=request.POST)
if form.is_valid():
profile = form.do_save()
profile = auth.authenticate(username = profile.user.email, password = form.cleaned_data.get('pass1'))
print(profile)
auth.login(request, profile)
return redirect('/')
else:
pass
return render_jinja(request, 'registration/registration_form.html',
type="register",
form = form
)
and i will post any other snipped required happily
You're getting the 'RegistrationProfile' object has no attribute 'backend' error because the user is not yet authenticated. To log someone in, you have to call the authenticate method first, which requires a password. So, what you can do instead, is this:
from django.contrib.auth import load_backend, login, logout
from django.conf import settings
def _login_user(request, user):
"""
Log in a user without requiring credentials (using ``login`` from
``django.contrib.auth``, first finding a matching backend).
"""
if not hasattr(user, 'backend'):
for backend in settings.AUTHENTICATION_BACKENDS:
if user == load_backend(backend).get_user(user.pk):
user.backend = backend
break
if hasattr(user, 'backend'):
return login(request, user)
Then, to log someone in, just call the _login_user function with the request and User model. (This will be profile.user in your case, probably) Do this instead of calling auth.login. I'm not sure on how you're going to determine whether this is a valid user or not, without a password or username, but I'll leave that to you. If you still have trouble, let me know.
Short Explanation:
What basically happens here is that Django requires a user to be authenticated in order to be logged in via the login function. That authentication is usually done by the authenticate function, which requires a username and password, and checks whether the supplied password matches the hashed version in the database. If it does, it adds an authentication backend to the User model.
So, since you don't have a password and username, you just have to write your own method for adding the authentication backend to the User model. And that's what my _login_user) function does - if the user is already authenticated, it just calls login, otherwise, it first adds the default backend to the User model, without checking for a correct username and password (like authenticate does).
For others reading this thread, I got a similar error message when I was using User.objects.create() instead of User.objects.create_user(). Basically, the first method was setting a clear password whereas create_user encrypts the password. Clear passwords will fail to authenticate. Check your database, if you have passwords set in the clear, then it's likely you need to use create_user() instead.
The author's request could be fixed by simply setting a default user and password using create_user() instead of just user.save().
You can create a known password (put it in settings.py ) and use that as though the user entered it. Create the user with this and authenticate the user with this.
I'm trying to set up a custom backend that queries another database, for which I have created a model in the system. It uses its own rules (email instead of username, and a differently salted/hashed password) so I can't use built in authentication. I've set up a custom authentication backend like so:
class BlahBlahBackend:
def check_password():
# check password code here
return true
def authenticate(self, email=None, password=None):
import myapp.models.loginmodel
try:
person = myapp.models.loginmodel.People.objects.get(email=email)
if check_password(password, person.password):
try:
user = User.objects.get(email=email)
except User.DoesNotExist:
username=person.first_name + person.last_name
name_count = User.objects.filter(username__startswith = username).count()
if name_count:
username = '%s%s'%(username, name_count + 1)
user = User.objects.create_user(username,email)
else:
user = User.objects.create_user(username,email)
except People.DoesNotExist:
return None
def get_user(self, user_id):
try:
return User.objects.get(pk=user_id)
except User.DoesNotExist:
return None
I've added BlahBlahBackend as an authentication backend:
AUTHENTICATION_BACKENDS = ('django.contrib.auth.backends.ModelBackend',
'socialauth.auth_backends.OpenIdBackend',
'socialauth.auth_backends.TwitterBackend',
'socialauth.auth_backends.FacebookBackend',
'socialauth.auth_backends.BlahBlahBackend',
)
As you can see, I'm also using some pre-existing auth backends that are also in socialauth.
I have a submission form that points to the following view:
def blahblah_login_complete(request):
email = request.POST.get('email')
password = request.POST.get('password')
user = authenticate(email,password)
# if user is authenticated then login user
if user:
login(request, user)
else:
return HttpResponseRedirect(reverse('socialauth_login_page'))
However, when I try to login in this way, it seems like one or more of the other backends are acting as if I'm trying to log in using their method.
I read that backends are cached and so ran
Session.objects.all().delete()
to clear out the backends cache.
My main questions are:
Does the order in which items are listed in AUTHENTICATION_BACKENDS
How does the system decide/know which Backend to use? This was never made clear by any of the documentation, and I find it a bit confusing.
Is there any way to force the use of a specific authorization based on the request. In other words, if someone submits a form, is there a way to force them to use the form-login-based authentication as opposed to the login via openid or Twitter?
Update:
It works! This is very cool, thanks. I guess it just seemed like the django doc was saying "You don't have to do anything else, it just sort of works like magic" and it turns out this is absolutely the case. So long as the backend is there and the credentials are set up correctly, the authentication will work. As it turns out the real problem was a misconfiguration in the urls.py file that wasn't sending the post from the login form to the correct handler, which is why it kept trying to use another authentication method.
You're supposed to use keyword arguments to django.contrib.auth.authenticate() The names should match the names of the arguments in your backend's authenticate method. The default backend handles the names 'username' & 'password'.
Your backend can use a different name for the keyword arguments e.g.: blahblah_email and blahblah_password, and then call authenticate(blahblah_email=..., blahblah_password=...).
It's clearly described here -
django tries each backend in order
defined, if first fails to
authenticate it goes to second etc.
I believe you can load backend class dynamically and authenticate
directly through it. Look at django authenticate() function sources on how to do that.
I guess django-cas will be a good reference for you :)
And yes, the order of AUTHENTICATION_BACKENDS matters.
Django loops over the backends list and stop at the first backend that has a authenticate method accepting the credential parameters you passed to it.