Skip to content

Commit

Permalink
Move middleware to middleware.py
Browse files Browse the repository at this point in the history
  • Loading branch information
hmpf committed Nov 10, 2023
1 parent d147a19 commit 735169d
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 159 deletions.
4 changes: 2 additions & 2 deletions python/nav/django/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@
MIDDLEWARE = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'nav.web.auth.AuthenticationMiddleware',
'nav.web.auth.AuthorizationMiddleware',
'nav.web.auth.middleware.AuthenticationMiddleware',
'nav.web.auth.middleware.AuthorizationMiddleware',
'nav.django.legacy.LegacyCleanupMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
Expand Down
129 changes: 2 additions & 127 deletions python/nav/web/auth/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,20 +26,13 @@

from django.conf import settings
from django.contrib.sessions.backends.db import SessionStore
from django.http import HttpResponseRedirect, HttpResponse
from django.urls import reverse

try:
from django.utils.deprecation import MiddlewareMixin
except ImportError: # Django <= 1.9
MiddlewareMixin = object


from nav.auditlog.models import LogEntry
from nav.models.profiles import Account, AccountGroup
from nav.web.auth import ldap, remote_user
from nav.web.auth.sudo import desudo, get_sudoer
from nav.web.auth.utils import _set_account, ACCOUNT_ID_VAR
from nav.web.auth.sudo import desudo
from nav.web.auth.utils import ACCOUNT_ID_VAR


_logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -143,124 +136,6 @@ def get_logout_url(request):
return remote_logouturl if remote_logouturl else LOGOUT_URL


# Middleware


class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
_logger.debug(
'AuthenticationMiddleware ENTER (session: %s, account: %s) from "%s"',
dict(request.session),
getattr(request, 'account', 'NOT SET'),
request.get_full_path(),
)
ensure_account(request)

account = request.account
sudo_operator = get_sudoer(request) # Account or None
logged_in = sudo_operator or account
_logger.debug(
('AuthenticationMiddleware ' '(logged_in: "%s" acting as "%s") from "%s"'),
logged_in.login,
account.login,
request.get_full_path(),
)

remote_username = remote_user.get_username(request)
if remote_username:
_logger.debug(
('AuthenticationMiddleware: ' '(REMOTE_USER: "%s") from "%s"'),
remote_username,
request.get_full_path(),
)
if logged_in.id == Account.DEFAULT_ACCOUNT:
# Switch from anonymous to REMOTE_USER
remote_user.login(request)
elif remote_username != logged_in.login:
# REMOTE_USER has changed, logout
logout(request, sudo=bool(sudo_operator))
sudo_operator = None
# Activate anonymous account for AuthorizationMiddleware's sake
ensure_account(request)

if sudo_operator is not None:
request.account.sudo_operator = sudo_operator

_logger.debug(
'AuthenticationMiddleware EXIT (session: %s, account: %s) from "%s"',
dict(request.session),
getattr(request, 'account', 'NOT SET'),
request.get_full_path(),
)


def ensure_account(request):
"""Guarantee that valid request.account is set"""
session = request.session

if not ACCOUNT_ID_VAR in session:
session[ACCOUNT_ID_VAR] = Account.DEFAULT_ACCOUNT

account = Account.objects.get(id=session[ACCOUNT_ID_VAR])

if account.locked:
# Switch back to fallback, the anonymous user
# Assumes nobody has locked it..
account = Account.objects.get(id=Account.DEFAULT_ACCOUNT)

_set_account(request, account)


class AuthorizationMiddleware(MiddlewareMixin):
def process_request(self, request):
account = request.account

authorized = authorization_not_required(
request.get_full_path()
) or account.has_perm('web_access', request.get_full_path())
if not authorized:
_logger.warning(
"User %s denied access to %s", account.login, request.get_full_path()
)
return self.redirect_to_login(request)
else:
if not account.is_default_account():
os.environ['REMOTE_USER'] = account.login
elif 'REMOTE_USER' in os.environ:
del os.environ['REMOTE_USER']

def redirect_to_login(self, request):
"""Redirects a request to the NAV login page, unless it was detected
to be an AJAX request, in which case return a 401 Not Authorized
response.
"""
if request.is_ajax():
return HttpResponse(status=401)

new_url = get_login_url(request)
return HttpResponseRedirect(new_url)


def authorization_not_required(fullpath):
"""Checks is authorization is required for the requested url
Should the user be able to decide this? Currently not.
"""
auth_not_required = [
'/api/',
'/doc/', # No auth/different auth system
'/about/',
'/index/login/',
'/refresh_session',
]
for url in auth_not_required:
if fullpath.startswith(url):
_logger.debug('authorization_not_required: %s', url)
return True


