Authenticate all urls except whitelist - django

Currently I wrote a method which authenticates a user using my company list of groups by getting the user using
request.meta['REMOTE_USER']
and comparing it to list of users in the group
My question is that how would I tie this in to my Django application such that it would check the users only for all urls except for a whitelist. I was thinking of calling this method in my views.py for the urls I needed but that seems to be maintenance nightmare

You could implement a middleware that would check a user against the url except the whitelist:
class MyAuthMiddleware(object):
def process_request(self, request):
if request.path in self.whitelist:
return
# Do the user checking otherwise

Related

Routing user profiles at the URL hierarchy root

Facebook, GitHub, and Twitter all place user profile URLs at the root of their URL hierarchies, e.g., http://twitter.com/jack.
This must be done so that other "system" URLs, like http://twitter.com/search are resolved first, so a user named #search can't hijack part of the site.
And if no system URL exists, and no such user profile is found, they must throw a 404.
What's the best way to achieve this using Django's URL routing? My current solution is:
urlpatterns = [
url(r'^admin/', admin.site.urls),
# etc, ..., then this last:
url(r'^(?P<username>.+)$', views.view_profile),
]
def view_profile(request, username):
try:
user = User.objects.get(username=username)
except User.DoesNotExist:
raise Http404('User does not exist')
return HttpResponse(username + ' exists!')
In this way, the view_profile view is a catch-all for any URL that isn't handled elsewhere. However, it doesn't seem ideal that it is responsible for throwing a 404 exception if no user exists, I'd rather raise some "wrong route" signal that tells Django's URL router to resume attempting to route the request. This way the 404 is generated by Django the same way as if I did not have the catch-all route.
This question asks essentially the same thing, but the solutions involved creating a subpath for the user profile.
I would actually recommend a more RESTfull way of addressing the users profile. A nice organisation or a users profile rout would be /users/{pk}/ or if you want it easier for the client (we are doing it like that) /users/me/ so the client doesn't need to rememer it's id and the user is choosen from the session or auth/bearer token or whatever authorisation system you prefer.

how can I securely perform Rest requests without authentication?

This is more a process logic question than a specific language-framework one.
I am developing a mobile app and want the user to be able to use it without having to login (i.e. try it and offer a plus to the logged users), but I don´t want other persons to make post requests from let´s say Postman or any other platform than the app without having some sort of key, so what would be the approach here?
I am thinking on basic auth with some secret username:password for guests, or some kind of token, but as I am totally new on this I am not sure if it´s the correct approach, I´ve read the authentication and permissions Django Rest Framework tutorial but haven´t found a solution
I am learning Django myself and have gotten to the more advanced topics in the subject. What you could do is create a function in your permissions.py file for this. like so:
from rest_framework import permissions
class specialMobileUserPermissions(permissions.BasePermission):
def has_object_permission(self, request, view, obj):
if request.method in request.SAFE_METHODS:
return True
if request.user.id == whatever your mobile users id is:
return false
return obj.id == request.user.id # if the user is a subscribed user and they are logged in return true
return false # because we need a way out if none of the above works
So when dealing with permissions classes the permissions.SAFE_PERMISSIONS is a list of permissions that are non-destructive. So the first if statement asks are you a GET, HEAD, or other non data altering method. If so return true.
The second if statement checks the user id of the user that is making the request. And if that user id is equal to the user id you set for the mobile trail user it would return false, denying permissions to whatever this class is used on.
In your viewset you would need to add the permissions_classes variable like below
from . import permissions # your permissions.py file
class FooViewSet(viewsets.ViewSet):
permission_classes = (permissions.specialMobileUserPermissions,)
Unless you need extra functionality, that should be everything you need, all the way down to the imports. I hope I have helped.

django-rest-framework authentication: require key parameter in URL?

I am working in Django 1.8 with the excellent django-rest-framework. I have a public RESTful API.
I would now like to start requiring a key GET parameter with this API, and disallowing any requests that do not have this parameter. I will allocate keys to users manually on request.
I have read through the DRF Authentication documentation, but I'm not sure there's anything that meets my use case. I find this strange, since my use case must be very common.
Token-based authentication requires the user to set an HTTP header. My typical API user is not sophisticated (Excel users who will be downloading CSVs), so I don't think I can ask them to do this.
I think Basic-Auth is what I need, but I'd much rather provide a simple URL-based key than a Django username and password (my app has no concept of users right now).
What is the best way to implement this?
Create a table which will contain all the keys that you issue to someone.
Example:
class RestApiKey(models.Model):
api_key = models.CharField(max_length=100)
Next create a custom Permision class which will check for the api Key in the url before forwarding the request to the view like:
from rest_framework import permissions
from yourappname.models import RestApiKey
class OnlyAPIPermission(permissions.BasePermission):
def has_permission(self, request, view):
try:
api_key = request.QUERY_PARAMS.get('apikey', False)
RestApiKey.objects.get(api_key=api_key)
return True
except:
return False
So the request url has to be like http://yourdomain.com/?apikey=sgvwregwrgwg
Next in your views add the permission class:
class YourViewSet(generics.ListAPIView):
permission_classes = (OnlyAPIPermission,)
or if you are using function based views then do like:
#permission_classes((OnlyAPIPermission, ))
def example_view(request, format=None):
. . .

Allowing login depending on hostname (remote)

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.

Looking for a comprehensive guide to setting up custom authentication backends in Django, or pointers

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.