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 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
|
||||
|
@ -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? :-)')
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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')
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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))
|
||||
|
||||
|
@ -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
|
||||
|
Binary file not shown.
@ -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 = '<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><p>' + _('In order to use this transport, you need to install first Nomachine Nx Client version 3.5.x') + '</p>'
|
||||
res += '<p>' + _('you can obtain it for your platform from') + '<a href="http://www.nomachine.com/download.php">' + _('nochamine web site') + '</a></p></div>'
|
||||
res += '<div>' + msg + '</div>'
|
||||
return res
|
||||
|
||||
|
||||
|
Binary file not shown.
@ -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() } )
|
||||
|
Loading…
Reference in New Issue
Block a user