1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-11 05:17:55 +03:00

Added ask credentials dialog

This commit is contained in:
Adolfo Gómez García 2022-10-13 20:02:02 +02:00
parent a005bf1ca0
commit ae2ffccbc3
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
10 changed files with 126 additions and 44 deletions

View File

@ -120,7 +120,7 @@ class TicketStore(UUIDModel):
validator=validator,
validity=validity,
owner=owner,
).uuid
).uuid or ''
@staticmethod
def get(
@ -168,6 +168,42 @@ class TicketStore(UUIDModel):
except TicketStore.DoesNotExist:
raise TicketStore.InvalidTicket('Does not exists')
@staticmethod
def update(
uuid: str,
secure: bool = False,
owner: typing.Optional[str] = None,
**kwargs: typing.Any,
) -> None:
try:
t = TicketStore.objects.get(uuid=uuid)
data: bytes = t.data
if secure: # Owner has already been tested and it's not emtpy
if not owner:
raise ValueError('Tried to use a secure ticket without owner')
data = cryptoManager().AESDecrypt(
data, typing.cast(str, owner).encode()
)
dct = pickle.loads(data)
for k, v in kwargs.items():
if v is not None:
dct[k] = v
# Reserialize
data = pickle.dumps(dct)
if secure:
if not owner:
raise ValueError('Tried to use a secure ticket without owner')
data = cryptoManager().AESCrypt(data, owner.encode())
t.data = data
t.save(update_fields=['data'])
except TicketStore.DoesNotExist:
pass
@staticmethod
def revalidate(
uuid: str,
@ -193,6 +229,8 @@ class TicketStore(UUIDModel):
validity: int = 60 * 60 * 24, # 24 Hours default validity for tunnel tickets
) -> str:
owner = cryptoManager().randomString(length=8)
if not userService.user:
raise ValueError('User is not set in userService')
data = {
'u': userService.user.uuid,
's': userService.uuid,

View File

@ -48,7 +48,7 @@ from .uuid_model import UUIDModel
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.core import auths
from uds.models import Group, UserService
from uds.models import Group, UserService, MFA
logger = logging.getLogger(__name__)
@ -59,7 +59,7 @@ class User(UUIDModel):
This class represents a single user, associated with one authenticator
"""
manager: 'models.ForeignKey[User, Authenticator]' = UnsavedForeignKey(
manager: 'models.ForeignKey[Authenticator]' = UnsavedForeignKey(
Authenticator, on_delete=models.CASCADE, related_name='users'
)
name = models.CharField(max_length=128, db_index=True)
@ -84,6 +84,7 @@ class User(UUIDModel):
objects: 'models.BaseManager[User]'
groups: 'models.manager.RelatedManager[Group]'
userServices: 'models.manager.RelatedManager[UserService]'
mfa: 'models.manager.RelatedManager[MFA]'
class Meta(UUIDModel.Meta):
"""

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -10,10 +10,14 @@ gettext("Service released");
gettext("Service reseted");
gettext("Are you sure?");
gettext("seconds");
gettext("Username");
gettext("Password");
gettext("Domain");
gettext("Your session has expired. Please, login again");
gettext("Error");
gettext("Please wait until the service is launched.");
gettext("Service ready");
gettext("Service ready");
gettext("UDS Client not launching");
gettext("UDS Client Download");
gettext("Error launching service");
@ -50,6 +54,7 @@ gettext("UDS Client");
gettext("About");
gettext("UDS Client");
gettext("About");
gettext("Please, enter access credentials");
gettext("You can access UDS Open Source code at");
gettext("UDS has been developed using these components:");
gettext("If you find that we missed any component, please let us know");

View File

@ -326,12 +326,12 @@ class HTML5RDPTransport(transports.Transport):
raise transports.Transport.ValidationException(
_('The server must be http or https')
)
if self.useEmptyCreds.isTrue() and self.security.value != 'rdp':
raise transports.Transport.ValidationException(
_(
'Empty credentials (on Credentials tab) is only allowed with Security level (on Parameters tab) set to "RDP"'
)
)
#if self.useEmptyCreds.isTrue() and self.security.value != 'rdp':
# raise transports.Transport.ValidationException(
# _(
# 'Empty credentials (on Credentials tab) is only allowed with Security level (on Parameters tab) set to "RDP"'
# )
# )
# Same check as normal RDP transport
def isAvailableFor(self, userService: 'models.UserService', ip: str) -> bool:
@ -462,7 +462,7 @@ class HTML5RDPTransport(transports.Transport):
}
if password == '' and self.security.value != 'rdp':
extra_params='&' + urlencode({'username': username, 'domain': domain, 'reqcreds': 'true'})
extra_params=f'&creds={username}@{domain}'
else:
extra_params=''

View File

@ -162,17 +162,17 @@ urlpatterns = [
),
# Enabler and Status action are first processed, and if not match, execute the generic "action" handler
re_path(
r'^uds/webapi/action/(?P<idService>.+)/enable/(?P<idTransport>[a-zA-Z0-9:-]+)$',
r'^uds/webapi/action/(?P<idService>[a-zA-Z0-9:-]+)/enable/(?P<idTransport>[a-zA-Z0-9:-]+)$',
uds.web.views.userServiceEnabler,
name='webapi.enabler',
),
re_path(
r'^uds/webapi/action/(?P<idService>.+)/status/(?P<idTransport>[a-zA-Z0-9:-]+)$',
r'^uds/webapi/action/(?P<idService>[a-zA-Z0-9:-]+)/status/(?P<idTransport>[a-zA-Z0-9:-]+)$',
uds.web.views.userServiceStatus,
name='webapi.status',
),
re_path(
r'^uds/webapi/action/(?P<idService>.+)/(?P<actionString>[a-zA-Z0-9:-]+)$',
r'^uds/webapi/action/(?P<idService>[a-zA-Z0-9:-]+)/(?P<actionString>[a-zA-Z0-9:-]+)$',
uds.web.views.action,
name='webapi.action',
),
@ -184,13 +184,19 @@ urlpatterns = [
),
# Transport own link processor
re_path(
r'^uds/webapi/trans/(?P<idService>.+)/(?P<idTransport>.+)$',
r'^uds/webapi/trans/(?P<idService>[a-zA-Z0-9:-]+)/(?P<idTransport>[a-zA-Z0-9:-]+)$',
uds.web.views.transportOwnLink,
name='TransportOwnLink',
),
# Transport ticket update (for username/password on html5)
re_path(
r'^uds/webapi/trans/ticket/(?P<idTicket>[a-zA-Z0-9:-]+)/(?P<scrambler>[a-zA-Z0-9:-]+)$',
uds.web.views.modern.update_transport_ticket,
name='webapi.transport.UpdateTransportTicket',
),
# Authenticators custom html
re_path(
r'^uds/webapi/customAuth/(?P<idAuth>.*)$',
r'^uds/webapi/customAuth/(?P<idAuth>[a-zA-Z0-9:-]*)$',
uds.web.views.customAuth,
name='uds.web.views.customAuth',
),

View File

@ -190,6 +190,7 @@ def udsJs(request: 'ExtendedHttpRequest') -> str:
),
'static': static(''),
'clientDownload': reverse('page.client-download'),
'updateTransportTicket': reverse('webapi.transport.UpdateTransportTicket', kwargs={'idTicket': 'param1', 'scrambler': 'param2'}),
# Launcher URL if exists
'launch': request.session.get('launch', ''),
'brand': settings.UDSBRAND if hasattr(settings, 'UDSBRAND') else ''

