authentic/src/authentic2/apps/authenticators/models.py

146 lines
4.7 KiB
Python

# authentic2 - versatile identity manager
# Copyright (C) 2022 Entr'ouvert
#
# This program is free software: you can redistribute it and/or modify it
# under the terms of the GNU Affero General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# 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 Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import datetime
import logging
import uuid
from django.db import models
from django.shortcuts import render, reverse
from django.utils.formats import date_format
from django.utils.translation import ugettext_lazy as _
from authentic2 import views
from authentic2.utils.evaluate import evaluate_condition
from .query import AuthenticatorManager
logger = logging.getLogger(__name__)
class BaseAuthenticator(models.Model):
uuid = models.CharField(max_length=255, unique=True, default=uuid.uuid4, editable=False)
name = models.CharField(_('Name'), max_length=128)
slug = models.SlugField(unique=True)
ou = models.ForeignKey(
verbose_name=_('organizational unit'),
to='a2_rbac.OrganizationalUnit',
null=True,
blank=False,
on_delete=models.CASCADE,
)
order = models.IntegerField(_('Order'), default=0)
enabled = models.BooleanField(default=False, editable=False)
show_condition = models.CharField(
_('Show condition'),
max_length=128,
blank=True,
help_text=_(
'Django template controlling authenticator display. For example, "\'backoffice\' in '
'login_hint or remotre_addr == \'1.2.3.4\'" would hide the authenticator from normal users '
'except if they come from the specified IP address. Available variables include '
'service_ou_slug, service_slug, remote_addr, login_hint and headers.'
),
)
objects = models.Manager()
authenticators = AuthenticatorManager()
type = ''
manager_form_class = None
internal = False
description_fields = ['show_condition']
class Meta:
ordering = ('-enabled', 'name', 'slug', 'ou')
def __str__(self):
if self.name:
return '%s - %s' % (self._meta.verbose_name, self.name)
return str(self._meta.verbose_name)
def get_identifier(self):
return '%s_%s' % (self.type, self.pk)
def get_absolute_url(self):
return reverse('a2-manager-authenticator-detail', kwargs={'pk': self.pk})
def get_short_description(self):
return ''
def get_full_description(self):
for field in self.description_fields:
value = getattr(self, field)
if not value:
continue
if isinstance(value, datetime.datetime):
value = date_format(value, 'DATETIME_FORMAT')
yield _('%(field)s: %(value)s') % {
'field': self._meta.get_field(field).verbose_name.capitalize(),
'value': value,
}
@property
def priority(self):
return self.order
def shown(self, ctx=()):
if not self.show_condition:
return True
ctx = dict(ctx, id=self.slug)
try:
return evaluate_condition(self.show_condition, ctx, on_raise=True)
except Exception as e:
logger.error(e)
return False
class LoginPasswordAuthenticator(BaseAuthenticator):
remember_me = models.PositiveIntegerField(
_('Remember me duration'),
blank=True,
null=True,
help_text=_(
'Session duration as seconds when using the remember me checkbox. Leave blank to hide the checkbox.'
),
)
include_ou_selector = models.BooleanField(_('Include OU selector in login form'), default=False)
type = 'password'
how = ['password', 'password-on-https']
internal = True
class Meta:
verbose_name = _('Password')
@property
def manager_form_class(self):
from .forms import LoginPasswordAuthenticatorEditForm
return LoginPasswordAuthenticatorEditForm
def login(self, request, *args, **kwargs):
return views.login_password_login(request, self, *args, **kwargs)
def profile(self, request, *args, **kwargs):
return views.login_password_profile(request, *args, **kwargs)
def registration(self, request, *args, **kwargs):
context = kwargs.get('context', {})
return render(request, 'authentic2/login_password_registration_form.html', context)