Skip to content
Draft
2 changes: 1 addition & 1 deletion python/nav/django/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from nav.config import find_config_file
from nav.web.auth import get_login_url, get_logout_url
from nav.web.auth.sudo import get_sudoer
from nav.django.utils import get_account, is_admin
from nav.web.auth.utils import get_account, is_admin
from nav.web.auth.utils import get_number_of_accounts_with_password_issues
from nav.web.message import Messages
from nav.web.webfront.utils import tool_list, quick_read, split_tools
Expand Down
10 changes: 8 additions & 2 deletions python/nav/django/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'nav.web.auth.middleware.AuthenticationMiddleware',
'nav.web.auth.middleware.NAVAuthenticationMiddleware',
'nav.web.auth.middleware.AuthorizationMiddleware',
'nav.django.legacy.LegacyCleanupMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
Expand Down Expand Up @@ -218,6 +218,8 @@
'nav.models',
'nav.web',
'nav.django',
'django.contrib.auth',
'django.contrib.contenttypes',
Comment on lines +221 to +222
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should probably switch places

'django.contrib.staticfiles',
'django.contrib.sessions',
'django.contrib.humanize',
Expand All @@ -233,7 +235,11 @@
)

DEFAULT_AUTO_FIELD = 'django.db.models.AutoField'
AUTH_USER_MODEL = "nav.models.Account"
AUTH_USER_MODEL = 'nav_models.Account'

AUTHENTICATION_BACKENDS = ['django.contrib.auth.backends.ModelBackend']
LOGIN_REDIRECT_URL = '/'
LOGIN_URL = '/index/login/'

