mirror of
https://github.com/dkmstr/openuds.git
synced 2025-10-07 15:33:51 +03:00
Compare commits
294 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 | ||
|
a0797ac07f | ||
|
bbcc06f503 | ||
|
6fbf419064 | ||
|
cabc906758 | ||
|
0b7535e76e | ||
|
f536112312 | ||
|
ab2ca6d527 | ||
|
442b78ef2e | ||
|
9cfc348ee6 | ||
|
0f1d1af736 | ||
|
6830d8db4e | ||
|
d8db218c6d | ||
|
81dd4e3b8c | ||
|
9519c7c95b | ||
|
afa7cb8f39 | ||
|
dc640fd400 | ||
|
76c822b015 | ||
|
c7513328eb | ||
|
e5f0fcce69 | ||
|
004acbab9a | ||
|
c566ec47a2 | ||
|
3e1a31954a | ||
|
ec5473d99f | ||
|
4cf62de3fd | ||
|
a43d6af237 | ||
|
f41f431f38 | ||
|
c5af877fce | ||
|
88dd6e7494 | ||
|
e69800ccd2 | ||
|
b1ba02c1f3 | ||
|
cb13ac1617 | ||
|
de051d99ac | ||
|
fa1e6a69e8 | ||
|
417ddc6d8d | ||
|
238f08b7dd | ||
|
739ee728d3 | ||
|
c5b9233d4a | ||
|
22a8933e82 | ||
|
a185baccdd | ||
|
2521f41e76 | ||
|
815de57b86 | ||
|
428ddd493c | ||
|
4de93ddf1f | ||
|
e6b75e3807 | ||
|
e835c018b4 | ||
|
098620cd05 | ||
|
214f9c397b | ||
|
47ae741952 | ||
|
907fad4a55 | ||
|
7c3e289a6b | ||
|
1f5a647ff3 | ||
|
0eebe6a0a5 | ||
|
8aa1607fee | ||
|
0aa1311836 | ||
|
6d49ae6667 | ||
|
e60a4bc8fa | ||
|
6e5b73c3b7 | ||
|
ae4330fc9d | ||
|
b7147661e7 | ||
|
3c07d2f667 | ||
|
f54d87a295 | ||
|
13f97248f6 | ||
|
786945fcbf | ||
|
1070b716a2 | ||
|
ad1bf0fe0b | ||
|
df70dd4fd8 | ||
|
3038c545ce | ||
|
8d3b28e3cb | ||
|
c431e39d4a | ||
|
770f2eef09 | ||
|
e702ff6bca | ||
|
c6cc1f2b43 | ||
|
06f5184ddd | ||
|
d5688116e3 | ||
|
1ec1104356 | ||
|
234eb98f0c | ||
|
fa7ab534a3 | ||
|
ee661461a5 | ||
|
aecf6854a4 | ||
|
34cd90f9a1 | ||
|
201fb8ff9b | ||
|
d071f86c31 | ||
|
1c133eacd0 | ||
|
89ed019788 | ||
|
9930f4323c | ||
|
115beefa8a | ||
|
611f3590e6 | ||
|
59635839cf | ||
|
4853729d7c | ||
|
a8b3b80f75 | ||
|
e5a6916109 | ||
|
6a3093c49d | ||
|
498154ffb1 |
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
|
||||
|
6
actors/.gitignore
vendored
6
actors/.gitignore
vendored
@@ -3,8 +3,8 @@ bin
|
||||
udsactor*.deb
|
||||
udsactor*.build
|
||||
udsactor*.changes
|
||||
/udsactor_1.7.0.dsc
|
||||
/udsactor_1.7.0.tar.xz
|
||||
/udsactor_*.dsc
|
||||
/udsactor_*.tar.xz
|
||||
/udsactor_*_amd64.buildinfo
|
||||
/udsactor*.rpm
|
||||
/udsactor_2.1.0_amd64.buildinfo
|
||||
linux/debian/files
|
||||
|
@@ -69,12 +69,14 @@ install-udsactor:
|
||||
cp scripts/udsactor $(BINDIR)
|
||||
cp scripts/UDSActorConfig-pkexec $(SBINDIR)
|
||||
cp scripts/UDSActorTool-startup $(BINDIR)
|
||||
cp scripts/udsvapp ${BINDIR}
|
||||
|
||||
# Policy to run as administrator
|
||||
cp policy/org.openuds.pkexec.UDSActorConfig.policy $(POLKITDIR)
|
||||
|
||||
# Fix permissions
|
||||
chmod 755 $(BINDIR)/udsactor
|
||||
chmod 755 $(BINDIR)/udsvapp
|
||||
chmod 755 $(BINDIR)/UDSActorTool-startup
|
||||
chmod 755 $(SBINDIR)/UDSActorConfig-pkexec
|
||||
chmod 755 $(LIBDIR)/UDSActorConfig.py
|
||||
@@ -93,4 +95,4 @@ endif
|
||||
uninstall:
|
||||
rm -rf $(LIBDIR)
|
||||
# rm -f $(BINDIR)/udsactor
|
||||
rm -rf $(CFGDIR)
|
||||
rm -rf $(CFGDIR)
|
||||
|
@@ -1,3 +1,15 @@
|
||||
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
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 19 Oct 2017 16:44:12 +0200
|
||||
|
||||
udsactor (2.1.0) stable; urgency=medium
|
||||
|
||||
* Fixes for 2.1.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
|
||||
python -m udsactor.linux.UDSActorService $@
|
||||
exec python3 -m udsactor.linux.UDSActorService $@
|
||||
|
5
actors/linux/scripts/udsvapp
Executable file
5
actors/linux/scripts/udsvapp
Executable file
@@ -0,0 +1,5 @@
|
||||
#!/bin/sh
|
||||
|
||||
/usr/bin/udsactor login "$USER"
|
||||
$@
|
||||
/usr/bin/udsactor logout "$USER"
|
@@ -60,6 +60,7 @@ This package provides the required components to allow this machine to work on a
|
||||
/etc/init.d/udsactor
|
||||
/usr/bin/UDSActorTool-startup
|
||||
/usr/bin/udsactor
|
||||
/usr/bin/udsvapp
|
||||
/usr/bin/UDSActorTool
|
||||
/usr/sbin/UDSActorConfig
|
||||
/usr/sbin/UDSActorConfig-pkexec
|
||||
|
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 python
|
||||
#!/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 python
|
||||
#!/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 self.stopped is False:
|
||||
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)
|
||||
@@ -327,17 +349,18 @@ if __name__ == '__main__':
|
||||
sys.exit(1)
|
||||
|
||||
# Sets a default idle duration, but will not be used unless idle is notified from server
|
||||
operations.initIdleDuration(3600 * 10)
|
||||
operations.initIdleDuration(3600 * 16)
|
||||
|
||||
trayIcon.show()
|
||||
|
||||
# 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.1.0'
|
||||
VERSION = '2.2.1'
|
||||
|
||||
__title__ = 'udsactor'
|
||||
__version__ = VERSION
|
||||
__build__ = 0x010750
|
||||
__build__ = 0x010756
|
||||
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
|
||||
__license__ = "BSD 3-clause"
|
||||
__copyright__ = "Copyright 2014-2016 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()
|
||||
|
@@ -44,6 +44,10 @@ from .utils import exceptionToMessage
|
||||
import socket
|
||||
import time
|
||||
import random
|
||||
import os
|
||||
import subprocess
|
||||
import shlex
|
||||
import stat
|
||||
|
||||
IPC_PORT = 39188
|
||||
|
||||
@@ -74,6 +78,7 @@ def initCfg():
|
||||
|
||||
|
||||
class CommonService(object):
|
||||
|
||||
def __init__(self):
|
||||
self.isAlive = True
|
||||
self.api = None
|
||||
@@ -81,28 +86,31 @@ 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, cmd, section):
|
||||
import os
|
||||
import subprocess
|
||||
import stat
|
||||
def execute(self, cmdLine, section): # pylint: disable=no-self-use
|
||||
cmd = shlex.split(cmdLine, posix=False)
|
||||
|
||||
if os.path.isfile(cmd):
|
||||
if (os.stat(cmd).st_mode & stat.S_IXUSR) != 0:
|
||||
subprocess.call([cmd, ])
|
||||
if os.path.isfile(cmd[0]):
|
||||
if (os.stat(cmd[0]).st_mode & stat.S_IXUSR) != 0:
|
||||
try:
|
||||
res = subprocess.check_call(cmd)
|
||||
except Exception as e:
|
||||
logger.error('Got exception executing: {} - {}'.format(cmdLine, e))
|
||||
return False
|
||||
logger.info('Result of executing cmd was {}'.format(res))
|
||||
return True
|
||||
else:
|
||||
logger.info('{} file exists but it it is not executable (needs execution permission by admin/root)'.format(section))
|
||||
logger.error('{} file exists but it it is not executable (needs execution permission by admin/root)'.format(section))
|
||||
else:
|
||||
logger.info('{} file not found & not executed'.format(section))
|
||||
logger.error('{} file not found & not executed'.format(section))
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def setReady(self):
|
||||
self.api.setReady([(v.mac, v.ip) for v in operations.getNetworkInfo()])
|
||||
|
||||
@@ -158,6 +166,7 @@ class CommonService(object):
|
||||
# Now try to run the "runonce" element
|
||||
runOnce = store.runApplication()
|
||||
if runOnce is not None:
|
||||
logger.info('Executing runOnce app: {}'.format(runOnce))
|
||||
if self.execute(runOnce, 'RunOnce') is True:
|
||||
# operations.reboot()
|
||||
return False
|
||||
@@ -252,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:
|
||||
@@ -301,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,15 @@
|
||||
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
|
||||
|
||||
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 27 Aug 2017 14:18:18 +0200
|
||||
|
||||
udsclient (2.1.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), rdesktop | freerdp-x11, 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.1.0_all.deb admin optional
|
||||
udsclient_2.1.0_amd64.buildinfo admin optional
|
||||
udsclient_2.2.1_all.deb admin optional
|
||||
udsclient_2.2.1_amd64.buildinfo admin optional
|
||||
|
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python2.7
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2017 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2017 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -43,9 +43,15 @@ from uds import tools
|
||||
from uds import VERSION
|
||||
|
||||
import webbrowser
|
||||
import json
|
||||
import sys
|
||||
import six
|
||||
|
||||
from UDSWindow import Ui_MainWindow
|
||||
|
||||
# Server before this version uses "unsigned" scripts
|
||||
OLD_METHOD_VERSION = '2.4.0'
|
||||
|
||||
class RetryException(Exception):
|
||||
pass
|
||||
|
||||
@@ -57,6 +63,7 @@ class UDSClient(QtGui.QMainWindow):
|
||||
animTimer = None
|
||||
anim = 0
|
||||
animInverted = False
|
||||
serverVersion = 'X.Y.Z' # Will be overwriten on getVersion
|
||||
|
||||
def __init__(self):
|
||||
QtGui.QMainWindow.__init__(self)
|
||||
@@ -146,6 +153,8 @@ class UDSClient(QtGui.QMainWindow):
|
||||
webbrowser.open(data['result']['downloadUrl'])
|
||||
self.closeWindow()
|
||||
return
|
||||
|
||||
self.serverVersion = data['result']['requiredVersion']
|
||||
self.getTransportData()
|
||||
|
||||
except RetryException as e:
|
||||
@@ -153,6 +162,7 @@ class UDSClient(QtGui.QMainWindow):
|
||||
QtCore.QTimer.singleShot(1000, self.getVersion)
|
||||
|
||||
except Exception as e:
|
||||
logger.exception('Version')
|
||||
self.showError(e)
|
||||
|
||||
|
||||
@@ -172,7 +182,20 @@ class UDSClient(QtGui.QMainWindow):
|
||||
try:
|
||||
self.processError(data)
|
||||
|
||||
script = data['result'].decode('base64').decode('bz2')
|
||||
params = None
|
||||
|
||||
if self.serverVersion <= OLD_METHOD_VERSION:
|
||||
script = data['result'].decode('base64').decode('bz2')
|
||||
else:
|
||||
res = data['result']
|
||||
# We have three elements on result:
|
||||
# * Script
|
||||
# * Signature
|
||||
# * Script data
|
||||
# We test that the Script has correct signature, and them execute it with the parameters
|
||||
script, signature, params = res['script'].decode('base64').decode('bz2'), res['signature'], json.loads(res['params'].decode('base64').decode('bz2'))
|
||||
if tools.verifySignature(script, signature) is False:
|
||||
raise Exception('Invalid UDS code signature. Please, report to administrator')
|
||||
|
||||
self.stopAnim()
|
||||
|
||||
@@ -182,7 +205,14 @@ class UDSClient(QtGui.QMainWindow):
|
||||
QtCore.QTimer.singleShot(3000, self.endScript)
|
||||
self.hide()
|
||||
|
||||
six.exec_(script, globals(), {'parent': self})
|
||||
# if self.serverVersion <= OLD_METHOD_VERSION:
|
||||
# errorString = '<p>The server <b>{}</b> runs an old version of UDS:</p>'.format(host)
|
||||
# errorString += '<p>To avoid security issues, you must approve old UDS Version access.</p>'
|
||||
|
||||
# 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:
|
||||
self.ui.info.setText(six.text_type(e) + ', retrying access...')
|
||||
@@ -224,7 +254,7 @@ def done(data):
|
||||
QtGui.QMessageBox.critical(None, 'Notice', six.text_type(data.data), QtGui.QMessageBox.Ok)
|
||||
sys.exit(0)
|
||||
|
||||
# Ask user to aprobe endpoint
|
||||
# Ask user to approve endpoint
|
||||
def approveHost(host, parentWindow=None):
|
||||
settings = QtCore.QSettings()
|
||||
settings.beginGroup('endpoints')
|
||||
@@ -241,6 +271,7 @@ def approveHost(host, parentWindow=None):
|
||||
settings.endGroup()
|
||||
return approved
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.debug('Initializing connector')
|
||||
# Initialize app
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2014 Virtual Cable S.L.
|
||||
# Copyright (c) 2014-2017 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -34,11 +34,11 @@ from __future__ import unicode_literals
|
||||
# On centos, old six release does not includes byte2int, nor six.PY2
|
||||
import six
|
||||
|
||||
VERSION = '2.1.0'
|
||||
VERSION = '2.2.0'
|
||||
|
||||
__title__ = 'udclient'
|
||||
__version__ = VERSION
|
||||
__build__ = 0x010750
|
||||
__build__ = 0x010760
|
||||
__author__ = 'Adolfo Gómez'
|
||||
__license__ = "BSD 3-clause"
|
||||
__copyright__ = "Copyright 2014-2015 VirtualCable S.L.U."
|
||||
__copyright__ = "Copyright 2014-2017 VirtualCable S.L.U."
|
||||
|
@@ -32,6 +32,8 @@
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from base64 import b64decode
|
||||
|
||||
import tempfile
|
||||
import string
|
||||
import random
|
||||
@@ -48,9 +50,25 @@ _unlinkFiles = []
|
||||
_tasksToWait = []
|
||||
_execBeforeExit = []
|
||||
|
||||
|
||||
sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
|
||||
|
||||
# Public key for scripts
|
||||
PUBLIC_KEY = '''-----BEGIN PUBLIC KEY-----
|
||||
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuNURlGjBpqbglkTTg2lh
|
||||
dU5qPbg9Q+RofoDDucGfrbY0pjB9ULgWXUetUWDZhFG241tNeKw+aYFTEorK5P+g
|
||||
ud7h9KfyJ6huhzln9eyDu3k+kjKUIB1PLtA3lZLZnBx7nmrHRody1u5lRaLVplsb
|
||||
FmcnptwYD+3jtJ2eK9ih935DYAkYS4vJFi2FO+npUQdYBZHPG/KwXLjP4oGOuZp0
|
||||
pCTLiCXWGjqh2GWsTECby2upGS/ZNZ1r4Ymp4V2A6DZnN0C0xenHIY34FWYahbXF
|
||||
ZGdr4DFBPdYde5Rb5aVKJQc/pWK0CV7LK6Krx0/PFc7OGg7ItdEuC7GSfPNV/ANt
|
||||
5BEQNF5w2nUUsyN8ziOrNih+z6fWQujAAUZfpCCeV9ekbwXGhbRtdNkbAryE5vH6
|
||||
eCE0iZ+cFsk72VScwLRiOhGNelMQ7mIMotNck3a0P15eaGJVE2JV0M/ag/Cnk0Lp
|
||||
wI1uJQRAVqz9ZAwvF2SxM45vnrBn6TqqxbKnHCeiwstLDYG4fIhBwFxP3iMH9EqV
|
||||
2+QXqdJW/wLenFjmXfxrjTRr+z9aYMIdtIkSpADIlbaJyTtuQpEdWnrlDS2b1IGd
|
||||
Okbm65EebVzOxfje+8dRq9Uqwip8f/qmzFsIIsx3wPSvkKawFwb0G5h2HX5oJrk0
|
||||
nVgtClKcDDlSaBsO875WDR0CAwEAAQ==
|
||||
-----END PUBLIC KEY-----'''
|
||||
|
||||
|
||||
def saveTempFile(content, filename=None):
|
||||
if filename is None:
|
||||
filename = b''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(16))
|
||||
@@ -68,6 +86,7 @@ def saveTempFile(content, filename=None):
|
||||
logger.info('Returning filename')
|
||||
return filename
|
||||
|
||||
|
||||
def readTempFile(filename):
|
||||
if 'win32' in sys.platform:
|
||||
filename = filename.encode('utf-8')
|
||||
@@ -79,6 +98,7 @@ def readTempFile(filename):
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def testServer(host, port, timeOut=4):
|
||||
try:
|
||||
sock = socket.create_connection((host, int(port)), timeOut)
|
||||
@@ -160,3 +180,24 @@ def addExecBeforeExit(fnc):
|
||||
def execBeforeExit():
|
||||
for fnc in _execBeforeExit:
|
||||
fnc.__call__()
|
||||
|
||||
|
||||
def verifySignature(script, signature):
|
||||
'''
|
||||
Verifies with a public key from whom the data came that it was indeed
|
||||
signed by their private key
|
||||
param: public_key_loc Path to public key
|
||||
param: signature String signature to be verified
|
||||
return: Boolean. True if the signature is valid; False otherwise.
|
||||
'''
|
||||
# For signature checking
|
||||
from Crypto.PublicKey import RSA
|
||||
from Crypto.Signature import PKCS1_v1_5
|
||||
from Crypto.Hash import SHA256
|
||||
|
||||
rsakey = RSA.importKey(PUBLIC_KEY)
|
||||
signer = PKCS1_v1_5.new(rsakey)
|
||||
digest = SHA256.new(script) # Script is "binary string" here
|
||||
if signer.verify(digest, b64decode(signature)):
|
||||
return True
|
||||
return False
|
||||
|
@@ -39,9 +39,9 @@ import json
|
||||
import six
|
||||
import osDetector
|
||||
|
||||
|
||||
from .log import logger
|
||||
|
||||
|
||||
class RetryException(Exception):
|
||||
pass
|
||||
|
||||
@@ -65,7 +65,7 @@ class RestRequest(object):
|
||||
logger.debug('Requesting {}'.format(url))
|
||||
|
||||
try:
|
||||
r = requests.get(url, headers={'Content-type': 'application/json', 'User-Agent': osDetector.getOs() + " - UDS Connector " + VERSION })
|
||||
r = requests.get(url, headers={'Content-type': 'application/json', 'User-Agent': osDetector.getOs() + " - UDS Connector " + VERSION }, verify=False)
|
||||
except requests.exceptions.ConnectionError as e:
|
||||
raise Exception('Error connecting to UDS Server at {}'.format(self.restApiUrl[0:-11]))
|
||||
|
||||
|
@@ -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)
|
||||
|
@@ -1,8 +1,8 @@
|
||||
#!/bin/sh
|
||||
pip install paramiko requests six
|
||||
pip install paramiko requests six pycrypto
|
||||
rm -rf lib
|
||||
mkdir -p lib/python2.7/site-packages
|
||||
for a in requests paramiko pyasn1 cryptography packaging idna asn1crypto six enum ipaddress cffi ; do cp -r /usr/lib/python2.7/site-packages/$a* lib/python2.7/site-packages/; done
|
||||
for a in requests paramiko pyasn1 cryptography packaging idna asn1crypto six enum ipaddress cffi Crypto; do cp -r /usr/lib/python2.7/site-packages/$a* lib/python2.7/site-packages/; done
|
||||
cp src/udsclient bin
|
||||
chmod 755 bin/udsclient
|
||||
mkdir lib/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
|
||||
|
@@ -65,6 +65,7 @@ def login():
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def logout():
|
||||
global headers
|
||||
h = Http()
|
||||
|
@@ -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'
|
||||
@@ -164,6 +166,7 @@ MIDDLEWARE_CLASSES = (
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'uds.core.util.request.GlobalRequestMiddleware',
|
||||
'uds.core.util.middleware.XUACompatibleMiddleware',
|
||||
'uds.core.util.middleware.RedirectMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
)
|
||||
|
||||
|
@@ -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()
|
||||
|
@@ -39,6 +39,7 @@ from django.contrib.sessions.backends.db import SessionStore
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
from uds.core.auths.auth import getRootUser
|
||||
from uds.models import Authenticator
|
||||
from uds.core.managers import cryptoManager
|
||||
|
||||
import logging
|
||||
|
||||
@@ -178,7 +179,7 @@ class Handler(object):
|
||||
return self._authToken
|
||||
|
||||
@staticmethod
|
||||
def storeSessionAuthdata(session, id_auth, username, locale, is_admin, staff_member):
|
||||
def storeSessionAuthdata(session, id_auth, username, password, locale, is_admin, staff_member, scrambler):
|
||||
'''
|
||||
Stores the authentication data inside current session
|
||||
:param session: session handler (Djano user session object)
|
||||
@@ -194,12 +195,13 @@ class Handler(object):
|
||||
session['REST'] = {
|
||||
'auth': id_auth,
|
||||
'username': username,
|
||||
'password': cryptoManager().symCrypt(password, scrambler), # Stores "bytes"
|
||||
'locale': locale,
|
||||
'is_admin': is_admin,
|
||||
'staff_member': staff_member
|
||||
}
|
||||
|
||||
def genAuthToken(self, id_auth, username, locale, is_admin, staf_member):
|
||||
def genAuthToken(self, id_auth, username, password, locale, is_admin, staf_member, scrambler):
|
||||
'''
|
||||
Generates the authentication token from a session, that is basically
|
||||
the session key itself
|
||||
@@ -211,7 +213,7 @@ class Handler(object):
|
||||
'''
|
||||
session = SessionStore()
|
||||
session.set_expiry(GlobalConfig.ADMIN_IDLE_TIME.getInt())
|
||||
Handler.storeSessionAuthdata(session, id_auth, username, locale, is_admin, staf_member)
|
||||
Handler.storeSessionAuthdata(session, id_auth, username, password, locale, is_admin, staf_member, scrambler)
|
||||
session.save()
|
||||
self._authToken = session.session_key
|
||||
self._session = session
|
||||
|
@@ -39,12 +39,12 @@ from uds.core.util.State import State
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.core.util import log
|
||||
from uds.core.managers import cryptoManager
|
||||
from uds.core.osmanagers import OSManager
|
||||
from uds.models import TicketStore
|
||||
from uds.REST import Handler
|
||||
from uds.REST import RequestError
|
||||
from uds.models import UserService
|
||||
|
||||
|
||||
import datetime
|
||||
import six
|
||||
|
||||
@@ -52,7 +52,6 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
# Actor key, configurable in Security Section of administration interface
|
||||
actorKey = Config.Config.section(Config.SECURITY_SECTION).value('Master Key',
|
||||
cryptoManager().uuid(datetime.datetime.now()).replace('-', ''),
|
||||
@@ -99,8 +98,8 @@ class Actor(Handler):
|
||||
'''
|
||||
# Ensures that key is first parameter
|
||||
# Here, path will be .../actor/ACTION/KEY (probably /rest/actor/KEY/...)
|
||||
logger.debug('{} == {}'.format(self._params.get('key'), actorKey.get(True)))
|
||||
if self._params.get('key') != actorKey.get(True):
|
||||
# logger.debug('{} == {}'.format(self._params.get('key'), actorKey.get()))
|
||||
if self._params.get('key') != actorKey.get():
|
||||
return Actor.result(_('Invalid key'), error=ERR_INVALID_KEY)
|
||||
return None
|
||||
|
||||
@@ -145,6 +144,10 @@ class Actor(Handler):
|
||||
if len(self._args) < 1:
|
||||
raise RequestError('Invalid request')
|
||||
|
||||
if self._args[0] == 'PostThoughGet':
|
||||
self._args = self._args[1:] # Remove first argument
|
||||
return self.post()
|
||||
|
||||
if self._args[0] == 'ticket':
|
||||
return self.getTicket()
|
||||
|
||||
@@ -211,8 +214,22 @@ class Actor(Handler):
|
||||
logger.debug(self._params)
|
||||
data = '\t'.join((self._params.get('message'), six.text_type(self._params.get('level', 10000))))
|
||||
|
||||
osmanager = service.getInstance().osmanager()
|
||||
|
||||
try:
|
||||
res = service.getInstance().osmanager().process(service, message, data, options={'scramble': False})
|
||||
if osmanager is None:
|
||||
if message in ('login', 'logout'):
|
||||
osm = OSManager(None, None) # Dummy os manager, just for using "logging" capability
|
||||
if message == 'login':
|
||||
osm.loggedIn(service)
|
||||
else:
|
||||
osm.loggedOut(service)
|
||||
# Mark for removal...
|
||||
service.release() # Release for removal
|
||||
return 'ok'
|
||||
raise Exception('Unknown message {} for an user service without os manager'.format(message))
|
||||
else:
|
||||
res = osmanager.process(service, message, data, options={'scramble': False})
|
||||
except Exception as e:
|
||||
return Actor.result(six.text_type(e), ERR_OSMANAGER_ERROR)
|
||||
|
||||
|
@@ -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
|
||||
|
@@ -44,6 +44,7 @@ from uds.core.managers import cryptoManager, userServiceManager
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
from uds.core.services.Exceptions import ServiceNotReadyError
|
||||
from uds.core import VERSION as UDS_VERSION
|
||||
from uds.core.util import encoders
|
||||
|
||||
import six
|
||||
|
||||
@@ -134,15 +135,13 @@ 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
|
||||
|
||||
transportScript = transportInstance.getUDSTransportScript(userService, transport, ip, self._request.os, self._request.user, password, self._request)
|
||||
transportScript = transportInstance.getEncodedTransportScript(userService, transport, ip, self._request.os, self._request.user, password, self._request)
|
||||
|
||||
logger.debug('Script:\n{}'.format(transportScript))
|
||||
|
||||
return Client.result(result=transportScript.encode('bz2').encode('base64'))
|
||||
return Client.result(result=transportScript)
|
||||
except ServiceNotReadyError as e:
|
||||
# Refresh ticket and make this retrayable
|
||||
TicketStore.revalidate(ticket, 20) # Retry will be in at most 5 seconds
|
||||
|
@@ -36,12 +36,13 @@ from django.utils.translation import ugettext as _
|
||||
|
||||
from uds.REST import Handler
|
||||
from uds.REST import RequestError
|
||||
from uds.models import UserService, DeployedService, Transport
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.core.managers.UserServiceManager import UserServiceManager
|
||||
from uds.core.util import log
|
||||
from uds.core.util.stats import events
|
||||
|
||||
from uds.models import UserService, DeployedService, ServicesPoolGroup
|
||||
from uds.core.managers import userServiceManager
|
||||
from uds.core.managers import cryptoManager
|
||||
from uds.core.ui.images import DEFAULT_THUMB_BASE64
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
from uds.core.services.Exceptions import ServiceNotReadyError
|
||||
from uds.web import errors
|
||||
|
||||
import datetime
|
||||
import six
|
||||
@@ -51,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
|
||||
@@ -61,7 +62,7 @@ class Connection(Handler):
|
||||
needs_staff = False
|
||||
|
||||
@staticmethod
|
||||
def result(result=None, error=None):
|
||||
def result(result=None, error=None, errorCode=0, retryable=False):
|
||||
'''
|
||||
Helper method to create a "result" set for connection response
|
||||
:param result: Result value to return (can be None, in which case it is converted to empty string '')
|
||||
@@ -71,7 +72,14 @@ class Connection(Handler):
|
||||
result = result if result is not None else ''
|
||||
res = {'result': result, 'date': datetime.datetime.now()}
|
||||
if error is not None:
|
||||
if isinstance(error, int):
|
||||
error = errors.errorString(error)
|
||||
if errorCode != 0:
|
||||
error += ' (code {0:04X})'.format(errorCode)
|
||||
res['error'] = error
|
||||
|
||||
res['retryable'] = retryable and '1' or '0'
|
||||
|
||||
return res
|
||||
|
||||
def serviceList(self):
|
||||
@@ -90,32 +98,50 @@ class Connection(Handler):
|
||||
if t.validForIp(self._request.ip) and t.getType().providesConnetionInfo():
|
||||
trans.append({'id': t.uuid, 'name': t.name})
|
||||
|
||||
servicePool = svr.deployed_service
|
||||
|
||||
services.append({'id': 'A' + svr.uuid,
|
||||
'name': svr['name'],
|
||||
'name': servicePool.name,
|
||||
'description': servicePool.comments,
|
||||
'visual_name': servicePool.visual_name,
|
||||
'group': servicePool.servicesPoolGroup if servicePool.servicesPoolGroup is not None else ServicesPoolGroup.default().as_dict,
|
||||
'thumb': servicePool.image.thumb64 if servicePool.image is not None else DEFAULT_THUMB_BASE64,
|
||||
'show_transports': servicePool.show_transports,
|
||||
'allow_users_remove': servicePool.allow_users_remove,
|
||||
'maintenance': servicePool.isInMaintenance(),
|
||||
'not_accesible': not servicePool.isAccessAllowed(),
|
||||
'to_be_replaced': False, # Manually assigned will not be autoremoved never
|
||||
'transports': trans,
|
||||
'maintenance': svr.isInMaintenance(),
|
||||
'in_use': svr.in_use})
|
||||
'in_use': servicePool.in_use})
|
||||
|
||||
logger.debug(services)
|
||||
|
||||
# Now generic user service
|
||||
for svr in availServices:
|
||||
for servicePool in availServices:
|
||||
trans = []
|
||||
for t in svr.transports.all().order_by('priority'):
|
||||
for t in servicePool.transports.all().order_by('priority'):
|
||||
if t.validForIp(self._request.ip) and t.getType().providesConnetionInfo():
|
||||
trans.append({'id': t.uuid, 'name': t.name})
|
||||
|
||||
# Locate if user service has any already assigned user service for this
|
||||
ads = UserServiceManager.manager().getExistingAssignationForUser(svr, self._user)
|
||||
ads = userServiceManager().getExistingAssignationForUser(servicePool, self._user)
|
||||
if ads is None:
|
||||
in_use = False
|
||||
else:
|
||||
in_use = ads.in_use
|
||||
|
||||
services.append({'id': 'F' + svr.uuid,
|
||||
'name': svr.name,
|
||||
services.append({'id': 'F' + servicePool.uuid,
|
||||
'name': servicePool.name,
|
||||
'description': servicePool.comments,
|
||||
'visual_name': servicePool.visual_name,
|
||||
'group': servicePool.servicesPoolGroup if servicePool.servicesPoolGroup is not None else ServicesPoolGroup.default().as_dict,
|
||||
'thumb': servicePool.image.thumb64 if servicePool.image is not None else DEFAULT_THUMB_BASE64,
|
||||
'show_transports': servicePool.show_transports,
|
||||
'allow_users_remove': servicePool.allow_users_remove,
|
||||
'maintenance': servicePool.isInMaintenance(),
|
||||
'not_accesible': not servicePool.isAccessAllowed(),
|
||||
'to_be_replaced': servicePool.toBeReplaced(),
|
||||
'transports': trans,
|
||||
'maintenance': svr.isInMaintenance(),
|
||||
'in_use': in_use})
|
||||
|
||||
logger.debug('Services: {0}'.format(services))
|
||||
@@ -125,71 +151,53 @@ class Connection(Handler):
|
||||
return Connection.result(result=services)
|
||||
|
||||
def connection(self, doNotCheck=False):
|
||||
kind, idService = self._args[0][0], self._args[0][1:]
|
||||
idService = self._args[0]
|
||||
idTransport = self._args[1]
|
||||
try:
|
||||
ip, userService, iads, trans, itrans = userServiceManager().getService(self._user, self._request.ip, idService, idTransport, not doNotCheck)
|
||||
ci = {
|
||||
'username': '',
|
||||
'password': '',
|
||||
'domain': '',
|
||||
'protocol': 'unknown',
|
||||
'ip': ip
|
||||
}
|
||||
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
|
||||
return Connection.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True)
|
||||
except Exception as e:
|
||||
logger.exception("Exception")
|
||||
return Connection.result(error=six.text_type(e))
|
||||
|
||||
logger.debug('Type: {}, Service: {}, Transport: {}'.format(kind, idService, idTransport))
|
||||
def script(self):
|
||||
idService = self._args[0]
|
||||
idTransport = self._args[1]
|
||||
scrambler = self._args[2]
|
||||
hostname = self._args[3]
|
||||
|
||||
try:
|
||||
logger.debug('Kind of service: {0}, idService: {1}'.format(kind, idService))
|
||||
if kind == 'A': # This is an assigned service
|
||||
ads = UserService.objects.get(uuid=processUuid(idService))
|
||||
else:
|
||||
ds = DeployedService.objects.get(uuid=processUuid(idService))
|
||||
# We first do a sanity check for this, if the user has access to this service
|
||||
# If it fails, will raise an exception
|
||||
ds.validateUser(self._user)
|
||||
# Now we have to locate an instance of the service, so we can assign it to user.
|
||||
ads = UserServiceManager.manager().getAssignationForUser(ds, self._user)
|
||||
res = userServiceManager().getService(self._user, self._request.ip, idService, idTransport)
|
||||
logger.debug('Res: {}'.format(res))
|
||||
ip, userService, userServiceInstance, transport, transportInstance = res
|
||||
password = cryptoManager().symDecrpyt(self.getValue('password'), scrambler)
|
||||
|
||||
if ads.isInMaintenance() is True:
|
||||
return Connection.result(error='Service in maintenance')
|
||||
userService.setConnectionSource(self._request.ip, hostname) # Store where we are accessing from so we can notify Service
|
||||
|
||||
logger.debug('Found service: {0}'.format(ads))
|
||||
trans = Transport.objects.get(uuid=processUuid(idTransport))
|
||||
transportScript = transportInstance.getEncodedTransportScript(userService, transport, ip, self._request.os, self._user, password, self._request)
|
||||
|
||||
if trans.validForIp(self._request.ip) is False:
|
||||
return Connection.result(error='Access denied')
|
||||
|
||||
# Test if the service is ready
|
||||
if doNotCheck or ads.isReady():
|
||||
log.doLog(ads, log.INFO, "User {0} from {1} has initiated access".format(self._user.name, self._request.ip), log.WEB)
|
||||
# If ready, show transport for this service, if also ready ofc
|
||||
iads = ads.getInstance()
|
||||
ip = iads.getIp()
|
||||
logger.debug('IP: {}'.format(ip))
|
||||
events.addEvent(ads.deployed_service, events.ET_ACCESS, username=self._user.name, srcip=self._request.ip, dstip=ip, uniqueid=ads.unique_id)
|
||||
if ip is not None:
|
||||
itrans = trans.getInstance()
|
||||
if itrans.providesConnetionInfo() and (doNotCheck or itrans.isAvailableFor(ads, ip)):
|
||||
ads.setConnectionSource(self._request.ip, 'unknown')
|
||||
log.doLog(ads, log.INFO, "User service ready, rendering transport", log.WEB)
|
||||
|
||||
ci = {
|
||||
'username': '',
|
||||
'password': '',
|
||||
'domain': '',
|
||||
'protocol': 'unknown',
|
||||
'ip': ip
|
||||
}
|
||||
ci.update(itrans.getConnectionInfo(ads, self._user, 'UNKNOWN'))
|
||||
|
||||
UserServiceManager.manager().notifyPreconnect(ads, itrans.processedUser(ads, self._user), itrans.protocol)
|
||||
|
||||
return Connection.result(result=ci)
|
||||
else:
|
||||
log.doLog(ads, log.WARN, "User service is not accessible by REST (ip {0})".format(ip), log.TRANSPORT)
|
||||
logger.debug('Transport {} is not accesible for user service {} from {}'.format(trans, ads, self._request.ip))
|
||||
logger.debug("{}, {}".format(itrans.providesConnetionInfo(), itrans.isAvailableFor(ads, ip)))
|
||||
else:
|
||||
logger.debug('Ip not available from user service {0}'.format(ads))
|
||||
else:
|
||||
log.doLog(ads, log.WARN, "User {0} from {1} tried to access, but service was not ready".format(self._user.name, self._request.ip), log.WEB)
|
||||
# Not ready, show message and return to this page in a while
|
||||
return Connection.result(error='Service not ready')
|
||||
return Connection.result(result=transportScript)
|
||||
except ServiceNotReadyError as e:
|
||||
# Refresh ticket and make this retrayable
|
||||
return Connection.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True)
|
||||
except Exception as e:
|
||||
logger.exception("Exception")
|
||||
return Connection.result(error=six.text_type(e))
|
||||
|
||||
return password
|
||||
|
||||
def get(self):
|
||||
'''
|
||||
Processes get requests
|
||||
@@ -207,7 +215,13 @@ class Connection(Handler):
|
||||
# Return connection & validate access for service/transport
|
||||
return self.connection()
|
||||
|
||||
if len(self._args) == 3 and self._args[2] == 'skipChecking':
|
||||
return self.connection(True)
|
||||
if len(self._args) == 3:
|
||||
# /connection/idService/idTransport/skipChecking
|
||||
if self._args[2] == 'skipChecking':
|
||||
return self.connection(True)
|
||||
|
||||
if len(self._args) == 4:
|
||||
# /connection/idService/idTransport/scrambler/hostname
|
||||
return self.script()
|
||||
|
||||
raise RequestError('Invalid Request')
|
||||
|
@@ -36,11 +36,14 @@ from uds.core.util.Config import GlobalConfig
|
||||
from uds.core.util.model import processUuid
|
||||
from uds.models import Authenticator
|
||||
from uds.core.auths.auth import authenticate
|
||||
from uds.core import VERSION as UDS_VERSION
|
||||
|
||||
from uds.REST import RequestError
|
||||
from uds.REST import Handler
|
||||
|
||||
import logging
|
||||
import random
|
||||
import string
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -75,6 +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.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)
|
||||
@@ -83,7 +87,7 @@ class Login(Handler):
|
||||
locale = self._params.get('locale', 'en')
|
||||
if authName == 'admin' or authSmallName == 'admin':
|
||||
if GlobalConfig.SUPER_USER_LOGIN.get(True) == username and GlobalConfig.SUPER_USER_PASS.get(True) == password:
|
||||
self.genAuthToken(-1, username, locale, True, True)
|
||||
self.genAuthToken(-1, username, password, locale, True, True, scrambler)
|
||||
return{'result': 'ok', 'token': self.getAuthToken()}
|
||||
else:
|
||||
raise Exception('Invalid credentials')
|
||||
@@ -98,14 +102,14 @@ class Login(Handler):
|
||||
auth = Authenticator.objects.get(small_name=authSmallName)
|
||||
|
||||
if password == '':
|
||||
password = 'xdaf44tgas4xd5ñasdłe4g€@#½|«ð2' # Extrange password if credential leaved empty
|
||||
password = 'xdaf44tgas4xd5ñasdłe4g€@#½|«ð2' # Extrange password if credential left empty
|
||||
|
||||
logger.debug('Auth obj: {0}'.format(auth))
|
||||
user = authenticate(username, password, auth)
|
||||
if user is None: # invalid credentials
|
||||
raise Exception()
|
||||
self.genAuthToken(auth.id, user.name, locale, user.is_admin, user.staff_member)
|
||||
return{'result': 'ok', 'token': self.getAuthToken()}
|
||||
self.genAuthToken(auth.id, user.name, password, locale, user.is_admin, user.staff_member, scrambler)
|
||||
return{'result': 'ok', 'token': self.getAuthToken(), 'version': UDS_VERSION, 'scrambler': scrambler }
|
||||
except:
|
||||
logger.exception('Credentials ')
|
||||
raise Exception('Invalid Credentials (invalid authenticator)')
|
||||
@@ -138,11 +142,13 @@ class Auths(Handler):
|
||||
|
||||
def auths(self):
|
||||
for a in Authenticator.objects.all():
|
||||
if a.getType().isCustom() is False:
|
||||
theType = a.getType()
|
||||
if theType.isCustom() is False and theType.typeType not in ('IP',):
|
||||
yield {
|
||||
'authId': a.id,
|
||||
'authId': a.uuid,
|
||||
'authSmallName': str(a.small_name),
|
||||
'auth': a.name,
|
||||
'type': theType.typeType,
|
||||
}
|
||||
|
||||
def get(self):
|
||||
|
@@ -158,7 +158,10 @@ class Providers(ModelHandler):
|
||||
self.ensureAccess(spType, permissions.PERMISSION_MANAGEMENT, root=True)
|
||||
|
||||
logger.debug('spType: {}'.format(spType))
|
||||
res = spType.test(Environment.getTempEnv(), self._params)
|
||||
|
||||
dct = self._params.copy()
|
||||
dct['_request'] = self._request
|
||||
res = spType.test(Environment.getTempEnv(), dct)
|
||||
if res[0]:
|
||||
return 'ok'
|
||||
else:
|
||||
|
@@ -34,7 +34,6 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
|
||||
|
||||
from uds.models import Service, UserService, Tag
|
||||
|
||||
from uds.core.services import Service as coreService
|
||||
@@ -63,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,
|
||||
@@ -75,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
|
||||
@@ -93,7 +94,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
|
||||
'type': item.data_type,
|
||||
'type_name': _(itemType.name()),
|
||||
'deployed_services_count': item.deployedServices.count(),
|
||||
'user_services_count': UserService.objects.filter(deployed_service__service=item).exclude(state__in=(State.REMOVED, State.ERROR)).count(),
|
||||
'user_services_count': UserService.objects.filter(deployed_service__service=item).exclude(state__in=State.INFO_STATES).count(),
|
||||
'maintenance_mode': item.provider.maintenance_mode,
|
||||
'permission': perm
|
||||
}
|
||||
@@ -158,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,18 +108,26 @@ 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):
|
||||
'''
|
||||
Processes the transports detail requests of a Service Pool
|
||||
'''
|
||||
custom_methods = ('execute')
|
||||
custom_methods = ('execute',)
|
||||
|
||||
@staticmethod
|
||||
def as_dict(item):
|
||||
@@ -129,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),
|
||||
@@ -147,7 +154,6 @@ class ActionsCalendars(DetailHandler):
|
||||
except Exception:
|
||||
self.invalidItemException()
|
||||
|
||||
|
||||
def getTitle(self, parent):
|
||||
return _('Scheduled actions')
|
||||
|
||||
@@ -168,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)
|
||||
@@ -186,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()
|
||||
|
@@ -69,7 +69,11 @@ class ServicesPools(ModelHandler):
|
||||
'actions': ActionsCalendars
|
||||
}
|
||||
|
||||
save_fields = ['name', 'comments', 'tags', 'service_id', 'osmanager_id', 'image_id', 'servicesPoolGroup_id', 'initial_srvs', 'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs', 'show_transports']
|
||||
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', 'allow_users_reset', 'ignores_unused']
|
||||
|
||||
remove_fields = ['osmanager_id', 'service_id']
|
||||
|
||||
table_title = _('Service Pools')
|
||||
@@ -88,53 +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,
|
||||
'tags': [tag.tag for tag in item.tags.all()],
|
||||
'short_name': item.short_name,
|
||||
'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.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
|
||||
|
||||
@@ -147,84 +163,109 @@ class ServicesPools(ModelHandler):
|
||||
if Service.objects.count() < 1:
|
||||
raise ResponseError(ugettext('Create at least a service before creating a new service pool'))
|
||||
|
||||
g = self.addDefaultFields([], ['name', 'comments', 'tags'])
|
||||
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': 'image_id',
|
||||
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]),
|
||||
'label': ugettext('Associated Image'),
|
||||
'tooltip': ugettext('Image assocciated with this service'),
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 102,
|
||||
'tab': ugettext('Display'),
|
||||
}, {
|
||||
'name': 'servicesPoolGroup_id',
|
||||
'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in ServicesPoolGroup.objects.all()]),
|
||||
'label': ugettext('Pool group'),
|
||||
'tooltip': ugettext('Pool group for this pool (for pool clasify on display)'),
|
||||
'type': gui.InputField.IMAGECHOICE_TYPE,
|
||||
'order': 103,
|
||||
'tab': ugettext('Display'),
|
||||
}, {
|
||||
'name': 'initial_srvs',
|
||||
'value': '0',
|
||||
'minValue': '0',
|
||||
'label': ugettext('Initial available services'),
|
||||
'tooltip': ugettext('Services created initially for this service pool'),
|
||||
'type': gui.InputField.NUMERIC_TYPE,
|
||||
'order': 110,
|
||||
'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': 111,
|
||||
'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': 112,
|
||||
'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': 113,
|
||||
'tab': ugettext('Availability'),
|
||||
}, {
|
||||
'name': 'show_transports',
|
||||
'value': True,
|
||||
'label': ugettext('Show transports'),
|
||||
'tooltip': ugettext('If active, alternative transports for user will be shown'),
|
||||
'type': gui.InputField.CHECKBOX_TYPE,
|
||||
'order': 120,
|
||||
}]:
|
||||
'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
|
||||
@@ -244,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-02-02'
|
||||
|
||||
__updated__ = '2018-11-20'
|
||||
|
||||
# a few constants
|
||||
OVERVIEW = 'overview'
|
||||
@@ -123,7 +121,7 @@ class BaseModelHandler(Handler):
|
||||
'label': _('Tags'),
|
||||
'type': 'taglist',
|
||||
'tooltip': _('Tags for this element'),
|
||||
'order': 0 - 101,
|
||||
'order': 0 - 105,
|
||||
})
|
||||
if 'name' in flds:
|
||||
self.addField(gui, {
|
||||
@@ -134,13 +132,23 @@ class BaseModelHandler(Handler):
|
||||
'tooltip': _('Name of this element'),
|
||||
'order': 0 - 100,
|
||||
})
|
||||
if 'short_name' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'short_name',
|
||||
'type': 'text',
|
||||
'label': _('Short name'),
|
||||
'tooltip': _('Short name for user service visualization'),
|
||||
'required': False,
|
||||
'length': 16,
|
||||
'order': 0 - 95,
|
||||
})
|
||||
if 'comments' in flds:
|
||||
self.addField(gui, {
|
||||
'name': 'comments',
|
||||
'label': _('Comments'),
|
||||
'tooltip': _('Comments for this element'),
|
||||
'length': 256,
|
||||
'order': 0 - 99,
|
||||
'order': 0 - 90,
|
||||
})
|
||||
if 'priority' in flds:
|
||||
self.addField(gui, {
|
||||
@@ -151,7 +159,7 @@ class BaseModelHandler(Handler):
|
||||
'required': True,
|
||||
'value': 1,
|
||||
'length': 4,
|
||||
'order': 0 - 97,
|
||||
'order': 0 - 85,
|
||||
})
|
||||
if 'small_name' in flds:
|
||||
self.addField(gui, {
|
||||
@@ -161,7 +169,7 @@ class BaseModelHandler(Handler):
|
||||
'tooltip': _('Label for this element'),
|
||||
'required': True,
|
||||
'length': 128,
|
||||
'order': 0 - 96,
|
||||
'order': 0 - 80,
|
||||
})
|
||||
|
||||
return gui
|
||||
@@ -246,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
|
||||
@@ -720,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
|
||||
|
@@ -1,7 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2012 Virtual Cable S.L.
|
||||
# Copyright (c) 2012-2016 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@@ -43,8 +43,9 @@ import ldap
|
||||
import ldap.filter
|
||||
import re
|
||||
import logging
|
||||
import six
|
||||
|
||||
__updated__ = '2016-04-18'
|
||||
__updated__ = '2017-10-05'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -67,6 +68,8 @@ class RegexLdap(auths.Authenticator):
|
||||
groupNameAttr = gui.TextField(length=640, label=_('Group Name Attr'), multiline=2, defvalue='cn', order=11, tooltip=_('Attribute that contains the group name'), required=True, tab=_('Ldap info'))
|
||||
# regex = gui.TextField(length=64, label = _('Regular Exp. for groups'), defvalue = '^(.*)', order = 12, tooltip = _('Regular Expression to extract the group name'), required = True)
|
||||
|
||||
altClass = gui.TextField(length=64, label=_('Alt. class'), defvalue='', order=20, tooltip=_('Class for LDAP objects that will be also checked for groups retrieval (normally empty)'), required=False, tab=_('Advanced'))
|
||||
|
||||
typeName = _('Regex LDAP Authenticator')
|
||||
typeType = 'RegexLdapAuthenticator'
|
||||
typeDescription = _('Regular Expressions LDAP authenticator')
|
||||
@@ -102,7 +105,7 @@ class RegexLdap(auths.Authenticator):
|
||||
self._groupNameAttr = values['groupNameAttr']
|
||||
# self._regex = values['regex']
|
||||
self._userNameAttr = values['userNameAttr']
|
||||
|
||||
self._altClass = values['altClass']
|
||||
else:
|
||||
self._host = None
|
||||
self._port = None
|
||||
@@ -116,6 +119,8 @@ class RegexLdap(auths.Authenticator):
|
||||
self._groupNameAttr = None
|
||||
# self._regex = None
|
||||
self._userNameAttr = None
|
||||
self._altClass = None
|
||||
|
||||
self._connection = None
|
||||
|
||||
def __validateField(self, field, fieldLabel):
|
||||
@@ -181,32 +186,43 @@ class RegexLdap(auths.Authenticator):
|
||||
'username': self._username, 'password': self._password, 'timeout': self._timeout,
|
||||
'ldapBase': self._ldapBase, 'userClass': self._userClass,
|
||||
'userIdAttr': self._userIdAttr, 'groupNameAttr': self._groupNameAttr,
|
||||
'userNameAttr': self._userNameAttr
|
||||
'userNameAttr': self._userNameAttr, 'altClass': self._altClass,
|
||||
}
|
||||
|
||||
def __str__(self):
|
||||
return "Ldap Auth: {0}:{1}@{2}:{3}, base = {4}, userClass = {5}, userIdAttr = {6}, groupNameAttr = {7}, userName attr = {8}".format(
|
||||
return "Ldap Auth: {}:{}@{}:{}, base = {}, userClass = {}, userIdAttr = {}, groupNameAttr = {}, userName attr = {}, altClass={}".format(
|
||||
self._username, self._password, self._host, self._port, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr,
|
||||
self._userNameAttr)
|
||||
self._userNameAttr, self._altClass)
|
||||
|
||||
def marshal(self):
|
||||
return '\t'.join([
|
||||
'v2',
|
||||
self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout,
|
||||
self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._userNameAttr
|
||||
'v3',
|
||||
self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password,
|
||||
self._timeout, self._ldapBase, self._userClass, self._userIdAttr,
|
||||
self._groupNameAttr, self._userNameAttr, self._altClass
|
||||
])
|
||||
|
||||
def unmarshal(self, val):
|
||||
data = val.split('\t')
|
||||
if data[0] == 'v1':
|
||||
logger.debug("Data: {0}".format(data[1:]))
|
||||
self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, _regex, self._userNameAttr = data[1:]
|
||||
self._host, self._port, self._ssl, self._username, self._password, \
|
||||
self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \
|
||||
self._groupNameAttr, _regex, self._userNameAttr = data[1:]
|
||||
self._ssl = gui.strToBool(self._ssl)
|
||||
self._groupNameAttr = self._groupNameAttr + '=' + _regex
|
||||
self._userNameAttr = '\n'.join(self._userNameAttr.split(','))
|
||||
elif data[0] == 'v2':
|
||||
logger.debug("Data v2: {0}".format(data[1:]))
|
||||
self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._userNameAttr = data[1:]
|
||||
self._host, self._port, self._ssl, self._username, self._password, \
|
||||
self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \
|
||||
self._groupNameAttr, self._userNameAttr = data[1:]
|
||||
self._ssl = gui.strToBool(self._ssl)
|
||||
elif data[0] == 'v3':
|
||||
logger.debug("Data v3: {0}".format(data[1:]))
|
||||
self._host, self._port, self._ssl, self._username, self._password, \
|
||||
self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \
|
||||
self._groupNameAttr, self._userNameAttr, self._altClass = data[1:]
|
||||
self._ssl = gui.strToBool(self._ssl)
|
||||
|
||||
def __connection(self, username=None, password=None):
|
||||
@@ -250,17 +266,52 @@ class RegexLdap(auths.Authenticator):
|
||||
|
||||
def __getUser(self, username):
|
||||
try:
|
||||
|
||||
con = self.__connection()
|
||||
filter_ = '(&(objectClass=%s)(%s=%s))' % (self._userClass, self._userIdAttr, ldap.filter.escape_filter_chars(username, 0))
|
||||
filter_ = b'(&(objectClass=%s)(%s=%s))' % (self._userClass, self._userIdAttr, ldap.filter.escape_filter_chars(username, 0))
|
||||
attrlist = [self._userIdAttr.encode('utf-8')] + self.__getAttrsFromField(self._userNameAttr) + self.__getAttrsFromField(self._groupNameAttr)
|
||||
|
||||
logger.debug('Getuser filter_: {0}, attr list: {1}'.format(filter_, attrlist))
|
||||
logger.debug('Getuser filter_: {}, attr list: {}'.format(filter_, attrlist))
|
||||
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE,
|
||||
filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)[0]
|
||||
|
||||
usr = dict((k, '') for k in attrlist)
|
||||
dct = {k.lower(): v for k, v in res[1].iteritems()}
|
||||
usr.update(dct)
|
||||
usr.update({'dn': res[0], '_id': username})
|
||||
|
||||
# If altClass
|
||||
if self._altClass is not None and self._altClass != '':
|
||||
logger.debug('Has alt class {}'.format(self._altClass))
|
||||
filter_ = b'(&(objectClass=%s)(%s=%s))' % (self._altClass, self._userIdAttr, ldap.filter.escape_filter_chars(username, 0))
|
||||
logger.debug('Get Alternate list filter: {}, attrlist: {}'.format(filter_, attrlist))
|
||||
# Get alternate class objects
|
||||
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE,
|
||||
filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)
|
||||
|
||||
for r in res:
|
||||
if r[0] is None:
|
||||
continue
|
||||
logger.debug('*** Item: {}'.format(r))
|
||||
|
||||
for k, v in six.iteritems(r[1]):
|
||||
kl = k.lower()
|
||||
# If already exists the field
|
||||
if kl in usr:
|
||||
# Convert existint to list, so we can add a new value
|
||||
if not isinstance(usr[kl], (list, tuple)):
|
||||
usr[kl] = [usr[kl]]
|
||||
|
||||
# Convert values to list, if not list
|
||||
if not isinstance(v, (list, tuple)):
|
||||
v = [v]
|
||||
|
||||
# Now append to existing values
|
||||
for x in v:
|
||||
usr[kl].append(x)
|
||||
else:
|
||||
usr[kl] = v
|
||||
|
||||
logger.debug('Usr: {0}'.format(usr))
|
||||
return usr
|
||||
except Exception:
|
||||
|
@@ -36,7 +36,7 @@ from django.utils.translation import ugettext as _
|
||||
from uds.core.ui.UserInterface import UserInterface
|
||||
from uds.core import Environmentable
|
||||
from uds.core import Serializable
|
||||
import base64
|
||||
from uds.core.util import encoders
|
||||
import os.path
|
||||
import sys
|
||||
import logging
|
||||
@@ -95,9 +95,6 @@ class Module(UserInterface, Environmentable, Serializable):
|
||||
module.
|
||||
'''
|
||||
# : Which coded to use to encode module by default.
|
||||
# : This overrides the Environmentable and Serializable Attribute, but in all cases we are using 'base64'
|
||||
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
|
||||
|
||||
# : Basic name used to provide the administrator an "huma readable" form for the module
|
||||
typeName = 'Base Module'
|
||||
# : Internal type name, used by system to locate this module
|
||||
@@ -174,7 +171,7 @@ class Module(UserInterface, Environmentable, Serializable):
|
||||
data = file_.read()
|
||||
file_.close()
|
||||
if inBase64 == True:
|
||||
return base64.encodestring(data)
|
||||
return encoders.encode(data, 'base64')
|
||||
else:
|
||||
return data
|
||||
|
||||
@@ -199,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)".
|
||||
@@ -226,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"
|
||||
@@ -268,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
|
||||
|
||||
|
@@ -32,6 +32,8 @@
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from uds.core.util import encoders
|
||||
|
||||
|
||||
class Serializable(object):
|
||||
'''
|
||||
@@ -42,8 +44,6 @@ class Serializable(object):
|
||||
- Initialize the object with default values
|
||||
- Read values from seralized data
|
||||
'''
|
||||
# Codify codec constant
|
||||
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
@@ -79,17 +79,11 @@ class Serializable(object):
|
||||
def serialize(self):
|
||||
'''
|
||||
Serializes and "obfuscates' the data.
|
||||
|
||||
The codec used to encode the string is obtained from the instance CODEC, so derived classes can
|
||||
overwrite this attribute to set another codec
|
||||
'''
|
||||
return self.marshal().encode(self.CODEC)
|
||||
return encoders.encode(self.marshal(), 'base64')
|
||||
|
||||
def unserialize(self, str_):
|
||||
'''
|
||||
des-obfuscates the data and then de-serializes it via unmarshal method
|
||||
|
||||
The codec used to decode the string is obtained from the instance CODEC, so derived classes can
|
||||
overwrite this attribute to set another codec
|
||||
'''
|
||||
return self.unmarshal(str_.decode(self.CODEC))
|
||||
return self.unmarshal(encoders.decode(str_, 'base64'))
|
||||
|
@@ -39,5 +39,5 @@ from uds.core.Environment import Environmentable
|
||||
from uds.core.Serializable import Serializable
|
||||
from uds.core.BaseModule import Module
|
||||
|
||||
VERSION = '2.1.0-DEVEL'
|
||||
VERSION_STAMP = '20161001-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__ = '2016-04-15'
|
||||
__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
|
||||
@@ -106,7 +106,9 @@ def webLoginRequired(admin=False):
|
||||
Decorator to set protection to access page
|
||||
Look for samples at uds.core.web.views
|
||||
'''
|
||||
|
||||
def decorator(view_func):
|
||||
|
||||
@wraps(view_func, assigned=available_attrs(view_func))
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
'''
|
||||
@@ -124,7 +126,9 @@ def webLoginRequired(admin=False):
|
||||
return HttpResponseForbidden(_('Forbidden'))
|
||||
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
return _wrapped_view
|
||||
|
||||
return decorator
|
||||
|
||||
|
||||
@@ -134,6 +138,7 @@ def trustedSourceRequired(view_func):
|
||||
Decorator to set protection to access page
|
||||
look for sample at uds.dispatchers.pam
|
||||
'''
|
||||
|
||||
@wraps(view_func)
|
||||
def _wrapped_view(request, *args, **kwargs):
|
||||
'''
|
||||
@@ -143,6 +148,7 @@ def trustedSourceRequired(view_func):
|
||||
if net.ipInNetwork(request.ip, GlobalConfig.TRUSTED_SOURCES.get(True)) is False:
|
||||
return HttpResponseForbidden()
|
||||
return view_func(request, *args, **kwargs)
|
||||
|
||||
return _wrapped_view
|
||||
|
||||
|
||||
@@ -159,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)
|
||||
@@ -201,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)
|
||||
@@ -253,7 +262,7 @@ def authInfoUrl(authenticator):
|
||||
Helper method, so we can get the info url for an authenticator
|
||||
'''
|
||||
from django.core.urlresolvers import reverse
|
||||
if isinstance(authenticator, unicode) or isinstance(authenticator, str):
|
||||
if isinstance(authenticator, (six.text_type, six.binary_type)):
|
||||
name = authenticator
|
||||
else:
|
||||
name = authenticator.name
|
||||
@@ -279,9 +288,9 @@ 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, get_language(), user.is_admin, user.staff_member)
|
||||
REST.Handler.storeSessionAuthdata(request.session, manager_id, user.name, password, get_language(), user.is_admin, user.staff_member, cookie)
|
||||
return True
|
||||
|
||||
|
||||
@@ -293,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):
|
||||
@@ -331,7 +340,7 @@ def authLogLogin(request, authenticator, userName, logStr=''):
|
||||
try:
|
||||
user = authenticator.users.get(name=userName)
|
||||
log.doLog(user, level,
|
||||
'{0} from {1} where os is {3}'.format(logStr, request.ip, request.os['OS']), log.WEB
|
||||
'{} from {} where OS is {}'.format(logStr, request.ip, request.os['OS']), log.WEB
|
||||
)
|
||||
except Exception:
|
||||
pass
|
||||
|
@@ -37,6 +37,7 @@ from django.db.models import Q
|
||||
from uds.models import DelayedTask as dbDelayedTask
|
||||
from uds.models import getSqlDatetime
|
||||
from uds.core.Environment import Environment
|
||||
from uds.core.util import encoders
|
||||
from socket import gethostname
|
||||
from pickle import loads, dumps
|
||||
from datetime import timedelta
|
||||
@@ -44,7 +45,7 @@ import threading
|
||||
import time
|
||||
import logging
|
||||
|
||||
__updated__ = '2016-03-07'
|
||||
__updated__ = '2018-03-02'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -53,6 +54,7 @@ class DelayedTaskThread(threading.Thread):
|
||||
'''
|
||||
Class responsible of executing a delayed task in its own thread
|
||||
'''
|
||||
|
||||
def __init__(self, taskInstance):
|
||||
super(DelayedTaskThread, self).__init__()
|
||||
self._taskInstance = taskInstance
|
||||
@@ -68,7 +70,6 @@ class DelayedTaskRunner(object):
|
||||
'''
|
||||
Delayed task runner class
|
||||
'''
|
||||
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
|
||||
# How often tasks r checked
|
||||
granularity = 2
|
||||
|
||||
@@ -106,7 +107,9 @@ class DelayedTaskRunner(object):
|
||||
try:
|
||||
with transaction.atomic(): # Encloses
|
||||
task = dbDelayedTask.objects.select_for_update().filter(filt).order_by('execution_time')[0] # @UndefinedVariable
|
||||
taskInstanceDump = task.instance.decode(self.CODEC)
|
||||
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)
|
||||
except Exception:
|
||||
@@ -123,7 +126,7 @@ class DelayedTaskRunner(object):
|
||||
now = getSqlDatetime()
|
||||
exec_time = now + timedelta(seconds=delay)
|
||||
cls = instance.__class__
|
||||
instanceDump = dumps(instance).encode(self.CODEC)
|
||||
instanceDump = encoders.encode(dumps(instance), 'base64')
|
||||
typeName = str(cls.__module__ + '.' + cls.__name__)
|
||||
|
||||
logger.debug('Inserting delayed task {0} with {1} bytes ({2})'.format(typeName, len(instanceDump), exec_time))
|
||||
|
@@ -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
|
||||
|
@@ -33,14 +33,15 @@
|
||||
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 codecs
|
||||
import struct
|
||||
import random
|
||||
import string
|
||||
|
||||
@@ -57,8 +58,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class CryptoManager(object):
|
||||
CODEC = 'base64'
|
||||
|
||||
instance = None
|
||||
|
||||
def __init__(self):
|
||||
@@ -66,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:
|
||||
@@ -77,7 +93,7 @@ class CryptoManager(object):
|
||||
value = value.encode('utf-8')
|
||||
|
||||
atfork()
|
||||
return six.text_type(codecs.encode(self._rsa.encrypt(value, six.b(''))[0], CryptoManager.CODEC))
|
||||
return encoders.encode((self._rsa.encrypt(value, six.b(''))[0]), 'base64', asText=True)
|
||||
|
||||
def decrypt(self, value):
|
||||
if isinstance(value, six.text_type):
|
||||
@@ -85,12 +101,34 @@ class CryptoManager(object):
|
||||
# import inspect
|
||||
try:
|
||||
atfork()
|
||||
return six.text_type(self._rsa.decrypt(value.decode(CryptoManager.CODEC)).decode('utf-8'))
|
||||
return six.text_type(self._rsa.decrypt(encoders.decode(value, 'base64')).decode('utf-8'))
|
||||
except Exception:
|
||||
logger.exception('Decripting: {0}'.format(value))
|
||||
# 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')
|
||||
@@ -102,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)
|
||||
@@ -134,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):
|
||||
@@ -142,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-03-22'
|
||||
__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:
|
||||
@@ -428,17 +456,44 @@ class UserServiceManager(object):
|
||||
|
||||
# All done
|
||||
|
||||
def requestLogoff(self, uService):
|
||||
'''
|
||||
Ask client to logoff user
|
||||
'''
|
||||
url = uService.getCommsUrl()
|
||||
if url is None:
|
||||
logger.error('Can\'t connect with actor (no actor or legacy actor)')
|
||||
return
|
||||
url += '/logoff'
|
||||
|
||||
try:
|
||||
r = requests.post(url, data=json.dumps({}), headers={'content-type': 'application/json'}, verify=False, timeout=4)
|
||||
r = json.loads(r.content)
|
||||
logger.debug('Sent logoff to client using {}: {}'.format(url, r))
|
||||
# In fact we ignore result right now
|
||||
except Exception as e:
|
||||
# TODO: Right now, this is an "experimental" feature, not supported on Apps (but will)
|
||||
pass
|
||||
# logger.info('Logoff requested but service was not listening: {}'.format(e, url))
|
||||
|
||||
# All done
|
||||
|
||||
def checkForRemoval(self, uService):
|
||||
'''
|
||||
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:
|
||||
@@ -449,35 +504,48 @@ 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))
|
||||
UserService.setState(State.ERROR)
|
||||
uService.setState(State.ERROR)
|
||||
return
|
||||
|
||||
def getService(self, user, srcIp, idService, idTransport, doTest=True):
|
||||
'''
|
||||
Get service info from
|
||||
'''
|
||||
def locateUserService(self, user, idService, create=False):
|
||||
kind, idService = idService[0], idService[1:]
|
||||
|
||||
logger.debug('Kind of service: {0}, idService: {1}'.format(kind, idService))
|
||||
userService = None
|
||||
|
||||
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)
|
||||
# We first do a sanity check for this, if the user has access to this service
|
||||
# If it fails, will raise an exception
|
||||
ds.validateUser(user)
|
||||
|
||||
# Now we have to locate an instance of the service, so we can assign it to user.
|
||||
userService = self.getAssignationForUser(ds, user)
|
||||
if create: # getAssignation, if no assignation is found, tries to create one
|
||||
userService = self.getAssignationForUser(ds, user)
|
||||
else: # Sometimes maybe we only need to locate the existint user service
|
||||
userService = self.getExistingAssignationForUser(ds, user)
|
||||
|
||||
logger.debug('Found service: {0}'.format(userService))
|
||||
return userService
|
||||
|
||||
def getService(self, user, srcIp, idService, idTransport, doTest=True):
|
||||
'''
|
||||
Get service info from
|
||||
'''
|
||||
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()
|
||||
@@ -502,17 +570,17 @@ 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
|
||||
|
||||
|
||||
if doTest is False:
|
||||
# traceLogger.info('GOT service "{}" for user "{}" with transport "{}" (NOT TESTED)'.format(userService.name, userName, trans.name))
|
||||
return (None, userService, None, trans, None)
|
||||
|
||||
|
||||
serviceNotReadyCode = 0x0001
|
||||
ip = 'unknown'
|
||||
# Test if the service is ready
|
||||
@@ -522,6 +590,7 @@ class UserServiceManager(object):
|
||||
# If ready, show transport for this service, if also ready ofc
|
||||
iads = userService.getInstance()
|
||||
ip = iads.getIp()
|
||||
userService.logIP(ip) # Update known ip
|
||||
|
||||
if self.checkUuid(userService) is False: # Machine is not what is expected
|
||||
serviceNotReadyCode = 0x0004
|
||||
@@ -533,14 +602,15 @@ 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))
|
||||
return (ip, userService, iads, trans, itrans)
|
||||
else:
|
||||
log.doLog(userService, log.WARN, "User service is not accessible (ip {0})".format(ip), log.TRANSPORT)
|
||||
logger.debug('Transport is not ready for user service {0}'.format(userService))
|
||||
message = itrans.getCustomAvailableErrorMsg(userService, ip)
|
||||
log.doLog(userService, log.WARN, message, log.TRANSPORT)
|
||||
logger.debug('Transport is not ready for user service {}: {}'.format(userService, message))
|
||||
else:
|
||||
logger.debug('Ip not available from user service {0}'.format(userService))
|
||||
else:
|
||||
|
@@ -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__ = '2016-10-03'
|
||||
__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:
|
||||
@@ -204,17 +202,20 @@ class OSManager(Module):
|
||||
knownUserIP = userService.src_ip + ':' + userService.src_hostname
|
||||
knownUserIP = knownUserIP if knownUserIP != ':' else 'unknown'
|
||||
|
||||
if userName is None:
|
||||
userName = 'unknown'
|
||||
|
||||
addEvent(userService.deployed_service, ET_LOGIN, fld1=userName, fld2=knownUserIP, fld3=serviceIp, fld4=fullUserName)
|
||||
|
||||
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):
|
||||
'''
|
||||
@@ -247,14 +248,24 @@ class OSManager(Module):
|
||||
knownUserIP = userService.src_ip + ':' + userService.src_hostname
|
||||
knownUserIP = knownUserIP if knownUserIP != ':' else 'unknown'
|
||||
|
||||
if userName is None:
|
||||
userName = 'unknown'
|
||||
|
||||
addEvent(userService.deployed_service, ET_LOGOUT, fld1=userName, fld2=knownUserIP, fld3=serviceIp, fld4=fullUserName)
|
||||
|
||||
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"
|
||||
|
@@ -35,13 +35,15 @@ from __future__ import unicode_literals
|
||||
from django.utils.translation import ugettext, ugettext_noop as _
|
||||
|
||||
from uds.core.ui.UserInterface import UserInterface
|
||||
from uds.core.util import encoders
|
||||
|
||||
import datetime
|
||||
import six
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__updated__ = '2015-06-21'
|
||||
__updated__ = '2017-11-15'
|
||||
|
||||
|
||||
class Report(UserInterface):
|
||||
@@ -126,7 +128,7 @@ class Report(UserInterface):
|
||||
'''
|
||||
data = self.generate()
|
||||
if self.encoded:
|
||||
return data.encode('base64').replace('\n', '')
|
||||
return encoders.encode(data, 'base64', asText=True).replace('\n', '')
|
||||
else:
|
||||
return data
|
||||
|
||||
|
@@ -36,7 +36,7 @@ from uds.core import Environmentable
|
||||
from uds.core import Serializable
|
||||
from uds.core.util.State import State
|
||||
|
||||
__updated__ = '2016-02-26'
|
||||
__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):
|
||||
|
@@ -37,16 +37,18 @@ from django.utils.translation import ugettext_noop as _
|
||||
from uds.core.util import OsDetector
|
||||
from uds.core import Module
|
||||
from uds.core.transports import protocols
|
||||
from uds.core.util import encoders
|
||||
|
||||
import logging
|
||||
|
||||
__updated__ = '2017-08-02'
|
||||
__updated__ = '2017-11-15'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
DIRECT_GROUP = _('Direct')
|
||||
TUNNELED_GROUP = _('Tunneled')
|
||||
|
||||
|
||||
class Transport(Module):
|
||||
'''
|
||||
An OS Manager is responsible for communication the service the different actions to take (i.e. adding a windows machine to a domain)
|
||||
@@ -113,6 +115,13 @@ class Transport(Module):
|
||||
'''
|
||||
return False
|
||||
|
||||
def getCustomAvailableErrorMsg(self, userService, ip):
|
||||
'''
|
||||
Returns a customized error message, that will be used when a service fails to check "isAvailableFor"
|
||||
Override this in yours transports if needed
|
||||
'''
|
||||
return "Not accessible (using service ip {0})".format(ip)
|
||||
|
||||
@classmethod
|
||||
def supportsProtocol(cls, protocol):
|
||||
if isinstance(protocol, (list, tuple)):
|
||||
@@ -181,6 +190,14 @@ from __future__ import unicode_literals
|
||||
raise Exception('The transport {transport.name} is not supported on your platform.')
|
||||
'''.format(service=userService, transport=transport)
|
||||
|
||||
def getEncodedTransportScript(self, userService, transport, ip, os, user, password, request):
|
||||
"""
|
||||
Encodes the script so the client can understand it
|
||||
"""
|
||||
script = self.getUDSTransportScript(userService, transport, ip, os, user, password, request)
|
||||
logger.debug('Transport script: {}'.format(script))
|
||||
return encoders.encode(encoders.encode(script, 'bz2'), 'base64', asText=True).replace('\n', '')
|
||||
|
||||
def getLink(self, userService, transport, ip, os, user, password, request):
|
||||
'''
|
||||
Must override if transport does provides its own link
|
||||
|
@@ -87,6 +87,7 @@ class gui(object):
|
||||
PARAMETERS_TAB = ugettext_noop('Parameters')
|
||||
CREDENTIALS_TAB = ugettext_noop('Credentials')
|
||||
TUNNEL_TAB = ugettext_noop('Tunnel')
|
||||
DISPLAY_TAB = ugettext_noop('Display')
|
||||
|
||||
# : Static Callbacks simple registry
|
||||
callbacks = {}
|
||||
@@ -347,6 +348,7 @@ class gui(object):
|
||||
tooltip = _('Other info'), rdonly = True)
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, **options):
|
||||
super(self.__class__, self).__init__(**options)
|
||||
self._type(gui.InputField.TEXT_TYPE)
|
||||
@@ -375,6 +377,7 @@ class gui(object):
|
||||
defvalue = '443', order = 1, tooltip = _('Port (usually 443)'),
|
||||
required = True)
|
||||
'''
|
||||
|
||||
def __init__(self, **options):
|
||||
super(self.__class__, self).__init__(**options)
|
||||
minValue = options.get('minValue', '987654321')
|
||||
@@ -463,6 +466,7 @@ class gui(object):
|
||||
required = True)
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, **options):
|
||||
super(self.__class__, self).__init__(**options)
|
||||
self._type(gui.InputField.PASSWORD_TYPE)
|
||||
@@ -498,6 +502,7 @@ class gui(object):
|
||||
self.hidden.setDefValue(self.parent().serialize())
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, **options):
|
||||
super(self.__class__, self).__init__(**options)
|
||||
self._isSerializable = options.get('serializable', '') != ''
|
||||
@@ -525,6 +530,7 @@ class gui(object):
|
||||
tooltip = _('If checked, will use a ssl connection'))
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, **options):
|
||||
super(self.__class__, self).__init__(**options)
|
||||
self._type(gui.InputField.CHECKBOX_TYPE)
|
||||
@@ -628,6 +634,7 @@ class gui(object):
|
||||
ev = gui.HiddenField() # ....
|
||||
|
||||
'''
|
||||
|
||||
def __init__(self, **options):
|
||||
super(self.__class__, self).__init__(**options)
|
||||
self._data['values'] = options.get('values', [])
|
||||
@@ -647,6 +654,7 @@ class gui(object):
|
||||
self._data['values'] = values
|
||||
|
||||
class ImageChoiceField(InputField):
|
||||
|
||||
def __init__(self, **options):
|
||||
super(self.__class__, self).__init__(**options)
|
||||
self._data['values'] = options.get('values', [])
|
||||
@@ -692,6 +700,7 @@ class gui(object):
|
||||
{'id': '1', 'text': 'datastore1' } ]
|
||||
)
|
||||
'''
|
||||
|
||||
def __init__(self, **options):
|
||||
super(self.__class__, self).__init__(**options)
|
||||
self._data['values'] = options.get('values', [])
|
||||
@@ -750,6 +759,7 @@ class gui(object):
|
||||
'''
|
||||
Image field
|
||||
'''
|
||||
|
||||
def __init__(self, **options):
|
||||
super(self.__class__, self).__init__(**options)
|
||||
self._type(gui.InputField.TEXT_TYPE)
|
||||
@@ -758,6 +768,7 @@ class gui(object):
|
||||
'''
|
||||
Informational field (no input is done)
|
||||
'''
|
||||
|
||||
def __init__(self, **options):
|
||||
super(self.__class__, self).__init__(**options)
|
||||
self._type(gui.InputField.INFO_TYPE)
|
||||
@@ -768,6 +779,7 @@ class UserInterfaceType(type):
|
||||
Metaclass definition for moving the user interface descriptions to a usable
|
||||
better place
|
||||
'''
|
||||
|
||||
def __new__(cls, classname, bases, classDict):
|
||||
newClassDict = {}
|
||||
_gui = {}
|
||||
|
@@ -32,6 +32,7 @@
|
||||
'''
|
||||
|
||||
from uds.core.Serializable import Serializable
|
||||
from uds.core.util import encoders
|
||||
import pickle
|
||||
import timeit
|
||||
|
||||
@@ -67,11 +68,6 @@ class AutoAttributes(Serializable):
|
||||
Access attrs as "self._attr1, self._attr2"
|
||||
'''
|
||||
|
||||
# : This codec is not intended to override Serializable codec
|
||||
# : Serializable codec is for encoding marshaled data,
|
||||
# : while this codec is for encoding pickled data from autoattributes
|
||||
ACODEC = 'zip'
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
self.declare(**kwargs)
|
||||
|
||||
@@ -93,13 +89,17 @@ class AutoAttributes(Serializable):
|
||||
self.dict = d
|
||||
|
||||
def marshal(self):
|
||||
return '\2'.join(['%s\1%s' % (k, pickle.dumps(v)) for k, v in self.dict.iteritems()]).encode(AutoAttributes.ACODEC)
|
||||
return encoders.encode('\2'.join(['%s\1%s' % (k, pickle.dumps(v)) for k, v in self.dict.iteritems()]), 'bz2')
|
||||
|
||||
def unmarshal(self, data):
|
||||
if data == '': # Can be empty
|
||||
return
|
||||
# We keep original data (maybe incomplete)
|
||||
for pair in data.decode(AutoAttributes.ACODEC).split('\2'):
|
||||
try:
|
||||
data = encoders.decode(data, 'bz2')
|
||||
except Exception: # With old zip encoding
|
||||
data = encoders.decode(data, 'zip')
|
||||
for pair in data.split('\2'):
|
||||
k, v = pair.split('\1')
|
||||
self.dict[k] = pickle.loads(str(v))
|
||||
|
||||
|
@@ -34,6 +34,7 @@ from __future__ import unicode_literals
|
||||
from django.db import transaction
|
||||
import uds.models.Cache
|
||||
from uds.models import getSqlDatetime
|
||||
from uds.core.util import encoders
|
||||
from datetime import datetime, timedelta
|
||||
import hashlib
|
||||
import logging
|
||||
@@ -47,7 +48,6 @@ class Cache(object):
|
||||
misses = 0
|
||||
|
||||
DEFAULT_VALIDITY = 60
|
||||
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
|
||||
|
||||
def __init__(self, owner):
|
||||
self._owner = owner.encode('utf-8')
|
||||
@@ -67,13 +67,17 @@ class Cache(object):
|
||||
expired = now > c.created + timedelta(seconds=c.validity)
|
||||
if expired:
|
||||
return defValue
|
||||
val = pickle.loads(c.value.decode(Cache.CODEC))
|
||||
val = pickle.loads(encoders.decode(c.value, 'base64'))
|
||||
Cache.hits += 1
|
||||
return val
|
||||
except uds.models.Cache.DoesNotExist: # @UndefinedVariable
|
||||
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):
|
||||
'''
|
||||
@@ -97,7 +101,7 @@ class Cache(object):
|
||||
if validity is None:
|
||||
validity = Cache.DEFAULT_VALIDITY
|
||||
key = self.__getKey(skey)
|
||||
value = pickle.dumps(value).encode(Cache.CODEC)
|
||||
value = encoders.encode(pickle.dumps(value), 'base64')
|
||||
now = getSqlDatetime()
|
||||
try:
|
||||
uds.models.Cache.objects.create(owner=self._owner, key=key, value=value, created=now, validity=validity) # @UndefinedVariable
|
||||
@@ -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
|
||||
@@ -50,6 +52,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FileStorage(Storage):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._base_url = getattr(settings, 'FILE_STORAGE', '/files')
|
||||
if self._base_url[-1] != '/':
|
||||
@@ -60,6 +63,7 @@ class FileStorage(Storage):
|
||||
try:
|
||||
cache = caches[cacheName]
|
||||
except:
|
||||
logger.info('No cache for FileStorage configured.')
|
||||
cache = None
|
||||
|
||||
self.cache = cache
|
||||
@@ -73,7 +77,6 @@ class FileStorage(Storage):
|
||||
|
||||
Storage.__init__(self, *args, **kwargs)
|
||||
|
||||
|
||||
def get_valid_name(self, name):
|
||||
return name.replace('\\', os.path.sep)
|
||||
|
||||
@@ -119,7 +122,6 @@ class FileStorage(Storage):
|
||||
return
|
||||
self.cache.delete(self._getKey(name))
|
||||
|
||||
|
||||
def _open(self, name, mode='rb'):
|
||||
f = six.BytesIO(self._dbFileForReadOnly(name).data)
|
||||
f.name = name
|
||||
@@ -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
|
||||
@@ -173,7 +177,9 @@ class FileStorage(Storage):
|
||||
except DBFile.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
class CompressorFileStorage(FileStorage):
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
FileStorage.__init__(self, *args, **dict(kwargs, owner='compressor'))
|
||||
|
||||
|
@@ -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))
|
||||
|
||||
|
@@ -34,6 +34,7 @@ from __future__ import unicode_literals
|
||||
|
||||
from django.db import transaction
|
||||
from uds.models import Storage as dbStorage
|
||||
from uds.core.util import encoders
|
||||
import hashlib
|
||||
import logging
|
||||
import pickle
|
||||
@@ -42,7 +43,6 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Storage(object):
|
||||
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
|
||||
|
||||
def __init__(self, owner):
|
||||
self._owner = owner.encode('utf-8')
|
||||
@@ -57,7 +57,7 @@ class Storage(object):
|
||||
key = self.__getKey(skey)
|
||||
if isinstance(data, unicode):
|
||||
data = data.encode('utf-8')
|
||||
data = data.encode(Storage.CODEC)
|
||||
data = encoders.encode(data, 'base64')
|
||||
attr1 = '' if attr1 is None else attr1
|
||||
try:
|
||||
dbStorage.objects.create(owner=self._owner, key=key, data=data, attr1=attr1) # @UndefinedVariable
|
||||
@@ -79,7 +79,7 @@ class Storage(object):
|
||||
key = self.__getKey(skey)
|
||||
logger.debug('Accesing to {0} {1}'.format(skey, key))
|
||||
c = dbStorage.objects.get(pk=key) # @UndefinedVariable
|
||||
val = c.data.decode(Storage.CODEC)
|
||||
val = encoders.decode(c.data, 'base64')
|
||||
|
||||
if fromPickle:
|
||||
return val
|
||||
@@ -103,7 +103,7 @@ class Storage(object):
|
||||
|
||||
def getPickleByAttr1(self, attr1):
|
||||
try:
|
||||
return pickle.loads(dbStorage.objects.get(owner=self._owner, attr1=attr1).data.decode(Storage.CODEC)) # @UndefinedVariable
|
||||
return pickle.loads(encoders.decode(dbStorage.objects.filter(owner=self._owner, attr1=attr1)[0].data, 'base64')) # @UndefinedVariable
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
@@ -133,7 +133,7 @@ class Storage(object):
|
||||
query = dbStorage.objects.filter(owner=self._owner, attr1=attr1) # @UndefinedVariable
|
||||
|
||||
for v in query:
|
||||
yield v.data.decode(Storage.CODEC)
|
||||
yield encoders.decode(v.data, 'base64')
|
||||
|
||||
def filter(self, attr1):
|
||||
if attr1 is None:
|
||||
@@ -142,7 +142,7 @@ class Storage(object):
|
||||
query = dbStorage.objects.filter(owner=self._owner, attr1=attr1) # @UndefinedVariable
|
||||
|
||||
for v in query: # @UndefinedVariable
|
||||
yield (v.key, v.data.decode(Storage.CODEC), v.attr1)
|
||||
yield (v.key, encoders.decode(v.data, 'base64'), v.attr1)
|
||||
|
||||
def filterPickle(self, attr1=None):
|
||||
for v in self.filter(attr1):
|
||||
@@ -150,7 +150,6 @@ class Storage(object):
|
||||
|
||||
@staticmethod
|
||||
def delete(owner=None):
|
||||
logger.info("Deleting storage items")
|
||||
if owner is None:
|
||||
objects = dbStorage.objects.all() # @UndefinedVariable
|
||||
else:
|
||||
|
@@ -46,8 +46,7 @@ import six
|
||||
import bitarray
|
||||
import logging
|
||||
|
||||
__updated__ = '2016-10-31'
|
||||
|
||||
__updated__ = '2017-12-12'
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -113,14 +112,14 @@ class CalendarChecker(object):
|
||||
|
||||
return data
|
||||
|
||||
|
||||
def _updateEvents(self, checkFrom, startEvent=True):
|
||||
|
||||
next_event = None
|
||||
for rule in self.calendar.rules.all():
|
||||
if rule.start > checkFrom or (rule.end is not None and rule.end < checkFrom.date()):
|
||||
# logger.debug('RULE: start = {}, checkFrom = {}, end'.format(rule.start.date(), checkFrom.date()))
|
||||
if rule.end is not None and rule.end < checkFrom.date():
|
||||
continue
|
||||
|
||||
# logger.debug('Rule in check interval...')
|
||||
if startEvent:
|
||||
event = rule.as_rrule().after(checkFrom) # At start
|
||||
else:
|
||||
@@ -162,7 +161,7 @@ class CalendarChecker(object):
|
||||
Returns next event for this interval
|
||||
Returns a list of two elements. First is datetime of event begining, second is timedelta of duration
|
||||
'''
|
||||
logger.debug('Obtainint nextEvent')
|
||||
logger.debug('Obtaining nextEvent')
|
||||
if checkFrom is None:
|
||||
checkFrom = getSqlDatetime()
|
||||
|
||||
@@ -173,7 +172,7 @@ class CalendarChecker(object):
|
||||
next_event = CalendarChecker.cache.get(cacheKey, None)
|
||||
if next_event is None:
|
||||
logger.debug('Regenerating cached nextEvent')
|
||||
next_event = self._updateEvents(checkFrom - offset, startEvent) # We substract on checkin, so we can take into account for next execution the "offset" on start & end (just the inverse of current, so we substract it)
|
||||
next_event = self._updateEvents(checkFrom + offset, startEvent) # We substract on checkin, so we can take into account for next execution the "offset" on start & end (just the inverse of current, so we substract it)
|
||||
if next_event is not None:
|
||||
next_event += offset
|
||||
CalendarChecker.cache.put(cacheKey, next_event, 3600)
|
||||
@@ -183,7 +182,5 @@ class CalendarChecker(object):
|
||||
|
||||
return next_event
|
||||
|
||||
|
||||
def debug(self):
|
||||
|
||||
return "Calendar checker for {}".format(self.calendar)
|
||||
|
@@ -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
|
||||
|
||||
|
55
server/src/uds/core/util/encoders.py
Normal file
55
server/src/uds/core/util/encoders.py
Normal file
@@ -0,0 +1,55 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2017 Virtual Cable S.L.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||
# this list of conditions and the following disclaimer in the documentation
|
||||
# and/or other materials provided with the distribution.
|
||||
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
from __future__ import unicode_literals
|
||||
import six
|
||||
import codecs
|
||||
|
||||
|
||||
def __toBinary(data):
|
||||
if isinstance(data, six.text_type):
|
||||
return data.encode('utf8')
|
||||
return data
|
||||
|
||||
|
||||
def encode(data, encoder, asText=False):
|
||||
res = codecs.encode(__toBinary(data), encoder)
|
||||
if asText:
|
||||
return res.decode('utf8')
|
||||
return res
|
||||
|
||||
|
||||
def decode(data, encoder, asText=False):
|
||||
res = codecs.decode(__toBinary(data), encoder)
|
||||
if asText:
|
||||
return res.decode('utf8')
|
||||
return res
|
@@ -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):
|
||||
|
@@ -27,6 +27,8 @@
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from __future__ import unicode_literals
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
from django.http import HttpResponseRedirect
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -42,3 +44,31 @@ class XUACompatibleMiddleware(object):
|
||||
if response.get('content-type', '').startswith('text/html'):
|
||||
response['X-UA-Compatible'] = 'IE=edge'
|
||||
return response
|
||||
|
||||
class RedirectMiddleware(object):
|
||||
NO_REDIRECT = [
|
||||
'rest',
|
||||
'pam',
|
||||
'guacamole',
|
||||
]
|
||||
|
||||
def process_request(self, request):
|
||||
full_path = request.get_full_path()
|
||||
redirect = True
|
||||
for nr in RedirectMiddleware.NO_REDIRECT:
|
||||
if full_path.startswith('/' + nr):
|
||||
redirect = False
|
||||
break
|
||||
|
||||
if GlobalConfig.REDIRECT_TO_HTTPS.getBool() and request.is_secure() is False and redirect:
|
||||
if request.method == 'POST':
|
||||
url = request.build_absolute_uri(GlobalConfig.LOGIN_URL.get())
|
||||
else:
|
||||
url = request.build_absolute_uri(full_path)
|
||||
url = url.replace('http://', 'https://')
|
||||
|
||||
return HttpResponseRedirect(url)
|
||||
|
||||
@staticmethod
|
||||
def registerException(path):
|
||||
RedirectMiddleware.NO_REDIRECT.append(path)
|
||||
|
@@ -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):
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user