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:
parent
a005bf1ca0
commit
ae2ffccbc3
@ -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,
|
||||
|
@ -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
@ -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");
|
||||
|
@ -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=''
|
||||
|
||||
|
@ -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',
|
||||
),
|
||||
|
@ -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 ''
|
||||
|
@ -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':
|
||||
|
@ -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')
|
||||
|
Loading…
Reference in New Issue
Block a user