Advancing a bit more towards new uds transport model

This commit is contained in:
Adolfo Gómez García 2015-03-23 20:06:03 +01:00
parent 38b7ac892e
commit 2303861650
12 changed files with 195 additions and 72 deletions

View File

@ -59,7 +59,6 @@ class Transport(Module):
typeType = 'Base Transport'
typeDescription = 'Base Transport'
iconFile = 'transport.png'
needsJava = False # If this transport needs java for rendering
# Supported names for OS (used right now, but lots of more names for sure)
# Windows
# Macintosh
@ -70,6 +69,9 @@ class Transport(Module):
webTransport = False
tcTransport = False
# If the link to use transport is provided by transport itself
ownLink = False
# Protocol "type". This is not mandatory, but will help
protocol = protocols.NONE
@ -158,9 +160,18 @@ class Transport(Module):
def getUDSTransportData(self, userService, transport, ip, os, user, password, request):
'''
Must ve overriden
Must override if transport does not provides its own link (that is, it is an UDS native transport)
Returns the transport data needed to connect with the userService
This is invoked right before service is accesed (secuentally)
This is invoked right before service is accesed (secuentally).
The class must provide either this method or the getLink method
'''
return None
def getLink(self, userService, transport, ip, os, user, password, request):
'''
Must override if transport does provides its own link
If transport provides own link, this method provides the link itself
'''
return None

View File

@ -41,6 +41,16 @@ import logging
logger = logging.getLogger(__name__)
def udsLink(request, ticket, scrambler, transport):
if request.is_secure():
proto = 'udss'
else:
proto = 'uds'
return "{}://{}{}/{}/{}".format(proto, request.build_absolute_uri('/').split('//')[1], ticket, scrambler, transport.uuid)
def parseDate(dateToParse):
import datetime

View File

@ -34,6 +34,7 @@ from __future__ import unicode_literals
from uds.core.util.Cache import Cache
from uds.core.jobs.Job import Job
from uds.models import TicketStore
import logging
logger = logging.getLogger(__name__)
@ -51,3 +52,17 @@ class CacheCleaner(Job):
logger.debug('Starting cache cleanup')
Cache.cleanUp()
logger.debug('Done cache cleanup')
class TicketStoreCleaner(Job):
frequency = 3600 * 12 # every twelve hours
friendly_name = 'Ticket Storage Cleaner'
def __init__(self, environment):
super(TicketStoreCleaner, self).__init__(environment)
def run(self):
logger.debug('Starting ticket storage cleanup')
TicketStore.cleanup()
logger.debug('Done ticket storage cleanup')

View File

@ -36,6 +36,8 @@ from django.http import HttpResponse
from uds.core.util.Cache import Cache
from uds.core.util import net
from uds.core.auths import auth
from uds.models import TicketStore
import logging
logger = logging.getLogger(__name__)
@ -47,7 +49,7 @@ CONTENT_TYPE = 'text/plain'
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
@ -55,18 +57,14 @@ def guacamole(request, tunnelId):
logger.debug('Received credentials request for tunnel id {0}'.format(tunnelId))
try:
cache = Cache('guacamole')
val = cache.get(tunnelId, None)
val = TicketStore.get(tunnelId, invalidate=False)
# Remove key from cache, just 1 use
# Cache has a limit lifetime, so we will allow to "reload" the page
# cache.remove(tunnelId)
# response = 'protocol\trdp\rhostname\tw7adolfo\rusername\tadmin\rpassword\ttemporal'
response = dict2resp(val)
except:
except Exception:
return HttpResponse(ERROR, content_type=CONTENT_TYPE)
return HttpResponse(response, content_type=CONTENT_TYPE)

View File

