1
0
mirror of https://github.com/dkmstr/openuds.git synced 2024-12-23 17:34:17 +03:00

Refactorin exceptions, types, constants, ...

This commit is contained in:
Adolfo Gómez García 2023-11-03 18:47:20 +01:00
parent d4ea0ad8c6
commit 29ff0081a4
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
54 changed files with 526 additions and 450 deletions

View File

@ -112,34 +112,50 @@ class Login(Handler):
raise AccessDenied('Too many fails')
try:
if (
'auth_id' not in self._params
and 'authId' not in self._params
and 'auth_id' not in self._params
and 'authSmallName' not in self._params
and 'authLabel' not in self._params
and 'auth_label' not in self._params
and 'auth' not in self._params
# if (
# 'auth_id' not in self._params
# and 'authId' not in self._params
# and 'auth_id' not in self._params
# and 'authSmallName' not in self._params
# and 'authLabel' not in self._params
# and 'auth_label' not in self._params
# and 'auth' not in self._params
# ):
# raise RequestError('Invalid parameters (no auth)')
# Check if we have a valid auth
if not any(
i in self._params
for i in ('auth_id', 'authId', 'authSmallName', 'authLabel', 'auth_label', 'auth')
):
raise RequestError('Invalid parameters (no auth)')
scrambler: str = ''.join(
random.SystemRandom().choice(string.ascii_letters + string.digits) for _ in range(32)
authId: typing.Optional[str] = self._params.get(
'auth_id',
self._params.get('authId', None), # Old compat, alias
)
authId: typing.Optional[str] = self._params.get('authId', self._params.get('auth_id', None))
authLabel: typing.Optional[str] = self._params.get(
'auth_label',
self._params.get('authSmallName', None),
self._params.get(
'authSmallName', # Old compat name
self._params.get('authLabel', None), # Old compat name
),
)
authName: typing.Optional[str] = self._params.get('auth', None)
platform: str = self._params.get('platform', self._request.os.os.value[0])
username: str
password: str
username, password = self._params['username'], self._params['password']
username: str = self._params['username']
password: str = self._params['password']
locale: str = self._params.get('locale', 'en')
if authName == 'admin' or authLabel == 'admin' or authId == '00000000-0000-0000-0000-000000000000':
# Generate a random scrambler
scrambler: str = CryptoManager.manager().randomString(32)
if (
authName == 'admin'
or authLabel == 'admin'
or authId == '00000000-0000-0000-0000-000000000000'
or (not authId and not authName and not authLabel)
):
if GlobalConfig.SUPER_USER_LOGIN.get(True) == username and CryptoManager().checkHash(
password, GlobalConfig.SUPER_USER_PASS.get(True)
):
@ -147,13 +163,6 @@ class Login(Handler):
return Login.result(result='ok', token=self.getAuthToken())
return Login.result(error='Invalid credentials')
# invalid login
if (
functools.reduce(lambda a, b: (a << 4) + b, [i for i in username.encode()])
== 474216907296766572900491101513
):
return Login.result(result=bytes([i ^ 64 for i in b'\x13(%+(`-!`3()%2!+)`!..)']).decode())
# Will raise an exception if no auth found
if authId:
auth = Authenticator.objects.get(uuid=processUuid(authId))
@ -162,8 +171,8 @@ class Login(Handler):
else:
auth = Authenticator.objects.get(small_name=authLabel)
if not password:
password = 'xdaf44tgas4xd5ñasdłe4g€@#½|«ð2' # nosec: Extrange password if credential left empty. Value is not important, just not empty
# No matter in fact the password, just not empty (so it can be encrypted, but will be invalid anyway)
password = password or CryptoManager().randomString(32)
logger.debug('Auth obj: %s', auth)
authResult = authenticate(username, password, auth, self._request, True)
@ -218,19 +227,22 @@ class Auths(Handler):
authenticated = False # By default, all handlers needs authentication
def auths(self) -> typing.Iterable[typing.Dict[str, typing.Any]]:
paramAll: bool = self._params.get('all', 'false') == 'true'
paramAll: bool = self._params.get('all', 'false').lower() == 'true'
auth: Authenticator
for auth in Authenticator.objects.all():
theType = auth.getType()
if paramAll or (theType.isCustom() is False and theType.typeType not in ('IP',)):
yield {
'authId': auth.uuid,
'authId': auth.uuid, # Deprecated, use 'auth_id'
'auth_id': auth.uuid,
'authSmallName': str(auth.small_name), # Deprecated
'authLabel': str(auth.small_name),
'authLabel': str(auth.small_name), # Deprecated, use 'auth_label'
'auth_label': str(auth.small_name),
'auth': auth.name,
'type': theType.typeType,
'priority': auth.priority,
'isCustom': theType.isCustom(),
'isCustom': theType.isCustom(), # Deprecated, use 'custom'
'custom': theType.isCustom(),
}
def get(self) -> typing.List[typing.Dict[str, typing.Any]]:

View File

@ -200,7 +200,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
)
) from e
raise RequestError(_('Element already exists (duplicate key error)')) from e
except exceptions.ValidationError as e:
except exceptions.validation.ValidationError as e:
if (
not item and service
): # Only remove partially saved element if creating new (if editing, ignore this)

View File

