mirror of
https://github.com/dkmstr/openuds.git
synced 2024-12-24 21:34:41 +03:00
Added "not tested" generic SMS sending using an HTTP server
This commit is contained in:
parent
77e021a371
commit
aec2f5b57f
@ -768,7 +768,7 @@ class gui:
|
||||
|
||||
def __init__(self, **options):
|
||||
super().__init__(**options)
|
||||
if options.get('values') and isinstance(options.get('values'), dict):
|
||||
if options.get('values') and isinstance(options.get('values'), (dict, list, tuple)):
|
||||
options['values'] = gui.convertToChoices(options['values'])
|
||||
self._data['values'] = options.get('values', [])
|
||||
if 'fills' in options:
|
||||
|
@ -5,7 +5,7 @@ import ssl
|
||||
import typing
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
from django.utils.translation import gettext_noop as _
|
||||
|
||||
from uds.core import mfas
|
||||
from uds.core.ui import gui
|
||||
|
1
server/src/uds/mfas/SMS/__init__.py
Normal file
1
server/src/uds/mfas/SMS/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from . import mfa
|
179
server/src/uds/mfas/SMS/mfa.py
Normal file
179
server/src/uds/mfas/SMS/mfa.py
Normal file
@ -0,0 +1,179 @@
|
||||
import typing
|
||||
import logging
|
||||
|
||||
from django.utils.translation import gettext_noop as _, gettext
|
||||
import requests
|
||||
import requests.auth
|
||||
|
||||
from uds.core import mfas
|
||||
from uds.core.ui import gui
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds.core.module import Module
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class SMSMFA(mfas.MFA):
|
||||
typeName = _('SMS Thought HTTP')
|
||||
typeType = 'smsHttpMFA'
|
||||
typeDescription = _('Simple SMS sending MFA using HTTP')
|
||||
iconFile = 'sms.png'
|
||||
|
||||
smsSendingUrl = gui.TextField(
|
||||
length=128,
|
||||
label=_('URL pattern for SMS sending'),
|
||||
order=1,
|
||||
tooltip=_(
|
||||
'URL pattern for SMS sending. It can contain the following '
|
||||
'variables:<br>'
|
||||
'<ul>'
|
||||
'<li>{code} - the code to send</li>'
|
||||
'<li>{phone} - the phone number</li>'
|
||||
'</ul>'
|
||||
),
|
||||
required=True,
|
||||
tab=_('HTTP Server'),
|
||||
)
|
||||
|
||||
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.'
|
||||
),
|
||||
)
|
||||
|
||||
smsSendingMethod = gui.ChoiceField(
|
||||
label=_('SMS sending method'),
|
||||
order=2,
|
||||
tooltip=_('Method for sending SMS'),
|
||||
required=True,
|
||||
tab=_('HTTP Server'),
|
||||
values=('GET', 'POST', 'PUT'),
|
||||
)
|
||||
|
||||
smsSendingParameters = gui.TextField(
|
||||
length=128,
|
||||
label=_('Parameters for SMS POST/PUT sending'),
|
||||
order=3,
|
||||
tooltip=_(
|
||||
'Parameters for SMS sending via POST/PUT. It can contain the following '
|
||||
'variables:<br>'
|
||||
'<ul>'
|
||||
'<li>{code} - the code to send</li>'
|
||||
'<li>{phone} - the phone number</li>'
|
||||
'</ul>'
|
||||
),
|
||||
required=False,
|
||||
tab=_('HTTP Server'),
|
||||
)
|
||||
|
||||
smsAuthenticationMethod = gui.ChoiceField(
|
||||
label=_('SMS authentication method'),
|
||||
order=3,
|
||||
tooltip=_('Method for sending SMS'),
|
||||
required=True,
|
||||
tab=_('HTTP Server'),
|
||||
values=[
|
||||
{'id': 0, 'text': _('None')},
|
||||
{'id': 1, 'text': _('HTTP Basic Auth')},
|
||||
{'id': 2, 'text': _('HTTP Digest Auth')},
|
||||
{'id': 3, 'text': _('HTTP Token Auth')},
|
||||
],
|
||||
)
|
||||
|
||||
smsAuthenticationUserOrToken = gui.TextField(
|
||||
length=128,
|
||||
label=_('SMS authentication user or token'),
|
||||
order=4,
|
||||
tooltip=_('User or token for SMS authentication'),
|
||||
required=False,
|
||||
tab=_('HTTP Server'),
|
||||
)
|
||||
|
||||
smsAuthenticationPassword = gui.TextField(
|
||||
length=128,
|
||||
label=_('SMS authentication password'),
|
||||
order=5,
|
||||
tooltip=_('Password for SMS authentication'),
|
||||
required=False,
|
||||
tab=_('HTTP Server'),
|
||||
)
|
||||
|
||||
def initialize(self, values: 'Module.ValuesType') -> None:
|
||||
return super().initialize(values)
|
||||
|
||||
def composeSmsUrl(self, code: str, phone: str) -> str:
|
||||
url = self.smsSendingUrl.value
|
||||
url = url.replace('{code}', code)
|
||||
url = url.replace('{phone}', phone)
|
||||
return url
|
||||
|
||||
def getSession(self) -> requests.Session:
|
||||
session = requests.Session()
|
||||
# 0 means no authentication
|
||||
if self.smsAuthenticationMethod.value == 1:
|
||||
session.auth = requests.auth.HTTPBasicAuth(
|
||||
username=self.smsAuthenticationUserOrToken.value,
|
||||
password=self.smsAuthenticationPassword.value,
|
||||
)
|
||||
elif self.smsAuthenticationMethod.value == 2:
|
||||
session.auth = requests.auth.HTTPDigestAuth(
|
||||
self.smsAuthenticationUserOrToken.value,
|
||||
self.smsAuthenticationPassword.value,
|
||||
)
|
||||
elif self.smsAuthenticationMethod.value == 3:
|
||||
session.headers['Authorization'] = (
|
||||
'Token ' + self.smsAuthenticationUserOrToken.value
|
||||
)
|
||||
# Any other value means no authentication
|
||||
return session
|
||||
|
||||
def sendSMS_GET(self, url: str) -> None:
|
||||
response = self.getSession().get(url)
|
||||
if response.status_code != 200:
|
||||
raise Exception('Error sending SMS: ' + response.text)
|
||||
|
||||
def sendSMS_POST(self, url: str, code: str, phone: str) -> None:
|
||||
# Compose POST data
|
||||
data = ''
|
||||
if self.smsSendingParameters.value:
|
||||
data = self.smsSendingParameters.value.replace('{code}', code).replace(
|
||||
'{phone}', phone
|
||||
)
|
||||
response = self.getSession().post(url, data=data.encode())
|
||||
if response.status_code != 200:
|
||||
raise Exception('Error sending SMS: ' + response.text)
|
||||
|
||||
def sendSMS_PUT(self, url: str, code: str, phone: str) -> None:
|
||||
# Compose POST data
|
||||
data = ''
|
||||
if self.smsSendingParameters.value:
|
||||
data = self.smsSendingParameters.value.replace('{code}', code).replace(
|
||||
'{phone}', phone
|
||||
)
|
||||
response = self.getSession().put(url, data=data.encode())
|
||||
if response.status_code != 200:
|
||||
raise Exception('Error sending SMS: ' + response.text)
|
||||
|
||||
def sendSMS(self, code: str, phone: str) -> None:
|
||||
url = self.composeSmsUrl(code, phone)
|
||||
if self.smsSendingMethod.value == 'GET':
|
||||
return self.sendSMS_GET(url)
|
||||
elif self.smsSendingMethod.value == 'POST':
|
||||
return self.sendSMS_POST(url, code, phone)
|
||||
elif self.smsSendingMethod.value == 'PUT':
|
||||
return self.sendSMS_PUT(url, code, phone)
|
||||
else:
|
||||
raise Exception('Unknown SMS sending method')
|
||||
|
||||
def label(self) -> str:
|
||||
return gettext('MFA Code')
|
||||
|
||||
def sendCode(self, userId: str, identifier: str, code: str) -> None:
|
||||
logger.debug('Sending code: %s', code)
|
||||
return
|
BIN
server/src/uds/mfas/SMS/sms.png
Executable file
BIN
server/src/uds/mfas/SMS/sms.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
@ -1,7 +1,7 @@
|
||||
import typing
|
||||
import logging
|
||||
|
||||
from django.utils.translation import ugettext_noop as _
|
||||
from django.utils.translation import gettext_noop as _
|
||||
|
||||
from uds.core import mfas
|
||||
from uds.core.ui import gui
|
||||
|
@ -30,8 +30,9 @@
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import logging
|
||||
import typing
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django import forms
|
||||
from uds.models import Authenticator
|
||||
|
||||
@ -69,4 +70,5 @@ class LoginForm(forms.Form):
|
||||
continue
|
||||
choices.append((a.uuid, a.name))
|
||||
|
||||
self.fields['authenticator'].choices = choices # type: ignore
|
||||
typing.cast(forms.ChoiceField, self.fields['authenticator']).choices = choices
|
||||
|
||||
|
@ -55,6 +55,8 @@ from uds.web.util import configjs
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CSRF_FIELD = 'csrfmiddlewaretoken'
|
||||
MFA_COOKIE_NAME = 'mfa_status'
|
||||
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds import models
|
||||
@ -116,10 +118,9 @@ def login(
|
||||
request.authorized = True
|
||||
if user.manager.getType().providesMfa() and user.manager.mfa:
|
||||
authInstance = user.manager.getInstance()
|
||||
if authInstance.mfaIdentifier():
|
||||
request.authorized = (
|
||||
False # We can ask for MFA so first disauthorize user
|
||||
)
|
||||
if authInstance.mfaIdentifier(user.name):
|
||||
# We can ask for MFA so first disauthorize user
|
||||
request.authorized = False
|
||||
response = HttpResponseRedirect(reverse('page.mfa'))
|
||||
|
||||
else:
|
||||
@ -182,11 +183,10 @@ def mfa(request: ExtendedHttpRequest) -> HttpResponse:
|
||||
userHashValue: str = hashlib.sha3_256(
|
||||
(request.user.name + request.user.uuid + mfaProvider.uuid).encode()
|
||||
).hexdigest()
|
||||
cookieName = 'bgd' + userHashValue
|
||||
|
||||
# Try to get cookie anc check it
|
||||
mfaCookie = request.COOKIES.get(cookieName, None)
|
||||
if mfaCookie: # Cookie is valid, skip MFA setting authorization
|
||||
mfaCookie = request.COOKIES.get(MFA_COOKIE_NAME, None)
|
||||
if mfaCookie == userHashValue: # Cookie is valid, skip MFA setting authorization
|
||||
request.authorized = True
|
||||
return HttpResponseRedirect(reverse('page.index'))
|
||||
|
||||
@ -203,7 +203,7 @@ def mfa(request: ExtendedHttpRequest) -> HttpResponse:
|
||||
request.session.flush() # Clear session, and redirect to login
|
||||
return HttpResponseRedirect(reverse('page.login'))
|
||||
|
||||
mfaIdentifier = authInstance.mfaIdentifier()
|
||||
mfaIdentifier = authInstance.mfaIdentifier(request.user.name)
|
||||
label = mfaInstance.label()
|
||||
|
||||
if request.method == 'POST': # User has provided MFA code
|
||||
@ -226,8 +226,8 @@ def mfa(request: ExtendedHttpRequest) -> HttpResponse:
|
||||
and form.cleaned_data['remember'] is True
|
||||
):
|
||||
response.set_cookie(
|
||||
cookieName,
|
||||
'true',
|
||||
MFA_COOKIE_NAME,
|
||||
userHashValue,
|
||||
max_age=mfaProvider.remember_device * 60 * 60,
|
||||
)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user