REST_FRAMEWORK = {
'DEFAULT_FILTER_BACKENDS': ('django_filters.rest_framework.DjangoFilterBackend',),
Expand Down
19 changes: 0 additions & 19 deletions python/nav/django/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,6 @@
from django.urls import reverse
from django.utils.http import urlencode

from nav.models.profiles import Account, AccountGroup


def reverse_with_query(viewname, **kwargs):
"""Wrapper for django.urls.reverse, but will adapt query arguments from kwargs"""
Expand All @@ -31,23 +29,6 @@ def reverse_with_query(viewname, **kwargs):
return "{}?{}".format(baseurl, getargs)


def default_account():
return Account.objects.get(id=Account.DEFAULT_ACCOUNT)


def get_account(request):
"""Returns the account associated with the request"""
try:
return request.account
except AttributeError:
return default_account()


def is_admin(account):
"""Check if user is a member of the administrator group"""
return account.groups.filter(pk=AccountGroup.ADMIN_GROUP).count() > 0


def get_verbose_name(model, lookup):
"""Verbose name introspection of ORM models.
Parameters:
Expand Down
1 change: 1 addition & 0 deletions python/nav/models/apps.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@

class NavModelsConfig(AppConfig):
name = 'nav.models'
label = 'nav_models'
verbose_name = 'NAV models'
18 changes: 9 additions & 9 deletions python/nav/models/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,12 +254,12 @@ def _fetch_subject(self):

subid = self.subid
if self.event_type_id in self.SUBID_MAP:
model = apps.get_model('models', self.SUBID_MAP[self.event_type_id])
model = apps.get_model('nav_models', self.SUBID_MAP[self.event_type_id])
elif (
self.event_type_id == 'maintenanceState'
and 'service' in self.varmap.get(EventQueue.STATE_START, {})
):
model = apps.get_model('models', 'Service')
model = apps.get_model('nav_models', 'Service')
elif self.event_type_id == 'thresholdState':
return ThresholdEvent(self)
else:
Expand Down Expand Up @@ -296,7 +296,7 @@ def __init__(self, event):
ruleid = event.subid
self.metric = None

klass = apps.get_model('models', 'ThresholdRule')
klass = apps.get_model('nav_models', 'ThresholdRule')
try:
self.rule = klass.objects.get(pk=ruleid)
except (klass.DoesNotExist, ValueError):
Expand Down Expand Up @@ -357,14 +357,14 @@ class EventQueue(models.Model, EventMixIn):
related_name='target_of_events',
)
device = models.ForeignKey(
'models.Device',
'nav_models.Device',
on_delete=models.CASCADE,
db_column='deviceid',
null=True,
related_name="events",
)
netbox = models.ForeignKey(
'models.Netbox',
'nav_models.Netbox',
on_delete=models.CASCADE,
db_column='netboxid',
null=True,
Expand Down Expand Up @@ -491,14 +491,14 @@ class AlertQueue(models.Model, EventMixIn):
related_name="alerts",
)
device = models.ForeignKey(
'models.Device',
'nav_models.Device',
on_delete=models.CASCADE,
db_column='deviceid',
null=True,
related_name="alerts",
)
netbox = models.ForeignKey(
'models.Netbox',
'nav_models.Netbox',
on_delete=models.CASCADE,
db_column='netboxid',
null=True,
Expand Down Expand Up @@ -660,14 +660,14 @@ class AlertHistory(models.Model, EventMixIn):
related_name="alert_history_set",
)
device = models.ForeignKey(
'models.Device',
'nav_models.Device',
on_delete=models.CASCADE,
db_column='deviceid',
null=True,
related_name="alert_history_set",
)
netbox = models.ForeignKey(
'models.Netbox',
'nav_models.Netbox',
on_delete=models.CASCADE,
db_column='netboxid',
null=True,
Expand Down
4 changes: 2 additions & 2 deletions python/nav/models/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
# to find models.py (module) from an app in INSTALLED_APPS.
#
# 'models' need to be listed as it expects to find model_name under
# app_label 'models', hence we add this empty placeholder and add 'models' to
# INSTALLED_APPS to make sure 'models.ModelName' is valid lookup on django
# app_label 'nav_models', hence we add this empty placeholder and add 'models' to
# INSTALLED_APPS to make sure 'nav_models.ModelName' is valid lookup on django
# form app_label.model_name

from .manage import *
Expand Down
19 changes: 19 additions & 0 deletions python/nav/models/profiles.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,14 @@
### Account models


class AccountManager(models.Manager):
"""Custom manager for Account objects"""

def get_by_natural_key(self, login):
"""Gets Account object by its 'natural' key: Its login name."""
return self.get(login=login)


class Account(AbstractBaseUser):
"""NAV's basic account model"""

Expand Down Expand Up @@ -120,6 +128,8 @@ class Account(AbstractBaseUser):
# objects are retrieved from session data
sudo_operator = None

objects = AccountManager()

class Meta(object):
db_table = 'account'
ordering = ('login',)
Expand All @@ -130,6 +140,10 @@ def __str__(self):
else:
return self.login

def natural_key(self) -> tuple[str]:
"""Returns the natural key for an account as a tuple"""
return (self.login,)

def get_active_profile(self):
"""Returns the account's active alert profile"""
try:
Expand Down Expand Up @@ -329,6 +343,11 @@ def _verify_old_password_hash_and_rehash(self, password):
def locked(self):
return not self.password or self.password.startswith('!')

@property
def is_active(self):
"""Returns True if this account is active (i.e. not locked)"""
return not self.locked

@locked.setter
def locked(self, value):
if not value:
Expand Down
38 changes: 38 additions & 0 deletions python/nav/models/sql/changes/sc.05.15.0001.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
-- Create models needed for Django auth

BEGIN;

--
-- Create model ContentType
--
CREATE TABLE "profiles"."django_content_type" ("id" integer NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "app_label" varchar(100) NOT NULL, "model" varchar(100) NOT NULL);
--
-- Alter unique_together for contenttype (1 constraint(s))
--
ALTER TABLE "profiles"."django_content_type" ADD CONSTRAINT "django_content_type_app_label_model_76bd3d3b_uniq" UNIQUE ("app_label", "model");

--
-- Create model Permission
--
CREATE TABLE "profiles"."auth_permission" ("id" integer NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "name" varchar(255) NOT NULL, "content_type_id" integer NOT NULL, "codename" varchar(100) NOT NULL);
COMMIT;

--
-- Create model Group
--
CREATE TABLE "profiles"."auth_group" ("id" integer NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "name" varchar(150) NOT NULL UNIQUE);
CREATE TABLE "profiles"."auth_group_permissions" ("id" integer NOT NULL PRIMARY KEY GENERATED BY DEFAULT AS IDENTITY, "group_id" integer NOT NULL, "permission_id" integer NOT NULL);

-- Set up constraints, indexes; add foreign keys

ALTER TABLE "profiles"."auth_permission" ADD CONSTRAINT "auth_permission_content_type_id_codename_01ab375a_uniq" UNIQUE ("content_type_id", "codename");
ALTER TABLE "profiles"."auth_permission" ADD CONSTRAINT "auth_permission_content_type_id_2f476e4b_fk_django_co" FOREIGN KEY ("content_type_id") REFERENCES "profiles"."django_content_type" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "auth_permission_content_type_id_2f476e4b" ON "profiles"."auth_permission" ("content_type_id");
CREATE INDEX "auth_group_name_a6ea08ec_like" ON "profiles"."auth_group" ("name" varchar_pattern_ops);
ALTER TABLE "profiles"."auth_group_permissions" ADD CONSTRAINT "auth_group_permissions_group_id_permission_id_0cd325b0_uniq" UNIQUE ("group_id", "permission_id");
ALTER TABLE "profiles"."auth_group_permissions" ADD CONSTRAINT "auth_group_permissions_group_id_b120cbf9_fk_auth_group_id" FOREIGN KEY ("group_id") REFERENCES "profiles"."auth_group" ("id") DEFERRABLE INITIALLY DEFERRED;
ALTER TABLE "profiles"."auth_group_permissions" ADD CONSTRAINT "auth_group_permissio_permission_id_84c5c92e_fk_auth_perm" FOREIGN KEY ("permission_id") REFERENCES "profiles"."auth_permission" ("id") DEFERRABLE INITIALLY DEFERRED;
CREATE INDEX "auth_group_permissions_group_id_b120cbf9" ON "profiles"."auth_group_permissions" ("group_id");
CREATE INDEX "auth_group_permissions_permission_id_84c5c92e" ON "profiles"."auth_group_permissions" ("permission_id");

COMMIT;
2 changes: 1 addition & 1 deletion python/nav/web/alertprofiles/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@

import nav.config
import nav.buildconf
from nav.django.utils import get_account, is_admin
from nav.web.auth.utils import get_account, is_admin
from nav.models.profiles import (
Filter,
FilterGroup,
Expand Down
2 changes: 1 addition & 1 deletion python/nav/web/alertprofiles/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@
SMSQueue,
AccountAlertQueue,
)
from nav.django.utils import get_account, is_admin
from nav.web.auth.utils import get_account, is_admin
from nav.web.message import Messages, new_message

from nav.web.alertprofiles.forms import TimePeriodForm, LanguageForm
Expand Down
4 changes: 2 additions & 2 deletions python/nav/web/api/v1/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ class NavBaseAuthentication(BaseAuthentication):

def authenticate(self, request):
_logger.debug("Baseauthentication account is %s", request.account)
if request.account and not request.account.is_default_account():
if request.account and not request.account.is_anonymous:
return request.account, None


Expand All @@ -67,7 +67,7 @@ class ReadOnlyNonAdminPermission(BasePermission):

def has_permission(self, request, _view):
"""If user is logged in and it is a safe method, it is authorized"""
if request.method in SAFE_METHODS and not request.account.is_default_account():
if request.method in SAFE_METHODS and not request.account.is_anonymous:
return True
if request.account.is_admin():
return True
Expand Down
2 changes: 1 addition & 1 deletion python/nav/web/arnold/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
)
from nav.models.arnold import Identity, Justification, QuarantineVlan, DetentionProfile
from nav.models.manage import Cam, Interface
from nav.django.utils import get_account
from nav.web.auth.utils import get_account
from nav.web.arnold.forms import (
SearchForm,
HistorySearchForm,
Expand Down
33 changes: 32 additions & 1 deletion python/nav/web/auth/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import os
from typing import Optional

from django.core.exceptions import ImproperlyConfigured
from django.http import HttpResponseRedirect, HttpResponse, HttpRequest
from django.utils.deprecation import MiddlewareMixin

Expand All @@ -29,6 +30,7 @@
from nav.web.auth.utils import (
ensure_account,
authorization_not_required,
get_user,
)
from nav.web.auth.sudo import get_sudoer
from nav.web.utils import is_ajax
Expand Down Expand Up @@ -98,7 +100,7 @@ def process_request(self, request: HttpRequest) -> Optional[HttpResponse]:
)
return self.redirect_to_login(request)
else:
if not account.is_default_account():
if not account.is_anonymous:
os.environ['REMOTE_USER'] = account.login
elif 'REMOTE_USER' in os.environ:
del os.environ['REMOTE_USER']
Expand All @@ -114,3 +116,32 @@ def redirect_to_login(self, request: HttpRequest) -> HttpResponse:

