I have a Django module which is called from an SSO service. The service has a single signout function which makes a single GET request to a URL given to it during login.
I'm trying to set up an APIView in Django to handle this logout. The origin service never checks the response; it only calls the GET URL once.
I'm trying something like this for the APIView but keep getting session.DoesNotExist exceptions:
class LogoutApi(APIView):
def get(self, request, *args, **kwargs):
s = Session.objects.get(session_key=kwargs.get('sk', ''))
s.delete()
return Response({'result': True})
I know I have a valid session but even when I try iterating through the Session.objects I can't find it.
I also tried pulling the key from the SessionStore:
class LogoutApi(APIView):
def get(self, request, *args, **kwargs):
sk = request.GET.get('sk', '')
try:
s = SessionStore(sk)
del s[sk]
return Response({'result': True})
except:
self.logger.error(sys.exc_info()[0])
return Response({'result': False})
It still wasn't successful. Is there a way I can set up a GET API call to terminate a specific session?
Turns out the issue was that the session engine was set to use signed cookies. After I removed the following line from my configuration, all worked as expected:
SESSION_ENGINE = "django.contrib.sessions.backends.signed_cookies" # Removed this line
For reference, this is the logout code that worked with the above setting:
class LogoutApi(APIView):
def get(self, request, *args, **kwargs):
sk = request.GET.get('sk', '')
if sk:
s = SessionStore(session_key=sk)
s.delete()
return Response({'result': True})
return Response({'result': False})
Related
I am using the built in authenticater by django and have encountered 2 issues:
Despite placing login decorators as such :
class sales_home(View):
#method_decorator(login_required)
def get(self, request, *args, **kwargs):
return render(request, 'sales/home.html')
I am still able to access the home view by typing it into the URL. The login decorator only works when I clear my browser caches, which is not a solution in production.
The second issue with the built-in authenticator is that the request.user.id returns None.
I am currently trying to display data based on the user's own inputs.
Here is my API call :
class ChartData(APIView):
authentication_classes = []
permission_classes = []
def get(self, request, format=None):
current_user = request.user
sales_rev_data = Sales_project.objects.values_list('sales_project_est_rev', flat=True).filter(sales_project_status = 'p4').filter(sales_extras = current_user.id)
labels = Sales_project.objects.values_list('sales_project_closing_date', flat=True).filter(sales_project_status = 'p4')
data = {
"labels1": labels,
"default1": sales_rev_data,
"labels2": labels,
"default2": sales_rev_data,
}
return Response(data)
because current_user.id returns None, I am unable to access the data that corresponds to the user.
Your help will be greatly appreciated!
I've created a base api view, which extends from APIView, where I log response time, log request, and other common stuffs.
Now, I also want to add request validation here, using the Serializer defined in sub-class Views. I thought the appropriate place is to put that in dispatch() method. But before I call API.dispatch() method, request.data is not prepared. So, that won't work. Can someone help me in right direction as to how to move validation to a single place?
Here's the class structure:
class BaseView(APIView):
validation_serializer = None
def dispatch(self, request, *args, **kwargs):
# Some code here
# How to use `validation_serializer` here, to validate request data?
# `request.data` is not available here.
response = super(BaseView, self).dispatch(request, *args, **kwargs)
# Some code here
return response
class MyView(BaseView):
validation_serializer = ViewValidationSerializer
def post(self, request, *args, **kwargs):
pass
I thought another approach could be use decorator on the top of post() method. But if only there was an cleaner way, than putting decorators all across the project?
Note: It's similar to the question here: Django - DRF - dispatch method flow. But as per the suggestion there, I don't want to just copy the entire dispatch method from DRF source code.
The method that processes the django request into a DRF request (and adds the request.data property) is the APIView.initialize_request . The APIView.dispatch() method calls it and then proceeds to call the appropriate method handler (post/patch/put).
You can try to do that yourself by calling it and using the returned object:
class BaseView(APIView):
validation_serializer = None
def dispatch(self, request, *args, **kwargs):
request = self.initialize_request(request, *args, **kwargs)
kwargs['context'] = self.get_serializer_context()
serializer = self.validation_serializer(data=request.data, *args, **kwargs)
# use `raise_exception=True` to raise a ValidationError
serializer.is_valid(raise_exception=True)
response = super(BaseView, self).dispatch(request, *args, **kwargs)
return response
However, I would suggest against this, as other functionality of dispatch() probably should be performed prior to handling validation; thus, you could move the above logic to the relevant post/patch/put methods instead.
In these methods you can also use self.request directly since it was already initialized by dispatch().
I think drf-tracking does what you are looking for. You may want to check it out.
I don't think you're going about this the correct way. The best way to log the request, with validation is in your Authentication Class and add the audit log to the request.
Then you can use your APIView to log the render time, against the AuditLog generated in the Authentication Class.
Here's an example using Token Authentication, assuming each request has a header Authorization: Bearer <Token>.
settings.py
...
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'common.authentication.MyTokenAuthenticationClass'
),
...,
}
common/authentication.py
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _
from ipware.ip import get_real_ip
from rest_framework import authentication
from rest_framework import exceptions
from accounts.models import Token, AuditLog
class MyTokenAuthenticationClass(authentication.BaseAuthentication):
def authenticate(self, request):
# Grab the Athorization Header from the HTTP Request
auth = authentication.get_authorization_header(request).split()
if not auth or auth[0].lower() != b'bearer':
return None
# Check that Token header is properly formatted and present, raise errors if not
if len(auth) == 1:
msg = _('Invalid token header. No credentials provided.')
raise exceptions.AuthenticationFailed(msg)
elif len(auth) > 2:
msg = _('Invalid token header. Credentials string should not contain spaces.')
raise exceptions.AuthenticationFailed(msg)
try:
token = Token.objects.get(token=auth[1])
# Using the `ipware.ip` module to get the real IP (if hosted on ElasticBeanstalk or Heroku)
token.last_ip = get_real_ip(request)
token.last_login = timezone.now()
token.save()
# Add the saved token instance to the request context
request.token = token
except Token.DoesNotExist:
raise exceptions.AuthenticationFailed('Invalid token.')
# At this point, insert the Log into your AuditLog table and add to request:
request.audit_log = AuditLog.objects.create(
user_id=token.user,
request_payload=request.body,
# Additional fields
...
)
# Return the Authenticated User associated with the Token
return (token.user, token)
Now, you have access to the AuditLog in your request. So you can log everything before and after validation.
I am learning testing in Django, and have a view which I want to test. This view should only be accessed by staff users. Suppose the view is:
def staff_users(request):
....
# some logic
return HttpResponseRedirect('/repositories/')
if the request is coming from staff users, it should redirect to repositories otherwise I should get something like permission denied. I am starting with something like in tests.py.
def test_request_object(self):
self.user = User.objects.create_user(
username='abc', email='abc#gmail.com', password='1234')
request = HttpRequest()
# User send a request to access repositories
response = staff_users(request)
self.assertIsNone(response)
The problem is here I am not associating my request object with any users, and I also got to know about from django.contrib.admin.views.decorators import staff_member_required but not sure how to use them here. Could anyone tell me how should I test my view should only be accessed by staff users?
All you need to do is decorate your view which you want to protect as shown below:
#staff_member_required
def staff_users(request):
....
# some logic
return HttpResponseRedirect('/repositories/')
If you want a custom logic for testing instead of using django decorator then you can write your own decorator as well.
def staff_users_only(function):
def wrap(request, *args, **kwargs):
profile = request.session['user_profile']
if profile is True: #then its a staff member
return function(request, *args, **kwargs)
else:
return HttpResponseRedirect('/')
wrap.__doc__=function.__doc__
wrap.__name__=function.__name__
return wrap
and use it as:
#staff_users_only
def staff_users(request):
....
# some logic
return HttpResponseRedirect('/repositories/')
Edit
Association of sessions on request object for testing can be done as:
def test_request_object(self):
self.user = User.objects.create_user(
username='abc', email='abc#gmail.com', password='1234')
request = HttpRequest()
#create a session which will hold the user profile that will be used in by our custom decorator
request.session = {} #Session middleware is not available in UnitTest hence create a blank dictionary for testing purpose
request.session['user_profile'] = self.user.is_staff #assuming its django user.
# User send a request to access repositories
response = staff_users(request)
#Check the response type for appropriate action
self.assertIsNone(response)
Edit 2
Also it would be a far better idea to use django Client library for testing:
>>> from django.test import Client
>>> c = Client()
>>> response = c.post('/login/', {'username': 'abc', 'password': '1234'})
>>> response.status_code
200
>>> response = c.get('/user/protected-page/')
>>> response.content
b'<!DOCTYPE html...
I have a Google App Engine application and my request hadnler has a decorator that does authentication. With WebTest I found out yesterday how you can set a logged in user and administrator.
Now today my authentication decorator got a little more complex. It's also checking if a user has a profile in the database and if he doesn't he'll get redirected to the 'new user' page.
def authenticated(method):
#functools.wraps(method)
def wrapper(self, *args, **kwargs):
user = users.get_current_user()
if not user:
self.redirect(users.create_login_url(self.request.uri))
return
profile = Profile.get_by_key_name(str(user.user_id))
if not profile:
self.redirect( '/newuser' )
return method(self, *args, **kwargs)
return wrapper
Now adding the profile part breaks my unit test that checks if a user is logged in and gets a status code 200(assertOK).
def user_ok(self):
os.environ['USER_EMAIL'] = 'info#example.com'
os.environ['USER_IS_ADMIN'] = ''
response = self.get( '/appindex' )
self.assertOK(response)
So now I need to be able to somehow inject the profile functionality into the decorator so I can set it in my tests. Does anybody got an idea how to do this I've been trying to think of a way but I keep getting stuck.
You should create a profile during the test, to be used by the decorator:
def user_ok(self):
key_name = 'info#example.com'
new_user = Profile(key_name=key_name)
new_user.put()
os.environ['USER_EMAIL'] = key_name
os.environ['USER_ID'] = key_name
os.environ['USER_IS_ADMIN'] = ''
response = self.get( '/appindex' )
self.assertOK(response)
# Now let's reset it to check that the user will be redirected.
new_user.delete()
response = self.get( '/appindex' )
self.assertEqual(response.headers['Location'], 'http://localhost/newuser')
I have a decorator that I use for my views #valid_session
from django.http import Http404
def valid_session(the_func):
"""
function to check if the user has a valid session
"""
def _decorated(*args, **kwargs):
if ## check if username is in the request.session:
raise Http404('not logged in.')
else:
return the_func(*args, **kwargs)
return _decorated
I would like to access my session in my decoartor. When user is logged in, I put the username in my session.
Will something like the following solve your problem:
def valid_session(func):
def decorated(request, *args, **kwargs):
print request.session
return func(request, *args, **kwargs)
return decorated
The view function takes the request as the first parameter, so the decorator will receive it as its first parameter as well. You can pull the session out of it with just request.session.
You could pass the request (or just the session) in as a parameter to the decorator. I just don't know how to get at it to pass it in. I was trying to figure out something similar last night.