added mfaData to admin

This commit is contained in:
Adolfo Gómez García 2022-07-04 21:29:41 +02:00
parent 8783db925f
commit 1c65722d24
9 changed files with 73 additions and 27 deletions

View File

@ -80,21 +80,21 @@ class Users(DetailHandler):
custom_methods = ['servicesPools', 'userServices'] custom_methods = ['servicesPools', 'userServices']
@staticmethod
def uuid_to_id(iterator):
for v in iterator:
v['id'] = v['uuid']
del v['uuid']
yield v
def getItems(self, parent: Authenticator, item: typing.Optional[str]): def getItems(self, parent: Authenticator, item: typing.Optional[str]):
# processes item to change uuid key for id
def uuid_to_id(iterable: typing.Iterable[typing.MutableMapping[str, typing.Any]]):
for v in iterable:
v['id'] = v['uuid']
del v['uuid']
yield v
logger.debug(item) logger.debug(item)
# Extract authenticator # Extract authenticator
try: try:
if item is None: if item is None:
values = list( values = list(
Users.uuid_to_id( uuid_to_id(
parent.users.all().values( (i for i in parent.users.all().values(
'uuid', 'uuid',
'name', 'name',
'real_name', 'real_name',
@ -104,7 +104,8 @@ class Users(DetailHandler):
'is_admin', 'is_admin',
'last_access', 'last_access',
'parent', 'parent',
) 'mfaData',
))
) )
) )
for res in values: for res in values:
@ -127,6 +128,7 @@ class Users(DetailHandler):
'is_admin', 'is_admin',
'last_access', 'last_access',
'parent', 'parent',
'mfaData',
), ),
) )
res['id'] = u.uuid res['id'] = u.uuid
@ -153,7 +155,7 @@ class Users(DetailHandler):
except Exception: except Exception:
return _('Current users') return _('Current users')
def getFields(self, parent): def getFields(self, parent: Authenticator):
return [ return [
{ {
'name': { 'name': {
@ -198,12 +200,16 @@ class Users(DetailHandler):
'staff_member', 'staff_member',
'is_admin', 'is_admin',
] ]
if self._params.get('name', '') == '': if self._params.get('name', '').strip() == '':
raise RequestError(_('Username cannot be empty')) raise RequestError(_('Username cannot be empty'))
if 'password' in self._params: if 'password' in self._params:
valid_fields.append('password') valid_fields.append('password')
self._params['password'] = cryptoManager().hash(self._params['password']) self._params['password'] = cryptoManager().hash(self._params['password'])
if 'mfaData' in self._params:
valid_fields.append('mfaData')
self._params['mfaData'] = self._params['mfaData'].strip()
fields = self.readFieldsFromParams(valid_fields) fields = self.readFieldsFromParams(valid_fields)
if not self._user.is_admin: if not self._user.is_admin:
@ -224,9 +230,8 @@ class Users(DetailHandler):
user.__dict__.update(fields) user.__dict__.update(fields)
logger.debug('User parent: %s', user.parent) logger.debug('User parent: %s', user.parent)
if auth.isExternalSource is False and ( # If internal auth, threat it "special"
user.parent is None or user.parent == '' if auth.isExternalSource is False and not user.parent:
):
groups = self.readFieldsFromParams(['groups'])['groups'] groups = self.readFieldsFromParams(['groups'])['groups']
logger.debug('Groups: %s', groups) logger.debug('Groups: %s', groups)
logger.debug('Got Groups %s', parent.groups.filter(uuid__in=groups)) logger.debug('Got Groups %s', parent.groups.filter(uuid__in=groups))
@ -414,7 +419,7 @@ class Groups(DetailHandler):
except Exception: except Exception:
raise self.invalidRequestException() raise self.invalidRequestException()
def saveItem(self, parent: Authenticator, item) -> None: def saveItem(self, parent: Authenticator, item: typing.Optional[str]) -> None:
group = None # Avoid warning on reference before assignment group = None # Avoid warning on reference before assignment
try: try:
is_meta = self._params['type'] == 'meta' is_meta = self._params['type'] == 'meta'
@ -429,7 +434,7 @@ class Groups(DetailHandler):
fields = self.readFieldsFromParams(valid_fields) fields = self.readFieldsFromParams(valid_fields)
is_pattern = fields.get('name', '').find('pat:') == 0 is_pattern = fields.get('name', '').find('pat:') == 0
auth = parent.getInstance() auth = parent.getInstance()
if item is None: # Create new if not item: # Create new
if not is_meta and not is_pattern: if not is_meta and not is_pattern:
auth.createGroup( auth.createGroup(
fields fields
@ -482,7 +487,9 @@ class Groups(DetailHandler):
except Exception: except Exception:
raise self.invalidItemException() raise self.invalidItemException()
def servicesPools(self, parent: Authenticator, item: str) -> typing.List[typing.Mapping[str, typing.Any]]: def servicesPools(
self, parent: Authenticator, item: str
) -> typing.List[typing.Mapping[str, typing.Any]]:
uuid = processUuid(item) uuid = processUuid(item)
group = parent.groups.get(uuid=processUuid(uuid)) group = parent.groups.get(uuid=processUuid(uuid))
res: typing.List[typing.Mapping[str, typing.Any]] = [] res: typing.List[typing.Mapping[str, typing.Any]] = []
@ -503,7 +510,9 @@ class Groups(DetailHandler):
return res return res
def users(self, parent: Authenticator, item: str) -> typing.List[typing.Mapping[str, typing.Any]]: def users(
self, parent: Authenticator, item: str
) -> typing.List[typing.Mapping[str, typing.Any]]:
uuid = processUuid(item) uuid = processUuid(item)
group = parent.groups.get(uuid=processUuid(uuid)) group = parent.groups.get(uuid=processUuid(uuid))

View File

@ -99,7 +99,7 @@ class InternalDBAuth(auths.Authenticator):
if self.reverseDns.isTrue(): if self.reverseDns.isTrue():
try: try:
return str( return str(
dns.resolver.query(dns.reversename.from_address(ip), 'PTR')[0] dns.resolver.query(dns.reversename.from_address(ip).to_text(), 'PTR')[0]
) )
except Exception: except Exception:
pass pass

View File

@ -178,6 +178,9 @@ class StorageAsDict(MutableMapping):
def get(self, key: str, default: typing.Any = None) -> typing.Any: def get(self, key: str, default: typing.Any = None) -> typing.Any:
return self[key] or default return self[key] or default
def delete(self, key: str) -> None:
self.__delitem__(key)
# Custom utility methods # Custom utility methods
@property @property
def group(self) -> str: def group(self) -> str:

View File

@ -1,4 +1,4 @@
# Generated by Django 3.2.10 on 2022-06-23 19:34 # Generated by Django 3.2.10 on 2022-07-04 21:20
from django.db import migrations, models from django.db import migrations, models
import django.db.models.deletion import django.db.models.deletion
@ -11,6 +11,11 @@ class Migration(migrations.Migration):
] ]
operations = [ operations = [
migrations.AddField(
model_name='user',
name='mfaData',
field=models.CharField(default='', max_length=128),
),
migrations.CreateModel( migrations.CreateModel(
name='MFA', name='MFA',
fields=[ fields=[

View File

@ -37,6 +37,7 @@ from django.db import models
from django.db.models import signals, Q, Count from django.db.models import signals, Q, Count
from uds.core.util import log from uds.core.util import log
from uds.core.util import storage
from .authenticator import Authenticator from .authenticator import Authenticator
from .util import UnsavedForeignKey from .util import UnsavedForeignKey
@ -67,6 +68,9 @@ class User(UUIDModel):
state = models.CharField(max_length=1, db_index=True) state = models.CharField(max_length=1, db_index=True)
password = models.CharField( password = models.CharField(
max_length=128, default='' max_length=128, default=''
) # Only used on "internal" sources or sources that "needs password"
mfaData = models.CharField(
max_length=128, default=''
) # Only used on "internal" sources ) # Only used on "internal" sources
staff_member = models.BooleanField( staff_member = models.BooleanField(
default=False default=False
@ -202,6 +206,26 @@ class User(UUIDModel):
# This group matches # This group matches
yield g yield g
# Get custom data
def getCustomData(self, key: str) -> typing.Optional[str]:
"""
Returns the custom data for this user for the provided key.
Usually custom data will be associated with transports, but can be custom data registered by ANY module.
Args:
key: key of the custom data to get
Returns:
The custom data for the key specified as a string (can be empty if key is not found).
If the key exists, the custom data will always contain something, but may be the values are the default ones.
"""
with storage.StorageAccess('manager' + self.manager.uuid) as store:
return store[self.uuid + '_' + key]
def __str__(self): def __str__(self):
return 'User {} (id:{}) from auth {}'.format( return 'User {} (id:{}) from auth {}'.format(
self.name, self.id, self.manager.name self.name, self.id, self.manager.name
@ -217,11 +241,15 @@ class User(UUIDModel):
:note: If destroy raises an exception, the deletion is not taken. :note: If destroy raises an exception, the deletion is not taken.
""" """
toDelete = kwargs['instance'] toDelete: User = kwargs['instance']
# first, we invoke removeUser. If this raises an exception, user will not # first, we invoke removeUser. If this raises an exception, user will not
# be removed # be removed
toDelete.getManager().removeUser(toDelete.name) toDelete.getManager().removeUser(toDelete.name)
# Remove related stored values
with storage.StorageAccess('manager' + toDelete.manager.uuid) as store:
for key in store.keys():
store.delete(key)
# now removes all "child" of this user, if it has children # now removes all "child" of this user, if it has children
User.objects.filter(parent=toDelete.id).delete() User.objects.filter(parent=toDelete.id).delete()

View File

@ -98,7 +98,7 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods
state_date = models.DateTimeField(db_index=True) state_date = models.DateTimeField(db_index=True)
creation_date = models.DateTimeField(db_index=True) creation_date = models.DateTimeField(db_index=True)
data = models.TextField(default='') data = models.TextField(default='')
user: 'models.ForeignKey[UserService, User]' = models.ForeignKey( user = models.ForeignKey(
User, User,
on_delete=models.CASCADE, on_delete=models.CASCADE,
related_name='userServices', related_name='userServices',
@ -394,7 +394,7 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods
self.os_state = state self.os_state = state
self.save(update_fields=['os_state', 'state_date']) self.save(update_fields=['os_state', 'state_date'])
def assignToUser(self, user: User) -> None: def assignToUser(self, user: typing.Optional[User]) -> None:
""" """
Assigns this user deployed service to an user. Assigns this user deployed service to an user.
@ -403,7 +403,7 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods
""" """
self.cache_level = 0 self.cache_level = 0
self.state_date = getSqlDatetime() self.state_date = getSqlDatetime()
self.user = user # type: ignore self.user = user
self.save(update_fields=['cache_level', 'state_date', 'user']) self.save(update_fields=['cache_level', 'state_date', 'user'])
def setInUse(self, inUse: bool) -> None: def setInUse(self, inUse: bool) -> None:

File diff suppressed because one or more lines are too long

View File

@ -487,6 +487,7 @@ gettext("Role");
gettext("Admin"); gettext("Admin");
gettext("Staff member"); gettext("Staff member");
gettext("User"); gettext("User");
gettext("MFA");
gettext("Groups"); gettext("Groups");
gettext("Cancel"); gettext("Cancel");
gettext("Ok"); gettext("Ok");

View File

@ -99,7 +99,7 @@
</svg> </svg>
</div> </div>
</uds-root> </uds-root>
<script src="/uds/res/admin/runtime.js?stamp=1656004218" defer></script><script src="/uds/res/admin/polyfills-es5.js?stamp=1656004218" nomodule defer></script><script src="/uds/res/admin/polyfills.js?stamp=1656004218" defer></script><script src="/uds/res/admin/main.js?stamp=1656004218" defer></script> <script src="/uds/res/admin/runtime.js?stamp=1656962877" defer></script><script src="/uds/res/admin/polyfills-es5.js?stamp=1656962877" nomodule defer></script><script src="/uds/res/admin/polyfills.js?stamp=1656962877" defer></script><script src="/uds/res/admin/main.js?stamp=1656962877" defer></script>
</body></html> </body></html>