View File

@ -128,33 +128,31 @@ def exceptionView(request: 'HttpRequest', exception: Exception) -> HttpResponseR
logger.debug(traceback.format_exc())
try:
raise exception # Raise it so we can "catch" and redirect
except UserService.DoesNotExist:
return errorView(request, ERR_USER_SERVICE_NOT_FOUND)
except ServicePool.DoesNotExist: # type: ignore
return errorView(request, SERVICE_NOT_FOUND)
except Transport.DoesNotExist: # type: ignore
return errorView(request, TRANSPORT_NOT_FOUND)
except Authenticator.DoesNotExist: # type: ignore
return errorView(request, AUTHENTICATOR_NOT_FOUND)
except InvalidUserException:
if isinstance(exception, InvalidUserException):
return errorView(request, ACCESS_DENIED)
except InvalidServiceException:
return errorView(request, INVALID_SERVICE)
except MaxServicesReachedError:
return errorView(request, MAX_SERVICES_REACHED)
except InvalidAuthenticatorException:
elif isinstance(exception, InvalidAuthenticatorException):
return errorView(request, INVALID_CALLBACK)
except ServiceInMaintenanceMode:
elif isinstance(exception, InvalidServiceException):
return errorView(request, INVALID_SERVICE)
elif isinstance(exception, MaxServicesReachedError):
return errorView(request, MAX_SERVICES_REACHED)
elif isinstance(exception, ServiceInMaintenanceMode):
return errorView(request, SERVICE_IN_MAINTENANCE)
except ServiceNotReadyError as e:
# add code as high bits of idError
elif isinstance(exception, ServiceNotReadyError):
return errorView(request, SERVICE_NOT_READY)
except Exception as e:
logger.exception('Exception cautgh at view!!!')
return errorView(request, UNKNOWN_ERROR)
# raise e
elif isinstance(exception, UserService.DoesNotExist):
return errorView(request, ERR_USER_SERVICE_NOT_FOUND)
elif isinstance(exception, Transport.DoesNotExist):
return errorView(request, TRANSPORT_NOT_FOUND)
elif isinstance(exception, ServicePool.DoesNotExist):
return errorView(request, SERVICE_NOT_FOUND)
elif isinstance(exception, Authenticator.DoesNotExist):
return errorView(request, AUTHENTICATOR_NOT_FOUND)
logger.error(
'Unexpected exception: %s, traceback: %s', exception, traceback.format_exc()
)
return errorView(request, UNKNOWN_ERROR)
def error(request: 'HttpRequest', err: str) -> 'HttpResponse':

