diff --git a/server/src/uds/core/auths/auth.py b/server/src/uds/core/auths/auth.py index 4d60bcc89..7d16f73bd 100644 --- a/server/src/uds/core/auths/auth.py +++ b/server/src/uds/core/auths/auth.py @@ -36,7 +36,7 @@ Provides useful functions for authenticating, used by web interface. from __future__ import unicode_literals from functools import wraps -from django.http import HttpResponseRedirect +from django.http import HttpResponseRedirect, HttpResponseForbidden from uds.core.util.Config import GlobalConfig from uds.core.util import log from uds.core import auths @@ -53,24 +53,25 @@ authLogger = logging.getLogger('authLog') USER_KEY = 'uk' PASS_KEY = 'pk' -def getIp(request): +def getIp(request, translateProxy = True): ''' Obtains the IP of a Django Request, even behind a proxy Returns the obtained IP, that is always be a valid ip address. ''' try: + if translateProxy is False: + raise KeyError() # Do not allow HTTP_X_FORWARDED_FOR request.ip = request.META['HTTP_X_FORWARDED_FOR'].split(",")[0] except KeyError: request.ip = request.META['REMOTE_ADDR'] return request.ip -# Decorator to make easier protect pages +# Decorator to make easier protect pages that needs to be logged in def webLoginRequired(view_func): ''' - Decorator to set protection to acces page - To use this decorator, the view must receive 'response' and 'user' - example: view(response, user) + Decorator to set protection to access page + Look for samples at uds.core.web.views ''' @wraps(view_func) def _wrapped_view(request, *args, **kwargs): @@ -96,6 +97,25 @@ def webLoginRequired(view_func): return view_func(request, *args, **kwargs) return _wrapped_view +# Decorator to protect pages that needs to be accessed from "trusted sites" +def trustedSourceRequired(view_func): + ''' + Decorator to set protection to access page + look for sample at uds.dispatchers.pam + ''' + @wraps(view_func) + def _wrapped_view(request, *args, **kwargs): + ''' + Wrapped function for decorator + ''' + from uds.core.util import net + getIp(request, False) + if net.ipInNetwork(request.ip, GlobalConfig.TRUSTED_SOURCES.get(True)) is False: + return HttpResponseForbidden() + return view_func(request, *args, **kwargs) + return _wrapped_view + + def __registerUser(authenticator, authInstance, username): ''' Check if this user already exists on database with this authenticator, if don't, create it with defaults diff --git a/server/src/uds/core/util/Config.py b/server/src/uds/core/util/Config.py index 9fd9f9b86..9f95cd818 100644 --- a/server/src/uds/core/util/Config.py +++ b/server/src/uds/core/util/Config.py @@ -39,7 +39,7 @@ import logging logger = logging.getLogger(__name__) GLOBAL_SECTION = 'UDS' - +SECURITY_SECTION = 'Security' class Config(object): ''' @@ -230,16 +230,21 @@ class GlobalConfig(object): # If disallow login using /login url, and must go to an authenticator DISALLOW_GLOBAL_LOGIN = Config.section(GLOBAL_SECTION).value('disallowGlobalLogin', '0') + # Allowed "trusted sources" for request + TRUSTED_SOURCES = Config.section(SECURITY_SECTION).value('Trusted Hosts', '*') + initDone = False @staticmethod def initialize(): try: - # All configurations are upper case - # Tries to initialize database data for global config so it is stored asap and get cached for use - for v in GlobalConfig.__dict__.itervalues(): - if type(v) is Config._Value: - v.get() + if GlobalConfig.initDone is False: + # All configurations are upper case + # Tries to initialize database data for global config so it is stored asap and get cached for use + for v in GlobalConfig.__dict__.itervalues(): + if type(v) is Config._Value: + v.get() + GlobalConfig.initDone = True except: logger.debug('Config table do not exists!!!, maybe we are installing? :-)') diff --git a/server/src/uds/dispatchers/guacamole/views.py b/server/src/uds/dispatchers/guacamole/views.py index ae0beeead..7c7d88ae2 100644 --- a/server/src/uds/dispatchers/guacamole/views.py +++ b/server/src/uds/dispatchers/guacamole/views.py @@ -36,7 +36,7 @@ from __future__ import unicode_literals from django.http import HttpResponse from uds.core.util.Cache import Cache from uds.core.util import net -from uds.core.auths.auth import getIp +from uds.core.auths import auth import logging logger = logging.getLogger(__name__) @@ -49,6 +49,7 @@ CONTENT_TYPE = 'text/plain' def dict2resp(dct): return '\r'.join(( k + '\t' + v for k, v in dct.iteritems())) +@auth.trustedSourceRequired def guacamole(request, tunnelId): logger.debug('Received credentials request for tunnel id {0}'.format(tunnelId)) @@ -57,23 +58,6 @@ def guacamole(request, tunnelId): val = cache.get(tunnelId, None) - logger.debug('Value of cache element: {0}'.format(val)) - - # Add request source ip to request object - getIp(request) - - # Ensure request for credentials are allowed - allowFrom = val['allow-from'].replace(' ', '') - # and remove allow-from from parameters - del val['allow-from'] - - logger.debug('Checking validity of ip in network(s) {1}'.format(request.ip, allowFrom)) - - if net.ipInNetwork(request.ip, allowFrom) is False: - logger.error('Ip {0} not allowed (not in range {1})'.format(request.ip, allowFrom)) - raise Exception() # Ip not allowed - - # Remove key from cache, just 1 use # Cache has a limit lifetime, so we will allow to "reload" the page # cache.remove(tunnelId) diff --git a/server/src/uds/dispatchers/pam/views.py b/server/src/uds/dispatchers/pam/views.py index ffa239a59..6c2867261 100644 --- a/server/src/uds/dispatchers/pam/views.py +++ b/server/src/uds/dispatchers/pam/views.py @@ -33,12 +33,14 @@ from django.http import HttpResponseNotAllowed, HttpResponse from uds.core.util.Cache import Cache +from uds.core.auths import auth import logging logger = logging.getLogger(__name__) # We will use the cache to "hold" the tickets valid for users +@auth.trustedSourceRequired def pam(request): response = '' cache = Cache('pam') diff --git a/server/src/uds/locale/de/LC_MESSAGES/django.mo b/server/src/uds/locale/de/LC_MESSAGES/django.mo index 1e47de189..b2e210c3b 100644 Binary files a/server/src/uds/locale/de/LC_MESSAGES/django.mo and b/server/src/uds/locale/de/LC_MESSAGES/django.mo differ diff --git a/server/src/uds/locale/es/LC_MESSAGES/django.mo b/server/src/uds/locale/es/LC_MESSAGES/django.mo index 9e2230519..b0c7a3e32 100644 Binary files a/server/src/uds/locale/es/LC_MESSAGES/django.mo and b/server/src/uds/locale/es/LC_MESSAGES/django.mo differ diff --git a/server/src/uds/locale/fr/LC_MESSAGES/django.mo b/server/src/uds/locale/fr/LC_MESSAGES/django.mo index b4e5bdbad..5a5577374 100644 Binary files a/server/src/uds/locale/fr/LC_MESSAGES/django.mo and b/server/src/uds/locale/fr/LC_MESSAGES/django.mo differ diff --git a/server/src/uds/transports/HTML5RDP/HTML5RDP.py b/server/src/uds/transports/HTML5RDP/HTML5RDP.py index 0defb893f..4dd2fd0fc 100644 --- a/server/src/uds/transports/HTML5RDP/HTML5RDP.py +++ b/server/src/uds/transports/HTML5RDP/HTML5RDP.py @@ -57,9 +57,7 @@ class HTML5RDPTransport(Transport): iconFile = 'rdp.png' needsJava = False # If this transport needs java for rendering - guacamoleServer = gui.TextField(label=_('Tunnel Server'), order = 1, tooltip = _('Host of the tunnel server (use http/https & port if needed)'), defvalue = 'https://', length = 64) - allowRequestsFrom = gui.TextField(label=_('Allowed hosts'), order = 1, tooltip = _('Hosts allowed to ask for credentials for users (use * for all host, but not recommended). Comma separated list'), - defvalue = '*', length = 256) + guacamoleServer = gui.TextField(label=_('Tunnel Server'), order = 1, tooltip = _('Host of the tunnel server (use http/https & port if needed) as accesible from users'), defvalue = 'https://', length = 64) useEmptyCreds = gui.CheckBoxField(label = _('Empty creds'), order = 2, tooltip = _('If checked, the credentials used to connect will be emtpy')) fixedName = gui.TextField(label=_('Username'), order = 3, tooltip = _('If not empty, this username will be always used as credential')) fixedPassword = gui.PasswordField(label=_('Password'), order = 4, tooltip = _('If not empty, this password will be always used as credential')) @@ -68,13 +66,8 @@ class HTML5RDPTransport(Transport): def initialize(self, values): if values is None: return - a = '' if self.guacamoleServer.value[0:4] != 'http': raise Transport.ValidationException(_('The server must be http or https')) - try: - net.networksFromString(self.allowRequestsFrom.value) - except Exception as e: - raise Transport.ValidationException(_('Invalid network: {0}').format(str(e))) # Same check as normal RDP transport def isAvailableFor(self, ip): @@ -124,7 +117,7 @@ class HTML5RDPTransport(Transport): username = username + '@' + username # Build params dict - params = { 'protocol':'rdp', 'hostname':ip, 'username': username, 'password': password, 'allow-from': self.allowRequestsFrom.value } + params = { 'protocol':'rdp', 'hostname':ip, 'username': username, 'password': password } logger.debug('RDP Params: {0}'.format(params)) diff --git a/server/src/uds/transports/NX/NXTransport.py b/server/src/uds/transports/NX/NXTransport.py index d086405b8..6f2e34b1a 100644 --- a/server/src/uds/transports/NX/NXTransport.py +++ b/server/src/uds/transports/NX/NXTransport.py @@ -175,7 +175,7 @@ class NXTransport(Transport): # Fix username/password acording to os manager username, password = userService.processUserPassword(username, password) - return generateHtmlForNX(self, idUserService, idTransport, ip, username, password, extra) + return generateHtmlForNX(self, idUserService, idTransport, ip, os, username, password, extra) def getHtmlComponent(self, theId, os, componentId): # We use helper to keep this clean diff --git a/server/src/uds/transports/NX/applet/nxtransport.jar b/server/src/uds/transports/NX/applet/nxtransport.jar index 8d3496acb..f8261e35e 100644 Binary files a/server/src/uds/transports/NX/applet/nxtransport.jar and b/server/src/uds/transports/NX/applet/nxtransport.jar differ diff --git a/server/src/uds/transports/NX/web.py b/server/src/uds/transports/NX/web.py index 790a43e6d..1dfe3147e 100644 --- a/server/src/uds/transports/NX/web.py +++ b/server/src/uds/transports/NX/web.py @@ -33,6 +33,7 @@ from django.utils.translation import ugettext as _ from django.core.urlresolvers import reverse +from uds.core.util import OsDetector import logging, os, sys logger = logging.getLogger(__name__) @@ -53,7 +54,8 @@ def simpleScrambler(data): -def generateHtmlForNX(transport, idUserService, idTransport, ip, user, password, extra): +def generateHtmlForNX(transport, idUserService, idTransport, ip, os, user, password, extra): + isMac = os['OS'] == OsDetector.Macintosh applet = reverse('uds.web.views.transcomp', kwargs = { 'idTransport' : idTransport, 'componentId' : '1' }) # Gets the codebase, simply remove last char from applet codebase = applet[:-1] @@ -71,9 +73,14 @@ def generateHtmlForNX(transport, idUserService, idTransport, ip, user, password, 'height:' + str(extra['height']), 'is:' + idUserService ])) + if isMac is True: + msg = '

