Skip to content

Commit

Permalink
reimplemented workspace auth check
Browse files Browse the repository at this point in the history
  • Loading branch information
Michel Käser committed Aug 11, 2015
1 parent fe8bd4a commit ebe18ea
Show file tree
Hide file tree
Showing 6 changed files with 51 additions and 48 deletions.
42 changes: 42 additions & 0 deletions ipynbsrv/core/auth/checks.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.http.response import HttpResponse
from ipynbsrv.wui.models import PortMapping


COOKIE_NAME = 'username'
URI_HEADER = 'HTTP_X_ORIGINAL_URI'


def login_allowed(user):
"""
@user_passes_test decorator to check whether the user is allowed to access the application or not.
Expand All @@ -8,3 +18,35 @@ def login_allowed(user):
if user is None or user.get_username() is None: # AnonymousUser
return False
return hasattr(user, 'backend_user') # not super = internal only user


def workspace_auth_access(request):
"""
This view is called by Nginx to check either a user is authorized to
access a given workspace or not.
The username can be obtained from the signed cookie 'username',
while the port/container needs to be extracted from the 'X-Original-URI' header.
Response codes of 20x will allow the user to access the requested resource.
"""
if request.method == "GET":
username = request.get_signed_cookie(COOKIE_NAME, default=None)
if username: # ensure the signed cookie set at login is there
try:
user = User.objects.get(username=username)
uri = request.META.get(URI_HEADER)
if uri: # ensure the X- header is present. its set by Nginx
splits = uri.split('/')
if len(splits) >= 3:
base_url = splits[2]
parts = base_url.decode('hex').split(':')
internal_ip = parts[0]
port = parts[1]
mapping = PortMapping.objects.filter(external_port=port).filter(server__internal_ip=internal_ip)
if mapping.exists() and mapping.first().container.owner == user.backend_user:
return HttpResponse(status=200)
except ObjectDoesNotExist:
pass

return HttpResponse(status=403)
4 changes: 2 additions & 2 deletions ipynbsrv/web/templates/web/snippets/navbar.html
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<li class="hidden-xs"><a href="{% settings 'PUBLIC_URL' %}{% request.user.get_username %}" data-tooltip data-placement="bottom" title="My publications" target="_blank">
<i class="glyphicon glyphicon-globe" aria-hidden="true"></i>
</a></li>
<li class="hidden-xs"><a href="/accounts/logout" data-tooltip data-placement="bottom" title="Logout">
<li class="hidden-xs"><a href="/accounts/unflag" data-tooltip data-placement="bottom" title="Logout">
<i class="glyphicon glyphicon-log-out" aria-hidden="true"></i>
</a></li>
<li class="hidden-sm hidden-md hidden-lg">
Expand All @@ -38,7 +38,7 @@
<a href="{% settings 'PUBLIC_URL' %}{% request.user.get_username %}" title="My publications" target="_blank">My publications</a>
</li>
<li class="hidden-sm hidden-md hidden-lg">
<a href="/accounts/logout" title="Logout">Logout</a>
<a href="/accounts/unflag" title="Logout">Logout</a>
</li>
</ul>
</div>
Expand Down
2 changes: 1 addition & 1 deletion ipynbsrv/web/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@
url(r'^notifications/mark_as_read$', 'ipynbsrv.web.views.notifications.mark_as_read', name='notification_mark_as_read'),

# internal
url(r'^_workspace_auth_check$', 'ipynbsrv.web.views.common.workspace_auth_access'),
url(r'^_workspace_auth_check$', 'ipynbsrv.core.auth.checks.workspace_auth_access'),
url(r'^error/404$', 'ipynbsrv.web.views.system.error_404'),
url(r'^error/500$', 'ipynbsrv.web.views.system.error_500'),

Expand Down
9 changes: 4 additions & 5 deletions ipynbsrv/web/views/accounts.py
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
from django.contrib.auth.decorators import user_passes_test
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from django.shortcuts import redirect
from ipynbsrv.core.auth.checks import login_allowed
from ipynbsrv.web import settings


@user_passes_test(login_allowed)
def create_cookie(request):
'''
"""
The flag view is called after a successful user login.
Since we use Nginx, which does a subrequest to check authorization of workspace access,
we need a way to identify the user there. So we bypass here to create a signed cookie
for that purpose.
'''
"""
response = HttpResponseRedirect(reverse('dashboard'))
response.set_signed_cookie(settings.AUTH_COOKIE_NAME, request.user.username, httponly=True)
return response


@user_passes_test(login_allowed)
def remove_cookie(request):
'''
"""
The unflag view is called before a user is actually logged out.
We use that chance to remove the cookie we created after his login
which authorizes him to access his workspaces.
'''
"""
response = HttpResponseRedirect(reverse('accounts_logout'))
response.delete_cookie(settings.AUTH_COOKIE_NAME)
return response
38 changes: 0 additions & 38 deletions ipynbsrv/web/views/common.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
from django.contrib.auth.decorators import user_passes_test
from django.contrib.auth.models import User
from django.core.exceptions import ObjectDoesNotExist
from django.http.response import HttpResponse
from django.shortcuts import render
from ipynbsrv.core.auth.checks import login_allowed
from ipynbsrv.core.models import Container
from ipynbsrv.web import settings
from ipynbsrv.web.api_client_proxy import get_httpclient_instance


Expand All @@ -24,36 +19,3 @@ def dashboard(request):
'containers': containers,
'new_notifications_count': new_notifications_count
})


def workspace_auth_access(request):
'''
This view is called by Nginx to check either a user is authorized to
access a given workspace or not.
The username can be obtained from the signed cookie 'username',
while the port/container needs to be extracted from the 'X-Original-URI' header.
Response codes of 20x will allow the user to access the requested resource.
'''

"""
Todo: rewrite
"""
# if request.method == "GET":
# username = request.get_signed_cookie(settings.AUTH_COOKIE_NAME, default=None)
# if username: # ensure the signed cookie set at login is there
# try:
# user = User.objects.get(username=username)
# uri = request.META.get(settings.PROXY_URI_HEADER)
# if uri: # ensure the X- header is present. its set by Nginx
# splits = uri.split('/')
# if len(splits) >= 3:
# port = splits[2]
# mapping = PortMapping.objects.filter(external=port)
# if mapping.exists() and mapping.first().container.owner == user:
# return HttpResponse(status=200)
# except ObjectDoesNotExist:
# pass
#
return HttpResponse(status=403)
4 changes: 2 additions & 2 deletions lib/confs/nginx/ipynbsrv.conf
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ server {
location ~* /ct/([^\/]+)/(.*) {
# authorization
# ensure only container's owner can access it
#satisfy all;
#auth_request /auth;
satisfy all;
auth_request /auth;

# get the IP and port from encoded part
set $decoded_backend '';
Expand Down

0 comments on commit ebe18ea

Please sign in to comment.