misc: prepare removing nonce application (#40685)

Remove all code and models.
This commit is contained in:
Benjamin Dauvergne 2023-12-13 20:58:50 +01:00
parent 785e28d93a
commit b2f6688190
7 changed files with 28 additions and 270 deletions

View File

@ -32,7 +32,6 @@ from django.views.decorators.cache import never_cache
from . import app_settings, attribute_kinds, decorators, models
from .custom_user.models import DeletedUser, Profile, ProfileType, User
from .forms.profile import BaseUserForm, modelform_factory
from .nonce.models import Nonce
from .utils import misc as utils_misc
@ -49,11 +48,6 @@ class CleanupAdminMixin(admin.ModelAdmin):
return actions
@admin.register(Nonce)
class NonceModelAdmin(admin.ModelAdmin):
list_display = ('value', 'context', 'not_on_or_after')
class AttributeValueAdmin(admin.ModelAdmin):
list_display = ('content_type', 'owner', 'attribute', 'content')

View File

@ -1,6 +0,0 @@
Nonce are value which should be used only once for a certain period or
eventually forever. The nonce application allows any Django application to
implement this behaviour, by taking care of the storage implementation to keep around invalidated nonce.
For nonce which should not be kept forever the application also provide a
cleanup_nonce() function to delete the no longer invalid nonces.

View File

@ -1,19 +0,0 @@
# authentic2 - versatile identity manager
# Copyright (C) 2010-2019 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/>.
from authentic2.nonce.utils import accept_nonce, cleanup_nonces
__all__ = ('accept_nonce', 'cleanup_nonces')

View File

@ -0,0 +1,13 @@
# Generated by Django 3.2.18 on 2023-12-13 20:00
from django.db import migrations
class Migration(migrations.Migration):
replaces = [('nonce', '0001_initial'), ('nonce', '0002_delete_nonce')]
initial = True
dependencies = []
operations = []

View File

@ -0,0 +1,15 @@
# Generated by Django 3.2.18 on 2023-12-13 19:03
from django.db import migrations
class Migration(migrations.Migration):
dependencies = [
('nonce', '0001_initial'),
]
operations = [
migrations.DeleteModel(
name='Nonce',
),
]

View File

@ -1,39 +0,0 @@
# authentic2 - versatile identity manager
# Copyright (C) 2010-2019 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/>.
from django.db import models
from django.utils import timezone
__all__ = ('Nonce',)
_NONCE_LENGTH_CONSTANT = 256
class NonceManager(models.Manager):
def cleanup(self, now=None):
now = now or timezone.now()
self.filter(not_on_or_after__lt=now).delete()
class Nonce(models.Model):
value = models.CharField(max_length=_NONCE_LENGTH_CONSTANT)
context = models.CharField(max_length=_NONCE_LENGTH_CONSTANT, blank=True, null=True)
not_on_or_after = models.DateTimeField(blank=True, null=True)
objects = NonceManager()
def __str__(self):
return str(self.value)

View File

@ -1,200 +0,0 @@
# authentic2 - versatile identity manager
# Copyright (C) 2010-2019 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 as dt
import errno
import glob
import os.path
import tempfile
from calendar import timegm
from django.conf import settings
__all__ = ('accept_nonce', 'cleanup_nonces')
STORAGE_MODEL = 'model'
STORAGE_FILESYSTEM = 'fs:'
def compute_not_on_or_after(now, not_on_or_after):
try: # first try integer semantic
seconds = int(not_on_or_after)
not_on_or_after = now + dt.timedelta(seconds=seconds)
except ValueError:
try: # try timedelta semantic
not_on_or_after = now + not_on_or_after
except TypeError: # datetime semantic
pass
return not_on_or_after
# For the nonce filesystem storage the policy is to catch any OSerror when
# errno == ENOENT and to handle this case gracefully as there can be many race
# condition errors. But any other OSError is problematic and should be
# reported to the administrator by mail and so we let it unroll the stack
def unlink_if_exists(path):
try:
os.unlink(path)
except OSError as e:
if e.errno != errno.ENOENT:
raise
def accept_nonce_file_storage(path, now, value, context=None, not_on_or_after=None):
"""
Use a directory as a storage for nonce-context values. The last
modification time is used to store the expiration timestamp.
"""
now = dt.datetime.now()
filename = '%s_%s' % (value.encode('base64'), context.encode('base64'))
file_path = os.path.join(path, filename)
# test if the file exists
try:
stat = os.stat(file_path)
except OSError as e:
# test if it doesnt exit or if another error happened
if e.errno != errno.ENOENT:
raise
else:
# if it exists, test if it is too old
if stat.st_mtime < timegm(now.utctimetuple()):
# if it is too old delete it
unlink_if_exists(file_path)
else:
# not too old, the nonce is unacceptable
return False
# pylint: disable=consider-using-with
temp_file = tempfile.NamedTemporaryFile(dir=path, delete=False)
temp_file.close()
if not_on_or_after:
not_on_or_after = compute_not_on_or_after(now, not_on_or_after)
mtime = timegm(not_on_or_after.utctimetuple())
else:
mtime = 0x7FFF
try:
os.utime(temp_file.name, (mtime, mtime))
except OSError:
unlink_if_exists(temp_file.name)
raise
try:
os.link(temp_file.name, file_path)
except OSError as e:
if e.errno == errno.EEXIST:
unlink_if_exists(temp_file.name)
return False
return True
def accept_nonce_model(now, value, context=None, not_on_or_after=None):
from authentic2.nonce.models import Nonce
if not_on_or_after:
not_on_or_after = compute_not_on_or_after(now, not_on_or_after)
nonce, created = Nonce.objects.get_or_create(value=value, context=context)
if created or (nonce.not_on_or_after and nonce.not_on_or_after < now):
nonce.not_on_or_after = not_on_or_after
nonce.save()
return True
else:
return False
def cleanup_nonces_file_storage(dir_path, now):
for nonce_path in glob.iglob(os.path.join(dir_path, '*')):
now_time = timegm(now.utctimetuple())
try:
stat = os.stat(nonce_path)
except OSError as e:
if e.errno == errno.ENOENT:
continue
raise
if stat.st_mtime < now_time:
try:
os.unlink(nonce_path)
except OSError as e:
if e.errno == errno.ENOENT:
continue
raise
def cleanup_nonces(now=None):
"""
Cleanup stored nonce whose timestamp has expired, i.e.
nonce.not_on_or_after < now.
:param now:
a datetime value to define what is the current time, if None is
given, datetime.now() is used. It can be used for unit testing.
"""
from authentic2.nonce.models import Nonce
now = now or dt.datetime.now()
mode = getattr(settings, 'NONCE_STORAGE', STORAGE_MODEL)
# the model always exists, so we always clean it
Nonce.objects.cleanup(now)
if mode == STORAGE_MODEL:
pass
if mode.startswith(STORAGE_FILESYSTEM):
dir_path = mode[len(STORAGE_FILESYSTEM) :]
return cleanup_nonces_file_storage(dir_path, now)
else:
raise ValueError('Invalid NONCE_STORAGE setting: %r' % mode)
def accept_nonce(value, context=None, not_on_or_after=None, now=None):
"""
Verify that the given nonce value has not already been seen in the
context. If not, remember it for ever or until not_on_or_after if is
not None.
Depending on the backend storage used there can be limitation on the
acceptable length for the value and the context. For example the model
storage backend limits the length of those strings to 256 bytes.
:param value:
a string representing a nonce value.
:param context:
a string giving context to the nonce value
:param not_on_or_after:
an integer, a datetime.timedelta or datetime.datetime value. If not
none it is used to compute the expiration time for remembering this
nonce value. If an integer is given it is interpreted as relative
number of seconds since now, if a timedelta object is given it is
used as an offset from now, and if a datetime is given it is used
as an absolute value for the expiration time.
:param now:
a datetime value to define what is the current time, if None is
given, datetime.now() is used. It can be used for unit testing.
:returns:
a boolean, if True the nonce has never been seen before, or it
expired since the last time seen, otherwise the nonce has already
been seen and is invalid.
"""
now = now or dt.datetime.now()
mode = getattr(settings, 'NONCE_STORAGE', STORAGE_MODEL)
if mode == STORAGE_MODEL:
return accept_nonce_model(now, value, context=context, not_on_or_after=not_on_or_after)
elif mode.startswith(STORAGE_FILESYSTEM):
dir_path = mode[len(STORAGE_FILESYSTEM) :]
return accept_nonce_file_storage(
dir_path, now, value, context=context, not_on_or_after=not_on_or_after
)
else:
raise ValueError('Invalid NONCE_STORAGE setting: %r' % mode)