View File

@ -32,6 +32,7 @@ import time
import logging
import hashlib
import typing
import json
from django.middleware import csrf
from django.shortcuts import render
@ -43,8 +44,10 @@ from django.utils.translation import gettext as _
from uds.core.util.request import ExtendedHttpRequest, ExtendedHttpRequestWithUser
from django.views.decorators.cache import never_cache
from uds import models
from uds.core import mfas
from uds.core.auths import auth, exceptions
from uds.core.managers import cryptoManager
from uds.web.util import errors
from uds.web.forms.LoginForm import LoginForm
from uds.web.forms.MFAForm import MFAForm
@ -52,6 +55,7 @@ from uds.web.util.authentication import checkLogin
from uds.web.util.services import getServicesData
from uds.web.util import configjs
logger = logging.getLogger(__name__)
CSRF_FIELD = 'csrfmiddlewaretoken'
@ -59,8 +63,7 @@ MFA_COOKIE_NAME = 'mfa_status'
if typing.TYPE_CHECKING:
from uds import models
pass
@never_cache
def index(request: HttpRequest) -> HttpResponse:
@ -174,12 +177,12 @@ def mfa(request: ExtendedHttpRequest) -> HttpResponse:
): # If no user, or user is already authorized, redirect to index
return HttpResponseRedirect(reverse('page.index')) # No user, no MFA
mfaProvider: 'models.MFA' = request.user.manager.mfa
mfaProvider: typing.Optional['models.MFA'] = request.user.manager.mfa
if not mfaProvider:
return HttpResponseRedirect(reverse('page.index'))
userHashValue: str = hashlib.sha3_256(
(request.user.name + request.user.uuid + mfaProvider.uuid).encode()
(request.user.name + (request.user.uuid or '') + mfaProvider.uuid).encode()
).hexdigest()
# Try to get cookie anc check it
@ -297,3 +300,33 @@ def mfa(request: ExtendedHttpRequest) -> HttpResponse:
'html': mfaHtml,
}
return index(request) # Render index with MFA data
@csrf_exempt
@auth.denyNonAuthenticated
def update_transport_ticket(request: ExtendedHttpRequestWithUser, idTicket: str, scrambler: str) -> HttpResponse:
try:
if request.method == 'POST':
# Get request body as json
data = json.loads(request.body)
# Update username andd password in ticket
username = data.get('username', None) or None # None if not present
password = data.get('password', None) or None # If password is empty, set it to None
domain = data.get('domain', None) or None # If empty string, set to None
if password:
password = cryptoManager().symCrypt(password, scrambler)
models.TicketStore.update(
uuid=idTicket,
username=username,
password=password,
domain=domain,
)
return HttpResponse('{"status": "OK"}', status=200, content_type='application/json')
except Exception as e:
# fallback to error
pass
# Invalid request
return HttpResponse('{"status": "Invalid Request"}', status=400, content_type='application/json')