@ -39,14 +39,13 @@ from django.core.exceptions import ValidationError
from uds.core.util.state import State
from uds.core.auths.exceptions import AuthenticatorException
from uds.core.auths.user import User as aUser
from uds.core.util import log, ensure
from uds.core.util.model import processUuid
from uds.models import Authenticator, User, Group, ServicePool
from uds.core.managers.crypto import CryptoManager
from uds.REST import RequestError
from uds.core.consts.images import DEFAULT_THUMB_BASE64
from uds.core import consts, exceptions
from uds.REST.model import DetailHandler
@ -84,10 +83,9 @@ class Users(DetailHandler):
def getItems(self, parent: 'Model', item: typing.Optional[str]) -> typing.Any:
parent = ensure.is_instance(parent, Authenticator)
# processes item to change uuid key for id
def uuid_to_id(
iterable: typing.Iterable[typing.Any] # will get values from a queryset
):
def uuid_to_id(iterable: typing.Iterable[typing.Any]): # will get values from a queryset
for v in iterable:
v['id'] = v['uuid']
del v['uuid']
@ -140,9 +138,7 @@ class Users(DetailHandler):
)
res['id'] = u.uuid
res['role'] = (
res['staff_member']
and (res['is_admin'] and _('Admin') or _('Staff member'))
or _('User')
res['staff_member'] and (res['is_admin'] and _('Admin') or _('Staff member')) or _('User')
)
usr = aUser(u)
res['groups'] = [g.dbGroup().uuid for g in usr.groups()]
@ -155,9 +151,7 @@ class Users(DetailHandler):
def getTitle(self, parent: 'Model') -> str:
try:
return _('Users of {0}').format(
Authenticator.objects.get(
uuid=processUuid(self._kwargs['parent_id'])
).name
Authenticator.objects.get(uuid=processUuid(self._kwargs['parent_id'])).name
)
except Exception:
return _('Current users')
@ -245,16 +239,12 @@ class Users(DetailHandler):
if not auth.isExternalSource and not user.parent:
groups = self.readFieldsFromParams(['groups'])['groups']
# Save but skip meta groups, they are not real groups, but just a way to group users based on rules
user.groups.set(
g
for g in parent.groups.filter(uuid__in=groups)
if g.is_meta is False
)
user.groups.set(g for g in parent.groups.filter(uuid__in=groups) if g.is_meta is False)
except User.DoesNotExist:
raise self.invalidItemException() from None
except IntegrityError: # Duplicate key probably
raise RequestError(_('User already exists (duplicate key error)')) from None
except AuthenticatorException as e:
except exceptions.auth.AuthenticatorException as e:
raise RequestError(str(e)) from e
except ValidationError as e:
raise RequestError(str(e.message)) from e
@ -310,9 +300,7 @@ class Users(DetailHandler):
{
'id': i.uuid,
'name': i.name,
'thumb': i.image.thumb64
if i.image is not None
else DEFAULT_THUMB_BASE64,
'thumb': i.image.thumb64 if i.image is not None else consts.images.DEFAULT_THUMB_BASE64,
'user_services_count': i.userServices.exclude(
state__in=(State.REMOVED, State.ERROR)
).count(),
@ -367,9 +355,7 @@ class Groups(DetailHandler):
'meta_if_any': i.meta_if_any,
}
if i.is_meta:
val['groups'] = list(
x.uuid for x in i.groups.all().order_by('name')
)
val['groups'] = list(x.uuid for x in i.groups.all().order_by('name'))
res.append(val)
if multi:
return res
@ -475,9 +461,7 @@ class Groups(DetailHandler):
if is_meta:
# Do not allow to add meta groups to meta groups
group.groups.set(
i
for i in parent.groups.filter(uuid__in=self._params['groups'])
if i.is_meta is False
i for i in parent.groups.filter(uuid__in=self._params['groups']) if i.is_meta is False
)
if pools:
@ -489,7 +473,7 @@ class Groups(DetailHandler):
raise self.invalidItemException() from None
except IntegrityError: # Duplicate key probably
raise RequestError(_('User already exists (duplicate key error)')) from None
except AuthenticatorException as e:
except exceptions.auth.AuthenticatorException as e:
raise RequestError(str(e)) from e
except RequestError: # pylint: disable=try-except-raise
raise # Re-raise
@ -506,9 +490,7 @@ class Groups(DetailHandler):
except Exception:
raise self.invalidItemException() from None
def servicesPools(
self, parent: 'Model', item: str
) -> typing.List[typing.Mapping[str, typing.Any]]:
def servicesPools(self, parent: 'Model', item: str) -> typing.List[typing.Mapping[str, typing.Any]]:
parent = ensure.is_instance(parent, Authenticator)
uuid = processUuid(item)
group = parent.groups.get(uuid=processUuid(uuid))
@ -518,9 +500,7 @@ class Groups(DetailHandler):
{
'id': i.uuid,
'name': i.name,
'thumb': i.image.thumb64
if i.image is not None
else DEFAULT_THUMB_BASE64,
'thumb': i.image.thumb64 if i.image is not None else consts.images.DEFAULT_THUMB_BASE64,
'user_services_count': i.userServices.exclude(
state__in=(State.REMOVED, State.ERROR)
).count(),
@ -530,9 +510,7 @@ class Groups(DetailHandler):
return res
def users(
self, parent: 'Model', item: str
) -> typing.List[typing.Mapping[str, typing.Any]]:
def users(self, parent: 'Model', item: str) -> typing.List[typing.Mapping[str, typing.Any]]:
uuid = processUuid(item)
parent = ensure.is_instance(parent, Authenticator)
group = parent.groups.get(uuid=processUuid(uuid))

View File

@ -1156,7 +1156,7 @@ class ModelHandler(BaseModelHandler):
raise exceptions.NotFound('Item not found') from None
except IntegrityError: # Duplicate key probably
raise exceptions.RequestError('Element already exists (duplicate key error)') from None
except (exceptions.SaveException, udsExceptions.ValidationError) as e:
except (exceptions.SaveException, udsexceptions.validation.ValidationError) as e:
raise exceptions.RequestError(str(e)) from e
except (exceptions.RequestError, exceptions.ResponseError):
raise

View File

@ -104,12 +104,12 @@ class IPAuth(auths.Authenticator):
credentials: str, # pylint: disable=unused-argument
groupsManager: 'auths.GroupsManager',
request: 'ExtendedHttpRequest',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
# If credentials is a dict, that can't be sent directly from web interface, we allow entering
if username == self.getIp(request):
self.getGroups(username, groupsManager)
return auths.SUCCESS_AUTH
return auths.FAILED_AUTH
return types.auth.SUCCESS_AUTH
return types.auth.FAILED_AUTH
def isAccesibleFrom(self, request: 'ExtendedHttpRequest'):
"""
@ -127,15 +127,15 @@ class IPAuth(auths.Authenticator):
credentials: str, # pylint: disable=unused-argument
groupsManager: 'auths.GroupsManager',
request: 'ExtendedHttpRequest',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
# In fact, username does not matter, will get IP from request
username = self.getIp(request) # Override provided username and use source IP
self.getGroups(username, groupsManager)
if groupsManager.hasValidGroups() and self.dbObj().isValidUser(
username, True
):
return auths.SUCCESS_AUTH
return auths.FAILED_AUTH
return types.auth.SUCCESS_AUTH
return types.auth.FAILED_AUTH
@staticmethod
def test(env, data): # pylint: disable=unused-argument

View File

@ -39,7 +39,7 @@ import dns.resolver
import dns.reversename
from django.utils.translation import gettext_noop as _
from uds.core import auths, types
from uds.core import auths, types, exceptions, consts
from uds.core.auths.auth import authLogLogin
from uds.core.managers.crypto import CryptoManager
from uds.core.ui import gui
@ -147,25 +147,25 @@ class InternalDBAuth(auths.Authenticator):
credentials: str,
groupsManager: 'auths.GroupsManager',
request: 'ExtendedHttpRequest',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
username = username.lower()
dbAuth = self.dbObj()
try:
user: 'models.User' = dbAuth.users.get(name=username, state=State.ACTIVE)
except Exception:
authLogLogin(request, self.dbObj(), username, 'Invalid user')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
if user.parent: # Direct auth not allowed for "derived" users
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
# Internal Db Auth has its own groups. (That is, no external source). If a group is active it is valid
if CryptoManager().checkHash(credentials, user.password):
groupsManager.validate([g.name for g in user.groups.all()])
return auths.SUCCESS_AUTH
return types.auth.SUCCESS_AUTH
authLogLogin(request, self.dbObj(), username, 'Invalid password')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
def getGroups(self, username: str, groupsManager: 'auths.GroupsManager'):
dbAuth = self.dbObj()

View File

@ -375,7 +375,7 @@ class OAuth2Authenticator(auths.Authenticator):
def _processToken(
self, userInfo: typing.Mapping[str, typing.Any], gm: 'auths.GroupsManager'
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
# After this point, we don't mind about the token, we only need to authenticate user
# and get some basic info from it
@ -398,11 +398,11 @@ class OAuth2Authenticator(auths.Authenticator):
# We don't mind about the token, we only need to authenticate user
# and if we are here, the user is authenticated, so we can return SUCCESS_AUTH
return auths.AuthenticationResult(auths.AuthenticationState.SUCCESS, username=username)
return types.auth.AuthenticationResult(types.auth.AuthenticationState.SUCCESS, username=username)
def _processTokenOpenId(
self, token_id: str, nonce: str, gm: 'auths.GroupsManager'
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
# Get token headers, to extract algorithm
info = jwt.get_unverified_header(token_id)
logger.debug('Token headers: %s', info)
@ -428,19 +428,19 @@ class OAuth2Authenticator(auths.Authenticator):
pass
except Exception as e:
logger.error('Error decoding token: %s', e)
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
# All keys tested, none worked
logger.error('Invalid token received on OAuth2 callback')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
def initialize(self, values: typing.Optional[typing.Dict[str, typing.Any]]) -> None:
if not values:
return
if ' ' in values['name']:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
gettext('This kind of Authenticator does not support white spaces on field NAME')
)
@ -449,9 +449,9 @@ class OAuth2Authenticator(auths.Authenticator):
if self.responseType.value in ('code', 'pkce', 'openid+code'):
if self.commonGroups.value.strip() == '':
raise exceptions.ValidationError(gettext('Common groups is required for "code" response types'))
raise exceptions.validation.ValidationError(gettext('Common groups is required for "code" response types'))
if self.tokenEndpoint.value.strip() == '':
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
gettext('Token endpoint is required for "code" response types')
)
# infoEndpoint will not be necesary if the response of tokenEndpoint contains the user info
@ -459,7 +459,7 @@ class OAuth2Authenticator(auths.Authenticator):
if self.responseType.value == 'openid+token_id':
# Ensure we have a public key
if self.publicKey.value.strip() == '':
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
gettext('Public key is required for "openid+token_id" response type')
)
@ -473,7 +473,7 @@ class OAuth2Authenticator(auths.Authenticator):
parameters: 'types.auth.AuthCallbackParams',
gm: 'auths.GroupsManager',
request: 'types.request.ExtendedHttpRequest',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
match self.responseType.value:
case 'code' | 'pkce':
return self.authCallbackCode(parameters, gm, request)
@ -491,8 +491,8 @@ class OAuth2Authenticator(auths.Authenticator):
self,
request: 'types.request.ExtendedHttpRequest', # pylint: disable=unused-argument
username: str, # pylint: disable=unused-argument
) -> auths.AuthenticationResult:
return auths.SUCCESS_AUTH
) -> types.auth.AuthenticationResult:
return types.auth.SUCCESS_AUTH
def getJavascript(self, request: 'HttpRequest') -> typing.Optional[str]:
"""
@ -517,7 +517,7 @@ class OAuth2Authenticator(auths.Authenticator):
parameters: 'types.auth.AuthCallbackParams',
gm: 'auths.GroupsManager',
request: 'types.request.ExtendedHttpRequest',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
"""Process the callback for code authorization flow"""
state = parameters.get_params.get('state', '')
# Remove state from cache
@ -526,13 +526,13 @@ class OAuth2Authenticator(auths.Authenticator):
if not state or not code_verifier:
logger.error('Invalid state received on OAuth2 callback')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
# Get the code
code = parameters.get_params.get('code', '')
if code == '':
logger.error('Invalid code received on OAuth2 callback')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
# Restore code_verifier "none" to None
if code_verifier == 'none':
@ -546,7 +546,7 @@ class OAuth2Authenticator(auths.Authenticator):
parameters: 'types.auth.AuthCallbackParams',
gm: 'auths.GroupsManager',
request: 'types.request.ExtendedHttpRequest',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
"""Process the callback for PKCE authorization flow"""
state = parameters.get_params.get('state', '')
stateValue = self.cache.get(state)
@ -554,7 +554,7 @@ class OAuth2Authenticator(auths.Authenticator):
if not state or not stateValue:
logger.error('Invalid state received on OAuth2 callback')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
# Get the token, token_type, expires
token = TokenInfo(
@ -574,7 +574,7 @@ class OAuth2Authenticator(auths.Authenticator):
parameters: 'types.auth.AuthCallbackParams',
gm: 'auths.GroupsManager',
request: 'types.request.ExtendedHttpRequest',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
"""Process the callback for OpenID authorization flow"""
state = parameters.post_params.get('state', '')
nonce = self.cache.get(state)
@ -582,20 +582,20 @@ class OAuth2Authenticator(auths.Authenticator):
if not state or not nonce:
logger.error('Invalid state received on OAuth2 callback')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
# Get the code
code = parameters.post_params.get('code', '')
if code == '':
logger.error('Invalid code received on OAuth2 callback')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
# Get the token, token_type, expires
token = self._requestToken(code)
if not token.id_token:
logger.error('No id_token received on OAuth2 callback')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
return self._processTokenOpenId(token.id_token, nonce, gm)
@ -604,7 +604,7 @@ class OAuth2Authenticator(auths.Authenticator):
parameters: 'types.auth.AuthCallbackParams',
gm: 'auths.GroupsManager',
request: 'types.request.ExtendedHttpRequest',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
"""Process the callback for OpenID authorization flow"""
state = parameters.post_params.get('state', '')
nonce = self.cache.get(state)
@ -612,12 +612,12 @@ class OAuth2Authenticator(auths.Authenticator):
if not state or not nonce:
logger.error('Invalid state received on OAuth2 callback')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
# Get the id_token
id_token = parameters.post_params.get('id_token', '')
if id_token == '':
logger.error('Invalid id_token received on OAuth2 callback')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
return self._processTokenOpenId(id_token, nonce, gm)

View File

@ -35,7 +35,7 @@ import typing
from django.utils.translation import gettext_noop as _
from uds.core import auths, types
from uds.core import auths, types, consts
from uds.core.auths.auth import authLogLogin
from uds.core.managers.crypto import CryptoManager
from uds.core.ui import gui
@ -146,7 +146,7 @@ class RadiusAuth(auths.Authenticator):
credentials: str,
groupsManager: 'auths.GroupsManager',
request: 'ExtendedHttpRequest',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
try:
connection = self.radiusClient()
groups, mfaCode, state = connection.authenticate(username=username, password=credentials, mfaField=self.mfaAttr.value.strip())
@ -167,7 +167,7 @@ class RadiusAuth(auths.Authenticator):
username,
'Access denied by Raiuds',
)
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
if self.globalGroup.value.strip():
groups.append(self.globalGroup.value.strip())
@ -179,7 +179,7 @@ class RadiusAuth(auths.Authenticator):
# Validate groups
groupsManager.validate(groups)
return auths.SUCCESS_AUTH
return types.auth.SUCCESS_AUTH
def getGroups(self, username: str, groupsManager: 'auths.GroupsManager') -> None:
with self.storage.map() as storage:

View File

@ -38,7 +38,7 @@ import typing
import ldap
from django.utils.translation import gettext_noop as _
from uds.core import auths, exceptions, types
from uds.core import auths, exceptions, types, consts
from uds.core.auths.auth import authLogLogin
from uds.core.ui import gui
from uds.core.util import ldaputil, auth as auth_utils
@ -61,7 +61,6 @@ LDAP_RESULT_LIMIT = 100
class RegexLdap(auths.Authenticator):
host = gui.TextField(
length=64,
label=_('Host'),
@ -80,9 +79,7 @@ class RegexLdap(auths.Authenticator):
ssl = gui.CheckBoxField(
label=_('Use SSL'),
order=3,
tooltip=_(
'If checked, the connection will be ssl, using port 636 instead of 389'
),
tooltip=_('If checked, the connection will be ssl, using port 636 instead of 389'),
)
username = gui.TextField(
length=64,
@ -113,9 +110,7 @@ class RegexLdap(auths.Authenticator):
label=_('Verify SSL'),
default=True,
order=11,
tooltip=_(
'If checked, SSL verification will be enforced. If not, SSL verification will be disabled'
),
tooltip=_('If checked, SSL verification will be enforced. If not, SSL verification will be disabled'),
tab=types.ui.Tab.ADVANCED,
)
certificate = gui.TextField(
@ -184,9 +179,7 @@ class RegexLdap(auths.Authenticator):
label=_('Alt. class'),
default='',
order=25,
tooltip=_(
'Class for LDAP objects that will be also checked for groups retrieval (normally empty)'
),
tooltip=_('Class for LDAP objects that will be also checked for groups retrieval (normally empty)'),
required=False,
tab=_('Advanced'),
)
@ -327,9 +320,9 @@ class RegexLdap(auths.Authenticator):
def unmarshal(self, data: bytes) -> None:
vals = data.decode('utf8').split('\t')
self._verifySsl = False # Backward compatibility
self._mfaAttr = '' # Backward compatibility
self._certificate = '' # Backward compatibility
self._verifySsl = False # Backward compatibility
self._mfaAttr = '' # Backward compatibility
self._certificate = '' # Backward compatibility
# Common
logger.debug('Common: %s', vals[1:11])
@ -386,7 +379,7 @@ class RegexLdap(auths.Authenticator):
@return: Connection established
@raise exception: If connection could not be established
"""
if self._connection is None: # If connection is not established, try to connect
if self._connection is None: # If connection is not established, try to connect
self._connection = ldaputil.connection(
self._username,
self._password,
@ -485,7 +478,7 @@ class RegexLdap(auths.Authenticator):
credentials: str,
groupsManager: 'auths.GroupsManager',
request: 'ExtendedHttpRequest',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
"""
Must authenticate the user.
We can have to different situations here:
@ -500,21 +493,15 @@ class RegexLdap(auths.Authenticator):
usr = self.__getUser(username)
if usr is None:
authLogLogin(
request, self.dbObj(), username, 'Invalid user'
)
return auths.FAILED_AUTH
authLogLogin(request, self.dbObj(), username, 'Invalid user')
return types.auth.FAILED_AUTH
try:
# Let's see first if it credentials are fine
self.__connectAs(
usr['dn'], credentials
) # Will raise an exception if it can't connect
self.__connectAs(usr['dn'], credentials) # Will raise an exception if it can't connect
except Exception:
authLogLogin(
request, self.dbObj(), username, 'Invalid password'
)
return auths.FAILED_AUTH
authLogLogin(request, self.dbObj(), username, 'Invalid password')
return types.auth.FAILED_AUTH
# store the user mfa attribute if it is set
if self._mfaAttr:
@ -525,10 +512,10 @@ class RegexLdap(auths.Authenticator):
groupsManager.validate(self.__getGroups(usr))
return auths.SUCCESS_AUTH
return types.auth.SUCCESS_AUTH
except Exception:
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
def createUser(self, usrData: typing.Dict[str, str]) -> None:
"""
@ -541,7 +528,7 @@ class RegexLdap(auths.Authenticator):
"""
res = self.__getUser(usrData['name'])
if res is None:
raise auths.exceptions.AuthenticatorException(_('Username not found'))
raise exceptions.auth.AuthenticatorException(_('Username not found'))
# Fills back realName field
usrData['real_name'] = self.__getUserRealName(res)
@ -572,7 +559,7 @@ class RegexLdap(auths.Authenticator):
"""
user = self.__getUser(username)
if user is None:
raise auths.exceptions.AuthenticatorException(_('Username not found'))
raise exceptions.auth.AuthenticatorException(_('Username not found'))
groups = self.__getGroups(user)
groupsManager.validate(groups)
@ -597,9 +584,7 @@ class RegexLdap(auths.Authenticator):
return res
except Exception as e:
logger.exception("Exception: ")
raise auths.exceptions.AuthenticatorException(
_('Too many results, be more specific')
) from e
raise exceptions.auth.AuthenticatorException(_('Too many results, be more specific')) from e
@staticmethod
def test(env, data):
@ -607,9 +592,7 @@ class RegexLdap(auths.Authenticator):
auth = RegexLdap(None, env, data) # type: ignore # Regexldap does not use "dbAuth", so it's safe...
return auth.testConnection()
except Exception as e:
logger.error(
'Exception found testing Simple LDAP auth %s: %s', e.__class__, e
)
logger.error('Exception found testing Simple LDAP auth %s: %s', e.__class__, e)
return [False, "Error testing connection"]
def testConnection(self):
@ -638,9 +621,7 @@ class RegexLdap(auths.Authenticator):
raise Exception()
return [
False,
_(
'Ldap user class seems to be incorrect (no user found by that class)'
),
_('Ldap user class seems to be incorrect (no user found by that class)'),
]
except Exception: # nosec: Control flow
# If found 1 or more, all right
@ -662,9 +643,7 @@ class RegexLdap(auths.Authenticator):
raise Exception()
return [
False,
_(
'Ldap user id attr is probably wrong (can\'t find any user with both conditions)'
),
_('Ldap user id attr is probably wrong (can\'t find any user with both conditions)'),
]
except Exception: # nosec: Control flow
# If found 1 or more, all right
@ -691,9 +670,7 @@ class RegexLdap(auths.Authenticator):
continue
return [
False,
_(
'Ldap group id attribute seems to be incorrect (no group found by that attribute)'
),
_('Ldap group id attribute seems to be incorrect (no group found by that attribute)'),
]
# Now try to test regular expression to see if it matches anything (

View File

@ -44,7 +44,7 @@ from onelogin.saml2.auth import OneLogin_Saml2_Auth
from onelogin.saml2.idp_metadata_parser import OneLogin_Saml2_IdPMetadataParser
from onelogin.saml2.settings import OneLogin_Saml2_Settings
from uds.core import auths, exceptions, types
from uds.core import auths, exceptions, types, consts
from uds.core.managers.crypto import CryptoManager
from uds.core.types.request import ExtendedHttpRequest
from uds.core.ui import gui
@ -335,7 +335,7 @@ class SAMLAuthenticator(auths.Authenticator):
return
if ' ' in values['name']:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
gettext('This kind of Authenticator does not support white spaces on field NAME')
)
@ -344,7 +344,7 @@ class SAMLAuthenticator(auths.Authenticator):
# This is in fact not needed, but we may say something useful to user if we check this
if self.serverCertificate.value.startswith('-----BEGIN CERTIFICATE-----\n') is False:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
gettext(
'Server certificate should be a valid PEM (PEM certificates starts with -----BEGIN CERTIFICATE-----)'
)
@ -353,13 +353,13 @@ class SAMLAuthenticator(auths.Authenticator):
try:
CryptoManager().loadCertificate(self.serverCertificate.value)
except Exception as e:
raise exceptions.ValidationError(gettext('Invalid server certificate. ') + str(e))
raise exceptions.validation.ValidationError(gettext('Invalid server certificate. ') + str(e))
if (
self.privateKey.value.startswith('-----BEGIN RSA PRIVATE KEY-----\n') is False
and self.privateKey.value.startswith('-----BEGIN PRIVATE KEY-----\n') is False
):
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
gettext(
'Private key should be a valid PEM (PEM private keys starts with -----BEGIN RSA PRIVATE KEY-----'
)
@ -368,12 +368,12 @@ class SAMLAuthenticator(auths.Authenticator):
try:
CryptoManager().loadPrivateKey(self.privateKey.value)
except Exception as e:
raise exceptions.ValidationError(gettext('Invalid private key. ') + str(e))
raise exceptions.validation.ValidationError(gettext('Invalid private key. ') + str(e))
if not security.checkCertificateMatchPrivateKey(
cert=self.serverCertificate.value, key=self.privateKey.value
):
raise exceptions.ValidationError(gettext('Certificate and private key do not match'))
raise exceptions.validation.ValidationError(gettext('Certificate and private key do not match'))
request: 'ExtendedHttpRequest' = values['_request']
@ -394,7 +394,7 @@ class SAMLAuthenticator(auths.Authenticator):
)
idpMetadata = resp.content.decode()
except Exception as e:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
gettext('Can\'t fetch url {0}: {1}').format(self.idpMetadata.value, str(e))
)
fromUrl = True
@ -405,7 +405,7 @@ class SAMLAuthenticator(auths.Authenticator):
xml.sax.parseString(idpMetadata, xml.sax.ContentHandler()) # type: ignore # nosec: url provided by admin
except Exception as e:
msg = (gettext(' (obtained from URL)') if fromUrl else '') + str(e)
raise exceptions.ValidationError(gettext('XML does not seem valid for IDP Metadata ') + msg)
raise exceptions.validation.ValidationError(gettext('XML does not seem valid for IDP Metadata ') + msg)
# Now validate regular expressions, if they exists
auth_utils.validateRegexField(self.userNameAttr)
@ -469,7 +469,7 @@ class SAMLAuthenticator(auths.Authenticator):
val = resp.content.decode()
except Exception as e:
logger.error('Error fetching idp metadata: %s', e)
raise auths.exceptions.AuthenticatorException(gettext('Can\'t access idp metadata'))
raise exceptions.auth.AuthenticatorException(gettext('Can\'t access idp metadata'))
else:
val = self.idpMetadata.value
@ -535,7 +535,7 @@ class SAMLAuthenticator(auths.Authenticator):
metadata = saml_settings.get_sp_metadata()
errors = saml_settings.validate_metadata(metadata)
if len(errors) > 0:
raise auths.exceptions.AuthenticatorException(
raise exceptions.auth.AuthenticatorException(
gettext('Error validating SP metadata: ') + str(errors)
)
if isinstance(metadata, str):
@ -572,7 +572,7 @@ class SAMLAuthenticator(auths.Authenticator):
self,
req: typing.Dict[str, typing.Any],
request: 'ExtendedHttpRequestWithUser',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
# Convert HTTP-POST to HTTP-REDIRECT on SAMLResponse, for just in case...
if 'SAMLResponse' in req['post_data']:
if isinstance(req['post_data']['SAMLResponse'], list):
@ -596,15 +596,15 @@ class SAMLAuthenticator(auths.Authenticator):
logger.debug('Error on SLO: %s', auth.get_last_response_xml())
logger.debug('post_data: %s', req['post_data'])
logger.info('Errors processing logout request: %s', errors)
raise auths.exceptions.AuthenticatorException(gettext('Error processing SLO: ') + str(errors))
raise exceptions.auth.AuthenticatorException(gettext('Error processing SLO: ') + str(errors))
# Remove MFA related data
if request.user:
self.mfaClean(request.user.name)
return auths.AuthenticationResult(
success=auths.AuthenticationState.REDIRECT,
url=url or auths.AuthenticationInternalUrl.LOGIN.getUrl(),
return types.auth.AuthenticationResult(
success=types.auth.AuthenticationState.REDIRECT,
url=url or types.auth.AuthenticationInternalUrl.LOGIN.getUrl(),
)
# pylint: disable=too-many-locals,too-many-branches,too-many-statements
@ -613,7 +613,7 @@ class SAMLAuthenticator(auths.Authenticator):
parameters: 'types.auth.AuthCallbackParams',
gm: 'auths.GroupsManager',
request: 'ExtendedHttpRequestWithUser',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
req = self.getReqFromRequest(request, params=parameters)
if 'logout' in parameters.get_params:
@ -624,13 +624,13 @@ class SAMLAuthenticator(auths.Authenticator):
auth = OneLogin_Saml2_Auth(req, settings)
auth.process_response()
except Exception as e:
raise auths.exceptions.AuthenticatorException(gettext('Error processing SAML response: ') + str(e))
raise exceptions.auth.AuthenticatorException(gettext('Error processing SAML response: ') + str(e))
errors = auth.get_errors()
if errors:
raise auths.exceptions.AuthenticatorException('SAML response error: ' + str(errors))
raise exceptions.auth.AuthenticatorException('SAML response error: ' + str(errors))
if not auth.is_authenticated():
raise auths.exceptions.AuthenticatorException(gettext('SAML response not authenticated'))
raise exceptions.auth.AuthenticatorException(gettext('SAML response not authenticated'))
# Store SAML attributes
request.session['SAML'] = {
@ -648,7 +648,7 @@ class SAMLAuthenticator(auths.Authenticator):
# 'RelayState' in req['post_data']
# and OneLogin_Saml2_Utils.get_self_url(req) != req['post_data']['RelayState']
# ):
# return auths.AuthenticationResult(
# return types.auth.AuthenticationResult(
# success=auths.AuthenticationSuccess.REDIRECT,
# url=auth.redirect_to(req['post_data']['RelayState'])
# )
@ -658,7 +658,7 @@ class SAMLAuthenticator(auths.Authenticator):
attributes.update(auth.get_friendlyname_attributes())
if not attributes:
raise auths.exceptions.AuthenticatorException(gettext('No attributes returned from IdP'))
raise exceptions.auth.AuthenticatorException(gettext('No attributes returned from IdP'))
logger.debug("Attributes: %s", attributes)
# Now that we have attributes, we can extract values from this, map groups, etc...
@ -689,11 +689,11 @@ class SAMLAuthenticator(auths.Authenticator):
gm.validate(groups)
return auths.AuthenticationResult(success=auths.AuthenticationState.SUCCESS, username=username)
return types.auth.AuthenticationResult(success=types.auth.AuthenticationState.SUCCESS, username=username)
def logout(self, request: 'ExtendedHttpRequest', username: str) -> auths.AuthenticationResult:
def logout(self, request: 'ExtendedHttpRequest', username: str) -> types.auth.AuthenticationResult:
if not self.globalLogout.isTrue():
return auths.SUCCESS_AUTH
return types.auth.SUCCESS_AUTH
req = self.getReqFromRequest(request)
@ -710,10 +710,10 @@ class SAMLAuthenticator(auths.Authenticator):
self.mfaClean(username)
if not saml:
return auths.SUCCESS_AUTH
return types.auth.SUCCESS_AUTH
return auths.AuthenticationResult(
success=auths.AuthenticationState.REDIRECT,
return types.auth.AuthenticationResult(
success=types.auth.AuthenticationState.REDIRECT,
url=auth.logout(
name_id=saml.get('nameid'),
session_index=saml.get('session_index'),

View File

@ -34,10 +34,9 @@ import logging
import typing
from django.utils.translation import gettext_noop as _
from uds.core.auths.authenticator import AuthenticationResult, AuthenticationState
from uds.core.types.request import ExtendedHttpRequest
from uds.core.ui import gui
from uds.core import auths, exceptions
from uds.core import auths, exceptions, types, consts
if typing.TYPE_CHECKING:
from django.http import (
@ -119,9 +118,7 @@ class SampleAuth(auths.Authenticator):
# : We will define a simple form where we will use a simple
# : list editor to allow entering a few group names
groups = gui.EditableListField(
label=_('Groups'), default=['Gods', 'Daemons', 'Mortals']
)
groups = gui.EditableListField(label=_('Groups'), default=['Gods', 'Daemons', 'Mortals'])
def initialize(self, values: typing.Optional[typing.Dict[str, typing.Any]]) -> None:
"""
@ -134,7 +131,7 @@ class SampleAuth(auths.Authenticator):
# unserialization, and at this point all will be default values
# so self.groups.value will be []
if values and len(self.groups.value) < 2:
raise exceptions.ValidationError(_('We need more than two groups!'))
raise exceptions.validation.ValidationError(_('We need more than two groups!'))
def searchUsers(self, pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
"""
@ -175,7 +172,7 @@ class SampleAuth(auths.Authenticator):
credentials: str,
groupsManager: 'GroupsManager',
request: 'ExtendedHttpRequest', # pylint: disable=unused-argument
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
"""
This method is invoked by UDS whenever it needs an user to be authenticated.
It is used from web interface, but also from administration interface to
@ -217,10 +214,8 @@ class SampleAuth(auths.Authenticator):
:note: groupsManager is an in/out parameter
"""
if (
username != credentials
): # All users with same username and password are allowed
return auths.FAILED_AUTH
if username != credentials: # All users with same username and password are allowed
return types.auth.FAILED_AUTH
# Now the tricky part. We will make this user belong to groups that contains at leat
# two letters equals to the groups names known by UDS
@ -230,7 +225,7 @@ class SampleAuth(auths.Authenticator):
if len(set(g.lower()).intersection(username.lower())) >= 2:
groupsManager.validate(g)
return auths.SUCCESS_AUTH
return types.auth.SUCCESS_AUTH
def getGroups(self, username: str, groupsManager: 'auths.GroupsManager'):
"""
@ -247,9 +242,7 @@ class SampleAuth(auths.Authenticator):
if len(set(g.lower()).intersection(username.lower())) >= 2:
groupsManager.validate(g)
def getJavascript(
self, request: 'HttpRequest' # pylint: disable=unused-argument
) -> typing.Optional[str]:
def getJavascript(self, request: 'HttpRequest') -> typing.Optional[str]: # pylint: disable=unused-argument
"""
If we override this method from the base one, we are telling UDS
that we want to draw our own authenticator.
@ -272,20 +265,16 @@ class SampleAuth(auths.Authenticator):
# I know, this is a bit ugly, but this is just a sample :-)
res = '<p>Login name: <input id="logname" type="text"/></p>'
res += (
'<p><a href="" onclick="window.location.replace(\''
+ self.callbackUrl()
+ '?user='
)
res += '<p><a href="" onclick="window.location.replace(\'' + self.callbackUrl() + '?user='
res += '\' + $(\'#logname\').val()); return false;">Login</a></p>'
return res
def authCallback(
self,
parameters: typing.Dict[str, typing.Any],
gm: 'auths.GroupsManager', # pylint: disable=unused-argument
gm: 'auths.GroupsManager', # pylint: disable=unused-argument
request: 'ExtendedHttpRequestWithUser', # pylint: disable=unused-argument
) -> AuthenticationResult:
) -> types.auth.AuthenticationResult:
"""
We provide this as a sample of callback for an user.
We will accept all petitions that has "user" parameter
@ -301,7 +290,7 @@ class SampleAuth(auths.Authenticator):
"""
user = parameters.get('user', None)
return AuthenticationResult(AuthenticationState.SUCCESS, username=user)
return types.auth.AuthenticationResult(types.auth.AuthenticationState.SUCCESS, username=user)
def createUser(self, usrData: typing.Dict[str, str]) -> None:
"""

View File

@ -36,7 +36,7 @@ import ldap
import ldap.filter
from django.utils.translation import gettext_noop as _
from uds.core import auths, types
from uds.core import auths, types, consts, exceptions
from uds.core.auths.auth import authLogLogin
from uds.core.ui import gui
from uds.core.util import ldaputil
@ -49,6 +49,7 @@ logger = logging.getLogger(__name__)
LDAP_RESULT_LIMIT = 100
# pylint: disable=too-many-instance-attributes
class SimpleLDAPAuthenticator(auths.Authenticator):
host = gui.TextField(
@ -69,9 +70,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
ssl = gui.CheckBoxField(
label=_('Use SSL'),
order=3,
tooltip=_(
'If checked, the connection will be ssl, using port 636 instead of 389'
),
tooltip=_('If checked, the connection will be ssl, using port 636 instead of 389'),
)
username = gui.TextField(
length=64,
@ -103,9 +102,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
label=_('Verify SSL'),
default=True,
order=11,
tooltip=_(
'If checked, SSL verification will be enforced. If not, SSL verification will be disabled'
),
tooltip=_('If checked, SSL verification will be enforced. If not, SSL verification will be disabled'),
tab=types.ui.Tab.ADVANCED,
)
certificate = gui.TextField(
@ -149,9 +146,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
label=_('User Name Attr'),
default='uid',
order=33,
tooltip=_(
'Attributes that contains the user name (list of comma separated values)'
),
tooltip=_('Attributes that contains the user name (list of comma separated values)'),
required=True,
tab=_('Ldap info'),
)
@ -240,9 +235,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
self._userIdAttr = values['userIdAttr']
self._groupIdAttr = values['groupIdAttr']
self._memberAttr = values['memberAttr']
self._userNameAttr = values['userNameAttr'].replace(
' ', ''
) # Removes white spaces
self._userNameAttr = values['userNameAttr'].replace(' ', '') # Removes white spaces
self._mfaAttr = values['mfaAttr']
self._verifySsl = gui.toBool(values['verifySsl'])
self._certificate = values['certificate']
@ -438,7 +431,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
credentials: str,
groupsManager: 'auths.GroupsManager',
request: 'ExtendedHttpRequest',
) -> auths.AuthenticationResult:
) -> types.auth.AuthenticationResult:
'''
Must authenticate the user.
We can have to different situations here:
@ -454,18 +447,14 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
if user is None:
authLogLogin(request, self.dbObj(), username, 'Invalid user')
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
try:
# Let's see first if it credentials are fine
self.__connectAs(
user['dn'], credentials
) # Will raise an exception if it can't connect
self.__connectAs(user['dn'], credentials) # Will raise an exception if it can't connect
except Exception:
authLogLogin(
request, self.dbObj(), username, 'Invalid password'
)
return auths.FAILED_AUTH
authLogLogin(request, self.dbObj(), username, 'Invalid password')
return types.auth.FAILED_AUTH
# store the user mfa attribute if it is set
if self._mfaAttr:
@ -476,10 +465,10 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
groupsManager.validate(self.__getGroups(user))
return auths.SUCCESS_AUTH
return types.auth.SUCCESS_AUTH
except Exception:
return auths.FAILED_AUTH
return types.auth.FAILED_AUTH
def createUser(self, usrData: typing.Dict[str, str]) -> None:
'''
@ -489,7 +478,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
'''
res = self.__getUser(usrData['name'])
if res is None:
raise auths.exceptions.AuthenticatorException(_('Username not found'))
raise exceptions.auth.AuthenticatorException(_('Username not found'))
# Fills back realName field
usrData['real_name'] = self.__getUserRealName(res)
@ -522,7 +511,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
'''
res = self.__getGroup(groupData['name'])
if res is None:
raise auths.exceptions.AuthenticatorException(_('Group not found'))
raise exceptions.auth.AuthenticatorException(_('Group not found'))
def getGroups(self, username: str, groupsManager: 'auths.GroupsManager'):
'''
@ -532,7 +521,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
'''
user = self.__getUser(username)
if user is None:
raise auths.exceptions.AuthenticatorException(_('Username not found'))
raise exceptions.auth.AuthenticatorException(_('Username not found'))
groupsManager.validate(self.__getGroups(user))
def searchUsers(self, pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
@ -555,9 +544,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
return res
except Exception as e:
logger.exception("Exception: ")
raise auths.exceptions.AuthenticatorException(
_('Too many results, be more specific')
) from e
raise exceptions.auth.AuthenticatorException(_('Too many results, be more specific')) from e
def searchGroups(self, pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
try:
@ -574,9 +561,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
return res
except Exception as e:
logger.exception("Exception: ")
raise auths.exceptions.AuthenticatorException(
_('Too many results, be more specific')
) from e
raise exceptions.auth.AuthenticatorException(_('Too many results, be more specific')) from e
@staticmethod
def test(env, data) -> typing.List[typing.Any]:
@ -589,9 +574,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
def testConnection(
self,
) -> typing.List[
typing.Any
]: # pylint: disable=too-many-return-statements,too-many-branches
) -> typing.List[typing.Any]: # pylint: disable=too-many-return-statements,too-many-branches
try:
con = self.__connection()
except Exception as e:
@ -617,9 +600,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
raise Exception()
return [
False,
_(
'Ldap user class seems to be incorrect (no user found by that class)'
),
_('Ldap user class seems to be incorrect (no user found by that class)'),
]
except Exception: # nosec: Flow control
# If found 1 or more, all right
@ -640,9 +621,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
raise Exception()
return [
False,
_(
'Ldap group class seems to be incorrect (no group found by that class)'
),
_('Ldap group class seems to be incorrect (no group found by that class)'),
]
except Exception: # nosec: Flow control
# If found 1 or more, all right
@ -663,9 +642,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
raise Exception()
return [
False,
_(
'Ldap user id attribute seems to be incorrect (no user found by that attribute)'
),
_('Ldap user id attribute seems to be incorrect (no user found by that attribute)'),
]
except Exception: # nosec: Flow control
# If found 1 or more, all right
@ -686,9 +663,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
raise Exception()
return [
False,
_(
'Ldap group id attribute seems to be incorrect (no group found by that attribute)'
),
_('Ldap group id attribute seems to be incorrect (no group found by that attribute)'),
]
except Exception: # nosec: Flow control
# If found 1 or more, all right
@ -738,9 +713,7 @@ class SimpleLDAPAuthenticator(auths.Authenticator):
ok = True
break
if ok is False:
raise Exception(
_('Can\'t locate any group with the membership attribute specified')
)
raise Exception(_('Can\'t locate any group with the membership attribute specified'))
except Exception as e:
return [False, str(e)]

View File

@ -34,17 +34,11 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from .authenticator import (
Authenticator,
AuthenticationResult,
AuthenticationState,
AuthenticationInternalUrl,
SUCCESS_AUTH,
FAILED_AUTH,
)
from .authfactory import AuthsFactory
from .user import User
from .group import Group
from .groups_manager import GroupsManager
from . import exceptions
def factory() -> AuthsFactory:

View File

@ -49,7 +49,7 @@ from django.urls import reverse
from django.utils.translation import gettext as _
from uds.core import auths, types
from uds.core import auths, types, exceptions
from uds.core.types.request import ExtendedHttpRequest
from uds.core.util import log
from uds.core.util import net
@ -58,7 +58,7 @@ from uds.core.util.config import GlobalConfig
from uds.core.util.stats import events
from uds.core.util.state import State
from uds.core.managers.crypto import CryptoManager
from uds.core.auths import Authenticator as AuthenticatorInstance, SUCCESS_AUTH
from uds.core.auths import Authenticator as AuthenticatorInstance
from uds import models
@ -309,12 +309,12 @@ def authenticate(
else:
res = authInstance.internalAuthenticate(username, password, gm, request)
if res.success == auths.AuthenticationState.FAIL:
if res.success == types.auth.AuthenticationState.FAIL:
logger.debug('Authentication failed')
# Maybe it's an redirection on auth failed?
return AuthResult()
if res.success == auths.AuthenticationState.REDIRECT:
if res.success == types.auth.AuthenticationState.REDIRECT:
return AuthResult(url=res.url)
logger.debug('Groups manager: %s', gm)
@ -358,15 +358,15 @@ def authenticateViaCallback(
# If there is no callback for this authenticator...
if authInstance.authCallback is auths.Authenticator.authCallback:
raise auths.exceptions.InvalidAuthenticatorException()
raise exceptions.auth.InvalidAuthenticatorException()
result = authInstance.authCallback(params, gm, request)
if result.success == auths.AuthenticationState.FAIL or (
result.success == auths.AuthenticationState.SUCCESS and not gm.hasValidGroups()
if result.success == types.auth.AuthenticationState.FAIL or (
result.success == types.auth.AuthenticationState.SUCCESS and not gm.hasValidGroups()
):
raise auths.exceptions.InvalidUserException('User doesn\'t has access to UDS')
raise exceptions.auth.InvalidUserException('User doesn\'t has access to UDS')
if result.success == auths.AuthenticationState.REDIRECT:
if result.success == types.auth.AuthenticationState.REDIRECT:
return AuthResult(url=result.url)
if result.username:
@ -374,7 +374,7 @@ def authenticateViaCallback(
else:
logger.warning('Authenticator %s returned empty username', authenticator.name)
raise auths.exceptions.InvalidUserException('User doesn\'t has access to UDS')
raise exceptions.auth.InvalidUserException('User doesn\'t has access to UDS')
def authCallbackUrl(authenticator: models.Authenticator) -> str:
@ -488,7 +488,7 @@ def webLogout(
authenticator = request.user.manager.getInstance()
username = request.user.name
logout = authenticator.logout(request, username)
if logout and logout.success == auths.AuthenticationState.REDIRECT:
if logout and logout.success == types.auth.AuthenticationState.REDIRECT:
exit_url = logout.url or exit_url
if request.user.id != ROOT_ID:
# Log the event if not root user

View File

@ -32,15 +32,13 @@ Base module for all authenticators
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import enum
import logging
import typing
from django.utils.translation import gettext_noop as _
from django.urls import reverse
from uds.core.module import Module
from uds.core import consts
from uds.core import consts, types
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
@ -57,40 +55,6 @@ if typing.TYPE_CHECKING:
logger = logging.getLogger(__name__)
class AuthenticationState(enum.IntEnum):
"""
Enumeration for authentication success
"""
FAIL = 0
SUCCESS = 1
REDIRECT = 2
class AuthenticationInternalUrl(enum.Enum):
"""
Enumeration for authentication success
"""
LOGIN = 'page.login'
def getUrl(self) -> str:
"""
Returns the url for the given internal url
"""
return reverse(self.value)
class AuthenticationResult(typing.NamedTuple):
success: AuthenticationState
url: typing.Optional[str] = None
username: typing.Optional[str] = None
FAILED_AUTH = AuthenticationResult(success=AuthenticationState.FAIL)
SUCCESS_AUTH = AuthenticationResult(success=AuthenticationState.SUCCESS)
class Authenticator(Module):
"""
This class represents the base interface to implement authenticators.
@ -202,7 +166,7 @@ class Authenticator(Module):
# : group class
groupType: typing.ClassVar[typing.Type[Group]] = Group
_dbObj: typing.Optional['models.Authenticator'] = None # Cached dbAuth object
_dbObj: typing.Optional['models.Authenticator'] = None # Cached dbAuth object
def __init__(
self,
@ -356,7 +320,7 @@ class Authenticator(Module):
credentials: str,
groupsManager: 'GroupsManager',
request: 'types.request.ExtendedHttpRequest',
) -> AuthenticationResult:
) -> types.auth.AuthenticationResult:
"""
This method must be overriden, and is responsible for authenticating
users.
@ -394,7 +358,7 @@ class Authenticator(Module):
This is done in this way, because UDS has only a subset of groups for this user, and
we let the authenticator decide inside wich groups of UDS this users is included.
"""
return FAILED_AUTH
return types.auth.FAILED_AUTH
def isAccesibleFrom(self, request: 'HttpRequest') -> bool:
"""
@ -429,7 +393,7 @@ class Authenticator(Module):
credentials: str,
groupsManager: 'GroupsManager',
request: 'types.request.ExtendedHttpRequest',
) -> AuthenticationResult:
) -> types.auth.AuthenticationResult:
"""
This method is provided so "plugins" (For example, a custom dispatcher), can test
the username/credentials in an alternative way.
@ -469,7 +433,7 @@ class Authenticator(Module):
self,
request: 'types.request.ExtendedHttpRequest',
username: str,
) -> AuthenticationResult:
) -> types.auth.AuthenticationResult:
"""
Invoked whenever an user logs out.
@ -495,7 +459,7 @@ class Authenticator(Module):
invoked if user requests "log out", but maybe it will never be invoked.
"""
return SUCCESS_AUTH
return types.auth.SUCCESS_AUTH
def webLogoutHook(
self,
@ -566,7 +530,7 @@ class Authenticator(Module):
parameters: 'types.auth.AuthCallbackParams',
gm: 'GroupsManager',
request: 'types.request.ExtendedHttpRequest',
) -> AuthenticationResult:
) -> types.auth.AuthenticationResult:
"""
There is a view inside UDS, an url, that will redirect the petition
to this callback.
@ -603,7 +567,7 @@ class Authenticator(Module):
There will be calls to getGroups one an again, and also to getRealName, not just
at login, but at future (from admin interface, at user editing for example)
"""
return FAILED_AUTH
return types.auth.FAILED_AUTH
def getInfo(
self, parameters: typing.Mapping[str, str]

View File

@ -43,4 +43,3 @@ DISABLED: typing.Final[str] = 'd'
NO_FILTERING: typing.Final[str] = 'n'
ALLOW: typing.Final[str] = 'a'
DENY: typing.Final[str] = 'd'

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2023 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from . import auth
from . import validation
from . import service
# Common exceptions inserted here
from .common import UDSException, BlockAccess

View File

@ -0,0 +1,54 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from . import common
class ActorException(common.UDSException):
"""
Base class for all Actor exceptions
"""
# Actor related exceptions
class NoActorComms(ActorException):
"""
Exception used to signal that the actor user service does not have comms url
"""
pass
class OldActorVersion(ActorException):
"""
Exception used to signal that the actor user service version is too old
"""

View File

@ -30,9 +30,9 @@
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from . import common
class AuthenticatorException(Exception):
class AuthenticatorException(common.UDSException):
"""
Generic authentication exception
"""

View File

@ -37,33 +37,8 @@ class UDSException(Exception):
Base class for all UDS exceptions
"""
class ValidationError(UDSException):
"""
Exception used to indicate that the params assigned are invalid
"""
class TransportError(UDSException):
"""
Exception used to indicate that the transport is not available
"""
class BlockAccess(UDSException):
"""
Exception used to signal that the access to a resource is blocked
"""
# Actor related exceptions
class NoActorComms(UDSException):
"""
Exception used to signal that the actor user service does not have comms url
"""
pass
class OldActorVersion(NoActorComms):
"""
Exception used to signal that the actor user service version is too old
"""

View File

@ -0,0 +1,40 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2023 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from .common import UDSException
class TransportError(UDSException):
"""
Exception used to indicate that the transport is not available
"""

View File

@ -0,0 +1,39 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2023 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
from .common import UDSException
class ValidationError(UDSException):
"""
Exception used to indicate that the params assigned are invalid
"""

View File

@ -52,6 +52,7 @@ from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
from django.conf import settings
from regex import E
from uds.core.util import singleton
@ -293,14 +294,21 @@ class CryptoManager(metaclass=singleton.Singleton):
elif isinstance(obj, bytes):
obj = obj.decode('utf8') # To string
else:
obj = str(obj)
try:
obj = str(obj)
except Exception:
obj = str(hash(obj)) # Get hash of object
return str(
uuid.uuid5(self._namespace, obj)
).lower() # I believe uuid returns a lowercase uuid always, but in case... :)
def randomString(self, length: int = 40, digits: bool = True) -> str:
base = string.ascii_letters + (string.digits if digits else '')
def randomString(self, length: int = 40, digits: bool = True, punctuation: bool = False) -> str:
base = (
string.ascii_letters
+ (string.digits if digits else '')
+ (string.punctuation if punctuation else '')
)
return ''.join(secrets.choice(base) for _ in range(length))
def unique(self) -> str:

View File

@ -63,20 +63,17 @@ class DownloadsManager(metaclass=singleton.Singleton):
@staticmethod
def manager() -> 'DownloadsManager':
return (
DownloadsManager()
) # Singleton pattern will return always the same instance
# Singleton pattern will return always the same instance
return DownloadsManager()
def registerDownloadable(
self, name: str, comment: str, path: str, mime: str = 'application/octet-stream'
):
def registerDownloadable(self, name: str, comment: str, path: str, mime: str = 'application/octet-stream'):
"""
Registers a downloadable file.
@param name: name shown
@param path: path to file
@params zip: If download as zip
"""
_id = CryptoManager().uuid(name)
_id = CryptoManager.manager().uuid(name)
self._downloadables[_id] = {
'name': name,
'comment': comment,
@ -89,9 +86,7 @@ class DownloadsManager(metaclass=singleton.Singleton):
def send(self, request, _id) -> HttpResponse:
if _id not in self._downloadables:
logger.error(
'Downloadable id %s not found in %s!!!', _id, self._downloadables
)
logger.error('Downloadable id %s not found in %s!!!', _id, self._downloadables)
raise Http404
return self._send_file(
request,
@ -106,8 +101,12 @@ class DownloadsManager(metaclass=singleton.Singleton):
memory at once. The FileWrapper will turn the file object into an
iterator for chunks of 8KB.
"""
wrapper = FileWrapper(open(filename, 'rb')) # pylint: disable=consider-using-with
response = HttpResponse(wrapper, content_type=mime)
response['Content-Length'] = os.path.getsize(filename)
response['Content-Disposition'] = 'attachment; filename=' + name
return response
try:
wrapper = FileWrapper(open(filename, 'rb')) # pylint: disable=consider-using-with
response = HttpResponse(wrapper, content_type=mime)
response['Content-Length'] = os.path.getsize(filename)
response['Content-Disposition'] = 'attachment; filename=' + name
return response
except Exception as e:
logger.error('Error sending file %s: %s', filename, e)
raise Http404

View File

@ -40,8 +40,8 @@ import typing
from django.utils.translation import gettext_noop as _, gettext
from uds.core.module import Module
from uds.core.util.model import getSqlDatetime
from uds.core.auths import exceptions
from uds.models.network import Network
from uds.core import exceptions
if typing.TYPE_CHECKING:
from uds.core.environment import Environment
@ -205,7 +205,7 @@ class MFA(Module):
If raises an error, the MFA code was not sent, and the user needs to enter the MFA code.
"""
logger.error('MFA.sendCode not implemented')
raise exceptions.MFAError('MFA.sendCode not implemented')
raise exceptions.auth.MFAError('MFA.sendCode not implemented')
def _getData(
self, request: 'ExtendedHttpRequest', userId: str
@ -326,7 +326,7 @@ class MFA(Module):
# if it is no more valid, raise an error
# Remove stored code and raise error
self._removeData(request, userId)
raise exceptions.MFAError('MFA Code expired')
raise exceptions.auth.MFAError('MFA Code expired')
# Check if the code is valid
if data[1] == code:
@ -337,7 +337,7 @@ class MFA(Module):
# Any error means invalid code
err = str(e)
raise exceptions.MFAError(err)
raise exceptions.auth.MFAError(err)
def resetData(
self,
@ -355,7 +355,7 @@ class MFA(Module):
"""
mfa = user.manager.mfa
if not mfa:
raise exceptions.MFAError('MFA is not enabled')
raise exceptions.auth.MFAError('MFA is not enabled')
return hashlib.sha3_256(
(user.name + (user.uuid or '') + mfa.uuid).encode()

View File

@ -30,12 +30,50 @@
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import typing
import enum
from django.urls import reverse
if typing.TYPE_CHECKING:
from django.http import HttpRequest
from django.http.request import QueryDict
class AuthenticationState(enum.IntEnum):
"""
Enumeration for authentication success
"""
FAIL = 0
SUCCESS = 1
REDIRECT = 2
class AuthenticationInternalUrl(enum.Enum):
"""
Enumeration for authentication success
"""
LOGIN = 'page.login'
LOGOUT = 'page.logout'
def getUrl(self) -> str:
"""
Returns the url for the given internal url
"""
return reverse(self.value)
class AuthenticationResult(typing.NamedTuple):
success: AuthenticationState
url: typing.Optional[str] = None
username: typing.Optional[str] = None
# Comodity values
FAILED_AUTH = AuthenticationResult(success=AuthenticationState.FAIL)
SUCCESS_AUTH = AuthenticationResult(success=AuthenticationState.SUCCESS)
class AuthCallbackParams(typing.NamedTuple):
'''Parameters passed to auth callback stage2
@ -61,3 +99,4 @@ class AuthCallbackParams(typing.NamedTuple):
post_params=request.POST.copy(),
query_string=request.META['QUERY_STRING'],
)

View File

@ -544,12 +544,12 @@ class gui:
elif pattern == types.ui.FieldPatternType.HOST:
try:
validators.validateHostname(self.value, allowDomain=True)
except exceptions.ValidationError:
except exceptions.validation.ValidationError:
validators.validateIpv4OrIpv6(self.value)
elif pattern == types.ui.FieldPatternType.PATH:
validators.validatePath(self.value)
return True
except exceptions.ValidationError:
except exceptions.validation.ValidationError:
return False
elif isinstance(pattern, str):
# It's a regex

View File

@ -55,7 +55,7 @@ def validateRegexField(field: ui.gui.TextField, fieldValue: typing.Optional[str]
try:
re.search(pattern, '')
except Exception as e:
raise exceptions.ValidationError(f'Invalid pattern at {field.label}: {line}') from e
raise exceptions.validation.ValidationError(f'Invalid pattern at {field.label}: {line}') from e
def processRegexField(

View File

@ -69,39 +69,39 @@ def validateNumeric(
try:
numeric = int(value)
if minValue is not None and numeric < minValue:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('{0} must be greater than or equal to {1}').format(fieldName, minValue)
)
if maxValue is not None and numeric > maxValue:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('{0} must be lower than or equal to {1}').format(fieldName, maxValue)
)
value = str(numeric)
except ValueError:
raise exceptions.ValidationError(_('{0} contains invalid characters').format(fieldName)) from None
raise exceptions.validation.ValidationError(_('{0} contains invalid characters').format(fieldName)) from None
return int(value)
def validateHostname(hostname: str, maxLength: int = 64, allowDomain=False) -> str:
if len(hostname) > maxLength:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('{} is not a valid hostname: maximum host name length exceeded.').format(hostname)
)
if not allowDomain:
if '.' in hostname:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('{} is not a valid hostname: (domains not allowed)').format(hostname)
)
allowed = re.compile(r'(?!-)[A-Z\d-]{1,63}(?<!-)$', re.IGNORECASE)
if not all(allowed.match(x) for x in hostname.split(".")):
raise exceptions.ValidationError(_('{} is not a valid hostname: (invalid characters)').format(hostname))
raise exceptions.validation.ValidationError(_('{} is not a valid hostname: (invalid characters)').format(hostname))
return hostname
@ -112,12 +112,12 @@ def validateFqdn(fqdn: str, maxLength: int = 255) -> str:
def validateUrl(url: str, maxLength: int = 1024) -> str:
if len(url) > maxLength:
raise exceptions.ValidationError(_('{} is not a valid URL: exceeds maximum length.').format(url))
raise exceptions.validation.ValidationError(_('{} is not a valid URL: exceeds maximum length.').format(url))
try:
url_validator(url)
except Exception as e:
raise exceptions.ValidationError(str(e))
raise exceptions.validation.ValidationError(str(e))
return url
@ -132,7 +132,7 @@ def validateIpv4(ipv4: str) -> str:
try:
dj_validators.validate_ipv4_address(ipv4)
except Exception:
raise exceptions.ValidationError(_('{} is not a valid IPv4 address').format(ipv4)) from None
raise exceptions.validation.ValidationError(_('{} is not a valid IPv4 address').format(ipv4)) from None
return ipv4
@ -146,7 +146,7 @@ def validateIpv6(ipv6: str) -> str:
try:
dj_validators.validate_ipv6_address(ipv6)
except Exception:
raise exceptions.ValidationError(_('{} is not a valid IPv6 address').format(ipv6)) from None
raise exceptions.validation.ValidationError(_('{} is not a valid IPv6 address').format(ipv6)) from None
return ipv6
@ -160,7 +160,7 @@ def validateIpv4OrIpv6(ipv4OrIpv6: str) -> str:
try:
dj_validators.validate_ipv46_address(ipv4OrIpv6)
except Exception:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('{} is not a valid IPv4 or IPv6 address').format(ipv4OrIpv6)
) from None
return ipv4OrIpv6
@ -188,20 +188,20 @@ def validatePath(
str: path
"""
if len(path) > maxLength:
raise exceptions.ValidationError(_('{} exceeds maximum path length.').format(path))
raise exceptions.validation.ValidationError(_('{} exceeds maximum path length.').format(path))
valid_for_windows = re.compile(r'^[a-zA-Z]:\\.*$')
valid_for_unix = re.compile(r'^/.*$')
if mustBeWindows:
if not valid_for_windows.match(path):
raise exceptions.ValidationError(_('{} is not a valid windows path').format(path))
raise exceptions.validation.ValidationError(_('{} is not a valid windows path').format(path))
elif mustBeUnix:
if not valid_for_unix.match(path):
raise exceptions.ValidationError(_('{} is not a valid unix path').format(path))
raise exceptions.validation.ValidationError(_('{} is not a valid unix path').format(path))
else:
if not valid_for_windows.match(path) and not valid_for_unix.match(path):
raise exceptions.ValidationError(_('{} is not a valid path').format(path))
raise exceptions.validation.ValidationError(_('{} is not a valid path').format(path))
return path
@ -255,7 +255,7 @@ def validateHostPortPair(hostPortPair: str) -> typing.Tuple[str, int]:
except Exception:
return validateHostname(host, 255, False), validatePort(port)
except Exception:
raise exceptions.ValidationError(_('{} is not a valid host:port pair').format(hostPortPair)) from None
raise exceptions.validation.ValidationError(_('{} is not a valid host:port pair').format(hostPortPair)) from None
def validateTimeout(timeOutStr: str) -> int:
@ -282,7 +282,7 @@ def validateMac(mac: str) -> str:
) # In fact, it could be XX-XX-XX-XX-XX-XX, but we use - as range separator
if macRE.match(mac) is None:
raise exceptions.ValidationError(_('{} is not a valid MAC address').format(mac))
raise exceptions.validation.ValidationError(_('{} is not a valid MAC address').format(mac))
return mac
@ -298,7 +298,7 @@ def validateMacRange(macRange: str) -> str:
validateMac(macRangeStart)
validateMac(macRangeEnd)
except Exception:
raise exceptions.ValidationError(_('{} is not a valid MAC range').format(macRange)) from None
raise exceptions.validation.ValidationError(_('{} is not a valid MAC range').format(macRange)) from None
return macRange
@ -310,10 +310,10 @@ def validateEmail(email: str) -> str:
:return: Raises exceptions.Validation exception if is invalid, else return the value "fixed"
"""
if len(email) > 254:
raise exceptions.ValidationError(_('Email address is too long'))
raise exceptions.validation.ValidationError(_('Email address is too long'))
if not re.match(r"[^@]+@[^@]+\.[^@]+", email):
raise exceptions.ValidationError(_('Email address is not valid'))
raise exceptions.validation.ValidationError(_('Email address is not valid'))
return email
@ -333,16 +333,16 @@ def validateBasename(baseName: str, length: int = -1) -> str:
None -- [description]
"""
if re.match(r'^[a-zA-Z0-9][a-zA-Z0-9-]*$', baseName) is None:
raise exceptions.ValidationError(_('The basename is not a valid for a hostname'))
raise exceptions.validation.ValidationError(_('The basename is not a valid for a hostname'))
if length == 0:
raise exceptions.ValidationError(_('The length of basename plus length must be greater than 0'))
raise exceptions.validation.ValidationError(_('The length of basename plus length must be greater than 0'))
if length != -1 and len(baseName) + length > 15:
raise exceptions.ValidationError(_('The length of basename plus length must not be greater than 15'))
raise exceptions.validation.ValidationError(_('The length of basename plus length must not be greater than 15'))
if baseName.isdigit():
raise exceptions.ValidationError(_('The machine name can\'t be only numbers'))
raise exceptions.validation.ValidationError(_('The machine name can\'t be only numbers'))
return baseName
@ -355,7 +355,7 @@ def validateJson(jsonData: typing.Optional[str]) -> typing.Any:
jsonData (typing.Optional[str]): Json data to validate
Raises:
exceptions.ValidationError: If json data is not valid
exceptions.validation.ValidationError: If json data is not valid
Returns:
typing.Any: Json data as python object
@ -365,7 +365,7 @@ def validateJson(jsonData: typing.Optional[str]) -> typing.Any:
try:
return json.loads(jsonData)
except Exception:
raise exceptions.ValidationError(_('Invalid JSON data')) from None
raise exceptions.validation.ValidationError(_('Invalid JSON data')) from None
def validateServerCertificate(cert: typing.Optional[str]) -> str:
@ -376,7 +376,7 @@ def validateServerCertificate(cert: typing.Optional[str]) -> str:
cert (str): Certificate to validate
Raises:
exceptions.ValidationError: If certificate is not valid
exceptions.validation.ValidationError: If certificate is not valid
Returns:
str: Certificate
@ -386,7 +386,7 @@ def validateServerCertificate(cert: typing.Optional[str]) -> str:
try:
security.checkServerCertificateIsValid(cert)
except Exception as e:
raise exceptions.ValidationError(_('Invalid certificate') + f' :{e}') from e
raise exceptions.validation.ValidationError(_('Invalid certificate') + f' :{e}') from e
return cert
@ -407,6 +407,6 @@ def validateServerCertificateMulti(value: typing.Optional[str]) -> str:
try:
load_pem_x509_certificate(pemCert.encode())
except Exception as e:
raise exceptions.ValidationError(_('Invalid certificate') + f' :{e}') from e
raise exceptions.validation.ValidationError(_('Invalid certificate') + f' :{e}') from e
return value

View File

@ -188,7 +188,7 @@ class EmailMFA(mfas.MFA):
# if hostname is not valid, we will raise an exception
hostname = self.hostname.cleanStr()
if not hostname:
raise exceptions.ValidationError(_('Invalid SMTP hostname'))
raise exceptions.validation.ValidationError(_('Invalid SMTP hostname'))
# Now check is valid format
if ':' in hostname:

View File

@ -36,7 +36,7 @@ import logging
from django.utils.translation import gettext_noop as _, gettext
from uds import models
from uds.core import mfas
from uds.core import mfas, exceptions
from uds.core.ui import gui
from uds.auths.Radius import client
@ -48,7 +48,6 @@ from uds.auths.Radius.client import (
# NEEDED
)
from uds.core.auths.auth import webPassword
from uds.core.auths import exceptions
if typing.TYPE_CHECKING:
from uds.core.module import Module
@ -291,4 +290,4 @@ class RadiusOTP(mfas.MFA):
username,
request.ip,
)
raise exceptions.MFAError(err)
raise exceptions.auth.MFAError(err)

View File

@ -40,11 +40,9 @@ from django.utils.translation import gettext_noop as _, gettext
from uds import models
from uds.core.util.model import getSqlDatetime
from uds.core import mfas
from uds.core import mfas, exceptions
from uds.core.ui import gui
from uds.core.auths import exceptions
if typing.TYPE_CHECKING:
from uds.core.module import Module
from uds.core.types.request import ExtendedHttpRequest
@ -201,7 +199,7 @@ class TOTP_MFA(mfas.MFA):
return
if self.cache.get(userId + code) is not None:
raise exceptions.MFAError(gettext('Code is already used. Wait a minute and try again.'))
raise exceptions.auth.MFAError(gettext('Code is already used. Wait a minute and try again.'))
# Get data from storage related to this user
secret, qrShown = self._userData(userId)
@ -210,7 +208,7 @@ class TOTP_MFA(mfas.MFA):
if not self.getTOTP(userId, username).verify(
code, valid_window=self.validWindow.num(), for_time=getSqlDatetime()
):
raise exceptions.MFAError(gettext('Invalid code'))
raise exceptions.auth.MFAError(gettext('Invalid code'))
self.cache.put(userId + code, True, self.validWindow.num() * (TOTP_INTERVAL + 1))

View File

@ -35,7 +35,7 @@ import typing
from django.db import models
from django.db.models import Count, Q, signals
from uds.core import auths, mfas
from uds.core import auths, mfas, types
from uds.core.util import log, storage
from .authenticator import Authenticator
@ -136,7 +136,7 @@ class User(UUIDModel):
self.last_access = getSqlDatetime()
self.save(update_fields=['last_access'])
def logout(self, request: 'ExtendedHttpRequest') -> auths.AuthenticationResult:
def logout(self, request: 'ExtendedHttpRequest') -> types.auth.AuthenticationResult:
"""
Invoked to log out this user
Returns the url where to redirect user, or None if default url will be used

View File

@ -148,7 +148,7 @@ class EmailNotifier(messaging.Notifier):
# if hostname is not valid, we will raise an exception
hostname = self.hostname.cleanStr()
if not hostname:
raise exceptions.ValidationError(_('Invalid SMTP hostname'))
raise exceptions.validation.ValidationError(_('Invalid SMTP hostname'))
# Now check is valid format
if ':' in hostname:

View File

@ -128,7 +128,7 @@ class TelegramNotifier(messaging.Notifier):
for i in (self.botname, self.accessToken, self.secret):
s = i.cleanStr()
if not s:
raise exceptions.ValidationError(_('Invalid value for {}').format(i.label))
raise exceptions.validation.ValidationError(_('Invalid value for {}').format(i.label))
i.value = s
def initGui(self) -> None:

View File

@ -155,11 +155,11 @@ class LinuxOsADManager(LinuxOsManager):
super().__init__(environment, values)
if values:
if values['domain'] == '':
raise exceptions.ValidationError(_('Must provide a domain!'))
raise exceptions.validation.ValidationError(_('Must provide a domain!'))
if values['account'] == '':
raise exceptions.ValidationError(_('Must provide an account to add machines to domain!'))
raise exceptions.validation.ValidationError(_('Must provide an account to add machines to domain!'))
if values['password'] == '':
raise exceptions.ValidationError(_('Must provide a password for the account!'))
raise exceptions.validation.ValidationError(_('Must provide a password for the account!'))
self._domain = values['domain']
self._account = values['account']
self._password = values['password']

View File

@ -146,11 +146,11 @@ class LinuxOsFreeIPAManager(LinuxOsManager):
super().__init__(environment, values)
if values:
if values['domain'] == '':
raise exceptions.ValidationError(_('Must provide a domain!'))
raise exceptions.validation.ValidationError(_('Must provide a domain!'))
if values['account'] == '':
raise exceptions.ValidationError(_('Must provide an account to add machines to domain!'))
raise exceptions.validation.ValidationError(_('Must provide an account to add machines to domain!'))
if values['password'] == '':
raise exceptions.ValidationError(_('Must provide a password for the account!'))
raise exceptions.validation.ValidationError(_('Must provide a password for the account!'))
self._domain = values['domain']
self._account = values['account']
self._password = values['password']

View File

@ -77,7 +77,7 @@ class LinuxRandomPassManager(LinuxOsManager):
super().__init__(environment, values)
if values is not None:
if values['userAccount'] == '':
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('Must provide an user account!!!')
)
self._userAccount = values['userAccount']

View File

@ -96,11 +96,11 @@ class WindowsOsManager(osmanagers.OSManager):
try:
length = int(length)
except Exception:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('Length must be numeric!!')
) from None
if length > 6 or length < 1:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('Length must be betwen 1 and 6')
)
return length

View File

@ -142,15 +142,15 @@ class WinDomainOsManager(WindowsOsManager):
super().__init__(environment, values)
if values:
if values['domain'] == '':
raise exceptions.ValidationError(_('Must provide a domain!'))
raise exceptions.validation.ValidationError(_('Must provide a domain!'))
# if values['domain'].find('.') == -1:
# raise exceptions.ValidationException(_('Must provide domain in FQDN'))
if values['account'] == '':
raise exceptions.ValidationError(_('Must provide an account to add machines to domain!'))
raise exceptions.validation.ValidationError(_('Must provide an account to add machines to domain!'))
if values['account'].find('\\') != -1:
raise exceptions.ValidationError(_('DOM\\USER form is not allowed!'))
raise exceptions.validation.ValidationError(_('DOM\\USER form is not allowed!'))
if values['password'] == '':
raise exceptions.ValidationError(_('Must provide a password for the account!'))
raise exceptions.validation.ValidationError(_('Must provide a password for the account!'))
self._domain = values['domain']
self._ou = values['ou'].strip()
self._account = values['account']

View File

@ -86,9 +86,9 @@ class WinRandomPassManager(WindowsOsManager):
super().__init__(environment, values)
if values:
if values['userAccount'] == '':
raise exceptions.ValidationError(_('Must provide an user account!!!'))
raise exceptions.validation.ValidationError(_('Must provide an user account!!!'))
if values['password'] == '':
raise exceptions.ValidationError(_('Must provide a password for the account!!!'))
raise exceptions.validation.ValidationError(_('Must provide a password for the account!!!'))
self._userAccount = values['userAccount']
self._password = values['password']
else:

View File

@ -225,7 +225,7 @@ class OVirtLinkedService(services.Service): # pylint: disable=too-many-public-m
if values:
validators.validateBasename(self.baseName.value, self.lenName.num())
if int(self.memory.value) < 256 or int(self.memoryGuaranteed.value) < 256:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('The minimum allowed memory is 256 Mb')
)
if int(self.memoryGuaranteed.value) > int(self.memory.value):

View File

@ -87,13 +87,13 @@ class PhysicalMachinesProvider(services.ServiceProvider):
config.read_string(self.config.value)
# Seems a valid configuration file, let's see if all se
except Exception as e:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('Invalid advanced configuration: ') + str(e)
)
for section in config.sections():
if section not in VALID_CONFIG_SECTIONS:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('Invalid section in advanced configuration: ') + section
)
@ -103,12 +103,12 @@ class PhysicalMachinesProvider(services.ServiceProvider):
try:
net.networksFromString(key) # Raises exception if net is invalid
except Exception:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('Invalid network in advanced configuration: ') + key
) from None
# Now check value is an url
if config['wol'][key][:4] != 'http':
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('Invalid url in advanced configuration: ') + key
)

View File

@ -154,7 +154,7 @@ class IPMachinesService(IPServiceBase):
# Check that ips are valid
for v in values['ipList']:
if not net.isValidHost(v.split(';')[0]): # Get only IP/hostname
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
gettext('Invalid value detected on servers list: "{}"').format(v)
)
self._ips = [

View File

@ -80,7 +80,7 @@ class IPSingleMachineService(IPServiceBase):
return
if not net.isValidHost(self.ip.value):
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
gettext('Invalid server used: "{}"'.format(self.ip.value))
)

View File

@ -181,7 +181,7 @@ class Provider(services.ServiceProvider):
# values are only passed from administration client. Internals
# instantiations are always empty.
if values and self.methAlive.isTrue():
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('Methuselah is not alive!!! :-)')
)
@ -222,7 +222,7 @@ class Provider(services.ServiceProvider):
instance.methAge.value,
instance.methAlive.value,
)
except exceptions.ValidationError as e:
except exceptions.validation.ValidationError as e:
# If we say that meth is alive, instantiation will
return [False, str(e)]
except Exception as e:

View File

@ -164,7 +164,7 @@ class ServiceOne(services.Service):
# so we only need to validate params if values is not None
if values:
if self.colour.value == 'nonsense':
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
'The selected colour is invalid!!!'
)

View File

@ -190,7 +190,7 @@ class XenLinkedService(services.Service): # pylint: disable=too-many-public-met
validators.validateBasename(self.baseName.value, self.lenName.num())
if int(self.memory.value) < 256:
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('The minimum allowed memory is 256 Mb')
)

View File

@ -90,7 +90,7 @@ class SPICETransport(BaseSpiceTransport):
logger.debug('Connection data: %s', con)
if not con:
raise exceptions.TransportError('No console connection data')
raise exceptions.service.TransportError('No console connection data')
port: str = con['port'] or '-1'
secure_port: str = con['secure_port'] or '-1'

View File

@ -106,7 +106,7 @@ class TSPICETransport(BaseSpiceTransport):
raise
if not con:
raise exceptions.TransportError(
raise exceptions.service.TransportError(
_('No console connection data received'),
)

View File

@ -89,7 +89,7 @@ class TestTransport(transports.Transport):
self.testURL.value.startswith('http://')
or self.testURL.value.startswith('https://')
):
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('The url must be http or https')
)

View File

@ -90,7 +90,7 @@ class URLCustomTransport(transports.Transport):
self.urlPattern.value.startswith('http://')
or self.urlPattern.value.startswith('https://')
):
raise exceptions.ValidationError(
raise exceptions.validation.ValidationError(
_('The url must be http or https')
)

View File

@ -36,7 +36,7 @@ from django.views.i18n import JavaScriptCatalog
from django.views.generic.base import RedirectView
from uds import REST
from uds.core.auths.authenticator import AuthenticationInternalUrl
from uds.core import types
import uds.web.views
import uds.admin.views
@ -85,7 +85,7 @@ urlpatterns = [
# Index
path(r'uds/page/services', uds.web.views.main.index, name='page.index'),
# Login/logout
path(r'uds/page/login', uds.web.views.main.login, name=AuthenticationInternalUrl.LOGIN.value),
path(r'uds/page/login', uds.web.views.main.login, name=types.auth.AuthenticationInternalUrl.LOGIN.value),
re_path(
r'^uds/page/login/(?P<tag>[a-zA-Z0-9-]+)$',
uds.web.views.main.login,

View File

@ -45,7 +45,7 @@ from django.utils.translation import gettext as _
from uds.core.types.request import ExtendedHttpRequest
from uds.core.types.request import ExtendedHttpRequestWithUser
from uds.core.auths import auth, exceptions, AuthenticationState
from uds.core.auths import auth
from uds.core.util.config import GlobalConfig
from uds.core.managers.crypto import CryptoManager
from uds.core.managers.user_service import UserServiceManager
@ -55,7 +55,7 @@ from uds.web.forms.MFAForm import MFAForm
from uds.web.util.authentication import checkLogin
from uds.web.util.services import getServicesData
from uds.web.util import configjs
from uds.core import mfas, types
from uds.core import mfas, types, exceptions
from uds import auths, models
from uds.core.util.model import getSqlStampInSeconds
@ -151,7 +151,7 @@ def logout(request: ExtendedHttpRequestWithUser) -> HttpResponse:
request.session['restricted'] = False # Remove restricted
request.authorized = False
logoutResponse = request.user.logout(request)
url = logoutResponse.url if logoutResponse.success == AuthenticationState.REDIRECT else None
url = logoutResponse.url if logoutResponse.success == types.auth.AuthenticationState.REDIRECT else None
return auth.webLogout(request, url or request.session.get('logouturl', None))
@ -256,7 +256,7 @@ def mfa(request: ExtendedHttpRequest) -> HttpResponse: # pylint: disable=too-ma
)
return response
except exceptions.MFAError as e:
except exceptions.auth.MFAError as e:
logger.error('MFA error: %s', e)
tries += 1
request.session['mfa_tries'] = tries