I have a server which feeds several applications.
Please imagine that I'm a user registered in 2 or more of those applications and I use one different login info when accessing each one.
Now, as that user, I use the same browser with different tabs to access those applications...
Logging the first time (for the 1rst application) everything goes as intended, but when I access the 2nd application (as the second user), that request will access the same request.session object. When I invoke the login (from the auth framework) the current user will be compared with the user in the actual request.session (request.session[SESSION_KEY] != user.id) and request.session.flush() will be called.
This means that I will loose all of the request.session content for the user that accessed the 1st application, and that same user request.session will be "marked" to be the second user request.session from that point.
What I wanted, in this situation, was to have a function/method that permits to create a new request.session for the second user, leaving the original as it is.
Edited after the first answer:
First of all, thank you for the answer.
I was trying not to detail too much in order to avoid too oriented answers, but now I think I should do it:
Ok, I've called "it" applications before, but in truth, my project serves requests in order to offer the same final "product" (a game, for instance).
(I have several django applications inside my project. Each one with specific orientations and backend's depending of business considerations applied.)
It would be more detailed if I told that I have different entry points URL's, use the correct business backend in order to process the request and retrieve a game.
My main URL is the same (namespace) and my project has only one settings file.
There may be several answers to your question depending on whether you are ready to change the "use case" or not:
a) You can't change the use case: it's not possible because one Django session is bound to a browser session be it several browser windows instance or tabs.
b) You can change the use case: The user can still achieve this using several browsers (or profiles (or private browsing mode in chrome/chromium)) without any modification to your code.
c) You can implement an "user"-switch feature in your website that allow user to have several active profile in the same session in different windows, it's similar in purpose to github organization-switch feature or facebook page/organization-switch but you can have several user's profiles in several tabs which is not the case on github or facebook.
To achieve c) you need to have a "SubProfile" model instances attached to your "User" model, and activate the right SubProfile on each incoming request based on query string parameters and persist subprofile information across requests.
1) I guess you already have something like that Subprofile a model with a foreign key to django.contrib.auth.models.User, you may also have a view that allow an user to change its subprofile. In order to make subprofile-switch work, it needs to persist the information in the current tab session which subprofile it is using, for that it needs to add a parameter in the query string because it's the only place bound the tab and not the user-session. For instance "subprofile=123". You need to properly validate the subprofile with a form et al., the view looks like this:
def select_subprofile(request):
if request.method == 'POST':
form = SubProfileSelectForm(request)
if form.is_valid():
subprofile = form.cleaned_data['subprofile']
url = '%s?subprofile' % (reverse('homepage'), subprofile)
return redirect(url) # the redirect is something like '/homepage?subprofile=123'
else:
form = SubProfileSelectForm()
return render(request, 'myapp/subprofile_select.html', {'form':form})
This view can be the first page of each game.
2) After that, you need to retrieve the subprofile of the user for the current tab.
For this matter we will use the query string in a middleware (look for howtos on SO and example middlewares bundled with Django if you don't know what it is) can be used to attach current SubProfile instance to request.user. The middleware will for each incoming request attach the SubProfile instance corresponding to the current subprofile information found in query string to current user object, the middleware looks like this:
class SubProfileMiddleware(object):
def process_request(self, request):
subprofile = request.GET.get('subprofile', None)
if subprofile:
# it's important to check for user here or malicious users will be
# able to use Subprofiles of other users
subprofile = Subprofile.objects.filter(user=request.user, id=subprofile)
# This can also be written
# subprofile = request.user.subprofile_set.filter(id=subprofile)
if not subprofile:
# this is a malicious user
raise Http403
else:
request.user.subprofile = subprofile
else:
# set default subprofile
request.user.subprofile = self.user.default_subprofile
This way you have access in every view of your app to a SubProfile instance on subprofile attribute of request.user. If there is a valid query string subprofile=123 the user will have these subprofile active, if not it's the default subprofile.
Say your application is an application with Organization models each of which instances have walls, on which user's can post message using a subprofile, the function to post a message on a wall has the following signature post_on_organization_wall(subprofile, message, organization), the view that use this function will look like this:
def organization_wall_post(request, organization):
organization = Organization.objects.get_object_or_404(organization)
if request.method == 'POST':
form = MessageForm(request.POST)
if form.is_valid():
post_on_organization_wall(request.user.subprofile, message, organisation)
else:
form = MessageForm()
return render(request, 'organisation/wall_post.html', {'form': form})
3) Now you need to persist subprofile information across requests. Simplest way to do that is replace everycall to {% url %} to your own url template tag which checks for the request query string presence of subprofile key and add it to the requested url. You can reuse the code of Django's url template tag.
If I understand your problem correctly, the issue is that you're sharing sessions between applications even though the users are different. You should be able to solve this by setting either SESSION_COOKIE_DOMAIN, SESSION_COOKIE_PATH, or SESSION_COOKIE_NAME in settings.py to ensure that your applications don't clobber each others sessions.
Related
I am building a quite complex Django application to be used on top of and email scanning service. The Django application is written using Python 3.5+
This application primarily uses Django Rest Framework to handle communication with the frontend in the browser.
The issue that I am currently having is that I try to implement the concept of a System Administrator, Domain Administrator and Application User
The System Administrator is basically the "normal" django superuser and is therefore capable of doing everything and see every record in the system.
The Domain Administrator is user who manages one or more email domains. I keep track of this using a Many2Many relationship between the users and the domains. The idea would then be to predefine a filter, so that the log of messages processed, will be automatically filtered to show only messages where the sender domain or the recipient domain equal a domain in the list of domains that the given user is assigned to.
The same would be true for blacklisting/whitelisting policies.
If the Domain Administrator is not assigned to any domains, then no data is shown.
The Application User is basically any authenticated user with one or more domains assigned to them, using the same Many2Many relationship as the Domain Administrator. If no domains are assigned, then no data is shown.
I have found some other solution here on Stackoverflow on making the request.user available to the QuerySet in the ModelManager, but that does not seem like the correct way to handle it.
I have looked at django-guardian, django-authority and django-permissions, but none of them seem to be affecting the QuerySet or the resulting list of objects.
Does anyone have a suggestion for Django package/addon that can be used to handle this or maybe an idea for how this could be handled?
I'm the author of django-cancan library https://github.com/pgorecki/django-cancan which strives to solve the exact problem you are describing.
The philosophy is as following: first, you determine per-user abilities, then in a view, you can check user abilities for a given object, model, or you can retrieve a queryset based on those abilities.
The declaration part looks like this:
def declare_abilities(user, ability):
if not user.is_authenticated:
# Allow anonymous users to view only published articles
ability.can('view', Article, published=True)
else:
# logged in user can view any article...
ability.can('view', Article)
# ... and change his own
ability.can('change', Article, author=user)
# ... and add new ones
ability.can('add', Article)
if user.is_superuser:
# Allow superuser to view and change any article
ability.can('view', Article)
ability.can('change', Article)
Then you can you can check for abilites on a per-object level:
def article_detail_view(request, pk):
article = Article.objects.get(pk=pk)
if request.ability.can('view', article):
...
or on a model level:
def article_create_view(request, pk):
if request.ability.can('add', Article):
...
or get a queryset with accessible objects:
def another_list_view(request, pk):
articles = request.ability.queryset_for('view', Article)
...
DRF's GenericAPIView has a get_queryset method that you can override to perform custom filtering:
def get_queryset(self):
qs = super(YourView, self).get_queryset()
return self.filter_queryset_for_user(qs, request.user)
def filter_queryset_for_user(self, qs, user):
pass # Your logic here
This is not necessarily a bad idea; DRF docstrings recommend overriding this:
You may want to override this if you need to provide different
querysets depending on the incoming request.
I think you are misunderstanding the concept of permission in Django. django-guardian, django-authority and django-permissions these all packages are for handling permission inside your Django application. What permission does is it checks certain model or instance of model, if the user has permission to view that particular model or object, otherwise it will return 403 Unauthorized response. Permission does not change or filter your queryset to return only the valid results.
Rather if you want to apply filter your queryset, you can do so by the above answer, or you can move that code to a Mixin to follow DRY Style. For Mixin Reference you can see this link:
https://reinout.vanrees.org/weblog/2015/06/03/10-mixins.html
https://www.youtube.com/watch?v=0513I6_f2Tc
My answer to this question also provides an alternative to filter your queryset by subclassing rest_framework.filters.BaseFilterBackend and implement filter_queryset() based on your permission pattern , which would be suitable for more complicated use cases.
I'm using Eve framework and I'm trying to use User-Restricted resource access as described in:
http://python-eve.org/authentication.html#user-restricted-resource-access
I'm doing something like:
class CustomAuth(TokenAuth):
def check_auth(self, token, allowed_roles, resource, method):
# Get user as an instance of UserResource.
if user and hasattr(user, 'id'):
self.set_request_auth_value(user['id'])
request.authenticated_user = user
...
So, there are a few question from my side:
Is it enough for using User-Restricted Resource Access?
How this field adds into user created objects?
Is this additional field called id in my user created objects? Is it possible to rename it?
As I understand it should be named same as it's called in User resource. Is it true?
Does this field (property) applies for newly created objects only? Is it possible to fetch previously created objects by current user following this way?
Well, I want to know an answers for my questions + clarify how it may be used.
Is it an expected way to extract it somehow in my hooks?
user_id = current_app.auth.get_request_auth_value()
current_app.data.driver.session.query(resource).find({'id': user_id})
Is this block of code from hook expected?
How it behaves if my requested resource has its own id field?
P.S. I was reading a post:
https://stackoverflow.com/a/35654252/7335432
The user-restricted access feature prevents users from accessing records they didn't create. The set_request_auth_value() method does:
1) Upon making a POST request to create a record, it automatically adds a field specified as AUTH_FIELD (or auth_field if you only want to do it to a specific resource). So for example, if you declare in settings.py
AUTH_FIELD = "my_auth_field"
and then add
set_request_auth_value(user['id'])
to your authentication method, that means that your app creates a field "my_auth_field" that has its value set to whatever user["id"] is. So if you were to go into Mongo Compass or some other DBMS and manually inspect your records, you'd see a "my_auth_field" field in there.
2) On GET requests when you access those records, Eve checks the "my_auth_field" value against whatever user["id"] is, and only displays the records where "my_auth_field" is equal to user["id"]. Since this field is added automatically when you create a record using Eve, it effectively filters out everything that specific user didn't create.
So yes, it only applies to newly created objects. I'm not sure exactly what you mean by "is it enough", but it doesn't look like 'user' is declared anywhere in your authentication class. You might wanna check out this tutorial they do incorporating user restricted access into token authentication.
I am trying to implement an SSO login, deriving all the authorization rights from saml response:
class SAMLServiceProviderBackend(object):
def authenticate(self, saml_authentication=None):
if not saml_authentication: # Using another authentication method
return None
if saml_authentication.is_authenticated():
attributes = saml_authentication.get_attributes()
user = User(username=saml_authentication.get_nameid())
user.first_name = attributes['givenName']
user.last_name = attributes['sn']
return None
in views I got something like
...
user = authenticate(saml_authentication=auth)
login(self.request, user)
...
login fails because of the missing save() method. The only way would be to inherit from User and override the save method. Trying this, I got the next errors with is_authenticated, get_and_delete_messages, and so on
Is there an easy way to insert a user object into session, without saving the user to database?
Something like:
request.session['user'] = authenticate(saml_authentication=auth)
I guess should be possible with some limitations, eg. you cannot save your data with a user being a FK.
I have tried this myself, I suspect that you can dynamically create a user instance in the authenticate() method just don't call user.save(), or overwrite the save() method to do nothing.
You might also need to hook up the user record between requests, so you might need to write your own serializer for the user and load that construct the user instance back from session.
I'm fairly certain that Django's session objects are required for the authentication backend. i.e. if you login, then Django needs to store a reference to this fact somewhere. Django generally uses it's database to do this, however, if you're not using a database with your app then you can look at caching sessions instead:
https://docs.djangoproject.com/en/1.11/topics/http/sessions/#using-cached-sessions
(I'm assuming you're not using a database judging by your question)
More importantly however, depending on your needs, you may need to look at creating / configuring, a custom User Model in order to get your backend to work:
https://docs.djangoproject.com/en/1.11/topics/auth/customizing/#substituting-a-custom-user-model
I am working with a site which is already connected to single sign on system. Now I want to add terms of agreement page. Here is the flow that I am thinking of:
If I have a record of the user who is logging in that means the user has already seen it so we no longer need to show the agreement page. But if the record doesn't exist in the table I want to redirect to a view that has terms of agreement and user can go further on the sign in process only upon acceptance. If fail to accept we show the non-signed on version of the site.
Since its a single sign on system I have created a table on my end to just keep track of the users who login-to the site. And this is the table I should look up for record.
Here is how I create the record when they sign in:
This is in the my middleware class
shib_umnPersonType = shib_meta.get('uPersonType','')
shib_uRole = shib_umnPersonType.strip().split(';')[0]
#raise(shib_meta)
try:
shib_user = ShibUser.objects.get(shib_username=username)
user = shib_user.auth_user
user.backend = "django.contrib.auth.backends.RemoteUserBackend"
except ObjectDoesNotExist:
user = auth.authenticate(remote_user=username)
user.shibuser_set.create(shib_username=username, shib_user_role=shib_uRole)
What I am thinking at this point is I should interrupt the request in the except: But I am not really sure if thats that the way it should be done. I am also not a experience django developer.
If thats the way to do it then I guess I should be calling a view with a form from that location then check the response ...?
You can do it with view decorator.
If you detect in decorator, that user has not agreed to recent Terms, then redirect it to Terms page with extra parameter passed on to that view. Pick that parameter up in the Terms view and based on that paramter redirect user to previous view if user accepted the Terms.
I am trying to find a way to prevent users from double-submitting my forms. I have javascript that disables the submit button, but there is still an occasional user who finds a way to double-submit.
I have a vision of a re-usable library that I could create to protect from this.
In my ideal library, the code block would look something like this:
try:
with acquire_lock({'field1':'abc', 'field2':'def'}) as lock:
response = #do some credit card processing
lock.response = response
except SubmissionWasDuplicate, e:
response = e.response
The lock table would look something like this:
duplicate_submission_locks
submission_hash # a MD5 of the submitted arguments
response # pickled data
created_at # used for sweeping this table
lock_expired # boolean signifying if the lock has expired
Does anyone know if this already exists? It doesn't seem to difficult to write, so if it doesn't exist I may write it myself.
You can use a session to store the hash
import hashlib
def contact(request):
if request.method == 'POST':
form = MyForm(request.POST)
#join all the fields in one string
hashstring=hashlib.sha1(fieldsstring)
if request.session.get('sesionform')!=hashstring:
if form.is_valid() :
request.session['sesionform'] = hashstring
#do some stuff...
return HttpResponseRedirect('/thanks/') # Redirect after POST
else
raise SubmissionWasDuplicate("duplicate")
else:
form = MyForm()
With this approach (not deleting the session cookie) the user can't re-store the data util the session expires, by the way, i'm assuming that exist something who identify the user who send the data
One easy solution to this problem is to add a unique hash to each form. Then you can have a rolling table of current forms. When a form is submitted, or the hash gets too old, you can expire it out of your table, and reject any form which does not have a matching hash in your table.
The HTTPRedirect is the correct way to do it, as previously mentioned.
Unfortunately, even Django's own built in admin is prone to problems related to this issue. In some cases, the cross-site scripting framework can assist to prevent some of this, but I'm afraid the current production versions just don't have this built in.
To be honest, your best bet (easy and good practice) is to issue a HTTPRedirect() to the thank you page, and if the thank you page is the same one as the form, that's OK. You can still do this.
Kristian Damian's answer is really a great suggestion. I just thought of a slight variation on that theme, but it might have more overhead.
You could try implementing something that is used in django-piston for BaseHandler objects, which is a method called exists() that checks to see if what you are submitting is already in the database.
From handler.py (BaseHandler):
def exists(self, **kwargs):
if not self.has_model():
raise NotImplementedError
try:
self.model.objects.get(**kwargs)
return True
except self.model.DoesNotExist:
return False
So let's say make that a function called request_exists(), instead of a method:
if form.is_valid()
if request_exists(request):
# gracefully reject dupe submission
else:
# do stuff to save the request
...
# and ALWAYS redirect after a POST!!
return HttpResponseRedirect('/thanks/')
It is always good to use the redirect-after-post method. This prevents user from accidently resubmitting the form using refresh function from the browser. It is also helpful even when you use the hash method. It's because without redirect after a POST, in case of hitting Back/Refresh button, user will see a question message about resubmitting the form, which can confuse her.
If you do a GET redirect after every POST, then hitting Back/Refresh won't display this wierd (for usual user) message. So for full protection use Hash+redirect-after-post.