mirror of
https://github.com/dkmstr/openuds.git
synced 2025-10-07 15:33:51 +03:00
Compare commits
246 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
15a76f3b9b | ||
|
a3110d4623 | ||
|
92c3fbd827 | ||
|
ea49e18f80 | ||
|
393819bc94 | ||
|
7ab956fa4a | ||
|
17044d38bd | ||
|
0dc461c30b | ||
|
ced5e06ff0 | ||
|
5e848cbc8c | ||
|
f924604217 | ||
|
efc59c0124 | ||
|
2eaa447155 | ||
|
3528424892 | ||
|
2eef7222d5 | ||
|
0d28a5b5f7 | ||
|
e0e7e26234 | ||
|
2674563535 | ||
|
a864fbac96 | ||
|
51c32a4350 | ||
|
ec95f27ee6 | ||
|
15ce5aa938 | ||
|
39b353fb24 | ||
|
7a0c8aa977 | ||
|
664418d4dc | ||
|
381cf47abf | ||
|
1578c92a88 | ||
|
6827380e99 | ||
|
62f637790c | ||
|
bf5311b5af | ||
|
d0e0418ede | ||
|
243cbbfd4b | ||
|
e7984d5a8e | ||
|
eacce7adda | ||
|
622e8629ff | ||
|
50e368ee7a | ||
|
6766fe41fe | ||
|
ba35968e8d | ||
|
4adfac1c02 | ||
|
437865b278 | ||
|
793b6c9004 | ||
|
96bd117622 | ||
|
8b2df76582 | ||
|
4196883bfb | ||
|
8585d10623 | ||
|
c715b92ed0 | ||
|
a3726399f1 | ||
|
8ce3efc7ee | ||
|
0bd3e1ac72 | ||
|
c2d4e995e7 | ||
|
1b71fef8b4 | ||
|
074a4d525d | ||
|
36e19574e0 | ||
|
ef9165b3a2 | ||
|
14cddbb210 | ||
|
2d14884454 | ||
|
ba4eeffc77 | ||
|
59179b818e | ||
|
dbcdc84b3b | ||
|
c73dae361f | ||
|
ec29371b41 | ||
|
9365f5937b | ||
|
971015d33a | ||
|
7a742c043b | ||
|
c08a0cb0ed | ||
|
b5926dec6f | ||
|
2fdcdb014f | ||
|
bd70a6290e | ||
|
3afa96f1c5 | ||
|
5da12a8091 | ||
|
08eeff5604 | ||
|
169a946a03 | ||
|
94842ce0ef | ||
|
0fb7d5ed1b | ||
|
2021fd69ec | ||
|
813764a100 | ||
|
cbd9330907 | ||
|
a250cf4aef | ||
|
50bc3cd3ef | ||
|
4fb863cfa7 | ||
|
3a1bd1eed3 | ||
|
bce3e429cf | ||
|
b5baea184f | ||
|
42cbad4117 | ||
|
09f329db62 | ||
|
6b5f9d266d | ||
|
242d9b5e6e | ||
|
99b17e573c | ||
|
701edb91f1 | ||
|
f0627db09f | ||
|
56a579e11b | ||
|
4427448eca | ||
|
203e2fcdd0 | ||
|
f7fa92e6c1 | ||
|
425257a464 | ||
|
0acc07ebb3 | ||
|
030078a619 | ||
|
0e6ca4c188 | ||
|
c7d5a1c928 | ||
|
e636a4afcd | ||
|
8b76324ffc | ||
|
8fe1e55770 | ||
|
caae694628 | ||
|
268e9d551a | ||
|
07137c2416 | ||
|
273b2a59c4 | ||
|
63364f4e72 | ||
|
abc9622d53 | ||
|
9cd7e2f67b | ||
|
9277d3b5fb | ||
|
6f46e16be8 | ||
|
0b390e406a | ||
|
f301e4654a | ||
|
62481899a1 | ||
|
869dfc8c06 | ||
|
939d456b9d | ||
|
9c70fb3caf | ||
|
baf4a677dd | ||
|
4c4820f166 | ||
|
2749bfc40c | ||
|
704e0607eb | ||
|
401fbac63e | ||
|
7f1252a70a | ||
|
2df103a348 | ||
|
489bb44c92 | ||
|
c606b6f00e | ||
|
0b4b38abe7 | ||
|
40b71fa983 | ||
|
6116db5147 | ||
|
e979c6e1e2 | ||
|
8f9d042cdd | ||
|
fcf030e693 | ||
|
31e6e01cad | ||
|
1aad0c85a4 | ||
|
e6e16334b3 | ||
|
8a285fddfa | ||
|
c9a690fe8c | ||
|
b5387d4922 | ||
|
b0a6807ea4 | ||
|
2e96074961 | ||
|
135392d245 | ||
|
ff622bb9cd | ||
|
608c1317d7 | ||
|
9f4ef20dc1 | ||
|
c45833c252 | ||
|
e260fc9790 | ||
|
cc6ba2ff41 | ||
|
bec74ddc99 | ||
|
7af7d11c8a | ||
|
8934d978fe | ||
|
8376e81532 | ||
|
6587d9ba2c | ||
|
2654036fd2 | ||
|
f9226e7deb | ||
|
56ac8aece9 | ||
|
12737df530 | ||
|
7b9c835562 | ||
|
cbb809db77 | ||
|
4f9085f0a2 | ||
|
389cf62150 | ||
|
44c367bf8f | ||
|
efb0083161 | ||
|
16c1aba3e7 | ||
|
07ed8b9762 | ||
|
fbd0a59a70 | ||
|
a08fe53383 | ||
|
e1b8c43cca | ||
|
60f20e64f3 | ||
|
6bb1109fe1 | ||
|
134f2059dd | ||
|
ec93e5c9cc | ||
|
fd6b0c9458 | ||
|
de4aef3a5c | ||
|
d024d74529 | ||
|
335fd338bd | ||
|
d7ac59f257 | ||
|
ca54c2c099 | ||
|
491a33421d | ||
|
f1cbcf86e1 | ||
|
e2e0f96d3e | ||
|
4be5d8d6e5 | ||
|
b9929566f6 | ||
|
f6492256a8 | ||
|
66d2c63a20 | ||
|
b6f582d84e | ||
|
d94cc70eff | ||
|
163245401b | ||
|
0add4b4321 | ||
|
19fdcadbcd | ||
|
7b85adaddf | ||
|
d8a0a2f80a | ||
|
ae9a9534fc | ||
|
1c1003eb41 | ||
|
4bc6d88006 | ||
|
f08593881f | ||
|
30cf167e5b | ||
|
064d881c1e | ||
|
c3f9d673bd | ||
|
4695dcaa0c | ||
|
e23f22d92d | ||
|
962f1fcc1c | ||
|
6bc70ff4de | ||
|
aaf4b539d5 | ||
|
03d0fc2ba4 | ||
|
407b0ebf55 | ||
|
033ac0d1f6 | ||
|
c9e9f60ed1 | ||
|
162c84e21c | ||
|
9f6d126484 | ||
|
0877cc01f4 | ||
|
00e85357cf | ||
|
cc72b3742f | ||
|
dff20bd6e1 | ||
|
69b6f7d7d8 | ||
|
9881df5117 | ||
|
bc2f83c937 | ||
|
fa78b4b295 | ||
|
95230292c3 | ||
|
fdaf974009 | ||
|
26764c4cab | ||
|
52d0e8977c | ||
|
1d7df7814c | ||
|
7e9a37e768 | ||
|
80bf6d77f1 | ||
|
2d7a2d6049 | ||
|
8363b41a68 | ||
|
9804678a30 | ||
|
1187c0aa5e | ||
|
36adbe387c | ||
|
df2dcf7acd | ||
|
3fbb492921 | ||
|
1b97e2015d | ||
|
3814986e41 | ||
|
037a4b523b | ||
|
522d493557 | ||
|
2ac06f7345 | ||
|
0745a7aa9a | ||
|
7e419aed8d | ||
|
baa9565140 | ||
|
069dfb154a | ||
|
485ed4d60b | ||
|
4de3e83ade | ||
|
73713eac69 | ||
|
6ed924655d | ||
|
7706262702 | ||
|
e0843179cd |
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
VERSION=1.9.1
|
||||
VERSION=`cat ../../VERSION`
|
||||
RELEASE=1
|
||||
|
||||
top=`pwd`
|
||||
|
@@ -1,3 +1,9 @@
|
||||
udsactor (2.0.0) stable; urgency=medium
|
||||
|
||||
* Upgrade for 2.0.0
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 01 Mar 2016 03:39:21 +0100
|
||||
|
||||
udsactor (1.9.1) stable; urgency=medium
|
||||
|
||||
* Upgrade for 1.9.1
|
||||
|
@@ -1,3 +1,3 @@
|
||||
udsactor-nx_1.9.1_all.deb x11 optional
|
||||
udsactor-xrdp_1.9.1_all.deb x11 optional
|
||||
udsactor_1.9.1_all.deb admin optional
|
||||
udsactor-nx_2.0.0_all.deb x11 optional
|
||||
udsactor-xrdp_2.0.0_all.deb x11 optional
|
||||
udsactor_2.0.0_all.deb admin optional
|
||||
|
@@ -33,10 +33,11 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
from PyQt4 import QtGui
|
||||
from PyQt4 import QtCore
|
||||
from PyQt4 import QtGui # @UnresolvedImport
|
||||
from PyQt4 import QtCore # @UnresolvedImport
|
||||
import pickle
|
||||
import time
|
||||
import datetime
|
||||
import signal
|
||||
from udsactor import ipc
|
||||
from udsactor import utils
|
||||
@@ -175,10 +176,13 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
|
||||
exitAction.triggered.connect(self.about)
|
||||
self.setContextMenu(self.menu)
|
||||
self.ipc = MessagesProcessor()
|
||||
self.sessionStart = datetime.datetime.now()
|
||||
self.maxIdleTime = None
|
||||
self.timer = QtCore.QTimer()
|
||||
self.timer.timeout.connect(self.checkIdle)
|
||||
self.showIdleWarn = True
|
||||
self.maxSessionTime = None
|
||||
self.showMaxSessionWarn = True
|
||||
self.timer = QtCore.QTimer()
|
||||
self.timer.timeout.connect(self.checkTimers)
|
||||
|
||||
if self.ipc.isAlive() is False:
|
||||
raise Exception('No connection to service, exiting.')
|
||||
@@ -206,6 +210,27 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
|
||||
# If this is running, it's because he have logged in
|
||||
self.ipc.sendLogin(operations.getCurrentUser())
|
||||
|
||||
def checkTimers(self):
|
||||
self.checkIdle()
|
||||
self.checkMaxSession()
|
||||
|
||||
def checkMaxSession(self):
|
||||
if self.maxSessionTime is None or self.maxSessionTime == 0:
|
||||
logger.debug('Returning because maxSessionTime is cero')
|
||||
return
|
||||
|
||||
remainingTime = self.maxSessionTime - (datetime.datetime.now() - self.sessionStart).total_seconds()
|
||||
logger.debug('Remaining time: {}'.format(remainingTime))
|
||||
|
||||
if self.showMaxSessionWarn is True and remainingTime < 300: # With five minutes, show a warning message
|
||||
self.showMaxSessionWarn = False
|
||||
self.msgDlg.displayMessage('Your session will expire in less that 5 minutes. Please, save your work and disconnect.')
|
||||
return
|
||||
|
||||
if remainingTime <= 0:
|
||||
logger.debug('Remaining time is less than cero, exiting')
|
||||
self.quit()
|
||||
|
||||
def checkIdle(self):
|
||||
if self.maxIdleTime is None: # No idle check
|
||||
return
|
||||
@@ -217,19 +242,19 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
|
||||
idleTime = operations.getIdleDuration()
|
||||
remainingTime = self.maxIdleTime - idleTime
|
||||
|
||||
if remainingTime > 300: # Reset show Warning dialog if we have more than 5 minutes left
|
||||
if remainingTime > 120: # Reset show Warning dialog if we have more than 5 minutes left
|
||||
self.showIdleWarn = True
|
||||
|
||||
logger.debug('User has been idle for: {}'.format(idleTime))
|
||||
|
||||
if remainingTime <= 0:
|
||||
logger.info('User has been idle for too long, notifying Broker that service can be reclaimed')
|
||||
self.quit()
|
||||
|
||||
if self.showIdleWarn is True and remainingTime < 120: # With two minutes, show a warning message
|
||||
self.showIdleWarn = False
|
||||
self.msgDlg.displayMessage("You have been idle for too long. The session will end if you don't resume operations")
|
||||
|
||||
if remainingTime <= 0:
|
||||
logger.info('User has been idle for too long, notifying Broker that service can be reclaimed')
|
||||
self.quit()
|
||||
|
||||
def displayMessage(self, message):
|
||||
logger.debug('Displaying message')
|
||||
self.msgDlg.displayMessage(message)
|
||||
@@ -247,7 +272,7 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
|
||||
'''
|
||||
Invoked when received information from service
|
||||
'''
|
||||
logger.debug('Got information message: {}'.format(info))
|
||||
logger.info('Got information message: {}'.format(info))
|
||||
if 'idle' in info:
|
||||
idle = int(info['idle'])
|
||||
operations.initIdleDuration(idle)
|
||||
@@ -256,6 +281,12 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
|
||||
else:
|
||||
self.maxIdleTime = None
|
||||
|
||||
if 'maxSession' in info:
|
||||
maxSession = int(info['maxSession'])
|
||||
# operations.initMaxSession(maxSession)
|
||||
self.maxSessionTime = maxSession
|
||||
logger.debug('Set maxsession to {}'.format(maxSession))
|
||||
|
||||
def about(self):
|
||||
self.aboutDlg.exec_()
|
||||
|
||||
|
@@ -88,7 +88,7 @@
|
||||
<item>
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
<number>2</number>
|
||||
</property>
|
||||
<widget class="QWidget" name="aboutTab">
|
||||
<attribute name="title">
|
||||
@@ -109,7 +109,7 @@
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Verdana'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif';"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-weight:600;">(c) 2014, Virtual Cable S.L.U.</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'Sans Serif'; font-weight:600;">(c) 2012-2016, Virtual Cable S.L.U.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-style:italic;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.udsenterprise.com"><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt; text-decoration: underline; color:#0000ff;">http://www.udsenterprise.com</span></a></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a href="http://www.openuds.org"><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt; text-decoration: underline; color:#0000ff;">http://www.openuds.org</span></a></p>
|
||||
@@ -167,7 +167,7 @@ p, li { white-space: pre-wrap; }
|
||||
<html><head><meta name="qrichtext" content="1" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'Verdana'; font-size:9pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;">Copyright (c) 2014 Virtual Cable S.L.</span></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;">Copyright (c) 2012-2016 Virtual Cable S.L.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p>
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'MS Shell Dlg 2'; font-size:8pt;">All rights reserved.</span></p>
|
||||
<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'MS Shell Dlg 2'; font-size:8pt;"><br /></p>
|
||||
|
@@ -109,6 +109,7 @@ class Api(object):
|
||||
self.mac = None
|
||||
self.url = "{}://{}/rest/actor/".format(('http', 'https')[self.useSSL], self.host)
|
||||
self.idle = None
|
||||
self.maxSession = None
|
||||
self.secretKey = six.text_type(uuid.uuid4())
|
||||
try:
|
||||
self.newerRequestLib = requests.__version__.split('.')[0] >= '1'
|
||||
|
@@ -34,14 +34,14 @@ from __future__ import unicode_literals
|
||||
# On centos, old six release does not includes byte2int, nor six.PY2
|
||||
import six
|
||||
|
||||
VERSION = '1.9.0'
|
||||
VERSION = '2.0.0'
|
||||
|
||||
__title__ = 'udsactor'
|
||||
__version__ = VERSION
|
||||
__build__ = 0x010750
|
||||
__author__ = 'Adolfo Gómez'
|
||||
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
|
||||
__license__ = "BSD 3-clause"
|
||||
__copyright__ = "Copyright 2014-2015 VirtualCable S.L.U."
|
||||
__copyright__ = "Copyright 2014-2016 VirtualCable S.L.U."
|
||||
|
||||
|
||||
if not hasattr(six, 'byte2int'):
|
||||
|
@@ -172,7 +172,10 @@ class HTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
|
||||
def get_information(self, params):
|
||||
# TODO: Return something useful? :)
|
||||
return 'Information'
|
||||
return 'Up and running'
|
||||
|
||||
def get_uuid(self, params):
|
||||
return self.service.api.uuid
|
||||
|
||||
def log_error(self, fmt, *args):
|
||||
logger.error('HTTP ' + fmt % args)
|
||||
@@ -188,9 +191,13 @@ class HTTPServerThread(threading.Thread):
|
||||
if HTTPServerHandler.uuid is None:
|
||||
HTTPServerHandler.uuid = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(48))
|
||||
|
||||
self.certFile = createSelfSignedCert()
|
||||
HTTPServerHandler.service = service
|
||||
|
||||
self.certFile = createSelfSignedCert()
|
||||
self.initiateServer(address)
|
||||
|
||||
|
||||
def initiateServer(self, address):
|
||||
self.server = socketserver.TCPServer(address, HTTPServerHandler)
|
||||
self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.certFile, server_side=True)
|
||||
|
||||
@@ -201,5 +208,14 @@ class HTTPServerThread(threading.Thread):
|
||||
logger.debug('Stopping REST Service')
|
||||
self.server.shutdown()
|
||||
|
||||
def restart(self, address=None):
|
||||
|
||||
if address is None:
|
||||
address = self.server.server_address
|
||||
|
||||
self.stop()
|
||||
|
||||
self.initiateServer(address)
|
||||
|
||||
def run(self):
|
||||
self.server.serve_forever()
|
||||
|
@@ -57,7 +57,7 @@ def _getMacAddr(ifname):
|
||||
try:
|
||||
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
|
||||
info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifname[:15])))
|
||||
return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1])
|
||||
return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1]).upper()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@@ -194,8 +194,10 @@ try:
|
||||
# Fix result type to XScreenSaverInfo Structure
|
||||
xss.XScreenSaverQueryExtension.restype = ctypes.c_int
|
||||
xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(XScreenSaverInfo) # Result in a XScreenSaverInfo structure
|
||||
display = xlib.XOpenDisplay(None)
|
||||
info = xss.XScreenSaverAllocInfo()
|
||||
except Exception: # Libraries not accesible, not found or whatever..
|
||||
xlib = xss = None
|
||||
xlib = xss = display = info = None
|
||||
|
||||
|
||||
def initIdleDuration(atLeastSeconds):
|
||||
@@ -219,20 +221,18 @@ def getIdleDuration():
|
||||
if xlib is None or xss is None:
|
||||
return 0 # Libraries not available
|
||||
|
||||
# production code might want to not hardcode the offset 16...
|
||||
display = xlib.XOpenDisplay(None)
|
||||
|
||||
event_base = ctypes.c_int()
|
||||
error_base = ctypes.c_int()
|
||||
|
||||
available = xss.XScreenSaverQueryExtension(display, ctypes.byref(event_base), ctypes.byref(error_base))
|
||||
|
||||
if available != 1:
|
||||
return 0 # No screen saver is available, no way of getting idle
|
||||
|
||||
info = xss.XScreenSaverAllocInfo()
|
||||
xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), info)
|
||||
|
||||
if info.contents.state != 0:
|
||||
# Centos seems to set state to 1?? (weird, but it's happening don't know why... will try this way)
|
||||
if info.contents.state != 0 and 'centos' not in platform.linux_distribution()[0].lower().strip():
|
||||
return 3600 * 100 * 1000 # If screen saver is active, return a high enough value
|
||||
|
||||
return info.contents.idle / 1000.0
|
||||
|
@@ -205,8 +205,17 @@ class CommonService(object):
|
||||
try:
|
||||
# Notifies all interfaces IPs
|
||||
self.api.notifyIpChanges(((v.mac, v.ip) for v in netInfo))
|
||||
|
||||
# Regenerates Known ips
|
||||
self.knownIps = dict(((v.mac, v.ip) for v in netInfo))
|
||||
|
||||
# And notify new listening address to broker
|
||||
address = (self.knownIps[self.api.mac], random.randrange(43900, 44000))
|
||||
# And new listening address
|
||||
self.httpServer.restart(address)
|
||||
# sends notification
|
||||
self.api.notifyComm(self.httpServer.getServerUrl())
|
||||
|
||||
except Exception as e:
|
||||
logger.warn('Got an error notifiying IPs to broker: {} (will retry in a bit)'.format(e.message.decode('windows-1250', 'ignore')))
|
||||
|
||||
@@ -217,14 +226,21 @@ class CommonService(object):
|
||||
return
|
||||
|
||||
if msg == ipc.REQ_LOGIN:
|
||||
self.api.login(data)
|
||||
elif msg == ipc.REQ_LOGOUT:
|
||||
res = self.api.login(data).split('\t')
|
||||
# third parameter, if exists, sets maxSession duration to this.
|
||||
# First & second parameters are ip & hostname of connection source
|
||||
if len(res) >= 3:
|
||||
self.api.maxSession = int(res[2]) # Third parameter is max session duration
|
||||
msg = ipc.REQ_INFORMATION # Senf information, requested or not, to client on login notification
|
||||
if msg == ipc.REQ_LOGOUT:
|
||||
self.api.logout(data)
|
||||
self.onLogout(data)
|
||||
elif msg == ipc.REQ_INFORMATION:
|
||||
if msg == ipc.REQ_INFORMATION:
|
||||
info = {}
|
||||
if self.api.idle is not None:
|
||||
info['idle'] = self.api.idle
|
||||
if self.api.maxSession is not None:
|
||||
info['maxSession'] = self.api.maxSession
|
||||
self.ipc.sendInformationMessage(info)
|
||||
|
||||
def initIPC(self):
|
||||
@@ -236,7 +252,7 @@ class CommonService(object):
|
||||
self.ipc.start()
|
||||
|
||||
if self.api.mac in self.knownIps:
|
||||
address = (self.knownIps[self.api.mac], random.randrange(40000, 44000))
|
||||
address = (self.knownIps[self.api.mac], random.randrange(43900, 44000))
|
||||
logger.info('Starting REST listener at {}'.format(address))
|
||||
self.httpServer = httpserver.HTTPServerThread(address, self)
|
||||
self.httpServer.start()
|
||||
|
@@ -76,7 +76,7 @@ class SensLogon(win32com.server.policy.DesignatedWrapPolicy):
|
||||
data = self.service.api.login(args[0])
|
||||
logger.debug('Data received for login: {}'.format(data))
|
||||
data = data.split('\t')
|
||||
if len(data) == 2:
|
||||
if len(data) >= 2:
|
||||
logger.debug('Data is valid: {}'.format(data))
|
||||
windir = os.environ['windir']
|
||||
with open(os.path.join(windir, 'remoteip.txt'), 'w') as f:
|
||||
|
@@ -48,7 +48,7 @@ class LocalLogger(object):
|
||||
filename=os.path.join(tempfile.gettempdir(), 'udsactor.log'),
|
||||
filemode='a',
|
||||
format='%(levelname)s %(asctime)s %(message)s',
|
||||
level=logging.DEBUG
|
||||
level=logging.INFO
|
||||
)
|
||||
self.logger = logging.getLogger('udsactor')
|
||||
self.serviceLogger = False
|
||||
|
@@ -45,6 +45,7 @@ from udsactor.log import logger
|
||||
|
||||
|
||||
def getErrorMessage(res=0):
|
||||
# sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
|
||||
msg = win32api.FormatMessage(res)
|
||||
return msg.decode('windows-1250', 'ignore')
|
||||
|
||||
|
@@ -1,3 +1,9 @@
|
||||
udsclient (2.0.0) stable; urgency=medium
|
||||
|
||||
* Release upgrade
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 01 Mar 2016 09:33:18 +0100
|
||||
|
||||
udsclient (1.9.1) stable; urgency=medium
|
||||
|
||||
* Minor fixes & making version match UDS version
|
||||
|
@@ -10,6 +10,6 @@ Package: udsclient
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: python-paramiko (>=0.8.2), python-qt4 (>=4.9), python-six(>=1.1), python (>=2.7), rdesktop | freerdp-x11, ${misc:Depends}
|
||||
Depends: python-paramiko (>=0.8.2), python-qt4 (>=4.9), python-six(>=1.1), python (>=2.7), rdesktop | freerdp-x11, desktop-file-utils, ${misc:Depends}
|
||||
Description: Client connector for Universal Desktop Services (UDS) Broker
|
||||
This package provides the required components to allow this machine to connect to services provided by UDS Broker.
|
||||
|
@@ -1 +1 @@
|
||||
udsclient_1.9.1_all.deb admin optional
|
||||
udsclient_2.0.0_all.deb admin optional
|
||||
|
@@ -54,6 +54,9 @@ class UDSClient(QtGui.QMainWindow):
|
||||
ticket = None
|
||||
scrambler = None
|
||||
withError = False
|
||||
animTimer = None
|
||||
anim = 0
|
||||
animInverted = False
|
||||
|
||||
def __init__(self):
|
||||
QtGui.QMainWindow.__init__(self)
|
||||
@@ -73,8 +76,14 @@ class UDSClient(QtGui.QMainWindow):
|
||||
vpos = (screen.height() - mysize.height() - mysize.height()) / 2
|
||||
self.move(hpos, vpos)
|
||||
|
||||
self.animTimer = QtCore.QTimer()
|
||||
QtCore.QObject.connect(self.animTimer, QtCore.SIGNAL('timeout()'), self.updateAnim)
|
||||
|
||||
self.activateWindow()
|
||||
|
||||
self.startAnim()
|
||||
|
||||
|
||||
def closeWindow(self):
|
||||
self.close()
|
||||
|
||||
@@ -90,21 +99,36 @@ class UDSClient(QtGui.QMainWindow):
|
||||
# return
|
||||
|
||||
def showError(self, e):
|
||||
self.ui.progressBar.setValue(100)
|
||||
self.ui.info.setText('Error')
|
||||
QtGui.QMessageBox.critical(self, 'Error', six.text_type(e), QtGui.QMessageBox.Ok)
|
||||
logger.error('got error: {}'.format(e))
|
||||
self.stopAnim()
|
||||
self.ui.info.setText('UDS Plugin Error') # In fact, main window is hidden, so this is not visible... :)
|
||||
self.closeWindow()
|
||||
QtGui.QMessageBox.critical(None, 'UDS Plugin Error', '{}'.format(e), QtGui.QMessageBox.Ok)
|
||||
self.withError = True
|
||||
|
||||
def cancelPushed(self):
|
||||
self.close()
|
||||
|
||||
def _updateProgressBar(self, increment, maximum=100):
|
||||
val = self.ui.progressBar.value()
|
||||
val += increment
|
||||
if val > maximum:
|
||||
val = maximum
|
||||
self.ui.progressBar.setValue(val)
|
||||
@QtCore.pyqtSlot()
|
||||
def updateAnim(self):
|
||||
self.anim += 2
|
||||
if self.anim > 99:
|
||||
self.animInverted = not self.animInverted
|
||||
self.ui.progressBar.setInvertedAppearance(self.animInverted)
|
||||
self.anim = 0
|
||||
|
||||
self.ui.progressBar.setValue(self.anim)
|
||||
|
||||
def startAnim(self):
|
||||
self.ui.progressBar.invertedAppearance = False
|
||||
self.anim = 0
|
||||
self.animInverted = False
|
||||
self.ui.progressBar.setInvertedAppearance(self.animInverted)
|
||||
self.animTimer.start(40)
|
||||
|
||||
def stopAnim(self):
|
||||
self.ui.progressBar.invertedAppearance = False
|
||||
self.animTimer.stop()
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def getVersion(self):
|
||||
@@ -114,10 +138,7 @@ class UDSClient(QtGui.QMainWindow):
|
||||
@QtCore.pyqtSlot(dict)
|
||||
def version(self, data):
|
||||
try:
|
||||
self.ui.progressBar.setValue(20)
|
||||
|
||||
self.processError(data)
|
||||
|
||||
self.ui.info.setText('Processing...')
|
||||
|
||||
if data['result']['requiredVersion'] > VERSION:
|
||||
@@ -128,17 +149,21 @@ class UDSClient(QtGui.QMainWindow):
|
||||
self.getTransportData()
|
||||
|
||||
except RetryException as e:
|
||||
self._updateProgressBar(5, 80)
|
||||
self.ui.info.setText(six.text_type(e))
|
||||
QtCore.QTimer.singleShot(1000, self.getVersion)
|
||||
|
||||
except Exception as e:
|
||||
self.showError(e)
|
||||
|
||||
|
||||
@QtCore.pyqtSlot()
|
||||
def getTransportData(self):
|
||||
self.req = RestRequest('/{}/{}'.format(self.ticket, self.scrambler), self, self.transportDataReceived, params={'hostname': tools.getHostName(), 'version': VERSION})
|
||||
self.req.get()
|
||||
try:
|
||||
self.req = RestRequest('/{}/{}'.format(self.ticket, self.scrambler), self, self.transportDataReceived, params={'hostname': tools.getHostName(), 'version': VERSION})
|
||||
self.req.get()
|
||||
except Exception as e:
|
||||
logger.exception('Got exception: {}'.format(e))
|
||||
raise e
|
||||
|
||||
|
||||
@QtCore.pyqtSlot(dict)
|
||||
@@ -147,11 +172,10 @@ class UDSClient(QtGui.QMainWindow):
|
||||
try:
|
||||
self.processError(data)
|
||||
|
||||
self.ui.progressBar.setValue(80)
|
||||
|
||||
script = data['result'].decode('base64').decode('bz2')
|
||||
|
||||
self.ui.progressBar.setValue(100)
|
||||
self.stopAnim()
|
||||
|
||||
if 'darwin' in sys.platform:
|
||||
self.showMinimized()
|
||||
|
||||
@@ -161,7 +185,6 @@ class UDSClient(QtGui.QMainWindow):
|
||||
six.exec_(script, globals(), {'parent': self})
|
||||
|
||||
except RetryException as e:
|
||||
self._updateProgressBar(5, 80)
|
||||
self.ui.info.setText(six.text_type(e) + ', retrying access...')
|
||||
# Retry operation in ten seconds
|
||||
QtCore.QTimer.singleShot(10000, self.getTransportData)
|
||||
@@ -201,6 +224,23 @@ def done(data):
|
||||
QtGui.QMessageBox.critical(None, 'Notice', six.text_type(data.data), QtGui.QMessageBox.Ok)
|
||||
sys.exit(0)
|
||||
|
||||
# Ask user to aprobe endpoint
|
||||
def approveHost(host, parentWindow=None):
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup('endpoints')
|
||||
|
||||
approved = settings.value(host, False).toBool()
|
||||
|
||||
errorString = '<p>The server <b>{}</b> must be approved:</p>'.format(host)
|
||||
errorString += '<p>Only approve UDS servers that you trust to avoid security issues.</p>'
|
||||
|
||||
if approved or QtGui.QMessageBox.warning(parentWindow, 'ACCESS Warning', errorString, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes:
|
||||
settings.setValue(host, True)
|
||||
approved = True
|
||||
|
||||
settings.endGroup()
|
||||
return approved
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.debug('Initializing connector')
|
||||
# Initialize app
|
||||
@@ -222,6 +262,10 @@ if __name__ == "__main__":
|
||||
# First parameter must be url
|
||||
try:
|
||||
uri = sys.argv[1]
|
||||
|
||||
if uri == '--test':
|
||||
sys.exit(0)
|
||||
|
||||
logger.debug('URI: {}'.format(uri))
|
||||
if uri[:6] != 'uds://' and uri[:7] != 'udss://':
|
||||
raise Exception()
|
||||
@@ -232,7 +276,7 @@ if __name__ == "__main__":
|
||||
|
||||
except Exception:
|
||||
logger.debug('Detected execution without valid URI, exiting')
|
||||
QtGui.QMessageBox.critical(None, 'Notice', 'This program is designed to be used by UDS', QtGui.QMessageBox.Ok)
|
||||
QtGui.QMessageBox.critical(None, 'Notice', 'UDS Client Version {}'.format(VERSION), QtGui.QMessageBox.Ok)
|
||||
sys.exit(1)
|
||||
|
||||
# Setup REST api endpoint
|
||||
@@ -242,8 +286,15 @@ if __name__ == "__main__":
|
||||
|
||||
try:
|
||||
logger.debug('Starting execution')
|
||||
|
||||
# Approbe before going on
|
||||
if approveHost(host) is False:
|
||||
raise Exception('Host {} was not approved'.format(host))
|
||||
|
||||
win = UDSClient()
|
||||
win.show()
|
||||
|
||||
|
||||
win.start()
|
||||
|
||||
exitVal = app.exec_()
|
||||
@@ -257,12 +308,3 @@ if __name__ == "__main__":
|
||||
logger.debug('Exiting')
|
||||
sys.exit(exitVal)
|
||||
|
||||
# Build base REST
|
||||
|
||||
# v = RestRequest('', done)
|
||||
# v.get()
|
||||
|
||||
# sys.exit(1)
|
||||
|
||||
# myapp = UDSConfigDialog(cfg)
|
||||
# myapp.show()
|
||||
|
@@ -34,7 +34,7 @@ from __future__ import unicode_literals
|
||||
# On centos, old six release does not includes byte2int, nor six.PY2
|
||||
import six
|
||||
|
||||
VERSION = '1.9.0'
|
||||
VERSION = '2.0.0'
|
||||
|
||||
__title__ = 'udclient'
|
||||
__version__ = VERSION
|
||||
|
@@ -14,15 +14,15 @@ import time
|
||||
|
||||
from .log import logger
|
||||
|
||||
class ForwardServer (SocketServer.ThreadingTCPServer):
|
||||
class ForwardServer(SocketServer.ThreadingTCPServer):
|
||||
daemon_threads = True
|
||||
allow_reuse_address = True
|
||||
|
||||
|
||||
class Handler (SocketServer.BaseRequestHandler):
|
||||
class Handler(SocketServer.BaseRequestHandler):
|
||||
|
||||
def handle(self):
|
||||
self.thread.isConnected = True
|
||||
self.thread.currentConnections += 1
|
||||
|
||||
try:
|
||||
chan = self.ssh_transport.open_channel('direct-tcpip',
|
||||
@@ -65,10 +65,11 @@ class Handler (SocketServer.BaseRequestHandler):
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
if self.thread.stoppable is True:
|
||||
self.thread.currentConnections -= 1
|
||||
|
||||
if self.thread.stoppable is True and self.thread.currentConnections == 0:
|
||||
self.thread.stop()
|
||||
|
||||
self.thread.isConnected = False
|
||||
|
||||
class ForwardThread(threading.Thread):
|
||||
status = 0 # Connecting
|
||||
@@ -92,31 +93,49 @@ class ForwardThread(threading.Thread):
|
||||
self.stopEvent = threading.Event()
|
||||
|
||||
self.timer = None
|
||||
self.isConnected = False
|
||||
self.currentConnections = 0
|
||||
self.stoppable = False
|
||||
self.client = None
|
||||
|
||||
def clone(self, redirectHost, redirectPort, localPort=None):
|
||||
if localPort is None:
|
||||
localPort = random.randrange(40000, 50000)
|
||||
|
||||
ft = ForwardThread(self.server, self.port, self.username, self.password, localPort, redirectHost, redirectPort, self.waitTime)
|
||||
ft.client = self.client
|
||||
self.client.useCount += 1 # One more using this client
|
||||
ft.start()
|
||||
|
||||
while ft.status == 0:
|
||||
time.sleep(0.1)
|
||||
|
||||
return (ft, localPort)
|
||||
|
||||
|
||||
def _timerFnc(self):
|
||||
self.timer = None
|
||||
logger.debug('Timer fnc: {}'.format(self.isConnected))
|
||||
logger.debug('Timer fnc: {}'.format(self.currentConnections))
|
||||
self.stoppable = True
|
||||
if self.isConnected is False:
|
||||
if self.currentConnections <= 0:
|
||||
self.stop()
|
||||
|
||||
def run(self):
|
||||
self.client = paramiko.SSHClient()
|
||||
self.client.load_system_host_keys()
|
||||
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
if self.client is None:
|
||||
self.client = paramiko.SSHClient()
|
||||
self.client.useCount = 1 # Custom added variable, to keep track on when to close tunnel
|
||||
self.client.load_system_host_keys()
|
||||
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
|
||||
|
||||
logger.debug('Connecting to ssh host %s:%d ...' % (self.server, self.port))
|
||||
logger.debug('Connecting to ssh host %s:%d ...' % (self.server, self.port))
|
||||
|
||||
try:
|
||||
self.client.connect(self.server, self.port, username=self.username, password=self.password, timeout=5)
|
||||
except Exception as e:
|
||||
logger.exception('Exception connecting: ')
|
||||
self.status = 2 # Error
|
||||
return
|
||||
try:
|
||||
self.client.connect(self.server, self.port, username=self.username, password=self.password, timeout=5)
|
||||
except Exception as e:
|
||||
logger.exception('Exception connecting: ')
|
||||
self.status = 2 # Error
|
||||
return
|
||||
|
||||
class SubHandler (Handler):
|
||||
class SubHandler(Handler):
|
||||
chain_host = self.redirectHost
|
||||
chain_port = self.redirectPort
|
||||
ssh_transport = self.client.get_transport()
|
||||
@@ -141,7 +160,10 @@ class ForwardThread(threading.Thread):
|
||||
self.fs.shutdown()
|
||||
|
||||
if self.client is not None:
|
||||
self.client.close()
|
||||
self.client.useCount -= 1
|
||||
if self.client.useCount == 0:
|
||||
self.client.close()
|
||||
self.client = None # Clean up
|
||||
except Exception:
|
||||
logger.exception('Exception stopping')
|
||||
pass
|
||||
|
@@ -39,7 +39,7 @@ logging.basicConfig(
|
||||
filename=os.path.join(tempfile.gettempdir(), b'udsclient.log'),
|
||||
filemode='a',
|
||||
format='%(levelname)s %(asctime)s %(message)s',
|
||||
level=logging.INFO
|
||||
level=logging.DEBUG
|
||||
)
|
||||
|
||||
logger = logging.getLogger('udsclient')
|
||||
|
@@ -56,7 +56,7 @@ class RestRequest(QObject):
|
||||
# private
|
||||
self._manager = QNetworkAccessManager()
|
||||
if params is not None:
|
||||
url += '?' + '&'.join('{}={}'.format(k, urllib.quote(six.text_type(v))) for k, v in params.iteritems())
|
||||
url += '?' + '&'.join('{}={}'.format(k, urllib.quote(six.text_type(v).encode('utf8'))) for k, v in params.iteritems())
|
||||
|
||||
self.url = QUrl(RestRequest.restApiUrl + url)
|
||||
|
||||
@@ -91,6 +91,7 @@ class RestRequest(QObject):
|
||||
@pyqtSlot(QNetworkReply, list)
|
||||
def _sslError(self, reply, errors):
|
||||
settings = QSettings()
|
||||
settings.beginGroup('ssl')
|
||||
cert = errors[0].certificate()
|
||||
digest = six.text_type(cert.digest().toHex())
|
||||
|
||||
@@ -107,6 +108,8 @@ class RestRequest(QObject):
|
||||
settings.setValue(digest, True)
|
||||
reply.ignoreSslErrors()
|
||||
|
||||
settings.endGroup()
|
||||
|
||||
def get(self):
|
||||
request = QNetworkRequest(self.url)
|
||||
request.setRawHeader('User-Agent', osDetector.getOs() + " - UDS Connector " + VERSION)
|
||||
|
@@ -40,28 +40,57 @@ import socket
|
||||
import stat
|
||||
import six
|
||||
import sys
|
||||
import time
|
||||
|
||||
from log import logger
|
||||
|
||||
_unlinkFiles = []
|
||||
_tasksToWait = []
|
||||
_execBeforeExit = []
|
||||
|
||||
|
||||
sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
|
||||
|
||||
def saveTempFile(content, filename=None):
|
||||
if filename is None:
|
||||
filename = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(16))
|
||||
filename = b''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(16))
|
||||
filename = filename + '.uds'
|
||||
|
||||
if 'win32' in sys.platform:
|
||||
filename = filename.encode('utf-8')
|
||||
logger.info('Fixing for win32')
|
||||
filename = filename.encode(sys_fs_enc)
|
||||
|
||||
filename = os.path.join(tempfile.gettempdir(), filename)
|
||||
|
||||
with open(filename, 'w') as f:
|
||||
f.write(content)
|
||||
|
||||
logger.info('Returning filename')
|
||||
return filename
|
||||
|
||||
def readTempFile(filename):
|
||||
if 'win32' in sys.platform:
|
||||
filename = filename.encode('utf-8')
|
||||
|
||||
filename = os.path.join(tempfile.gettempdir(), filename)
|
||||
try:
|
||||
with open(filename, 'r') as f:
|
||||
return f.read()
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
def testServer(host, port, timeOut=4):
|
||||
try:
|
||||
sock = socket.create_connection((host, int(port)), timeOut)
|
||||
sock.close()
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def findApp(appName, extraPath=None):
|
||||
if 'win32' in sys.platform and isinstance(appName, six.text_type):
|
||||
appName = six.binary_type(appName)
|
||||
appName = appName.encode(sys_fs_enc)
|
||||
searchPath = os.environ['PATH'].split(os.pathsep)
|
||||
if extraPath is not None:
|
||||
searchPath += list(extraPath)
|
||||
@@ -78,7 +107,13 @@ def getHostName():
|
||||
Returns current host name
|
||||
In fact, it's a wrapper for socket.gethostname()
|
||||
'''
|
||||
return six.text_type(socket.gethostname())
|
||||
hostname = socket.gethostname()
|
||||
if 'win32' in sys.platform:
|
||||
hostname = hostname.decode(sys_fs_enc)
|
||||
|
||||
hostname = six.text_type(hostname)
|
||||
logger.info('Hostname: {}'.format(hostname))
|
||||
return hostname
|
||||
|
||||
# Queing operations (to be executed before exit)
|
||||
|
||||
@@ -94,6 +129,8 @@ def unlinkFiles():
|
||||
'''
|
||||
Removes all wait-and-unlink files
|
||||
'''
|
||||
if len(_unlinkFiles) > 0:
|
||||
time.sleep(5) # Wait 5 seconds before deleting anything
|
||||
for f in _unlinkFiles:
|
||||
try:
|
||||
os.unlink(f)
|
||||
@@ -107,7 +144,13 @@ def addTaskToWait(taks):
|
||||
|
||||
def waitForTasks():
|
||||
for t in _tasksToWait:
|
||||
t.wait()
|
||||
try:
|
||||
if hasattr(t, 'join'):
|
||||
t.join()
|
||||
elif hasattr(t, 'wait'):
|
||||
t.wait()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
def addExecBeforeExit(fnc):
|
||||
|
5
guacamole-tunnel/NOTICE
Normal file
5
guacamole-tunnel/NOTICE
Normal file
@@ -0,0 +1,5 @@
|
||||
Apache Guacamole
|
||||
Copyright 2016 The Apache Software Foundation
|
||||
|
||||
This product includes software developed at
|
||||
The Apache Software Foundation (http://www.apache.org/).
|
@@ -7,36 +7,43 @@
|
||||
<groupId>org.openuds.server</groupId>
|
||||
<artifactId>transport</artifactId>
|
||||
<packaging>war</packaging>
|
||||
<version>1.9.0</version>
|
||||
<version>2.0.0</version>
|
||||
<name>Guacamole Transport</name>
|
||||
<url>http://openuds.org/</url>
|
||||
<url>https://github.com/dkmstr/openuds</url>
|
||||
|
||||
<properties>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
</properties>
|
||||
|
||||
<build>
|
||||
<finalName>transport</finalName>
|
||||
<build>
|
||||
<finalName>transport</finalName>
|
||||
<plugins>
|
||||
|
||||
<!-- Compile using Java 1.6 -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.3</version>
|
||||
<configuration>
|
||||
<source>1.6</source>
|
||||
<target>1.6</target>
|
||||
<compilerArgs>
|
||||
<arg>-Xlint:all</arg>
|
||||
<arg>-Werror</arg>
|
||||
</compilerArgs>
|
||||
<fork>true</fork>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<!-- Overlay guacamole-common-js (zip) -->
|
||||
<!-- Overlay guacamole-common-js (zip) -->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-war-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<overlays>
|
||||
<overlay>
|
||||
<groupId>org.glyptodon.guacamole</groupId>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>guacamole-common-js</artifactId>
|
||||
<type>zip</type>
|
||||
</overlay>
|
||||
@@ -46,9 +53,8 @@
|
||||
|
||||
</plugins>
|
||||
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
</build>
|
||||
<dependencies>
|
||||
|
||||
<!-- Servlet API -->
|
||||
<dependency>
|
||||
@@ -58,22 +64,107 @@
|
||||
<version>2.5</version>
|
||||
</dependency>
|
||||
|
||||
<!-- SLF4J - logging -->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-api</artifactId>
|
||||
<version>1.6.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-jcl</artifactId>
|
||||
<version>1.6.1</version>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Main Guacamole library -->
|
||||
<dependency>
|
||||
<groupId>org.glyptodon.guacamole</groupId>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>guacamole-common</artifactId>
|
||||
<version>0.9.7</version>
|
||||
<version>0.9.9-incubating</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Guacamole JavaScript library -->
|
||||
<dependency>
|
||||
<groupId>org.glyptodon.guacamole</groupId>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>guacamole-common-js</artifactId>
|
||||
<version>0.9.8</version>
|
||||
<version>0.9.9-incubating</version>
|
||||
<type>zip</type>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JSR 356 WebSocket API -->
|
||||
<dependency>
|
||||
<groupId>javax.websocket</groupId>
|
||||
<artifactId>javax.websocket-api</artifactId>
|
||||
<version>1.0</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Jetty 8 servlet API (websocket) -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-websocket</artifactId>
|
||||
<version>8.1.1.v20120215</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Jetty 9.0 servlet API (websocket) -->
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-parent</artifactId>
|
||||
<version>20</version>
|
||||
<scope>provided</scope>
|
||||
<type>pom</type>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-api</artifactId>
|
||||
<version>9.0.7.v20131107</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty.websocket</groupId>
|
||||
<artifactId>websocket-servlet</artifactId>
|
||||
<version>9.0.7.v20131107</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Tomcat servlet API (websocket) -->
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-catalina</artifactId>
|
||||
<version>7.0.37</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.tomcat</groupId>
|
||||
<artifactId>tomcat-coyote</artifactId>
|
||||
<version>7.0.37</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Jersey - Guice extension -->
|
||||
<dependency>
|
||||
<groupId>com.sun.jersey.contribs</groupId>
|
||||
<artifactId>jersey-guice</artifactId>
|
||||
<version>1.17.1</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Guice Servlet -->
|
||||
<dependency>
|
||||
<groupId>com.google.inject.extensions</groupId>
|
||||
<artifactId>guice-servlet</artifactId>
|
||||
<version>3.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Guice - Dependency Injection -->
|
||||
<dependency>
|
||||
<groupId>com.google.inject</groupId>
|
||||
<artifactId>guice</artifactId>
|
||||
<version>3.0</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
</project>
|
||||
|
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel;
|
||||
|
||||
import com.google.inject.Module;
|
||||
|
||||
/**
|
||||
* Generic means of loading a tunnel without adding explicit dependencies within
|
||||
* the main ServletModule, as not all servlet containers may have the classes
|
||||
* required by all tunnel implementations.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public interface TunnelLoader extends Module {
|
||||
|
||||
/**
|
||||
* Checks whether this type of tunnel is supported by the servlet container.
|
||||
*
|
||||
* @return true if this type of tunnel is supported and can be loaded
|
||||
* without errors, false otherwise.
|
||||
*/
|
||||
public boolean isSupported();
|
||||
|
||||
}
|
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* This file has been modified from the original, upstream version to facilitate
|
||||
* integration with OpenUDS.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel;
|
||||
|
||||
import org.apache.guacamole.tunnel.http.RestrictedGuacamoleHTTPTunnelServlet;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Module which loads tunnel implementations.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class TunnelModule extends ServletModule {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(TunnelModule.class);
|
||||
|
||||
/**
|
||||
* Classnames of all implementation-specific WebSocket tunnel modules.
|
||||
*/
|
||||
private static final String[] WEBSOCKET_MODULES = {
|
||||
"org.apache.guacamole.tunnel.websocket.WebSocketTunnelModule",
|
||||
"org.apache.guacamole.tunnel.websocket.jetty8.WebSocketTunnelModule",
|
||||
"org.apache.guacamole.tunnel.websocket.jetty9.WebSocketTunnelModule",
|
||||
"org.apache.guacamole.tunnel.websocket.tomcat.WebSocketTunnelModule"
|
||||
};
|
||||
|
||||
private boolean loadWebSocketModule(String classname) {
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to find WebSocket module
|
||||
Class<?> module = Class.forName(classname);
|
||||
|
||||
// Create loader
|
||||
TunnelLoader loader = (TunnelLoader) module.getConstructor().newInstance();
|
||||
|
||||
// Install module, if supported
|
||||
if (loader.isSupported()) {
|
||||
install(loader);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If no such class or constructor, etc., then this particular
|
||||
// WebSocket support is not present
|
||||
catch (ClassNotFoundException e) {}
|
||||
catch (NoClassDefFoundError e) {}
|
||||
catch (NoSuchMethodException e) {}
|
||||
|
||||
// Log errors which indicate bugs
|
||||
catch (InstantiationException e) {
|
||||
logger.debug("Error instantiating WebSocket module.", e);
|
||||
}
|
||||
catch (IllegalAccessException e) {
|
||||
logger.debug("Error instantiating WebSocket module.", e);
|
||||
}
|
||||
catch (InvocationTargetException e) {
|
||||
logger.debug("Error instantiating WebSocket module.", e);
|
||||
}
|
||||
|
||||
// Load attempt failed
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
|
||||
bind(TunnelRequestService.class);
|
||||
|
||||
// Set up HTTP tunnel
|
||||
serve("/tunnel").with(RestrictedGuacamoleHTTPTunnelServlet.class);
|
||||
|
||||
// Try to load each WebSocket tunnel in sequence
|
||||
for (String classname : WEBSOCKET_MODULES) {
|
||||
if (loadWebSocketModule(classname)) {
|
||||
logger.debug("WebSocket module loaded: {}", classname);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Warn of lack of WebSocket
|
||||
logger.info("WebSocket support NOT present. Only HTTP will be used.");
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,371 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel;
|
||||
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
|
||||
/**
|
||||
* A request object which provides only the functions absolutely required to
|
||||
* retrieve and connect to a tunnel.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public abstract class TunnelRequest {
|
||||
|
||||
/**
|
||||
* The name of the request parameter containing the user's authentication
|
||||
* token.
|
||||
*/
|
||||
public static final String AUTH_TOKEN_PARAMETER = "token";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the identifier of the
|
||||
* AuthenticationProvider associated with the UserContext containing the
|
||||
* object to which a tunnel is being requested.
|
||||
*/
|
||||
public static final String AUTH_PROVIDER_IDENTIFIER_PARAMETER = "GUAC_DATA_SOURCE";
|
||||
|
||||
/**
|
||||
* The name of the parameter specifying the type of object to which a
|
||||
* tunnel is being requested. Currently, this may be "c" for a Guacamole
|
||||
* connection, or "g" for a Guacamole connection group.
|
||||
*/
|
||||
public static final String TYPE_PARAMETER = "GUAC_TYPE";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the unique identifier of the object
|
||||
* to which a tunnel is being requested.
|
||||
*/
|
||||
public static final String IDENTIFIER_PARAMETER = "GUAC_ID";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the desired display width, in
|
||||
* pixels.
|
||||
*/
|
||||
public static final String WIDTH_PARAMETER = "GUAC_WIDTH";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the desired display height, in
|
||||
* pixels.
|
||||
*/
|
||||
public static final String HEIGHT_PARAMETER = "GUAC_HEIGHT";
|
||||
|
||||
/**
|
||||
* The name of the parameter containing the desired display resolution, in
|
||||
* DPI.
|
||||
*/
|
||||
public static final String DPI_PARAMETER = "GUAC_DPI";
|
||||
|
||||
/**
|
||||
* The name of the parameter specifying one supported audio mimetype. This
|
||||
* will normally appear multiple times within a single tunnel request -
|
||||
* once for each mimetype.
|
||||
*/
|
||||
public static final String AUDIO_PARAMETER = "GUAC_AUDIO";
|
||||
|
||||
/**
|
||||
* The name of the parameter specifying one supported video mimetype. This
|
||||
* will normally appear multiple times within a single tunnel request -
|
||||
* once for each mimetype.
|
||||
*/
|
||||
public static final String VIDEO_PARAMETER = "GUAC_VIDEO";
|
||||
|
||||
/**
|
||||
* The name of the parameter specifying one supported image mimetype. This
|
||||
* will normally appear multiple times within a single tunnel request -
|
||||
* once for each mimetype.
|
||||
*/
|
||||
public static final String IMAGE_PARAMETER = "GUAC_IMAGE";
|
||||
|
||||
/**
|
||||
* All supported object types that can be used as the destination of a
|
||||
* tunnel.
|
||||
*/
|
||||
public static enum Type {
|
||||
|
||||
/**
|
||||
* A Guacamole connection.
|
||||
*/
|
||||
CONNECTION("c"),
|
||||
|
||||
/**
|
||||
* A Guacamole connection group.
|
||||
*/
|
||||
CONNECTION_GROUP("g");
|
||||
|
||||
/**
|
||||
* The parameter value which denotes a destination object of this type.
|
||||
*/
|
||||
final String PARAMETER_VALUE;
|
||||
|
||||
/**
|
||||
* Defines a Type having the given corresponding parameter value.
|
||||
*
|
||||
* @param value
|
||||
* The parameter value which denotes a destination object of this
|
||||
* type.
|
||||
*/
|
||||
Type(String value) {
|
||||
PARAMETER_VALUE = value;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns the value of the parameter having the given name.
|
||||
*
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* The value of the parameter having the given name, or null if no such
|
||||
* parameter was specified.
|
||||
*/
|
||||
public abstract String getParameter(String name);
|
||||
|
||||
/**
|
||||
* Returns a list of all values specified for the given parameter.
|
||||
*
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* All values of the parameter having the given name , or null if no
|
||||
* such parameter was specified.
|
||||
*/
|
||||
public abstract List<String> getParameterValues(String name);
|
||||
|
||||
/**
|
||||
* Returns the value of the parameter having the given name, throwing an
|
||||
* exception if the parameter is missing.
|
||||
*
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* The value of the parameter having the given name.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the parameter is not present in the request.
|
||||
*/
|
||||
public String getRequiredParameter(String name) throws GuacamoleException {
|
||||
|
||||
// Pull requested parameter, aborting if absent
|
||||
String value = getParameter(name);
|
||||
if (value == null)
|
||||
throw new GuacamoleClientException("Parameter \"" + name + "\" is required.");
|
||||
|
||||
return value;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the integer value of the parameter having the given name,
|
||||
* throwing an exception if the parameter cannot be parsed.
|
||||
*
|
||||
* @param name
|
||||
* The name of the parameter to return.
|
||||
*
|
||||
* @return
|
||||
* The integer value of the parameter having the given name, or null if
|
||||
* the parameter is missing.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the parameter is not a valid integer.
|
||||
*/
|
||||
public Integer getIntegerParameter(String name) throws GuacamoleException {
|
||||
|
||||
// Pull requested parameter
|
||||
String value = getParameter(name);
|
||||
if (value == null)
|
||||
return null;
|
||||
|
||||
// Attempt to parse as an integer
|
||||
try {
|
||||
return Integer.parseInt(value);
|
||||
}
|
||||
|
||||
// Rethrow any parsing error as a GuacamoleClientException
|
||||
catch (NumberFormatException e) {
|
||||
throw new GuacamoleClientException("Parameter \"" + name + "\" must be a valid integer.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authentication token associated with this tunnel request.
|
||||
*
|
||||
* @return
|
||||
* The authentication token associated with this tunnel request, or
|
||||
* null if no authentication token is present.
|
||||
*/
|
||||
public String getAuthenticationToken() {
|
||||
return getParameter(AUTH_TOKEN_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of the AuthenticationProvider associated with the
|
||||
* UserContext from which the connection or connection group is to be
|
||||
* retrieved when the tunnel is created. In the context of the REST API and
|
||||
* the JavaScript side of the web application, this is referred to as the
|
||||
* data source identifier.
|
||||
*
|
||||
* @return
|
||||
* The identifier of the AuthenticationProvider associated with the
|
||||
* UserContext from which the connection or connection group is to be
|
||||
* retrieved when the tunnel is created.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the identifier was not present in the request.
|
||||
*/
|
||||
public String getAuthenticationProviderIdentifier()
|
||||
throws GuacamoleException {
|
||||
return getRequiredParameter(AUTH_PROVIDER_IDENTIFIER_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of object for which the tunnel is being requested.
|
||||
*
|
||||
* @return
|
||||
* The type of object for which the tunnel is being requested.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the type was not present in the request, or if the type requested
|
||||
* is in the wrong format.
|
||||
*/
|
||||
public Type getType() throws GuacamoleException {
|
||||
|
||||
String type = getRequiredParameter(TYPE_PARAMETER);
|
||||
|
||||
// For each possible object type
|
||||
for (Type possibleType : Type.values()) {
|
||||
|
||||
// Match against defined parameter value
|
||||
if (type.equals(possibleType.PARAMETER_VALUE))
|
||||
return possibleType;
|
||||
|
||||
}
|
||||
|
||||
throw new GuacamoleClientException("Illegal identifier - unknown type.");
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the identifier of the destination of the tunnel being requested.
|
||||
* As there are multiple types of destination objects available, and within
|
||||
* multiple data sources, the associated object type and data source are
|
||||
* also necessary to determine what this identifier refers to.
|
||||
*
|
||||
* @return
|
||||
* The identifier of the destination of the tunnel being requested.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the identifier was not present in the request.
|
||||
*/
|
||||
public String getIdentifier() throws GuacamoleException {
|
||||
return getRequiredParameter(IDENTIFIER_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display width desired for the Guacamole session over the
|
||||
* tunnel being requested.
|
||||
*
|
||||
* @return
|
||||
* The display width desired for the Guacamole session over the tunnel
|
||||
* being requested, or null if no width was given.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the width specified was not a valid integer.
|
||||
*/
|
||||
public Integer getWidth() throws GuacamoleException {
|
||||
return getIntegerParameter(WIDTH_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display height desired for the Guacamole session over the
|
||||
* tunnel being requested.
|
||||
*
|
||||
* @return
|
||||
* The display height desired for the Guacamole session over the tunnel
|
||||
* being requested, or null if no width was given.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the height specified was not a valid integer.
|
||||
*/
|
||||
public Integer getHeight() throws GuacamoleException {
|
||||
return getIntegerParameter(HEIGHT_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the display resolution desired for the Guacamole session over
|
||||
* the tunnel being requested, in DPI.
|
||||
*
|
||||
* @return
|
||||
* The display resolution desired for the Guacamole session over the
|
||||
* tunnel being requested, or null if no resolution was given.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the resolution specified was not a valid integer.
|
||||
*/
|
||||
public Integer getDPI() throws GuacamoleException {
|
||||
return getIntegerParameter(DPI_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all audio mimetypes declared as supported within the
|
||||
* tunnel request.
|
||||
*
|
||||
* @return
|
||||
* A list of all audio mimetypes declared as supported within the
|
||||
* tunnel request, or null if no mimetypes were specified.
|
||||
*/
|
||||
public List<String> getAudioMimetypes() {
|
||||
return getParameterValues(AUDIO_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all video mimetypes declared as supported within the
|
||||
* tunnel request.
|
||||
*
|
||||
* @return
|
||||
* A list of all video mimetypes declared as supported within the
|
||||
* tunnel request, or null if no mimetypes were specified.
|
||||
*/
|
||||
public List<String> getVideoMimetypes() {
|
||||
return getParameterValues(VIDEO_PARAMETER);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a list of all image mimetypes declared as supported within the
|
||||
* tunnel request.
|
||||
*
|
||||
* @return
|
||||
* A list of all image mimetypes declared as supported within the
|
||||
* tunnel request, or null if no mimetypes were specified.
|
||||
*/
|
||||
public List<String> getImageMimetypes() {
|
||||
return getParameterValues(IMAGE_PARAMETER);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,159 @@
|
||||
/*
|
||||
* This file has been modified from the original, upstream version to facilitate
|
||||
* integration with OpenUDS.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import java.util.List;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleSocket;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.net.InetGuacamoleSocket;
|
||||
import org.apache.guacamole.net.SimpleGuacamoleTunnel;
|
||||
import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket;
|
||||
import org.apache.guacamole.protocol.GuacamoleClientInformation;
|
||||
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||
import org.openuds.guacamole.connection.ConnectionService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Utility class that takes a standard request from the Guacamole JavaScript
|
||||
* client and produces the corresponding GuacamoleTunnel. The implementation
|
||||
* of this utility is specific to the form of request used by the upstream
|
||||
* Guacamole web application, and is not necessarily useful to applications
|
||||
* that use purely the Guacamole API.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
* @author Vasily Loginov
|
||||
*/
|
||||
@Singleton
|
||||
public class TunnelRequestService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(TunnelRequestService.class);
|
||||
|
||||
/**
|
||||
* Service for retrieving remotely-maintained connection information.
|
||||
*/
|
||||
@Inject
|
||||
private ConnectionService connectionService;
|
||||
|
||||
/**
|
||||
* The hostname of the server hosting guacd.
|
||||
*/
|
||||
private static final String GUACD_HOSTNAME = "127.0.0.1";
|
||||
|
||||
/**
|
||||
* The port that guacd will be listening on.
|
||||
*/
|
||||
private static final int GUACD_PORT = 4822;
|
||||
|
||||
/**
|
||||
* Creates a new tunnel using the parameters and credentials present in
|
||||
* the given request.
|
||||
*
|
||||
* @param request
|
||||
* The HttpServletRequest describing the tunnel to create.
|
||||
*
|
||||
* @return
|
||||
* The created tunnel, or null if the tunnel could not be created.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while creating the tunnel.
|
||||
*/
|
||||
public GuacamoleTunnel createTunnel(TunnelRequest request) throws GuacamoleException {
|
||||
|
||||
// Pull OpenUDS-specific "data" parameter
|
||||
String data = request.getParameter("data");
|
||||
if (data == null || data.isEmpty()) {
|
||||
logger.debug("No ID received in tunnel connect request.");
|
||||
throw new GuacamoleClientException("Connection data not provided.");
|
||||
}
|
||||
|
||||
logger.debug("Establishing tunnel and connection with data from \"{}\"...", data);
|
||||
|
||||
// Get connection from remote service
|
||||
GuacamoleConfiguration config = connectionService.getConnectionConfiguration(data);
|
||||
if (config == null)
|
||||
throw new GuacamoleClientException("Connection configuration could not be retrieved.");
|
||||
|
||||
// Get client information
|
||||
GuacamoleClientInformation info = new GuacamoleClientInformation();
|
||||
|
||||
// Set width if provided
|
||||
String width = request.getParameter("GUAC_WIDTH");
|
||||
if (width != null)
|
||||
info.setOptimalScreenWidth(Integer.parseInt(width));
|
||||
|
||||
// Set height if provided
|
||||
String height = request.getParameter("GUAC_HEIGHT");
|
||||
if (height != null)
|
||||
info.setOptimalScreenHeight(Integer.parseInt(height));
|
||||
|
||||
// Set resolution if provided
|
||||
String dpi = request.getParameter("GUAC_DPI");
|
||||
if (dpi != null)
|
||||
info.setOptimalResolution(Integer.parseInt(dpi));
|
||||
|
||||
// Add audio mimetypes
|
||||
List<String> audio_mimetypes = request.getParameterValues("GUAC_AUDIO");
|
||||
if (audio_mimetypes != null)
|
||||
info.getAudioMimetypes().addAll(audio_mimetypes);
|
||||
|
||||
// Add video mimetypes
|
||||
List<String> video_mimetypes = request.getParameterValues("GUAC_VIDEO");
|
||||
if (video_mimetypes != null)
|
||||
info.getVideoMimetypes().addAll(video_mimetypes);
|
||||
|
||||
// Add image mimetypes
|
||||
List<String> image_mimetypes = request.getParameterValues("GUAC_IMAGE");
|
||||
if (image_mimetypes != null)
|
||||
info.getImageMimetypes().addAll(image_mimetypes);
|
||||
|
||||
// Connect socket for connection
|
||||
GuacamoleSocket socket;
|
||||
try {
|
||||
socket = new ConfiguredGuacamoleSocket(
|
||||
new InetGuacamoleSocket(GUACD_HOSTNAME, GUACD_PORT),
|
||||
config, info
|
||||
);
|
||||
}
|
||||
|
||||
// Log any errors during connection
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Unable to connect to guacd.", e);
|
||||
throw e;
|
||||
}
|
||||
|
||||
// Return corresponding tunnel
|
||||
return new SimpleGuacamoleTunnel(socket);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.http;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||
|
||||
/**
|
||||
* HTTP-specific implementation of TunnelRequest.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class HTTPTunnelRequest extends TunnelRequest {
|
||||
|
||||
/**
|
||||
* A copy of the parameters obtained from the HttpServletRequest used to
|
||||
* construct the HTTPTunnelRequest.
|
||||
*/
|
||||
private final Map<String, List<String>> parameterMap =
|
||||
new HashMap<String, List<String>>();
|
||||
|
||||
/**
|
||||
* Creates a HTTPTunnelRequest which copies and exposes the parameters
|
||||
* from the given HttpServletRequest.
|
||||
*
|
||||
* @param request
|
||||
* The HttpServletRequest to copy parameter values from.
|
||||
*/
|
||||
@SuppressWarnings("unchecked") // getParameterMap() is defined as returning Map<String, String[]>
|
||||
public HTTPTunnelRequest(HttpServletRequest request) {
|
||||
|
||||
// For each parameter
|
||||
for (Map.Entry<String, String[]> mapEntry : ((Map<String, String[]>)
|
||||
request.getParameterMap()).entrySet()) {
|
||||
|
||||
// Get parameter name and corresponding values
|
||||
String parameterName = mapEntry.getKey();
|
||||
List<String> parameterValues = Arrays.asList(mapEntry.getValue());
|
||||
|
||||
// Store copy of all values in our own map
|
||||
parameterMap.put(
|
||||
parameterName,
|
||||
new ArrayList<String>(parameterValues)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
List<String> values = getParameterValues(name);
|
||||
|
||||
// Return the first value from the list if available
|
||||
if (values != null && !values.isEmpty())
|
||||
return values.get(0);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getParameterValues(String name) {
|
||||
return parameterMap.get(name);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* This file has been modified from the original, upstream version to facilitate
|
||||
* integration with OpenUDS.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.http;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.servlet.GuacamoleHTTPTunnelServlet;
|
||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Connects users to a tunnel associated with the authorized connection
|
||||
* having the given ID.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Singleton
|
||||
public class RestrictedGuacamoleHTTPTunnelServlet extends GuacamoleHTTPTunnelServlet {
|
||||
|
||||
/**
|
||||
* Service for handling tunnel requests.
|
||||
*/
|
||||
@Inject
|
||||
private TunnelRequestService tunnelRequestService;
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(RestrictedGuacamoleHTTPTunnelServlet.class);
|
||||
|
||||
@Override
|
||||
protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
|
||||
|
||||
// Attempt to create HTTP tunnel
|
||||
GuacamoleTunnel tunnel = tunnelRequestService.createTunnel(new HTTPTunnelRequest(request));
|
||||
|
||||
// If successful, warn of lack of WebSocket
|
||||
logger.info("Using HTTP tunnel (not WebSocket). Performance may be sub-optimal.");
|
||||
|
||||
return tunnel;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes which leverage Guacamole's built-in HTTP tunnel implementation.
|
||||
*/
|
||||
package org.apache.guacamole.tunnel.http;
|
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes which are common to all tunnel implementations.
|
||||
*/
|
||||
package org.apache.guacamole.tunnel;
|
@@ -0,0 +1,122 @@
|
||||
/*
|
||||
* This file has been modified from the original, upstream version to facilitate
|
||||
* integration with OpenUDS.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket;
|
||||
|
||||
import com.google.inject.Provider;
|
||||
import java.util.Map;
|
||||
import javax.websocket.EndpointConfig;
|
||||
import javax.websocket.HandshakeResponse;
|
||||
import javax.websocket.Session;
|
||||
import javax.websocket.server.HandshakeRequest;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||
import org.apache.guacamole.websocket.GuacamoleWebSocketTunnelEndpoint;
|
||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||
|
||||
/**
|
||||
* Tunnel implementation which uses WebSocket as a tunnel backend, rather than
|
||||
* HTTP, properly parsing connection IDs included in the connection request.
|
||||
*/
|
||||
public class RestrictedGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTunnelEndpoint {
|
||||
|
||||
/**
|
||||
* Unique string which shall be used to store the TunnelRequest
|
||||
* associated with a WebSocket connection.
|
||||
*/
|
||||
private static final String TUNNEL_REQUEST_PROPERTY = "WS_GUAC_TUNNEL_REQUEST";
|
||||
|
||||
/**
|
||||
* Unique string which shall be used to store the TunnelRequestService to
|
||||
* be used for processing TunnelRequests.
|
||||
*/
|
||||
private static final String TUNNEL_REQUEST_SERVICE_PROPERTY = "WS_GUAC_TUNNEL_REQUEST_SERVICE";
|
||||
|
||||
/**
|
||||
* Configurator implementation which stores the requested GuacamoleTunnel
|
||||
* within the user properties. The GuacamoleTunnel will be later retrieved
|
||||
* during the connection process.
|
||||
*/
|
||||
public static class Configurator extends ServerEndpointConfig.Configurator {
|
||||
|
||||
/**
|
||||
* Provider which provides instances of a service for handling
|
||||
* tunnel requests.
|
||||
*/
|
||||
private final Provider<TunnelRequestService> tunnelRequestServiceProvider;
|
||||
|
||||
/**
|
||||
* Creates a new Configurator which uses the given tunnel request
|
||||
* service provider to retrieve the necessary service to handle new
|
||||
* connections requests.
|
||||
*
|
||||
* @param tunnelRequestServiceProvider
|
||||
* The tunnel request service provider to use for all new
|
||||
* connections.
|
||||
*/
|
||||
public Configurator(Provider<TunnelRequestService> tunnelRequestServiceProvider) {
|
||||
this.tunnelRequestServiceProvider = tunnelRequestServiceProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modifyHandshake(ServerEndpointConfig config,
|
||||
HandshakeRequest request, HandshakeResponse response) {
|
||||
|
||||
super.modifyHandshake(config, request, response);
|
||||
|
||||
// Store tunnel request and tunnel request service for retrieval
|
||||
// upon WebSocket open
|
||||
Map<String, Object> userProperties = config.getUserProperties();
|
||||
userProperties.clear();
|
||||
userProperties.put(TUNNEL_REQUEST_PROPERTY, new WebSocketTunnelRequest(request));
|
||||
userProperties.put(TUNNEL_REQUEST_SERVICE_PROPERTY, tunnelRequestServiceProvider.get());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuacamoleTunnel createTunnel(Session session,
|
||||
EndpointConfig config) throws GuacamoleException {
|
||||
|
||||
Map<String, Object> userProperties = config.getUserProperties();
|
||||
|
||||
// Get original tunnel request
|
||||
TunnelRequest tunnelRequest = (TunnelRequest) userProperties.get(TUNNEL_REQUEST_PROPERTY);
|
||||
if (tunnelRequest == null)
|
||||
return null;
|
||||
|
||||
// Get tunnel request service
|
||||
TunnelRequestService tunnelRequestService = (TunnelRequestService) userProperties.get(TUNNEL_REQUEST_SERVICE_PROPERTY);
|
||||
if (tunnelRequestService == null)
|
||||
return null;
|
||||
|
||||
// Create and return tunnel
|
||||
return tunnelRequestService.createTunnel(tunnelRequest);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,106 @@
|
||||
/*
|
||||
* This file has been modified from the original, upstream version to facilitate
|
||||
* integration with OpenUDS.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket;
|
||||
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import java.util.Arrays;
|
||||
import javax.websocket.DeploymentException;
|
||||
import javax.websocket.server.ServerContainer;
|
||||
import javax.websocket.server.ServerEndpointConfig;
|
||||
import org.apache.guacamole.tunnel.TunnelLoader;
|
||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Loads the JSR-356 WebSocket tunnel implementation.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to find WebSocket servlet
|
||||
Class.forName("javax.websocket.Endpoint");
|
||||
|
||||
// Support found
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// If no such servlet class, this particular WebSocket support
|
||||
// is not present
|
||||
catch (ClassNotFoundException e) {}
|
||||
catch (NoClassDefFoundError e) {}
|
||||
|
||||
// Support not found
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureServlets() {
|
||||
|
||||
logger.info("Loading JSR-356 WebSocket support...");
|
||||
|
||||
// Get container
|
||||
ServerContainer container = (ServerContainer) getServletContext().getAttribute("javax.websocket.server.ServerContainer");
|
||||
if (container == null) {
|
||||
logger.warn("ServerContainer attribute required by JSR-356 is missing. Cannot load JSR-356 WebSocket support.");
|
||||
return;
|
||||
}
|
||||
|
||||
Provider<TunnelRequestService> tunnelRequestServiceProvider = getProvider(TunnelRequestService.class);
|
||||
|
||||
// Build configuration for WebSocket tunnel
|
||||
ServerEndpointConfig config =
|
||||
ServerEndpointConfig.Builder.create(RestrictedGuacamoleWebSocketTunnelEndpoint.class, "/websocket-tunnel")
|
||||
.configurator(new RestrictedGuacamoleWebSocketTunnelEndpoint.Configurator(tunnelRequestServiceProvider))
|
||||
.subprotocols(Arrays.asList(new String[]{"guacamole"}))
|
||||
.build();
|
||||
|
||||
try {
|
||||
|
||||
// Add configuration to container
|
||||
container.addEndpoint(config);
|
||||
|
||||
}
|
||||
catch (DeploymentException e) {
|
||||
logger.error("Unable to deploy WebSocket tunnel.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import javax.websocket.server.HandshakeRequest;
|
||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||
|
||||
/**
|
||||
* WebSocket-specific implementation of TunnelRequest.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelRequest extends TunnelRequest {
|
||||
|
||||
/**
|
||||
* All parameters passed via HTTP to the WebSocket handshake.
|
||||
*/
|
||||
private final Map<String, List<String>> handshakeParameters;
|
||||
|
||||
/**
|
||||
* Creates a TunnelRequest implementation which delegates parameter and
|
||||
* session retrieval to the given HandshakeRequest.
|
||||
*
|
||||
* @param request The HandshakeRequest to wrap.
|
||||
*/
|
||||
public WebSocketTunnelRequest(HandshakeRequest request) {
|
||||
this.handshakeParameters = request.getParameterMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
|
||||
// Pull list of values, if present
|
||||
List<String> values = getParameterValues(name);
|
||||
if (values == null || values.isEmpty())
|
||||
return null;
|
||||
|
||||
// Return first parameter value arbitrarily
|
||||
return values.get(0);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getParameterValues(String name) {
|
||||
return handshakeParameters.get(name);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,236 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.jetty8;
|
||||
|
||||
import java.io.IOException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.io.GuacamoleReader;
|
||||
import org.apache.guacamole.io.GuacamoleWriter;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.eclipse.jetty.websocket.WebSocket;
|
||||
import org.eclipse.jetty.websocket.WebSocket.Connection;
|
||||
import org.eclipse.jetty.websocket.WebSocketServlet;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
||||
import org.apache.guacamole.protocol.GuacamoleInstruction;
|
||||
import org.apache.guacamole.tunnel.http.HTTPTunnelRequest;
|
||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||
import org.apache.guacamole.protocol.GuacamoleStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
|
||||
|
||||
/**
|
||||
* The default, minimum buffer size for instructions.
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
/**
|
||||
* Sends the given status on the given WebSocket connection and closes the
|
||||
* connection.
|
||||
*
|
||||
* @param connection The WebSocket connection to close.
|
||||
* @param guac_status The status to send.
|
||||
*/
|
||||
public static void closeConnection(Connection connection,
|
||||
GuacamoleStatus guac_status) {
|
||||
|
||||
connection.close(guac_status.getWebSocketCode(),
|
||||
Integer.toString(guac_status.getGuacamoleStatusCode()));
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
|
||||
|
||||
final TunnelRequest tunnelRequest = new HTTPTunnelRequest(request);
|
||||
|
||||
// Return new WebSocket which communicates through tunnel
|
||||
return new WebSocket.OnTextMessage() {
|
||||
|
||||
/**
|
||||
* The GuacamoleTunnel associated with the connected WebSocket. If
|
||||
* the WebSocket has not yet been connected, this will be null.
|
||||
*/
|
||||
private GuacamoleTunnel tunnel = null;
|
||||
|
||||
@Override
|
||||
public void onMessage(String string) {
|
||||
|
||||
// Ignore inbound messages if there is no associated tunnel
|
||||
if (tunnel == null)
|
||||
return;
|
||||
|
||||
GuacamoleWriter writer = tunnel.acquireWriter();
|
||||
|
||||
// Write message received
|
||||
try {
|
||||
writer.write(string.toCharArray());
|
||||
}
|
||||
catch (GuacamoleConnectionClosedException e) {
|
||||
logger.debug("Connection to guacd closed.", e);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("WebSocket tunnel write failed.", e);
|
||||
}
|
||||
|
||||
tunnel.releaseWriter();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(final Connection connection) {
|
||||
|
||||
try {
|
||||
tunnel = doConnect(tunnelRequest);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
||||
logger.debug("Error connecting WebSocket tunnel.", e);
|
||||
closeConnection(connection, e.getStatus());
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not start connection if tunnel does not exist
|
||||
if (tunnel == null) {
|
||||
closeConnection(connection, GuacamoleStatus.RESOURCE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
Thread readThread = new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
|
||||
GuacamoleReader reader = tunnel.acquireReader();
|
||||
char[] readMessage;
|
||||
|
||||
try {
|
||||
|
||||
// Send tunnel UUID
|
||||
connection.sendMessage(new GuacamoleInstruction(
|
||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||
tunnel.getUUID().toString()
|
||||
).toString());
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to read
|
||||
while ((readMessage = reader.read()) != null) {
|
||||
|
||||
// Buffer message
|
||||
buffer.append(readMessage);
|
||||
|
||||
// Flush if we expect to wait or buffer is getting full
|
||||
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
|
||||
connection.sendMessage(buffer.toString());
|
||||
buffer.setLength(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No more data
|
||||
closeConnection(connection, GuacamoleStatus.SUCCESS);
|
||||
|
||||
}
|
||||
|
||||
// Catch any thrown guacamole exception and attempt
|
||||
// to pass within the WebSocket connection, logging
|
||||
// each error appropriately.
|
||||
catch (GuacamoleClientException e) {
|
||||
logger.info("WebSocket connection terminated: {}", e.getMessage());
|
||||
logger.debug("WebSocket connection terminated due to client error.", e);
|
||||
closeConnection(connection, e.getStatus());
|
||||
}
|
||||
catch (GuacamoleConnectionClosedException e) {
|
||||
logger.debug("Connection to guacd closed.", e);
|
||||
closeConnection(connection, GuacamoleStatus.SUCCESS);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
||||
logger.debug("Internal error during connection to guacd.", e);
|
||||
closeConnection(connection, e.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.debug("WebSocket tunnel read failed due to I/O error.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
readThread.start();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int i, String string) {
|
||||
try {
|
||||
if (tunnel != null)
|
||||
tunnel.close();
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("Unable to close connection to guacd.", e);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the JavaScript Guacamole client makes a connection
|
||||
* request. It it up to the implementor of this function to define what
|
||||
* conditions must be met for a tunnel to be configured and returned as a
|
||||
* result of this connection request (whether some sort of credentials must
|
||||
* be specified, for example).
|
||||
*
|
||||
* @param request
|
||||
* The TunnelRequest associated with the connection request received.
|
||||
* Any parameters specified along with the connection request can be
|
||||
* read from this object.
|
||||
*
|
||||
* @return
|
||||
* A newly constructed GuacamoleTunnel if successful, null otherwise.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while constructing the GuacamoleTunnel, or if the
|
||||
* conditions required for connection are not met.
|
||||
*/
|
||||
protected abstract GuacamoleTunnel doConnect(TunnelRequest request)
|
||||
throws GuacamoleException;
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file has been modified from the original, upstream version to facilitate
|
||||
* integration with OpenUDS.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.jetty8;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||
|
||||
/**
|
||||
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
|
||||
* rather than HTTP, properly parsing connection IDs included in the connection
|
||||
* request.
|
||||
*/
|
||||
@Singleton
|
||||
public class RestrictedGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
|
||||
|
||||
/**
|
||||
* Service for handling tunnel requests.
|
||||
*/
|
||||
@Inject
|
||||
private TunnelRequestService tunnelRequestService;
|
||||
|
||||
@Override
|
||||
protected GuacamoleTunnel doConnect(TunnelRequest request)
|
||||
throws GuacamoleException {
|
||||
return tunnelRequestService.createTunnel(request);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.jetty8;
|
||||
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import org.apache.guacamole.tunnel.TunnelLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Loads the Jetty 8 WebSocket tunnel implementation.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to find WebSocket servlet
|
||||
Class.forName("org.apache.guacamole.tunnel.websocket.jetty8.RestrictedGuacamoleWebSocketTunnelServlet");
|
||||
|
||||
// Support found
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// If no such servlet class, this particular WebSocket support
|
||||
// is not present
|
||||
catch (ClassNotFoundException e) {}
|
||||
catch (NoClassDefFoundError e) {}
|
||||
|
||||
// Support not found
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureServlets() {
|
||||
|
||||
logger.info("Loading Jetty 8 WebSocket support...");
|
||||
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jetty 8 WebSocket tunnel implementation. The classes here require Jetty 8.
|
||||
*/
|
||||
package org.apache.guacamole.tunnel.websocket.jetty8;
|
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||
|
||||
import java.io.IOException;
|
||||
import org.eclipse.jetty.websocket.api.CloseStatus;
|
||||
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.eclipse.jetty.websocket.api.WebSocketListener;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.io.GuacamoleReader;
|
||||
import org.apache.guacamole.io.GuacamoleWriter;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.protocol.GuacamoleInstruction;
|
||||
import org.apache.guacamole.protocol.GuacamoleStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* WebSocket listener implementation which provides a Guacamole tunnel
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListener {
|
||||
|
||||
/**
|
||||
* The default, minimum buffer size for instructions.
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private static final Logger logger = LoggerFactory.getLogger(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
||||
|
||||
/**
|
||||
* The underlying GuacamoleTunnel. WebSocket reads/writes will be handled
|
||||
* as reads/writes to this tunnel.
|
||||
*/
|
||||
private GuacamoleTunnel tunnel;
|
||||
|
||||
/**
|
||||
* Sends the given status on the given WebSocket connection and closes the
|
||||
* connection.
|
||||
*
|
||||
* @param session The outbound WebSocket connection to close.
|
||||
* @param guac_status The status to send.
|
||||
*/
|
||||
private void closeConnection(Session session, GuacamoleStatus guac_status) {
|
||||
|
||||
try {
|
||||
int code = guac_status.getWebSocketCode();
|
||||
String message = Integer.toString(guac_status.getGuacamoleStatusCode());
|
||||
session.close(new CloseStatus(code, message));
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.debug("Unable to close WebSocket connection.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new tunnel for the given session. How this tunnel is created
|
||||
* or retrieved is implementation-dependent.
|
||||
*
|
||||
* @param session The session associated with the active WebSocket
|
||||
* connection.
|
||||
* @return A connected tunnel, or null if no such tunnel exists.
|
||||
* @throws GuacamoleException If an error occurs while retrieving the
|
||||
* tunnel, or if access to the tunnel is denied.
|
||||
*/
|
||||
protected abstract GuacamoleTunnel createTunnel(Session session)
|
||||
throws GuacamoleException;
|
||||
|
||||
@Override
|
||||
public void onWebSocketConnect(final Session session) {
|
||||
|
||||
try {
|
||||
|
||||
// Get tunnel
|
||||
tunnel = createTunnel(session);
|
||||
if (tunnel == null) {
|
||||
closeConnection(session, GuacamoleStatus.RESOURCE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
||||
logger.debug("Error connecting WebSocket tunnel.", e);
|
||||
closeConnection(session, e.getStatus());
|
||||
return;
|
||||
}
|
||||
|
||||
// Prepare read transfer thread
|
||||
Thread readThread = new Thread() {
|
||||
|
||||
/**
|
||||
* Remote (client) side of this connection
|
||||
*/
|
||||
private final RemoteEndpoint remote = session.getRemote();
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
|
||||
GuacamoleReader reader = tunnel.acquireReader();
|
||||
char[] readMessage;
|
||||
|
||||
try {
|
||||
|
||||
// Send tunnel UUID
|
||||
remote.sendString(new GuacamoleInstruction(
|
||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||
tunnel.getUUID().toString()
|
||||
).toString());
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to read
|
||||
while ((readMessage = reader.read()) != null) {
|
||||
|
||||
// Buffer message
|
||||
buffer.append(readMessage);
|
||||
|
||||
// Flush if we expect to wait or buffer is getting full
|
||||
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
|
||||
remote.sendString(buffer.toString());
|
||||
buffer.setLength(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No more data
|
||||
closeConnection(session, GuacamoleStatus.SUCCESS);
|
||||
|
||||
}
|
||||
|
||||
// Catch any thrown guacamole exception and attempt
|
||||
// to pass within the WebSocket connection, logging
|
||||
// each error appropriately.
|
||||
catch (GuacamoleClientException e) {
|
||||
logger.info("WebSocket connection terminated: {}", e.getMessage());
|
||||
logger.debug("WebSocket connection terminated due to client error.", e);
|
||||
closeConnection(session, e.getStatus());
|
||||
}
|
||||
catch (GuacamoleConnectionClosedException e) {
|
||||
logger.debug("Connection to guacd closed.", e);
|
||||
closeConnection(session, GuacamoleStatus.SUCCESS);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
||||
logger.debug("Internal error during connection to guacd.", e);
|
||||
closeConnection(session, e.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.debug("I/O error prevents further reads.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
readThread.start();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketText(String message) {
|
||||
|
||||
// Ignore inbound messages if there is no associated tunnel
|
||||
if (tunnel == null)
|
||||
return;
|
||||
|
||||
GuacamoleWriter writer = tunnel.acquireWriter();
|
||||
|
||||
try {
|
||||
// Write received message
|
||||
writer.write(message.toCharArray());
|
||||
}
|
||||
catch (GuacamoleConnectionClosedException e) {
|
||||
logger.debug("Connection to guacd closed.", e);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("WebSocket tunnel write failed.", e);
|
||||
}
|
||||
|
||||
tunnel.releaseWriter();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketBinary(byte[] payload, int offset, int length) {
|
||||
throw new UnsupportedOperationException("Binary WebSocket messages are not supported.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onWebSocketError(Throwable t) {
|
||||
|
||||
logger.debug("WebSocket tunnel closing due to error.", t);
|
||||
|
||||
try {
|
||||
if (tunnel != null)
|
||||
tunnel.close();
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("Unable to close connection to guacd.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void onWebSocketClose(int statusCode, String reason) {
|
||||
|
||||
try {
|
||||
if (tunnel != null)
|
||||
tunnel.close();
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("Unable to close connection to guacd.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,74 @@
|
||||
/*
|
||||
* This file has been modified from the original, upstream version to facilitate
|
||||
* integration with OpenUDS.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeResponse;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
|
||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||
|
||||
/**
|
||||
* WebSocketCreator which selects the appropriate WebSocketListener
|
||||
* implementation if the "guacamole" subprotocol is in use.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class RestrictedGuacamoleWebSocketCreator implements WebSocketCreator {
|
||||
|
||||
/**
|
||||
* Service for handling tunnel requests.
|
||||
*/
|
||||
private final TunnelRequestService tunnelRequestService;
|
||||
|
||||
/**
|
||||
* Creates a new WebSocketCreator which uses the given TunnelRequestService
|
||||
* to create new GuacamoleTunnels for inbound requests.
|
||||
*
|
||||
* @param tunnelRequestService The service to use for inbound tunnel
|
||||
* requests.
|
||||
*/
|
||||
public RestrictedGuacamoleWebSocketCreator(TunnelRequestService tunnelRequestService) {
|
||||
this.tunnelRequestService = tunnelRequestService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) {
|
||||
|
||||
// Validate and use "guacamole" subprotocol
|
||||
for (String subprotocol : request.getSubProtocols()) {
|
||||
|
||||
if ("guacamole".equals(subprotocol)) {
|
||||
response.setAcceptedSubProtocol(subprotocol);
|
||||
return new RestrictedGuacamoleWebSocketTunnelListener(tunnelRequestService);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Invalid protocol
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
/*
|
||||
* This file has been modified from the original, upstream version to facilitate
|
||||
* integration with OpenUDS.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||
|
||||
/**
|
||||
* WebSocket listener implementation which properly parses connection IDs
|
||||
* included in the connection request.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class RestrictedGuacamoleWebSocketTunnelListener extends GuacamoleWebSocketTunnelListener {
|
||||
|
||||
/**
|
||||
* Service for handling tunnel requests.
|
||||
*/
|
||||
private final TunnelRequestService tunnelRequestService;
|
||||
|
||||
/**
|
||||
* Creates a new WebSocketListener which uses the given TunnelRequestService
|
||||
* to create new GuacamoleTunnels for inbound requests.
|
||||
*
|
||||
* @param tunnelRequestService The service to use for inbound tunnel
|
||||
* requests.
|
||||
*/
|
||||
public RestrictedGuacamoleWebSocketTunnelListener(TunnelRequestService tunnelRequestService) {
|
||||
this.tunnelRequestService = tunnelRequestService;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuacamoleTunnel createTunnel(Session session) throws GuacamoleException {
|
||||
return tunnelRequestService.createTunnel(new WebSocketTunnelRequest(session.getUpgradeRequest()));
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* This file has been modified from the original, upstream version to facilitate
|
||||
* integration with OpenUDS.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
|
||||
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
|
||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||
|
||||
/**
|
||||
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
@Singleton
|
||||
public class RestrictedGuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
|
||||
/**
|
||||
* Service for handling tunnel requests.
|
||||
*/
|
||||
@Inject
|
||||
private TunnelRequestService tunnelRequestService;
|
||||
|
||||
@Override
|
||||
public void configure(WebSocketServletFactory factory) {
|
||||
|
||||
// Register WebSocket implementation
|
||||
factory.setCreator(new RestrictedGuacamoleWebSocketCreator(tunnelRequestService));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import org.apache.guacamole.tunnel.TunnelLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Loads the Jetty 9 WebSocket tunnel implementation.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to find WebSocket servlet
|
||||
Class.forName("org.apache.guacamole.tunnel.websocket.jetty9.RestrictedGuacamoleWebSocketTunnelServlet");
|
||||
|
||||
// Support found
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// If no such servlet class, this particular WebSocket support
|
||||
// is not present
|
||||
catch (ClassNotFoundException e) {}
|
||||
catch (NoClassDefFoundError e) {}
|
||||
|
||||
// Support not found
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureServlets() {
|
||||
|
||||
logger.info("Loading Jetty 9 WebSocket support...");
|
||||
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,73 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.jetty9;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import org.eclipse.jetty.websocket.api.UpgradeRequest;
|
||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||
|
||||
/**
|
||||
* Jetty 9 WebSocket-specific implementation of TunnelRequest.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelRequest extends TunnelRequest {
|
||||
|
||||
/**
|
||||
* All parameters passed via HTTP to the WebSocket handshake.
|
||||
*/
|
||||
private final Map<String, String[]> handshakeParameters;
|
||||
|
||||
/**
|
||||
* Creates a TunnelRequest implementation which delegates parameter and
|
||||
* session retrieval to the given UpgradeRequest.
|
||||
*
|
||||
* @param request The UpgradeRequest to wrap.
|
||||
*/
|
||||
public WebSocketTunnelRequest(UpgradeRequest request) {
|
||||
this.handshakeParameters = request.getParameterMap();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParameter(String name) {
|
||||
|
||||
// Pull list of values, if present
|
||||
List<String> values = getParameterValues(name);
|
||||
if (values == null || values.isEmpty())
|
||||
return null;
|
||||
|
||||
// Return first parameter value arbitrarily
|
||||
return values.get(0);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> getParameterValues(String name) {
|
||||
|
||||
String[] values = handshakeParameters.get(name);
|
||||
if (values == null)
|
||||
return null;
|
||||
|
||||
return Arrays.asList(values);
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jetty 9 WebSocket tunnel implementation. The classes here require at least
|
||||
* Jetty 9, prior to Jetty 9.1 (when support for JSR 356 was implemented).
|
||||
*/
|
||||
package org.apache.guacamole.tunnel.websocket.jetty9;
|
@@ -0,0 +1,24 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Standard WebSocket tunnel implementation. The classes here require a recent
|
||||
* servlet container that supports JSR 356.
|
||||
*/
|
||||
package org.apache.guacamole.tunnel.websocket;
|
@@ -0,0 +1,269 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.tomcat;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Reader;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.util.List;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.io.GuacamoleReader;
|
||||
import org.apache.guacamole.io.GuacamoleWriter;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.catalina.websocket.StreamInbound;
|
||||
import org.apache.catalina.websocket.WebSocketServlet;
|
||||
import org.apache.catalina.websocket.WsOutbound;
|
||||
import org.apache.guacamole.GuacamoleClientException;
|
||||
import org.apache.guacamole.GuacamoleConnectionClosedException;
|
||||
import org.apache.guacamole.protocol.GuacamoleInstruction;
|
||||
import org.apache.guacamole.tunnel.http.HTTPTunnelRequest;
|
||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||
import org.apache.guacamole.protocol.GuacamoleStatus;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
|
||||
|
||||
/**
|
||||
* The default, minimum buffer size for instructions.
|
||||
*/
|
||||
private static final int BUFFER_SIZE = 8192;
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
|
||||
|
||||
/**
|
||||
* Sends the given status on the given WebSocket connection and closes the
|
||||
* connection.
|
||||
*
|
||||
* @param outbound The outbound WebSocket connection to close.
|
||||
* @param guac_status The status to send.
|
||||
*/
|
||||
public void closeConnection(WsOutbound outbound, GuacamoleStatus guac_status) {
|
||||
|
||||
try {
|
||||
byte[] message = Integer.toString(guac_status.getGuacamoleStatusCode()).getBytes("UTF-8");
|
||||
outbound.close(guac_status.getWebSocketCode(), ByteBuffer.wrap(message));
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.debug("Unable to close WebSocket tunnel.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String selectSubProtocol(List<String> subProtocols) {
|
||||
|
||||
// Search for expected protocol
|
||||
for (String protocol : subProtocols)
|
||||
if ("guacamole".equals(protocol))
|
||||
return "guacamole";
|
||||
|
||||
// Otherwise, fail
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public StreamInbound createWebSocketInbound(String protocol,
|
||||
HttpServletRequest request) {
|
||||
|
||||
final TunnelRequest tunnelRequest = new HTTPTunnelRequest(request);
|
||||
|
||||
// Return new WebSocket which communicates through tunnel
|
||||
return new StreamInbound() {
|
||||
|
||||
/**
|
||||
* The GuacamoleTunnel associated with the connected WebSocket. If
|
||||
* the WebSocket has not yet been connected, this will be null.
|
||||
*/
|
||||
private GuacamoleTunnel tunnel = null;
|
||||
|
||||
@Override
|
||||
protected void onTextData(Reader reader) throws IOException {
|
||||
|
||||
// Ignore inbound messages if there is no associated tunnel
|
||||
if (tunnel == null)
|
||||
return;
|
||||
|
||||
GuacamoleWriter writer = tunnel.acquireWriter();
|
||||
|
||||
// Write all available data
|
||||
try {
|
||||
|
||||
char[] buffer = new char[BUFFER_SIZE];
|
||||
|
||||
int num_read;
|
||||
while ((num_read = reader.read(buffer)) > 0)
|
||||
writer.write(buffer, 0, num_read);
|
||||
|
||||
}
|
||||
catch (GuacamoleConnectionClosedException e) {
|
||||
logger.debug("Connection to guacd closed.", e);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("WebSocket tunnel write failed.", e);
|
||||
}
|
||||
|
||||
tunnel.releaseWriter();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onOpen(final WsOutbound outbound) {
|
||||
|
||||
try {
|
||||
tunnel = doConnect(tunnelRequest);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
|
||||
logger.debug("Error connecting WebSocket tunnel.", e);
|
||||
closeConnection(outbound, e.getStatus());
|
||||
return;
|
||||
}
|
||||
|
||||
// Do not start connection if tunnel does not exist
|
||||
if (tunnel == null) {
|
||||
closeConnection(outbound, GuacamoleStatus.RESOURCE_NOT_FOUND);
|
||||
return;
|
||||
}
|
||||
|
||||
Thread readThread = new Thread() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
|
||||
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
|
||||
GuacamoleReader reader = tunnel.acquireReader();
|
||||
char[] readMessage;
|
||||
|
||||
try {
|
||||
|
||||
// Send tunnel UUID
|
||||
outbound.writeTextMessage(CharBuffer.wrap(new GuacamoleInstruction(
|
||||
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
|
||||
tunnel.getUUID().toString()
|
||||
).toString()));
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to read
|
||||
while ((readMessage = reader.read()) != null) {
|
||||
|
||||
// Buffer message
|
||||
buffer.append(readMessage);
|
||||
|
||||
// Flush if we expect to wait or buffer is getting full
|
||||
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
|
||||
outbound.writeTextMessage(CharBuffer.wrap(buffer));
|
||||
buffer.setLength(0);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// No more data
|
||||
closeConnection(outbound, GuacamoleStatus.SUCCESS);
|
||||
|
||||
}
|
||||
|
||||
// Catch any thrown guacamole exception and attempt
|
||||
// to pass within the WebSocket connection, logging
|
||||
// each error appropriately.
|
||||
catch (GuacamoleClientException e) {
|
||||
logger.info("WebSocket connection terminated: {}", e.getMessage());
|
||||
logger.debug("WebSocket connection terminated due to client error.", e);
|
||||
closeConnection(outbound, e.getStatus());
|
||||
}
|
||||
catch (GuacamoleConnectionClosedException e) {
|
||||
logger.debug("Connection to guacd closed.", e);
|
||||
closeConnection(outbound, GuacamoleStatus.SUCCESS);
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
|
||||
logger.debug("Internal error during connection to guacd.", e);
|
||||
closeConnection(outbound, e.getStatus());
|
||||
}
|
||||
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.debug("I/O error prevents further reads.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
readThread.start();
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onClose(int i) {
|
||||
try {
|
||||
if (tunnel != null)
|
||||
tunnel.close();
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.debug("Unable to close connection to guacd.", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onBinaryData(InputStream in) throws IOException {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Called whenever the JavaScript Guacamole client makes a connection
|
||||
* request. It it up to the implementor of this function to define what
|
||||
* conditions must be met for a tunnel to be configured and returned as a
|
||||
* result of this connection request (whether some sort of credentials must
|
||||
* be specified, for example).
|
||||
*
|
||||
* @param request
|
||||
* The TunnelRequest associated with the connection request received.
|
||||
* Any parameters specified along with the connection request can be
|
||||
* read from this object.
|
||||
*
|
||||
* @return
|
||||
* A newly constructed GuacamoleTunnel if successful, null otherwise.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If an error occurs while constructing the GuacamoleTunnel, or if the
|
||||
* conditions required for connection are not met.
|
||||
*/
|
||||
protected abstract GuacamoleTunnel doConnect(TunnelRequest request)
|
||||
throws GuacamoleException;
|
||||
|
||||
}
|
||||
|
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* This file has been modified from the original, upstream version to facilitate
|
||||
* integration with OpenUDS.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.tomcat;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.net.GuacamoleTunnel;
|
||||
import org.apache.guacamole.tunnel.TunnelRequest;
|
||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||
|
||||
/**
|
||||
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
|
||||
* rather than HTTP, properly parsing connection IDs included in the connection
|
||||
* request.
|
||||
*/
|
||||
@Singleton
|
||||
public class RestrictedGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
|
||||
|
||||
/**
|
||||
* Service for handling tunnel requests.
|
||||
*/
|
||||
@Inject
|
||||
private TunnelRequestService tunnelRequestService;
|
||||
|
||||
@Override
|
||||
protected GuacamoleTunnel doConnect(TunnelRequest request)
|
||||
throws GuacamoleException {
|
||||
return tunnelRequestService.createTunnel(request);
|
||||
};
|
||||
|
||||
}
|
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
package org.apache.guacamole.tunnel.websocket.tomcat;
|
||||
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import org.apache.guacamole.tunnel.TunnelLoader;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Loads the Jetty 9 WebSocket tunnel implementation.
|
||||
*
|
||||
* @author Michael Jumper
|
||||
*/
|
||||
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
|
||||
try {
|
||||
|
||||
// Attempt to find WebSocket servlet
|
||||
Class.forName("org.apache.guacamole.tunnel.websocket.tomcat.RestrictedGuacamoleWebSocketTunnelServlet");
|
||||
|
||||
// Support found
|
||||
return true;
|
||||
|
||||
}
|
||||
|
||||
// If no such servlet class, this particular WebSocket support
|
||||
// is not present
|
||||
catch (ClassNotFoundException e) {}
|
||||
catch (NoClassDefFoundError e) {}
|
||||
|
||||
// Support not found
|
||||
return false;
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configureServlets() {
|
||||
|
||||
logger.info("Loading Tomcat 7 WebSocket support...");
|
||||
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one
|
||||
* or more contributor license agreements. See the NOTICE file
|
||||
* distributed with this work for additional information
|
||||
* regarding copyright ownership. The ASF licenses this file
|
||||
* to you under the Apache License, Version 2.0 (the
|
||||
* "License"); you may not use this file except in compliance
|
||||
* with the License. You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing,
|
||||
* software distributed under the License is distributed on an
|
||||
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
* KIND, either express or implied. See the License for the
|
||||
* specific language governing permissions and limitations
|
||||
* under the License.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tomcat WebSocket tunnel implementation. The classes here require at least
|
||||
* Tomcat 7.0, and may change significantly as there is no common WebSocket
|
||||
* API for Java yet.
|
||||
*/
|
||||
package org.apache.guacamole.tunnel.websocket.tomcat;
|
@@ -1,148 +0,0 @@
|
||||
package org.openuds.guacamole;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Enumeration;
|
||||
import java.util.Hashtable;
|
||||
import java.util.Properties;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpSession;
|
||||
|
||||
import org.glyptodon.guacamole.GuacamoleException;
|
||||
import org.glyptodon.guacamole.net.GuacamoleSocket;
|
||||
import org.glyptodon.guacamole.net.GuacamoleTunnel;
|
||||
import org.glyptodon.guacamole.net.SimpleGuacamoleTunnel;
|
||||
import org.glyptodon.guacamole.net.InetGuacamoleSocket;
|
||||
import org.glyptodon.guacamole.protocol.ConfiguredGuacamoleSocket;
|
||||
import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
|
||||
import org.glyptodon.guacamole.protocol.GuacamoleConfiguration;
|
||||
import org.glyptodon.guacamole.servlet.GuacamoleHTTPTunnelServlet;
|
||||
import org.glyptodon.guacamole.servlet.GuacamoleSession;
|
||||
|
||||
public class TunnelServlet
|
||||
extends GuacamoleHTTPTunnelServlet {
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
private static final long serialVersionUID = 2010742981126080080L;
|
||||
private static final String UDS_PATH = "/guacamole/";
|
||||
private static final String UDSFILE = "udsfile";
|
||||
private static final String UDS = "uds";
|
||||
|
||||
|
||||
private static Properties config = null;
|
||||
|
||||
private String getConfigValue(String value) throws GuacamoleException {
|
||||
if( config == null ) {
|
||||
try {
|
||||
config = new Properties();
|
||||
config.load(getServletContext().getResourceAsStream("/WEB-INF/tunnel.properties"));
|
||||
if( null != config.getProperty(UDSFILE)) {
|
||||
|
||||
BufferedReader bufferedReader = new BufferedReader(new FileReader(config.getProperty(UDSFILE)));
|
||||
URL u = new URL(bufferedReader.readLine());
|
||||
String uds = u.getProtocol() + "://" + u.getAuthority();
|
||||
bufferedReader.close();
|
||||
|
||||
config.put(UDS, uds);
|
||||
}
|
||||
|
||||
} catch( Exception e ) {
|
||||
throw new GuacamoleException(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
System.out.println("Getting value of " + value + ": " + config.getProperty(value));
|
||||
|
||||
return config.getProperty(value);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected GuacamoleTunnel doConnect(HttpServletRequest request)
|
||||
throws GuacamoleException {
|
||||
|
||||
String data = request.getParameter("data");
|
||||
String width = request.getParameter("width");
|
||||
String height = request.getParameter("height");
|
||||
|
||||
if( data == null || width == null || height == null)
|
||||
throw new GuacamoleException("Can't read required parameters");
|
||||
|
||||
Hashtable<String,String> params = Util.readParameters( getConfigValue(UDS) + UDS_PATH + data);
|
||||
|
||||
if( params == null ) {
|
||||
System.out.println("Invalid credentials");
|
||||
throw new GuacamoleException("Can't access required user credentials");
|
||||
}
|
||||
|
||||
System.out.println("Got parameters from remote server: " + data + ", " + width + "x" + height);
|
||||
|
||||
GuacamoleClientInformation info = new GuacamoleClientInformation();
|
||||
info.setOptimalScreenWidth(Integer.parseInt(width));
|
||||
info.setOptimalScreenHeight(Integer.parseInt(height));
|
||||
|
||||
System.out.println("Optiomal size: " + width + "x" + height);
|
||||
|
||||
// Add audio mimetypes
|
||||
String[] audio_mimetypes = request.getParameterValues("audio");
|
||||
if (audio_mimetypes != null)
|
||||
info.getAudioMimetypes().addAll(Arrays.asList(audio_mimetypes));
|
||||
|
||||
// Add video mimetypes
|
||||
String[] video_mimetypes = request.getParameterValues("video");
|
||||
if (video_mimetypes != null)
|
||||
info.getVideoMimetypes().addAll(Arrays.asList(video_mimetypes));
|
||||
|
||||
// Create our configuration
|
||||
GuacamoleConfiguration config = new GuacamoleConfiguration();
|
||||
config.setProtocol(params.get("protocol"));
|
||||
|
||||
System.out.println("Parsing parameters");
|
||||
|
||||
Enumeration<String> keys = params.keys();
|
||||
while( keys.hasMoreElements() ) {
|
||||
String key = keys.nextElement();
|
||||
if( "protocol".equals(key) )
|
||||
continue;
|
||||
System.out.println("Parameter " + key + ": " + params.get(key));
|
||||
config.setParameter(key, params.get(key));
|
||||
}
|
||||
|
||||
System.out.println("Opening soket");
|
||||
|
||||
// Connect to guacd - everything is hard-coded here.
|
||||
GuacamoleSocket socket = null;
|
||||
try {
|
||||
socket = new ConfiguredGuacamoleSocket(
|
||||
new InetGuacamoleSocket("127.0.0.1", 4822),
|
||||
config, info
|
||||
);
|
||||
} catch( Exception e ) {
|
||||
System.out.print(e.getMessage());
|
||||
System.out.print(e);
|
||||
}
|
||||
|
||||
System.out.println("Initializing socket " + socket.toString());
|
||||
|
||||
// Establish the tunnel using the connected socket
|
||||
GuacamoleTunnel tunnel = new SimpleGuacamoleTunnel(socket);
|
||||
|
||||
System.out.println("Initializing tunnel " + tunnel.toString());
|
||||
|
||||
// Attach tunnel to session
|
||||
HttpSession httpSession = request.getSession(true);
|
||||
GuacamoleSession session = new GuacamoleSession(httpSession);
|
||||
session.attachTunnel(tunnel);
|
||||
|
||||
System.out.println("Returning tunnel");
|
||||
|
||||
// Return pre-attached tunnel
|
||||
return tunnel;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Virtual Cable S.L.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.openuds.guacamole;
|
||||
|
||||
import com.google.inject.servlet.ServletModule;
|
||||
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
|
||||
import org.apache.guacamole.tunnel.TunnelRequestService;
|
||||
import org.openuds.guacamole.config.ConfigurationService;
|
||||
import org.openuds.guacamole.connection.ConnectionService;
|
||||
|
||||
/**
|
||||
* Guice module which binds classes required by the OpenUDS integration of
|
||||
* Apache Guacamole.
|
||||
*/
|
||||
public class UDSModule extends ServletModule {
|
||||
|
||||
@Override
|
||||
protected void configureServlets() {
|
||||
|
||||
// Serve servlets, etc. with Guice
|
||||
bind(GuiceContainer.class);
|
||||
|
||||
// Bind UDS-specific services
|
||||
bind(ConfigurationService.class);
|
||||
bind(ConnectionService.class);
|
||||
bind(TunnelRequestService.class);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Virtual Cable S.L.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.openuds.guacamole;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.google.inject.servlet.GuiceServletContextListener;
|
||||
import org.apache.guacamole.tunnel.TunnelModule;
|
||||
|
||||
/**
|
||||
* ServletContextListener implementation which initializes Guice and services
|
||||
* specific to OpenUDS.
|
||||
*/
|
||||
public class UDSServletContextListener extends GuiceServletContextListener {
|
||||
|
||||
@Override
|
||||
protected Injector getInjector() {
|
||||
|
||||
// Create an injector with OpenUDS- and Guacamole-specific services
|
||||
// properly bound
|
||||
return Guice.createInjector(
|
||||
new UDSModule(),
|
||||
new TunnelModule()
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -1,185 +0,0 @@
|
||||
package org.openuds.guacamole;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.security.cert.X509Certificate;
|
||||
import java.util.Hashtable;
|
||||
|
||||
import javax.net.ssl.HostnameVerifier;
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
import javax.net.ssl.SSLContext;
|
||||
import javax.net.ssl.SSLSession;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
import javax.net.ssl.TrustManager;
|
||||
import javax.net.ssl.X509TrustManager;
|
||||
|
||||
public class Util {
|
||||
|
||||
//
|
||||
public static Hashtable<String,String> readParameters(String url) {
|
||||
//String url = unscramble(data);
|
||||
//String params = getUrl(url);
|
||||
//return parseParams(params);
|
||||
//String params = Credentials.getAndRemove(data);
|
||||
String params = getUrl(url);
|
||||
if( params == null || params.equals("ERROR"))
|
||||
return null;
|
||||
return parseParams(params);
|
||||
}
|
||||
|
||||
public static Hashtable<String,String> parseParams(String params)
|
||||
{
|
||||
Hashtable<String,String> res = new Hashtable<String, String>();
|
||||
String[] parms = params.split("\n");
|
||||
for( int i = 0; i < parms.length; i++) {
|
||||
String[] val = parms[i].split("\t");
|
||||
if( val.length == 1 )
|
||||
res.put(val[0], "");
|
||||
else
|
||||
res.put(val[0], val[1]);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
|
||||
public static boolean download(String baseUrl, String id, String outputFileName)
|
||||
{
|
||||
return Util.download(baseUrl, id, outputFileName, true);
|
||||
}
|
||||
|
||||
public static boolean download(String baseUrl, String id, String outputFileName, boolean ignoreCert)
|
||||
{
|
||||
// SSL Part got from sample at http://code.google.com/p/misc-utils/wiki/JavaHttpsUrl
|
||||
try {
|
||||
final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted( final X509Certificate[] chain, final String authType ) {
|
||||
}
|
||||
@Override
|
||||
public void checkServerTrusted( final X509Certificate[] chain, final String authType ) {
|
||||
}
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
} };
|
||||
|
||||
// Install the all-trusting trust manager
|
||||
final SSLContext sslContext = SSLContext.getInstance( "SSL" );
|
||||
sslContext.init( null, trustAllCerts, new java.security.SecureRandom() );
|
||||
// Create an ssl socket factory with our all-trusting manager
|
||||
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
|
||||
|
||||
java.net.URL u = new java.net.URL(baseUrl + id);
|
||||
java.net.URLConnection uc = u.openConnection();
|
||||
|
||||
System.out.println(baseUrl);
|
||||
System.out.println(uc);
|
||||
|
||||
// If ignoring server certificates, disable ssl certificate checking
|
||||
if( ignoreCert && uc instanceof HttpsURLConnection) {
|
||||
((HttpsURLConnection)uc).setSSLSocketFactory( sslSocketFactory );
|
||||
}
|
||||
|
||||
String contentType = uc.getContentType();
|
||||
int contentLength = uc.getContentLength();
|
||||
if (contentType.startsWith("text/") || contentLength == -1) {
|
||||
throw new IOException("This is not a binary file.");
|
||||
}
|
||||
InputStream raw = uc.getInputStream();
|
||||
InputStream in = new BufferedInputStream(raw);
|
||||
byte[] data = new byte[contentLength];
|
||||
int bytesRead = 0;
|
||||
int offset = 0;
|
||||
while (offset < contentLength) {
|
||||
bytesRead = in.read(data, offset, data.length - offset);
|
||||
if (bytesRead == -1)
|
||||
break;
|
||||
offset += bytesRead;
|
||||
}
|
||||
in.close();
|
||||
|
||||
if (offset != contentLength) {
|
||||
throw new IOException("Only read " + offset + " bytes; Expected " + contentLength + " bytes");
|
||||
}
|
||||
|
||||
java.io.FileOutputStream out = new java.io.FileOutputStream(outputFileName);
|
||||
out.write(data);
|
||||
out.flush();
|
||||
out.close();
|
||||
|
||||
} catch(Exception e) {
|
||||
System.out.println("Unable to download file, already present or network error? " + e.getMessage());
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public static String getUrl(String url) {
|
||||
return Util.getUrl(url, true);
|
||||
}
|
||||
|
||||
public static String getUrl(String url, boolean ignoreCert) {
|
||||
try {
|
||||
// SSL Part got from sample at http://code.google.com/p/misc-utils/wiki/JavaHttpsUrl
|
||||
final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
|
||||
@Override
|
||||
public void checkClientTrusted( final X509Certificate[] chain, final String authType ) {
|
||||
}
|
||||
@Override
|
||||
public void checkServerTrusted( final X509Certificate[] chain, final String authType ) {
|
||||
}
|
||||
@Override
|
||||
public X509Certificate[] getAcceptedIssuers() {
|
||||
return null;
|
||||
}
|
||||
} };
|
||||
|
||||
// Install the all-trusting trust manager
|
||||
final SSLContext sslContext = SSLContext.getInstance( "SSL" );
|
||||
sslContext.init( null, trustAllCerts, new java.security.SecureRandom() );
|
||||
// Create an ssl socket factory with our all-trusting manager
|
||||
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
|
||||
|
||||
java.net.URL u = new java.net.URL(url);
|
||||
java.net.URLConnection uc = u.openConnection();
|
||||
|
||||
System.out.println(url);
|
||||
System.out.println(uc instanceof HttpsURLConnection);
|
||||
|
||||
// If ignoring server certificates, disable ssl certificate checking and hostname checking
|
||||
if( ignoreCert && uc instanceof HttpsURLConnection) {
|
||||
((HttpsURLConnection)uc).setSSLSocketFactory( sslSocketFactory );
|
||||
((HttpsURLConnection)uc).setHostnameVerifier(
|
||||
new HostnameVerifier() {
|
||||
@Override
|
||||
public boolean verify(String arg0, SSLSession arg1) {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
BufferedReader in = new BufferedReader(new InputStreamReader(uc.getInputStream()));
|
||||
StringBuilder data = new StringBuilder();
|
||||
|
||||
String inputLine;
|
||||
while ((inputLine = in.readLine()) != null) {
|
||||
data.append(inputLine);
|
||||
data.append("\n");
|
||||
}
|
||||
|
||||
in.close();
|
||||
return data.toString();
|
||||
|
||||
} catch(Exception e) {
|
||||
System.out.println("Unable to get url. Network error? " + e.getMessage());
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,246 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Virtual Cable S.L.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.openuds.guacamole.config;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.util.Properties;
|
||||
import javax.servlet.ServletContext;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleServerException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Service that provides access to configuration information stored within
|
||||
* OpenUDS' tunnel.properties file.
|
||||
*/
|
||||
@Singleton
|
||||
public class ConfigurationService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(ConfigurationService.class);
|
||||
|
||||
/**
|
||||
* The name of the property within tunnel.properties which defines the file
|
||||
* whose content dictates the base URL of the service providing connection
|
||||
* configuration information.
|
||||
*/
|
||||
private static final String UDSFILE_PROPERTY = "udsfile";
|
||||
|
||||
/**
|
||||
* The path beneath the OpenUDS service base URI (scheme + hostname) at
|
||||
* which the connection configuration service can be found. Currently, this
|
||||
* is hard-coded as "/guacamole/".
|
||||
*/
|
||||
private static final String UDS_CONNECTION_PATH = "/guacamole/";
|
||||
|
||||
/**
|
||||
* The base URI (scheme + hostname) where OpenUDS is being served.
|
||||
*/
|
||||
private final URI udsBaseURI;
|
||||
|
||||
/**
|
||||
* Parses the contents of the given file, reading the URI of the OpenUDS
|
||||
* service contained therein. The file is expected to define this URI on
|
||||
* the first line, and only the first line is read.
|
||||
*
|
||||
* @param udsFile
|
||||
* The file from which the URI of the OpenUDS service should be read.
|
||||
*
|
||||
* @return
|
||||
* The URI of the OpenUDS service.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the file could not be opened or read for any reason, or if the
|
||||
* line read from the file is not a valid URI.
|
||||
*/
|
||||
private URI readServiceURI(String udsFile) throws GuacamoleException {
|
||||
|
||||
// Open UDS file
|
||||
BufferedReader input;
|
||||
try {
|
||||
input = new BufferedReader(new FileReader(udsFile));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new GuacamoleServerException("Failed to open UDS file.", e);
|
||||
}
|
||||
|
||||
// Parse the first line (and only the first line) assuming it contains
|
||||
// the URL of the OpenUDS service
|
||||
try {
|
||||
return new URI(input.readLine());
|
||||
}
|
||||
|
||||
// Rethrow general failure to read from the file
|
||||
catch (IOException e) {
|
||||
throw new GuacamoleServerException("Failed to read UDS service URI from file.", e);
|
||||
}
|
||||
|
||||
// Rethrow failure to parse the URL
|
||||
catch (URISyntaxException e) {
|
||||
throw new GuacamoleServerException("Failed to parse UDS service URI from file.", e);
|
||||
}
|
||||
|
||||
// Always close the file
|
||||
finally {
|
||||
|
||||
try {
|
||||
input.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.warn("Closure of OpenUDS file failed. Resource may leak.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Given an arbitrary URI, returns a new URI which contains only the scheme
|
||||
* and host. The path, fragment, etc. of the given URI, if any, are
|
||||
* discarded.
|
||||
*
|
||||
* @param uri
|
||||
* An arbitrary URI from which a base URI should be derived.
|
||||
*
|
||||
* @return
|
||||
* A new URI containing only the scheme and host of the provided URI.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the new URI could not be generated because the result is not a
|
||||
* valid URI.
|
||||
*/
|
||||
private URI getBaseURI(URI uri) throws GuacamoleException {
|
||||
|
||||
// Build base URI from only the scheme and host of the given URI
|
||||
try {
|
||||
return new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(),
|
||||
null, null, null);
|
||||
}
|
||||
catch (URISyntaxException e) {
|
||||
throw new GuacamoleServerException("Failed to derive base URI.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new ConfigurationService which provides access to the
|
||||
* configuration information stored within the "/WEB-INF/tunnel.properties"
|
||||
* file in the classpath. This file will be parsed immediately, but any
|
||||
* resulting errors will simply be logged. If configuration information
|
||||
* cannot be read, attempts to retrieve this information later through calls
|
||||
* to the getters of this service will fail with appropriate exceptions.
|
||||
*
|
||||
* @param context
|
||||
* The ServletContext associated with the servlet container which is
|
||||
* serving this web application.
|
||||
*/
|
||||
@Inject
|
||||
public ConfigurationService(ServletContext context) {
|
||||
|
||||
// Read tunnel.properties
|
||||
Properties config = new Properties();
|
||||
try {
|
||||
config.load(context.getResourceAsStream("/WEB-INF/tunnel.properties"));
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.error("Unable to read tunnel.properties.", e);
|
||||
}
|
||||
|
||||
// Parse URI from the UDS file (if defined)
|
||||
URI parsedURI = null;
|
||||
String udsFile = config.getProperty(UDSFILE_PROPERTY);
|
||||
if (udsFile != null) {
|
||||
|
||||
// Attempt to parse base URI from the UDS file, logging any failures
|
||||
try {
|
||||
parsedURI = getBaseURI(readServiceURI(udsFile));
|
||||
}
|
||||
catch (GuacamoleException e) {
|
||||
logger.error("OpenUDS service URI could not be parsed. This "
|
||||
+ "web application WILL NOT FUNCTION.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// If no UDS file is defined, web application startup has failed
|
||||
else
|
||||
logger.error("Property \"{}\" not found within tunnel.properties. "
|
||||
+ "This web application WILL NOT FUNCTION.", UDSFILE_PROPERTY);
|
||||
|
||||
// Assign the parsed URI, which may be null
|
||||
udsBaseURI = parsedURI;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the base URI of the OpenUDS service. All services providing data
|
||||
* to this Guacamole integration are hosted beneath this base URI.
|
||||
*
|
||||
* @return
|
||||
* The base URI of the OpenUDS service.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the base URI of the OpenUDS service is not defined because the
|
||||
* tunnel.properties file could not be parsed when the web application
|
||||
* started.
|
||||
*/
|
||||
public URI getUDSBaseURI() throws GuacamoleException {
|
||||
|
||||
// Explicitly fail if the configuration was not successfully read
|
||||
if (udsBaseURI == null)
|
||||
throw new GuacamoleServerException("The UDS base URI is not defined.");
|
||||
|
||||
return udsBaseURI;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the path beneath the OpenUDS base URI at which the connection
|
||||
* configuration service can be found. This service is expected to respond
|
||||
* to HTTP GET requests, returning the configuration of requested
|
||||
* connections.
|
||||
*
|
||||
* @return
|
||||
* The path beneath the OpenUDS base URI at which the connection
|
||||
* configuration service can be found.
|
||||
*/
|
||||
public String getUDSConnectionPath() {
|
||||
return UDS_CONNECTION_PATH;
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Virtual Cable S.L.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes used to retrieve OpenUDS-specific configuration information.
|
||||
*/
|
||||
package org.openuds.guacamole.config;
|
@@ -0,0 +1,200 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Virtual Cable S.L.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
package org.openuds.guacamole.connection;
|
||||
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Singleton;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.URI;
|
||||
import java.net.URLConnection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.ws.rs.core.UriBuilder;
|
||||
import org.apache.guacamole.GuacamoleException;
|
||||
import org.apache.guacamole.GuacamoleServerException;
|
||||
import org.apache.guacamole.protocol.GuacamoleConfiguration;
|
||||
import org.openuds.guacamole.config.ConfigurationService;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
* Service which communicates with the remote OpenUDS connection service,
|
||||
* providing access to the underlying connection configuration.
|
||||
*/
|
||||
@Singleton
|
||||
public class ConnectionService {
|
||||
|
||||
/**
|
||||
* Logger for this class.
|
||||
*/
|
||||
private final Logger logger = LoggerFactory.getLogger(ConnectionService.class);
|
||||
|
||||
/**
|
||||
* The name of the parameter returned by the OpenUDS connection
|
||||
* configuration service which will contain the protocol that Guacamole
|
||||
* should use to initiate the remote desktop connection.
|
||||
*/
|
||||
private static final String PROTOCOL_PARAMETER = "protocol";
|
||||
|
||||
/**
|
||||
* Service for retrieving configuration information.
|
||||
*/
|
||||
@Inject
|
||||
private ConfigurationService configService;
|
||||
|
||||
/**
|
||||
* Makes an HTTP GET request to the OpenUDS service running at the given
|
||||
* URI, parsing the response into connection configuration data. The
|
||||
* response MUST be simple text, one line per connection parameter, with the
|
||||
* name of the connection parameter separated from the corresponding value
|
||||
* by a tab character. If the OpenUDS service encounters an error, it is
|
||||
* expected to return the single word "ERROR" on one line. Lines which do
|
||||
* not match these expectations will be skipped.
|
||||
*
|
||||
* @param uri
|
||||
* The URI of the OpenUDS service to which the HTTP GET request should
|
||||
* be made.
|
||||
*
|
||||
* @return
|
||||
* A map of all parameter name/value pairs returned by the OpenUDS
|
||||
* service.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the OpenUDS service returns an error, or the response from the
|
||||
* service cannot be read.
|
||||
*/
|
||||
private Map<String, String> readConnectionConfiguration(URI uri)
|
||||
throws GuacamoleException {
|
||||
|
||||
BufferedReader response;
|
||||
|
||||
// Connect to OpenUDS
|
||||
try {
|
||||
URLConnection connection = uri.toURL().openConnection();
|
||||
response = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
}
|
||||
catch (IOException e) {
|
||||
throw new GuacamoleServerException("Unable to open connection to OpenUDS service.", e);
|
||||
}
|
||||
|
||||
Map<String, String> parameters = new HashMap<String, String>();
|
||||
|
||||
// Read and parse each line of the response
|
||||
try {
|
||||
|
||||
String inputLine;
|
||||
while ((inputLine = response.readLine()) != null) {
|
||||
|
||||
// Abort upon error
|
||||
if (inputLine.equals("ERROR"))
|
||||
throw new GuacamoleServerException("OpenUDS service returned an error.");
|
||||
|
||||
// Determine separation between each line's key and value
|
||||
int tab = inputLine.indexOf('\t');
|
||||
if (tab == -1)
|
||||
continue;
|
||||
|
||||
// Add key/value pair from either side of the tab
|
||||
parameters.put(
|
||||
inputLine.substring(0, tab),
|
||||
inputLine.substring(tab + 1)
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Rethrow any error which occurs during reading
|
||||
catch (IOException e) {
|
||||
throw new GuacamoleServerException("Failed to read response from OpenUDS service.", e);
|
||||
}
|
||||
|
||||
// Always close the stream
|
||||
finally {
|
||||
|
||||
try {
|
||||
response.close();
|
||||
}
|
||||
catch (IOException e) {
|
||||
logger.warn("Closure of connection to OpenUDS failed. Resource may leak.", e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// Parameters have been successfully parsed
|
||||
return parameters;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Queries OpenUDS for the connection configuration for the connection
|
||||
* associated with the given data. This data is an opaque value provided
|
||||
* via the "data" parameter to the Guacamole tunnel.
|
||||
*
|
||||
* @param data
|
||||
* The OpenUDS-specific data which defines the connection whose
|
||||
* configuration should be retrieved.
|
||||
*
|
||||
* @return
|
||||
* The configuration of the connection associated with the provided
|
||||
* OpenUDS-specific data.
|
||||
*
|
||||
* @throws GuacamoleException
|
||||
* If the connection configuration could not be retrieved from OpenUDS,
|
||||
* of the response from OpenUDS was missing required information.
|
||||
*/
|
||||
public GuacamoleConfiguration getConnectionConfiguration(String data)
|
||||
throws GuacamoleException {
|
||||
|
||||
// Build URI of remote service from the base URI and given data
|
||||
URI serviceURI = UriBuilder.fromUri(configService.getUDSBaseURI())
|
||||
.path(configService.getUDSConnectionPath())
|
||||
.path(data)
|
||||
.build();
|
||||
|
||||
// Pull connection configuration from remote service
|
||||
Map<String, String> params = readConnectionConfiguration(serviceURI);
|
||||
|
||||
// Pull the protocol from the parameters
|
||||
String protocol = params.remove(PROTOCOL_PARAMETER);
|
||||
if (protocol == null)
|
||||
throw new GuacamoleServerException("Protocol missing from OpenUDS response.");
|
||||
|
||||
// Create our configuration
|
||||
GuacamoleConfiguration config = new GuacamoleConfiguration();
|
||||
config.setProtocol(protocol);
|
||||
config.setParameters(params);
|
||||
|
||||
return config;
|
||||
|
||||
}
|
||||
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Virtual Cable S.L.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes used to communicate with OpenUDS' connection configuration web
|
||||
* service.
|
||||
*/
|
||||
package org.openuds.guacamole.connection;
|
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2015 Virtual Cable S.L.
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without modification,
|
||||
* are permitted provided that the following conditions are met:
|
||||
*
|
||||
* * Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright notice,
|
||||
* this list of conditions and the following disclaimer in the documentation
|
||||
* and/or other materials provided with the distribution.
|
||||
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Classes associated with the OpenUDS integration of Apache Guacamole.
|
||||
*/
|
||||
package org.openuds.guacamole;
|
@@ -1,5 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!--
|
||||
Copyright (c) 2015 Virtual Cable S.L.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
* Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software
|
||||
without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
-->
|
||||
<web-app version="2.5"
|
||||
xmlns="http://java.sun.com/xml/ns/javaee"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
@@ -11,31 +37,19 @@
|
||||
<welcome-file>index.xhtml</welcome-file>
|
||||
</welcome-file-list>
|
||||
|
||||
<!-- Guacamole Tunnel Servlet -->
|
||||
<servlet>
|
||||
<description>Tunnel Servlet</description>
|
||||
<servlet-name>Tunnel</servlet-name>
|
||||
<servlet-class>
|
||||
org.openuds.guacamole.TunnelServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
<!-- Initialization ServletContextListener -->
|
||||
<listener>
|
||||
<listener-class>org.openuds.guacamole.UDSServletContextListener</listener-class>
|
||||
</listener>
|
||||
|
||||
<servlet>
|
||||
<description>Credentials Servlet</description>
|
||||
<servlet-name>Credentials</servlet-name>
|
||||
<servlet-class>
|
||||
org.openuds.guacamole.CredentialsServlet
|
||||
</servlet-class>
|
||||
</servlet>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Tunnel</servlet-name>
|
||||
<url-pattern>/tunnel</url-pattern>
|
||||
</servlet-mapping>
|
||||
|
||||
<servlet-mapping>
|
||||
<servlet-name>Credentials</servlet-name>
|
||||
<url-pattern>/creds</url-pattern>
|
||||
</servlet-mapping>
|
||||
<!-- Guice -->
|
||||
<filter>
|
||||
<filter-name>guiceFilter</filter-name>
|
||||
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
|
||||
</filter>
|
||||
<filter-mapping>
|
||||
<filter-name>guiceFilter</filter-name>
|
||||
<url-pattern>/*</url-pattern>
|
||||
</filter-mapping>
|
||||
|
||||
</web-app>
|
||||
|
@@ -1,5 +1,8 @@
|
||||
This portion of UDS (HTML5 tunnel) is based on guacamole. As such, the code used for creating this is fully GPL compliant.
|
||||
This portion of UDS (HTML5 tunnel) is based on Apache Guacamole. The source
|
||||
code that produced this war file can be obtained from:
|
||||
|
||||
To obtain the code that produced this war file, you can do it from http://openuds.org.
|
||||
http://openuds.org/
|
||||
|
||||
Guacamole source code is accesible from http://guac-dev.org/
|
||||
The Apache Guacamole source code is available from:
|
||||
|
||||
http://guacamole.incubator.apache.org/
|
||||
|
@@ -964,14 +964,14 @@ GuacUI.Client.connect = function() {
|
||||
var tunnel;
|
||||
|
||||
// If WebSocket available, try to use it.
|
||||
/*if (window.WebSocket)
|
||||
if (window.WebSocket)
|
||||
tunnel = new Guacamole.ChainedTunnel(
|
||||
new Guacamole.WebSocketTunnel("websocket-tunnel"),
|
||||
new Guacamole.HTTPTunnel("tunnel")
|
||||
)
|
||||
);
|
||||
|
||||
// If no WebSocket, then use HTTP.
|
||||
else*/
|
||||
else
|
||||
tunnel = new Guacamole.HTTPTunnel("tunnel");
|
||||
|
||||
// Instantiate client
|
||||
@@ -1005,18 +1005,18 @@ GuacUI.Client.connect = function() {
|
||||
|
||||
var connect_string =
|
||||
queryArr.join('&')
|
||||
+ "&width=" + Math.floor(optimal_width)
|
||||
+ "&height=" + Math.floor(optimal_height)
|
||||
+ "&dpi=" + Math.floor(optimal_dpi);
|
||||
+ "&GUAC_WIDTH=" + Math.floor(optimal_width)
|
||||
+ "&GUAC_HEIGHT=" + Math.floor(optimal_height)
|
||||
+ "&GUAC_DPI=" + Math.floor(optimal_dpi);
|
||||
|
||||
// Add audio mimetypes to connect_string
|
||||
GuacUI.Audio.supported.forEach(function(mimetype) {
|
||||
connect_string += "&audio=" + encodeURIComponent(mimetype);
|
||||
connect_string += "&GUAC_AUDIO=" + encodeURIComponent(mimetype);
|
||||
});
|
||||
|
||||
// Add video mimetypes to connect_string
|
||||
GuacUI.Video.supported.forEach(function(mimetype) {
|
||||
connect_string += "&video=" + encodeURIComponent(mimetype);
|
||||
connect_string += "&GUAC_VIDEO=" + encodeURIComponent(mimetype);
|
||||
});
|
||||
|
||||
// Show connection errors from tunnel
|
||||
|
@@ -9,6 +9,6 @@ ovirt-engine-sdk-python
|
||||
pycurl
|
||||
pyOpenSSL
|
||||
python-ldap
|
||||
six
|
||||
MySQL-python
|
||||
reportlab
|
||||
reportlab
|
||||
bitarray
|
||||
|
@@ -122,8 +122,12 @@ CACHES = {
|
||||
'CULL_FREQUENCY': 3, # 0 = Entire cache will be erased once MAX_ENTRIES is reached, this is faster on DB. if other value, will remove 1/this number items fromm cache
|
||||
}
|
||||
},
|
||||
# 'memory': {
|
||||
# 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
# }
|
||||
'memory': {
|
||||
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
|
||||
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
|
||||
'LOCATION': '127.0.0.1:11211',
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,18 +149,17 @@ TEMPLATE_LOADERS = (
|
||||
)
|
||||
|
||||
# Own context processors plus django's own
|
||||
TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + (
|
||||
TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + [
|
||||
'uds.core.util.Config.context_processor',
|
||||
'uds.core.util.html.context',
|
||||
'django.core.context_processors.request',
|
||||
)
|
||||
]
|
||||
|
||||
MIDDLEWARE_CLASSES = (
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'uds.core.util.request.GlobalRequestMiddleware',
|
||||
'uds.core.util.middleware.XUACompatibleMiddleware',
|
||||
@@ -203,6 +206,12 @@ COMPRESS_PRECOMPILERS = (
|
||||
)
|
||||
if DEBUG:
|
||||
COMPRESS_DEBUG_TOGGLE = 'debug'
|
||||
#
|
||||
# Enable this if you need to allow round robin load balancing of web server
|
||||
# This is so because we need to share the files between servers
|
||||
# Another options is put /var/server/static on a shared nfs forder for all servers
|
||||
#
|
||||
# COMPRESS_STORAGE = 'uds.core.util.FileStorage.CompressorFileStorage'
|
||||
|
||||
# See http://docs.djangoproject.com/en/dev/topics/logging for
|
||||
# more details on how to customize your logging configuration.
|
||||
@@ -245,7 +254,7 @@ LOGGING = {
|
||||
'handlers': {
|
||||
'null': {
|
||||
'level':'DEBUG',
|
||||
'class':'django.utils.log.NullHandler',
|
||||
'class':'logging.NullHandler',
|
||||
},
|
||||
|
||||
'file':{
|
||||
|
@@ -111,7 +111,7 @@ class Actor(Handler):
|
||||
logger.debug('Getting User services from ids: {}'.format(self._params.get('id')))
|
||||
|
||||
try:
|
||||
clientIds = self._params.get('id').split(',')[:5]
|
||||
clientIds = [i.upper() for i in self._params.get('id').split(',')[:5]]
|
||||
except Exception:
|
||||
raise RequestError('Invalid request: (no id found)')
|
||||
|
||||
@@ -124,7 +124,7 @@ class Actor(Handler):
|
||||
def getTicket(self):
|
||||
'''
|
||||
Processes get requests in order to obtain a ticket content
|
||||
GET /rest/ticket/[ticketId]
|
||||
GET /rest/actor/ticket/[ticketId]
|
||||
'''
|
||||
logger.debug("Ticket args for GET: {0}".format(self._args))
|
||||
|
||||
|
@@ -52,7 +52,7 @@ class Authenticators(ModelHandler):
|
||||
# Custom get method "search" that requires authenticator id
|
||||
custom_methods = [('search', True)]
|
||||
detail = {'users': Users, 'groups': Groups}
|
||||
save_fields = ['name', 'comments', 'priority', 'small_name']
|
||||
save_fields = ['name', 'comments', 'tags', 'priority', 'small_name']
|
||||
|
||||
table_title = _('Current authenticators')
|
||||
table_fields = [
|
||||
@@ -60,7 +60,8 @@ class Authenticators(ModelHandler):
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '5em'}},
|
||||
{'small_name': {'title': _('Tag')}},
|
||||
{'users_count': {'title': _('Users'), 'type': 'numeric', 'width': '5em'}}
|
||||
{'users_count': {'title': _('Users'), 'type': 'numeric', 'width': '5em'}},
|
||||
{'tags': {'title': _('tags'), 'visible': False}},
|
||||
]
|
||||
|
||||
def enum_types(self):
|
||||
@@ -80,7 +81,7 @@ class Authenticators(ModelHandler):
|
||||
|
||||
def getGui(self, type_):
|
||||
try:
|
||||
return self.addDefaultFields(auths.factory().lookup(type_).guiDescription(), ['name', 'comments', 'priority', 'small_name'])
|
||||
return self.addDefaultFields(auths.factory().lookup(type_).guiDescription(), ['name', 'comments', 'tags', 'priority', 'small_name'])
|
||||
except:
|
||||
raise NotFound('type not found')
|
||||
|
||||
@@ -89,6 +90,7 @@ class Authenticators(ModelHandler):
|
||||
return {
|
||||
'id': auth.uuid,
|
||||
'name': auth.name,
|
||||
'tags': [tag.tag for tag in auth.tags.all()],
|
||||
'comments': auth.comments,
|
||||
'priority': auth.priority,
|
||||
'small_name': auth.small_name,
|
||||
|
152
server/src/uds/REST/methods/calendarrules.py
Normal file
152
server/src/uds/REST/methods/calendarrules.py
Normal file
@@ -0,0 +1,152 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
from uds.models.CalendarRule import freqs, CalendarRule
|
||||
from uds.models.Util import getSqlDatetime
|
||||
|
||||
from uds.core.util import log
|
||||
from uds.core.util import permissions
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.core.Environment import Environment
|
||||
from uds.REST.model import DetailHandler
|
||||
from uds.REST import NotFound, ResponseError, RequestError
|
||||
from django.db import IntegrityError
|
||||
|
||||
import six
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CalendarRules(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
'''
|
||||
Detail handler for Services, whose parent is a Provider
|
||||
'''
|
||||
|
||||
@staticmethod
|
||||
def ruleToDict(item, perm):
|
||||
'''
|
||||
Convert a calRule db item to a dict for a rest response
|
||||
:param item: Service item (db)
|
||||
:param full: If full is requested, add "extra" fields to complete information
|
||||
'''
|
||||
retVal = {
|
||||
'id': item.uuid,
|
||||
'name': item.name,
|
||||
'comments': item.comments,
|
||||
'start': item.start,
|
||||
'end': item.end,
|
||||
'frequency': item.frequency,
|
||||
'interval': item.interval,
|
||||
'duration': item.duration,
|
||||
'duration_unit': item.duration_unit,
|
||||
'permission': perm
|
||||
}
|
||||
|
||||
return retVal
|
||||
|
||||
def getItems(self, parent, item):
|
||||
# Check what kind of access do we have to parent provider
|
||||
perm = permissions.getEffectivePermission(self._user, parent)
|
||||
try:
|
||||
if item is None:
|
||||
return [CalendarRules.ruleToDict(k, perm) for k in parent.rules.all()]
|
||||
else:
|
||||
k = parent.rules.get(uuid=processUuid(item))
|
||||
return CalendarRules.ruleToDict(k, perm)
|
||||
except Exception:
|
||||
logger.exception('itemId {}'.format(item))
|
||||
self.invalidItemException()
|
||||
|
||||
def getFields(self, parent):
|
||||
return [
|
||||
{'name': {'title': _('Rule name')}},
|
||||
{'start': {'title': _('Starts'), 'type': 'datetime'}},
|
||||
{'end': {'title': _('Ends'), 'type': 'date'}},
|
||||
{'frequency': {'title': _('Repeats'), 'type': 'dict', 'dict': dict((v[0], six.text_type(v[1])) for v in freqs) }},
|
||||
{'interval': {'title': _('Every'), 'type': 'callback'}},
|
||||
{'duration': {'title': _('Duration'), 'type': 'callback'}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
]
|
||||
|
||||
def saveItem(self, parent, item):
|
||||
# Extract item db fields
|
||||
# We need this fields for all
|
||||
logger.debug('Saving rule {0} / {1}'.format(parent, item))
|
||||
fields = self.readFieldsFromParams(['name', 'comments', 'frequency', 'start', 'end', 'interval', 'duration', 'duration_unit'])
|
||||
# Convert timestamps to datetimes
|
||||
fields['start'] = datetime.datetime.fromtimestamp(fields['start'])
|
||||
if fields['end'] != None:
|
||||
fields['end'] = datetime.datetime.fromtimestamp(fields['end'])
|
||||
|
||||
calRule = None
|
||||
try:
|
||||
if item is None: # Create new
|
||||
calRule = parent.rules.create(**fields)
|
||||
else:
|
||||
calRule = parent.rules.get(uuid=processUuid(item))
|
||||
calRule.__dict__.update(fields)
|
||||
calRule.save()
|
||||
except CalendarRule.DoesNotExist:
|
||||
self.invalidItemException()
|
||||
except IntegrityError: # Duplicate key probably
|
||||
raise RequestError(_('Element already exists (duplicate key error)'))
|
||||
except Exception as e:
|
||||
logger.exception('Saving calendar')
|
||||
raise RequestError('incorrect invocation to PUT: {0}'.format(e))
|
||||
|
||||
return self.getItems(parent, calRule.uuid)
|
||||
|
||||
def deleteItem(self, parent, item):
|
||||
logger.debug('Deleting rule {} from {}'.format(item, parent))
|
||||
try:
|
||||
calRule = parent.rules.get(uuid=processUuid(item))
|
||||
calRule.calendar.modified = getSqlDatetime()
|
||||
calRule.calendar.save()
|
||||
calRule.delete()
|
||||
except Exception:
|
||||
logger.exception('Exception')
|
||||
self.invalidItemException()
|
||||
|
||||
return 'deleted'
|
||||
|
||||
def getTitle(self, parent):
|
||||
try:
|
||||
return _('Rules of {0}').format(parent.name)
|
||||
except Exception:
|
||||
return _('Current rules')
|
77
server/src/uds/REST/methods/calendars.py
Normal file
77
server/src/uds/REST/methods/calendars.py
Normal file
@@ -0,0 +1,77 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@itemor: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _, ugettext
|
||||
from uds.models import Calendar
|
||||
from uds.core.util import permissions
|
||||
|
||||
from uds.REST.model import ModelHandler
|
||||
from .calendarrules import CalendarRules
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Enclosed methods under /item path
|
||||
|
||||
|
||||
class Calendars(ModelHandler):
|
||||
'''
|
||||
Processes REST requests about calendars
|
||||
'''
|
||||
model = Calendar
|
||||
detail = {'rules': CalendarRules}
|
||||
|
||||
save_fields = ['name', 'comments', 'tags']
|
||||
|
||||
table_title = _('Calendars')
|
||||
table_fields = [
|
||||
{'name': {'title': _('Name'), 'visible': True, 'type': 'icon', 'icon': 'fa fa-calendar text-success'}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'modified': {'title': _('Modified'), 'type': 'datetime'}},
|
||||
{'tags': {'title': _('tags'), 'visible': False}},
|
||||
]
|
||||
|
||||
def item_as_dict(self, calendar):
|
||||
return {
|
||||
'id': calendar.uuid,
|
||||
'name': calendar.name,
|
||||
'tags': [tag.tag for tag in calendar.tags.all()],
|
||||
'comments': calendar.comments,
|
||||
'modified': calendar.modified,
|
||||
'permission': permissions.getEffectivePermission(self._user, calendar)
|
||||
}
|
||||
|
||||
def getGui(self, type_):
|
||||
return self.addDefaultFields([], ['name', 'comments', 'tags'])
|
@@ -51,8 +51,8 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
CLIENT_VERSION = '1.9.1'
|
||||
REQUIRED_CLIENT_VERSION = '1.9.0'
|
||||
CLIENT_VERSION = UDS_VERSION
|
||||
REQUIRED_CLIENT_VERSION = '2.0.0'
|
||||
|
||||
|
||||
# Enclosed methods under /actor path
|
||||
|
@@ -60,7 +60,8 @@ class Config(Handler):
|
||||
'value': cfg.get(),
|
||||
'crypt': cfg.isCrypted(),
|
||||
'longText': cfg.isLongText(),
|
||||
'type': cfg.getType()
|
||||
'type': cfg.getType(),
|
||||
'params': cfg.getParams()
|
||||
}
|
||||
logger.debug('Configuration: {0}'.format(res))
|
||||
return res
|
||||
|
@@ -49,7 +49,7 @@ class Images(ModelHandler):
|
||||
'''
|
||||
Handles the gallery REST interface
|
||||
'''
|
||||
needs_admin = True
|
||||
# needs_admin = True
|
||||
|
||||
path = 'gallery'
|
||||
model = Image
|
||||
@@ -57,9 +57,9 @@ class Images(ModelHandler):
|
||||
|
||||
table_title = _('Image Gallery')
|
||||
table_fields = [
|
||||
{'thumb': {'title': _('Image'), 'visible': True, 'type': 'image', 'width': '96px' }},
|
||||
{'name': {'title': _('Name')}},
|
||||
{'size': {'title': _('Size')}},
|
||||
{'thumb': {'title': _('Image'), 'visible': True, 'type': 'image'}},
|
||||
]
|
||||
|
||||
def beforeSave(self, fields):
|
||||
@@ -78,7 +78,7 @@ class Images(ModelHandler):
|
||||
'value': '',
|
||||
'label': ugettext('Image'),
|
||||
'tooltip': ugettext('Image object'),
|
||||
'type': gui.InputField.IMAGE_TYPE,
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 100, # At end
|
||||
}
|
||||
)
|
||||
|
@@ -53,13 +53,14 @@ class Networks(ModelHandler):
|
||||
Implements specific handling for network related requests using GUI
|
||||
'''
|
||||
model = Network
|
||||
save_fields = ['name', 'net_string']
|
||||
save_fields = ['name', 'net_string', 'tags']
|
||||
|
||||
table_title = _('Current Networks')
|
||||
table_fields = [
|
||||
{'name': {'title': _('Name'), 'visible': True, 'type': 'icon', 'icon': 'fa fa-globe text-success'}},
|
||||
{'net_string': {'title': _('Range')}},
|
||||
{'networks_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}}
|
||||
{'networks_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}},
|
||||
{'tags': {'title': _('tags'), 'visible': False}},
|
||||
]
|
||||
|
||||
def beforeSave(self, fields):
|
||||
@@ -74,7 +75,7 @@ class Networks(ModelHandler):
|
||||
|
||||
def getGui(self, type_):
|
||||
return self.addField(
|
||||
self.addDefaultFields([], ['name']), {
|
||||
self.addDefaultFields([], ['name', 'tags']), {
|
||||
'name': 'net_string',
|
||||
'value': '',
|
||||
'label': ugettext('Network range'),
|
||||
@@ -88,6 +89,7 @@ class Networks(ModelHandler):
|
||||
return {
|
||||
'id': item.uuid,
|
||||
'name': item.name,
|
||||
'tags': [tag.tag for tag in item.tags.all()],
|
||||
'net_string': item.net_string,
|
||||
'networks_count': item.transports.count(),
|
||||
'permission': permissions.getEffectivePermission(self._user, item)
|
||||
|
@@ -50,13 +50,14 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class OsManagers(ModelHandler):
|
||||
model = OSManager
|
||||
save_fields = ['name', 'comments']
|
||||
save_fields = ['name', 'comments', 'tags']
|
||||
|
||||
table_title = _('Current OS Managers')
|
||||
table_fields = [
|
||||
{'name': {'title': _('Name'), 'visible': True, 'type': 'iconType'}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}}
|
||||
{'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}},
|
||||
{'tags': {'title': _('tags'), 'visible': False}},
|
||||
]
|
||||
|
||||
def osmToDict(self, osm):
|
||||
@@ -64,6 +65,7 @@ class OsManagers(ModelHandler):
|
||||
return {
|
||||
'id': osm.uuid,
|
||||
'name': osm.name,
|
||||
'tags': [tag.tag for tag in osm.tags.all()],
|
||||
'deployed_count': osm.deployedServices.count(),
|
||||
'type': type_.type(),
|
||||
'servicesTypes': type_.servicesType,
|
||||
@@ -88,6 +90,6 @@ class OsManagers(ModelHandler):
|
||||
# Gui related
|
||||
def getGui(self, type_):
|
||||
try:
|
||||
return self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments'])
|
||||
return self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments', 'tags'])
|
||||
except:
|
||||
raise NotFound('type not found')
|
||||
|
@@ -38,7 +38,7 @@ from uds.REST import Handler
|
||||
from uds.REST import RequestError
|
||||
from uds.core.util import permissions
|
||||
|
||||
from uds.models import Provider, Service, Authenticator, OSManager, Transport, Network, ServicePool
|
||||
from uds.models import Provider, Service, Authenticator, OSManager, Transport, Network, ServicePool, Calendar
|
||||
from uds.models import User, Group
|
||||
|
||||
import six
|
||||
@@ -64,7 +64,8 @@ class Permissions(Handler):
|
||||
'osmanagers': OSManager,
|
||||
'transports': Transport,
|
||||
'networks': Network,
|
||||
'servicespools': ServicePool
|
||||
'servicespools': ServicePool,
|
||||
'calendars': Calendar
|
||||
}.get(arg, None)
|
||||
|
||||
if cls is None:
|
||||
|
@@ -34,11 +34,12 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
from uds.models import Provider, Service, UserService
|
||||
from uds.REST.methods.services import Services as DetailServices
|
||||
from uds.core import services
|
||||
from uds.core.util import permissions
|
||||
from uds.core.util.model import processUuid
|
||||
|
||||
from .services import Services as DetailServices
|
||||
|
||||
from uds.REST import NotFound, RequestError
|
||||
from uds.REST.model import ModelHandler
|
||||
|
||||
@@ -55,7 +56,7 @@ class Providers(ModelHandler):
|
||||
detail = {'services': DetailServices}
|
||||
custom_methods = [('allservices', False), ('service', False), ('maintenance', True)]
|
||||
|
||||
save_fields = ['name', 'comments']
|
||||
save_fields = ['name', 'comments', 'tags']
|
||||
|
||||
table_title = _('Service providers')
|
||||
|
||||
@@ -64,8 +65,9 @@ class Providers(ModelHandler):
|
||||
{'name': {'title': _('Name'), 'type': 'iconType'}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'maintenance_state': {'title': _('Status')}},
|
||||
{'services_count': {'title': _('Services'), 'type': 'numeric', 'width': '5em'}},
|
||||
{'user_services_count': {'title': _('User Services'), 'type': 'numeric', 'width': '8em'}},
|
||||
{'services_count': {'title': _('Services'), 'type': 'numeric'}},
|
||||
{'user_services_count': {'title': _('User Services'), 'type': 'numeric'}}, # , 'width': '132px'
|
||||
{'tags': {'title': _('tags'), 'visible': False}},
|
||||
]
|
||||
# Field from where to get "class" and prefix for that class, so this will generate "row-state-A, row-state-X, ....
|
||||
table_row_style = {'field': 'maintenance_mode', 'prefix': 'row-maintenance-'}
|
||||
@@ -83,6 +85,7 @@ class Providers(ModelHandler):
|
||||
return {
|
||||
'id': provider.uuid,
|
||||
'name': provider.name,
|
||||
'tags': [tag.vtag for tag in provider.tags.all()],
|
||||
'services_count': provider.services.count(),
|
||||
'user_services_count': UserService.objects.filter(deployed_service__service__provider=provider).count(),
|
||||
'maintenance_mode': provider.maintenance_mode,
|
||||
@@ -103,7 +106,7 @@ class Providers(ModelHandler):
|
||||
# Gui related
|
||||
def getGui(self, type_):
|
||||
try:
|
||||
return self.addDefaultFields(services.factory().lookup(type_).guiDescription(), ['name', 'comments'])
|
||||
return self.addDefaultFields(services.factory().lookup(type_).guiDescription(), ['name', 'comments', 'tags'])
|
||||
except Exception:
|
||||
raise NotFound('type not found')
|
||||
|
||||
|
@@ -35,7 +35,7 @@ from __future__ import unicode_literals
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
from uds.models import Service, UserService
|
||||
from uds.models import Service, UserService, Tag
|
||||
|
||||
from uds.core.services import Service as coreService
|
||||
from uds.core.util import log
|
||||
@@ -45,6 +45,7 @@ from uds.core.Environment import Environment
|
||||
from uds.REST.model import DetailHandler
|
||||
from uds.REST import NotFound, ResponseError, RequestError
|
||||
from django.db import IntegrityError
|
||||
from uds.core.ui.images import DEFAULT_THUMB_BASE64
|
||||
|
||||
import logging
|
||||
|
||||
@@ -56,6 +57,8 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
Detail handler for Services, whose parent is a Provider
|
||||
'''
|
||||
|
||||
custom_methods = ['servicesPools']
|
||||
|
||||
@staticmethod
|
||||
def serviceInfo(item):
|
||||
info = item.getType()
|
||||
@@ -84,6 +87,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
retVal = {
|
||||
'id': item.uuid,
|
||||
'name': item.name,
|
||||
'tags': [tag.tag for tag in item.tags.all()],
|
||||
'comments': item.comments,
|
||||
'type': item.data_type,
|
||||
'type_name': _(itemType.name()),
|
||||
@@ -129,7 +133,9 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
# Extract item db fields
|
||||
# We need this fields for all
|
||||
logger.debug('Saving service {0} / {1}'.format(parent, item))
|
||||
fields = self.readFieldsFromParams(['name', 'comments', 'data_type'])
|
||||
fields = self.readFieldsFromParams(['name', 'comments', 'data_type', 'tags'])
|
||||
tags = fields['tags']
|
||||
del fields['tags']
|
||||
service = None
|
||||
try:
|
||||
if item is None: # Create new
|
||||
@@ -138,14 +144,16 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
service = parent.services.get(uuid=processUuid(item))
|
||||
service.__dict__.update(fields)
|
||||
|
||||
service.data = service.getInstance(self._params).serialize()
|
||||
service.tags = [Tag.objects.get_or_create(tag=val)[0] for val in tags]
|
||||
|
||||
service.data = service.getInstance(self._params).serialize() # This may launch an validation exception (the getInstance(...) part)
|
||||
service.save()
|
||||
except Service.DoesNotExist:
|
||||
self.invalidItemException()
|
||||
except IntegrityError: # Duplicate key probably
|
||||
raise RequestError(_('Element already exists (duplicate key error)'))
|
||||
except coreService.ValidationException as e:
|
||||
if item is None:
|
||||
if item is None: # Only remove partially saved element if creating new (if editing, ignore this)
|
||||
self._deleteIncompleteService(service)
|
||||
raise RequestError(_('Input error: {0}'.format(unicode(e))))
|
||||
except Exception as e:
|
||||
@@ -159,14 +167,15 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
try:
|
||||
service = parent.services.get(uuid=processUuid(item))
|
||||
|
||||
if service.deployedServices.count() != 0:
|
||||
raise RequestError('Item has associated deployed services')
|
||||
if service.deployedServices.count() == 0:
|
||||
service.delete()
|
||||
return 'deleted'
|
||||
|
||||
service.delete()
|
||||
except Exception:
|
||||
logger.exception('Deleting service')
|
||||
self.invalidItemException()
|
||||
|
||||
return 'deleted'
|
||||
raise RequestError('Item has associated deployed services')
|
||||
|
||||
def getTitle(self, parent):
|
||||
try:
|
||||
@@ -179,8 +188,9 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
{'name': {'title': _('Service name'), 'visible': True, 'type': 'iconType'}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'type_name': {'title': _('Type')}},
|
||||
{'deployed_services_count': {'title': _('Deployed services'), 'type': 'numeric', 'width': '7em'}},
|
||||
{'user_services_count': {'title': _('User services'), 'type': 'numeric', 'width': '7em'}},
|
||||
{'deployed_services_count': {'title': _('Deployed services'), 'type': 'numeric'}},
|
||||
{'user_services_count': {'title': _('User services'), 'type': 'numeric'}},
|
||||
{'tags': {'title': _('tags'), 'visible': False}},
|
||||
]
|
||||
|
||||
def getTypes(self, parent, forType):
|
||||
@@ -208,7 +218,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
parentInstance = parent.getInstance()
|
||||
serviceType = parentInstance.getServiceByType(forType)
|
||||
service = serviceType(Environment.getTempEnv(), parentInstance) # Instantiate it so it has the opportunity to alter gui description based on parent
|
||||
return self.addDefaultFields(service.guiDescription(service), ['name', 'comments'])
|
||||
return self.addDefaultFields(service.guiDescription(service), ['name', 'comments', 'tags'])
|
||||
except Exception as e:
|
||||
logger.exception('getGui')
|
||||
raise ResponseError(unicode(e))
|
||||
@@ -220,3 +230,20 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
return log.getLogs(item)
|
||||
except Exception:
|
||||
self.invalidItemException()
|
||||
|
||||
def servicesPools(self, parent, item):
|
||||
self.ensureAccess(item, permissions.PERMISSION_READ)
|
||||
logger.debug('Got parameters for servicepools: {}, {}'.format(parent, item))
|
||||
uuid = processUuid(item)
|
||||
service = parent.services.get(uuid=uuid)
|
||||
res = []
|
||||
for i in service.deployedServices.all():
|
||||
res.append({
|
||||
'id': i.uuid,
|
||||
'name': i.name,
|
||||
'thumb': i.image.thumb64 if i.image is not None else DEFAULT_THUMB_BASE64,
|
||||
'user_services_count': i.userServices.count(),
|
||||
'state': _('With errors') if i.isRestrained() else _('Ok'),
|
||||
})
|
||||
|
||||
return res
|
||||
|
201
server/src/uds/REST/methods/services_pool_calendars.py
Normal file
201
server/src/uds/REST/methods/services_pool_calendars.py
Normal file
@@ -0,0 +1,201 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
from uds.models import CalendarAccess, CalendarAction, Calendar
|
||||
from uds.models.CalendarAction import CALENDAR_ACTION_DICT
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.core.util import log
|
||||
from uds.REST.model import DetailHandler
|
||||
from uds.REST import ResponseError
|
||||
from uds.core.util import permissions
|
||||
|
||||
import json
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
ALLOW = 'ALLOW'
|
||||
DENY = 'DENY'
|
||||
|
||||
|
||||
class AccessCalendars(DetailHandler):
|
||||
'''
|
||||
Processes the transports detail requests of a Service Pool
|
||||
'''
|
||||
@staticmethod
|
||||
def as_dict(item):
|
||||
return {
|
||||
'id': item.uuid,
|
||||
'calendarId': item.calendar.uuid,
|
||||
'calendar': item.calendar.name,
|
||||
'access': item.access,
|
||||
'priority': item.priority,
|
||||
}
|
||||
|
||||
def getItems(self, parent, item):
|
||||
try:
|
||||
if item is None:
|
||||
return [AccessCalendars.as_dict(i) for i in parent.calendaraccess_set.all()]
|
||||
else:
|
||||
i = CalendarAccess.objects.get(uuid=processUuid(item))
|
||||
return AccessCalendars.as_dict(i)
|
||||
except Exception:
|
||||
self.invalidItemException()
|
||||
|
||||
|
||||
def getTitle(self, parent):
|
||||
return _('Access restrictions by calendar')
|
||||
|
||||
def getFields(self, parent):
|
||||
return [
|
||||
{'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}},
|
||||
{'calendar': {'title': _('Calendar')}},
|
||||
{'access': {'title': _('Access')}},
|
||||
]
|
||||
|
||||
def saveItem(self, parent, item):
|
||||
# If already exists
|
||||
uuid = processUuid(item) if item is not None else None
|
||||
|
||||
calendar = Calendar.objects.get(uuid=processUuid(self._params['calendarId']))
|
||||
access = self._params['access'].upper()
|
||||
priority = int(self._params['priority'])
|
||||
|
||||
if uuid is not None:
|
||||
calAccess = CalendarAccess.objects.get(uuid=uuid)
|
||||
calAccess.calendar = calendar
|
||||
calAccess.service_pool = parent
|
||||
calAccess.access = access
|
||||
calAccess.priority = priority
|
||||
calAccess.save()
|
||||
else:
|
||||
CalendarAccess.objects.create(calendar=calendar, service_pool=parent, access=access, priority=priority)
|
||||
|
||||
return self.success()
|
||||
|
||||
def deleteItem(self, parent, item):
|
||||
CalendarAccess.objects.get(uuid=processUuid(self._args[0])).delete()
|
||||
|
||||
|
||||
|
||||
class ActionsCalendars(DetailHandler):
|
||||
'''
|
||||
Processes the transports detail requests of a Service Pool
|
||||
'''
|
||||
custom_methods = ('execute')
|
||||
|
||||
@staticmethod
|
||||
def as_dict(item):
|
||||
return {
|
||||
'id': item.uuid,
|
||||
'calendarId': item.calendar.uuid,
|
||||
'calendar': item.calendar.name,
|
||||
'action': item.action,
|
||||
'actionDescription': CALENDAR_ACTION_DICT[item.action]['description'],
|
||||
'atStart': item.at_start,
|
||||
'eventsOffset': item.events_offset,
|
||||
'params': json.loads(item.params),
|
||||
'nextExecution': item.next_execution,
|
||||
'lastExecution': item.last_execution
|
||||
}
|
||||
|
||||
def getItems(self, parent, item):
|
||||
try:
|
||||
if item is None:
|
||||
return [ActionsCalendars.as_dict(i) for i in parent.calendaraction_set.all()]
|
||||
else:
|
||||
i = CalendarAction.objects.get(uuid=processUuid(item))
|
||||
return ActionsCalendars.as_dict(i)
|
||||
except Exception:
|
||||
self.invalidItemException()
|
||||
|
||||
|
||||
def getTitle(self, parent):
|
||||
return _('Scheduled actions')
|
||||
|
||||
def getFields(self, parent):
|
||||
return [
|
||||
{'calendar': {'title': _('Calendar')}},
|
||||
{'actionDescription': {'title': _('Action')}},
|
||||
{'params': {'title': _('Parameters')}},
|
||||
{'atStart': {'title': _('Relative to')}},
|
||||
{'eventsOffset': {'title': _('Time offset')}},
|
||||
{'nextExecution': {'title': _('Next execution'), 'type': 'datetime'}},
|
||||
{'lastExecution': {'title': _('Last execution'), 'type': 'datetime'}},
|
||||
]
|
||||
|
||||
def saveItem(self, parent, item):
|
||||
# If already exists
|
||||
uuid = processUuid(item) if item is not None else None
|
||||
|
||||
calendar = Calendar.objects.get(uuid=processUuid(self._params['calendarId']))
|
||||
action = self._params['action'].upper()
|
||||
eventsOffset = int(self._params['eventsOffset'])
|
||||
atStart = self._params['atStart'] not in ('false', False, '0', 0)
|
||||
params = json.dumps(self._params['params'])
|
||||
|
||||
logger.debug('Got parameters: {} {} {} {} ----> {}'.format(calendar, action, eventsOffset, atStart, params))
|
||||
|
||||
if uuid is not None:
|
||||
calAction = CalendarAction.objects.get(uuid=uuid)
|
||||
calAction.calendar = calendar
|
||||
calAction.service_pool = parent
|
||||
calAction.action = action
|
||||
calAction.at_start = atStart
|
||||
calAction.events_offset = eventsOffset
|
||||
calAction.params = params
|
||||
calAction.save()
|
||||
else:
|
||||
CalendarAction.objects.create(calendar=calendar, service_pool=parent, action=action, at_start=atStart, events_offset=eventsOffset, params=params)
|
||||
|
||||
return self.success()
|
||||
|
||||
def deleteItem(self, parent, item):
|
||||
CalendarAction.objects.get(uuid=processUuid(self._args[0])).delete()
|
||||
|
||||
def execute(self, parent, item):
|
||||
self.ensureAccess(item, permissions.PERMISSION_MANAGEMENT)
|
||||
logger.debug('Launching action')
|
||||
uuid = processUuid(item)
|
||||
calAction = CalendarAction.objects.get(uuid=uuid)
|
||||
calAction.execute()
|
||||
|
||||
return self.success()
|
111
server/src/uds/REST/methods/services_pool_groups.py
Normal file
111
server/src/uds/REST/methods/services_pool_groups.py
Normal file
@@ -0,0 +1,111 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@itemor: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _, ugettext
|
||||
from uds.models import ServicesPoolGroup, Image
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.core.ui.UserInterface import gui
|
||||
from uds.core.ui.images import DEFAULT_THUMB_BASE64
|
||||
|
||||
from uds.REST.model import ModelHandler
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Enclosed methods under /item path
|
||||
|
||||
|
||||
class ServicesPoolGroups(ModelHandler):
|
||||
'''
|
||||
Handles the gallery REST interface
|
||||
'''
|
||||
# needs_admin = True
|
||||
|
||||
path = 'gallery'
|
||||
model = ServicesPoolGroup
|
||||
save_fields = ['name', 'comments', 'image_id', 'priority']
|
||||
|
||||
table_title = _('Services Pool Groups')
|
||||
table_fields = [
|
||||
{'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}},
|
||||
{'thumb': {'title': _('Image'), 'visible': True, 'type': 'image', 'width': '96px' }},
|
||||
{'name': {'title': _('Name')}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
]
|
||||
|
||||
def beforeSave(self, fields):
|
||||
imgId = fields['image_id']
|
||||
fields['image_id'] = None
|
||||
logger.debug('Image id: {}'.format(imgId))
|
||||
try:
|
||||
if imgId != '-1':
|
||||
image = Image.objects.get(uuid=processUuid(imgId))
|
||||
fields['image_id'] = image.id
|
||||
except Exception:
|
||||
logger.exception('At image recovering')
|
||||
|
||||
# Gui related
|
||||
def getGui(self, type_):
|
||||
g = self.addDefaultFields([], ['name', 'comments', 'priority'])
|
||||
|
||||
for f in [{
|
||||
'name': 'image_id',
|
||||
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]),
|
||||
'label': ugettext('Associated Image'),
|
||||
'tooltip': ugettext('Image assocciated with this service'),
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 102,
|
||||
}]:
|
||||
self.addField(g, f)
|
||||
|
||||
return g
|
||||
|
||||
def item_as_dict(self, item):
|
||||
return {
|
||||
'id': item.uuid,
|
||||
'priority': item.priority,
|
||||
'name': item.name,
|
||||
'comments': item.comments,
|
||||
'image_id': item.image.uuid if item.image is not None else None,
|
||||
}
|
||||
|
||||
def item_as_dict_overview(self, item):
|
||||
return {
|
||||
'id': item.uuid,
|
||||
'priority': item.priority,
|
||||
'name': item.name,
|
||||
'comments': item.comments,
|
||||
'thumb': item.thumb64,
|
||||
}
|
@@ -33,7 +33,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext, ugettext_lazy as _
|
||||
from uds.models import DeployedService, OSManager, Service, Image
|
||||
from uds.models import DeployedService, OSManager, Service, Image, ServicesPoolGroup
|
||||
from uds.models.CalendarAction import CALENDAR_ACTION_INITIAL, CALENDAR_ACTION_MAX, CALENDAR_ACTION_CACHE_L1, CALENDAR_ACTION_CACHE_L2, CALENDAR_ACTION_PUBLISH
|
||||
from uds.core.ui.images import DEFAULT_THUMB_BASE64
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util.model import processUuid
|
||||
@@ -43,6 +44,7 @@ from uds.REST.model import ModelHandler
|
||||
from uds.REST import RequestError, ResponseError
|
||||
from uds.core.ui.UserInterface import gui
|
||||
from .user_services import AssignedService, CachedService, Groups, Transports, Publications, Changelog
|
||||
from .services_pool_calendars import AccessCalendars, ActionsCalendars
|
||||
from .services import Services
|
||||
|
||||
import logging
|
||||
@@ -61,29 +63,44 @@ class ServicesPools(ModelHandler):
|
||||
'groups': Groups,
|
||||
'transports': Transports,
|
||||
'publications': Publications,
|
||||
'changelog': Changelog
|
||||
'changelog': Changelog,
|
||||
'access': AccessCalendars,
|
||||
'actions': ActionsCalendars
|
||||
}
|
||||
|
||||
save_fields = ['name', 'comments', 'service_id', 'osmanager_id', 'image_id', 'initial_srvs', 'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs', 'show_transports']
|
||||
save_fields = ['name', 'comments', 'tags', 'service_id', 'osmanager_id', 'image_id', 'servicesPoolGroup_id', 'initial_srvs', 'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs', 'show_transports']
|
||||
remove_fields = ['osmanager_id', 'service_id']
|
||||
|
||||
table_title = _('Service Pools')
|
||||
table_fields = [
|
||||
{'name': {'title': _('Name')}},
|
||||
{'parent': {'title': _('Parent Service')}}, # Will process this field on client in fact, not sent by server
|
||||
{'parent': {'title': _('Parent Service')}},
|
||||
{'state': {'title': _('status'), 'type': 'dict', 'dict': State.dictionary()}},
|
||||
{'show_transports': {'title': _('Shows transports')}}, # Will process this field on client in fact, not sent by server
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'show_transports': {'title': _('Shows transports'), 'type': 'callback'}},
|
||||
{'pool_group_name': {'title': _('Pool Group')}},
|
||||
{'tags': {'title': _('tags'), 'visible': False}},
|
||||
]
|
||||
# Field from where to get "class" and prefix for that class, so this will generate "row-state-A, row-state-X, ....
|
||||
table_row_style = {'field': 'state', 'prefix': 'row-state-'}
|
||||
|
||||
custom_methods = [('setFallbackAccess', True), ('actionsList', True)]
|
||||
|
||||
|
||||
def item_as_dict(self, item):
|
||||
# if item does not have an associated service, hide it (the case, for example, for a removed service)
|
||||
# Access from dict will raise an exception, and item will be skipped
|
||||
poolGroupId = None
|
||||
poolGroupName = _('Default')
|
||||
poolGroupThumb = DEFAULT_THUMB_BASE64
|
||||
if item.servicesPoolGroup is not None:
|
||||
poolGroupId = item.servicesPoolGroup.uuid
|
||||
poolGroupName = item.servicesPoolGroup.name
|
||||
if item.servicesPoolGroup.image is not None:
|
||||
poolGroupThumb = item.servicesPoolGroup.image.thumb64
|
||||
val = {
|
||||
'id': item.uuid,
|
||||
'name': item.name,
|
||||
'tags': [tag.tag for tag in item.tags.all()],
|
||||
'parent': item.service.name,
|
||||
'parent_type': item.service.data_type,
|
||||
'comments': item.comments,
|
||||
@@ -92,6 +109,9 @@ class ServicesPools(ModelHandler):
|
||||
'service_id': item.service.uuid,
|
||||
'provider_id': item.service.provider.uuid,
|
||||
'image_id': item.image.uuid if item.image is not None else None,
|
||||
'servicesPoolGroup_id': poolGroupId,
|
||||
'pool_group_name': poolGroupName,
|
||||
'pool_group_thumb': poolGroupThumb,
|
||||
'initial_srvs': item.initial_srvs,
|
||||
'cache_l1_srvs': item.cache_l1_srvs,
|
||||
'cache_l2_srvs': item.cache_l2_srvs,
|
||||
@@ -99,6 +119,7 @@ class ServicesPools(ModelHandler):
|
||||
'user_services_count': item.userServices.count(),
|
||||
'restrained': item.isRestrained(),
|
||||
'show_transports': item.show_transports,
|
||||
'fallbackAccess': item.fallbackAccess,
|
||||
'permission': permissions.getEffectivePermission(self._user, item),
|
||||
'info': Services.serviceInfo(item.service),
|
||||
}
|
||||
@@ -115,7 +136,7 @@ class ServicesPools(ModelHandler):
|
||||
if Service.objects.count() < 1:
|
||||
raise ResponseError(ugettext('Create at least a service before creating a new service pool'))
|
||||
|
||||
g = self.addDefaultFields([], ['name', 'comments'])
|
||||
g = self.addDefaultFields([], ['name', 'comments', 'tags'])
|
||||
|
||||
for f in [{
|
||||
'name': 'service_id',
|
||||
@@ -135,11 +156,20 @@ class ServicesPools(ModelHandler):
|
||||
'order': 101,
|
||||
}, {
|
||||
'name': 'image_id',
|
||||
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.name) for v in Image.objects.all()]),
|
||||
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]),
|
||||
'label': ugettext('Associated Image'),
|
||||
'tooltip': ugettext('Image assocciated with this service'),
|
||||
'type': gui.InputField.CHOICE_TYPE,
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 102,
|
||||
'tab': ugettext('Display'),
|
||||
}, {
|
||||
'name': 'servicesPoolGroup_id',
|
||||
'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in ServicesPoolGroup.objects.all()]),
|
||||
'label': ugettext('Pool group'),
|
||||
'tooltip': ugettext('Pool group for this pool (for pool clasify on display)'),
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 103,
|
||||
'tab': ugettext('Display'),
|
||||
}, {
|
||||
'name': 'initial_srvs',
|
||||
'value': '0',
|
||||
@@ -147,7 +177,8 @@ class ServicesPools(ModelHandler):
|
||||
'label': ugettext('Initial available services'),
|
||||
'tooltip': ugettext('Services created initially for this service pool'),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 103,
|
||||
'order': 110,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
'name': 'cache_l1_srvs',
|
||||
'value': '0',
|
||||
@@ -155,7 +186,8 @@ class ServicesPools(ModelHandler):
|
||||
'label': ugettext('Services to keep in cache'),
|
||||
'tooltip': ugettext('Services kept in cache for improved user service assignation'),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 104,
|
||||
'order': 111,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
'name': 'cache_l2_srvs',
|
||||
'value': '0',
|
||||
@@ -163,7 +195,8 @@ class ServicesPools(ModelHandler):
|
||||
'label': ugettext('Services to keep in L2 cache'),
|
||||
'tooltip': ugettext('Services kept in cache of level2 for improved service generation'),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 105,
|
||||
'order': 112,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
'name': 'max_srvs',
|
||||
'value': '0',
|
||||
@@ -171,14 +204,15 @@ class ServicesPools(ModelHandler):
|
||||
'label': ugettext('Maximum number of services to provide'),
|
||||
'tooltip': ugettext('Maximum number of service (assigned and L1 cache) that can be created for this service'),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 106,
|
||||
'order': 113,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
'name': 'show_transports',
|
||||
'value': True,
|
||||
'label': ugettext('Show transports'),
|
||||
'tooltip': ugettext('If active, alternative transports for user will be shown'),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 107,
|
||||
'order': 120,
|
||||
}]:
|
||||
self.addField(g, f)
|
||||
|
||||
@@ -212,6 +246,9 @@ class ServicesPools(ModelHandler):
|
||||
except Exception:
|
||||
raise RequestError(ugettext('This service requires an OS Manager'))
|
||||
|
||||
# If max < initial or cache_1 or cache_l2
|
||||
fields['max_srvs'] = max((int(fields['initial_srvs']), int(fields['cache_l1_srvs']), int(fields['max_srvs'])))
|
||||
|
||||
imgId = fields['image_id']
|
||||
fields['image_id'] = None
|
||||
logger.debug('Image id: {}'.format(imgId))
|
||||
@@ -222,6 +259,17 @@ class ServicesPools(ModelHandler):
|
||||
except Exception:
|
||||
logger.exception('At image recovering')
|
||||
|
||||
# Servicepool Group
|
||||
spgrpId = fields['servicesPoolGroup_id']
|
||||
fields['servicesPoolGroup_id'] = None
|
||||
logger.debug('servicesPoolGroup_id: {}'.format(spgrpId))
|
||||
try:
|
||||
if spgrpId != '-1':
|
||||
spgrp = ServicesPoolGroup.objects.get(uuid=processUuid(spgrpId))
|
||||
fields['servicesPoolGroup_id'] = spgrp.id
|
||||
except Exception:
|
||||
logger.exception('At service pool group recovering')
|
||||
|
||||
except (RequestError, ResponseError):
|
||||
raise
|
||||
except Exception as e:
|
||||
@@ -240,3 +288,27 @@ class ServicesPools(ModelHandler):
|
||||
return log.getLogs(item)
|
||||
except Exception:
|
||||
return []
|
||||
|
||||
# Set fallback status
|
||||
def setFallbackAccess(self, item):
|
||||
self.ensureAccess(item, permissions.PERMISSION_MANAGEMENT)
|
||||
|
||||
fallback = self._params.get('fallbackAccess')
|
||||
logger.debug('Setting fallback of {} to {}'.format(item.name, fallback))
|
||||
item.fallbackAccess = fallback
|
||||
item.save()
|
||||
return ''
|
||||
|
||||
# Returns the action list based on current element, for calendar
|
||||
def actionsList(self, item):
|
||||
validActions = ()
|
||||
itemInfo = item.service.getType()
|
||||
if itemInfo.usesCache is True:
|
||||
validActions += (CALENDAR_ACTION_INITIAL, CALENDAR_ACTION_CACHE_L1, CALENDAR_ACTION_MAX)
|
||||
if itemInfo.usesCache_L2 is True:
|
||||
validActions += (CALENDAR_ACTION_CACHE_L2,)
|
||||
|
||||
if itemInfo.publicationType is not None:
|
||||
validActions += (CALENDAR_ACTION_PUBLISH,)
|
||||
|
||||
return validActions
|
||||
|
@@ -37,7 +37,7 @@ from uds.models import User, Service, UserService, DeployedService, getSqlDateti
|
||||
from uds.core.util.stats import counters
|
||||
from uds.core.util.Cache import Cache
|
||||
from uds.REST import Handler, RequestError, ResponseError
|
||||
import cPickle
|
||||
import pickle
|
||||
from datetime import timedelta
|
||||
|
||||
import logging
|
||||
@@ -70,11 +70,11 @@ def getServicesPoolsCounters(servicePool, counter_type):
|
||||
for x in counters.getCounters(us, counter_type, since=since, to=to, limit=POINTS, use_max=USE_MAX, all=complete):
|
||||
val.append({'stamp': x[0], 'value': int(x[1])})
|
||||
if len(val) > 2:
|
||||
cache.put(cacheKey, cPickle.dumps(val).encode('zip'), 600)
|
||||
cache.put(cacheKey, pickle.dumps(val).encode('zip'), 600)
|
||||
else:
|
||||
val = [{'stamp': since, 'value': 0}, {'stamp': to, 'value': 0}]
|
||||
else:
|
||||
val = cPickle.loads(val.decode('zip'))
|
||||
val = pickle.loads(val.decode('zip'))
|
||||
|
||||
return val
|
||||
except:
|
||||
|
@@ -48,14 +48,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class Transports(ModelHandler):
|
||||
model = Transport
|
||||
save_fields = ['name', 'comments', 'priority', 'nets_positive']
|
||||
save_fields = ['name', 'comments', 'tags', 'priority', 'nets_positive']
|
||||
|
||||
table_title = _('Current Transports')
|
||||
table_fields = [
|
||||
{'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}},
|
||||
{'name': {'title': _('Name'), 'visible': True, 'type': 'iconType'}},
|
||||
{'comments': {'title': _('Comments')}},
|
||||
{'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}}
|
||||
{'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}},
|
||||
{'tags': {'title': _('tags'), 'visible': False}},
|
||||
]
|
||||
|
||||
def enum_types(self):
|
||||
@@ -64,7 +65,7 @@ class Transports(ModelHandler):
|
||||
def getGui(self, type_):
|
||||
try:
|
||||
return self.addField(
|
||||
self.addField(self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments', 'priority']), {
|
||||
self.addField(self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments', 'tags', 'priority']), {
|
||||
'name': 'nets_positive',
|
||||
'value': True,
|
||||
'label': ugettext('Network access'),
|
||||
@@ -88,6 +89,7 @@ class Transports(ModelHandler):
|
||||
return {
|
||||
'id': item.uuid,
|
||||
'name': item.name,
|
||||
'tags': [tag.tag for tag in item.tags.all()],
|
||||
'comments': item.comments,
|
||||
'priority': item.priority,
|
||||
'nets_positive': item.nets_positive,
|
||||
|
@@ -86,6 +86,10 @@ class AssignedService(DetailHandler):
|
||||
else:
|
||||
val.update({
|
||||
'owner': item.user.manager.name + "-" + item.user.name,
|
||||
'owner_info': {
|
||||
'auth_id': item.user.manager.uuid,
|
||||
'user_id': item.user.uuid
|
||||
},
|
||||
'in_use': item.in_use,
|
||||
'in_use_date': item.in_use_date,
|
||||
'source_host': item.src_hostname,
|
||||
@@ -143,10 +147,12 @@ class AssignedService(DetailHandler):
|
||||
self.invalidItemException()
|
||||
|
||||
logger.debug('Deleting assigned service')
|
||||
if service.state == State.USABLE:
|
||||
if service.state in (State.USABLE, State.REMOVING):
|
||||
service.remove()
|
||||
elif service.state == State.PREPARING:
|
||||
service.cancel()
|
||||
elif service.state == State.REMOVABLE:
|
||||
self.invalidItemException(_('Item already being removed'))
|
||||
else:
|
||||
self.invalidItemException(_('Item is not removable'))
|
||||
|
||||
@@ -202,6 +208,7 @@ class Groups(DetailHandler):
|
||||
def getItems(self, parent, item):
|
||||
return [{
|
||||
'id': i.uuid,
|
||||
'auth_id': i.manager.uuid,
|
||||
'name': i.name,
|
||||
'comments': i.comments,
|
||||
'state': i.state,
|
||||
|
@@ -42,6 +42,8 @@ from uds.REST.handlers import Handler, HandlerError
|
||||
from uds.core.util import log
|
||||
from uds.core.util import permissions
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.models import Tag
|
||||
|
||||
|
||||
import fnmatch
|
||||
import re
|
||||
@@ -52,7 +54,7 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__updated__ = '2015-04-28'
|
||||
__updated__ = '2016-04-18'
|
||||
|
||||
|
||||
# a few constants
|
||||
@@ -86,7 +88,7 @@ class BaseModelHandler(Handler):
|
||||
:param gui: List of "gui" items where the field will be added
|
||||
:param field: Field to be added (dictionary)
|
||||
'''
|
||||
gui.append({
|
||||
v = {
|
||||
'name': field.get('name', ''),
|
||||
'value': '',
|
||||
'gui': {
|
||||
@@ -103,7 +105,10 @@ class BaseModelHandler(Handler):
|
||||
'order': field.get('order', 0),
|
||||
'values': field.get('values', [])
|
||||
}
|
||||
})
|
||||
}
|
||||
if 'tab' in field:
|
||||
v['gui']['tab'] = field['tab']
|
||||
gui.append(v)
|
||||
return gui
|
||||
|
||||
def addDefaultFields(self, gui, flds):
|
||||
@@ -112,6 +117,14 @@ class BaseModelHandler(Handler):
|
||||
:param gui: Gui list where the "default" fielsds will be added
|
||||
:param flds: List of fields names requested to be added. Valid values are 'name', 'comments', 'priority' and 'small_name'
|
||||
'''
|
||||
if 'tags' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'tags',
|
||||
'label': _('Tags'),
|
||||
'type': 'taglist',
|
||||
'tooltip': _('Tags for this element'),
|
||||
'order': 0 - 101,
|
||||
})
|
||||
if 'name' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'name',
|
||||
@@ -137,17 +150,17 @@ class BaseModelHandler(Handler):
|
||||
'required': True,
|
||||
'value': 1,
|
||||
'length': 4,
|
||||
'order': 0 - 98,
|
||||
'order': 0 - 97,
|
||||
})
|
||||
if 'small_name' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'small_name',
|
||||
'type': 'text',
|
||||
'label': _('Tag'),
|
||||
'tooltip': _('Tag for this element'),
|
||||
'label': _('Label'),
|
||||
'tooltip': _('Label for this element'),
|
||||
'required': True,
|
||||
'length': 128,
|
||||
'order': 0 - 97,
|
||||
'order': 0 - 96,
|
||||
})
|
||||
|
||||
return gui
|
||||
@@ -212,7 +225,9 @@ class BaseModelHandler(Handler):
|
||||
:param res: Dictionary to "extend" with instance key-values pairs
|
||||
'''
|
||||
if hasattr(item, 'getInstance'):
|
||||
for key, value in item.getInstance().valuesDict().iteritems():
|
||||
i = item.getInstance()
|
||||
i.initGui() # Defaults & stuff
|
||||
for key, value in i.valuesDict().iteritems():
|
||||
if type(value) in (unicode, str):
|
||||
value = {"true": True, "false": False}.get(value, value) # Translate "true" & "false" to True & False (booleans)
|
||||
logger.debug('{0} = {1}'.format(key, value))
|
||||
@@ -238,8 +253,9 @@ class BaseModelHandler(Handler):
|
||||
'''
|
||||
Raises a NotFound exception, with location info
|
||||
'''
|
||||
message = _('Item not found') if message is None else None
|
||||
raise NotFound('{} {}: {}'.format(message, self.__class__, self._args))
|
||||
message = _('Item not found') if message is None else message
|
||||
raise NotFound(message)
|
||||
# raise NotFound('{} {}: {}'.format(message, self.__class__, self._args))
|
||||
|
||||
def accessDenied(self, message=None):
|
||||
raise AccessDenied(message or _('Access denied'))
|
||||
@@ -832,6 +848,13 @@ class ModelHandler(BaseModelHandler):
|
||||
args = self.readFieldsFromParams(self.save_fields)
|
||||
logger.debug('Args: {}'.format(args))
|
||||
self.beforeSave(args)
|
||||
# If tags is in save fields, treat it "specially"
|
||||
if 'tags' in self.save_fields:
|
||||
tags = args['tags']
|
||||
del args['tags']
|
||||
else:
|
||||
tags = None
|
||||
|
||||
deleteOnError = False
|
||||
if len(self._args) == 0: # create new
|
||||
item = self.model.objects.create(**args)
|
||||
@@ -843,6 +866,12 @@ class ModelHandler(BaseModelHandler):
|
||||
if v in args:
|
||||
del args[v]
|
||||
item.__dict__.update(args) # Update fields from args
|
||||
|
||||
# Now if tags, update them
|
||||
if tags is not None:
|
||||
logger.debug('Updating tags: {}'.format(tags))
|
||||
item.tags = [ Tag.objects.get_or_create(tag=val)[0] for val in tags]
|
||||
|
||||
except self.model.DoesNotExist:
|
||||
raise NotFound('Item not found')
|
||||
except IntegrityError: # Duplicate key probably
|
||||
|
@@ -33,7 +33,8 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
# from django.utils import simplejson as json
|
||||
import ujson as json
|
||||
#import ujson as json
|
||||
import json
|
||||
from xml_marshaller import xml_marshaller
|
||||
import datetime
|
||||
import time
|
||||
@@ -94,7 +95,9 @@ class ContentProcessor(object):
|
||||
'''
|
||||
Helper for renderers. Alters some types so they can be serialized correctly (as we want them to be)
|
||||
'''
|
||||
if isinstance(obj, (bool, int, float, six.text_type)):
|
||||
if obj is None:
|
||||
return None
|
||||
elif isinstance(obj, (bool, int, float, six.text_type)):
|
||||
return obj
|
||||
elif isinstance(obj, long):
|
||||
return int(obj)
|
||||
@@ -108,7 +111,7 @@ class ContentProcessor(object):
|
||||
for v in obj:
|
||||
res.append(ContentProcessor.procesForRender(v))
|
||||
return res
|
||||
elif isinstance(obj, datetime.datetime):
|
||||
elif isinstance(obj, (datetime.datetime, datetime.date)):
|
||||
return int(time.mktime(obj.timetuple()))
|
||||
elif isinstance(obj, six.binary_type):
|
||||
return obj.decode('utf-8')
|
||||
|
@@ -32,20 +32,11 @@
|
||||
'''
|
||||
|
||||
# Make sure that all services are "available" at service startup
|
||||
from . import services # to make sure that the packages are initialized at this point
|
||||
from . import auths # To make sure that the packages are initialized at this point
|
||||
from . import osmanagers # To make sure that packages are initialized at this point
|
||||
from . import transports # To make sure that packages are initialized at this point
|
||||
from . import dispatchers
|
||||
from . import models
|
||||
from . import plugins # To make sure plugins are loaded on memory
|
||||
from . import REST # To make sure REST initializes all what it needs
|
||||
|
||||
import uds.xmlrpc # To make actor live
|
||||
|
||||
from django.db.backends.signals import connection_created
|
||||
from django.dispatch import receiver
|
||||
import math
|
||||
import ssl
|
||||
|
||||
|
||||
from django.apps import AppConfig
|
||||
@@ -55,7 +46,17 @@ import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
__updated__ = '2015-06-09'
|
||||
__updated__ = '2016-04-04'
|
||||
|
||||
|
||||
# Default ssl context is unverified, as MOST servers that we will connect will be with self signed certificates...
|
||||
try:
|
||||
_create_unverified_https_context = ssl._create_unverified_context
|
||||
ssl._create_default_https_context = _create_unverified_https_context
|
||||
except AttributeError:
|
||||
# Legacy Python that doesn't verify HTTPS certificates by default
|
||||
pass
|
||||
|
||||
|
||||
|
||||
class UDSAppConfig(AppConfig):
|
||||
@@ -67,6 +68,16 @@ class UDSAppConfig(AppConfig):
|
||||
# with ANY command from manage.
|
||||
logger.debug('Initializing app (ready) ***************')
|
||||
|
||||
# Now, ensures that all dynamic elements are loadad and present
|
||||
from . import services # to make sure that the packages are initialized at this point
|
||||
from . import auths # To make sure that the packages are initialized at this point
|
||||
from . import osmanagers # To make sure that packages are initialized at this point
|
||||
from . import transports # To make sure that packages are initialized at this point
|
||||
from . import dispatchers # Ensure all dischatchers all also available
|
||||
from . import plugins # To make sure plugins are loaded on memory
|
||||
from . import REST # To make sure REST initializes all what it needs
|
||||
|
||||
|
||||
default_app_config = 'uds.UDSAppConfig'
|
||||
|
||||
|
||||
|
@@ -43,13 +43,13 @@ from uds.core.ui.UserInterface import gui
|
||||
|
||||
import logging
|
||||
|
||||
__updated__ = '2015-01-21'
|
||||
__updated__ = '2016-04-18'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class IPAuth(Authenticator):
|
||||
acceptProxy = gui.CheckBoxField(label=_('Accept proxy'), order=3, tooltip=_('If checked, requests via proxy will get FORWARDED ip address (take care with this bein checked, can take internal IP addresses from internet)'))
|
||||
acceptProxy = gui.CheckBoxField(label=_('Accept proxy'), order=3, tooltip=_('If checked, requests via proxy will get FORWARDED ip address (take care with this bein checked, can take internal IP addresses from internet)'), tab=gui.ADVANCED_TAB)
|
||||
|
||||
typeName = _('IP Authenticator')
|
||||
typeType = 'IPAuth'
|
||||
|
@@ -40,10 +40,11 @@ from uds.models import Authenticator as dbAuthenticator
|
||||
from uds.core.ui import gui
|
||||
from uds.core.managers import cryptoManager
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util.request import getRequest
|
||||
import dns
|
||||
import logging
|
||||
|
||||
__updated__ = '2015-02-02'
|
||||
__updated__ = '2016-04-20'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -60,14 +61,16 @@ class InternalDBAuth(Authenticator):
|
||||
# This is the only internal source
|
||||
isExternalSource = False
|
||||
|
||||
differentForEachHost = gui.CheckBoxField(label=_('Different user for each host'), order=1, tooltip=_('If checked, each host will have a different user name'), defvalue="false", rdonly=True)
|
||||
reverseDns = gui.CheckBoxField(label=_('Reverse DNS'), order=2, tooltip=_('If checked, the host will be reversed dns'), defvalue="false", rdonly=True)
|
||||
differentForEachHost = gui.CheckBoxField(label=_('Different user for each host'), order=1, tooltip=_('If checked, each host will have a different user name'), defvalue="false", rdonly=True, tab=gui.ADVANCED_TAB)
|
||||
reverseDns = gui.CheckBoxField(label=_('Reverse DNS'), order=2, tooltip=_('If checked, the host will be reversed dns'), defvalue="false", rdonly=True, tab=gui.ADVANCED_TAB)
|
||||
acceptProxy = gui.CheckBoxField(label=_('Accept proxy'), order=3, tooltip=_('If checked, requests via proxy will get FORWARDED ip address (take care with this bein checked, can take internal IP addresses from internet)'), tab=gui.ADVANCED_TAB)
|
||||
|
||||
def initialize(self, values):
|
||||
if values is None:
|
||||
return
|
||||
|
||||
def getIp(self, ip):
|
||||
def getIp(self):
|
||||
ip = getRequest().ip_proxy if self.acceptProxy.isTrue() else getRequest().ip # pylint: disable=maybe-no-member
|
||||
if self.reverseDns.isTrue():
|
||||
try:
|
||||
return str(dns.resolver.query(dns.reversename.from_address(ip), 'PTR')[0])
|
||||
@@ -76,9 +79,8 @@ class InternalDBAuth(Authenticator):
|
||||
return ip
|
||||
|
||||
def transformUsername(self, username):
|
||||
from uds.core.util.request import getRequest
|
||||
if self.differentForEachHost.isTrue():
|
||||
newUsername = self.getIp(getRequest().ip) + '-' + username # pylint: disable=maybe-no-member
|
||||
newUsername = self.getIp() + '-' + username # pylint: disable=maybe-no-member
|
||||
# Duplicate basic user into username.
|
||||
auth = self.dbAuthenticator()
|
||||
# "Derived" users will belong to no group at all, because we will extract groups from "base" user
|
||||
|
@@ -44,7 +44,7 @@ import ldap.filter
|
||||
import re
|
||||
import logging
|
||||
|
||||
__updated__ = '2015-02-02'
|
||||
__updated__ = '2016-04-18'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -56,14 +56,15 @@ class RegexLdap(auths.Authenticator):
|
||||
host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Ldap Server Host'), required=True)
|
||||
port = gui.NumericField(length=5, label=_('Port'), defvalue='389', order=2, tooltip=_('Ldap port (usually 389 for non ssl and 636 for ssl)'), required=True)
|
||||
ssl = gui.CheckBoxField(label=_('Use SSL'), order=3, tooltip=_('If checked, the connection will be ssl, using port 636 instead of 389'))
|
||||
username = gui.TextField(length=64, label=_('Ldap User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True)
|
||||
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True)
|
||||
username = gui.TextField(length=64, label=_('User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True, tab=gui.CREDENTIALS_TAB)
|
||||
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True, tab=gui.CREDENTIALS_TAB)
|
||||
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to LDAP'), required=True, minValue=1)
|
||||
ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True)
|
||||
userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True)
|
||||
userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True)
|
||||
userNameAttr = gui.TextField(length=640, label=_('User Name Attr'), multiline=2, defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True)
|
||||
groupNameAttr = gui.TextField(length=640, label=_('Group Name Attr'), multiline=2, defvalue='cn', order=11, tooltip=_('Attribute that contains the group name'), required=True)
|
||||
|
||||
ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True, tab=_('Ldap info'))
|
||||
userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True, tab=_('Ldap info'))
|
||||
userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True, tab=_('Ldap info'))
|
||||
userNameAttr = gui.TextField(length=640, label=_('User Name Attr'), multiline=2, defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True, tab=_('Ldap info'))
|
||||
groupNameAttr = gui.TextField(length=640, label=_('Group Name Attr'), multiline=2, defvalue='cn', order=11, tooltip=_('Attribute that contains the group name'), required=True, tab=_('Ldap info'))
|
||||
# regex = gui.TextField(length=64, label = _('Regular Exp. for groups'), defvalue = '^(.*)', order = 12, tooltip = _('Regular Expression to extract the group name'), required = True)
|
||||
|
||||
typeName = _('Regex LDAP Authenticator')
|
||||
|
@@ -45,7 +45,7 @@ import ldap
|
||||
import logging
|
||||
import six
|
||||
|
||||
__updated__ = '2015-02-02'
|
||||
__updated__ = '2016-04-18'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -57,16 +57,16 @@ class SimpleLDAPAuthenticator(Authenticator):
|
||||
host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Ldap Server IP or Hostname'), required=True)
|
||||
port = gui.NumericField(length=5, label=_('Port'), defvalue='389', order=2, tooltip=_('Ldap port (usually 389 for non ssl and 636 for ssl)'), required=True)
|
||||
ssl = gui.CheckBoxField(label=_('Use SSL'), order=3, tooltip=_('If checked, the connection will be ssl, using port 636 instead of 389'))
|
||||
username = gui.TextField(length=64, label=_('Ldap User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True)
|
||||
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True)
|
||||
username = gui.TextField(length=64, label=_('Ldap User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True, tab=gui.CREDENTIALS_TAB)
|
||||
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True, tab=gui.CREDENTIALS_TAB)
|
||||
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to LDAP'), required=True, minValue=1)
|
||||
ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True)
|
||||
userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True)
|
||||
userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True)
|
||||
userNameAttr = gui.TextField(length=64, label=_('User Name Attr'), defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True)
|
||||
groupClass = gui.TextField(length=64, label=_('Group class'), defvalue='posixGroup', order=11, tooltip=_('Class for LDAP groups (normally poxisGroup)'), required=True)
|
||||
groupIdAttr = gui.TextField(length=64, label=_('Group Id Attr'), defvalue='cn', order=12, tooltip=_('Attribute that contains the group id'), required=True)
|
||||
memberAttr = gui.TextField(length=64, label=_('Group membership attr'), defvalue='memberUid', order=13, tooltip=_('Attribute of the group that contains the users belonging to it'), required=True)
|
||||
ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True, tab=_('Ldap info'))
|
||||
userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True, tab=_('Ldap info'))
|
||||
userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True, tab=_('Ldap info'))
|
||||
userNameAttr = gui.TextField(length=64, label=_('User Name Attr'), defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True, tab=_('Ldap info'))
|
||||
groupClass = gui.TextField(length=64, label=_('Group class'), defvalue='posixGroup', order=11, tooltip=_('Class for LDAP groups (normally poxisGroup)'), required=True, tab=_('Ldap info'))
|
||||
groupIdAttr = gui.TextField(length=64, label=_('Group Id Attr'), defvalue='cn', order=12, tooltip=_('Attribute that contains the group id'), required=True, tab=_('Ldap info'))
|
||||
memberAttr = gui.TextField(length=64, label=_('Group membership attr'), defvalue='memberUid', order=13, tooltip=_('Attribute of the group that contains the users belonging to it'), required=True, tab=_('Ldap info'))
|
||||
|
||||
typeName = _('SimpleLDAP Authenticator')
|
||||
typeType = 'SimpleLdapAuthenticator'
|
||||
|
@@ -207,8 +207,8 @@ class Module(UserInterface, Environmentable, Serializable):
|
||||
We want to use the env, cache and storage methods outside class.
|
||||
If not called, you must implement your own methods.
|
||||
|
||||
cache and storage are "convenient" methods to access _env.cache() and
|
||||
_env.storage()
|
||||
cache and storage are "convenient" methods to access _env.cache and
|
||||
_env.storage
|
||||
|
||||
The values param is passed directly to UserInterface base.
|
||||
|
||||
|
@@ -59,6 +59,7 @@ class Environment(object):
|
||||
self._storage = Storage(uniqueKey)
|
||||
self._idGenerators = idGenerators
|
||||
|
||||
@property
|
||||
def cache(self):
|
||||
'''
|
||||
Method to acces the cache of the environment.
|
||||
@@ -66,6 +67,7 @@ class Environment(object):
|
||||
'''
|
||||
return self._cache
|
||||
|
||||
@property
|
||||
def storage(self):
|
||||
'''
|
||||
Method to acces the cache of the environment.
|
||||
@@ -83,6 +85,7 @@ class Environment(object):
|
||||
'''
|
||||
return self._idGenerators.get(generatorId, None)
|
||||
|
||||
@property
|
||||
def key(self):
|
||||
'''
|
||||
@return: the key used for this environment
|
||||
@@ -156,15 +159,7 @@ class Environmentable(object):
|
||||
'''
|
||||
self._env = environment
|
||||
|
||||
def setEnv(self, environment):
|
||||
'''
|
||||
Assigns a new environment
|
||||
|
||||
Args:
|
||||
environment: Environment to assign
|
||||
'''
|
||||
self._env = environment
|
||||
|
||||
@property
|
||||
def env(self):
|
||||
'''
|
||||
Utility method to access the envionment contained by this object
|
||||
@@ -174,6 +169,18 @@ class Environmentable(object):
|
||||
'''
|
||||
return self._env
|
||||
|
||||
@env.setter
|
||||
def env(self, environment):
|
||||
'''
|
||||
Assigns a new environment
|
||||
|
||||
Args:
|
||||
environment: Environment to assign
|
||||
'''
|
||||
self._env = environment
|
||||
|
||||
|
||||
@property
|
||||
def cache(self):
|
||||
'''
|
||||
Utility method to access the cache of the environment containe by this object
|
||||
@@ -181,8 +188,9 @@ class Environmentable(object):
|
||||
Returns:
|
||||
Cache for the object
|
||||
'''
|
||||
return self._env.cache()
|
||||
return self._env.cache
|
||||
|
||||
@property
|
||||
def storage(self):
|
||||
'''
|
||||
Utility method to access the storage of the environment containe by this object
|
||||
@@ -190,7 +198,7 @@ class Environmentable(object):
|
||||
Returns:
|
||||
Storage for the object
|
||||
'''
|
||||
return self._env.storage()
|
||||
return self._env.storage
|
||||
|
||||
def idGenerators(self, generatorId):
|
||||
'''
|
||||
|
@@ -38,8 +38,6 @@ from __future__ import unicode_literals
|
||||
from uds.core.Environment import Environmentable
|
||||
from uds.core.Serializable import Serializable
|
||||
from uds.core.BaseModule import Module
|
||||
from uds.core import services
|
||||
from uds.core import auths
|
||||
from uds.core import transports
|
||||
|
||||
VERSION = '1.9.1'
|
||||
VERSION = '2.0.0-DEVEL'
|
||||
VERSION_STAMP = '20160501-DEVEL'
|
||||
|
@@ -53,7 +53,7 @@ from uds.models import User
|
||||
import logging
|
||||
import six
|
||||
|
||||
__updated__ = '2015-11-06'
|
||||
__updated__ = '2016-04-15'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
authLogger = logging.getLogger('authLog')
|
||||
@@ -303,7 +303,7 @@ def webLogout(request, exit_url=None):
|
||||
'''
|
||||
# Invoke exit for authenticator
|
||||
|
||||
if request.user.id != ROOT_ID:
|
||||
if request.user is not None and request.user.id != ROOT_ID:
|
||||
events.addEvent(request.user.manager, events.ET_LOGOUT, username=request.user.name, srcip=request.ip)
|
||||
|
||||
request.session.clear()
|
||||
|
@@ -44,7 +44,7 @@ import threading
|
||||
import time
|
||||
import logging
|
||||
|
||||
__updated__ = '2016-02-01'
|
||||
__updated__ = '2016-03-07'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -60,8 +60,8 @@ class DelayedTaskThread(threading.Thread):
|
||||
def run(self):
|
||||
try:
|
||||
self._taskInstance.execute()
|
||||
except Exception, e:
|
||||
logger.debug("Exception in thread {0}: {1}".format(e.__class__, e))
|
||||
except Exception as e:
|
||||
logger.exception("Exception in thread {0}: {1}".format(e.__class__, e))
|
||||
|
||||
|
||||
class DelayedTaskRunner(object):
|
||||
@@ -116,8 +116,7 @@ class DelayedTaskRunner(object):
|
||||
|
||||
if taskInstance is not None:
|
||||
logger.debug('Executing delayedTask:>{0}<'.format(task))
|
||||
env = Environment.getEnvForType(taskInstance.__class__)
|
||||
taskInstance.setEnv(env)
|
||||
taskInstance.env = Environment.getEnvForType(taskInstance.__class__)
|
||||
DelayedTaskThread(taskInstance).start()
|
||||
|
||||
def __insert(self, instance, delay, tag):
|
||||
|
@@ -43,7 +43,7 @@ import threading
|
||||
import time
|
||||
import logging
|
||||
|
||||
__updated__ = '2015-10-15'
|
||||
__updated__ = '2016-05-18'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -175,6 +175,7 @@ class Scheduler(object):
|
||||
logger.debug('Releasing all owned scheduled tasks')
|
||||
with transaction.atomic():
|
||||
dbScheduler.objects.select_for_update().filter(owner_server=platform.node()).update(owner_server='') # @UndefinedVariable
|
||||
dbScheduler.objects.select_for_update().filter(last_execution__lt=getSqlDatetime() - timedelta(minutes=15), state=State.RUNNING).update(owner_server='', state=State.FOR_EXECUTE) # @UndefinedVariable
|
||||
dbScheduler.objects.select_for_update().filter(owner_server='').update(state=State.FOR_EXECUTE) # @UndefinedVariable
|
||||
|
||||
def run(self):
|
||||
|
@@ -35,7 +35,7 @@ from __future__ import unicode_literals
|
||||
import os
|
||||
import uuid
|
||||
from django.http import HttpResponse, Http404
|
||||
from django.core.servers.basehttp import FileWrapper
|
||||
from wsgiref.util import FileWrapper
|
||||
|
||||
from uds.core.managers import cryptoManager
|
||||
|
||||
|
@@ -41,6 +41,8 @@ from uds.models import DeployedServicePublication, getSqlDatetime
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util import log
|
||||
import logging
|
||||
import datetime
|
||||
import pickle
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -91,6 +93,7 @@ class PublicationLauncher(DelayedTask):
|
||||
state = pi.publish()
|
||||
deployedService = servicePoolPub.deployed_service
|
||||
deployedService.current_pub_revision += 1
|
||||
deployedService.storeValue('toBeReplacedIn', pickle.dumps(datetime.datetime.now() + datetime.timedelta(hours=GlobalConfig.SESSION_EXPIRE_TIME.getInt(True))))
|
||||
deployedService.save()
|
||||
PublicationFinishChecker.checkAndUpdateState(servicePoolPub, pi, state)
|
||||
except Exception:
|
||||
|
@@ -35,140 +35,26 @@ from __future__ import unicode_literals
|
||||
from django.utils.translation import ugettext as _
|
||||
from django.db.models import Q
|
||||
from django.db import transaction
|
||||
from uds.core.jobs.DelayedTask import DelayedTask
|
||||
from uds.core.jobs.DelayedTaskRunner import DelayedTaskRunner
|
||||
from uds.core.services.Exceptions import OperationException
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util import log
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
from uds.core.services.Exceptions import MaxServicesReachedError, ServiceInMaintenanceMode, InvalidServiceException, ServiceNotReadyError
|
||||
from uds.core.services.Exceptions import MaxServicesReachedError, ServiceInMaintenanceMode, InvalidServiceException, ServiceNotReadyError, ServiceAccessDeniedByCalendar
|
||||
from uds.models import ServicePool, UserService, getSqlDatetime, Transport
|
||||
from uds.core import services
|
||||
from uds.core.services import Service
|
||||
from uds.core.util.stats import events
|
||||
|
||||
from .userservice.opchecker import UserServiceOpChecker
|
||||
|
||||
import requests
|
||||
import json
|
||||
import logging
|
||||
|
||||
__updated__ = '2015-11-11'
|
||||
__updated__ = '2016-10-14'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
USERSERVICE_TAG = 'cm-'
|
||||
|
||||
|
||||
class UserServiceOpChecker(DelayedTask):
|
||||
def __init__(self, service):
|
||||
super(UserServiceOpChecker, self).__init__()
|
||||
self._svrId = service.id
|
||||
self._state = service.state
|
||||
|
||||
@staticmethod
|
||||
def makeUnique(userService, userServiceInstance, state):
|
||||
'''
|
||||
This method ensures that there will be only one delayedtask related to the userService indicated
|
||||
'''
|
||||
DelayedTaskRunner.runner().remove(USERSERVICE_TAG + str(userService.id))
|
||||
UserServiceOpChecker.checkAndUpdateState(userService, userServiceInstance, state)
|
||||
|
||||
@staticmethod
|
||||
def checkAndUpdateState(userService, userServiceInstance, state):
|
||||
'''
|
||||
Checks the value returned from invocation to publish or checkPublishingState, updating the servicePoolPub database object
|
||||
Return True if it has to continue checking, False if finished
|
||||
'''
|
||||
try:
|
||||
prevState = userService.state
|
||||
userService.unique_id = userServiceInstance.getUniqueId() # Updates uniqueId
|
||||
userService.friendly_name = userServiceInstance.getName() # And name, both methods can modify serviceInstance, so we save it later
|
||||
if State.isFinished(state):
|
||||
checkLater = False
|
||||
userServiceInstance.finish()
|
||||
if State.isPreparing(prevState):
|
||||
if userServiceInstance.service().publicationType is None or userService.publication == userService.deployed_service.activePublication():
|
||||
userService.setState(State.USABLE)
|
||||
# and make this usable if os manager says that it is usable, else it pass to configuring state
|
||||
if userServiceInstance.osmanager() is not None and userService.os_state == State.PREPARING: # If state is already "Usable", do not recheck it
|
||||
stateOs = userServiceInstance.osmanager().checkState(userService)
|
||||
# If state is finish, we need to notify the userService again that os has finished
|
||||
if State.isFinished(stateOs):
|
||||
state = userServiceInstance.notifyReadyFromOsManager('')
|
||||
userService.updateData(userServiceInstance)
|
||||
else:
|
||||
stateOs = State.FINISHED
|
||||
|
||||
if State.isRuning(stateOs):
|
||||
userService.setOsState(State.PREPARING)
|
||||
else:
|
||||
userService.setOsState(State.USABLE)
|
||||
else:
|
||||
# We ignore OsManager info and if userService don't belong to "current" publication, mark it as removable
|
||||
userService.setState(State.REMOVABLE)
|
||||
elif State.isRemoving(prevState):
|
||||
if userServiceInstance.osmanager() is not None:
|
||||
userServiceInstance.osmanager().release(userService)
|
||||
userService.setState(State.REMOVED)
|
||||
else:
|
||||
# Canceled,
|
||||
logger.debug("Canceled us {2}: {0}, {1}".format(prevState, State.toString(state), State.toString(userService)))
|
||||
userService.setState(State.CANCELED)
|
||||
userServiceInstance.osmanager().release(userService)
|
||||
userService.updateData(userServiceInstance)
|
||||
elif State.isErrored(state):
|
||||
checkLater = False
|
||||
userService.updateData(userServiceInstance)
|
||||
userService.setState(State.ERROR)
|
||||
else:
|
||||
checkLater = True # The task is running
|
||||
userService.updateData(userServiceInstance)
|
||||
userService.save()
|
||||
if checkLater:
|
||||
UserServiceOpChecker.checkLater(userService, userServiceInstance)
|
||||
except Exception as e:
|
||||
logger.exception('Checking service state')
|
||||
log.doLog(userService, log.ERROR, 'Exception: {0}'.format(e), log.INTERNAL)
|
||||
userService.setState(State.ERROR)
|
||||
userService.save()
|
||||
|
||||
@staticmethod
|
||||
def checkLater(userService, ci):
|
||||
'''
|
||||
Inserts a task in the delayedTaskRunner so we can check the state of this publication
|
||||
@param dps: Database object for DeployedServicePublication
|
||||
@param pi: Instance of Publication manager for the object
|
||||
'''
|
||||
# Do not add task if already exists one that updates this service
|
||||
if DelayedTaskRunner.runner().checkExists(USERSERVICE_TAG + str(userService.id)):
|
||||
return
|
||||
DelayedTaskRunner.runner().insert(UserServiceOpChecker(userService), ci.suggestedTime, USERSERVICE_TAG + str(userService.id))
|
||||
|
||||
def run(self):
|
||||
logger.debug('Checking user service finished {0}'.format(self._svrId))
|
||||
uService = None
|
||||
try:
|
||||
uService = UserService.objects.get(pk=self._svrId)
|
||||
if uService.state != self._state:
|
||||
logger.debug('Task overrided by another task (state of item changed)')
|
||||
# This item is no longer valid, returning will not check it again (no checkLater called)
|
||||
return
|
||||
ci = uService.getInstance()
|
||||
logger.debug("uService instance class: {0}".format(ci.__class__))
|
||||
state = ci.checkState()
|
||||
UserServiceOpChecker.checkAndUpdateState(uService, ci, state)
|
||||
except UserService.DoesNotExist, e:
|
||||
logger.error('User service not found (erased from database?) {0} : {1}'.format(e.__class__, e))
|
||||
except Exception, e:
|
||||
# Exception caught, mark service as errored
|
||||
logger.exception("Error {0}, {1} :".format(e.__class__, e))
|
||||
if uService is not None:
|
||||
log.doLog(uService, log.ERROR, 'Exception: {0}'.format(e), log.INTERNAL)
|
||||
try:
|
||||
uService.setState(State.ERROR)
|
||||
uService.save()
|
||||
except Exception:
|
||||
logger.error('Can\'t update state of uService object')
|
||||
|
||||
|
||||
class UserServiceManager(object):
|
||||
_manager = None
|
||||
@@ -436,9 +322,11 @@ class UserServiceManager(object):
|
||||
def canRemoveServiceFromDeployedService(self, ds):
|
||||
'''
|
||||
checks if we can do a "remove" from a deployed service
|
||||
serviceIsntance is just a helper, so if we already have unserialized deployedService
|
||||
'''
|
||||
removing = self.getServicesInStateForProvider(ds.service.provider_id, State.REMOVING)
|
||||
if removing >= GlobalConfig.MAX_REMOVING_SERVICES.getInt() and GlobalConfig.IGNORE_LIMITS.getBool() is False:
|
||||
serviceInstance = ds.service.getInstance()
|
||||
if removing >= serviceInstance.parent().getMaxRemovingServices() and serviceInstance.parent().getIgnoreLimits() is False:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -447,7 +335,8 @@ class UserServiceManager(object):
|
||||
Checks if we can start a new service
|
||||
'''
|
||||
preparing = self.getServicesInStateForProvider(ds.service.provider_id, State.PREPARING)
|
||||
if preparing >= GlobalConfig.MAX_PREPARING_SERVICES.getInt() and GlobalConfig.IGNORE_LIMITS.getBool() is False:
|
||||
serviceInstance = ds.service.getInstance()
|
||||
if preparing >= serviceInstance.parent().getMaxPreparingServices() and serviceInstance.parent().getIgnoreLimits() is False:
|
||||
return False
|
||||
return True
|
||||
|
||||
@@ -489,6 +378,34 @@ class UserServiceManager(object):
|
||||
except Exception as e:
|
||||
logger.info('preConnection failed: {}. Check connection on destination machine: {}'.format(e, url))
|
||||
|
||||
def checkUuid(self, uService):
|
||||
|
||||
url = uService.getCommsUrl()
|
||||
|
||||
if url is None:
|
||||
logger.debug('No uuid to retrieve because agent does not supports notifications')
|
||||
return True # UUid is valid because it is not supported checking it
|
||||
|
||||
if uService.getProperty('actor_version', '') < '2.0.0': # Just for 2.0 or newer, previous actors will not support this method
|
||||
return True
|
||||
|
||||
url += '/uuid'
|
||||
|
||||
try:
|
||||
r = requests.get(url, verify=False, timeout=5)
|
||||
uuid = json.loads(r.content)
|
||||
if uuid != uService.uuid:
|
||||
logger.info('The requested machine has uuid {} and the expected was {}'.format(uuid, uService.uuid))
|
||||
return False
|
||||
|
||||
logger.debug('Got uuid from machine: {} {} {}'.format(url, uuid, uService.uuid))
|
||||
# In fact we ignore result right now
|
||||
except Exception as e:
|
||||
logger.info('Get uuid failed: {}. Check connection on destination machine: {}'.format(e, url))
|
||||
# return True
|
||||
|
||||
return True
|
||||
|
||||
def sendScript(self, uService, script):
|
||||
'''
|
||||
If allowed, send script to user service
|
||||
@@ -553,10 +470,13 @@ class UserServiceManager(object):
|
||||
# Now we have to locate an instance of the service, so we can assign it to user.
|
||||
userService = self.getAssignationForUser(ds, user)
|
||||
|
||||
logger.debug('Found service: {0}'.format(userService))
|
||||
|
||||
if userService.isInMaintenance() is True:
|
||||
raise ServiceInMaintenanceMode()
|
||||
|
||||
logger.debug('Found service: {0}'.format(userService))
|
||||
if userService.deployed_service.isAccessAllowed() is False:
|
||||
raise ServiceAccessDeniedByCalendar()
|
||||
|
||||
if idTransport is None or idTransport == '': # Find a suitable transport
|
||||
for v in userService.deployed_service.transports.order_by('priority'):
|
||||
@@ -589,20 +509,26 @@ class UserServiceManager(object):
|
||||
# If ready, show transport for this service, if also ready ofc
|
||||
iads = userService.getInstance()
|
||||
ip = iads.getIp()
|
||||
events.addEvent(userService.deployed_service, events.ET_ACCESS, username=user.name, srcip=srcIp, dstip=ip, uniqueid=userService.unique_id)
|
||||
if ip is not None:
|
||||
serviceNotReadyCode = 0x0003
|
||||
itrans = trans.getInstance()
|
||||
if itrans.isAvailableFor(userService, ip):
|
||||
userService.setConnectionSource(srcIp, 'unknown')
|
||||
log.doLog(userService, log.INFO, "User service ready", log.WEB)
|
||||
UserServiceManager.manager().notifyPreconnect(userService, itrans.processedUser(userService, user), itrans.protocol)
|
||||
return (ip, userService, iads, trans, itrans)
|
||||
else:
|
||||
log.doLog(userService, log.WARN, "User service is not accessible (ip {0})".format(ip), log.TRANSPORT)
|
||||
logger.debug('Transport is not ready for user service {0}'.format(userService))
|
||||
|
||||
if self.checkUuid(userService) is False: # Machine is not what is expected
|
||||
serviceNotReadyCode = 0x0004
|
||||
log.doLog(userService, log.WARN, "User service is not accessible (ip {0})".format(ip), log.TRANSPORT)
|
||||
logger.debug('Transport is not ready for user service {0}'.format(userService))
|
||||
else:
|
||||
logger.debug('Ip not available from user service {0}'.format(userService))
|
||||
events.addEvent(userService.deployed_service, events.ET_ACCESS, username=user.name, srcip=srcIp, dstip=ip, uniqueid=userService.unique_id)
|
||||
if ip is not None:
|
||||
serviceNotReadyCode = 0x0003
|
||||
itrans = trans.getInstance()
|
||||
if itrans.isAvailableFor(userService, ip):
|
||||
userService.setConnectionSource(srcIp, 'unknown')
|
||||
log.doLog(userService, log.INFO, "User service ready", log.WEB)
|
||||
self.notifyPreconnect(userService, itrans.processedUser(userService, user), itrans.protocol)
|
||||
return (ip, userService, iads, trans, itrans)
|
||||
else:
|
||||
log.doLog(userService, log.WARN, "User service is not accessible (ip {0})".format(ip), log.TRANSPORT)
|
||||
logger.debug('Transport is not ready for user service {0}'.format(userService))
|
||||
else:
|
||||
logger.debug('Ip not available from user service {0}'.format(userService))
|
||||
else:
|
||||
log.doLog(userService, log.WARN, "User {0} from {1} tried to access, but service was not ready".format(user.name, srcIp), log.WEB)
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user