forked from shaba/openuds
Compare commits
201 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
1a18ff1d5e | ||
|
56553a70a1 | ||
|
2c10e9445b | ||
|
4ca387c370 | ||
|
67eb4332b7 | ||
|
a6077a61b4 | ||
|
dc42433b86 | ||
|
f67730cfad | ||
|
8b8135ea98 | ||
|
07c304cecb | ||
|
a3dd038e79 | ||
|
f3522ae154 | ||
|
67feb4aefa | ||
|
5953a67d3a | ||
|
c6f3c87b93 | ||
|
eb270d883e | ||
|
5ddd2519b0 | ||
|
e5574fe2ba | ||
|
4b9b386f14 | ||
|
8a61986dad | ||
|
d068bcef60 | ||
|
4c001b044f | ||
|
70141ae972 | ||
|
20ead2954b | ||
|
8331c886de | ||
|
66c217a988 | ||
|
d6935d0210 | ||
|
c47501e3e6 | ||
|
7a2bd7dcf5 | ||
|
87e7f1ca3c | ||
|
ea11a25c52 | ||
|
f79f659711 | ||
|
fddc69e0ea | ||
|
b795260e4d | ||
|
66464df043 | ||
|
5d4e05309a | ||
|
a288dd4df3 | ||
|
6156f2c6f6 | ||
|
b1f3407299 | ||
|
2b653b8b05 | ||
|
cd7beac87e | ||
|
dbee05d1bc | ||
|
9f01f2c368 | ||
|
9e26580af5 | ||
|
689b20ce2f | ||
|
bac0b9755e | ||
|
33d2ca4ece | ||
|
efb3965d76 | ||
|
4778f5f931 | ||
|
78656ed4a2 | ||
|
278bcb87de | ||
|
690fd7c9cc | ||
|
7b51bb7985 | ||
|
6f81892324 | ||
|
a496fd97f1 | ||
|
77bf401bd5 | ||
|
812e863e7f | ||
|
cd2d529524 | ||
|
8f76363b27 | ||
|
6a05403464 | ||
|
43398cd125 | ||
|
481eca7d16 | ||
|
e382bfa19b | ||
|
9094547f22 | ||
|
c2bd664545 | ||
|
8b4485d533 | ||
|
682ce422b6 | ||
|
906824cafc | ||
|
eb6065b203 | ||
|
9451889bb3 | ||
|
8120ae5c92 | ||
|
d1bb018dff | ||
|
43cb02b177 | ||
|
3e3ea59261 | ||
|
688a91325c | ||
|
97e8c42b2f | ||
|
e814c47a24 | ||
|
9fb19c6b9d | ||
|
4375ee6d50 | ||
|
ff2f508240 | ||
|
68a58b7c73 | ||
|
4441286c89 | ||
|
c9410fbbac | ||
|
43157d722f | ||
|
861258532d | ||
|
ba45e4c6d6 | ||
|
8088efa225 | ||
|
300f2f3333 | ||
|
c1e2e12dd9 | ||
|
265d2d8702 | ||
|
95e99670ee | ||
|
73be005f6a | ||
|
4c30e9d45e | ||
|
68fbaf7ae0 | ||
|
cf7e8f19e2 | ||
|
256a7f2584 | ||
|
7f1a776f69 | ||
|
d5be31f1d9 | ||
|
a47df49ca8 | ||
|
2e0972b71e | ||
|
ee9a062201 | ||
|
8384a43699 | ||
|
8c882852db | ||
|
cbdd61cfa8 | ||
|
e642f4df18 | ||
|
8c85639dfe | ||
|
5fbdb6834d | ||
|
6bcf8a832f | ||
|
aa4bbcbeb3 | ||
|
5863962e8a | ||
|
c2a95711da | ||
|
eb3188c6c1 | ||
|
34b8902c71 | ||
|
3f76f6c1ab | ||
|
6cee32d680 | ||
|
cce00adc51 | ||
|
4b75be2f02 | ||
|
34046c8a7e | ||
|
f177b98c78 | ||
|
4777b7e8f9 | ||
|
fbbdc529fe | ||
|
e9f61b6a94 | ||
|
f333f2f71f | ||
|
6c2a7ff6e6 | ||
|
1936a02cf7 | ||
|
d8eb440b34 | ||
|
4570d79645 | ||
|
6936f6994d | ||
|
d60f1d82db | ||
|
c72a6a32dc | ||
|
7bdb3d77ba | ||
|
f211f3482f | ||
|
ff226dd8e5 | ||
|
b0268346e5 | ||
|
9906b80702 | ||
|
be4a4a5b09 | ||
|
070088909e | ||
|
e9d23cf170 | ||
|
b5ebd1f1fb | ||
|
0e99f53f0d | ||
|
2bbc74a87c | ||
|
9e270f6f7e | ||
|
ba09bab8a1 | ||
|
4c4a54e50b | ||
|
0648a27daf | ||
|
6db1fdb86d | ||
|
b1223f623b | ||
|
22a78d8e69 | ||
|
39b6a59538 | ||
|
772a31da37 | ||
|
0070b1618b | ||
|
3b04e2a180 | ||
|
5d43c1dbff | ||
|
9c65db5c25 | ||
|
a6397e9ff6 | ||
|
5909533161 | ||
|
f3420a50ac | ||
|
1bd56debb9 | ||
|
4641fde008 | ||
|
1d3d79c562 | ||
|
33600bda6e | ||
|
e4807cf648 | ||
|
92de41d410 | ||
|
ee72b65534 | ||
|
dba5e4267a | ||
|
f6d78201cb | ||
|
8162230f23 | ||
|
bfb96b6d9f | ||
|
b37c87afb5 | ||
|
df98efc2da | ||
|
7320ba0e6d | ||
|
4271be9340 | ||
|
b56b3ef6d8 | ||
|
5f74c7fca8 | ||
|
0bd09e70bf | ||
|
9e991eebb2 | ||
|
ab8faf3ebb | ||
|
752b84a17a | ||
|
4ffdd4b882 | ||
|
d59661eb05 | ||
|
a03f86f031 | ||
|
cc033105e2 | ||
|
a1bf0b92bb | ||
|
b6e5aeac29 | ||
|
3420ac44be | ||
|
d0fb880880 | ||
|
f43a65e026 | ||
|
ea0727993e | ||
|
8d00883213 | ||
|
8ad77c736d | ||
|
34ff0259ce | ||
|
b4ba79cad2 | ||
|
5a13f52d8f | ||
|
0242eaaa54 | ||
|
7ec92d6005 | ||
|
dbc2bc1754 | ||
|
18f75ecb37 | ||
|
033a6265a8 | ||
|
02ab1a8c7c | ||
|
89155d6db9 | ||
|
9b35224fc5 |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -7,6 +7,7 @@
|
||||
*_enterprise.*
|
||||
.settings/
|
||||
.ipynb_checkpoints
|
||||
.mypy_cache
|
||||
|
||||
# Debian buildings
|
||||
*.debhelper*
|
||||
@@ -164,3 +165,5 @@
|
||||
/udsService/udsgui/obj/Debug
|
||||
/udsService/udsgui/obj/Release
|
||||
/udsService/udsgui/obj/x86
|
||||
|
||||
.vscode
|
||||
|
@@ -1,3 +1,9 @@
|
||||
udsactor (2.2.1) stable; urgency=medium
|
||||
|
||||
* Upgraded to 2.2.1 release
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 2 Oct 2018 12:44:12 +0200
|
||||
|
||||
udsactor (2.2.0) stable; urgency=medium
|
||||
|
||||
* Upgraded to 2.2.0 release
|
||||
|
@@ -10,22 +10,8 @@ Package: udsactor
|
||||
Section: admin
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: policykit-1(>=0.100), python-requests (>=0.8.2), python-qt4 (>=4.9), python-six(>=1.1), python-prctl(>=1.1.1), python (>=2.7), libxss1, ${misc:Depends}
|
||||
Depends: policykit-1(>=0.100), python3-requests (>=0.8.2), python3-pyqt4 (>=4.9), python3-six(>=1.1), python3 (>=3.4), libxss1, xscreensaver, ${misc:Depends}
|
||||
Recommends: python3-prctl(>=1.1.1)
|
||||
Description: Actor for Universal Desktop Services (UDS) Broker
|
||||
This package provides the required components to allow this machine to work on an environment managed by UDS Broker.
|
||||
|
||||
Package: udsactor-xrdp
|
||||
Section: x11
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: xrdp (>= 0.5.0), udsactor (>= ${binary:Version}), libpam-modules-bin (>=1.0), ${misc:Depends}
|
||||
Description: UDS Actor component for xrdp
|
||||
This package provides connection between uds actor and xrdp
|
||||
|
||||
Package: udsactor-nx
|
||||
Section: x11
|
||||
Priority: optional
|
||||
Architecture: all
|
||||
Depends: nxnode (>= 3.5.0), udsactor (>= ${binary:Version}), ${misc:Depends}
|
||||
Description: UDS Actor component for nx
|
||||
This package provides connection between uds actor and nx
|
||||
|
||||
|
@@ -5,7 +5,7 @@
|
||||
set -e
|
||||
case "$1" in
|
||||
configure)
|
||||
/usr/bin/python2.7 -m compileall /usr/share/UDSActor > /dev/nul 2>&1
|
||||
/usr/bin/python3 -m compileall /usr/share/UDSActor > /dev/nul 2>&1
|
||||
# If new "fresh" install or if configuration file has disappeared...
|
||||
if [ "$2" = "" ] || [ ! -f /etc/udsactor/udsactor.cfg ]; then
|
||||
db_get udsactor/host
|
||||
|
@@ -3,4 +3,4 @@
|
||||
FOLDER=/usr/share/UDSActor
|
||||
|
||||
cd $FOLDER
|
||||
python2.7 -m udsactor.linux.UDSActorService $@
|
||||
exec python3 -m udsactor.linux.UDSActorService $@
|
||||
|
2
actors/src/.gitignore
vendored
2
actors/src/.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
build
|
||||
dist
|
||||
*.spec
|
||||
.idea
|
||||
*_enterprise*
|
||||
/samples/
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2.7
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
@@ -33,6 +33,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import os
|
||||
from PyQt4 import QtCore, QtGui
|
||||
import six
|
||||
|
||||
@@ -92,7 +93,13 @@ class UDSConfigDialog(QtGui.QDialog):
|
||||
store.writeConfig(cfg)
|
||||
self.close()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
# If to be run as "sudo" on linux, we will need this to avoid problems
|
||||
if 'linux' in sys.platform:
|
||||
os.environ['QT_X11_NO_MITSHM'] = '1'
|
||||
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
||||
if store.checkPermissions() is False:
|
||||
|
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2.7
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
@@ -39,6 +39,7 @@ import pickle
|
||||
import time
|
||||
import datetime
|
||||
import signal
|
||||
import six
|
||||
from udsactor import ipc
|
||||
from udsactor import utils
|
||||
from udsactor.log import logger
|
||||
@@ -53,14 +54,17 @@ trayIcon = None
|
||||
|
||||
doLogoff = False
|
||||
|
||||
TIMER_TIMEOUT = 5 # In seconds
|
||||
|
||||
|
||||
def sigTerm(sigNo, stackFrame):
|
||||
if trayIcon:
|
||||
trayIcon.quit()
|
||||
trayIcon.quit(extra=" (by sigterm)")
|
||||
|
||||
|
||||
# About dialog
|
||||
class UDSAboutDialog(QtGui.QDialog):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QtGui.QDialog.__init__(self, parent)
|
||||
self.ui = Ui_UDSAboutDialog()
|
||||
@@ -72,6 +76,7 @@ class UDSAboutDialog(QtGui.QDialog):
|
||||
|
||||
|
||||
class UDSMessageDialog(QtGui.QDialog):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
QtGui.QDialog.__init__(self, parent)
|
||||
self.ui = Ui_UDSMessageDialog()
|
||||
@@ -88,13 +93,13 @@ class UDSMessageDialog(QtGui.QDialog):
|
||||
class MessagesProcessor(QtCore.QThread):
|
||||
|
||||
logoff = QtCore.pyqtSignal(name='logoff')
|
||||
displayMessage = QtCore.pyqtSignal(QtCore.QString, name='displayMessage')
|
||||
script = QtCore.pyqtSignal(QtCore.QString, name='script')
|
||||
displayMessage = QtCore.pyqtSignal(six.text_type, name='displayMessage')
|
||||
script = QtCore.pyqtSignal(six.text_type, name='script')
|
||||
exit = QtCore.pyqtSignal(name='exit')
|
||||
information = QtCore.pyqtSignal(dict, name='information')
|
||||
|
||||
def __init__(self):
|
||||
super(self.__class__, self).__init__()
|
||||
super(MessagesProcessor, self).__init__()
|
||||
# Retries connection for a while
|
||||
for _ in range(10):
|
||||
try:
|
||||
@@ -145,11 +150,11 @@ class MessagesProcessor(QtCore.QThread):
|
||||
msgId, data = msg
|
||||
logger.debug('Got Message on User Space: {}:{}'.format(msgId, data))
|
||||
if msgId == ipc.MSG_MESSAGE:
|
||||
self.displayMessage.emit(QtCore.QString.fromUtf8(data))
|
||||
self.displayMessage.emit(data)
|
||||
elif msgId == ipc.MSG_LOGOFF:
|
||||
self.logoff.emit()
|
||||
elif msgId == ipc.MSG_SCRIPT:
|
||||
self.script.emit(QtCore.QString.fromUtf8(data))
|
||||
self.script.emit(data)
|
||||
elif msgId == ipc.MSG_INFORMATION:
|
||||
self.information.emit(pickle.loads(data))
|
||||
except Exception as e:
|
||||
@@ -165,6 +170,7 @@ class MessagesProcessor(QtCore.QThread):
|
||||
|
||||
|
||||
class UDSSystemTray(QtGui.QSystemTrayIcon):
|
||||
|
||||
def __init__(self, app_, parent=None):
|
||||
self.app = app_
|
||||
|
||||
@@ -205,20 +211,34 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
|
||||
|
||||
self.counter = 0
|
||||
|
||||
self.timer.start(5000) # Launch idle checking every 5 seconds
|
||||
self.graceTimerShots = 6 # Start counting for idle after 30 seconds after login, got on windows some "instant" logout because of idle timer not being reset??
|
||||
self.resetTimervars()
|
||||
self.timer.start(TIMER_TIMEOUT * 1000) # Launch idle checking every 5 seconds
|
||||
|
||||
self.ipc.start()
|
||||
# If this is running, it's because he have logged in
|
||||
self.ipc.sendLogin(operations.getCurrentUser())
|
||||
|
||||
def resetTimervars(self):
|
||||
self.lastTimerTime = datetime.datetime.now()
|
||||
self.graceTimerShots = 6 # Start counting for idle after 30 seconds after login, got on windows some "instant" logout because of idle timer not being reset??
|
||||
|
||||
def checkTimers(self):
|
||||
# Check clock readjustment
|
||||
# This is executed
|
||||
elapsed_seconds = (datetime.datetime.now() - self.lastTimerTime).total_seconds()
|
||||
if elapsed_seconds > TIMER_TIMEOUT * 4 or elapsed_seconds < 0:
|
||||
# Clock has changed a lot, reset session variables, idle timer, etc..
|
||||
self.resetTimervars()
|
||||
return
|
||||
|
||||
self.lastTimerTime = datetime.datetime.now()
|
||||
|
||||
self.checkIdle()
|
||||
self.checkMaxSession()
|
||||
|
||||
def checkMaxSession(self):
|
||||
if self.maxSessionTime is None or self.maxSessionTime == 0:
|
||||
logger.debug('Returning because maxSessionTime is cero')
|
||||
logger.debug('Returning because maxSessionTime is zero')
|
||||
return
|
||||
|
||||
remainingTime = self.maxSessionTime - (datetime.datetime.now() - self.sessionStart).total_seconds()
|
||||
@@ -231,7 +251,7 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
|
||||
|
||||
if remainingTime <= 0:
|
||||
logger.debug('Remaining time is less than cero, exiting')
|
||||
self.quit()
|
||||
self.quit(extra=" (max session time {} {})".format(self.maxSessionTime, self.sessionStart))
|
||||
|
||||
def checkIdle(self):
|
||||
if self.maxIdleTime is None: # No idle check
|
||||
@@ -255,7 +275,7 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
|
||||
|
||||
if remainingTime <= 0:
|
||||
logger.info('User has been idle for too long, notifying Broker that service can be reclaimed')
|
||||
self.quit(logoff=True)
|
||||
self.quit(logoff=True, extra=' (idle: {} vs {})'.format(idleTime, self.maxIdleTime))
|
||||
|
||||
def displayMessage(self, message):
|
||||
logger.debug('Displaying message')
|
||||
@@ -292,14 +312,15 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
|
||||
def about(self):
|
||||
self.aboutDlg.exec_()
|
||||
|
||||
def quit(self, logoff=False):
|
||||
global doLogoff
|
||||
def quit(self, logoff=False, extra=''):
|
||||
global doLogoff # pylint: disable=global-statement
|
||||
logger.debug('Quit invoked')
|
||||
if not self.stopped:
|
||||
self.stopped = True
|
||||
try:
|
||||
# If we close Client, send Logoff to Broker
|
||||
self.ipc.sendLogout(operations.getCurrentUser())
|
||||
# if sys.platform != 'win32':
|
||||
self.ipc.sendLogout(operations.getCurrentUser() + extra)
|
||||
self.timer.stop()
|
||||
self.ipc.stop()
|
||||
except Exception:
|
||||
@@ -310,12 +331,13 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
|
||||
|
||||
self.app.quit()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QtGui.QApplication(sys.argv)
|
||||
|
||||
if not QtGui.QSystemTrayIcon.isSystemTrayAvailable():
|
||||
# QtGui.QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.")
|
||||
sys.exit(1)
|
||||
# if not QtGui.QSystemTrayIcon.isSystemTrayAvailable():
|
||||
# # QtGui.QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.")
|
||||
# sys.exit(1)
|
||||
|
||||
# This is important so our app won't close on message windows
|
||||
QtGui.QApplication.setQuitOnLastWindowClosed(False)
|
||||
@@ -334,10 +356,11 @@ if __name__ == '__main__':
|
||||
# Catch kill and logout user :)
|
||||
signal.signal(signal.SIGTERM, sigTerm)
|
||||
|
||||
# app.aboutToQuit.connect()
|
||||
res = app.exec_()
|
||||
|
||||
logger.debug('Exiting')
|
||||
trayIcon.quit()
|
||||
trayIcon.quit(logoff=doLogoff) # Pass existing doLogoff
|
||||
|
||||
if doLogoff:
|
||||
try:
|
||||
@@ -346,5 +369,4 @@ if __name__ == '__main__':
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
|
||||
sys.exit(res)
|
||||
|
@@ -2,8 +2,7 @@
|
||||
|
||||
# Resource object code
|
||||
#
|
||||
# Created: Mon Apr 27 22:05:02 2015
|
||||
# by: The Resource Compiler for PyQt (Qt v4.8.6)
|
||||
# Created by: The Resource Compiler for PyQt4 (Qt v4.8.7)
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
|
@@ -2,8 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'about-dialog.ui'
|
||||
#
|
||||
# Created: Mon Apr 27 22:05:02 2015
|
||||
# by: PyQt4 UI code generator 4.11.2
|
||||
# Created by: PyQt4 UI code generator 4.12.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -34,8 +33,8 @@ class Ui_UDSAboutDialog(object):
|
||||
UDSAboutDialog.setLocale(QtCore.QLocale(QtCore.QLocale.English, QtCore.QLocale.UnitedStates))
|
||||
UDSAboutDialog.setModal(True)
|
||||
self.vboxlayout = QtGui.QVBoxLayout(UDSAboutDialog)
|
||||
self.vboxlayout.setSpacing(9)
|
||||
self.vboxlayout.setMargin(9)
|
||||
self.vboxlayout.setSpacing(9)
|
||||
self.vboxlayout.setObjectName(_fromUtf8("vboxlayout"))
|
||||
self.LogoLabel = QtGui.QLabel(UDSAboutDialog)
|
||||
self.LogoLabel.setObjectName(_fromUtf8("LogoLabel"))
|
||||
@@ -55,8 +54,8 @@ class Ui_UDSAboutDialog(object):
|
||||
self.aboutTab = QtGui.QWidget()
|
||||
self.aboutTab.setObjectName(_fromUtf8("aboutTab"))
|
||||
self.vboxlayout1 = QtGui.QVBoxLayout(self.aboutTab)
|
||||
self.vboxlayout1.setSpacing(6)
|
||||
self.vboxlayout1.setMargin(9)
|
||||
self.vboxlayout1.setSpacing(6)
|
||||
self.vboxlayout1.setObjectName(_fromUtf8("vboxlayout1"))
|
||||
self.aboutBrowser = QtGui.QTextBrowser(self.aboutTab)
|
||||
self.aboutBrowser.setOpenExternalLinks(True)
|
||||
@@ -66,8 +65,8 @@ class Ui_UDSAboutDialog(object):
|
||||
self.authorsTab = QtGui.QWidget()
|
||||
self.authorsTab.setObjectName(_fromUtf8("authorsTab"))
|
||||
self.vboxlayout2 = QtGui.QVBoxLayout(self.authorsTab)
|
||||
self.vboxlayout2.setSpacing(6)
|
||||
self.vboxlayout2.setMargin(9)
|
||||
self.vboxlayout2.setSpacing(6)
|
||||
self.vboxlayout2.setObjectName(_fromUtf8("vboxlayout2"))
|
||||
self.authorsBrowser = QtGui.QTextBrowser(self.authorsTab)
|
||||
self.authorsBrowser.setOpenExternalLinks(True)
|
||||
@@ -77,8 +76,8 @@ class Ui_UDSAboutDialog(object):
|
||||
self.licenseTab = QtGui.QWidget()
|
||||
self.licenseTab.setObjectName(_fromUtf8("licenseTab"))
|
||||
self.vboxlayout3 = QtGui.QVBoxLayout(self.licenseTab)
|
||||
self.vboxlayout3.setSpacing(6)
|
||||
self.vboxlayout3.setMargin(9)
|
||||
self.vboxlayout3.setSpacing(6)
|
||||
self.vboxlayout3.setObjectName(_fromUtf8("vboxlayout3"))
|
||||
self.licenseBrowser = QtGui.QTextBrowser(self.licenseTab)
|
||||
self.licenseBrowser.setObjectName(_fromUtf8("licenseBrowser"))
|
||||
@@ -92,7 +91,7 @@ class Ui_UDSAboutDialog(object):
|
||||
self.vboxlayout.addWidget(self.buttonBox)
|
||||
|
||||
self.retranslateUi(UDSAboutDialog)
|
||||
self.tabWidget.setCurrentIndex(0)
|
||||
self.tabWidget.setCurrentIndex(2)
|
||||
QtCore.QObject.connect(self.buttonBox, QtCore.SIGNAL(_fromUtf8("clicked(QAbstractButton*)")), UDSAboutDialog.closeDialog)
|
||||
QtCore.QMetaObject.connectSlotsByName(UDSAboutDialog)
|
||||
|
||||
@@ -106,7 +105,7 @@ class Ui_UDSAboutDialog(object):
|
||||
"p, li { white-space: pre-wrap; }\n"
|
||||
"</style></head><body style=\" font-family:\'Verdana\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
|
||||
"<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>\n"
|
||||
"<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>\n"
|
||||
"<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>\n"
|
||||
"<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>\n"
|
||||
"<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>\n"
|
||||
"<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>\n"
|
||||
@@ -122,7 +121,7 @@ class Ui_UDSAboutDialog(object):
|
||||
"<html><head><meta name=\"qrichtext\" content=\"1\" /><style type=\"text/css\">\n"
|
||||
"p, li { white-space: pre-wrap; }\n"
|
||||
"</style></head><body style=\" font-family:\'Verdana\'; font-size:9pt; font-weight:400; font-style:normal;\">\n"
|
||||
"<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>\n"
|
||||
"<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>\n"
|
||||
"<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>\n"
|
||||
"<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>\n"
|
||||
"<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>\n"
|
||||
|
@@ -2,8 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'message-dialog.ui'
|
||||
#
|
||||
# Created: Mon Apr 27 22:05:02 2015
|
||||
# by: PyQt4 UI code generator 4.11.2
|
||||
# Created by: PyQt4 UI code generator 4.12.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
|
@@ -121,7 +121,7 @@
|
||||
<x>20</x>
|
||||
<y>20</y>
|
||||
<width>361</width>
|
||||
<height>131</height>
|
||||
<height>166</height>
|
||||
</rect>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
@@ -205,7 +205,7 @@
|
||||
<item row="3" column="1">
|
||||
<widget class="QComboBox" name="logLevelComboBox">
|
||||
<property name="currentIndex">
|
||||
<number>1</number>
|
||||
<number>3</number>
|
||||
</property>
|
||||
<property name="frame">
|
||||
<bool>true</bool>
|
||||
@@ -220,6 +220,11 @@
|
||||
<string notr="true">INFO</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string>WARN</string>
|
||||
</property>
|
||||
</item>
|
||||
<item>
|
||||
<property name="text">
|
||||
<string notr="true">ERROR</string>
|
||||
|
@@ -2,8 +2,7 @@
|
||||
|
||||
# Form implementation generated from reading ui file 'setup-dialog.ui'
|
||||
#
|
||||
# Created: Mon Apr 27 22:05:03 2015
|
||||
# by: PyQt4 UI code generator 4.11.2
|
||||
# Created by: PyQt4 UI code generator 4.12.1
|
||||
#
|
||||
# WARNING! All changes made in this file will be lost!
|
||||
|
||||
@@ -63,7 +62,7 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.cancelButton.setSizePolicy(sizePolicy)
|
||||
self.cancelButton.setObjectName(_fromUtf8("cancelButton"))
|
||||
self.layoutWidget = QtGui.QWidget(UdsActorSetupDialog)
|
||||
self.layoutWidget.setGeometry(QtCore.QRect(20, 20, 361, 131))
|
||||
self.layoutWidget.setGeometry(QtCore.QRect(20, 20, 361, 166))
|
||||
self.layoutWidget.setObjectName(_fromUtf8("layoutWidget"))
|
||||
self.formLayout = QtGui.QFormLayout(self.layoutWidget)
|
||||
self.formLayout.setFieldGrowthPolicy(QtGui.QFormLayout.AllNonFixedFieldsGrow)
|
||||
@@ -102,13 +101,14 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.logLevelComboBox.addItem(_fromUtf8(""))
|
||||
self.logLevelComboBox.setItemText(1, _fromUtf8("INFO"))
|
||||
self.logLevelComboBox.addItem(_fromUtf8(""))
|
||||
self.logLevelComboBox.setItemText(2, _fromUtf8("ERROR"))
|
||||
self.logLevelComboBox.addItem(_fromUtf8(""))
|
||||
self.logLevelComboBox.setItemText(3, _fromUtf8("FATAL"))
|
||||
self.logLevelComboBox.setItemText(3, _fromUtf8("ERROR"))
|
||||
self.logLevelComboBox.addItem(_fromUtf8(""))
|
||||
self.logLevelComboBox.setItemText(4, _fromUtf8("FATAL"))
|
||||
self.formLayout.setWidget(3, QtGui.QFormLayout.FieldRole, self.logLevelComboBox)
|
||||
|
||||
self.retranslateUi(UdsActorSetupDialog)
|
||||
self.logLevelComboBox.setCurrentIndex(1)
|
||||
self.logLevelComboBox.setCurrentIndex(3)
|
||||
QtCore.QObject.connect(self.host, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), UdsActorSetupDialog.textChanged)
|
||||
QtCore.QObject.connect(self.masterKey, QtCore.SIGNAL(_fromUtf8("textChanged(QString)")), UdsActorSetupDialog.textChanged)
|
||||
QtCore.QObject.connect(self.cancelButton, QtCore.SIGNAL(_fromUtf8("pressed()")), UdsActorSetupDialog.cancelAndDiscard)
|
||||
@@ -139,6 +139,7 @@ class Ui_UdsActorSetupDialog(object):
|
||||
self.useSSl.setItemText(0, _translate("UdsActorSetupDialog", "Do not use SSL", None))
|
||||
self.useSSl.setItemText(1, _translate("UdsActorSetupDialog", "Use SSL", None))
|
||||
self.logLevelLabel.setText(_translate("UdsActorSetupDialog", "Log Level", None))
|
||||
self.logLevelComboBox.setItemText(2, _translate("UdsActorSetupDialog", "WARN", None))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
@@ -78,7 +78,7 @@ class OsManagerError(RESTError):
|
||||
try:
|
||||
import urllib3 # @UnusedImport
|
||||
except Exception:
|
||||
from requests.packages import urllib3 # @Reimport
|
||||
from requests.packages import urllib3 # @Reimport @UnresolvedImport
|
||||
|
||||
try:
|
||||
urllib3.disable_warnings() # @UndefinedVariable
|
||||
@@ -101,6 +101,7 @@ def ensureResultIsOk(result):
|
||||
|
||||
|
||||
class Api(object):
|
||||
|
||||
def __init__(self, host, masterKey, ssl):
|
||||
self.host = host
|
||||
self.masterKey = masterKey
|
||||
@@ -112,7 +113,7 @@ class Api(object):
|
||||
self.maxSession = None
|
||||
self.secretKey = six.text_type(uuid.uuid4())
|
||||
try:
|
||||
self.newerRequestLib = requests.__version__.split('.')[0] >= '1'
|
||||
self.newerRequestLib = requests.__version__.split('.')[0] >= '1' # @UndefinedVariable
|
||||
except Exception:
|
||||
self.newerRequestLib = False # I no version, guess this must be an old requests
|
||||
|
||||
@@ -154,7 +155,11 @@ class Api(object):
|
||||
logger.debug('Requesting with old')
|
||||
r = requests.post(url, data=data, headers={'content-type': 'application/json'})
|
||||
|
||||
r = json.loads(r.content) # Using instead of r.json() to make compatible with oooold rquests lib versions
|
||||
# From versions of requests, content maybe bytes or str. We need str for json.loads
|
||||
content = r.content
|
||||
if not isinstance(content, six.text_type):
|
||||
content = content.decode('utf8')
|
||||
r = json.loads(content) # Using instead of r.json() to make compatible with oooold rquests lib versions
|
||||
except requests.exceptions.RequestException as e:
|
||||
raise ConnectionError(e)
|
||||
except Exception as e:
|
||||
@@ -201,6 +206,8 @@ class Api(object):
|
||||
raise ConnectionError('REST api has not been initialized')
|
||||
|
||||
if processData:
|
||||
if data and not isinstance(data, six.text_type):
|
||||
data = data.decode('utf8')
|
||||
data = json.dumps({'data': data})
|
||||
url = self._getUrl('/'.join([self.uuid, msg]))
|
||||
return self._request(url, data)['result']
|
||||
|
@@ -34,21 +34,22 @@ from __future__ import unicode_literals
|
||||
# On centos, old six release does not includes byte2int, nor six.PY2
|
||||
import six
|
||||
|
||||
VERSION = '2.2.0'
|
||||
VERSION = '2.2.1'
|
||||
|
||||
__title__ = 'udsactor'
|
||||
__version__ = VERSION
|
||||
__build__ = 0x010755
|
||||
__build__ = 0x010756
|
||||
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
|
||||
__license__ = "BSD 3-clause"
|
||||
__copyright__ = "Copyright 2014-2017 VirtualCable S.L.U."
|
||||
|
||||
__copyright__ = "Copyright 2014-2018 VirtualCable S.L.U."
|
||||
|
||||
if not hasattr(six, 'byte2int'):
|
||||
if six.PY3:
|
||||
import operator
|
||||
six.byte2int = operator.itemgetter(0)
|
||||
else:
|
||||
|
||||
def _byte2int(bs):
|
||||
return ord(bs[0])
|
||||
|
||||
six.byte2int = _byte2int
|
||||
|
@@ -116,8 +116,8 @@ class HTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
|
||||
try:
|
||||
HTTPServerHandler.lock.acquire()
|
||||
length = int(self.headers.getheader('content-length'))
|
||||
content = self.rfile.read(length)
|
||||
length = int(self.headers.get('content-length'))
|
||||
content = self.rfile.read(length).decode('utf8')
|
||||
logger.debug('length: {}, content >>{}<<'.format(length, content))
|
||||
params = json.loads(content)
|
||||
|
||||
@@ -181,7 +181,7 @@ class HTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
|
||||
logger.error('HTTP ' + fmt % args)
|
||||
|
||||
def log_message(self, fmt, *args):
|
||||
logger.info('HTTP ' + fmt % args)
|
||||
logger.debug('HTTP ' + fmt % args)
|
||||
|
||||
|
||||
class HTTPServerThread(threading.Thread):
|
||||
|
@@ -33,11 +33,11 @@ from __future__ import unicode_literals
|
||||
import socket
|
||||
import threading
|
||||
import sys
|
||||
import six
|
||||
import traceback
|
||||
import pickle
|
||||
import errno
|
||||
import time
|
||||
import six
|
||||
|
||||
from udsactor.utils import toUnicode
|
||||
from udsactor.log import logger
|
||||
@@ -89,11 +89,9 @@ REV_DICT = {
|
||||
|
||||
MAGIC = b'\x55\x44\x53\x00' # UDS in hexa with a padded 0 to the right
|
||||
|
||||
|
||||
# Allows notifying login/logout from client for linux platform
|
||||
ALLOW_LOG_METHODS = sys.platform != 'win32'
|
||||
|
||||
|
||||
# States for client processor
|
||||
ST_SECOND_BYTE = 0x01
|
||||
ST_RECEIVING = 0x02
|
||||
@@ -101,8 +99,9 @@ ST_PROCESS_MESSAGE = 0x02
|
||||
|
||||
|
||||
class ClientProcessor(threading.Thread):
|
||||
|
||||
def __init__(self, parent, clientSocket):
|
||||
super(self.__class__, self).__init__()
|
||||
super(ClientProcessor, self).__init__()
|
||||
self.parent = parent
|
||||
self.clientSocket = clientSocket
|
||||
self.running = False
|
||||
@@ -133,6 +132,7 @@ class ClientProcessor(threading.Thread):
|
||||
if b == b'':
|
||||
# Client disconnected
|
||||
self.running = False
|
||||
# self.processRequest(REQ_LOGOUT, 'CLIENT_CONNECTION_LOST')
|
||||
break
|
||||
buf = six.byte2int(b) # Empty buffer, this is set as non-blocking
|
||||
if state is None:
|
||||
@@ -187,8 +187,8 @@ class ClientProcessor(threading.Thread):
|
||||
|
||||
try:
|
||||
m = msg[1] if msg[1] is not None else b''
|
||||
l = len(m)
|
||||
data = MAGIC + six.int2byte(msg[0]) + six.int2byte(l & 0xFF) + six.int2byte(l >> 8) + m
|
||||
ln = len(m)
|
||||
data = MAGIC + six.int2byte(msg[0]) + six.int2byte(ln & 0xFF) + six.int2byte(ln >> 8) + m
|
||||
try:
|
||||
self.clientSocket.sendall(data)
|
||||
except socket.error as e:
|
||||
@@ -208,7 +208,7 @@ class ClientProcessor(threading.Thread):
|
||||
class ServerIPC(threading.Thread):
|
||||
|
||||
def __init__(self, listenPort, clientMessageProcessor=None):
|
||||
super(self.__class__, self).__init__()
|
||||
super(ServerIPC, self).__init__()
|
||||
self.port = listenPort
|
||||
self.running = False
|
||||
self.serverSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
||||
@@ -291,6 +291,7 @@ class ServerIPC(threading.Thread):
|
||||
|
||||
|
||||
class ClientIPC(threading.Thread):
|
||||
|
||||
def __init__(self, listenPort):
|
||||
super(ClientIPC, self).__init__()
|
||||
self.port = listenPort
|
||||
@@ -320,8 +321,8 @@ class ClientIPC(threading.Thread):
|
||||
if isinstance(data, six.text_type): # Convert to bytes if necessary
|
||||
data = data.encode('utf-8')
|
||||
|
||||
l = len(data)
|
||||
msg = six.int2byte(msg) + six.int2byte(l & 0xFF) + six.int2byte(l >> 8) + data
|
||||
ln = len(data)
|
||||
msg = six.int2byte(msg) + six.int2byte(ln & 0xFF) + six.int2byte(ln >> 8) + data
|
||||
self.clientSocket.sendall(msg)
|
||||
|
||||
def requestInformation(self):
|
||||
|
@@ -31,6 +31,11 @@
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import sys
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
|
||||
from udsactor import operations
|
||||
|
||||
from udsactor.service import CommonService
|
||||
@@ -38,23 +43,18 @@ from udsactor.service import initCfg
|
||||
from udsactor.service import IPC_PORT
|
||||
|
||||
from udsactor import ipc
|
||||
|
||||
from udsactor import store
|
||||
from udsactor.log import logger
|
||||
|
||||
from udsactor.linux.daemon import Daemon
|
||||
from udsactor.linux import renamer
|
||||
|
||||
import sys
|
||||
import os
|
||||
import stat
|
||||
import subprocess
|
||||
|
||||
POST_CMD = '/etc/udsactor/post'
|
||||
PRECONNECT_CMD = '/etc/udsactor/pre'
|
||||
|
||||
try:
|
||||
from prctl import set_proctitle # @UnresolvedImport
|
||||
except Exception: # Platform may not include prctl, so in case it's not available, we let the "name" as is
|
||||
|
||||
def set_proctitle(_):
|
||||
pass
|
||||
|
||||
@@ -97,7 +97,6 @@ class UDSActorSvc(Daemon, CommonService):
|
||||
logger.info('Rebooting computer to activate new name {}'.format(name))
|
||||
self.reboot()
|
||||
|
||||
|
||||
def joinDomain(self, name, domain, ou, account, password):
|
||||
logger.fatal('Join domain is not supported on linux platforms right now')
|
||||
|
||||
@@ -108,18 +107,19 @@ class UDSActorSvc(Daemon, CommonService):
|
||||
# Execute script in /etc/udsactor/post after interacting with broker, if no reboot is requested ofc
|
||||
# This will be executed only when machine gets "ready"
|
||||
try:
|
||||
|
||||
if os.path.isfile(PRECONNECT_CMD):
|
||||
if (os.stat(PRECONNECT_CMD).st_mode & stat.S_IXUSR) != 0:
|
||||
subprocess.call([PRECONNECT_CMD, user, protocol])
|
||||
pre_cmd = store.preApplication()
|
||||
if os.path.isfile(pre_cmd):
|
||||
if (os.stat(pre_cmd).st_mode & stat.S_IXUSR) != 0:
|
||||
subprocess.call([pre_cmd, user, protocol])
|
||||
else:
|
||||
logger.info('PRECONNECT file exists but it it is not executable (needs execution permission by root)')
|
||||
else:
|
||||
logger.info('PRECONNECT file not found & not executed')
|
||||
except Exception as e:
|
||||
except Exception:
|
||||
# Ignore output of execution command
|
||||
logger.error('Executing preconnect command give')
|
||||
|
||||
return 'ok'
|
||||
|
||||
def run(self):
|
||||
cfg = initCfg() # Gets a local copy of config to get "reboot"
|
||||
@@ -196,6 +196,7 @@ def usage():
|
||||
sys.stderr.write("usage: {} start|stop|restart|login 'username'|logout 'username'\n".format(sys.argv[0]))
|
||||
sys.exit(2)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
logger.setLevel(20000)
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2018 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -46,6 +46,7 @@ class Daemon:
|
||||
|
||||
Usage: subclass the Daemon class and override the run() method
|
||||
"""
|
||||
|
||||
def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'):
|
||||
self.stdin = stdin
|
||||
self.stdout = stdout
|
||||
@@ -88,8 +89,8 @@ class Daemon:
|
||||
sys.stdout.flush()
|
||||
sys.stderr.flush()
|
||||
si = open(self.stdin, 'r')
|
||||
so = open(self.stdout, 'a+')
|
||||
se = open(self.stderr, 'a+', 0)
|
||||
so = open(self.stdout, 'ab+')
|
||||
se = open(self.stderr, 'ab+', 0)
|
||||
os.dup2(si.fileno(), sys.stdin.fileno())
|
||||
os.dup2(so.fileno(), sys.stdout.fileno())
|
||||
os.dup2(se.fileno(), sys.stderr.fileno())
|
||||
|
@@ -232,7 +232,7 @@ def getIdleDuration():
|
||||
xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), info)
|
||||
|
||||
# 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():
|
||||
if info.contents.state == 1:
|
||||
return 3600 * 100 * 1000 # If screen saver is active, return a high enough value
|
||||
|
||||
return info.contents.idle / 1000.0
|
||||
|
@@ -30,13 +30,13 @@
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
|
||||
|
||||
import six
|
||||
import os
|
||||
|
||||
DEBUG = False
|
||||
|
||||
CONFIGFILE = '/etc/udsactor/udsactor.cfg' if DEBUG is False else '/tmp/udsactor.cfg'
|
||||
PRECONNECT_CMD = '/etc/udsactor/pre'
|
||||
|
||||
|
||||
def checkPermissions():
|
||||
@@ -79,10 +79,15 @@ def writeConfig(data):
|
||||
|
||||
os.chmod(CONFIGFILE, 0o0600)
|
||||
|
||||
|
||||
def useOldJoinSystem():
|
||||
return False
|
||||
|
||||
|
||||
# Right now, we do not really need an application to be run on "startup" as could ocur with windows
|
||||
def runApplication():
|
||||
return None
|
||||
|
||||
|
||||
def preApplication():
|
||||
return PRECONNECT_CMD
|
||||
|
@@ -45,6 +45,7 @@ OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in six.moves.xra
|
||||
|
||||
|
||||
class Logger(object):
|
||||
|
||||
def __init__(self):
|
||||
self.logLevel = INFO
|
||||
self.logger = LocalLogger()
|
||||
|
@@ -78,6 +78,7 @@ def initCfg():
|
||||
|
||||
|
||||
class CommonService(object):
|
||||
|
||||
def __init__(self):
|
||||
self.isAlive = True
|
||||
self.api = None
|
||||
@@ -85,12 +86,13 @@ class CommonService(object):
|
||||
self.httpServer = None
|
||||
self.rebootRequested = False
|
||||
self.knownIps = []
|
||||
self.loggedIn = False
|
||||
socket.setdefaulttimeout(20)
|
||||
|
||||
def reboot(self):
|
||||
self.rebootRequested = True
|
||||
|
||||
def execute(self, cmdLine, section):
|
||||
def execute(self, cmdLine, section): # pylint: disable=no-self-use
|
||||
cmd = shlex.split(cmdLine, posix=False)
|
||||
|
||||
if os.path.isfile(cmd[0]):
|
||||
@@ -259,13 +261,15 @@ class CommonService(object):
|
||||
return
|
||||
|
||||
if msg == ipc.REQ_LOGIN:
|
||||
self.loggedIn = True
|
||||
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:
|
||||
if msg == ipc.REQ_LOGOUT and self.loggedIn is True:
|
||||
self.loggedIn = False
|
||||
self.api.logout(data)
|
||||
self.onLogout(data)
|
||||
if msg == ipc.REQ_INFORMATION:
|
||||
@@ -308,11 +312,14 @@ class CommonService(object):
|
||||
def endAPI(self):
|
||||
if self.api is not None:
|
||||
try:
|
||||
if self.loggedIn:
|
||||
self.loggedIn = False
|
||||
self.api.logout('service_stopped')
|
||||
self.api.notifyComm(None)
|
||||
except Exception:
|
||||
logger.error('Couln\'t remove comms url from broker')
|
||||
except Exception as e:
|
||||
logger.error('Couln\'t remove comms url from broker: {}'.format(e))
|
||||
|
||||
self.notifyStop()
|
||||
# self.notifyStop()
|
||||
|
||||
# ***************************************************
|
||||
# Methods that ARE overriden by linux & windows Actor
|
||||
|
@@ -32,6 +32,10 @@
|
||||
from __future__ import unicode_literals
|
||||
# pylint: disable=unused-wildcard-import, wildcard-import
|
||||
|
||||
import subprocess
|
||||
import os
|
||||
import stat
|
||||
|
||||
import win32serviceutil # @UnresolvedImport, pylint: disable=import-error
|
||||
import win32service # @UnresolvedImport, pylint: disable=import-error
|
||||
import win32security # @UnresolvedImport, pylint: disable=import-error
|
||||
@@ -40,8 +44,6 @@ import win32event # @UnresolvedImport, pylint: disable=import-error
|
||||
import win32com.client # @UnresolvedImport, @UnusedImport, pylint: disable=import-error
|
||||
import pythoncom # @UnresolvedImport, pylint: disable=import-error
|
||||
import servicemanager # @UnresolvedImport, pylint: disable=import-error
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
from udsactor import operations
|
||||
from udsactor import store
|
||||
@@ -187,7 +189,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
resumeHandle = 0
|
||||
while True:
|
||||
users, _, resumeHandle = win32net.NetLocalGroupGetMembers(None, groupName, 1, resumeHandle, 32768)
|
||||
if user in [u['name'] for u in users]:
|
||||
if user.lower() in [u['name'].lower() for u in users]:
|
||||
useraAlreadyInGroup = True
|
||||
break
|
||||
if resumeHandle == 0:
|
||||
@@ -205,6 +207,20 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
self._user = None
|
||||
logger.debug('User {} already in group'.format(user))
|
||||
|
||||
# Now try to run pre connect command
|
||||
try:
|
||||
pre_cmd = store.preApplication()
|
||||
if os.path.isfile(pre_cmd):
|
||||
if (os.stat(pre_cmd).st_mode & stat.S_IXUSR) != 0:
|
||||
subprocess.call([pre_cmd, user, protocol])
|
||||
else:
|
||||
logger.info('PRECONNECT file exists but it it is not executable (needs execution permission by root)')
|
||||
else:
|
||||
logger.info('PRECONNECT file not found & not executed')
|
||||
except Exception as e:
|
||||
# Ignore output of execution command
|
||||
logger.error('Executing preconnect command give {}'.format(e))
|
||||
|
||||
return 'ok'
|
||||
|
||||
def onLogout(self, user):
|
||||
@@ -223,7 +239,7 @@ class UDSActorSvc(win32serviceutil.ServiceFramework, CommonService):
|
||||
except Exception as e:
|
||||
logger.error('Exception removing user from Remote Desktop Users: {}'.format(e))
|
||||
|
||||
def SvcDoRun(self):
|
||||
def SvcDoRun(self): # pylint: disable=too-many-statements, too-many-branches
|
||||
'''
|
||||
Main service loop
|
||||
'''
|
||||
|
@@ -37,7 +37,7 @@ import os
|
||||
import tempfile
|
||||
|
||||
# Valid logging levels, from UDS Broker (uds.core.utils.log)
|
||||
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in xrange(6))
|
||||
OTHER, DEBUG, INFO, WARN, ERROR, FATAL = (10000 * (x + 1) for x in range(6))
|
||||
|
||||
|
||||
class LocalLogger(object):
|
||||
@@ -58,7 +58,7 @@ class LocalLogger(object):
|
||||
# our loglevels are 10000 (other), 20000 (debug), ....
|
||||
# logging levels are 10 (debug), 20 (info)
|
||||
# OTHER = logging.NOTSET
|
||||
self.logger.log(level / 1000 - 10, message)
|
||||
self.logger.log(level // 1000 - 10, message)
|
||||
|
||||
if level < INFO or self.serviceLogger is False: # Only information and above will be on event log
|
||||
return
|
||||
|
@@ -91,6 +91,7 @@ def getDomainName():
|
||||
def getWindowsVersion():
|
||||
return win32api.GetVersionEx()
|
||||
|
||||
|
||||
EWX_LOGOFF = 0x00000000
|
||||
EWX_SHUTDOWN = 0x00000001
|
||||
EWX_REBOOT = 0x00000002
|
||||
@@ -121,6 +122,7 @@ def renameComputer(newName):
|
||||
computerName = win32api.GetComputerNameEx(win32con.ComputerNamePhysicalDnsHostname)
|
||||
raise Exception('Error renaming computer from {} to {}: {}'.format(computerName, newName, error))
|
||||
|
||||
|
||||
NETSETUP_JOIN_DOMAIN = 0x00000001
|
||||
NETSETUP_ACCT_CREATE = 0x00000002
|
||||
NETSETUP_ACCT_DELETE = 0x00000004
|
||||
@@ -187,8 +189,8 @@ def changeUserPassword(user, oldPassword, newPassword):
|
||||
|
||||
if res != 0:
|
||||
# Log the error, and raise exception to parent
|
||||
error = getErrorMessage()
|
||||
raise Exception('Error changing password for user {}: {}'.format(user.value, error))
|
||||
error = getErrorMessage(res)
|
||||
raise Exception('Error changing password for user {}: {} {}'.format(user.value, res, error))
|
||||
|
||||
|
||||
class LASTINPUTINFO(ctypes.Structure):
|
||||
@@ -206,11 +208,21 @@ def initIdleDuration(atLeastSeconds):
|
||||
|
||||
|
||||
def getIdleDuration():
|
||||
lastInputInfo = LASTINPUTINFO()
|
||||
lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo)
|
||||
ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lastInputInfo))
|
||||
millis = ctypes.windll.kernel32.GetTickCount() - lastInputInfo.dwTime # @UndefinedVariable
|
||||
return millis / 1000.0
|
||||
try:
|
||||
lastInputInfo = LASTINPUTINFO()
|
||||
lastInputInfo.cbSize = ctypes.sizeof(lastInputInfo)
|
||||
if ctypes.windll.user32.GetLastInputInfo(ctypes.byref(lastInputInfo)) == 0:
|
||||
return 0
|
||||
# if lastInputInfo.dwTime > 1000000000: # Value toooo high, nonsense...
|
||||
# return 0
|
||||
current = ctypes.c_uint(ctypes.windll.kernel32.GetTickCount())
|
||||
millis = current.value - lastInputInfo.dwTime # @UndefinedVariable
|
||||
if millis < 0:
|
||||
return 0
|
||||
return millis / 1000.0
|
||||
except Exception as e:
|
||||
logger.error('Getting idle duration: {}'.format(e))
|
||||
return 0
|
||||
|
||||
|
||||
def getCurrentUser():
|
||||
|
@@ -31,10 +31,13 @@
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import pickle
|
||||
from win32com.shell import shell # @UnresolvedImport, pylint: disable=import-error
|
||||
import _winreg as wreg # @UnresolvedImport, pylint: disable=import-error
|
||||
try:
|
||||
import winreg as wreg
|
||||
except ImportError: # Python 2.7 fallback
|
||||
import _winreg as wreg # @UnresolvedImport, pylint: disable=import-error
|
||||
import win32security # @UnresolvedImport, pylint: disable=import-error
|
||||
import cPickle
|
||||
|
||||
DEBUG = False
|
||||
|
||||
@@ -47,6 +50,7 @@ def encoder(data):
|
||||
def decoder(data):
|
||||
return data.decode('bz2')
|
||||
|
||||
|
||||
path = 'Software\\UDSActor'
|
||||
baseKey = wreg.HKEY_CURRENT_USER if DEBUG is True else wreg.HKEY_LOCAL_MACHINE # @UndefinedVariable
|
||||
|
||||
@@ -78,10 +82,11 @@ def readConfig():
|
||||
key = wreg.OpenKey(baseKey, path, 0, wreg.KEY_QUERY_VALUE) # @UndefinedVariable
|
||||
data, _ = wreg.QueryValueEx(key, '') # @UndefinedVariable
|
||||
wreg.CloseKey(key) # @UndefinedVariable
|
||||
return cPickle.loads(decoder(data))
|
||||
return pickle.loads(decoder(data))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def writeConfig(data, fixPermissions=True):
|
||||
try:
|
||||
key = wreg.OpenKey(baseKey, path, 0, wreg.KEY_ALL_ACCESS) # @UndefinedVariable
|
||||
@@ -90,9 +95,10 @@ def writeConfig(data, fixPermissions=True):
|
||||
if fixPermissions is True:
|
||||
fixRegistryPermissions(key.handle)
|
||||
|
||||
wreg.SetValueEx(key, "", 0, wreg.REG_BINARY, encoder(cPickle.dumps(data))) # @UndefinedVariable
|
||||
wreg.SetValueEx(key, "", 0, wreg.REG_BINARY, encoder(pickle.dumps(data))) # @UndefinedVariable
|
||||
wreg.CloseKey(key) # @UndefinedVariable
|
||||
|
||||
|
||||
def useOldJoinSystem():
|
||||
try:
|
||||
key = wreg.OpenKey(baseKey, 'Software\\UDSEnterpriseActor', 0, wreg.KEY_QUERY_VALUE) # @UndefinedVariable
|
||||
@@ -106,6 +112,7 @@ def useOldJoinSystem():
|
||||
|
||||
return data == 'old'
|
||||
|
||||
|
||||
# Gives the oportunity to run an application ONE TIME (because, the registry key "run" will be deleted after read)
|
||||
def runApplication():
|
||||
try:
|
||||
@@ -116,8 +123,21 @@ def runApplication():
|
||||
except Exception:
|
||||
data = None
|
||||
wreg.CloseKey(key) # @UndefinedVariable
|
||||
except:
|
||||
except Exception:
|
||||
data = None
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def preApplication():
|
||||
try:
|
||||
key = wreg.OpenKey(baseKey, 'Software\\UDSEnterpriseActor', 0, wreg.KEY_ALL_ACCESS) # @UndefinedVariable
|
||||
try:
|
||||
data, _ = wreg.QueryValueEx(key, 'pre') # @UndefinedVariable
|
||||
except Exception:
|
||||
data = None
|
||||
wreg.CloseKey(key) # @UndefinedVariable
|
||||
except Exception:
|
||||
data = None
|
||||
|
||||
return data
|
||||
|
@@ -1,3 +1,9 @@
|
||||
udsclient (2.2.1) stable; urgency=medium
|
||||
|
||||
* Upgraded to 2.2.1 release
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 2 Oct 2018 12:44:12 +0200
|
||||
|
||||
udsclient (2.2.0) stable; urgency=medium
|
||||
|
||||
* Updated release
|
||||
|
@@ -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), freerdp-x11 | rdesktop, desktop-file-utils, ${misc:Depends}
|
||||
Depends: python-paramiko (>=0.8.2), python-qt4 (>=4.9), python-six(>=1.1), python (>=2.7), freerdp2-x11 | 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,2 +1,2 @@
|
||||
udsclient_2.2.0_all.deb admin optional
|
||||
udsclient_2.2.0_amd64.buildinfo admin optional
|
||||
udsclient_2.2.1_all.deb admin optional
|
||||
udsclient_2.2.1_amd64.buildinfo admin optional
|
||||
|
@@ -162,6 +162,7 @@ class UDSClient(QtGui.QMainWindow):
|
||||
QtCore.QTimer.singleShot(1000, self.getVersion)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception('Version')
|
||||
self.showError(e)
|
||||
|
||||
|
||||
@@ -210,7 +211,7 @@ class UDSClient(QtGui.QMainWindow):
|
||||
|
||||
# if QtGui.QMessageBox.warning(None, 'ACCESS Warning', errorString, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) == QtGui.QMessageBox.No:
|
||||
# raise Exception('Server not approved. Access denied.')
|
||||
|
||||
logger.debug('Script: {}'.format(script))
|
||||
six.exec_(script, globals(), {'parent': self, 'sp': params})
|
||||
|
||||
except RetryException as e:
|
||||
|
@@ -1,5 +1,5 @@
|
||||
Steps:
|
||||
1.- If building from repository, full copy (recursive) the "src" folder of "udsclient/thin" inside the "udsclient" folder. If building from the .tar.gz, simply ignor4e this step
|
||||
1.- If building from repository, full copy (recursive) the "src" folder of "client/thin" (from openuds project) inside the "udsclient" folder here (so we will have an client/thin/udsclient/src folder, with the source code of thin client). If building from the .tar.gz, simply ignore this step
|
||||
2.- Copy the folder "udsclient" to /build/packages inside the thinstation build environment
|
||||
3.- enter the chroot of thinstation
|
||||
4.- go to the udsclient folder (/build/packages/udsclient)
|
||||
|
@@ -81,14 +81,14 @@
|
||||
<dependency>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>guacamole-common</artifactId>
|
||||
<version>0.9.10-incubating</version>
|
||||
<version>1.0.0</version>
|
||||
</dependency>
|
||||
|
||||
<!-- Guacamole JavaScript library -->
|
||||
<dependency>
|
||||
<groupId>org.apache.guacamole</groupId>
|
||||
<artifactId>guacamole-common-js</artifactId>
|
||||
<version>0.9.12-incubating</version>
|
||||
<version>1.0.0</version>
|
||||
<type>zip</type>
|
||||
<scope>runtime</scope>
|
||||
</dependency>
|
||||
|
@@ -51,6 +51,12 @@ GuacUI.Client = {
|
||||
0x0205: "This connection is currently in use, and concurrent access to \
|
||||
this connection is not allowed. Please try again later.",
|
||||
|
||||
0x0207: "The Guacamole server is not currently reachable. Please \
|
||||
check your network and try again.",
|
||||
|
||||
0x0208: "The Guacamole server is not accepting connections. Please \
|
||||
check your network and try again.",
|
||||
|
||||
0x0301: "You do not have permission to access this connection because \
|
||||
you are not logged in. Please log in and try again.",
|
||||
|
||||
@@ -94,14 +100,32 @@ GuacUI.Client = {
|
||||
the connection. Please try again or contact your system \
|
||||
administrator.",
|
||||
|
||||
0x0205: "This connection has been closed because it conflicts with \
|
||||
another connection. Please try again later.",
|
||||
0x0207: "The remote desktop server is currently unreachable. If the \
|
||||
problem persists, please notify your system administrator, or \
|
||||
check your system logs.",
|
||||
|
||||
0x0208: "The remote desktop server is currently unavailable. If the \
|
||||
problem persists, please notify your system administrator, or \
|
||||
check your system logs.",
|
||||
|
||||
0x0209: "The remote desktop server has closed the connection because \
|
||||
it conflicts with another connection. Please try again later.",
|
||||
|
||||
0x020A: "The remote desktop server has closed the connection because \
|
||||
it appeared to be inactive. If this is undesired or \
|
||||
unexpected, please notify your system administrator, or check \
|
||||
your system settings.",
|
||||
|
||||
0x020B: "The remote desktop server has forcibly closed the connection. \
|
||||
If this is undesired or unexpected, please notify your system \
|
||||
administrator, or check your system logs.",
|
||||
|
||||
0x0301: "Log in failed. Please reconnect and try again.",
|
||||
|
||||
0x0303: "You do not have permission to access this connection. If you \
|
||||
require access, please ask your system administrator to add \
|
||||
you the list of allowed users, or check your system settings.",
|
||||
0x0303: "The remote desktop server has denied access to this \
|
||||
connection. If you require access, please ask your system \
|
||||
administrator to grant your account access, or check your \
|
||||
system settings.",
|
||||
|
||||
0x0308: "The Guacamole server has closed the connection because there \
|
||||
has been no response from your browser for long enough that \
|
||||
@@ -179,6 +203,8 @@ GuacUI.Client = {
|
||||
0x0200: true,
|
||||
0x0202: true,
|
||||
0x0203: true,
|
||||
0x0207: true,
|
||||
0x0208: true,
|
||||
0x0308: true
|
||||
},
|
||||
|
||||
@@ -190,6 +216,8 @@ GuacUI.Client = {
|
||||
0x0200: true,
|
||||
0x0202: true,
|
||||
0x0203: true,
|
||||
0x0207: true,
|
||||
0x0208: true,
|
||||
0x0301: true,
|
||||
0x0308: true
|
||||
},
|
||||
@@ -235,7 +263,7 @@ GuacUI.Client = {
|
||||
"min_zoom" : 1,
|
||||
"max_zoom" : 3,
|
||||
|
||||
"connectionName" : "Guacamole",
|
||||
"connectionName" : "UDS Remote Connection",
|
||||
"attachedClient" : null,
|
||||
|
||||
/* Mouse emulation */
|
||||
@@ -1405,7 +1433,11 @@ GuacUI.Client.attach = function(guac) {
|
||||
* Route document-level keyboard events to the client.
|
||||
*/
|
||||
|
||||
var sink = new Guacamole.InputSink();
|
||||
document.body.appendChild(sink.getElement());
|
||||
|
||||
var keyboard = new Guacamole.Keyboard(document);
|
||||
keyboard.listenTo(sink.getElement());
|
||||
var show_keyboard_gesture_possible = true;
|
||||
|
||||
function __send_key(pressed, keysym) {
|
||||
@@ -1553,6 +1585,54 @@ GuacUI.Client.attach = function(guac) {
|
||||
GuacUI.Client.attachedClient.disconnect();
|
||||
};
|
||||
|
||||
/**
|
||||
* Calculates maximum possible height that can be scrolled. This helps avoid
|
||||
* the constant increasing value of scrollHeight on IOS Safari. The
|
||||
* continuous increase of height causes a crash on IOS Safari when the
|
||||
* unnaturally high value of scrollHeight is passed to the scrollTo
|
||||
* function. Therefore in the case where the scrollHeight parameter exceeds
|
||||
* the maximum possible height value, the return value of this parameter is
|
||||
* used instead.
|
||||
*
|
||||
* For additional info, visit:
|
||||
* https://muffinman.io/ios-safari-get-bounding-client-rect-bug/
|
||||
*
|
||||
* @returns {number} A number that represents the max height in pixels.
|
||||
*/
|
||||
function getPageMaxHeight() {
|
||||
return Math.max(
|
||||
document.body.scrollHeight,
|
||||
document.body.offsetHeight,
|
||||
document.documentElement.clientHeight,
|
||||
document.documentElement.scrollHeight,
|
||||
document.documentElement.offsetHeight
|
||||
) - window.innerHeight; // Subtract viewport height
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates maximum possible width that can be scrolled. This helps avoid
|
||||
* the constant increasing value of scrollWidth on IOS Safari. The
|
||||
* continuous increase of width causes a crash on IOS Safari when the
|
||||
* unnaturally high value of scrollWidth is passed to the scrollTo
|
||||
* function. Therefore in the case where the scrollWidth parameter exceeds
|
||||
* the maximum possible width value, the return value of this parameter is
|
||||
* used instead.
|
||||
*
|
||||
* For additional info, visit:
|
||||
* https://muffinman.io/ios-safari-get-bounding-client-rect-bug/
|
||||
*
|
||||
* @returns {number} A number that represents the max width in pixels.
|
||||
*/
|
||||
function getPageMaxWidth() {
|
||||
return Math.max(
|
||||
document.body.scrollWidth,
|
||||
document.body.offsetWidth,
|
||||
document.documentElement.clientWidth,
|
||||
document.documentElement.scrollWidth,
|
||||
document.documentElement.offsetWidth
|
||||
) - window.innerWidth; // Subtract viewport width
|
||||
}
|
||||
|
||||
/*
|
||||
* Reflow layout and send size events on resize/scroll
|
||||
*/
|
||||
@@ -1580,9 +1660,17 @@ GuacUI.Client.attach = function(guac) {
|
||||
last_scroll_height = document.body.scrollHeight;
|
||||
last_window_width = window.innerWidth;
|
||||
last_window_height = window.innerHeight;
|
||||
|
||||
// Get appropriate scrolling height (not greater than max value)
|
||||
var maxHeight = getPageMaxHeight();
|
||||
var scrollHeight = Math.min(document.body.scrollHeight, maxHeight);
|
||||
|
||||
// Get appropriate scrolling width (not greater than max value)
|
||||
var maxWidth = getPageMaxWidth();
|
||||
var scrollWidth = Math.min(document.body.scrollWidth, maxWidth);
|
||||
|
||||
// Reset scroll and reposition document such that it's on-screen
|
||||
window.scrollTo(document.body.scrollWidth, document.body.scrollHeight);
|
||||
window.scrollTo(scrollWidth, scrollHeight);
|
||||
|
||||
// Determine height of bottom section (currently only text input)
|
||||
var bottom = GuacUI.Client.text_input.container;
|
||||
|
@@ -97,53 +97,15 @@ GuacUI.removeClass = function(element, classname) {
|
||||
*/
|
||||
GuacUI.Audio = new (function() {
|
||||
|
||||
var codecs = [
|
||||
'audio/ogg; codecs="vorbis"',
|
||||
'audio/mp4; codecs="mp4a.40.5"',
|
||||
'audio/mpeg; codecs="mp3"',
|
||||
'audio/webm; codecs="vorbis"',
|
||||
'audio/wav; codecs=1'
|
||||
];
|
||||
|
||||
var probably_supported = [];
|
||||
var maybe_supported = [];
|
||||
|
||||
/**
|
||||
* Array of all supported audio mimetypes, ordered by liklihood of
|
||||
* working.
|
||||
*/
|
||||
this.supported = [];
|
||||
this.supported = Guacamole.AudioPlayer.getSupportedTypes();
|
||||
|
||||
// If sound disabled, we're done now.
|
||||
// If sound disabled, declare that no types are supported
|
||||
if (GuacamoleSessionStorage.getItem("disable-sound", false))
|
||||
return;
|
||||
|
||||
// Build array of supported audio formats
|
||||
codecs.forEach(function(mimetype) {
|
||||
|
||||
var audio = new Audio();
|
||||
var support_level = audio.canPlayType(mimetype);
|
||||
|
||||
// Trim semicolon and trailer
|
||||
var semicolon = mimetype.indexOf(";");
|
||||
if (semicolon != -1)
|
||||
mimetype = mimetype.substring(0, semicolon);
|
||||
|
||||
// Partition by probably/maybe
|
||||
if (support_level == "probably")
|
||||
probably_supported.push(mimetype);
|
||||
else if (support_level == "maybe")
|
||||
maybe_supported.push(mimetype);
|
||||
|
||||
});
|
||||
|
||||
// Add probably supported types first
|
||||
Array.prototype.push.apply(
|
||||
this.supported, probably_supported);
|
||||
|
||||
// Prioritize "maybe" supported types second
|
||||
Array.prototype.push.apply(
|
||||
this.supported, maybe_supported);
|
||||
this.supported = [];
|
||||
|
||||
})();
|
||||
|
||||
@@ -152,48 +114,12 @@ GuacUI.Audio = new (function() {
|
||||
*/
|
||||
GuacUI.Video = new (function() {
|
||||
|
||||
var codecs = [
|
||||
'video/ogg; codecs="theora, vorbis"',
|
||||
'video/mp4; codecs="avc1.4D401E, mp4a.40.5"',
|
||||
'video/webm; codecs="vp8.0, vorbis"'
|
||||
];
|
||||
|
||||
var probably_supported = [];
|
||||
var maybe_supported = [];
|
||||
|
||||
/**
|
||||
* Array of all supported video mimetypes, ordered by liklihood of
|
||||
* working.
|
||||
*/
|
||||
this.supported = [];
|
||||
this.supported = Guacamole.VideoPlayer.getSupportedTypes();
|
||||
|
||||
// Build array of supported audio formats
|
||||
codecs.forEach(function(mimetype) {
|
||||
|
||||
var video = document.createElement("video");
|
||||
var support_level = video.canPlayType(mimetype);
|
||||
|
||||
// Trim semicolon and trailer
|
||||
var semicolon = mimetype.indexOf(";");
|
||||
if (semicolon != -1)
|
||||
mimetype = mimetype.substring(0, semicolon);
|
||||
|
||||
// Partition by probably/maybe
|
||||
if (support_level == "probably")
|
||||
probably_supported.push(mimetype);
|
||||
else if (support_level == "maybe")
|
||||
maybe_supported.push(mimetype);
|
||||
|
||||
});
|
||||
|
||||
// Add probably supported types first
|
||||
Array.prototype.push.apply(
|
||||
this.supported, probably_supported);
|
||||
|
||||
// Prioritize "maybe" supported types second
|
||||
Array.prototype.push.apply(
|
||||
this.supported, maybe_supported);
|
||||
|
||||
})();
|
||||
|
||||
/**
|
||||
|
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?><pydev_project>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">uds 2.2</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
|
||||
<pydev_variables_property name="org.python.pydev.PROJECT_VARIABLE_SUBSTITUTION">
|
||||
<key>DJANGO_MANAGE_LOCATION</key>
|
||||
|
@@ -1,4 +1,3 @@
|
||||
ujson
|
||||
xml_marshaller
|
||||
six
|
||||
pycrypto
|
||||
@@ -13,3 +12,4 @@ MySQL-python
|
||||
reportlab
|
||||
bitarray
|
||||
paramiko
|
||||
Django==1.9.9
|
||||
|
@@ -60,8 +60,10 @@ LANGUAGES = (
|
||||
('de', ugettext('German')),
|
||||
('pt', ugettext('Portuguese')),
|
||||
('it', ugettext('Italian')),
|
||||
('ar', ugettext('Arabic')),
|
||||
('eu', ugettext('Basque')),
|
||||
('ca', ugettext('Catalan')),
|
||||
('zh-hans', ugettext('Chinese')),
|
||||
)
|
||||
|
||||
LANGUAGE_COOKIE_NAME = 'uds_lang'
|
||||
|
@@ -78,7 +78,7 @@ class Dispatcher(View):
|
||||
content_type = None
|
||||
|
||||
cls = None
|
||||
while len(path) > 0:
|
||||
while path:
|
||||
# .json, .xml, ... will break path recursion
|
||||
if path[0].find('.') != -1:
|
||||
content_type = path[0].split('.')[1]
|
||||
@@ -92,12 +92,12 @@ class Dispatcher(View):
|
||||
break
|
||||
|
||||
full_path = '/'.join(full_path)
|
||||
logger.debug("REST request: {} ({})".format(full_path, content_type))
|
||||
logger.debug('REST request: %s (%s)', full_path, content_type)
|
||||
|
||||
# Here, service points to the path
|
||||
cls = service['']
|
||||
if cls is None:
|
||||
return http.HttpResponseNotFound('method not found')
|
||||
return http.HttpResponseNotFound('method not found', content_type="text/plain")
|
||||
|
||||
# Guess content type from content type header (post) or ".xxx" to method
|
||||
try:
|
||||
@@ -116,48 +116,44 @@ class Dispatcher(View):
|
||||
except processors.ParametersException as e:
|
||||
logger.debug('Path: {0}'.format(full_path))
|
||||
logger.debug('Error: {0}'.format(e))
|
||||
return http.HttpResponseServerError('Invalid parameters invoking {0}: {1}'.format(full_path, e))
|
||||
return http.HttpResponseServerError('Invalid parameters invoking {0}: {1}'.format(full_path, e), content_type="text/plain")
|
||||
except AttributeError:
|
||||
allowedMethods = []
|
||||
for n in ['get', 'post', 'put', 'delete']:
|
||||
if hasattr(handler, n):
|
||||
allowedMethods.append(n)
|
||||
return http.HttpResponseNotAllowed(allowedMethods)
|
||||
return http.HttpResponseNotAllowed(allowedMethods, content_type="text/plain")
|
||||
except AccessDenied:
|
||||
return http.HttpResponseForbidden('access denied')
|
||||
return http.HttpResponseForbidden('access denied', content_type="text/plain")
|
||||
except Exception:
|
||||
logger.exception('error accessing attribute')
|
||||
logger.debug('Getting attribute {0} for {1}'.format(http_method, full_path))
|
||||
return http.HttpResponseServerError('Unexcepected error')
|
||||
return http.HttpResponseServerError('Unexcepected error', content_type="text/plain")
|
||||
|
||||
# Invokes the handler's operation, add headers to response and returns
|
||||
try:
|
||||
start = time.time()
|
||||
response = operation()
|
||||
logger.debug('Execution time for method: {0}'.format(time.time() - start))
|
||||
|
||||
if not handler.raw: # Raw handlers will return an HttpResponse Object
|
||||
start = time.time()
|
||||
response = processor.getResponse(response)
|
||||
logger.debug('Execution time for encoding: {0}'.format(time.time() - start))
|
||||
for k, val in handler.headers().iteritems():
|
||||
response[k] = val
|
||||
return response
|
||||
except RequestError as e:
|
||||
return http.HttpResponseBadRequest(six.text_type(e))
|
||||
return http.HttpResponseBadRequest(six.text_type(e), content_type="text/plain")
|
||||
except ResponseError as e:
|
||||
return http.HttpResponseServerError(six.text_type(e))
|
||||
return http.HttpResponseServerError(six.text_type(e), content_type="text/plain")
|
||||
except NotSupportedError as e:
|
||||
return http.HttpResponseBadRequest(six.text_type(e))
|
||||
return http.HttpResponseBadRequest(six.text_type(e), content_type="text/plain")
|
||||
except AccessDenied as e:
|
||||
return http.HttpResponseForbidden(six.text_type(e))
|
||||
return http.HttpResponseForbidden(six.text_type(e), content_type="text/plain")
|
||||
except NotFound as e:
|
||||
return http.HttpResponseNotFound(six.text_type(e))
|
||||
return http.HttpResponseNotFound(six.text_type(e), content_type="text/plain")
|
||||
except HandlerError as e:
|
||||
return http.HttpResponseBadRequest(six.text_type(e))
|
||||
return http.HttpResponseBadRequest(six.text_type(e), content_type="text/plain")
|
||||
except Exception as e:
|
||||
logger.exception('Error processing request')
|
||||
return http.HttpResponseServerError(six.text_type(e))
|
||||
return http.HttpResponseServerError(six.text_type(e), content_type="text/plain")
|
||||
|
||||
@staticmethod
|
||||
def registerSubclasses(classes):
|
||||
@@ -207,4 +203,5 @@ class Dispatcher(View):
|
||||
|
||||
Dispatcher.registerSubclasses(Handler.__subclasses__()) # @UndefinedVariable
|
||||
|
||||
|
||||
Dispatcher.initialize()
|
||||
|
@@ -195,7 +195,7 @@ class Handler(object):
|
||||
session['REST'] = {
|
||||
'auth': id_auth,
|
||||
'username': username,
|
||||
'password': cryptoManager().xor(password, scrambler), # Stores "bytes"
|
||||
'password': cryptoManager().symCrypt(password, scrambler), # Stores "bytes"
|
||||
'locale': locale,
|
||||
'is_admin': is_admin,
|
||||
'staff_member': staff_member
|
||||
|
@@ -121,8 +121,8 @@ class Authenticators(ModelHandler):
|
||||
return auth.searchUsers(term)
|
||||
else:
|
||||
return auth.searchGroups(term)
|
||||
except Exception:
|
||||
self.invalidRequestException()
|
||||
except Exception as e:
|
||||
self.invalidResponseException('{}'.format(e))
|
||||
|
||||
def test(self, type_):
|
||||
from uds.core.Environment import Environment
|
||||
|
@@ -135,7 +135,7 @@ class Client(Handler):
|
||||
res = userServiceManager().getService(self._request.user, self._request.ip, data['service'], data['transport'])
|
||||
logger.debug('Res: {}'.format(res))
|
||||
ip, userService, userServiceInstance, transport, transportInstance = res
|
||||
password = cryptoManager().xor(data['password'], scrambler).decode('utf-8')
|
||||
password = cryptoManager().symDecrpyt(data['password'], scrambler)
|
||||
|
||||
userService.setConnectionSource(srcIp, hostname) # Store where we are accessing from so we can notify Service
|
||||
|
||||
|
@@ -52,7 +52,7 @@ import logging
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Enclosed methods under /actor path
|
||||
# Enclosed methods under /connection path
|
||||
class Connection(Handler):
|
||||
'''
|
||||
Processes actor requests
|
||||
@@ -112,7 +112,7 @@ class Connection(Handler):
|
||||
'not_accesible': not servicePool.isAccessAllowed(),
|
||||
'to_be_replaced': False, # Manually assigned will not be autoremoved never
|
||||
'transports': trans,
|
||||
'in_use': svr.in_use})
|
||||
'in_use': servicePool.in_use})
|
||||
|
||||
logger.debug(services)
|
||||
|
||||
@@ -162,7 +162,8 @@ class Connection(Handler):
|
||||
'protocol': 'unknown',
|
||||
'ip': ip
|
||||
}
|
||||
ci.update(itrans.getConnectionInfo(userService, self._user, 'UNKNOWN'))
|
||||
if doNotCheck is False:
|
||||
ci.update(itrans.getConnectionInfo(userService, self._user, 'UNKNOWN'))
|
||||
return Connection.result(result=ci)
|
||||
except ServiceNotReadyError as e:
|
||||
# Refresh ticket and make this retrayable
|
||||
@@ -181,7 +182,7 @@ class Connection(Handler):
|
||||
res = userServiceManager().getService(self._user, self._request.ip, idService, idTransport)
|
||||
logger.debug('Res: {}'.format(res))
|
||||
ip, userService, userServiceInstance, transport, transportInstance = res
|
||||
password = cryptoManager().xor(self.getValue('password'), scrambler).decode('utf-8')
|
||||
password = cryptoManager().symDecrpyt(self.getValue('password'), scrambler)
|
||||
|
||||
userService.setConnectionSource(self._request.ip, hostname) # Store where we are accessing from so we can notify Service
|
||||
|
||||
|
@@ -78,7 +78,7 @@ class Login(Handler):
|
||||
if 'authId' not in self._params and 'authSmallName' not in self._params and 'auth' not in self._params:
|
||||
raise RequestError('Invalid parameters (no auth)')
|
||||
|
||||
scrambler = ''.join(random.choice(string.letters + string.digits) for _ in range(32)) # @UndefinedVariable
|
||||
scrambler = ''.join(random.SystemRandom().choice(string.letters + string.digits) for _ in range(32)) # @UndefinedVariable
|
||||
authId = self._params.get('authId', None)
|
||||
authSmallName = self._params.get('authSmallName', None)
|
||||
authName = self._params.get('auth', None)
|
||||
|
@@ -62,6 +62,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
@staticmethod
|
||||
def serviceInfo(item):
|
||||
info = item.getType()
|
||||
|
||||
return {
|
||||
'icon': info.icon().replace('\n', ''),
|
||||
'needs_publication': info.publicationType is not None,
|
||||
@@ -74,6 +75,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
'allowedProtocols': info.allowedProtocols,
|
||||
'servicesTypeProvided': info.servicesTypeProvided,
|
||||
'must_assign_manually': info.mustAssignManually,
|
||||
'can_reset': info.canReset,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
@@ -157,7 +159,8 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
self._deleteIncompleteService(service)
|
||||
raise RequestError(_('Input error: {0}'.format(unicode(e))))
|
||||
except Exception as e:
|
||||
self._deleteIncompleteService(service)
|
||||
if item is None:
|
||||
self._deleteIncompleteService(service)
|
||||
logger.exception('Saving Service')
|
||||
raise RequestError('incorrect invocation to PUT: {0}'.format(e))
|
||||
|
||||
|
@@ -37,7 +37,6 @@ 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
|
||||
@@ -60,6 +59,7 @@ class AccessCalendars(DetailHandler):
|
||||
'''
|
||||
Processes the transports detail requests of a Service Pool
|
||||
'''
|
||||
|
||||
@staticmethod
|
||||
def as_dict(item):
|
||||
return {
|
||||
@@ -80,7 +80,6 @@ class AccessCalendars(DetailHandler):
|
||||
except Exception:
|
||||
self.invalidItemException()
|
||||
|
||||
|
||||
def getTitle(self, parent):
|
||||
return _('Access restrictions by calendar')
|
||||
|
||||
@@ -109,10 +108,19 @@ class AccessCalendars(DetailHandler):
|
||||
else:
|
||||
CalendarAccess.objects.create(calendar=calendar, service_pool=parent, access=access, priority=priority)
|
||||
|
||||
log.doLog(parent, log.INFO, "Added access calendar {}/{} by {}".format(calendar.name, access, self._user.pretty_name), log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
def deleteItem(self, parent, item):
|
||||
CalendarAccess.objects.get(uuid=processUuid(self._args[0])).delete()
|
||||
calendarAccess = CalendarAccess.objects.get(uuid=processUuid(self._args[0]))
|
||||
logStr = "Removed access calendar {} by {}".format(calendarAccess.calendar.name, self._user.pretty_name)
|
||||
|
||||
calendarAccess.delete()
|
||||
|
||||
log.doLog(parent, log.INFO, logStr, log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
|
||||
class ActionsCalendars(DetailHandler):
|
||||
@@ -128,7 +136,7 @@ class ActionsCalendars(DetailHandler):
|
||||
'calendarId': item.calendar.uuid,
|
||||
'calendar': item.calendar.name,
|
||||
'action': item.action,
|
||||
'actionDescription': CALENDAR_ACTION_DICT[item.action]['description'],
|
||||
'actionDescription': CALENDAR_ACTION_DICT.get(item.action, {}).get('description', ''),
|
||||
'atStart': item.at_start,
|
||||
'eventsOffset': item.events_offset,
|
||||
'params': json.loads(item.params),
|
||||
@@ -146,7 +154,6 @@ class ActionsCalendars(DetailHandler):
|
||||
except Exception:
|
||||
self.invalidItemException()
|
||||
|
||||
|
||||
def getTitle(self, parent):
|
||||
return _('Scheduled actions')
|
||||
|
||||
@@ -167,11 +174,17 @@ class ActionsCalendars(DetailHandler):
|
||||
|
||||
calendar = Calendar.objects.get(uuid=processUuid(self._params['calendarId']))
|
||||
action = self._params['action'].upper()
|
||||
if action not in CALENDAR_ACTION_DICT:
|
||||
self.invalidRequestException()
|
||||
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))
|
||||
# logger.debug('Got parameters: {} {} {} {} ----> {}'.format(calendar, action, eventsOffset, atStart, params))
|
||||
logStr = "Added scheduled action \"{},{},{},{},{}\" by {}".format(
|
||||
calendar.name, action, eventsOffset,
|
||||
atStart and 'Start' or 'End', params, self._user.pretty_name
|
||||
)
|
||||
|
||||
if uuid is not None:
|
||||
calAction = CalendarAction.objects.get(uuid=uuid)
|
||||
@@ -185,16 +198,36 @@ class ActionsCalendars(DetailHandler):
|
||||
else:
|
||||
CalendarAction.objects.create(calendar=calendar, service_pool=parent, action=action, at_start=atStart, events_offset=eventsOffset, params=params)
|
||||
|
||||
log.doLog(parent, log.INFO, logStr, log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
def deleteItem(self, parent, item):
|
||||
CalendarAction.objects.get(uuid=processUuid(self._args[0])).delete()
|
||||
calendarAction = CalendarAction.objects.get(uuid=processUuid(self._args[0]))
|
||||
logStr = "Removed scheduled action \"{},{},{},{},{}\" by {}".format(
|
||||
calendarAction.calendar.name, calendarAction.action,
|
||||
calendarAction.events_offset, calendarAction.at_start and 'Start' or 'End', calendarAction.params,
|
||||
self._user.pretty_name
|
||||
)
|
||||
|
||||
calendarAction.delete()
|
||||
|
||||
log.doLog(parent, log.INFO, logStr, log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
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()
|
||||
calendarAction = CalendarAction.objects.get(uuid=uuid)
|
||||
logStr = "Launched scheduled action \"{},{},{},{},{}\" by {}".format(
|
||||
calendarAction.calendar.name, calendarAction.action,
|
||||
calendarAction.events_offset, calendarAction.at_start and 'Start' or 'End', calendarAction.params,
|
||||
self._user.pretty_name
|
||||
)
|
||||
|
||||
calendarAction.execute()
|
||||
|
||||
log.doLog(parent, log.INFO, logStr, log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
@@ -72,7 +72,8 @@ class ServicesPools(ModelHandler):
|
||||
save_fields = ['name', 'short_name', 'comments', 'tags', 'service_id',
|
||||
'osmanager_id', 'image_id', 'servicesPoolGroup_id', 'initial_srvs',
|
||||
'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs', 'show_transports',
|
||||
'allow_users_remove', 'ignores_unused']
|
||||
'allow_users_remove', 'allow_users_reset', 'ignores_unused']
|
||||
|
||||
remove_fields = ['osmanager_id', 'service_id']
|
||||
|
||||
table_title = _('Service Pools')
|
||||
@@ -91,55 +92,65 @@ class ServicesPools(ModelHandler):
|
||||
|
||||
custom_methods = [('setFallbackAccess', True), ('actionsList', True)]
|
||||
|
||||
def getItems(self, *args, **kwargs):
|
||||
return super(ServicesPools, self).getItems(overview=kwargs.get('overview', True), prefetch=['service', 'service__provider', 'servicesPoolGroup', 'image', 'tags'])
|
||||
# return super(ServicesPools, self).getItems(overview)
|
||||
|
||||
def item_as_dict(self, item):
|
||||
summary = 'summarize' in self._params
|
||||
# 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
|
||||
|
||||
state = item.state
|
||||
if item.isInMaintenance():
|
||||
state = State.MAINTENANCE
|
||||
elif userServiceManager().canInitiateServiceFromDeployedService(item) is False:
|
||||
state = State.SLOWED_DOWN
|
||||
|
||||
val = {
|
||||
'id': item.uuid,
|
||||
'name': item.name,
|
||||
'short_name': item.short_name,
|
||||
'tags': [tag.tag for tag in item.tags.all()],
|
||||
'parent': item.service.name,
|
||||
'parent_type': item.service.data_type,
|
||||
'comments': item.comments,
|
||||
'state': state,
|
||||
'thumb': item.image.thumb64 if item.image is not None else DEFAULT_THUMB_BASE64,
|
||||
'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,
|
||||
'max_srvs': item.max_srvs,
|
||||
'user_services_count': item.userServices.exclude(state__in=State.INFO_STATES).count(),
|
||||
'user_services_in_preparation': item.userServices.filter(state=State.PREPARING).count(),
|
||||
'restrained': item.isRestrained(),
|
||||
'show_transports': item.show_transports,
|
||||
'allow_users_remove': item.allow_users_remove,
|
||||
'allow_users_reset': item.allow_users_reset,
|
||||
'ignores_unused': item.ignores_unused,
|
||||
'fallbackAccess': item.fallbackAccess,
|
||||
'permission': permissions.getEffectivePermission(self._user, item),
|
||||
'info': Services.serviceInfo(item.service),
|
||||
}
|
||||
|
||||
# Extended info
|
||||
if not summary:
|
||||
state = item.state
|
||||
if item.isInMaintenance():
|
||||
state = State.MAINTENANCE
|
||||
elif userServiceManager().canInitiateServiceFromDeployedService(item) is False:
|
||||
state = State.SLOWED_DOWN
|
||||
|
||||
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['state'] = state
|
||||
val['thumb'] = item.image.thumb64 if item.image is not None else DEFAULT_THUMB_BASE64
|
||||
val['user_services_count'] = item.userServices.exclude(state__in=State.INFO_STATES).count()
|
||||
val['user_services_in_preparation'] = item.userServices.filter(state=State.PREPARING).count()
|
||||
val['tags'] = [tag.tag for tag in item.tags.all()]
|
||||
val['restrained'] = item.isRestrained()
|
||||
val['permission'] = permissions.getEffectivePermission(self._user, item)
|
||||
val['info'] = Services.serviceInfo(item.service)
|
||||
val['servicesPoolGroup_id'] = poolGroupId
|
||||
val['pool_group_name'] = poolGroupName
|
||||
val['pool_group_thumb'] = poolGroupThumb
|
||||
|
||||
if item.osmanager is not None:
|
||||
val['osmanager_id'] = item.osmanager.uuid
|
||||
|
||||
@@ -155,98 +166,106 @@ class ServicesPools(ModelHandler):
|
||||
g = self.addDefaultFields([], ['name', 'short_name', 'comments', 'tags'])
|
||||
|
||||
for f in [{
|
||||
'name': 'service_id',
|
||||
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.provider.name + '\\' + v.name) for v in Service.objects.all()]),
|
||||
'label': ugettext('Base service'),
|
||||
'tooltip': ugettext('Service used as base of this service pool'),
|
||||
'type': gui.InputField.CHOICE_TYPE,
|
||||
'rdonly': True,
|
||||
'order': 100, # Ensueres is At end
|
||||
}, {
|
||||
'name': 'osmanager_id',
|
||||
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.name) for v in OSManager.objects.all()]),
|
||||
'label': ugettext('OS Manager'),
|
||||
'tooltip': ugettext('OS Manager used as base of this service pool'),
|
||||
'type': gui.InputField.CHOICE_TYPE,
|
||||
'rdonly': True,
|
||||
'order': 101,
|
||||
}, {
|
||||
'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': 110,
|
||||
'tab': ugettext('Advanced'),
|
||||
}, {
|
||||
'name': 'allow_users_remove',
|
||||
'value': False,
|
||||
'label': ugettext('Allow removal by users'),
|
||||
'tooltip': ugettext('If active, the user will be allowed to remove the service "manually". Be care with this, because the user will have the "poser" to delete it\'s own service'),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 111,
|
||||
'tab': ugettext('Advanced'),
|
||||
}, {
|
||||
'name': 'ignores_unused',
|
||||
'value': False,
|
||||
'label': ugettext('Ignores unused'),
|
||||
'tooltip': ugettext('If active, UDS will not try to detect and remove assigned but not used user services.'),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 112,
|
||||
'tab': ugettext('Advanced'),
|
||||
}, {
|
||||
'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': 120,
|
||||
'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': 121,
|
||||
'tab': ugettext('Display'),
|
||||
}, {
|
||||
'name': 'initial_srvs',
|
||||
'value': '0',
|
||||
'minValue': '0',
|
||||
'label': ugettext('Initial available services'),
|
||||
'tooltip': ugettext('Services created initially for this service pool'),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 130,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
'name': 'cache_l1_srvs',
|
||||
'value': '0',
|
||||
'minValue': '0',
|
||||
'label': ugettext('Services to keep in cache'),
|
||||
'tooltip': ugettext('Services kept in cache for improved user service assignation'),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 131,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
'name': 'cache_l2_srvs',
|
||||
'value': '0',
|
||||
'minValue': '0',
|
||||
'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': 132,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
'name': 'max_srvs',
|
||||
'value': '0',
|
||||
'minValue': '1',
|
||||
'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': 133,
|
||||
'tab': ugettext('Availability'),
|
||||
}]:
|
||||
'name': 'service_id',
|
||||
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.provider.name + '\\' + v.name) for v in Service.objects.all()]),
|
||||
'label': ugettext('Base service'),
|
||||
'tooltip': ugettext('Service used as base of this service pool'),
|
||||
'type': gui.InputField.CHOICE_TYPE,
|
||||
'rdonly': True,
|
||||
'order': 100, # Ensueres is At end
|
||||
}, {
|
||||
'name': 'osmanager_id',
|
||||
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.name) for v in OSManager.objects.all()]),
|
||||
'label': ugettext('OS Manager'),
|
||||
'tooltip': ugettext('OS Manager used as base of this service pool'),
|
||||
'type': gui.InputField.CHOICE_TYPE,
|
||||
'rdonly': True,
|
||||
'order': 101,
|
||||
}, {
|
||||
'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': 110,
|
||||
'tab': ugettext('Advanced'),
|
||||
}, {
|
||||
'name': 'allow_users_remove',
|
||||
'value': False,
|
||||
'label': ugettext('Allow removal by users'),
|
||||
'tooltip': ugettext('If active, the user will be allowed to remove the service "manually". Be careful with this, because the user will have the "power" to delete it\'s own service'),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 111,
|
||||
'tab': ugettext('Advanced'),
|
||||
}, {
|
||||
'name': 'allow_users_reset',
|
||||
'value': False,
|
||||
'label': ugettext('Allow reset by users'),
|
||||
'tooltip': ugettext('If active, the user will be allowed to reset the service'),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 112,
|
||||
'tab': ugettext('Advanced'),
|
||||
}, {
|
||||
'name': 'ignores_unused',
|
||||
'value': False,
|
||||
'label': ugettext('Ignores unused'),
|
||||
'tooltip': ugettext('If the option is enabled, UDS will not attempt to detect and remove the user services assigned but not in use.'),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 113,
|
||||
'tab': ugettext('Advanced'),
|
||||
}, {
|
||||
'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': 120,
|
||||
'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 classify on display)'),
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 121,
|
||||
'tab': ugettext('Display'),
|
||||
}, {
|
||||
'name': 'initial_srvs',
|
||||
'value': '0',
|
||||
'minValue': '0',
|
||||
'label': ugettext('Initial available services'),
|
||||
'tooltip': ugettext('Services created initially for this service pool'),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 130,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
'name': 'cache_l1_srvs',
|
||||
'value': '0',
|
||||
'minValue': '0',
|
||||
'label': ugettext('Services to keep in cache'),
|
||||
'tooltip': ugettext('Services kept in cache for improved user service assignation'),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 131,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
'name': 'cache_l2_srvs',
|
||||
'value': '0',
|
||||
'minValue': '0',
|
||||
'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': 132,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
'name': 'max_srvs',
|
||||
'value': '0',
|
||||
'minValue': '1',
|
||||
'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': 133,
|
||||
'tab': ugettext('Availability'),
|
||||
}]:
|
||||
self.addField(g, f)
|
||||
|
||||
return g
|
||||
@@ -266,6 +285,9 @@ class ServicesPools(ModelHandler):
|
||||
if serviceType.publicationType is None:
|
||||
self._params['publish_on_save'] = False
|
||||
|
||||
if serviceType.canReset is False:
|
||||
self._params['allow_users_reset'] = False
|
||||
|
||||
if serviceType.needsManager is True:
|
||||
osmanager = OSManager.objects.get(uuid=processUuid(fields['osmanager_id']))
|
||||
fields['osmanager_id'] = osmanager.id
|
||||
|
@@ -38,6 +38,7 @@ from uds.models import Authenticator
|
||||
from uds.models import DeployedService
|
||||
from uds.models import Transport
|
||||
from uds.models import TicketStore
|
||||
from uds.core.managers import cryptoManager
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.core.util import tools
|
||||
|
||||
@@ -207,7 +208,7 @@ class Tickets(Handler):
|
||||
|
||||
data = {
|
||||
'username': username,
|
||||
'password': password,
|
||||
'password': cryptoManager().encrypt(password),
|
||||
'realname': realname,
|
||||
'groups': groups,
|
||||
'auth': auth.uuid,
|
||||
|
@@ -33,7 +33,7 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _, ugettext
|
||||
from uds.models import Transport, Network
|
||||
from uds.models import Transport, Network, ServicePool
|
||||
from uds.core.transports import factory
|
||||
from uds.core.util import permissions
|
||||
from uds.core.util import OsDetector
|
||||
@@ -65,34 +65,47 @@ class Transports(ModelHandler):
|
||||
|
||||
def getGui(self, type_):
|
||||
try:
|
||||
return self.addField(
|
||||
self.addField(
|
||||
self.addField(self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments', 'tags', 'priority']), {
|
||||
'name': 'nets_positive',
|
||||
'value': True,
|
||||
'label': ugettext('Network access'),
|
||||
'tooltip': ugettext('If checked, the transport will be enabled for the selected networks.If unchecked, transport will be disabled for selected networks'),
|
||||
'type': 'checkbox',
|
||||
'order': 100, # At end
|
||||
}), {
|
||||
'name': 'networks',
|
||||
'value': [],
|
||||
'values': sorted([{'id': x.id, 'text': x.name} for x in Network.objects.all()], key=lambda x: x['text'].lower()), # TODO: We will fix this behavior after current admin client is fully removed
|
||||
'label': ugettext('Networks'),
|
||||
'tooltip': ugettext('Networks associated with this transport. If No network selected, will mean "all networks"'),
|
||||
'type': 'multichoice',
|
||||
'order': 101
|
||||
}), {
|
||||
'name': 'allowed_oss',
|
||||
'value': [],
|
||||
'values': sorted([{'id': x, 'text': x} for x in OsDetector.knownOss], key=lambda x: x['text'].lower()), # TODO: We will fix this behavior after current admin client is fully removed
|
||||
'label': ugettext('Allowed Devices'),
|
||||
'tooltip': ugettext('If empty, any kind of device compatible with this transport will be allowed. Else, only devices compatible with selected values will be allowed'),
|
||||
'type': 'multichoice',
|
||||
'order': 102
|
||||
})
|
||||
field = self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments', 'tags', 'priority'])
|
||||
field = self.addField(field, {
|
||||
'name': 'nets_positive',
|
||||
'value': True,
|
||||
'label': ugettext('Network access'),
|
||||
'tooltip': ugettext('If checked, the transport will be enabled for the selected networks. If unchecked, transport will be disabled for selected networks'),
|
||||
'type': 'checkbox',
|
||||
'order': 100, # At end
|
||||
})
|
||||
field = self.addField(field, {
|
||||
'name': 'networks',
|
||||
'value': [],
|
||||
'values': sorted([{'id': x.id, 'text': x.name} for x in Network.objects.all()], key=lambda x: x['text'].lower()), # TODO: We will fix this behavior after current admin client is fully removed
|
||||
'label': ugettext('Networks'),
|
||||
'tooltip': ugettext('Networks associated with this transport. If No network selected, will mean "all networks"'),
|
||||
'type': 'multichoice',
|
||||
'order': 101
|
||||
})
|
||||
field = self.addField(field, {
|
||||
'name': 'allowed_oss',
|
||||
'value': [],
|
||||
'values': sorted([{'id': x, 'text': x} for x in OsDetector.knownOss], key=lambda x: x['text'].lower()), # TODO: We will fix this behavior after current admin client is fully removed
|
||||
'label': ugettext('Allowed Devices'),
|
||||
'tooltip': ugettext('If empty, any kind of device compatible with this transport will be allowed. Else, only devices compatible with selected values will be allowed'),
|
||||
'type': 'multichoice',
|
||||
'order': 102
|
||||
})
|
||||
field = self.addField(field, {
|
||||
'name': 'pools',
|
||||
'value': [],
|
||||
'values': [{'id': x.id, 'text': x.name} for x in ServicePool.objects.all().order_by('name')], # TODO: We will fix this behavior after current admin client is fully removed
|
||||
'label': ugettext('Service Pools'),
|
||||
'tooltip': ugettext('Currently assigned services pools'),
|
||||
'type': 'multichoice',
|
||||
'order': 103
|
||||
})
|
||||
|
||||
return field
|
||||
|
||||
except Exception:
|
||||
logger.exception('eeeeeh')
|
||||
self.invalidItemException()
|
||||
|
||||
def item_as_dict(self, item):
|
||||
@@ -106,6 +119,7 @@ class Transports(ModelHandler):
|
||||
'nets_positive': item.nets_positive,
|
||||
'networks': [{'id': n.id} for n in item.networks.all()],
|
||||
'allowed_oss': [{'id': x} for x in item.allowed_oss.split(',')] if item.allowed_oss != '' else [],
|
||||
'pools': [{'id': x.id} for x in item.deployedServices.all()],
|
||||
'deployed_count': item.deployedServices.count(),
|
||||
'type': type_.type(),
|
||||
'protocol': type_.protocol,
|
||||
@@ -115,7 +129,6 @@ class Transports(ModelHandler):
|
||||
def beforeSave(self, fields):
|
||||
fields['allowed_oss'] = ','.join(fields['allowed_oss'])
|
||||
|
||||
|
||||
def afterSave(self, item):
|
||||
try:
|
||||
networks = self._params['networks']
|
||||
@@ -124,8 +137,20 @@ class Transports(ModelHandler):
|
||||
return
|
||||
if networks is None:
|
||||
return
|
||||
logger.debug('Networks: {0}'.format(networks))
|
||||
item.networks = Network.objects.filter(id__in=networks)
|
||||
logger.debug('Networks: {}'.format(networks))
|
||||
item.networks.set(Network.objects.filter(id__in=networks))
|
||||
|
||||
try:
|
||||
pools = self._params['pools']
|
||||
except Exception:
|
||||
logger.debug('No pools')
|
||||
pools = None
|
||||
|
||||
if pools is None:
|
||||
return
|
||||
|
||||
logger.debug('Pools: %s', pools)
|
||||
item.deployedServices.set(pools)
|
||||
|
||||
# try:
|
||||
# oss = ','.join(self._params['allowed_oss'])
|
||||
|
@@ -37,8 +37,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
from uds.models import Group, Transport, DeployedServicePublication
|
||||
from uds.models import Group, Transport, DeployedServicePublication, User
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.core.util import log
|
||||
@@ -46,8 +45,6 @@ from uds.REST.model import DetailHandler
|
||||
from uds.REST import ResponseError
|
||||
from uds.core.util import permissions
|
||||
|
||||
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -156,7 +153,11 @@ class AssignedService(DetailHandler):
|
||||
logger.exception('deleteItem')
|
||||
self.invalidItemException()
|
||||
|
||||
logger.debug('Deleting assigned service')
|
||||
if service.user:
|
||||
logStr = 'Deleted assigned service {} to user {} by {}'.format(service.friendly_name, service.user.pretty_name, self._user.pretty_name)
|
||||
else:
|
||||
logStr = 'Deleted cached service {} by {}'.format(service.friendly_name, self._user.pretty_name)
|
||||
|
||||
if service.state in (State.USABLE, State.REMOVING):
|
||||
service.remove()
|
||||
elif service.state == State.PREPARING:
|
||||
@@ -166,6 +167,28 @@ class AssignedService(DetailHandler):
|
||||
else:
|
||||
self.invalidItemException(_('Item is not removable'))
|
||||
|
||||
log.doLog(parent, log.INFO, logStr, log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
# Only owner is allowed to change right now
|
||||
def saveItem(self, parent, item):
|
||||
fields = self.readFieldsFromParams(['auth_id', 'user_id'])
|
||||
service = parent.userServices.get(uuid=processUuid(item))
|
||||
user = User.objects.get(uuid=processUuid(fields['user_id']))
|
||||
|
||||
logStr = 'Changing ownership of service from {} to {} by {}'.format(service.user.pretty_name, user.pretty_name, self._user.pretty_name)
|
||||
|
||||
# If there is another service that has this same owner, raise an exception
|
||||
if parent.userServices.filter(user=user).exclude(uuid=service.uuid).exclude(state__in=State.INFO_STATES).count() > 0:
|
||||
raise self.invalidResponseException('There is already another user service assigned to {}'.format(user.pretty_name))
|
||||
|
||||
service.user = user
|
||||
service.save()
|
||||
|
||||
# Log change
|
||||
log.doLog(service, log.INFO, logStr, log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
|
||||
@@ -215,6 +238,7 @@ class Groups(DetailHandler):
|
||||
'''
|
||||
Processes the groups detail requests of a Service Pool
|
||||
'''
|
||||
|
||||
def getItems(self, parent, item):
|
||||
return [{
|
||||
'id': i.uuid,
|
||||
@@ -241,17 +265,25 @@ class Groups(DetailHandler):
|
||||
return {'field': 'state', 'prefix': 'row-state-'}
|
||||
|
||||
def saveItem(self, parent, item):
|
||||
parent.assignedGroups.add(Group.objects.get(uuid=processUuid(self._params['id'])))
|
||||
grp = Group.objects.get(uuid=processUuid(self._params['id']))
|
||||
parent.assignedGroups.add(grp)
|
||||
log.doLog(parent, log.INFO, "Added group {} by {}".format(grp.pretty_name, self._user.pretty_name), log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
def deleteItem(self, parent, item):
|
||||
parent.assignedGroups.remove(Group.objects.get(uuid=processUuid(self._args[0])))
|
||||
grp = Group.objects.get(uuid=processUuid(self._args[0]))
|
||||
parent.assignedGroups.remove(grp)
|
||||
log.doLog(parent, log.INFO, "Removed group {} by {}".format(grp.pretty_name, self._user.pretty_name), log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
|
||||
class Transports(DetailHandler):
|
||||
'''
|
||||
Processes the transports detail requests of a Service Pool
|
||||
'''
|
||||
|
||||
def getItems(self, parent, item):
|
||||
return [{
|
||||
'id': i.uuid,
|
||||
@@ -273,11 +305,18 @@ class Transports(DetailHandler):
|
||||
]
|
||||
|
||||
def saveItem(self, parent, item):
|
||||
parent.transports.add(Transport.objects.get(uuid=processUuid(self._params['id'])))
|
||||
transport = Transport.objects.get(uuid=processUuid(self._params['id']))
|
||||
parent.transports.add(transport)
|
||||
log.doLog(parent, log.INFO, "Added transport {} by {}".format(transport.name, self._user.pretty_name), log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
def deleteItem(self, parent, item):
|
||||
parent.transports.remove(Transport.objects.get(uuid=processUuid(self._args[0])))
|
||||
transport = Transport.objects.get(uuid=processUuid(self._args[0]))
|
||||
parent.transports.remove(transport)
|
||||
log.doLog(parent, log.INFO, "Removed transport {} by {}".format(transport.name, self._user.pretty_name), log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
|
||||
class Publications(DetailHandler):
|
||||
@@ -299,6 +338,9 @@ class Publications(DetailHandler):
|
||||
|
||||
logger.debug('Custom "publish" invoked for {}'.format(parent))
|
||||
parent.publish(changeLog) # Can raise exceptions that will be processed on response
|
||||
|
||||
log.doLog(parent, log.INFO, "Initated publication v{} by {}".format(parent.current_pub_revision, self._user.pretty_name), log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
def cancel(self, parent, uuid):
|
||||
@@ -318,6 +360,8 @@ class Publications(DetailHandler):
|
||||
except Exception as e:
|
||||
raise ResponseError(unicode(e))
|
||||
|
||||
log.doLog(parent, log.INFO, "Canceled publication v{} by {}".format(parent.current_pub_revision, self._user.pretty_name), log.ADMIN)
|
||||
|
||||
return self.success()
|
||||
|
||||
def getItems(self, parent, item):
|
||||
@@ -352,6 +396,7 @@ class Changelog(DetailHandler):
|
||||
'''
|
||||
Processes the transports detail requests of a Service Pool
|
||||
'''
|
||||
|
||||
def getItems(self, parent, item):
|
||||
return [{
|
||||
'revision': i.revision,
|
||||
|
@@ -42,7 +42,7 @@ from uds.core.util.State import State
|
||||
from uds.core.auths.Exceptions import AuthenticatorException
|
||||
from uds.core.util import log
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.models import Authenticator, User, Group
|
||||
from uds.models import Authenticator, User, Group, ServicePool
|
||||
from uds.core.auths.User import User as aUser
|
||||
from uds.core.managers import cryptoManager
|
||||
from uds.REST import RequestError
|
||||
@@ -57,6 +57,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Details of /auth
|
||||
|
||||
|
||||
def getGroupsFromMeta(groups):
|
||||
for g in groups:
|
||||
if g.is_meta:
|
||||
@@ -67,9 +68,8 @@ def getGroupsFromMeta(groups):
|
||||
|
||||
|
||||
def getPoolsForGroups(groups):
|
||||
for g in groups:
|
||||
for servicePool in g.deployedServices.all():
|
||||
yield servicePool
|
||||
for servicePool in ServicePool.getDeployedServicesForGroups(groups):
|
||||
yield servicePool
|
||||
|
||||
|
||||
class Users(DetailHandler):
|
||||
@@ -179,11 +179,19 @@ class Users(DetailHandler):
|
||||
user = parent.users.get(uuid=processUuid(item))
|
||||
|
||||
for us in user.userServices.all():
|
||||
us.user = None
|
||||
us.removeOrCancel()
|
||||
try:
|
||||
us.user = None
|
||||
us.removeOrCancel()
|
||||
except Exception:
|
||||
logger.exception('Removing user service')
|
||||
try:
|
||||
us.save()
|
||||
except Exception as e:
|
||||
logger.exception('Saving user on removing error')
|
||||
|
||||
user.delete()
|
||||
except Exception:
|
||||
logger.exception('Removing user')
|
||||
self.invalidItemException()
|
||||
|
||||
return 'deleted'
|
||||
@@ -192,7 +200,8 @@ class Users(DetailHandler):
|
||||
uuid = processUuid(item)
|
||||
user = parent.users.get(uuid=processUuid(uuid))
|
||||
res = []
|
||||
for i in getPoolsForGroups(user.groups.all()):
|
||||
groups = list(user.getGroups())
|
||||
for i in getPoolsForGroups(groups):
|
||||
res.append({
|
||||
'id': i.uuid,
|
||||
'name': i.name,
|
||||
@@ -217,7 +226,6 @@ class Users(DetailHandler):
|
||||
return res
|
||||
|
||||
|
||||
|
||||
class Groups(DetailHandler):
|
||||
|
||||
custom_methods = ['servicesPools', 'users']
|
||||
@@ -241,11 +249,17 @@ class Groups(DetailHandler):
|
||||
'meta_if_any': i.meta_if_any
|
||||
}
|
||||
if i.is_meta:
|
||||
val['groups'] = list(x.uuid for x in i.groups.all())
|
||||
val['groups'] = list(x.uuid for x in i.groups.all().order_by('name'))
|
||||
res.append(val)
|
||||
if multi:
|
||||
return res
|
||||
return res[0]
|
||||
# Add pools field if 1 item only
|
||||
res = res[0]
|
||||
if i.is_meta:
|
||||
res['pools'] = [] # Meta groups do not have "assigned "pools, they get it from groups interaction
|
||||
else:
|
||||
res['pools'] = [v.uuid for v in i.deployedServices.all()]
|
||||
return res
|
||||
except:
|
||||
logger.exception('REST groups')
|
||||
self.invalidItemException()
|
||||
@@ -286,8 +300,10 @@ class Groups(DetailHandler):
|
||||
try:
|
||||
is_meta = self._params['type'] == 'meta'
|
||||
meta_if_any = self._params.get('meta_if_any', False)
|
||||
pools = self._params.get('pools', None)
|
||||
logger.debug('Saving group {0} / {1}'.format(parent, item))
|
||||
logger.debug('Meta any {}'.format(meta_if_any))
|
||||
logger.debug('Pools: %s', pools)
|
||||
valid_fields = ['name', 'comments', 'state']
|
||||
fields = self.readFieldsFromParams(valid_fields)
|
||||
is_pattern = fields.get('name', '').find('pat:') == 0
|
||||
@@ -316,7 +332,11 @@ class Groups(DetailHandler):
|
||||
group.__dict__.update(toSave)
|
||||
|
||||
if is_meta:
|
||||
group.groups = parent.groups.filter(uuid__in=self._params['groups'])
|
||||
group.groups.set(parent.groups.filter(uuid__in=self._params['groups']))
|
||||
|
||||
if pools:
|
||||
# Update pools
|
||||
group.deployedServices.set(ServicePool.objects.filter(uuid__in=pools))
|
||||
|
||||
group.save()
|
||||
except Group.DoesNotExist:
|
||||
|
@@ -44,7 +44,6 @@ from uds.core.util import permissions
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.models import Tag
|
||||
|
||||
|
||||
import fnmatch
|
||||
import re
|
||||
import itertools
|
||||
@@ -54,8 +53,7 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__updated__ = '2017-10-25'
|
||||
|
||||
__updated__ = '2018-11-20'
|
||||
|
||||
# a few constants
|
||||
OVERVIEW = 'overview'
|
||||
@@ -256,6 +254,10 @@ class BaseModelHandler(Handler):
|
||||
message = _('Invalid Request') if message is None else message
|
||||
raise RequestError('{} {}: {}'.format(message, self.__class__, self._args))
|
||||
|
||||
def invalidResponseException(self, message=None):
|
||||
message = 'Invalid response' if message is None else message
|
||||
raise ResponseError(message)
|
||||
|
||||
def invalidMethodException(self):
|
||||
'''
|
||||
Raises a NotFound exception with translated "Method not found" string to current locale
|
||||
@@ -730,8 +732,28 @@ class ModelHandler(BaseModelHandler):
|
||||
|
||||
return method()
|
||||
|
||||
def getItems(self, overview=True, *args, **kwargs):
|
||||
for item in self.model.objects.filter(*args, **kwargs):
|
||||
def getItems(self, *args, **kwargs):
|
||||
if 'overview' in kwargs:
|
||||
overview = kwargs['overview']
|
||||
del kwargs['overview']
|
||||
else:
|
||||
overview = False
|
||||
|
||||
if 'prefetch' in kwargs:
|
||||
prefetch = kwargs['prefetch']
|
||||
logger.debug('Prefetching %s', prefetch)
|
||||
del kwargs['prefetch']
|
||||
else:
|
||||
prefetch = []
|
||||
|
||||
if 'query' in kwargs:
|
||||
query = kwargs['query']
|
||||
del kwargs['query']
|
||||
else:
|
||||
logger.debug('Args: %s, kwargs: %s', args, kwargs)
|
||||
query = self.model.objects.filter(*args, **kwargs).prefetch_related(*prefetch)
|
||||
|
||||
for item in query:
|
||||
try:
|
||||
if permissions.checkPermissions(self._user, item, permissions.PERMISSION_READ) is False:
|
||||
continue
|
||||
|
@@ -196,7 +196,7 @@ class Module(UserInterface, Environmentable, Serializable):
|
||||
'''
|
||||
return [True, _("No connection checking method is implemented.")]
|
||||
|
||||
def __init__(self, environment, values=None):
|
||||
def __init__(self, environment, values=None, uuid=None):
|
||||
'''
|
||||
Do not forget to invoke this in your derived class using
|
||||
"super(self.__class__, self).__init__(environment, values)".
|
||||
@@ -223,6 +223,7 @@ class Module(UserInterface, Environmentable, Serializable):
|
||||
UserInterface.__init__(self, values)
|
||||
Environmentable.__init__(self, environment)
|
||||
Serializable.__init__(self)
|
||||
self._uuid = uuid if uuid is not None else ''
|
||||
|
||||
def __str__(self):
|
||||
return "Base Module"
|
||||
@@ -265,6 +266,9 @@ class Module(UserInterface, Environmentable, Serializable):
|
||||
'''
|
||||
return _("No check method provided.")
|
||||
|
||||
def getUuid(self):
|
||||
return self._uuid
|
||||
|
||||
def destroy(self):
|
||||
'''
|
||||
Invoked before deleting an module from database.
|
||||
|
@@ -64,6 +64,7 @@ class Environment(object):
|
||||
'''
|
||||
Method to acces the cache of the environment.
|
||||
@return: a referente to a Cache instance
|
||||
:rtype uds.core.util.Cache.Cache
|
||||
'''
|
||||
return self._cache
|
||||
|
||||
@@ -72,6 +73,7 @@ class Environment(object):
|
||||
'''
|
||||
Method to acces the cache of the environment.
|
||||
@return: a referente to an Storage Instance
|
||||
:rtype uds.core.util.Storage.Storage
|
||||
'''
|
||||
return self._storage
|
||||
|
||||
@@ -179,7 +181,6 @@ class Environmentable(object):
|
||||
'''
|
||||
self._env = environment
|
||||
|
||||
|
||||
@property
|
||||
def cache(self):
|
||||
'''
|
||||
@@ -187,6 +188,7 @@ class Environmentable(object):
|
||||
|
||||
Returns:
|
||||
Cache for the object
|
||||
:rtype uds.core.util.Cache.Cache
|
||||
'''
|
||||
return self._env.cache
|
||||
|
||||
@@ -197,6 +199,8 @@ class Environmentable(object):
|
||||
|
||||
Returns:
|
||||
Storage for the object
|
||||
|
||||
:rtype uds.core.util.Storage.Storage
|
||||
'''
|
||||
return self._env.storage
|
||||
|
||||
|
@@ -39,5 +39,5 @@ from uds.core.Environment import Environmentable
|
||||
from uds.core.Serializable import Serializable
|
||||
from uds.core.BaseModule import Module
|
||||
|
||||
VERSION = '2.2.0-DEVEL'
|
||||
VERSION_STAMP = '20170901-DEVEL'
|
||||
VERSION = '2.2.1-DEVEL'
|
||||
VERSION_STAMP = '20180901-DEVEL'
|
||||
|
@@ -169,7 +169,7 @@ class GroupsManager(object):
|
||||
Checks if this group name is marked as valid inside this groups manager.
|
||||
Returns True if group name is marked as valid, False if it isn't.
|
||||
'''
|
||||
for grp in self.checkAllGroup(groupName):
|
||||
for grp in self.checkAllGroups(groupName):
|
||||
if grp['valid']:
|
||||
return True
|
||||
return False
|
||||
|
@@ -46,14 +46,14 @@ from uds.core.util import log
|
||||
from uds.core.util.decorators import deprecated
|
||||
from uds.core import auths
|
||||
from uds.core.util.stats import events
|
||||
from uds.core.managers.CryptoManager import CryptoManager
|
||||
from uds.core.managers import cryptoManager
|
||||
from uds.core.util.State import State
|
||||
from uds.models import User
|
||||
|
||||
import logging
|
||||
import six
|
||||
|
||||
__updated__ = '2017-11-22'
|
||||
__updated__ = '2019-05-10'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
authLogger = logging.getLogger('authLog')
|
||||
@@ -70,7 +70,7 @@ def getUDSCookie(request, response=None, force=False):
|
||||
if 'uds' not in request.COOKIES:
|
||||
import random
|
||||
import string
|
||||
cookie = ''.join(random.choice(string.letters + string.digits) for _ in range(32)) # @UndefinedVariable
|
||||
cookie = ''.join(random.SystemRandom().choice(string.letters + string.digits) for _ in range(32)) # @UndefinedVariable
|
||||
if response is not None:
|
||||
response.set_cookie('uds', cookie)
|
||||
request.COOKIES['uds'] = cookie
|
||||
@@ -165,7 +165,9 @@ def __registerUser(authenticator, authInstance, username):
|
||||
|
||||
request = getRequest()
|
||||
|
||||
usr = authenticator.getOrCreateUser(username, authInstance.getRealName(username))
|
||||
usr = authenticator.getOrCreateUser(username, username)
|
||||
usr.real_name = authInstance.getRealName(username)
|
||||
usr.save()
|
||||
if usr is not None and State.isActive(usr.state):
|
||||
# Now we update database groups for this user
|
||||
usr.getManager().recreateGroups(usr)
|
||||
@@ -207,6 +209,7 @@ def authenticate(username, password, authenticator, useInternalAuthenticate=Fals
|
||||
|
||||
# If do not have any valid group
|
||||
if gm.hasValidGroups() is False:
|
||||
logger.info('User {} has been authenticated, but he does not belongs to any UDS know group')
|
||||
return None
|
||||
|
||||
return __registerUser(authenticator, authInstance, username)
|
||||
@@ -285,7 +288,7 @@ def webLogin(request, response, user, password):
|
||||
user.updateLastAccess()
|
||||
request.session.clear()
|
||||
request.session[USER_KEY] = user.id
|
||||
request.session[PASS_KEY] = CryptoManager.manager().xor(password, cookie) # Stores "bytes"
|
||||
request.session[PASS_KEY] = cryptoManager().symCrypt(password, cookie) # Stores "bytes"
|
||||
# Ensures that this user will have access through REST api if logged in through web interface
|
||||
REST.Handler.storeSessionAuthdata(request.session, manager_id, user.name, password, get_language(), user.is_admin, user.staff_member, cookie)
|
||||
return True
|
||||
@@ -299,7 +302,7 @@ def webPassword(request):
|
||||
@param request: DJango Request
|
||||
@return: Unscrambled user password
|
||||
'''
|
||||
return CryptoManager.manager().xor(request.session.get(PASS_KEY, ''), getUDSCookie(request)).decode('utf-8') # recover as original unicode string
|
||||
return cryptoManager().symDecrpyt(request.session.get(PASS_KEY, ''), getUDSCookie(request)) # recover as original unicode string
|
||||
|
||||
|
||||
def webLogout(request, exit_url=None):
|
||||
|
@@ -45,7 +45,7 @@ import threading
|
||||
import time
|
||||
import logging
|
||||
|
||||
__updated__ = '2017-11-15'
|
||||
__updated__ = '2018-03-02'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -107,6 +107,8 @@ class DelayedTaskRunner(object):
|
||||
try:
|
||||
with transaction.atomic(): # Encloses
|
||||
task = dbDelayedTask.objects.select_for_update().filter(filt).order_by('execution_time')[0] # @UndefinedVariable
|
||||
if task.insert_date > now + timedelta(seconds=30):
|
||||
logger.warn('EXecuted {} due to insert_date being in the future!'.format(task.type))
|
||||
taskInstanceDump = encoders.decode(task.instance, 'base64')
|
||||
task.delete()
|
||||
taskInstance = loads(taskInstanceDump)
|
||||
|
@@ -43,7 +43,7 @@ import threading
|
||||
import time
|
||||
import logging
|
||||
|
||||
__updated__ = '2017-04-17'
|
||||
__updated__ = '2018-03-02'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -55,6 +55,7 @@ class JobThread(threading.Thread):
|
||||
Ensures that the job is executed in a controlled way (any exception will be catch & processed)
|
||||
Ensures that the scheduler db entry is released after run
|
||||
'''
|
||||
|
||||
def __init__(self, jobInstance, dbJob):
|
||||
super(JobThread, self).__init__()
|
||||
self._jobInstance = jobInstance
|
||||
@@ -143,6 +144,8 @@ class Scheduler(object):
|
||||
# If next execution is before now or last execution is in the future (clock changed on this server, we take that task as executable)
|
||||
# This params are all set inside fltr (look at __init__)
|
||||
job = dbScheduler.objects.select_for_update().filter(fltr).order_by('next_execution')[0] # @UndefinedVariable
|
||||
if job.last_execution > now:
|
||||
logger.warn('EXecuted {} due to last_execution being in the future!'.format(job.name))
|
||||
job.state = State.RUNNING
|
||||
job.owner_server = self._hostname
|
||||
job.last_execution = now
|
||||
|
@@ -35,12 +35,13 @@ from __future__ import unicode_literals
|
||||
from django.conf import settings
|
||||
from uds.core.util import encoders
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Cipher import AES
|
||||
from OpenSSL import crypto
|
||||
from Crypto.Random import atfork
|
||||
import hashlib
|
||||
import array
|
||||
import uuid
|
||||
import datetime
|
||||
import struct
|
||||
import random
|
||||
import string
|
||||
|
||||
@@ -64,6 +65,23 @@ class CryptoManager(object):
|
||||
self._namespace = uuid.UUID('627a37a5-e8db-431a-b783-73f7d20b4934')
|
||||
self._counter = 0
|
||||
|
||||
@staticmethod
|
||||
def AESKey(key, length):
|
||||
if isinstance(key, six.text_type):
|
||||
key = key.encode('utf8')
|
||||
|
||||
while len(key) < length:
|
||||
key += key # Dup key
|
||||
|
||||
kl = [ord(v) for v in key]
|
||||
pos = 0
|
||||
while len(kl) > length:
|
||||
kl[pos] ^= kl[length]
|
||||
pos = (pos + 1) % length
|
||||
del kl[length]
|
||||
|
||||
return b''.join([chr(v) for v in kl])
|
||||
|
||||
@staticmethod
|
||||
def manager():
|
||||
if CryptoManager.instance is None:
|
||||
@@ -89,6 +107,28 @@ class CryptoManager(object):
|
||||
# logger.error(inspect.stack())
|
||||
return 'decript error'
|
||||
|
||||
def AESCrypt(self, text, key, base64=False):
|
||||
# First, match key to 16 bytes. If key is over 16, create a new one based on key of 16 bytes length
|
||||
cipher = AES.new(CryptoManager.AESKey(key, 16), AES.MODE_CBC, 'udsinitvectoruds')
|
||||
rndStr = self.randomString(cipher.block_size)
|
||||
paddedLength = ((len(text) + 4 + 15) // 16) * 16
|
||||
toEncode = struct.pack('>i', len(text)) + text + rndStr[:paddedLength - len(text) - 4]
|
||||
encoded = cipher.encrypt(toEncode)
|
||||
if hex:
|
||||
return encoders.encode(encoded, 'base64', True)
|
||||
|
||||
return encoded
|
||||
|
||||
def AESDecrypt(self, text, key, base64=False):
|
||||
if base64:
|
||||
text = encoders.decode(text, 'base64')
|
||||
|
||||
cipher = AES.new(CryptoManager.AESKey(key, 16), AES.MODE_CBC, 'udsinitvectoruds')
|
||||
toDecode = cipher.decrypt(text)
|
||||
return toDecode[4:4 + struct.unpack('>i', toDecode[:4])[0]]
|
||||
|
||||
return
|
||||
|
||||
def xor(self, s1, s2):
|
||||
if isinstance(s1, six.text_type):
|
||||
s1 = s1.encode('utf-8')
|
||||
@@ -100,6 +140,12 @@ class CryptoManager(object):
|
||||
# We must return bynary in xor, because result is in fact binary
|
||||
return six.binary_type(array.array(six.binary_type('B'), (s1[i] ^ s2[i] for i in range(len(s1)))).tostring())
|
||||
|
||||
def symCrypt(self, text, key):
|
||||
return self.xor(text, key)
|
||||
|
||||
def symDecrpyt(self, cryptText, key):
|
||||
return self.xor(cryptText, key).decode('utf-8')
|
||||
|
||||
def loadPrivateKey(self, rsaKey):
|
||||
try:
|
||||
pk = RSA.importKey(rsaKey)
|
||||
@@ -132,7 +178,7 @@ class CryptoManager(object):
|
||||
If obj is None, returns an uuid based on current datetime + counter
|
||||
'''
|
||||
if obj is None:
|
||||
obj = six.text_type(datetime.datetime.now()) + six.text_type(self._counter)
|
||||
obj = self.randomString()
|
||||
self._counter += 1
|
||||
|
||||
if isinstance(obj, six.text_type):
|
||||
@@ -140,7 +186,7 @@ class CryptoManager(object):
|
||||
else:
|
||||
obj = six.binary_type(obj)
|
||||
|
||||
return six.text_type(uuid.uuid5(self._namespace, six.binary_type(obj))).lower() # I believe uuid returns a lowercase uuid always, but in case... :)
|
||||
return six.text_type(uuid.uuid5(self._namespace, six.binary_type(obj))).lower() # uuid must return a lowercase uuid always?, just in case... :)
|
||||
|
||||
def randomString(self, length=40):
|
||||
return ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(length))
|
||||
return ''.join(random.SystemRandom().choice(string.ascii_lowercase + string.digits) for _ in range(length))
|
||||
|
@@ -86,6 +86,9 @@ class LogManager(object):
|
||||
from uds.models import getSqlDatetime
|
||||
from uds.models import Log
|
||||
|
||||
# Ensure message fits on space
|
||||
message = message[:255]
|
||||
|
||||
qs = Log.objects.filter(owner_id=owner_id, owner_type=owner_type)
|
||||
# First, ensure we do not have more than requested logs, and we can put one more log item
|
||||
if qs.count() >= GlobalConfig.MAX_LOGS_PER_ELEMENT.getInt():
|
||||
|
@@ -53,6 +53,7 @@ class PublicationOldMachinesCleaner(DelayedTask):
|
||||
'''
|
||||
This delayed task is for removing a pending "removable" publication
|
||||
'''
|
||||
|
||||
def __init__(self, publicationId):
|
||||
super(PublicationOldMachinesCleaner, self).__init__()
|
||||
self._id = publicationId
|
||||
@@ -77,6 +78,7 @@ class PublicationLauncher(DelayedTask):
|
||||
'''
|
||||
This delayed task if for launching a new publication
|
||||
'''
|
||||
|
||||
def __init__(self, publish):
|
||||
super(PublicationLauncher, self).__init__()
|
||||
self._publishId = publish.id
|
||||
@@ -84,6 +86,7 @@ class PublicationLauncher(DelayedTask):
|
||||
def run(self):
|
||||
logger.debug('Publishing')
|
||||
try:
|
||||
now = getSqlDatetime()
|
||||
with transaction.atomic():
|
||||
servicePoolPub = DeployedServicePublication.objects.select_for_update().get(pk=self._publishId)
|
||||
if servicePoolPub.state != State.LAUNCHING: # If not preparing (may has been canceled by user) just return
|
||||
@@ -94,7 +97,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.storeValue('toBeReplacedIn', pickle.dumps(now + datetime.timedelta(hours=GlobalConfig.SESSION_EXPIRE_TIME.getInt(True))))
|
||||
deployedService.save()
|
||||
PublicationFinishChecker.checkAndUpdateState(servicePoolPub, pi, state)
|
||||
except Exception:
|
||||
@@ -108,6 +111,7 @@ class PublicationFinishChecker(DelayedTask):
|
||||
'''
|
||||
This delayed task is responsible of checking if a publication is finished
|
||||
'''
|
||||
|
||||
def __init__(self, servicePoolPub):
|
||||
super(PublicationFinishChecker, self).__init__()
|
||||
self._publishId = servicePoolPub.id
|
||||
@@ -128,11 +132,17 @@ class PublicationFinishChecker(DelayedTask):
|
||||
for old in servicePoolPub.deployed_service.publications.filter(state=State.USABLE):
|
||||
old.state = State.REMOVABLE
|
||||
old.save()
|
||||
pc = PublicationOldMachinesCleaner(old.id)
|
||||
pc.register(GlobalConfig.SESSION_EXPIRE_TIME.getInt(True) * 3600, 'pclean-' + str(old.id), True)
|
||||
|
||||
osm = servicePoolPub.deployed_service.osmanager
|
||||
# If os manager says "machine is persistent", do not tray to delete "previous version" assigned machines
|
||||
doPublicationCleanup = True if osm is None else not osm.getInstance().isPersistent()
|
||||
|
||||
if doPublicationCleanup:
|
||||
pc = PublicationOldMachinesCleaner(old.id)
|
||||
pc.register(GlobalConfig.SESSION_EXPIRE_TIME.getInt(True) * 3600, 'pclean-' + str(old.id), True)
|
||||
servicePoolPub.deployed_service.markOldUserServicesAsRemovables(servicePoolPub)
|
||||
|
||||
servicePoolPub.setState(State.USABLE)
|
||||
servicePoolPub.deployed_service.markOldUserServicesAsRemovables(servicePoolPub)
|
||||
elif State.isRemoving(prevState):
|
||||
servicePoolPub.setState(State.REMOVED)
|
||||
else: # State is canceling
|
||||
|
@@ -71,9 +71,6 @@ class StatsManager(object):
|
||||
# Newer Django versions (at least 1.7) does this deletions as it must (executes a DELETE FROM ... WHERE...)
|
||||
model.objects.filter(stamp__lt=minTime).delete()
|
||||
|
||||
# Optimize mysql tables after deletions
|
||||
optimizeTable(model._meta.db_table)
|
||||
|
||||
# Counter stats
|
||||
def addCounter(self, owner_type, owner_id, counterType, counterValue, stamp=None):
|
||||
'''
|
||||
|
@@ -51,7 +51,7 @@ import requests
|
||||
import json
|
||||
import logging
|
||||
|
||||
__updated__ = '2017-11-17'
|
||||
__updated__ = '2019-05-08'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
traceLogger = logging.getLogger('traceLog')
|
||||
@@ -142,6 +142,12 @@ class UserServiceManager(object):
|
||||
'''
|
||||
Creates a new assigned deployed service for the publication and user indicated
|
||||
'''
|
||||
# First, honor maxPreparingServices
|
||||
if self.canInitiateServiceFromDeployedService(ds) is False:
|
||||
# Cannot create new
|
||||
logger.warn('Too many preparing services. Creation of assigned service denied by max preparing services parameter. (login storm with insufficient cache?).')
|
||||
raise ServiceNotReadyError()
|
||||
|
||||
if ds.service.getType().publicationType is not None:
|
||||
dsp = ds.activePublication()
|
||||
logger.debug('Creating a new assigned element for user {0} por publication {1}'.format(user, dsp))
|
||||
@@ -182,6 +188,7 @@ class UserServiceManager(object):
|
||||
ci = cache.getInstance()
|
||||
state = ci.moveToCache(cacheLevel)
|
||||
cache.cache_level = cacheLevel
|
||||
cache.save(update_fields=['cache_level'])
|
||||
logger.debug('Service State: {0} {1} {2}'.format(State.toString(state), State.toString(cache.state), State.toString(cache.os_state)))
|
||||
if State.isRuning(state) and cache.isUsable():
|
||||
cache.setState(State.PREPARING)
|
||||
@@ -261,23 +268,31 @@ class UserServiceManager(object):
|
||||
# Now try to locate 1 from cache already "ready" (must be usable and at level 1)
|
||||
with transaction.atomic():
|
||||
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE, os_state=State.USABLE)[:1]
|
||||
if len(cache) == 0:
|
||||
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE)[:1]
|
||||
if len(cache) > 0:
|
||||
if len(cache) != 0:
|
||||
cache = cache[0]
|
||||
cache.assignToUser(user)
|
||||
cache.save() # Store assigned ASAP, we do not know how long assignToUser method of instance will take
|
||||
# Ensure element is reserved correctly on DB
|
||||
if ds.cachedUserServices().select_for_update().filter(user=None, uuid=cache.uuid).update(user=user, cache_level=0) != 1:
|
||||
cache = None
|
||||
else:
|
||||
cache = None
|
||||
|
||||
if cache == None:
|
||||
with transaction.atomic():
|
||||
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE)[:1]
|
||||
if len(cache) > 0:
|
||||
cache = cache[0]
|
||||
if ds.cachedUserServices().select_for_update().filter(user=None, uuid=cache.uuid).update(user=user, cache_level=0) != 1:
|
||||
cache = None
|
||||
else:
|
||||
cache = None
|
||||
|
||||
if cache:
|
||||
cache.assignToUser(user) # Store assigned ASAP, we do not know how long assignToUser method of instance will take
|
||||
|
||||
# Out of atomic transaction
|
||||
if cache is not None:
|
||||
logger.debug('Found a cached-ready service from {0} for user {1}, item {2}'.format(ds, user, cache))
|
||||
events.addEvent(ds, events.ET_CACHE_HIT, fld1=ds.cachedUserServices().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE).count())
|
||||
ci = cache.getInstance() # User Deployment instance
|
||||
ci.assignToUser(user)
|
||||
cache.updateData(ci)
|
||||
cache.save()
|
||||
return cache
|
||||
|
||||
# Cache missed
|
||||
@@ -287,8 +302,10 @@ class UserServiceManager(object):
|
||||
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.PREPARING)[:1]
|
||||
if len(cache) > 0:
|
||||
cache = cache[0]
|
||||
cache.assignToUser(user)
|
||||
cache.save()
|
||||
if ds.cachedUserServices().select_for_update().filter(user=None, uuid=cache.uuid).update(user=user, cache_level=0) != 1:
|
||||
cache = None
|
||||
else:
|
||||
cache.assignToUser(user) # Store assigned ASAP, we do not know how long assignToUser method of instance will take
|
||||
else:
|
||||
cache = None
|
||||
|
||||
@@ -296,10 +313,6 @@ class UserServiceManager(object):
|
||||
if cache is not None:
|
||||
logger.debug('Found a cached-preparing service from {0} for user {1}, item {2}'.format(ds, user, cache))
|
||||
events.addEvent(ds, events.ET_CACHE_MISS, fld1=ds.cachedUserServices().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.PREPARING).count())
|
||||
ci = cache.getInstance() # User Deployment instance
|
||||
ci.assignToUser(user)
|
||||
cache.updateData(ci)
|
||||
cache.save()
|
||||
return cache
|
||||
|
||||
# Can't assign directly from L2 cache... so we check if we can create e new service in the limits requested
|
||||
@@ -309,6 +322,7 @@ class UserServiceManager(object):
|
||||
inAssigned = ds.assignedUserServices().filter(UserServiceManager.getStateFilter()).count()
|
||||
# totalL1Assigned = inCacheL1 + inAssigned
|
||||
if inAssigned >= ds.max_srvs: # cacheUpdater will drop necesary L1 machines, so it's not neccesary to check against inCacheL1
|
||||
log.doLog(ds, log.WARN, 'Max number of services reached: {}'.format(ds.max_srvs), log.INTERNAL)
|
||||
raise MaxServicesReachedError()
|
||||
# Can create new service, create it
|
||||
events.addEvent(ds, events.ET_CACHE_MISS, fld1=0)
|
||||
@@ -360,6 +374,20 @@ class UserServiceManager(object):
|
||||
UserServiceOpChecker.makeUnique(uService, ui, state)
|
||||
return False
|
||||
|
||||
def reset(self, uService):
|
||||
UserService.objects.update()
|
||||
uService = UserService.objects.get(id=uService.id)
|
||||
if uService.deployed_service.service.getType().canReset is False:
|
||||
return
|
||||
|
||||
logger.debug('Reseting'.format(uService))
|
||||
|
||||
ui = uService.getInstance()
|
||||
try:
|
||||
ui.reset()
|
||||
except Exception:
|
||||
logger.exception('Reseting service')
|
||||
|
||||
def notifyPreconnect(self, uService, userName, protocol):
|
||||
url = uService.getCommsUrl()
|
||||
if url is None:
|
||||
@@ -455,12 +483,17 @@ class UserServiceManager(object):
|
||||
This method is used by UserService when a request for setInUse(False) is made
|
||||
This checks that the service can continue existing or not
|
||||
'''
|
||||
# uService = UserService.objects.get(id=uService.id)
|
||||
if uService.publication is None:
|
||||
return
|
||||
if uService.publication.id != uService.deployed_service.activePublication().id:
|
||||
logger.debug('Old revision of user service, marking as removable: {0}'.format(uService))
|
||||
uService.remove()
|
||||
osm = uService.deployed_service.osmanager
|
||||
# If os manager says "machine is persistent", do not tray to delete "previous version" assigned machines
|
||||
doPublicationCleanup = True if osm is None else not osm.getInstance().isPersistent()
|
||||
|
||||
if doPublicationCleanup:
|
||||
if uService.publication is None:
|
||||
return
|
||||
|
||||
if uService.publication.id != uService.deployed_service.activePublication().id:
|
||||
logger.debug('Old revision of user service, marking as removable: {0}'.format(uService))
|
||||
uService.remove()
|
||||
|
||||
def notifyReadyFromOsManager(self, uService, data):
|
||||
try:
|
||||
@@ -471,10 +504,10 @@ class UserServiceManager(object):
|
||||
uService.updateData(ui)
|
||||
if state == State.FINISHED:
|
||||
logger.debug('Service is now ready')
|
||||
uService.save()
|
||||
elif uService.state in (State.USABLE, State.PREPARING): # We don't want to get active deleting or deleted machines...
|
||||
uService.setState(State.PREPARING)
|
||||
UserServiceOpChecker.makeUnique(uService, ui, state)
|
||||
uService.save(update_fields=['os_state'])
|
||||
except Exception as e:
|
||||
logger.exception('Unhandled exception on notyfyReady: {}'.format(e))
|
||||
uService.setState(State.ERROR)
|
||||
@@ -488,7 +521,7 @@ class UserServiceManager(object):
|
||||
|
||||
if kind == 'A': # This is an assigned service
|
||||
logger.debug('Getting A service {}'.format(idService))
|
||||
userService = UserService.objects.get(uuid=idService)
|
||||
userService = UserService.objects.get(uuid=idService, user=user)
|
||||
userService.deployed_service.validateUser(user)
|
||||
else:
|
||||
ds = ServicePool.objects.get(uuid=idService)
|
||||
@@ -511,6 +544,9 @@ class UserServiceManager(object):
|
||||
'''
|
||||
userService = self.locateUserService(user, idService, create=True)
|
||||
|
||||
# Early log of "access try" so we can imagine what is going on
|
||||
userService.setConnectionSource(srcIp, 'unknown')
|
||||
|
||||
if userService.isInMaintenance() is True:
|
||||
raise ServiceInMaintenanceMode()
|
||||
|
||||
@@ -534,7 +570,9 @@ class UserServiceManager(object):
|
||||
|
||||
# If transport is not available for the request IP...
|
||||
if trans.validForIp(srcIp) is False:
|
||||
raise InvalidServiceException()
|
||||
msg = 'The requested transport {} is not valid for {}'.format(trans.name, srcIp)
|
||||
logger.error(msg)
|
||||
raise InvalidServiceException(msg)
|
||||
|
||||
if user is not None:
|
||||
userName = user.name
|
||||
@@ -564,7 +602,7 @@ class UserServiceManager(object):
|
||||
serviceNotReadyCode = 0x0003
|
||||
itrans = trans.getInstance()
|
||||
if itrans.isAvailableFor(userService, ip):
|
||||
userService.setConnectionSource(srcIp, 'unknown')
|
||||
# userService.setConnectionSource(srcIp, 'unknown')
|
||||
log.doLog(userService, log.INFO, "User service ready", log.WEB)
|
||||
self.notifyPreconnect(userService, itrans.processedUser(userService, user), itrans.protocol)
|
||||
traceLogger.info('READY on service "{}" for user "{}" with transport "{}" (ip:{})'.format(userService.name, userName, trans.name, ip))
|
||||
|
@@ -34,34 +34,40 @@ UDS managers (downloads, users preferences, publications, ...)
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
__updated__ = '2015-04-30'
|
||||
__updated__ = '2018-06-25'
|
||||
|
||||
|
||||
def cryptoManager():
|
||||
':rtype uds.core.managers.CryptoManager.CryptoManager'
|
||||
from CryptoManager import CryptoManager
|
||||
return CryptoManager.manager()
|
||||
|
||||
|
||||
def taskManager():
|
||||
':rtype uds.core.managers.TaskManager.TaskManager'
|
||||
from TaskManager import TaskManager
|
||||
return TaskManager
|
||||
|
||||
|
||||
def downloadsManager():
|
||||
':rtype uds.core.managers.DownloadsManager.DownloadsManager'
|
||||
from DownloadsManager import DownloadsManager
|
||||
return DownloadsManager.manager()
|
||||
|
||||
|
||||
def logManager():
|
||||
':rtype uds.core.managers.LogManager.LogManager'
|
||||
from LogManager import LogManager
|
||||
return LogManager.manager()
|
||||
|
||||
|
||||
def statsManager():
|
||||
':rtype uds.core.managers.StatsManager.StatsManager'
|
||||
from StatsManager import StatsManager
|
||||
return StatsManager.manager()
|
||||
|
||||
|
||||
def userServiceManager():
|
||||
from UserServiceManager import UserServiceManager
|
||||
':rtype uds.core.managers.UserServiceManager.UserServiceManager'
|
||||
return UserServiceManager.manager()
|
||||
|
@@ -40,15 +40,17 @@ from uds.models import UserService
|
||||
|
||||
import logging
|
||||
|
||||
__updated__ = '2017-06-15'
|
||||
__updated__ = '2019-05-21'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
USERSERVICE_TAG = 'cm-'
|
||||
|
||||
|
||||
# State updaters
|
||||
# This will be executed on current service state for checking transitions to new state, task states, etc..
|
||||
class StateUpdater(object):
|
||||
|
||||
def __init__(self, userService, userServiceInstance=None):
|
||||
self.userService = userService
|
||||
self.userServiceInstance = userServiceInstance if userServiceInstance is not None else userService.getInstance()
|
||||
@@ -61,18 +63,18 @@ class StateUpdater(object):
|
||||
|
||||
def save(self, newState=None):
|
||||
if newState is not None:
|
||||
self.userService.setState(newState)
|
||||
self.userService.updateData(self.userServiceInstance)
|
||||
self.userService.save()
|
||||
self.userService.setState(newState) # This saves state & state_date
|
||||
self.userService.updateData(self.userServiceInstance) # This saves data
|
||||
self.userService.save(update_fields=['state', 'os_state', 'state_date'])
|
||||
|
||||
def checkLater(self):
|
||||
UserServiceOpChecker.checkLater(self.userService, self.userServiceInstance)
|
||||
|
||||
def run(self, state):
|
||||
executor = {
|
||||
State.RUNNING: self.running,
|
||||
State.ERROR: self.error,
|
||||
State.FINISHED: self.finish
|
||||
State.RUNNING: self.running,
|
||||
State.ERROR: self.error,
|
||||
State.FINISHED: self.finish
|
||||
}.get(state, self.error)
|
||||
|
||||
logger.debug('Running updater with state {} and executor {}'.format(State.toString(state), executor))
|
||||
@@ -136,7 +138,9 @@ class UpdateFromPreparing(StateUpdater):
|
||||
|
||||
self.save(state)
|
||||
|
||||
|
||||
class UpdateFromRemoving(StateUpdater):
|
||||
|
||||
def finish(self):
|
||||
osManager = self.userServiceInstance.osmanager()
|
||||
if osManager is not None:
|
||||
@@ -144,7 +148,9 @@ class UpdateFromRemoving(StateUpdater):
|
||||
|
||||
self.save(State.REMOVED)
|
||||
|
||||
|
||||
class UpdateFromCanceling(StateUpdater):
|
||||
|
||||
def finish(self):
|
||||
osManager = self.userServiceInstance.osmanager()
|
||||
if osManager is not None:
|
||||
@@ -152,7 +158,9 @@ class UpdateFromCanceling(StateUpdater):
|
||||
|
||||
self.save(State.CANCELED)
|
||||
|
||||
|
||||
class UpdateFromOther(StateUpdater):
|
||||
|
||||
def finish(self):
|
||||
self.setError('Unknown running transition from {}'.format(State.toString(self.userService.state)))
|
||||
|
||||
@@ -164,6 +172,7 @@ class UserServiceOpChecker(DelayedTask):
|
||||
'''
|
||||
This is the delayed task responsible of executing the service tasks and the service state transitions
|
||||
'''
|
||||
|
||||
def __init__(self, service):
|
||||
super(UserServiceOpChecker, self).__init__()
|
||||
self._svrId = service.id
|
||||
@@ -187,6 +196,7 @@ class UserServiceOpChecker(DelayedTask):
|
||||
# Fills up basic data
|
||||
userService.unique_id = userServiceInstance.getUniqueId() # Updates uniqueId
|
||||
userService.friendly_name = userServiceInstance.getName() # And name, both methods can modify serviceInstance, so we save it later
|
||||
userService.save(update_fields=['unique_id', 'friendly_name'])
|
||||
|
||||
updater = {
|
||||
State.PREPARING: UpdateFromPreparing,
|
||||
@@ -202,7 +212,7 @@ class UserServiceOpChecker(DelayedTask):
|
||||
logger.exception('Checking service state')
|
||||
log.doLog(userService, log.ERROR, 'Exception: {0}'.format(e), log.INTERNAL)
|
||||
userService.setState(State.ERROR)
|
||||
userService.save()
|
||||
userService.save(update_fields=['data'])
|
||||
|
||||
@staticmethod
|
||||
def checkLater(userService, ci):
|
||||
@@ -238,6 +248,6 @@ class UserServiceOpChecker(DelayedTask):
|
||||
log.doLog(uService, log.ERROR, 'Exception: {0}'.format(e), log.INTERNAL)
|
||||
try:
|
||||
uService.setState(State.ERROR)
|
||||
uService.save()
|
||||
uService.save(update_fields=['data', 'state', 'state_date'])
|
||||
except Exception:
|
||||
logger.error('Can\'t update state of uService object')
|
||||
|
@@ -42,7 +42,7 @@ from uds.core import Module
|
||||
|
||||
import six
|
||||
|
||||
__updated__ = '2017-10-02'
|
||||
__updated__ = '2019-02-24'
|
||||
|
||||
STORAGE_KEY = 'osmk'
|
||||
|
||||
@@ -177,11 +177,9 @@ class OSManager(Module):
|
||||
def logKnownIp(self, userService, ip):
|
||||
userService.logIP(ip)
|
||||
|
||||
|
||||
def toReady(self, userService):
|
||||
userService.setProperty('loginsCounter', '0')
|
||||
|
||||
|
||||
def loggedIn(self, userService, userName=None, save=True):
|
||||
'''
|
||||
This method:
|
||||
@@ -211,13 +209,13 @@ class OSManager(Module):
|
||||
|
||||
log.doLog(userService, log.INFO, "User {0} has logged in".format(userName), log.OSMANAGER)
|
||||
|
||||
log.useLog('login', uniqueId, serviceIp, userName, knownUserIP, fullUserName)
|
||||
log.useLog('login', uniqueId, serviceIp, userName, knownUserIP, fullUserName, userService.friendly_name, userService.deployed_service.name)
|
||||
|
||||
counter = int(userService.getProperty('loginsCounter', '0')) + 1
|
||||
userService.setProperty('loginsCounter', six.text_type(counter))
|
||||
|
||||
if save:
|
||||
userService.save()
|
||||
userService.save(update_fields=['in_use', 'in_use_date', 'data'])
|
||||
|
||||
def loggedOut(self, userService, userName=None, save=True):
|
||||
'''
|
||||
@@ -257,10 +255,17 @@ class OSManager(Module):
|
||||
|
||||
log.doLog(userService, log.INFO, "User {0} has logged out".format(userName), log.OSMANAGER)
|
||||
|
||||
log.useLog('logout', uniqueId, serviceIp, userName, knownUserIP, fullUserName)
|
||||
log.useLog('logout', uniqueId, serviceIp, userName, knownUserIP, fullUserName, userService.friendly_name, userService.deployed_service.name)
|
||||
|
||||
if save:
|
||||
userService.save()
|
||||
userService.save(update_fields=['in_use', 'in_use_date', 'data'])
|
||||
|
||||
def isPersistent(self):
|
||||
'''
|
||||
When a publication if finished, old assigned machines will be removed if this value is True.
|
||||
Defaults to False
|
||||
'''
|
||||
return False
|
||||
|
||||
def __str__(self):
|
||||
return "Base OS Manager"
|
||||
|
@@ -36,7 +36,7 @@ from uds.core import Environmentable
|
||||
from uds.core import Serializable
|
||||
from uds.core.util.State import State
|
||||
|
||||
__updated__ = '2017-09-29'
|
||||
__updated__ = '2018-06-11'
|
||||
|
||||
|
||||
class UserDeployment(Environmentable, Serializable):
|
||||
@@ -138,6 +138,10 @@ class UserDeployment(Environmentable, Serializable):
|
||||
self._publication = kwargs.get('publication', None)
|
||||
self._osmanager = kwargs.get('osmanager', None)
|
||||
self._dbService = kwargs.get('dbservice', None)
|
||||
self._uuid = kwargs.get('uuid', '')
|
||||
# If it has dbService
|
||||
if self._dbService:
|
||||
self._uuid = self._dbService.uuid
|
||||
|
||||
self.initialize()
|
||||
|
||||
@@ -196,6 +200,9 @@ class UserDeployment(Environmentable, Serializable):
|
||||
'''
|
||||
return self._osmanager
|
||||
|
||||
def getUuid(self):
|
||||
return self._uuid
|
||||
|
||||
def dbservice(self):
|
||||
'''
|
||||
Utility method to access database object for the object this represents.
|
||||
@@ -574,6 +581,13 @@ class UserDeployment(Environmentable, Serializable):
|
||||
'''
|
||||
raise Exception('cancel method for class {0} not provided!'.format(self.__class__.__name__))
|
||||
|
||||
def reset(self):
|
||||
'''
|
||||
This method is invoked for "reset" an user service
|
||||
This method is not intended to be a task right now, it's more like the
|
||||
'''
|
||||
raise Exception('reset method for class {0} not provided!'.format(self.__class__.__name__))
|
||||
|
||||
def __str__(self):
|
||||
'''
|
||||
Mainly used for debugging purposses
|
||||
|
@@ -35,7 +35,7 @@ from __future__ import unicode_literals
|
||||
from uds.core import Environmentable
|
||||
from uds.core import Serializable
|
||||
|
||||
__updated__ = '2016-02-26'
|
||||
__updated__ = '2018-06-07'
|
||||
|
||||
|
||||
class Publication(Environmentable, Serializable):
|
||||
@@ -90,6 +90,7 @@ class Publication(Environmentable, Serializable):
|
||||
self._service = kwargs['service'] # Raises an exception if service is not included
|
||||
self._revision = kwargs.get('revision', -1)
|
||||
self._dsName = kwargs.get('dsName', 'Unknown')
|
||||
self._uuid = kwargs.get('uuid', '')
|
||||
|
||||
self.initialize()
|
||||
|
||||
@@ -141,6 +142,11 @@ class Publication(Environmentable, Serializable):
|
||||
'''
|
||||
return self._dsName
|
||||
|
||||
def getUuid(self):
|
||||
"""
|
||||
"""
|
||||
return self._uuid
|
||||
|
||||
def publish(self):
|
||||
'''
|
||||
This method is invoked whenever the administrator requests a new publication.
|
||||
|
@@ -37,7 +37,7 @@ from uds.core import Module
|
||||
from uds.core.transports import protocols
|
||||
from . import types
|
||||
|
||||
__updated__ = '2016-03-09'
|
||||
__updated__ = '2018-06-07'
|
||||
|
||||
|
||||
class Service(Module):
|
||||
@@ -167,21 +167,21 @@ class Service(Module):
|
||||
# : Default behavior is False (and most common), but some services may need to respawn a new "copy" on every launch
|
||||
spawnsNew = False
|
||||
|
||||
# : If the service allows "reset", here we will announce it
|
||||
# : Defaults to False
|
||||
canReset = False
|
||||
|
||||
# : 'kind' of services that this service provides:
|
||||
# : For example, VDI, VAPP, ...
|
||||
servicesTypeProvided = types.ALL
|
||||
|
||||
# : If the service can provide any other option on release appart of "delete" & "keep assigned"
|
||||
# : Defaults to None (no any other options are provided)
|
||||
actionsOnRelease = None
|
||||
|
||||
def __init__(self, environment, parent, values=None):
|
||||
def __init__(self, environment, parent, values=None, uuid=None):
|
||||
'''
|
||||
Do not forget to invoke this in your derived class using "super(self.__class__, self).__init__(environment, parent, values)".
|
||||
We want to use the env, parent 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
|
||||
'''
|
||||
super(Service, self).__init__(environment, values)
|
||||
super(Service, self).__init__(environment, values, uuid=uuid)
|
||||
self._provider = parent
|
||||
self.initialize(values)
|
||||
|
||||
|
@@ -39,7 +39,7 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__updated__ = '2016-04-25'
|
||||
__updated__ = '2018-06-07'
|
||||
|
||||
|
||||
class ServiceProvider(Module):
|
||||
@@ -120,7 +120,6 @@ class ServiceProvider(Module):
|
||||
# : Note: this variable can be either a fixed value (integer, string) or a Gui text field (with a .value)
|
||||
ignoreLimits = None
|
||||
|
||||
|
||||
@classmethod
|
||||
def getServicesTypes(cls):
|
||||
'''
|
||||
@@ -145,7 +144,7 @@ class ServiceProvider(Module):
|
||||
break
|
||||
return res
|
||||
|
||||
def __init__(self, environment, values=None):
|
||||
def __init__(self, environment, values=None, uuid=None):
|
||||
'''
|
||||
Do not forget to invoke this in your derived class using "super(self.__class__, self).__init__(environment, values)"
|
||||
if you override this method. Better is to provide an "__initialize__" method, that will be invoked
|
||||
@@ -153,7 +152,7 @@ class ServiceProvider(Module):
|
||||
Values parameter is provided (are not None) when creating or modifying the service provider, so params check should ocur here and, if not
|
||||
valid, raise an "ValidationException" message
|
||||
'''
|
||||
super(ServiceProvider, self).__init__(environment, values)
|
||||
super(ServiceProvider, self).__init__(environment, values, uuid=uuid)
|
||||
self.initialize(values)
|
||||
|
||||
def initialize(self, values):
|
||||
|
@@ -74,6 +74,10 @@ class Cache(object):
|
||||
Cache.misses += 1
|
||||
logger.debug('key not found: {}'.format(skey))
|
||||
return defValue
|
||||
except Exception as e:
|
||||
Cache.misses += 1
|
||||
logger.debug('Cache inaccesible: {}:{}'.format(skey, e))
|
||||
return defValue
|
||||
|
||||
def remove(self, skey):
|
||||
'''
|
||||
@@ -107,7 +111,7 @@ class Cache(object):
|
||||
c.owner = self._owner
|
||||
c.key = key
|
||||
c.value = value
|
||||
c.created = datetime.now()
|
||||
c.created = now
|
||||
c.validity = validity
|
||||
c.save()
|
||||
|
||||
|
@@ -50,6 +50,7 @@ getLater = []
|
||||
# For custom params (for choices mainly)
|
||||
configParams = {}
|
||||
|
||||
|
||||
class Config(object):
|
||||
'''
|
||||
Keeps persistence configuration data
|
||||
@@ -61,8 +62,11 @@ class Config(object):
|
||||
NUMERIC_FIELD = 2
|
||||
BOOLEAN_FIELD = 3
|
||||
CHOICE_FIELD = 4 # Choice fields must set its parameters on global "configParams" (better by calling ".setParams" method)
|
||||
READ_FIELD = 5 # Only can viewed, but not changed (can be changed througn API, it's just read only to avoid "mistakes")
|
||||
HIDDEN_FIELD = 6 # Not visible on "admin" config edition
|
||||
|
||||
class _Value(object):
|
||||
|
||||
def __init__(self, section, key, default='', crypt=False, longText=False, **kwargs):
|
||||
logger.debug('Var: {} {} KWARGS: {}'.format(section, key, kwargs))
|
||||
self._type = kwargs.get('type', -1)
|
||||
@@ -169,6 +173,7 @@ class Config(object):
|
||||
logger.info("Could not save configuration key {0}.{1}".format(self._section.name(), self._key))
|
||||
|
||||
class _Section(object):
|
||||
|
||||
def __init__(self, sectionName):
|
||||
self._sectionName = sectionName
|
||||
|
||||
@@ -324,6 +329,9 @@ class GlobalConfig(object):
|
||||
# This is used so templates can change "styles" from admin interface
|
||||
LOWERCASE_USERNAME = Config.section(SECURITY_SECTION).value('Convert username to lowercase', '1', type=Config.BOOLEAN_FIELD)
|
||||
|
||||
# Global UDS ID (common for all servers on the same cluster)
|
||||
UDS_ID = Config.section(GLOBAL_SECTION).value('UDS ID', CryptoManager.manager().uuid(), type=Config.READ_FIELD)
|
||||
|
||||
initDone = False
|
||||
|
||||
@staticmethod
|
||||
@@ -339,7 +347,6 @@ class GlobalConfig(object):
|
||||
|
||||
GlobalConfig.UDS_THEME.setParams(themes)
|
||||
|
||||
|
||||
@staticmethod
|
||||
def initialize():
|
||||
if GlobalConfig.initDone is False:
|
||||
|
@@ -39,7 +39,9 @@ from django.core.files.storage import Storage
|
||||
from django.conf import settings
|
||||
from six.moves.urllib import parse as urlparse # @UnresolvedImport
|
||||
|
||||
from uds.models.DBFile import DBFile
|
||||
from uds.models import DBFile
|
||||
from uds.models import getSqlDatetime
|
||||
|
||||
from .tools import DictAsObj
|
||||
|
||||
import six
|
||||
@@ -131,9 +133,11 @@ class FileStorage(Storage):
|
||||
try:
|
||||
f = self._dbFileForReadWrite(name)
|
||||
except DBFile.DoesNotExist:
|
||||
f = DBFile.objects.create(owner=self.owner, name=name)
|
||||
now = getSqlDatetime()
|
||||
f = DBFile.objects.create(owner=self.owner, name=name, created=now, modified=now)
|
||||
|
||||
f.data = content.read()
|
||||
f.modified = getSqlDatetime()
|
||||
f.save()
|
||||
|
||||
# Store on cache also
|
||||
|
@@ -40,6 +40,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Knowns OSs
|
||||
Linux = 'Linux'
|
||||
ChromeOS = 'CrOS'
|
||||
WindowsPhone = 'Windows Phone'
|
||||
Windows = 'Windows'
|
||||
Macintosh = 'Mac'
|
||||
@@ -48,9 +49,9 @@ iPad = 'iPad'
|
||||
iPhone = 'iPhone'
|
||||
Unknown = 'Unknown'
|
||||
|
||||
knownOss = (WindowsPhone, Android, Linux, Windows, Macintosh, iPad, iPhone) # Android is linux also, so it is cheched on first place
|
||||
knownOss = (WindowsPhone, Android, Linux, Windows, Macintosh, iPad, iPhone, ChromeOS) # Android is linux also, so it is cheched on first place
|
||||
|
||||
allOss = tuple(knownOss) + tuple(Unknown)
|
||||
allOss = (knownOss) + (Unknown,)
|
||||
desktopOss = (Linux, Windows, Macintosh)
|
||||
mobilesODD = list(set(allOss) - set(desktopOss))
|
||||
|
||||
|
@@ -103,7 +103,7 @@ class Storage(object):
|
||||
|
||||
def getPickleByAttr1(self, attr1):
|
||||
try:
|
||||
return pickle.loads(encoders.decode(dbStorage.objects.get(owner=self._owner, attr1=attr1).data, 'base64')) # @UndefinedVariable
|
||||
return pickle.loads(encoders.decode(dbStorage.objects.filter(owner=self._owner, attr1=attr1)[0].data, 'base64')) # @UndefinedVariable
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
@@ -39,7 +39,7 @@ from functools import wraps
|
||||
|
||||
import logging
|
||||
|
||||
__updated__ = '2016-04-05'
|
||||
__updated__ = '2018-06-25'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -51,7 +51,9 @@ def denyBrowsers(browsers=['ie<9'], errorResponse=lambda request: errors.errorVi
|
||||
Decorator to set protection to access page
|
||||
Look for samples at uds.core.web.views
|
||||
'''
|
||||
|
||||
def wrap(view_func):
|
||||
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
'''
|
||||
@@ -62,7 +64,9 @@ def denyBrowsers(browsers=['ie<9'], errorResponse=lambda request: errors.errorVi
|
||||
return errorResponse(request)
|
||||
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
return _wrapped_view
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
@@ -85,4 +89,58 @@ def deprecated(func):
|
||||
logger.info('No stack info on deprecated function call {0}'.format(func.__name__))
|
||||
|
||||
return func(*args, **kwargs)
|
||||
|
||||
return new_func
|
||||
|
||||
|
||||
# Decorator that allows us a "fast&clean" caching system on service providers
|
||||
#
|
||||
# Decorator for caching
|
||||
# Decorator that tries to get from cache before executing
|
||||
def allowCache(cachePrefix, cacheTimeout, cachingArgs=None, cachingKeyFnc=None):
|
||||
"""Decorator that give us a "quick& clean" caching feature on service providers.
|
||||
|
||||
Note: This decorator is intended ONLY for service providers
|
||||
|
||||
:param cachePrefix: the cache key "prefix" (prepended on generated key from args)
|
||||
:param cacheTimeout: The cache timeout in seconds
|
||||
:param cachingArgs: The caching args. Can be a single integer or a list.
|
||||
First arg (self) is 0, so normally cachingArgs are 1, or [1,2,..]
|
||||
"""
|
||||
if not cachingKeyFnc:
|
||||
cachingKeyFnc = lambda x:''
|
||||
|
||||
def allowCacheDecorator(fnc):
|
||||
|
||||
@wraps(fnc)
|
||||
def wrapper(*args, **kwargs):
|
||||
if cachingArgs is not None:
|
||||
if isinstance(cachingArgs, (list, tuple)):
|
||||
argList = [args[i] if i < len(args) else '' for i in cachingArgs]
|
||||
else:
|
||||
argList = args[cachingArgs] if cachingArgs < len(args) else ''
|
||||
cacheKey = '{}-{}.{}'.format(cachePrefix, cachingKeyFnc(args[0]), argList)
|
||||
else:
|
||||
cacheKey = '{}-{}.gen'.format(cachePrefix, cachingKeyFnc(args[0]))
|
||||
|
||||
data = None
|
||||
if kwargs.get('force', False) is False and args[0].cache is not None:
|
||||
data = args[0].cache.get(cacheKey)
|
||||
|
||||
if kwargs.has_key('force'):
|
||||
# Remove force key
|
||||
del kwargs['force']
|
||||
|
||||
if data is None:
|
||||
data = fnc(*args, **kwargs)
|
||||
try:
|
||||
# Maybe returned data is not serializable. In that case, cache will fail but no harm is done with this
|
||||
args[0].cache.put(cacheKey, data, cacheTimeout)
|
||||
except Exception as e:
|
||||
logger.debug('Data for {} is not serializable, not cached. {} ({})'.format(cacheKey, data, e))
|
||||
return data
|
||||
|
||||
return wrapper
|
||||
|
||||
return allowCacheDecorator
|
||||
|
||||
|
@@ -73,7 +73,7 @@ def logStrFromLevel(level):
|
||||
return __valueLevels.get(level, 'OTHER')
|
||||
|
||||
|
||||
def useLog(type_, serviceUniqueId, serviceIp, username, srcIP=None, srcUser=None):
|
||||
def useLog(type_, serviceUniqueId, serviceIp, username, srcIP=None, srcUser=None, userServiceName=None, poolName=None):
|
||||
'''
|
||||
Logs an "use service" event (logged from actors)
|
||||
:param type_: Type of event (commonly 'login' or 'logout' )
|
||||
@@ -85,7 +85,10 @@ def useLog(type_, serviceUniqueId, serviceIp, username, srcIP=None, srcUser=None
|
||||
'''
|
||||
srcIP = 'unknown' if srcIP is None else srcIP
|
||||
srcUser = 'unknown' if srcUser is None else srcUser
|
||||
useLogger.info('|'.join([type_, serviceUniqueId, serviceIp, srcIP, srcUser, username]))
|
||||
userServiceName = 'unknown' if userServiceName is None else userServiceName
|
||||
poolName = 'unknown' if poolName is None else poolName
|
||||
|
||||
useLogger.info('|'.join([type_, serviceUniqueId, serviceIp, srcIP, srcUser, username, userServiceName, poolName]))
|
||||
|
||||
|
||||
def doLog(wichObject, level, message, source=UNKNOWN, avoidDuplicates=True):
|
||||
|
@@ -59,12 +59,12 @@ def longToIp(n):
|
||||
convert long int to dotted quad string
|
||||
'''
|
||||
try:
|
||||
d = 256 * 256 * 256
|
||||
d = 1 << 24
|
||||
q = []
|
||||
while d > 0:
|
||||
m, n = divmod(n, d)
|
||||
q.append(str(m))
|
||||
d = d / 256
|
||||
q.append(str(m)) # As m is an integer, this works on py2 and p3 correctly
|
||||
d >>= 8
|
||||
|
||||
return '.'.join(q)
|
||||
except:
|
||||
@@ -97,7 +97,7 @@ def networksFromString(strNets, allowMultipleNetworks=True):
|
||||
val = 0
|
||||
for n in args:
|
||||
val += start * int(n)
|
||||
start /= 256
|
||||
start >>= 8
|
||||
return val
|
||||
|
||||
def maskFromBits(nBits):
|
||||
|
@@ -132,7 +132,7 @@ class DeployedServiceRemover(Job):
|
||||
# First check if there is someone in "removable" estate
|
||||
rems = DeployedService.objects.filter(state=State.REMOVABLE)[:10]
|
||||
if len(rems) > 0:
|
||||
logger.debug('Found a deployed service marked for removal. Starting removal of {0}'.format(rems))
|
||||
# logger.debug('Found a deployed service marked for removal. Starting removal of {0}'.format(rems))
|
||||
for ds in rems:
|
||||
try:
|
||||
# Skips checking deployed services in maintenance mode
|
||||
@@ -145,10 +145,9 @@ class DeployedServiceRemover(Job):
|
||||
except Exception as e2:
|
||||
logger.error('Could not delete {}'.format(e2))
|
||||
|
||||
|
||||
rems = DeployedService.objects.filter(state=State.REMOVING)[:10]
|
||||
if len(rems) > 0:
|
||||
logger.debug('Found a deployed service in removing state, continuing removal of {0}'.format(rems))
|
||||
# logger.debug('Found a deployed service in removing state, continuing removal of {0}'.format(rems))
|
||||
for ds in rems:
|
||||
try:
|
||||
# Skips checking deployed services in maintenance mode
|
||||
|
@@ -32,13 +32,8 @@
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import transaction
|
||||
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
from uds.models import CalendarAction, getSqlDatetime
|
||||
from uds.core.util.State import State
|
||||
from uds.core.jobs.Job import Job
|
||||
from datetime import timedelta
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -52,10 +47,9 @@ class ScheduledAction(Job):
|
||||
super(ScheduledAction, self).__init__(environment)
|
||||
|
||||
def run(self):
|
||||
with transaction.atomic():
|
||||
for ca in CalendarAction.objects.select_for_update().filter(service_pool__service__provider__maintenance_mode=False, next_execution__lt=getSqlDatetime()).order_by('next_execution'):
|
||||
logger.debug('Executing calendar action {}.{}'.format(ca.service_pool.name, ca.calendar.name))
|
||||
try:
|
||||
ca.execute()
|
||||
except Exception as e:
|
||||
logger.exception('Got an exception executing calendar access action: {}'.format(e))
|
||||
for ca in CalendarAction.objects.filter(service_pool__service__provider__maintenance_mode=False, next_execution__lt=getSqlDatetime()).order_by('next_execution'):
|
||||
logger.info('Executing calendar action {}.{} ({})'.format(ca.service_pool.name, ca.calendar.name, ca.action))
|
||||
try:
|
||||
ca.execute()
|
||||
except Exception as e:
|
||||
logger.exception('Got an exception executing calendar access action: {}'.format(e))
|
||||
|
@@ -234,7 +234,6 @@ class ServiceCacheUpdater(Job):
|
||||
logger.debug('Starting cache checking')
|
||||
# We need to get
|
||||
servicesThatNeedsUpdate = self.servicesPoolsNeedingCacheUpdate()
|
||||
logger.debug('**** Services That Needs Update: {}'.format(servicesThatNeedsUpdate))
|
||||
for sp, cacheL1, cacheL2, assigned in servicesThatNeedsUpdate:
|
||||
# We have cache to update??
|
||||
logger.debug("Updating cache for {0}".format(sp))
|
||||
|
@@ -59,7 +59,7 @@ class CacheCleaner(Job):
|
||||
|
||||
class TicketStoreCleaner(Job):
|
||||
|
||||
frecuency = 3600 * 12 # every twelve hours
|
||||
frecuency = 60 # every minute (60 seconds)
|
||||
friendly_name = 'Ticket Storage Cleaner'
|
||||
|
||||
def __init__(self, environment):
|
||||
|
@@ -36,6 +36,7 @@ from django.http import HttpResponse
|
||||
from uds.models import TicketStore
|
||||
from uds.core.util import net
|
||||
from uds.core.auths import auth
|
||||
from uds.core.managers import cryptoManager
|
||||
|
||||
import logging
|
||||
|
||||
@@ -55,8 +56,11 @@ def dict2resp(dct):
|
||||
def guacamole(request, tunnelId):
|
||||
logger.debug('Received credentials request for tunnel id {0}'.format(tunnelId))
|
||||
|
||||
tunnelId, scrambler = tunnelId.split('.')
|
||||
|
||||
try:
|
||||
val = TicketStore.get(tunnelId, invalidate=False)
|
||||
val['password'] = cryptoManager().symDecrpyt(val['password'], scrambler)
|
||||
|
||||
response = dict2resp(val)
|
||||
except Exception:
|
||||
|
@@ -27,9 +27,9 @@
|
||||
# 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.http import HttpResponseNotAllowed, HttpResponse
|
||||
@@ -49,12 +49,21 @@ def pam(request):
|
||||
return HttpResponseNotAllowed(['GET'])
|
||||
if 'id' in request.GET and 'pass' in request.GET:
|
||||
# This is an "auth" request
|
||||
logger.debug("Auth request for user [{0}] and pass [{1}]".format(request.GET['id'], request.GET['pass']))
|
||||
password = TicketStore.get(request.GET['id'])
|
||||
ids = request.GET.getlist('id')
|
||||
response = '0'
|
||||
if password == request.GET['pass']:
|
||||
response = '1'
|
||||
|
||||
if len(ids) == 1:
|
||||
userId = ids[0]
|
||||
logger.debug("Auth request for user [{0}] and pass [{1}]".format(request.GET['id'], request.GET['pass']))
|
||||
try:
|
||||
password = TicketStore.get(userId)
|
||||
if password == request.GET['pass']:
|
||||
response = '1'
|
||||
except Exception:
|
||||
# Non existing ticket, log it and stop
|
||||
logger.info('Invalid access from {} using user {}'.format(request.ip, userId))
|
||||
else:
|
||||
logger.warn('Invalid request from {}: {}'.format(request.ip, [v for v in request.GET.lists()]))
|
||||
|
||||
elif 'uid' in request.GET:
|
||||
# This is an "get name for id" call
|
||||
logger.debug("NSS Request for id [{0}]".format(request.GET['uid']))
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user