' + _('In order to use this transport, you need to install first OpenNX Client for mac') + '

' + msg += '

' + _('You can oibtain it from ') + '' + _('OpenNx Website') + '

' + else: + msg = '

' + _('In order to use this transport, you need to install first Nomachine Nx Client version 3.5.x') + '

' + msg +='

' + _('you can obtain it for your platform from') + '' + _('nochamine web site') + '

' res = '
' % (codebase, '1', data ) - res += '

' + _('In order to use this transport, you need to install first Nomachine Nx Client version 3.5.x') + '

' - res += '

' + _('you can obtain it for your platform from') + '' + _('nochamine web site') + '

' + res += '
' + msg + '
' return res diff --git a/server/src/uds/transports/TSNX/applet/nxtuntransport.jar b/server/src/uds/transports/TSNX/applet/nxtuntransport.jar index 88300eff1..918ac3522 100644 Binary files a/server/src/uds/transports/TSNX/applet/nxtuntransport.jar and b/server/src/uds/transports/TSNX/applet/nxtuntransport.jar differ diff --git a/server/src/uds/xmlrpc/tools/Config.py b/server/src/uds/xmlrpc/tools/Config.py index 315bb4fb5..9d6843ca0 100644 --- a/server/src/uds/xmlrpc/tools/Config.py +++ b/server/src/uds/xmlrpc/tools/Config.py @@ -32,7 +32,7 @@ ''' from ..auths.AdminAuth import needs_credentials -from uds.core.util.Config import Config +from uds.core.util.Config import Config, GLOBAL_SECTION, SECURITY_SECTION import logging @@ -42,7 +42,10 @@ logger = logging.getLogger(__name__) def getConfiguration(credentials): res = [] addCrypt = credentials.isAdmin - for cfg in Config.enumerate(): + + priorities = { GLOBAL_SECTION: 0, SECURITY_SECTION: 1 } + + for cfg in (v[0] for v in sorted([(x, priorities.get(x.section(), 20)) for x in Config.enumerate()], key = lambda c:c[1])): if cfg.isCrypted() is True and addCrypt is False: continue res.append( {'section': cfg.section(), 'key' : cfg.key(), 'value' : cfg.get(), 'crypt': cfg.isCrypted(), 'longText': cfg.isLongText() } )