def logout(request, sudo=False):
"""Log out a user from a request
Expand Down
115 changes: 115 additions & 0 deletions python/nav/web/auth/middleware.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
# Copyright (C) 2010, 2011, 2013, 2019 Uninett AS
# Copyright (C) 2022, 2023 Sikt
#
# This file is part of Network Administration Visualized (NAV).
#
# NAV is free software: you can redistribute it and/or modify it under
# the terms of the GNU General Public License version 3 as published by
# the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details. You should have received a copy of the GNU General Public
# License along with NAV. If not, see <http://www.gnu.org/licenses/>.
#

import logging
import os

from django.http import HttpResponseRedirect, HttpResponse

try:
from django.utils.deprecation import MiddlewareMixin
except ImportError: # Django <= 1.9
MiddlewareMixin = object

from nav.models.profiles import Account
from nav.web.auth import remote_user, get_login_url, logout
from nav.web.auth.utils import (
ensure_account,
authorization_not_required,
)
from nav.web.auth.sudo import get_sudoer


_logger = logging.getLogger(__name__)


class AuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
_logger.debug(
'AuthenticationMiddleware ENTER (session: %s, account: %s) from "%s"',
dict(request.session),
getattr(request, 'account', 'NOT SET'),
request.get_full_path(),
)
ensure_account(request)

account = request.account
sudo_operator = get_sudoer(request) # Account or None
logged_in = sudo_operator or account
_logger.debug(
('AuthenticationMiddleware ' '(logged_in: "%s" acting as "%s") from "%s"'),
logged_in.login,
account.login,
request.get_full_path(),
)

remote_username = remote_user.get_username(request)
if remote_username:
_logger.debug(
('AuthenticationMiddleware: ' '(REMOTE_USER: "%s") from "%s"'),
remote_username,
request.get_full_path(),
)
if logged_in.id == Account.DEFAULT_ACCOUNT:
# Switch from anonymous to REMOTE_USER
remote_user.login(request)
elif remote_username != logged_in.login:
# REMOTE_USER has changed, logout
logout(request, sudo=bool(sudo_operator))
sudo_operator = None
# Activate anonymous account for AuthorizationMiddleware's sake
ensure_account(request)

if sudo_operator is not None:
request.account.sudo_operator = sudo_operator

_logger.debug(
'AuthenticationMiddleware EXIT (session: %s, account: %s) from "%s"',
dict(request.session),
getattr(request, 'account', 'NOT SET'),
request.get_full_path(),
)


class AuthorizationMiddleware(MiddlewareMixin):
def process_request(self, request):
account = request.account

authorized = authorization_not_required(
request.get_full_path()
) or account.has_perm('web_access', request.get_full_path())
if not authorized:
_logger.warning(
"User %s denied access to %s", account.login, request.get_full_path()
)
return self.redirect_to_login(request)
else:
if not account.is_default_account():
os.environ['REMOTE_USER'] = account.login
elif 'REMOTE_USER' in os.environ:
del os.environ['REMOTE_USER']

def redirect_to_login(self, request):
"""Redirects a request to the NAV login page, unless it was detected
to be an AJAX request, in which case return a 401 Not Authorized
response.
"""
if request.is_ajax():
return HttpResponse(status=401)

new_url = get_login_url(request)
return HttpResponseRedirect(new_url)
38 changes: 38 additions & 0 deletions python/nav/web/auth/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@

import logging

from nav.models.profiles import Account


_logger = logging.getLogger(__name__)

Expand All @@ -28,3 +30,39 @@ def _set_account(request, account):
request.account = account
_logger.debug('Set active account to "%s"', account.login)
request.session.save()


def ensure_account(request):
"""Guarantee that valid request.account is set"""
session = request.session

if not ACCOUNT_ID_VAR in session:
session[ACCOUNT_ID_VAR] = Account.DEFAULT_ACCOUNT

account = Account.objects.get(id=session[ACCOUNT_ID_VAR])

if account.locked:
# Switch back to fallback, the anonymous user
# Assumes nobody has locked it..
account = Account.objects.get(id=Account.DEFAULT_ACCOUNT)

_set_account(request, account)


def authorization_not_required(fullpath):
"""Checks is authorization is required for the requested url
Should the user be able to decide this? Currently not.
"""
auth_not_required = [
'/api/',
'/doc/', # No auth/different auth system
'/about/',
'/index/login/',
'/refresh_session',
]
for url in auth_not_required:
if fullpath.startswith(url):
_logger.debug('authorization_not_required: %s', url)
return True
2 changes: 1 addition & 1 deletion python/nav/web/modpython.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
from nav.bootstrap import bootstrap_django

from django.contrib.sessions.middleware import SessionMiddleware
from nav.web.auth import AuthenticationMiddleware, AuthorizationMiddleware
from nav.web.auth.middleware import AuthenticationMiddleware, AuthorizationMiddleware
from nav.web import loginit
from django.db import connection

Expand Down
Loading

0 comments on commit 735169d

Please sign in to comment.