forked from shaba/openuds
Merge remote-tracking branch 'origin/v3.6'
This commit is contained in:
commit
33258b0dcc
@ -119,6 +119,13 @@ class MFA(Module):
|
||||
"""
|
||||
return 'MFA Code'
|
||||
|
||||
def html(self, request: 'ExtendedHttpRequest') -> str:
|
||||
"""
|
||||
This method will be invoked from the MFA form, to know the HTML that will be presented
|
||||
to the user below the MFA code form.
|
||||
"""
|
||||
return ''
|
||||
|
||||
def validity(self) -> int:
|
||||
"""
|
||||
This method will be invoked from the MFA form, to know the validity in secods
|
||||
@ -127,7 +134,7 @@ class MFA(Module):
|
||||
"""
|
||||
return self.cacheTime
|
||||
|
||||
def emptyIndentifierAllowedToLogin(self) -> bool:
|
||||
def emptyIndentifierAllowedToLogin(self, request: 'ExtendedHttpRequest') -> bool:
|
||||
"""
|
||||
If this method returns True, an user that has no "identifier" is allowed to login without MFA
|
||||
"""
|
||||
|
@ -6,8 +6,9 @@ import ssl
|
||||
import typing
|
||||
import logging
|
||||
|
||||
from django.utils.translation import gettext_noop as _
|
||||
from django.utils.translation import gettext_noop as _, gettext
|
||||
|
||||
from uds import models
|
||||
from uds.core import mfas
|
||||
from uds.core.ui import gui
|
||||
from uds.core.util import validators, decorators
|
||||
@ -96,6 +97,32 @@ class EmailMFA(mfas.MFA):
|
||||
tab=_('Config'),
|
||||
)
|
||||
|
||||
|
||||
allowLoginWithoutMFA = gui.ChoiceField(
|
||||
label=_('User without MFA policy'),
|
||||
order=31,
|
||||
defaultValue='0',
|
||||
tooltip=_('Action for SMS response error'),
|
||||
required=True,
|
||||
values={
|
||||
'0': _('Allow user login'),
|
||||
'1': _('Deny user login'),
|
||||
'2': _('Allow user to login if it IP is in the networks list'),
|
||||
'3': _('Deny user to login if it IP is in the networks list'),
|
||||
},
|
||||
tab=_('Config'),
|
||||
)
|
||||
|
||||
networks = gui.MultiChoiceField(
|
||||
label=_('SMS networks'),
|
||||
rdonly=False,
|
||||
rows=5,
|
||||
order=32,
|
||||
tooltip=_('Networks for SMS authentication'),
|
||||
required=True,
|
||||
tab=_('Config'),
|
||||
)
|
||||
|
||||
def initialize(self, values: 'Module.ValuesType' = None):
|
||||
"""
|
||||
We will use the "autosave" feature for form fields
|
||||
@ -123,7 +150,35 @@ class EmailMFA(mfas.MFA):
|
||||
# now check from email and to email
|
||||
self.fromEmail.value = validators.validateEmail(self.fromEmail.value)
|
||||
|
||||
# Done
|
||||
def html(self, request: 'ExtendedHttpRequest') -> str:
|
||||
return gettext('Check your mail. You will receive an email with the verification code')
|
||||
|
||||
@classmethod
|
||||
def initClassGui(cls) -> None:
|
||||
# Populate the networks list
|
||||
cls.networks.setValues([
|
||||
gui.choiceItem(v.uuid, v.name)
|
||||
for v in models.Network.objects.all().order_by('name')
|
||||
])
|
||||
|
||||
|
||||
def checkAction(self, action: str, request: 'ExtendedHttpRequest') -> bool:
|
||||
def checkIp() -> bool:
|
||||
return any(i.ipInNetwork(request.ip) for i in models.Network.objects.filter(uuid__in = self.networks.value))
|
||||
|
||||
if action == '0':
|
||||
return True
|
||||
elif action == '1':
|
||||
return False
|
||||
elif action == '2':
|
||||
return checkIp()
|
||||
elif action == '3':
|
||||
return not checkIp()
|
||||
else:
|
||||
return False
|
||||
|
||||
def emptyIndentifierAllowedToLogin(self, request: 'ExtendedHttpRequest') -> bool:
|
||||
return self.checkAction(self.allowLoginWithoutMFA.value, request)
|
||||
|
||||
def label(self) -> str:
|
||||
return 'OTP received via email'
|
||||
|
@ -43,12 +43,12 @@ class SMSMFA(mfas.MFA):
|
||||
ignoreCertificateErrors = gui.CheckBoxField(
|
||||
label=_('Ignore certificate errors'),
|
||||
order=2,
|
||||
tab=_('HTTP Server'),
|
||||
defvalue=False,
|
||||
tooltip=_(
|
||||
'If checked, the server certificate will be ignored. This is '
|
||||
'useful if the server uses a self-signed certificate.'
|
||||
),
|
||||
tab=_('HTTP Server'),
|
||||
)
|
||||
|
||||
sendingMethod = gui.ChoiceField(
|
||||
@ -56,8 +56,8 @@ class SMSMFA(mfas.MFA):
|
||||
order=3,
|
||||
tooltip=_('Method for sending SMS'),
|
||||
required=True,
|
||||
tab=_('HTTP Server'),
|
||||
values=('GET', 'POST', 'PUT'),
|
||||
tab=_('HTTP Server'),
|
||||
)
|
||||
|
||||
headersParameters = gui.TextField(
|
||||
@ -101,8 +101,8 @@ class SMSMFA(mfas.MFA):
|
||||
order=5,
|
||||
tooltip=_('Encoding for SMS'),
|
||||
required=True,
|
||||
tab=_('HTTP Server'),
|
||||
values=('utf-8', 'iso-8859-1'),
|
||||
tab=_('HTTP Server'),
|
||||
)
|
||||
|
||||
authenticationMethod = gui.ChoiceField(
|
||||
@ -110,12 +110,12 @@ class SMSMFA(mfas.MFA):
|
||||
order=20,
|
||||
tooltip=_('Method for sending SMS'),
|
||||
required=True,
|
||||
tab=_('HTTP Authentication'),
|
||||
values={
|
||||
'0': _('None'),
|
||||
'1': _('HTTP Basic Auth'),
|
||||
'2': _('HTTP Digest Auth'),
|
||||
},
|
||||
tab=_('HTTP Authentication'),
|
||||
)
|
||||
|
||||
authenticationUserOrToken = gui.TextField(
|
||||
@ -153,13 +153,28 @@ class SMSMFA(mfas.MFA):
|
||||
defaultValue='0',
|
||||
tooltip=_('Action for SMS response error'),
|
||||
required=True,
|
||||
tab=_('HTTP Response'),
|
||||
values={
|
||||
'0': _('Allow user log in without MFA'),
|
||||
'1': _('Deny user log in'),
|
||||
'2': _('Allow user to log in if it IP is in the networks list'),
|
||||
'3': _('Deny user to log in if it IP is in the networks list'),
|
||||
'0': _('Allow user login'),
|
||||
'1': _('Deny user login'),
|
||||
'2': _('Allow user to login if it IP is in the networks list'),
|
||||
'3': _('Deny user to login if it IP is in the networks list'),
|
||||
},
|
||||
tab=_('Config'),
|
||||
)
|
||||
|
||||
allowLoginWithoutMFA = gui.ChoiceField(
|
||||
label=_('User without MFA policy'),
|
||||
order=33,
|
||||
defaultValue='0',
|
||||
tooltip=_('Action for SMS response error'),
|
||||
required=True,
|
||||
values={
|
||||
'0': _('Allow user login'),
|
||||
'1': _('Deny user login'),
|
||||
'2': _('Allow user to login if it IP is in the networks list'),
|
||||
'3': _('Deny user to login if it IP is in the networks list'),
|
||||
},
|
||||
tab=_('Config'),
|
||||
)
|
||||
|
||||
networks = gui.MultiChoiceField(
|
||||
@ -169,7 +184,7 @@ class SMSMFA(mfas.MFA):
|
||||
order=32,
|
||||
tooltip=_('Networks for SMS authentication'),
|
||||
required=True,
|
||||
tab=_('HTTP Response'),
|
||||
tab=_('Config'),
|
||||
)
|
||||
|
||||
def initialize(self, values: 'Module.ValuesType') -> None:
|
||||
@ -215,6 +230,25 @@ class SMSMFA(mfas.MFA):
|
||||
session.headers[headerName.strip()] = headerValue.strip()
|
||||
return session
|
||||
|
||||
|
||||
def checkAction(self, action: str, request: 'ExtendedHttpRequest') -> bool:
|
||||
def checkIp() -> bool:
|
||||
return any(i.ipInNetwork(request.ip) for i in models.Network.objects.filter(uuid__in = self.networks.value))
|
||||
|
||||
if action == '0':
|
||||
return True
|
||||
elif action == '1':
|
||||
return False
|
||||
elif action == '2':
|
||||
return checkIp()
|
||||
elif action == '3':
|
||||
return not checkIp()
|
||||
else:
|
||||
return False
|
||||
|
||||
def emptyIndentifierAllowedToLogin(self, request: 'ExtendedHttpRequest') -> bool:
|
||||
return self.checkAction(self.allowLoginWithoutMFA.value, request)
|
||||
|
||||
def processResponse(self, request: 'ExtendedHttpRequest', response: requests.Response) -> mfas.MFA.RESULT:
|
||||
logger.debug('Response: %s', response)
|
||||
if not response.ok:
|
||||
@ -227,20 +261,9 @@ class SMSMFA(mfas.MFA):
|
||||
'SMS response error: %s',
|
||||
response.text,
|
||||
)
|
||||
if self.responseErrorAction.value == '0':
|
||||
return mfas.MFA.RESULT.ALLOWED
|
||||
elif self.responseErrorAction.value == '1':
|
||||
raise Exception('SMS response error')
|
||||
else:
|
||||
isInNetwork = any(i.ipInNetwork(request.ip) for i in models.Network.objects.filter(uuid__in = self.networks.value))
|
||||
if self.responseErrorAction.value == '2':
|
||||
# Allow user to log in if it IP is in the networks list
|
||||
if isInNetwork:
|
||||
return mfas.MFA.RESULT.ALLOWED
|
||||
elif self.responseErrorAction.value == '3':
|
||||
if isInNetwork:
|
||||
raise Exception('SMS response error')
|
||||
return mfas.MFA.RESULT.ALLOWED
|
||||
if not self.checkAction(self.responseErrorAction.value, request):
|
||||
raise Exception(_('SMS response error'))
|
||||
return mfas.MFA.RESULT.ALLOWED
|
||||
return mfas.MFA.RESULT.OK
|
||||
|
||||
def getData(
|
||||
@ -295,6 +318,9 @@ class SMSMFA(mfas.MFA):
|
||||
def label(self) -> str:
|
||||
return gettext('MFA Code')
|
||||
|
||||
def html(self, request: 'ExtendedHttpRequest') -> str:
|
||||
return gettext('Check your phone. You will receive an SMS with the verification code')
|
||||
|
||||
def sendCode(self, request: 'ExtendedHttpRequest', userId: str, username: str, identifier: str, code: str) -> mfas.MFA.RESULT:
|
||||
logger.debug(
|
||||
'Sending SMS code "%s" for user %s (userId="%s", identifier="%s")',
|
||||
|
@ -203,7 +203,7 @@ def mfa(request: ExtendedHttpRequest) -> HttpResponse:
|
||||
label = mfaInstance.label()
|
||||
|
||||
if not mfaIdentifier:
|
||||
if mfaInstance.emptyIndentifierAllowedToLogin():
|
||||
if mfaInstance.emptyIndentifierAllowedToLogin(request):
|
||||
# Allow login
|
||||
request.authorized = True
|
||||
return HttpResponseRedirect(reverse('page.index'))
|
||||
@ -284,10 +284,14 @@ def mfa(request: ExtendedHttpRequest) -> HttpResponse:
|
||||
else:
|
||||
remember_device = _('{} hours').format(mfaProvider.remember_device)
|
||||
|
||||
# Html from MFA provider
|
||||
mfaHtml = mfaInstance.html(request)
|
||||
|
||||
# Redirect to index, but with MFA data
|
||||
request.session['mfa'] = {
|
||||
'label': label or _('MFA Code'),
|
||||
'validity': validity if validity >= 0 else 0,
|
||||
'remember_device': remember_device,
|
||||
'html': mfaHtml,
|
||||
}
|
||||
return index(request) # Render index with MFA data
|
||||
|
Loading…
Reference in New Issue
Block a user