def clientEnabler(request, idService, idTransport): # Maybe we could even protect this even more by limiting referer to own server /? (just a meditation..) url = '' error = _('Service not ready. Please, try again in a while.') try: res = getService(request, idService, idTransport, doTest=False) if res is not None: scrambler = cryptoManager().randomString(32) password = cryptoManager().xor(webPassword(request), scrambler) _x, ads, _x, trans, _x = res data = { 'service': 'A' + ads.uuid, 'transport': trans.uuid, 'user': request.user.uuid, 'password': password } ticket = TicketStore.create(data) error = '' url = html.udsLink(request, ticket, scrambler) except Exception as e: error = six.text_type(e) # Not ready, show message and return to this page in a while return HttpResponse('{{ "url": "{}", "error": "{}" }}'.format(url, error), content_type='application/json')
def __init__(self, key=None, data=None): self.uuidGenerator = lambda: (cryptoManager().uuid() + cryptoManager().uuid()).replace('-', '') self.cache = Cache(TICKET_OWNER) self.data = data self.key = key if key is not None: self.load() else: self.key = self.uuidGenerator()
def scrambleId(request, id_): if SCRAMBLE_SES not in request.session: request.session[SCRAMBLE_SES] = cryptoManager().randomString(SCRAMBLE_LEN) id_ = str(id_) if len(id_) < SCRAMBLE_LEN: id_ = (id_ + '~' + cryptoManager().randomString(SCRAMBLE_LEN))[:SCRAMBLE_LEN] scrambled = cryptoManager().xor(id_, request.session[SCRAMBLE_SES]) return encoders.encode(scrambled, 'base64', asText=True)[:-3]
def getLink(self, userService, transport, ip, os, user, password, request): ci = self.processUserPassword(userService, user, password) username, password, domain = ci['username'], ci['password'], ci['domain'] if domain != '': username = domain + '\\' + username scrambler = cryptoManager().randomString(32) passwordCrypted = cryptoManager().symCrypt(password, scrambler) # Build params dict params = { 'protocol': 'rdp', 'hostname': ip, 'username': username, 'password': passwordCrypted, 'ignore-cert': 'true', 'security': self.security.value, 'drive-path': '/share/{}'.format(user.uuid), 'create-drive-path': 'true' } if self.enableFileSharing.isTrue(): params['enable-drive'] = 'true' if self.serverLayout.value != '-': params['server-layout'] = self.serverLayout.value if self.enableAudio.isTrue() is False: params['disable-audio'] = 'true' if self.enablePrinting.isTrue() is True: params['enable-printing'] = 'true' if self.wallpaper.isTrue() is True: params['enable-wallpaper'] = 'true' if self.desktopComp.isTrue() is True: params['enable-desktop-composition'] = 'true' if self.smooth.isTrue() is True: params['enable-font-smoothing'] = 'true' logger.debug('RDP Params: {0}'.format(params)) ticket = TicketStore.create(params, validity=self.ticketValidity.num()) return HttpResponseRedirect("{}/transport/?{}.{}&{}".format(self.guacamoleServer.value, ticket, scrambler, request.build_absolute_uri(reverse('utility.closer'))))
def get(uuid, invalidate=True, owner=None, secure=False): try: t = TicketStore.objects.get(uuid=uuid, owner=owner) validity = datetime.timedelta(seconds=t.validity) now = getSqlDatetime() logger.debug('Ticket validity: {} {}'.format(t.stamp + validity, now)) if t.stamp + validity < now: raise TicketStore.InvalidTicket('Not valid anymore') if secure is True: data = pickle.loads(cryptoManager().decrypt(t.data)) else: data = pickle.loads(t.data) # If has validator, execute it if t.validator is not None: validator = pickle.loads(t.validator) if validator(data) is False: raise TicketStore.InvalidTicket('Validation failed') if invalidate is True: t.stamp = now - validity - datetime.timedelta(seconds=1) t.save() return data except TicketStore.DoesNotExist: raise TicketStore.InvalidTicket('Does not exists')
def script(self): idService = self._args[0] idTransport = self._args[1] scrambler = self._args[2] hostname = self._args[3] try: res = userServiceManager().getService(self._user, self._request.os, self._request.ip, idService, idTransport) logger.debug('Res: {}'.format(res)) ip, userService, userServiceInstance, transport, transportInstance = res password = cryptoManager().symDecrpyt(self.getValue('password'), scrambler) userService.setConnectionSource(self._request.ip, hostname) # Store where we are accessing from so we can notify Service transportScript = transportInstance.getEncodedTransportScript(userService, transport, ip, self._request.os, self._user, password, self._request) return Connection.result(result=transportScript) except ServiceNotReadyError as e: # Refresh ticket and make this retrayable return Connection.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True) except Exception as e: logger.exception("Exception") return Connection.result(error=six.text_type(e)) return password
def userServiceEnabler(request, idService, idTransport): # Maybe we could even protect this even more by limiting referer to own server /? (just a meditation..) logger.debug('idService: {}, idTransport: {}'.format(idService, idTransport)) url = '' error = _('Service not ready. Please, try again in a while.') # If meta service, process and rebuild idService & idTransport try: res = userServiceManager().getService(request.user, request.os, request.ip, idService, idTransport, doTest=False) scrambler = cryptoManager().randomString(32) password = cryptoManager().symCrypt(webPassword(request), scrambler) _x, userService, _x, trans, _x = res data = { 'service': 'A' + userService.uuid, 'transport': trans.uuid, 'user': request.user.uuid, 'password': password } ticket = TicketStore.create(data) error = '' url = html.udsLink(request, ticket, scrambler) except ServiceNotReadyError as e: logger.debug('Service not ready') # Not ready, show message and return to this page in a while error += ' (code {0:04X})'.format(e.code) except MaxServicesReachedError: logger.info('Number of service reached MAX for service pool "{}"'.format(idService)) error = errors.errorString(errors.MAX_SERVICES_REACHED) except ServiceAccessDeniedByCalendar: logger.info('Access tried to a calendar limited access pool "{}"'.format(idService)) error = errors.errorString(errors.SERVICE_CALENDAR_DENIED) except Exception as e: logger.exception('Error') error = str(e) return HttpResponse( json.dumps({ 'url': str(url), 'error': str(error) }), content_type='application/json' )
def registerDownloadable(self, name, comment, path, mime='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) self._downloadables[_id] = {'name': name, 'comment': comment, 'path': path, 'mime': mime}
def webPassword(request): """ The password is stored at session using a simple scramble algorithm that keeps the password splited at session (db) and client browser cookies. This method uses this two values to recompose the user password so we can provide it to remote sessions. @param request: DJango Request @return: Unscrambled user password """ return cryptoManager().symDecrpyt(request.session.get(PASS_KEY, ''), getUDSCookie(request)) # recover as original unicode string
def create(data, validator=None, validity=DEFAULT_VALIDITY, owner=None, secure=False): ''' validity is in seconds ''' if validator is not None: validator = pickle.dumps(validator) data = pickle.dumps(data) if secure: data = cryptoManager().encrypt(data) return TicketStore.objects.create(stamp=getSqlDatetime(), data=data, validator=validator, validity=validity, owner=owner).uuid
def clientEnabler(request, idService, idTransport): # Maybe we could even protect this even more by limiting referer to own server /? (just a meditation..) logger.debug('idService: {}, idTransport: {}'.format(idService, idTransport)) url = '' error = _('Service not ready. Please, try again in a while.') try: res = userServiceManager().getService(request.user, request.ip, idService, idTransport, doTest=False) scrambler = cryptoManager().randomString(32) password = cryptoManager().xor(webPassword(request), scrambler) _x, userService, _x, trans, _x = res data = { 'service': 'A' + userService.uuid, 'transport': trans.uuid, 'user': request.user.uuid, 'password': password } ticket = TicketStore.create(data) error = '' url = html.udsLink(request, ticket, scrambler) except ServiceNotReadyError as e: logger.debug('Service not ready') # Not ready, show message and return to this page in a while error += ' (code {0:04X})'.format(e.code) except MaxServicesReachedError: logger.info('Number of service reached MAX for service pool "{}"'.format(idService)) error = errors.errorString(errors.MAX_SERVICES_REACHED) except Exception as e: logger.exception('Error') error = six.text_type(e) return HttpResponse( '{{ "url": "{}", "error": "{}" }}'.format(url, error), content_type='application/json' )
def guacamole(request, tunnelId): logger.debug('Received credentials request for tunnel id {0}'.format(tunnelId)) tunnelId, scrambler = tunnelId.split('.') try: val = TicketStore.get(tunnelId, invalidate=False) val['password'] = cryptoManager().symDecrpyt(val['password'], scrambler) response = dict2resp(val) except Exception: logger.error('Invalid guacamole ticket (F5 on client?): {}'.format(tunnelId)) return HttpResponse(ERROR, content_type=CONTENT_TYPE) return HttpResponse(response, content_type=CONTENT_TYPE)
def getUDSCookie(request, response=None, force=False): ''' Generates a random cookie for uds, used, for example, to encript things ''' if 'uds' not in request.COOKIES: cookie = cryptoManager().randomString(48) if response is not None: response.set_cookie('uds', cookie) request.COOKIES['uds'] = cookie else: cookie = request.COOKIES['uds'] if response is not None and force is True: response.set_cookie('uds', cookie) return cookie
def saveItem(self, parent, item): logger.debug('Saving user {0} / {1}'.format(parent, item)) valid_fields = ['name', 'real_name', 'comments', 'state', 'staff_member', 'is_admin'] if 'password' in self._params: valid_fields.append('password') self._params['password'] = cryptoManager().hash(self._params['password']) fields = self.readFieldsFromParams(valid_fields) user = None try: auth = parent.getInstance() if item is None: # Create new auth.createUser(fields) # this throws an exception if there is an error (for example, this auth can't create users) toSave = {} for k in valid_fields: toSave[k] = fields[k] user = parent.users.create(**toSave) else: auth.modifyUser(fields) # Notifies authenticator toSave = {} for k in valid_fields: toSave[k] = fields[k] user = parent.users.get(uuid=processUuid(item)) user.__dict__.update(toSave) logger.debug('User parent: {}'.format(user.parent)) if auth.isExternalSource is False and (user.parent is None or user.parent == ''): groups = self.readFieldsFromParams(['groups'])['groups'] logger.debug('Groups: {}'.format(groups)) logger.debug('Got Groups {}'.format(parent.groups.filter(uuid__in=groups))) user.groups = parent.groups.filter(uuid__in=groups) user.save() except User.DoesNotExist: self.invalidItemException() except IntegrityError: # Duplicate key probably raise RequestError(_('User already exists (duplicate key error)')) except AuthenticatorException as e: raise RequestError(six.text_type(e)) except ValidationError as e: raise RequestError(six.text_type(e.message)) except Exception: logger.exception('Saving user') self.invalidRequestException() return self.getItems(parent, user.uuid)
def get(self): ''' Processes get requests ''' logger.debug("Client args for GET: {0}".format(self._args)) if len(self._args) == 0: url = self._request.build_absolute_uri(reverse('ClientDownload')) return Client.result({ 'availableVersion': CLIENT_VERSION, 'requiredVersion': REQUIRED_CLIENT_VERSION, 'downloadUrl': url }) if len(self._args) == 1: return Client.result(_('Correct')) try: ticket, scrambler = self._args except Exception: raise RequestError('Invalid request') try: data = TicketStore.get(ticket) except Exception: return Client.result(error=errors.ACCESS_DENIED) self._request.user = User.objects.get(uuid=data['user']) try: logger.debug(data) res = getService(self._request, data['service'], data['transport']) logger.debug('Res: {}'.format(res)) if res is not None: ip, userService, userServiceInstance, transport, transportInstance = res password = cryptoManager().xor(data['password'], scrambler).decode('utf-8') transportScript = transportInstance.getUDSTransportScript(userService, transport, ip, self._request.os, self._request.user, password, self._request) logger.debug('Script:\n{}'.format(transportScript)) return Client.result(result=transportScript.encode('bz2').encode('base64')) except Exception as e: logger.exception("Exception") return Client.result(error=six.text_type(e)) return Client.result(error=errors.SERVICE_NOT_READY)
def unserializeForm(self, values: bytes): """ This method unserializes the values previously obtained using :py:meth:`serializeForm`, and stores the valid values form form fileds inside its corresponding field """ if not values: # Has nothing return try: # Set all values to defaults ones for k in self._gui: if self._gui[k].isType( gui.InputField.HIDDEN_TYPE ) and self._gui[k].isSerializable() is False: # logger.debug('Field {0} is not unserializable'.format(k)) continue self._gui[k].value = self._gui[k].defValue values = typing.cast(bytes, encoders.decode(values, 'zip')) if not values: # Has nothing return for txt in values.split(b'\002'): kb, v = txt.split(b'\003') k = kb.decode('utf8') # Convert name to unicode if k in self._gui: try: if v and v[0] == 1: val = pickle.loads(v[1:]) elif v and v[0] == 4: val = cryptoManager().AESDecrypt( v[1:], UDSB, True).decode() else: val = v # Ensure "legacy bytes" values are loaded correctly as unicode if isinstance(val, bytes): val = val.decode('utf_8') except Exception: logger.exception('Pickling {} from {}'.format(k, self)) val = '' self._gui[k].value = val # logger.debug('Value for {0}:{1}'.format(k, val)) except Exception: logger.exception('Exception on unserialization on %s', self.__class__)
def get( uuid: str, invalidate: bool = True, owner: typing.Optional[str] = None, secure: bool = False, ) -> typing.Any: try: dbOwner = owner if secure: if not owner: raise ValueError( 'Tried to use a secure ticket without owner') dbOwner = SECURED t = TicketStore.objects.get(uuid=uuid, owner=dbOwner) validity = datetime.timedelta(seconds=t.validity) now = getSqlDatetime() logger.debug('Ticket validity: %s %s', t.stamp + validity, now) if t.stamp + validity < now: raise TicketStore.InvalidTicket('Not valid anymore') data: bytes = t.data if secure: # Owner has already been tested and it's not emtpy data = cryptoManager().AESDecrypt( data, typing.cast(str, owner).encode()) data = pickle.loads(data) # If has validator, execute it if t.validator: validator: ValidatorType = pickle.loads(t.validator) if validator(data) is False: raise TicketStore.InvalidTicket('Validation failed') if invalidate is True: t.stamp = now - validity - datetime.timedelta(seconds=1) t.save(update_fields=['stamp']) return data except TicketStore.DoesNotExist: raise TicketStore.InvalidTicket('Does not exists')
def getUDSCookie(request: HttpRequest, response: typing.Optional[HttpResponse] = None, force: bool = False) -> str: ''' Generates a random cookie for uds, used, for example, to encript things ''' if 'uds' not in request.COOKIES: cookie = cryptoManager().randomString(48) if response is not None: response.set_cookie('uds', cookie) request.COOKIES['uds'] = cookie else: cookie = request.COOKIES['uds'] if response and force: response.set_cookie('uds', cookie) return cookie
def registerDownloadable(self, name, comment, path, mime='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) self._downloadables[_id] = { 'name': name, 'comment': comment, 'path': path, 'mime': mime }
def guacamole(request, tunnelId): logger.debug( 'Received credentials request for tunnel id {0}'.format(tunnelId)) tunnelId, scrambler = tunnelId.split('.') try: val = TicketStore.get(tunnelId, invalidate=False) val['password'] = cryptoManager().symDecrpyt(val['password'], scrambler) response = dict2resp(val) except Exception: logger.error( 'Invalid guacamole ticket (F5 on client?): {}'.format(tunnelId)) return HttpResponse(ERROR, content_type=CONTENT_TYPE) return HttpResponse(response, content_type=CONTENT_TYPE)
def getLink( # pylint: disable=too-many-locals self, userService: 'models.UserService', transport: 'models.Transport', ip: str, os: typing.Dict[str, str], user: '******', password: str, request: 'HttpRequest') -> str: # Build params dict params = { 'protocol': 'vnc', 'hostname': ip, 'port': str(self.vncPort.num()), } if self.username.value.strip(): params['username'] = self.username.value.strip() if self.password.value.strip(): params['password'] = self.password.value.strip() if self.colorDepth.value != '-': params['color-depth'] = self.colorDepth.value if self.swapRedBlue.isTrue(): params['swap-red-blue'] = 'true' if self.cursor.isTrue(): params['cursor'] = 'remote' if self.readOnly.isTrue(): params['read-only'] = 'true' logger.debug('VNC Params: %s', params) scrambler = cryptoManager().randomString(32) ticket = models.TicketStore.create(params, validity=self.ticketValidity.num()) onw = '' if self.forceNewWindow.value == gui.TRUE: onw = 'o_n_w={}' elif self.forceNewWindow.value == 'overwrite': onw = 'o_s_w=yes' onw = onw.format(hash(transport.name)) return str("{}/guacamole/#/?data={}.{}{}".format( self.guacamoleServer.value, ticket, scrambler, onw))
def set(self, value: str): if GlobalConfig.isInitialized() is False: _saveLater.append((self, value)) return if self._crypt is True: value = cryptoManager().encrypt(value) # Editable here means that this configuration value can be edited by admin directly (generally, that this is a "clean text" value) logger.debug('Saving config %s.%s as %s', self._section.name(), self._key, value) try: obj, _ = DBConfig.objects.get_or_create(section=self._section.name(), key=self._key) # @UndefinedVariable obj.value, obj.crypt, obj.long, obj.field_type = value, self._crypt, self._longText, self._type obj.save() except Exception: logger.exception('Exception') # Probably a migration issue, just ignore it logger.info("Could not save configuration key %s.%s", self._section.name(), self._key)
def authenticate(self, username: str, credentials: str, groupsManager: 'auths.GroupsManager') -> bool: logger.debug('Username: %s, Password: %s', username, credentials) dbAuth = self.dbAuthenticator() try: user: '******' = dbAuth.users.get(name=username, state=State.ACTIVE) except Exception: return False if user.parent: # Direct auth not allowed for "derived" users return False # Internal Db Auth has its own groups, and if it active it is valid if user.password == cryptoManager().hash(credentials): # hashlib.sha1(credentials.encode('utf-8')).hexdigest(): groupsManager.validate([g.name for g in user.groups.all()]) return True return False
def marshal(self) -> bytes: """ Serializes the os manager data so we can store it in database """ base = codecs.encode(super().marshal(), 'hex').decode() return '\t'.join( [ 'v4', self._domain, self._ou, self._account, cryptoManager().encrypt(self._password), base, self._group, self._serverHint, self._ssl, self._removeOnExit, ] ).encode('utf8')
def __init__(self, section: 'Config.Section', key: str, default: str = '', crypt: bool = False, longText: bool = False, **kwargs): logger.debug('Var: %s %s KWARGS: %s', section, key, kwargs) self._type: int = kwargs.get('type', -1) self._section: 'Config.Section' = section self._key: str = key self._crypt: bool = crypt self._longText: bool = longText if crypt is False or not default: self._default: str = default else: self._default = cryptoManager().encrypt(default) self._data: typing.Optional[str] = None
def authenticate(self, username, credentials, groupsManager): logger.debug('Username: {0}, Password: {1}'.format(username, credentials)) auth = self.dbAuthenticator() try: try: usr = auth.users.get(name=username, state=State.ACTIVE) except Exception: return False if usr.parent is not None and usr.parent != '': # Direct auth not allowed for "derived" users return False # Internal Db Auth has its own groups, and if it active it is valid if usr.password == cryptoManager().hash(credentials): # hashlib.sha1(credentials.encode('utf-8')).hexdigest(): groupsManager.validate([g.name for g in usr.groups.all()]) return True return False except dbAuthenticator.DoesNotExist: # @UndefinedVariable return False
def store(uuid, data, validator=None, validity=DEFAULT_VALIDITY, owner=owner, secure=False): ''' Stores an ticketstore. If one with this uuid already exists, replaces it. Else, creates a new one validity is in seconds ''' if validator is not None: validator = pickle.dumps(validator) data = pickle.dumps(data) if secure: data = cryptoManager().encrypt() try: t = TicketStore.objects.get(uuid=uuid, owner=owner) t.data = data t.stamp = getSqlDatetime() t.validity = validity t.save() except TicketStore.DoesNotExist: t = TicketStore.objects.create(uuid=uuid, stamp=getSqlDatetime(), data=data, validator=validator, validity=validity, owner=owner)
def storeSessionAuthdata(session, id_auth, username, password, locale, is_admin, staff_member, scrambler): ''' Stores the authentication data inside current session :param session: session handler (Djano user session object) :param id_auth: Authenticator id (DB object id) :param username: Name of user (login name) :param locale: Assigned locale :param is_admin: If user is considered admin or not :param staff_member: If is considered as staff member ''' if is_admin: staff_member = True # Make admins also staff members :-) session['REST'] = { 'auth': id_auth, 'username': username, 'password': cryptoManager().xor(password, scrambler), # Stores "bytes" 'locale': locale, 'is_admin': is_admin, 'staff_member': staff_member }
def webLogin(request: HttpRequest, response: HttpResponse, user: User, password: str) -> bool: """ Helper function to, once the user is authenticated, store the information at the user session. @return: Always returns True """ from uds import REST if user.id != ROOT_ID: # If not ROOT user (this user is not inside any authenticator) manager_id = user.manager.id else: manager_id = -1 # If for any reason the "uds" cookie is removed, recreated it cookie = getUDSCookie(request, response) user.updateLastAccess() request.session.clear() request.session[USER_KEY] = user.id request.session[PASS_KEY] = cryptoManager().symCrypt(password, cookie) # Stores "bytes" # Ensures that this user will have access through REST api if logged in through web interface REST.Handler.storeSessionAuthdata(request.session, manager_id, user.name, password, get_language(), request.os, user.is_admin, user.staff_member, cookie) return True
def storeSessionAuthdata(session, id_auth, username, password, locale, platform, is_admin, staff_member, scrambler): """ Stores the authentication data inside current session :param session: session handler (Djano user session object) :param id_auth: Authenticator id (DB object id) :param username: Name of user (login name) :param locale: Assigned locale :param is_admin: If user is considered admin or not :param staff_member: If is considered as staff member """ if is_admin: staff_member = True # Make admins also staff members :-) session['REST'] = { 'auth': id_auth, 'username': username, 'password': cryptoManager().symCrypt(password, scrambler), # Stores "bytes" 'locale': locale, 'platform': platform, 'is_admin': is_admin, 'staff_member': staff_member }
def create_for_tunnel( userService: 'UserService', port: int, host: typing.Optional[str] = None, extra: typing.Optional[typing.Mapping[str, typing.Any]] = None, validity: int = 60 * 60 * 24, # 24 Hours default validity for tunnel tickets ) -> str: owner = cryptoManager().randomString(length=8) data = { 'u': userService.user.uuid, 's': userService.uuid, 'h': host, 'p': port, 'e': extra, } return (TicketStore.create( data=data, validity=validity, owner=owner, secure=True, ) + owner)
def webLogin(request, response, user, password): """ Helper function to, once the user is authenticated, store the information at the user session. @return: Always returns True """ from uds import REST if user.id != ROOT_ID: # If not ROOT user (this user is not inside any authenticator) manager_id = user.manager.id else: manager_id = -1 # If for any reason the "uds" cookie is removed, recreated it cookie = getUDSCookie(request, response) user.updateLastAccess() request.session.clear() request.session[USER_KEY] = user.id request.session[PASS_KEY] = cryptoManager().symCrypt(password, cookie) # Stores "bytes" # Ensures that this user will have access through REST api if logged in through web interface REST.Handler.storeSessionAuthdata(request.session, manager_id, user.name, password, get_language(), request.os, user.is_admin, user.staff_member, cookie) return True
def script(self): # Could be one-liner, (... = ..[0:4]), but mypy complains so this is fine :) idService = self._args[0] idTransport = self._args[1] scrambler = self._args[2] hostname = self._args[3] try: res = userServiceManager().getService(self._user, self._request.os, self._request.ip, idService, idTransport) logger.debug('Res: %s', res) ip, userService, userServiceInstance, transport, transportInstance = res # pylint: disable=unused-variable password = cryptoManager().symDecrpyt(self.getValue('password'), scrambler) userService.setConnectionSource(self._request.ip, hostname) # Store where we are accessing from so we can notify Service transportScript = transportInstance.getEncodedTransportScript(userService, transport, ip, self._request.os, self._user, password, self._request) return Connection.result(result=transportScript) except ServiceNotReadyError as e: # Refresh ticket and make this retrayable return Connection.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True) except Exception as e: logger.exception("Exception") return Connection.result(error=str(e))
def guacamole(request: HttpRequest, tunnelId: str) -> HttpResponse: logger.debug('Received credentials request for tunnel id %s', tunnelId) try: tunnelId, scrambler = tunnelId.split('.') val = TicketStore.get(tunnelId, invalidate=False) # Extra check that the ticket data belongs to original requested user service/user if 'ticket-info' in val: ti = val['ticket-info'] del val['ticket-info'] # Do not send this data to guacamole!! :) try: userService = UserService.objects.get(uuid=ti['userService']) except Exception: logger.error( 'The requested guacamole userservice does not exists anymore' ) raise if userService.user.uuid != ti['user']: logger.error( 'The requested userservice has changed owner and is not accesible' ) raise Exception() if 'password' in val: val['password'] = cryptoManager().symDecrpyt( val['password'], scrambler) response = dict2resp(val) except Exception: # logger.error('Invalid guacamole ticket (F5 on client?): %s', tunnelId) return HttpResponse(ERROR, content_type=CONTENT_TYPE) return HttpResponse(response, content_type=CONTENT_TYPE)
class GlobalConfig: """ Simple helper to keep track of global configuration """ SESSION_EXPIRE_TIME: Config.Value = Config.section(GLOBAL_SECTION).value('sessionExpireTime', '24', type=Config.NUMERIC_FIELD) # Max session duration (in use) after a new publishment has been made # Delay between cache checks. reducing this number will increase cache generation speed but also will load service providers CACHE_CHECK_DELAY: Config.Value = Config.section(GLOBAL_SECTION).value('cacheCheckDelay', '19', type=Config.NUMERIC_FIELD) # Delayed task number of threads PER SERVER, with higher number of threads, deplayed task will complete sooner, but it will give more load to overall system DELAYED_TASKS_THREADS: Config.Value = Config.section(GLOBAL_SECTION).value('delayedTasksThreads', '4', type=Config.NUMERIC_FIELD) # Number of scheduler threads running PER SERVER, with higher number of threads, deplayed task will complete sooner, but it will give more load to overall system SCHEDULER_THREADS: Config.Value = Config.section(GLOBAL_SECTION).value('schedulerThreads', '3', type=Config.NUMERIC_FIELD) # Waiting time before removing "errored" and "removed" publications, cache, and user assigned machines. Time is in seconds CLEANUP_CHECK: Config.Value = Config.section(GLOBAL_SECTION).value('cleanupCheck', '3607', type=Config.NUMERIC_FIELD) # Time to maintaing "info state" items before removing it, in seconds KEEP_INFO_TIME: Config.Value = Config.section(GLOBAL_SECTION).value('keepInfoTime', '14401', type=Config.NUMERIC_FIELD) # Defaults to 2 days 172800?? better 4 hours xd # Max number of services to be "preparing" at same time MAX_PREPARING_SERVICES: Config.Value = Config.section(GLOBAL_SECTION).value('maxPreparingServices', '15', type=Config.NUMERIC_FIELD) # Defaults to 15 services at once (per service provider) # Max number of service to be at "removal" state at same time MAX_REMOVING_SERVICES: Config.Value = Config.section(GLOBAL_SECTION).value('maxRemovingServices', '15', type=Config.NUMERIC_FIELD) # Defaults to 15 services at once (per service provider) # If we ignore limits (max....) IGNORE_LIMITS: Config.Value = Config.section(GLOBAL_SECTION).value('ignoreLimits', '0', type=Config.BOOLEAN_FIELD) # Number of services to initiate removal per run of CacheCleaner USER_SERVICE_CLEAN_NUMBER: Config.Value = Config.section(GLOBAL_SECTION).value('userServiceCleanNumber', '3', type=Config.NUMERIC_FIELD) # Defaults to 3 per wun # Removal Check time for cache, publications and deployed services REMOVAL_CHECK: Config.Value = Config.section(GLOBAL_SECTION).value('removalCheck', '31', type=Config.NUMERIC_FIELD) # Defaults to 30 seconds # Login URL LOGIN_URL: Config.Value = Config.section(GLOBAL_SECTION).value('loginUrl', '/login', type=Config.TEXT_FIELD) # Defaults to /login # Session duration USER_SESSION_LENGTH: Config.Value = Config.section(SECURITY_SECTION).value('userSessionLength', '14400', type=Config.NUMERIC_FIELD) # Defaults to 4 hours # Superuser (do not need to be at database!!!) SUPER_USER_LOGIN: Config.Value = Config.section(SECURITY_SECTION).value('superUser', 'root', type=Config.TEXT_FIELD) # Superuser password (do not need to be at database!!!) SUPER_USER_PASS: Config.Value = Config.section(SECURITY_SECTION).valueCrypt('rootPass', 'udsmam0', type=Config.TEXT_FIELD) # Idle time before closing session on admin SUPER_USER_ALLOW_WEBACCESS: Config.Value = Config.section(SECURITY_SECTION).value('allowRootWebAccess', '1', type=Config.BOOLEAN_FIELD) # Time an admi session can be idle before being "logged out" ADMIN_IDLE_TIME: Config.Value = Config.section(SECURITY_SECTION).value('adminIdleTime', '14400', type=Config.NUMERIC_FIELD) # Defaults to 4 hous # Time betwen checks of unused services by os managers # Unused services will be invoked for every machine assigned but not in use AND that has been assigned at least this time # (only if os manager asks for this characteristic) CHECK_UNUSED_TIME: Config.Value = Config.section(GLOBAL_SECTION).value('checkUnusedTime', '631', type=Config.NUMERIC_FIELD) # Defaults to 10 minutes # Default CSS Used CSS: Config.Value = Config.section(GLOBAL_SECTION).value('css', settings.STATIC_URL + 'css/uds.css', type=Config.TEXT_FIELD) # Max logins before blocking an account MAX_LOGIN_TRIES: Config.Value = Config.section(GLOBAL_SECTION).value('maxLoginTries', '3', type=Config.NUMERIC_FIELD) # Block time in second for an user that makes too many mistakes, 5 minutes default LOGIN_BLOCK: Config.Value = Config.section(GLOBAL_SECTION).value('loginBlockTime', '300', type=Config.NUMERIC_FIELD) # Do autorun of service if just one service. # 0 = No autorun, 1 = Autorun at login # In a future, maybe necessary another value "2" that means that autorun always AUTORUN_SERVICE: Config.Value = Config.section(GLOBAL_SECTION).value('autorunService', '0', type=Config.BOOLEAN_FIELD) # Redirect HTTP to HTTPS REDIRECT_TO_HTTPS: Config.Value = Config.section(GLOBAL_SECTION).value('redirectToHttps', '0', type=Config.BOOLEAN_FIELD) # Max time needed to get a service "fully functional" before it's considered "failed" and removed # The time is in seconds MAX_INITIALIZING_TIME: Config.Value = Config.section(GLOBAL_SECTION).value('maxInitTime', '3601', type=Config.NUMERIC_FIELD) # Custom HTML for login page CUSTOM_HTML_LOGIN: Config.Value = Config.section(GLOBAL_SECTION).value('customHtmlLogin', '', type=Config.LONGTEXT_FIELD) # Maximum logs per user service MAX_LOGS_PER_ELEMENT: Config.Value = Config.section(GLOBAL_SECTION).value('maxLogPerElement', '100', type=Config.NUMERIC_FIELD) # Time to restrain a deployed service in case it gives some errors at some point RESTRAINT_TIME: Config.Value = Config.section(GLOBAL_SECTION).value('restrainTime', '600', type=Config.NUMERIC_FIELD) # Number of errors that must occurr in RESTRAIN_TIME to restrain deployed service RESTRAINT_COUNT: Config.Value = Config.section(GLOBAL_SECTION).value('restrainCount', '3', type=Config.NUMERIC_FIELD) # Statistics duration, in days STATS_DURATION: Config.Value = Config.section(GLOBAL_SECTION).value('statsDuration', '365', type=Config.NUMERIC_FIELD) # If disallow login using /login url, and must go to an authenticator DISALLOW_GLOBAL_LOGIN: Config.Value = Config.section(GLOBAL_SECTION).value('disallowGlobalLogin', '0', type=Config.BOOLEAN_FIELD) # Allos preferences access to users PREFERENCES_ALLOWED: Config.Value = Config.section(GLOBAL_SECTION).value('allowPreferencesAccess', '1', type=Config.BOOLEAN_FIELD) # Allowed "trusted sources" for request TRUSTED_SOURCES: Config.Value = Config.section(SECURITY_SECTION).value('Trusted Hosts', '*', type=Config.TEXT_FIELD) # Allow clients to notify their own ip (if set), or use always the request extracted IP HONOR_CLIENT_IP_NOTIFY: Config.Value = Config.section(SECURITY_SECTION).value('honorClientNotifyIP', '0', type=Config.BOOLEAN_FIELD) # If there is a proxy in front of us BEHIND_PROXY: Config.Value = Config.section(SECURITY_SECTION).value('Behind a proxy', '0', type=Config.BOOLEAN_FIELD) # If we use new logout mechanics EXCLUSIVE_LOGOUT: Config.Value = Config.section(SECURITY_SECTION).value('Exclusive Logout', '0', type=Config.BOOLEAN_FIELD) # Clusters related vars # Maximum desired CPU Load. If cpu is over this value, a migration of a service is "desirable" # CLUSTER_MIGRATE_CPULOAD: Config.Value = Config.section(CLUSTER_SECTION).value('Migration CPU Load', '80', type=Config.NUMERIC_FIELD) # Maximum CPU Load for a node to be elegible for destination of a migration # CLUSTER_ELEGIBLE_CPULOAD: Config.Value = Config.section(CLUSTER_SECTION).value('Destination CPU Load', '60', type=Config.NUMERIC_FIELD) # Minimum desired Memory free for a cluster node. If free memory (in %) is under this percentage, # a migration of a service inside this node is "desirable" # CLUSTER_MIGRATE_MEMORYLOAD: Config.Value = Config.section(CLUSTER_SECTION).value('Migration Free Memory', '20', type=Config.NUMERIC_FIELD) # Minimum Free memory for a node to be elegible for a destination of a migration # CLUSTER_ELEGIBLE_MEMORYLOAD: Config.Value = Config.section(CLUSTER_SECTION).value('Migration Free Memory', '40', type=Config.NUMERIC_FIELD) RELOAD_TIME: Config.Value = Config.section(GLOBAL_SECTION).value('Page reload Time', '300', type=Config.NUMERIC_FIELD) LIMITED_BY_CALENDAR_TEXT: Config.Value = Config.section(GLOBAL_SECTION).value('Calendar access denied text', '', type=Config.TEXT_FIELD) # Defaults to Nothing # Custom message for error when limiting by calendar # This is used so templates can change "styles" from admin interface LOWERCASE_USERNAME: Config.Value = Config.section(SECURITY_SECTION).value('Convert username to lowercase', '1', type=Config.BOOLEAN_FIELD) # Global UDS ID (common for all servers on the same cluster) UDS_ID: Config.Value = Config.section(GLOBAL_SECTION).value('UDS ID', cryptoManager().uuid(), type=Config.READ_FIELD) # Site display name & copyright info SITE_NAME: Config.Value = Config.section(GLOBAL_SECTION).value('Site name', 'UDS Enterprise', type=Config.TEXT_FIELD) SITE_COPYRIGHT: Config.Value = Config.section(GLOBAL_SECTION).value('Site copyright info', '© Virtual Cable S.L.U.', type=Config.TEXT_FIELD) SITE_COPYRIGHT_LINK: Config.Value = Config.section(GLOBAL_SECTION).value('Site copyright link', 'https://www.udsenterprise.com', type=Config.TEXT_FIELD) EXPERIMENTAL_FEATURES: Config.Value = Config.section(GLOBAL_SECTION).value('Experimental Features', '0', type=Config.BOOLEAN_FIELD) _initDone = False @staticmethod def isInitialized(): return GlobalConfig._initDone @staticmethod def initialize() -> None: if GlobalConfig._initDone is False: try: # Tries to initialize database data for global config so it is stored asap and get cached for use GlobalConfig._initDone = True for v in GlobalConfig.__dict__.values(): if isinstance(v, Config.Value): v.get() for c in _getLater: logger.debug('Get later: %s', c) c.get() _getLater[:] = [] for c, v in _saveLater: logger.debug('Saving delayed value: %s', c) c.set(v) _saveLater[:] = [] # Process some global config parameters # GlobalConfig.UDS_THEME.setParams(['html5', 'semantic']) except Exception: logger.debug('Config table do not exists!!!, maybe we are installing? :-)')
def get(self): ''' Processes get requests ''' logger.debug("Client args for GET: {0}".format(self._args)) if len(self._args) == 0: # Gets version url = self._request.build_absolute_uri(reverse('ClientDownload')) return Client.result({ 'availableVersion': CLIENT_VERSION, 'requiredVersion': REQUIRED_CLIENT_VERSION, 'downloadUrl': url }) if len(self._args) == 1: # Simple test return Client.result(_('Correct')) try: ticket, scrambler = self._args # If more than 2 args, got an error hostname = self._params['hostname'] # Or if hostname is not included... srcIp = self._request.ip # Ip is optional, if GlobalConfig.HONOR_CLIENT_IP_NOTIFY.getBool() is True: srcIp = self._params.get('ip', srcIp) except Exception: raise RequestError('Invalid request') logger.debug('Got Ticket: {}, scrambled: {}, Hostname: {}, Ip: {}'.format(ticket, scrambler, hostname, srcIp)) try: data = TicketStore.get(ticket) except Exception: return Client.result(error=errors.ACCESS_DENIED) self._request.user = User.objects.get(uuid=data['user']) try: logger.debug(data) res = userServiceManager().getService(self._request.user, self._request.ip, data['service'], data['transport']) logger.debug('Res: {}'.format(res)) ip, userService, userServiceInstance, transport, transportInstance = res password = cryptoManager().xor(data['password'], scrambler).decode('utf-8') userService.setConnectionSource(srcIp, hostname) # Store where we are accessing from so we can notify Service transportScript = transportInstance.getUDSTransportScript(userService, transport, ip, self._request.os, self._request.user, password, self._request) logger.debug('Script:\n{}'.format(transportScript)) return Client.result(result=transportScript.encode('bz2').encode('base64')) except ServiceNotReadyError as e: # Refresh ticket and make this retrayable TicketStore.revalidate(ticket, 20) # Retry will be in at most 5 seconds return Client.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True) except Exception as e: logger.exception("Exception") return Client.result(error=six.text_type(e)) # Will never reach this raise RuntimeError('Unreachable point reached!!!')
def generateUuid(obj: typing.Any = None) -> str: """ Generates a ramdom uuid for models default """ return cryptoManager().uuid(obj=obj).lower()
def get(self): # pylint: disable=too-many-locals """ Processes get requests """ logger.debug('Client args for GET: %s', self._args) if not self._args: # Gets version return Client.result({ 'availableVersion': CLIENT_VERSION, 'requiredVersion': REQUIRED_CLIENT_VERSION, 'downloadUrl': self._request.build_absolute_uri( reverse('page.client-download')) }) if len(self._args) == 1: # Simple test return Client.result(_('Correct')) try: ticket, scrambler = self._args # If more than 2 args, got an error. pylint: disable=unbalanced-tuple-unpacking hostname = self._params[ 'hostname'] # Or if hostname is not included... srcIp = self._request.ip # Ip is optional, if GlobalConfig.HONOR_CLIENT_IP_NOTIFY.getBool() is True: srcIp = self._params.get('ip', srcIp) except Exception: raise RequestError('Invalid request') logger.debug('Got Ticket: %s, scrambled: %s, Hostname: %s, Ip: %s', ticket, scrambler, hostname, srcIp) try: data = TicketStore.get(ticket) except Exception: return Client.result(error=errors.ACCESS_DENIED) self._request.user = User.objects.get(uuid=data['user']) try: logger.debug(data) ip, userService, userServiceInstance, transport, transportInstance = userServiceManager( ).getService(self._request.user, self._request.os, self._request.ip, data['service'], data['transport']) logger.debug('Res: %s %s %s %s %s', ip, userService, userServiceInstance, transport, transportInstance) password = cryptoManager().symDecrpyt(data['password'], scrambler) userService.setConnectionSource( srcIp, hostname ) # Store where we are accessing from so we can notify Service transportScript, signature, params = transportInstance.getEncodedTransportScript( userService, transport, ip, self._request.os, self._request.user, password, self._request) logger.debug('Signature: %s', signature) logger.debug('Data:#######\n%s\n###########', params) return Client.result( result={ 'script': transportScript, 'signature': signature, # It is already on base64 'params': encoders.encode(encoders.encode(json.dumps(params), 'bz2'), 'base64', asText=True), }) except ServiceNotReadyError as e: # Refresh ticket and make this retrayable TicketStore.revalidate( ticket, 20) # Retry will be in at most 5 seconds, so 20 is fine :) return Client.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True) except Exception as e: logger.exception("Exception") return Client.result(error=str(e))
def generateUuid(): # more owner is this: # ''.join(random.SystemRandom().choice(string.ascii_lowercase + string.digits) for _ in range(40)) return cryptoManager().randomString(40)
def generateUuid() -> str: return cryptoManager().randomString(40)
def userServiceEnabler(request: 'HttpRequest', idService: str, idTransport: str) -> HttpResponse: # Maybe we could even protect this even more by limiting referer to own server /? (just a meditation..) logger.debug('idService: %s, idTransport: %s', idService, idTransport) url = '' error = _('Service not ready. Please, try again in a while.') # If meta service, process and rebuild idService & idTransport try: res = userServiceManager().getService(request.user, request.os, request.ip, idService, idTransport, doTest=False) scrambler = cryptoManager().randomString(32) password = cryptoManager().symCrypt(webPassword(request), scrambler) userService, trans = res[1], res[3] typeTrans = trans.getType() error = '' # No error if typeTrans.ownLink: url = reverse('TransportOwnLink', args=('A' + userService.uuid, trans.uuid)) else: data = { 'service': 'A' + userService.uuid, 'transport': trans.uuid, 'user': request.user.uuid, 'password': password } ticket = TicketStore.create(data) url = html.udsLink(request, ticket, scrambler) except ServiceNotReadyError as e: logger.debug('Service not ready') # Not ready, show message and return to this page in a while # error += ' (code {0:04X})'.format(e.code) error = _( 'Your service is being created, please, wait for a few seconds while we complete it.)' ) + '({}%)'.format(int(e.code * 25)) except MaxServicesReachedError: logger.info('Number of service reached MAX for service pool "%s"', idService) error = errors.errorString(errors.MAX_SERVICES_REACHED) except ServiceAccessDeniedByCalendar: logger.info('Access tried to a calendar limited access pool "%s"', idService) error = errors.errorString(errors.SERVICE_CALENDAR_DENIED) except Exception as e: logger.exception('Error') error = str(e) return HttpResponse(json.dumps({ 'url': str(url), 'error': str(error) }), content_type='application/json')
def ticketAuth(request: 'HttpRequest', ticketId: str) -> HttpResponse: # pylint: disable=too-many-locals,too-many-branches,too-many-statements """ Used to authenticate an user via a ticket """ try: data = TicketStore.get(ticketId, invalidate=True) try: # Extract ticket.data from ticket.data storage, and remove it if success username = data['username'] groups = data['groups'] auth = data['auth'] realname = data['realname'] servicePool = data['servicePool'] password = cryptoManager().decrypt(data['password']) transport = data['transport'] except Exception: logger.error('Ticket stored is not valid') raise auths.exceptions.InvalidUserException() auth = Authenticator.objects.get(uuid=auth) # If user does not exists in DB, create it right now # Add user to groups, if they exists... grps: typing.List = [] for g in groups: try: grps.append(auth.groups.get(uuid=g)) except Exception: logger.debug('Group list has changed since ticket assignment') if not grps: logger.error('Ticket has no valid groups') raise Exception('Invalid ticket authentication') usr = auth.getOrCreateUser(username, realname) if usr is None or State.isActive( usr.state) is False: # If user is inactive, raise an exception raise auths.exceptions.InvalidUserException() # Add groups to user (replace existing groups) usr.groups.set(grps) # Force cookie generation webLogin(request, None, usr, password) request.user = usr # Temporarily store this user as "authenticated" user, next requests will be done using session request.session[ 'ticket'] = '1' # Store that user access is done using ticket # Override and recalc transport based on current os transport = None logger.debug("Service & transport: %s, %s", servicePool, transport) # Check if servicePool is part of the ticket if servicePool: # If service pool is in there, also is transport res = userServiceManager().getService(request.user, request.os, request.ip, 'F' + servicePool, transport, False) _, userService, _, transport, _ = res transportInstance = transport.getInstance() if transportInstance.ownLink is True: link = reverse('TransportOwnLink', args=('A' + userService.uuid, transport.uuid)) else: link = html.udsAccessLink(request, 'A' + userService.uuid, transport.uuid) request.session['launch'] = link response = HttpResponseRedirect(reverse('page.ticket.launcher')) else: response = HttpResponseRedirect(reverse('page.index')) # Now ensure uds cookie is at response getUDSCookie(request, response, True) return response except ServiceNotReadyError as e: return errors.errorView(request, errors.SERVICE_NOT_READY) except TicketStore.InvalidTicket: return errors.errorView(request, errors.RELOAD_NOT_SUPPORTED) except Authenticator.DoesNotExist: logger.error('Ticket has an non existing authenticator') return errors.errorView(request, errors.ACCESS_DENIED) except ServicePool.DoesNotExist: logger.error('Ticket has an invalid Service Pool') return errors.errorView(request, errors.SERVICE_NOT_FOUND) except Exception as e: logger.exception('Exception') return errors.exceptionView(request, e)
from uds.REST import Handler from uds.REST import RequestError from uds.models import UserService import datetime import six import logging logger = logging.getLogger(__name__) # Actor key, configurable in Security Section of administration interface actorKey = Config.Config.section(Config.SECURITY_SECTION).value('Master Key', cryptoManager().uuid(datetime.datetime.now()).replace('-', ''), type=Config.Config.TEXT_FIELD) actorKey.get() # Error codes: ERR_INVALID_KEY = 1 ERR_HOST_NOT_MANAGED = 2 ERR_USER_SERVICE_NOT_FOUND = 3 ERR_OSMANAGER_ERROR = 4 # Enclosed methods under /actor path class Actor(Handler): ''' Processes actor requests '''
def put(self): """ Processes put requests, currently only under "create" """ logger.debug(self._args) # Parameters can only be theese for p in self._params: if p not in VALID_PARAMS: logger.debug('Parameter {} not in valid ticket parameters list'.format(p)) raise RequestError('Invalid parameters') if len(self._args) != 1 or self._args[0] not in ('create',): raise RequestError('Invalid method') if 'username' not in self._params or 'groups' not in self._params: raise RequestError('Invalid parameters') found = None for i in ('authId', 'authTag', 'auth', 'authSmallName'): if i in self._params: found = i break if found is None: raise RequestError('Invalid parameters (no auth)') force = self._params.get('force', '0') in ('1', 'true', 'True') userIp = self._params.get('userIp', None) try: authId = self._params.get('authId', None) authTag = self._params.get('authTag', self._params.get('authSmallName', None)) authName = self._params.get('auth', None) # Will raise an exception if no auth found if authId is not None: auth = Authenticator.objects.get(uuid=processUuid(authId.lower())) elif authName is not None: auth = Authenticator.objects.get(name=authName) else: auth = Authenticator.objects.get(small_name=authTag) username = self._params['username'] password = self._params.get('password', '') # Some machines needs password, depending on configuration groups = self._params['groups'] if isinstance(groups, (six.text_type, six.binary_type)): groups = (groups,) grps = [] for g in groups: try: grps.append(auth.groups.get(name=g).uuid) except Exception: logger.info('Group {} from ticket does not exists on auth {}, forced creation: {}'.format(g, auth, force)) if force: grps.append(auth.groups.create(name=g, comments='Autocreated form ticket by using force paratemeter').uuid) if len(grps) == 0: # No valid group in groups names raise Exception('Authenticator does not contain ANY of the requested groups') groups = grps time = int(self._params.get('time', 60)) time = 60 if time < 1 else time realname = self._params.get('realname', self._params['username']) servicePool = self._params.get('servicePool', None) transport = self._params.get('transport', None) if servicePool is not None: servicePool = DeployedService.objects.get(uuid=processUuid(servicePool)) # If forced that servicePool must honor groups if force: for addGrp in set(groups) - set(servicePool.assignedGroups.values_list('uuid', flat=True)): servicePool.assignedGroups.add(auth.groups.get(uuid=addGrp)) if transport is not None: transport = Transport.objects.get(uuid=processUuid(transport)) try: servicePool.validateTransport(transport) except Exception: logger.error('Transport {} is not valid for Service Pool {}'.format(transport.name, servicePool.name)) raise Exception('Invalid transport for Service Pool') else: if userIp is None: transport = tools.DictAsObj({'uuid': None}) else: transport = None for v in servicePool.transports.order_by('priority'): if v.validForIp(userIp): transport = v break if transport is None: logger.error('Service pool {} does not has valid transports for ip {}'.format(servicePool.name, userIp)) raise Exception('Service pool does not has any valid transports for ip {}'.format(userIp)) servicePool = servicePool.uuid transport = transport.uuid # pylint: disable=maybe-no-member except Authenticator.DoesNotExist: return Tickets.result(error='Authenticator does not exists') except DeployedService.DoesNotExist: return Tickets.result(error='Service pool does not exists') except Transport.DoesNotExist: return Tickets.result(error='Transport does not exists') except Exception as e: return Tickets.result(error=six.text_type(e)) data = { 'username': username, 'password': cryptoManager().encrypt(password), 'realname': realname, 'groups': groups, 'auth': auth.uuid, 'servicePool': servicePool, 'transport': transport, } ticket = TicketStore.create(data) return Tickets.result(ticket)
def generateUuid(): ''' Generates a ramdom uuid for models default ''' return cryptoManager().uuid().lower()
def put(self): # pylint: disable=too-many-locals,too-many-branches,too-many-statements """ Processes put requests, currently only under "create" """ logger.debug(self._args) # Check that call is correct (pamateters, args, ...) self._checkInput() if 'username' not in self._params or 'groups' not in self._params: raise RequestError('Invalid parameters') force: bool = self._params.get('force', '0') in ('1', 'true', 'True') userIp: typing.Optional[str] = self._params.get('userIp', None) try: authId = self._params.get('authId', None) authName = self._params.get('auth', None) authTag = self._params.get('authTag', self._params.get('authSmallName', None)) # Will raise an exception if no auth found if authId: auth = models.Authenticator.objects.get(uuid=processUuid(authId.lower())) elif authName: auth = models.Authenticator.objects.get(name=authName) else: auth = models.Authenticator.objects.get(small_name=authTag) username: str = self._params['username'] password: str = self._params.get('password', '') # Some machines needs password, depending on configuration groupIds: typing.List[str] = [] for groupName in tools.asList(self._params['groups']): try: groupIds.append(auth.groups.get(name=groupName).uuid) except Exception: logger.info('Group %s from ticket does not exists on auth %s, forced creation: %s', groupName, auth, force) if force: # Force creation by call groupIds.append(auth.groups.create(name=groupName, comments='Autocreated form ticket by using force paratemeter').uuid) if not groupIds: # No valid group in groups names raise RequestError('Authenticator does not contain ANY of the requested groups and force is not used') time = int(self._params.get('time', 60)) time = 60 if time < 1 else time realname: str = self._params.get('realname', self._params['username']) if 'servicePool' in self._params: servicePool: models.ServicePool = models.ServicePool.objects.get(uuid=processUuid(self._params['servicePool'])) # If forced that servicePool must honor groups if force: for addGrp in set(groupIds) - set(servicePool.assignedGroups.values_list('uuid', flat=True)): servicePool.assignedGroups.add(auth.groups.get(uuid=addGrp)) if 'transport' in self._params: transport: models.Transport = models.Transport.objects.get(uuid=processUuid(self._params['transport'])) try: servicePool.validateTransport(transport) except Exception: logger.error('Transport %s is not valid for Service Pool %s', transport.name, servicePool.name) raise Exception('Invalid transport for Service Pool') else: transport = models.Transport(uuid=None) if userIp: for v in servicePool.transports.order_by('priority'): if v.validForIp(userIp): transport = v break if transport.uuid is None: logger.error('Service pool %s does not has valid transports for ip %s', servicePool.name, userIp) raise Exception('Service pool does not has any valid transports for ip {}'.format(userIp)) servicePool = servicePool.uuid transport = transport.uuid except models.Authenticator.DoesNotExist: return Tickets.result(error='Authenticator does not exists') except models.ServicePool.DoesNotExist: return Tickets.result(error='Service pool does not exists') except models.Transport.DoesNotExist: return Tickets.result(error='Transport does not exists') except Exception as e: return Tickets.result(error=str(e)) data = { 'username': username, 'password': cryptoManager().encrypt(password), 'realname': realname, 'groups': groupIds, 'auth': auth.uuid, 'servicePool': servicePool, 'transport': transport, } ticket = models.TicketStore.create(data) return Tickets.result(ticket)
def get(self): """ Processes get requests """ logger.debug("Client args for GET: {0}".format(self._args)) if len(self._args) == 0: # Gets version url = self._request.build_absolute_uri(reverse('page.client-download')) return Client.result({ 'availableVersion': CLIENT_VERSION, 'requiredVersion': REQUIRED_CLIENT_VERSION, 'downloadUrl': url }) if len(self._args) == 1: # Simple test return Client.result(_('Correct')) try: ticket, scrambler = self._args # If more than 2 args, got an error hostname = self._params['hostname'] # Or if hostname is not included... srcIp = self._request.ip # Ip is optional, if GlobalConfig.HONOR_CLIENT_IP_NOTIFY.getBool() is True: srcIp = self._params.get('ip', srcIp) except Exception: raise RequestError('Invalid request') logger.debug('Got Ticket: {}, scrambled: {}, Hostname: {}, Ip: {}'.format(ticket, scrambler, hostname, srcIp)) try: data = TicketStore.get(ticket) except Exception: return Client.result(error=errors.ACCESS_DENIED) self._request.user = User.objects.get(uuid=data['user']) try: logger.debug(data) res = userServiceManager().getService(self._request.user, self._request.ip, data['service'], data['transport']) logger.debug('Res: {}'.format(res)) ip, userService, userServiceInstance, transport, transportInstance = res password = cryptoManager().symDecrpyt(data['password'], scrambler) userService.setConnectionSource(srcIp, hostname) # Store where we are accessing from so we can notify Service transportScript, signature, params = transportInstance.getEncodedTransportScript(userService, transport, ip, self._request.os, self._request.user, password, self._request) logger.debug('Signature: {}'.format(signature)) logger.debug('Data:#######\n{}\n###########'.format(params)) return Client.result(result={ 'script': transportScript, 'signature': signature, # It is already on base64 'params': json.dumps(params).encode('bz2').encode('base64'), }) except ServiceNotReadyError as e: # Refresh ticket and make this retrayable TicketStore.revalidate(ticket, 20) # Retry will be in at most 5 seconds return Client.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True) except Exception as e: logger.exception("Exception") return Client.result(error=six.text_type(e)) # Will never reach this raise RuntimeError('Unreachable point reached!!!')
from uds.REST import Handler from uds.REST import RequestError from uds.core.managers import cryptoManager from uds.core.osmanagers import OSManager from uds.core.util import Config from uds.core.util.State import State from uds.core.util.model import processUuid from uds.models import TicketStore from uds.models import UserService logger = logging.getLogger(__name__) # Actor key, configurable in Security Section of administration interface actorKey = Config.Config.section(Config.SECURITY_SECTION).value( 'Master Key', cryptoManager().uuid(datetime.datetime.now()).replace('-', ''), type=Config.Config.TEXT_FIELD) actorKey.get() # Error codes: ERR_INVALID_KEY = 1 ERR_HOST_NOT_MANAGED = 2 ERR_USER_SERVICE_NOT_FOUND = 3 ERR_OSMANAGER_ERROR = 4 # Constants for tickets OWNER = 'ACTOR' SECURE_OWNER = 'SACTOR'
def generateUuid(): """ Generates a ramdom uuid for models default """ return cryptoManager().uuid().lower()
def unscrambleId(request, id_): idd = cryptoManager().xor(encoders.decode(id_ + '==\n', 'base64', asText=False), request.session[SCRAMBLE_SES]) return idd.decode('utf8').split('~')[0]
def getLink( # pylint: disable=too-many-locals self, userService: 'models.UserService', transport: 'models.Transport', ip: str, os: typing.Dict[str, str], user: '******', password: str, request: 'HttpRequest' ) -> str: credsInfo = self.processUserAndPassword(userService, user, password) username, password, domain = credsInfo['username'], credsInfo['password'], credsInfo['domain'] if domain != '': username = domain + '\\' + username scrambler = cryptoManager().randomString(32) passwordCrypted = cryptoManager().symCrypt(password, scrambler) # Build params dict params = { 'protocol': 'rdp', 'hostname': ip, 'username': username, 'password': passwordCrypted, 'ignore-cert': 'true', 'security': self.security.value, 'drive-path': '/share/{}'.format(user.uuid), 'create-drive-path': 'true' } if self.enableFileSharing.isTrue(): params['enable-drive'] = 'true' if self.serverLayout.value != '-': params['server-layout'] = self.serverLayout.value if self.enableAudio.isTrue() is False: params['disable-audio'] = 'true' if self.enablePrinting.isTrue() is True: params['enable-printing'] = 'true' params['printer-name'] = 'UDS-Printer' if self.wallpaper.isTrue() is True: params['enable-wallpaper'] = 'true' if self.desktopComp.isTrue() is True: params['enable-desktop-composition'] = 'true' if self.smooth.isTrue() is True: params['enable-font-smoothing'] = 'true' logger.debug('RDP Params: %s', params) ticket = models.TicketStore.create(params, validity=self.ticketValidity.num()) return HttpResponseRedirect( "{}/transport/{}?{}.{}&{}".format( self.guacamoleServer.value, 'o_n_w' if self.forceNewWindow.isTrue() else '', ticket, scrambler, 'javascript:window.close();' ) )