mirror of
https://github.com/dkmstr/openuds.git
synced 2024-12-22 13:34:04 +03:00
* Added new "Security" setting, so we can set up trusted sources for requests.
* Secured access from Tunneler to Broker. Now pam & html5 requests can only be done from trusted sources. (They will not work only from trusted hosts) * Upgraded jars for transports to fix Mac NX functionallity
This commit is contained in:
parent
66461057ec
commit
b51d918881
@ -36,7 +36,7 @@ Provides useful functions for authenticating, used by web interface.
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
from functools import wraps
|
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.Config import GlobalConfig
|
||||||
from uds.core.util import log
|
from uds.core.util import log
|
||||||
from uds.core import auths
|
from uds.core import auths
|
||||||
@ -53,24 +53,25 @@ authLogger = logging.getLogger('authLog')
|
|||||||
USER_KEY = 'uk'
|
USER_KEY = 'uk'
|
||||||
PASS_KEY = 'pk'
|
PASS_KEY = 'pk'
|
||||||
|
|
||||||
def getIp(request):
|
def getIp(request, translateProxy = True):
|
||||||
'''
|
'''
|
||||||
Obtains the IP of a Django Request, even behind a proxy
|
Obtains the IP of a Django Request, even behind a proxy
|
||||||
|
|
||||||
Returns the obtained IP, that is always be a valid ip address.
|
Returns the obtained IP, that is always be a valid ip address.
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
|
if translateProxy is False:
|
||||||
|
raise KeyError() # Do not allow HTTP_X_FORWARDED_FOR
|
||||||
request.ip = request.META['HTTP_X_FORWARDED_FOR'].split(",")[0]
|
request.ip = request.META['HTTP_X_FORWARDED_FOR'].split(",")[0]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
request.ip = request.META['REMOTE_ADDR']
|
request.ip = request.META['REMOTE_ADDR']
|
||||||
return request.ip
|
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):
|
def webLoginRequired(view_func):
|
||||||
'''
|
'''
|
||||||
Decorator to set protection to acces page
|
Decorator to set protection to access page
|
||||||
To use this decorator, the view must receive 'response' and 'user'
|
Look for samples at uds.core.web.views
|
||||||
example: view(response, user)
|
|
||||||
'''
|
'''
|
||||||
@wraps(view_func)
|
@wraps(view_func)
|
||||||
def _wrapped_view(request, *args, **kwargs):
|
def _wrapped_view(request, *args, **kwargs):
|
||||||
@ -96,6 +97,25 @@ def webLoginRequired(view_func):
|
|||||||
return view_func(request, *args, **kwargs)
|
return view_func(request, *args, **kwargs)
|
||||||
return _wrapped_view
|
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):
|
def __registerUser(authenticator, authInstance, username):
|
||||||
'''
|
'''
|
||||||
Check if this user already exists on database with this authenticator, if don't, create it with defaults
|
Check if this user already exists on database with this authenticator, if don't, create it with defaults
|
||||||
|
@ -39,7 +39,7 @@ import logging
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
GLOBAL_SECTION = 'UDS'
|
GLOBAL_SECTION = 'UDS'
|
||||||
|
SECURITY_SECTION = 'Security'
|
||||||
|
|
||||||
class Config(object):
|
class Config(object):
|
||||||
'''
|
'''
|
||||||
@ -230,16 +230,21 @@ class GlobalConfig(object):
|
|||||||
# If disallow login using /login url, and must go to an authenticator
|
# If disallow login using /login url, and must go to an authenticator
|
||||||
DISALLOW_GLOBAL_LOGIN = Config.section(GLOBAL_SECTION).value('disallowGlobalLogin', '0')
|
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
|
initDone = False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def initialize():
|
def initialize():
|
||||||
try:
|
try:
|
||||||
|
if GlobalConfig.initDone is False:
|
||||||
# All configurations are upper case
|
# All configurations are upper case
|
||||||
# Tries to initialize database data for global config so it is stored asap and get cached for use
|
# Tries to initialize database data for global config so it is stored asap and get cached for use
|
||||||
for v in GlobalConfig.__dict__.itervalues():
|
for v in GlobalConfig.__dict__.itervalues():
|
||||||
if type(v) is Config._Value:
|
if type(v) is Config._Value:
|
||||||
v.get()
|
v.get()
|
||||||
|
GlobalConfig.initDone = True
|
||||||
except:
|
except:
|
||||||
logger.debug('Config table do not exists!!!, maybe we are installing? :-)')
|
logger.debug('Config table do not exists!!!, maybe we are installing? :-)')
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ from __future__ import unicode_literals
|
|||||||
from django.http import HttpResponse
|
from django.http import HttpResponse
|
||||||
from uds.core.util.Cache import Cache
|
from uds.core.util.Cache import Cache
|
||||||
from uds.core.util import net
|
from uds.core.util import net
|
||||||
from uds.core.auths.auth import getIp
|
from uds.core.auths import auth
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@ -49,6 +49,7 @@ CONTENT_TYPE = 'text/plain'
|
|||||||
def dict2resp(dct):
|
def dict2resp(dct):
|
||||||
return '\r'.join(( k + '\t' + v for k, v in dct.iteritems()))
|
return '\r'.join(( k + '\t' + v for k, v in dct.iteritems()))
|
||||||
|
|
||||||
|
@auth.trustedSourceRequired
|
||||||
def guacamole(request, tunnelId):
|
def guacamole(request, tunnelId):
|
||||||
logger.debug('Received credentials request for tunnel id {0}'.format(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)
|
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
|
# Remove key from cache, just 1 use
|
||||||
# Cache has a limit lifetime, so we will allow to "reload" the page
|
# Cache has a limit lifetime, so we will allow to "reload" the page
|
||||||
# cache.remove(tunnelId)
|
# cache.remove(tunnelId)
|
||||||
|
@ -33,12 +33,14 @@
|
|||||||
|
|
||||||
from django.http import HttpResponseNotAllowed, HttpResponse
|
from django.http import HttpResponseNotAllowed, HttpResponse
|
||||||
from uds.core.util.Cache import Cache
|
from uds.core.util.Cache import Cache
|
||||||
|
from uds.core.auths import auth
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
# We will use the cache to "hold" the tickets valid for users
|
# We will use the cache to "hold" the tickets valid for users
|
||||||
|
|
||||||
|
@auth.trustedSourceRequired
|
||||||
def pam(request):
|
def pam(request):
|
||||||
response = ''
|
response = ''
|
||||||
cache = Cache('pam')
|
cache = Cache('pam')
|
||||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -57,9 +57,7 @@ class HTML5RDPTransport(Transport):
|
|||||||
iconFile = 'rdp.png'
|
iconFile = 'rdp.png'
|
||||||
needsJava = False # If this transport needs java for rendering
|
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)
|
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)
|
||||||
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)
|
|
||||||
useEmptyCreds = gui.CheckBoxField(label = _('Empty creds'), order = 2, tooltip = _('If checked, the credentials used to connect will be emtpy'))
|
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'))
|
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'))
|
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):
|
def initialize(self, values):
|
||||||
if values is None:
|
if values is None:
|
||||||
return
|
return
|
||||||
a = ''
|
|
||||||
if self.guacamoleServer.value[0:4] != 'http':
|
if self.guacamoleServer.value[0:4] != 'http':
|
||||||
raise Transport.ValidationException(_('The server must be http or https'))
|
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
|
# Same check as normal RDP transport
|
||||||
def isAvailableFor(self, ip):
|
def isAvailableFor(self, ip):
|
||||||
@ -124,7 +117,7 @@ class HTML5RDPTransport(Transport):
|
|||||||
username = username + '@' + username
|
username = username + '@' + username
|
||||||
|
|
||||||
# Build params dict
|
# 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))
|
logger.debug('RDP Params: {0}'.format(params))
|
||||||
|
|
||||||
|
@ -175,7 +175,7 @@ class NXTransport(Transport):
|
|||||||
# Fix username/password acording to os manager
|
# Fix username/password acording to os manager
|
||||||
username, password = userService.processUserPassword(username, password)
|
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):
|
def getHtmlComponent(self, theId, os, componentId):
|
||||||
# We use helper to keep this clean
|
# We use helper to keep this clean
|
||||||
|
Binary file not shown.
@ -33,6 +33,7 @@
|
|||||||
|
|
||||||
from django.utils.translation import ugettext as _
|
from django.utils.translation import ugettext as _
|
||||||
from django.core.urlresolvers import reverse
|
from django.core.urlresolvers import reverse
|
||||||
|
from uds.core.util import OsDetector
|
||||||
import logging, os, sys
|
import logging, os, sys
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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' })
|
applet = reverse('uds.web.views.transcomp', kwargs = { 'idTransport' : idTransport, 'componentId' : '1' })
|
||||||
# Gets the codebase, simply remove last char from applet
|
# Gets the codebase, simply remove last char from applet
|
||||||
codebase = applet[:-1]
|
codebase = applet[:-1]
|
||||||
@ -71,9 +73,14 @@ def generateHtmlForNX(transport, idUserService, idTransport, ip, user, password,
|
|||||||
'height:' + str(extra['height']),
|
'height:' + str(extra['height']),
|
||||||
'is:' + idUserService
|
'is:' + idUserService
|
||||||
]))
|
]))
|
||||||
|
if isMac is True:
|
||||||
|
msg = '<p>' + _('In order to use this transport, you need to install first OpenNX Client for mac') + '</p>'
|
||||||
|
msg += '<p>' + _('You can oibtain it from ') + '<a href="http://opennx.net/download.html">' + _('OpenNx Website') + '</a></p>'
|
||||||
|
else:
|
||||||
|
msg = '<p>' + _('In order to use this transport, you need to install first Nomachine Nx Client version 3.5.x') + '</p>'
|
||||||
|
msg +='<p>' + _('you can obtain it for your platform from') + '<a href="http://www.nomachine.com/download.php">' + _('nochamine web site') + '</a></p>'
|
||||||
res = '<div idTransport="applet"><applet code="NxTransportApplet.class" codebase="%s" archive="%s" width="140" height="22"><param name="data" value="%s"/></applet></div>' % (codebase, '1', data )
|
res = '<div idTransport="applet"><applet code="NxTransportApplet.class" codebase="%s" archive="%s" width="140" height="22"><param name="data" value="%s"/></applet></div>' % (codebase, '1', data )
|
||||||
res += '<div><p>' + _('In order to use this transport, you need to install first Nomachine Nx Client version 3.5.x') + '</p>'
|
res += '<div>' + msg + '</div>'
|
||||||
res += '<p>' + _('you can obtain it for your platform from') + '<a href="http://www.nomachine.com/download.php">' + _('nochamine web site') + '</a></p></div>'
|
|
||||||
return res
|
return res
|
||||||
|
|
||||||
|
|
||||||
|
Binary file not shown.
@ -32,7 +32,7 @@
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
from ..auths.AdminAuth import needs_credentials
|
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
|
import logging
|
||||||
|
|
||||||
@ -42,7 +42,10 @@ logger = logging.getLogger(__name__)
|
|||||||
def getConfiguration(credentials):
|
def getConfiguration(credentials):
|
||||||
res = []
|
res = []
|
||||||
addCrypt = credentials.isAdmin
|
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:
|
if cfg.isCrypted() is True and addCrypt is False:
|
||||||
continue
|
continue
|
||||||
res.append( {'section': cfg.section(), 'key' : cfg.key(), 'value' : cfg.get(), 'crypt': cfg.isCrypted(), 'longText': cfg.isLongText() } )
|
res.append( {'section': cfg.section(), 'key' : cfg.key(), 'value' : cfg.get(), 'crypt': cfg.isCrypted(), 'longText': cfg.isLongText() } )
|
||||||
|
Loading…
Reference in New Issue
Block a user