From ebe18ea20f2d69a5ae809332cd574a7a7c081790 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michel=20Ka=CC=88ser?= Date: Tue, 11 Aug 2015 13:05:41 +0200 Subject: [PATCH] reimplemented workspace auth check --- ipynbsrv/core/auth/checks.py | 42 +++++++++++++++++++ .../web/templates/web/snippets/navbar.html | 4 +- ipynbsrv/web/urls.py | 2 +- ipynbsrv/web/views/accounts.py | 9 ++-- ipynbsrv/web/views/common.py | 38 ----------------- lib/confs/nginx/ipynbsrv.conf | 4 +- 6 files changed, 51 insertions(+), 48 deletions(-) diff --git a/ipynbsrv/core/auth/checks.py b/ipynbsrv/core/auth/checks.py index 7123ab2..8da43d9 100644 --- a/ipynbsrv/core/auth/checks.py +++ b/ipynbsrv/core/auth/checks.py @@ -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. @@ -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) diff --git a/ipynbsrv/web/templates/web/snippets/navbar.html b/ipynbsrv/web/templates/web/snippets/navbar.html index 940e646..ba77957 100644 --- a/ipynbsrv/web/templates/web/snippets/navbar.html +++ b/ipynbsrv/web/templates/web/snippets/navbar.html @@ -28,7 +28,7 @@ - diff --git a/ipynbsrv/web/urls.py b/ipynbsrv/web/urls.py index 9311d14..fd13ebc 100644 --- a/ipynbsrv/web/urls.py +++ b/ipynbsrv/web/urls.py @@ -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'), diff --git a/ipynbsrv/web/views/accounts.py b/ipynbsrv/web/views/accounts.py index 7c14103..014bca0 100644 --- a/ipynbsrv/web/views/accounts.py +++ b/ipynbsrv/web/views/accounts.py @@ -1,20 +1,19 @@ 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 @@ -22,12 +21,12 @@ def create_cookie(request): @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 diff --git a/ipynbsrv/web/views/common.py b/ipynbsrv/web/views/common.py index f034be8..ce43229 100644 --- a/ipynbsrv/web/views/common.py +++ b/ipynbsrv/web/views/common.py @@ -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 @@ -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) diff --git a/lib/confs/nginx/ipynbsrv.conf b/lib/confs/nginx/ipynbsrv.conf index d8c3a82..9ac31e3 100644 --- a/lib/confs/nginx/ipynbsrv.conf +++ b/lib/confs/nginx/ipynbsrv.conf @@ -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 '';