new_url = get_login_url(request)
return HttpResponseRedirect(new_url)


class NAVAuthenticationMiddleware(MiddlewareMixin):
def process_request(self, request):
if not hasattr(request, "session"):
raise ImproperlyConfigured(
"The NAV Django authentication middleware requires session "
"middleware to be installed. Edit your MIDDLEWARE setting to "
"insert "
"'django.contrib.sessions.middleware.SessionMiddleware' before "
"'nav.web.auth.middleware.NAVAuthenticationMiddleware'."
)

user = get_user(request) # NOT lazy!
request.user = user
request.account = user # remove this eventually

# NAV-specific sudo method
sudo_operator = get_sudoer(request) # Account or None
if sudo_operator:
logged_in = sudo_operator or user
_logger.debug(
('AuthenticationMiddleware (logged_in: "%s" acting as "%s") from "%s"'),
logged_in.login,
user.login,
request.get_full_path(),
)
request.account.sudo_operator = sudo_operator
request.user.sudo_operator = sudo_operator
2 changes: 1 addition & 1 deletion python/nav/web/auth/sudo.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@
from django.http import HttpRequest

from nav.auditlog.models import LogEntry
from nav.django.utils import is_admin, get_account
from nav.web.auth.utils import is_admin, get_account
from nav.models.profiles import Account
from nav.web.auth.utils import set_account, clear_session

Expand Down
Loading
Loading