@ -53,6 +53,8 @@ class TicketStore(UUIDModel):
This is intended for small images (i will limit them to 128x128), so storing at db is fine
'''
DEFAULT_VALIDITY = 60
MAX_VALIDITY = 60 * 60 * 12
# Cleanup will purge all elements that have been created MAX_VALIDITY ago
stamp = models.DateTimeField() # Date creation or validation of this entry
validity = models.IntegerField(default=60) # Duration allowed for this ticket to be valid, in seconds
@ -133,6 +135,13 @@ class TicketStore(UUIDModel):
except TicketStore.DoesNotExist:
raise Exception('Does not exists')
@staticmethod
def cleanup():
now = getSqlDatetime()
cleanSince = now - datetime.timedelta(seconds=TicketStore.MAX_VALIDITY)
number = TicketStore.objects.filter(stamp__lt=cleanSince).delete()
logger.debug('Cleaned {} tickets'.format(number))
def __unicode__(self):
if self.validator is not None:
validator = pickle.loads(self.validator)

View File

@ -97,4 +97,4 @@ from .DelayedTask import DelayedTask
# Image galery related
from .Image import Image
from .Ticket import TicketStore
from .TicketStore import TicketStore

View File

@ -8,14 +8,14 @@
{% for ser in services %}
{% if ser.transports %}
<div class="service-container">
{% with trans=ser.transports|first numTransports=ser.transports|length link=host|add:ser.ticket|add:'/'|add:scrambler|add:'/' %}
{% with trans=ser.transports|first numTransports=ser.transports|length %}
<div class="service{% if ser.maintenance %} maintenance{% endif %}{% if ser.in_use %} inuse{% endif %}"
{% if ser.maintenance %}
data-content="{% trans "Under maintenance" %}"
{% elif ser.in_use %}
data-content="{%trans "Currently in use" %}"
{% endif %}
data-href="{{ link }}{{ trans.id }}">
data-href="{{ trans.link }}">
<div class="service-image">
<img src="{% url "uds.web.views.serviceImage" idImage=ser.imageId %}" />
</div>
@ -36,7 +36,7 @@
<div class="modal-body">
<ul>
{% for trans in ser.transports %}
<li><a class="uds-service-transport" data-href-alt="{% url 'uds.web.views.client_downloads' %}" href="{{ link }}{{ trans.id }}"><img src="{% url "uds.web.views.transportIcon" idTrans=trans.id %}" alt="{{ trans.name }}" />{{ trans.name }}</a></li>
<li><a class="uds-service-transport" data-href-alt="{% url 'uds.web.views.client_downloads' %}" href="{{ trans.link }}"><img src="{% url "uds.web.views.transportIcon" idTrans=trans.id %}" alt="{{ trans.name }}" />{{ trans.name }}</a></li>
{% endfor %}
</ul>
@ -192,20 +192,43 @@
$('.inuse').popover({container: 'body', trigger: 'hover', delay: { show: 500, hide: 100 }, placement: 'auto top'});
$('div.service:not(.maintenance)').on("click", function (event){
var url, el;
event.preventDefault();
uds.launch($(this));
// check url
el = $(this)
url = el.attr('data-href');
url = url != null ? url : el.attr('href');
if ( url.substring(0, 3) == 'uds' ) {
uds.launch(el);
} else {
window.location = url;
}
return false;
}).on("mouseenter mouseleave", function (event) {
$(this).toggleClass('over');
});
$('.uds-service-transport').on("click", function (event){
var url, el, modal;
event.preventDefault();
var modal = $(this).parent().parent().parent().parent().parent().parent();
modal = $(this).parent().parent().parent().parent().parent().parent();
modal.modal('hide');
uds.launch($(this));
// check url
el = $(this)
url = el.attr('data-href');
url = url != null ? url : el.attr('href');
if ( url.substring(0, 3) == 'uds' ) {
uds.launch(el);
} else {
window.location = url;
}
return false;
})

View File

@ -33,16 +33,17 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext_noop as _, ugettext
from django.utils.translation import ugettext_noop as _
from django.core.urlresolvers import reverse
from django.http import HttpResponseRedirect
from uds.core.ui.UserInterface import gui
from uds.core.util.Cache import Cache
from uds.core.util import net
from uds.core.transports.BaseTransport import Transport
from uds.core.transports import protocols
from uds.core.util import connection
from uds.core.util import OsDetector
from uds.models import TicketStore
import uuid
import logging
logger = logging.getLogger(__name__)
@ -59,7 +60,8 @@ class HTML5RDPTransport(Transport):
typeType = 'HTML5RDPTransport'
typeDescription = _('RDP Transport using HTML5 client')
iconFile = 'rdp.png'
needsJava = False # If this transport needs java for rendering
ownLink = True
supportedOss = OsDetector.allOss
protocol = protocols.RDP
@ -131,7 +133,7 @@ class HTML5RDPTransport(Transport):
return {'protocol': self.protocol, 'username': username, 'password': password, 'domain': domain}
def renderForHtml(self, userService, transport, ip, os, user, password):
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']
@ -155,18 +157,6 @@ class HTML5RDPTransport(Transport):
logger.debug('RDP Params: {0}'.format(params))
cache = Cache('guacamole')
key = uuid.uuid4().hex
cache.put(key, params)
url = "{0}/transport/?{1}".format(self.guacamoleServer.value, key)
return '''
<script type="text/javascript">
$(document).ready(function() {{
var url = "{0}&" + window.location.protocol + "//" + window.location.host + "/";
window.location = url;
}})
</script>
<div>{1}...</div>
'''.format(url, ugettext('Launching HTML5 RDP connection'))
ticket = TicketStore.create(params)
return HttpResponseRedirect("{}/transport/?{}&{}".format(self.guacamoleServer.value, ticket, reverse('Index')))

View File

@ -35,14 +35,13 @@ js_info_dict = {
'packages': ('uds',),
}
from django.conf.urls import patterns, include, url
from uds.core.util.modfinder import loadModulesUrls
from uds import REST
urlpatterns = patterns(
'uds',
(r'^$', 'web.views.index'),
url(r'^$', 'web.views.index', name='Index'),
(r'^login/$', 'web.views.login'),
(r'^login/(?P<smallName>.+)$', 'web.views.login'),
(r'^logout$', 'web.views.logout'),
@ -54,7 +53,9 @@ urlpatterns = patterns(
# Error URL
(r'^error/(?P<idError>.+)$', 'web.views.error'),
# Transport component url
(r'^transcomp/(?P<idTransport>.+)/(?P<componentId>.+)$', 'web.views.transcomp'),
url(r'^transcomp/(?P<idTransport>.+)/(?P<componentId>.+)$', 'web.views.transcomp', name='TransportComponent'),
# Transport own link processor
url(r'^trans/(?P<idService>.+)/(?P<idTransport>.+)$', 'web.views.trans', name='TransportOwnLink'),
# Service notification url
(r'^sernotify/(?P<idUserService>.+)/(?P<notification>.+)$', 'web.views.sernotify'),
# Authenticators custom html
@ -77,7 +78,6 @@ urlpatterns = patterns(
# Ticket authentication
url(r'^tkauth/(?P<ticketId>.+)$', 'web.views.ticketAuth', name='TicketAuth'),
# XMLRPC Processor
(r'^xmlrpc$', 'xmlrpc.views.xmlrpc'),

View File

@ -30,7 +30,7 @@
'''
from __future__ import unicode_literals
__updated__ = '2015-03-18'
__updated__ = '2015-03-23'
import logging
@ -39,7 +39,7 @@ logger = logging.getLogger(__name__)
from .login import login, logout, customAuth
from .index import index, about
from .prefs import prefs
from .service import service, transcomp, sernotify, transportIcon, serviceImage
from .service import service, trans, transcomp, sernotify, transportIcon, serviceImage
from .auth import authCallback, authInfo, authJava, ticketAuth
from .download import download
from .client_download import client_downloads

View File

@ -35,6 +35,7 @@ __updated__ = '2015-03-23'
from django.shortcuts import render_to_response
from django.shortcuts import render
from django.shortcuts import redirect
from django.core.urlresolvers import reverse
from django.template import RequestContext
from uds.core.auths.auth import webLoginRequired, webPassword
@ -42,6 +43,7 @@ from uds.core.auths.auth import webLoginRequired, webPassword
from uds.models import DeployedService, Transport, UserService, Network, TicketStore
from uds.core.util.Config import GlobalConfig
from uds.core.util import OsDetector
from uds.core.util import html
from uds.core.ui import theme
from uds.core.managers.UserServiceManager import UserServiceManager
@ -101,17 +103,6 @@ def index(request):
services = []
# Select assigned user services
for svr in availUserServices:
# Skip maintenance services...
trans = []
for t in svr.transports.all().order_by('priority'):
typeTrans = t.getType()
if t.validForIp(request.ip) and typeTrans.supportsOs(os['OS']):
trans.append({'id': t.uuid, 'name': t.name, 'needsJava': t.getType().needsJava})
if svr.deployed_service.image is not None:
imageId = svr.deployed_service.image.uuid
else:
imageId = 'x' # Invalid
# Generate ticket
data = {
'type': 'A',
@ -121,7 +112,26 @@ def index(request):
}
ticket = TicketStore.create(data)
link = '{}://{}/{}'.format(proto, ticket, scrambler)
trans = []
for t in svr.transports.all().order_by('priority'):
typeTrans = t.getType()
if t.validForIp(request.ip) and typeTrans.supportsOs(os['OS']):
if typeTrans.ownLink is True:
link = reverse('TransportOwnLink', args=('A' + svr.uuid, trans.uuid))
else:
link = html.udsLink(request, ticket, scrambler, t)
trans.append(
{
'id': t.uuid,
'name': t.name,
'link': link
}
)
if svr.deployed_service.image is not None:
imageId = svr.deployed_service.image.uuid
else:
imageId = 'x' # Invalid
services.append({
'id': 'A' + svr.uuid,
@ -131,19 +141,37 @@ def index(request):
'show_transports': svr.deployed_service.show_transports,
'maintenance': svr.deployed_service.service.provider.maintenance_mode,
'in_use': svr.in_use,
'ticket': ticket,
})
logger.debug(services)
# Now generic user service
for svr in availServices:
# Generate ticket
data = {
'type': 'F',
'service': svr.uuid,
'user': request.user.uuid,
'password': password
}
ticket = TicketStore.create(data)
trans = []
for t in svr.transports.all().order_by('priority'):
if t.validForIp(request.ip):
typeTrans = t.getType()
if typeTrans.supportsOs(os['OS']):
trans.append({'id': t.uuid, 'name': t.name, 'needsJava': typeTrans.needsJava})
typeTrans = t.getType()
if t.validForIp(request.ip) and typeTrans.supportsOs(os['OS']):
if typeTrans.ownLink is True:
link = reverse('TransportOwnLink', args=('F' + svr.uuid, t.uuid))
else:
link = html.udsLink(request, ticket, scrambler, t)
trans.append(
{
'id': t.uuid,
'name': t.name,
'link': link
}
)
if svr.image is not None:
imageId = svr.image.uuid
else:
@ -156,16 +184,6 @@ def index(request):
else:
in_use = ads.in_use
# Generate tickets for every transport
data = {
'type': 'F',
'service': svr.uuid,
'user': request.user.uuid,
'password': password
}
ticket = TicketStore.create(data)
services.append({
'id': 'F' + svr.uuid,
'name': svr.name,
@ -174,7 +192,6 @@ def index(request):
'show_transports': svr.show_transports,
'maintenance': svr.service.provider.maintenance_mode,
'in_use': in_use,
'ticket': ticket,
})
logger.debug('Services: {0}'.format(services))
@ -184,6 +201,7 @@ def index(request):
if len(services) == 1 and GlobalConfig.AUTORUN_SERVICE.get(True) == '1' and len(services[0]['transports']) > 0:
if request.session.get('autorunDone', '0') == '0':
request.session['autorunDone'] = '1'
# TODO: Make this to redirect to uds link directly
return redirect('uds.web.views.service', idService=services[0]['id'], idTransport=services[0]['transports'][0]['id'])
response = render_to_response(

View File

@ -30,7 +30,7 @@
'''
from __future__ import unicode_literals
__updated__ = '2015-03-16'
__updated__ = '2015-03-23'
from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import render_to_response
@ -106,6 +106,55 @@ def service(request, idService, idTransport):
return errors.exceptionView(request, e)
@webLoginRequired(admin=False)
def trans(request, idService, idTransport):
kind, idService = idService[0], idService[1:]
try:
logger.debug('Kind of service: {0}, idService: {1}'.format(kind, idService))
if kind == 'A': # This is an assigned service
ads = UserService.objects.get(uuid=idService)
else:
ds = DeployedService.objects.get(uuid=idService)
# We first do a sanity check for this, if the user has access to this service
# If it fails, will raise an exception
ds.validateUser(request.user)
# Now we have to locate an instance of the service, so we can assign it to user.
ads = UserServiceManager.manager().getAssignationForUser(ds, request.user)
if ads.isInMaintenance() is True:
raise ServiceInMaintenanceMode()
logger.debug('Found service: {0}'.format(ads))
trans = Transport.objects.get(uuid=idTransport)
# Test if the service is ready
if ads.isReady():
log.doLog(ads, log.INFO, "User {0} from {1} has initiated access".format(request.user.name, request.ip), log.WEB)
# If ready, show transport for this service, if also ready ofc
iads = ads.getInstance()
ip = iads.getIp()
events.addEvent(ads.deployed_service, events.ET_ACCESS, username=request.user.name, srcip=request.ip, dstip=ip, uniqueid=ads.unique_id)
if ip is not None:
itrans = trans.getInstance()
if itrans.isAvailableFor(ip):
ads.setConnectionSource(request.ip, 'unknown')
log.doLog(ads, log.INFO, "User service ready, rendering transport", log.WEB)
UserServiceManager.manager().notifyPreconnect(ads, itrans.processedUser(ads, request.user), itrans.protocol)
return itrans.getLink(ads, trans, ip, request.os, request.user, webPassword(request), request)
else:
log.doLog(ads, log.WARN, "User service is not accessible (ip {0})".format(ip), log.TRANSPORT)
logger.debug('Transport is not ready for user service {0}'.format(ads))
else:
logger.debug('Ip not available from user service {0}'.format(ads))
else:
log.doLog(ads, log.WARN, "User {0} from {1} tried to access, but machine was not ready".format(request.user.name, request.ip), log.WEB)
# Not ready, show message and return to this page in a while
return render_to_response(theme.template('service_not_ready.html'), context_instance=RequestContext(request))
except Exception, e:
logger.exception("Exception")
return errors.exceptionView(request, e)
@webLoginRequired(admin=False)
def transcomp(request, idTransport, componentId):
try: