Compare commits

..

201 Commits
2.2 ... v2.2

Author SHA1 Message Date
Adolfo Gómez García
1a18ff1d5e Changed "/multimedia" for "/video" 2020-06-17 10:16:48 +02:00
Adolfo Gómez García
56553a70a1 minor fixes for 2.2 version 2020-05-26 21:29:39 +02:00
Adolfo Gómez García
2c10e9445b fixed actor log level 2020-05-15 13:56:02 +02:00
Adolfo Gómez García
4ca387c370 Fixed log level for actor 2020-05-15 12:34:59 +02:00
Adolfo Gómez García
67eb4332b7 fixed logger.info issue 2020-05-13 13:47:12 +02:00
Adolfo Gómez García
a6077a61b4 fixed x2go app closing too early 2020-05-13 13:32:16 +02:00
Adolfo Gómez García
dc42433b86 fixed operations getidle for linux 2020-05-12 14:20:35 +02:00
Adolfo Gómez García
f67730cfad fixed Publication manager error on full persistent services 2020-04-20 10:18:45 +02:00
Adolfo Gómez García
8b8135ea98 Fixed xen SR error 2020-04-16 10:10:59 +02:00
Adolfo Gómez García
07c304cecb small fix on REST (visual) 2020-03-19 13:52:10 +01:00
Adolfo Gómez García
a3dd038e79 Testing clients 2020-03-04 15:24:17 +01:00
Adolfo Gómez García
f3522ae154 Updating udsclient 2020-03-04 15:17:43 +01:00
Adolfo Gómez García
67feb4aefa Fixed operations for getidle 2019-12-04 15:00:10 +01:00
Adolfo Gómez García
5953a67d3a Fixed support for deathkey on UDS 2019-10-14 09:21:27 +02:00
Adolfo Gómez García
c6f3c87b93 Fixed fastlinks for owner. Thanks to Daniel Torregosa for the fix :) 2019-09-11 11:10:01 +02:00
Adolfo Gómez García
eb270d883e Fixed OpenNebula "weird". We were defining "VmState" Module, and after that, redefining by importing common. Removed initial definition 2019-08-16 11:12:41 +02:00
Adolfo Gómez García
5ddd2519b0 fix for OpenNebula 2019-08-01 14:43:17 +02:00
Adolfo Gómez García
e5574fe2ba fix for newer opennebula settiong image to "non persistent" on publication clone 2019-07-25 11:47:46 +02:00
Adolfo Gómez García
4b9b386f14 fixed authorize of x2go for python2/3 compat 2019-07-10 11:30:04 +02:00
Adolfo Gómez García
8a61986dad fixed httpserver for python3 compat 2019-07-09 13:25:50 +02:00
Adolfo Gómez García
d068bcef60 fixed os_state on backend 2019-05-21 15:36:35 +02:00
Adolfo Gómez García
4c001b044f security for PAM 2019-05-14 08:33:58 +02:00
Adolfo Gómez García
70141ae972 fixing up pam_http 2019-05-13 17:11:12 +02:00
Adolfo Gómez García
20ead2954b fixing up pam_http 2019-05-13 16:56:19 +02:00
Adolfo Gómez García
8331c886de Set "tbr" to none always. It's a non useful parameter 2019-05-10 10:10:36 +02:00
Adolfo Gómez García
66c217a988 Backport of random fixes 2019-05-10 09:56:07 +02:00
Adolfo Gómez García
d6935d0210 fixed cache 2 -> 1 2019-05-08 11:34:55 +02:00
Adolfo Gómez García
c47501e3e6 Fixed guacamole audio & video 2019-05-06 21:38:32 +02:00
Adolfo Gómez García
7a2bd7dcf5 actor fix 2019-04-15 11:49:12 +02:00
Adolfo Gómez García
87e7f1ca3c fixed double removal check 2019-04-03 21:50:00 +02:00
Adolfo Gómez García
ea11a25c52 Final fix 2019-03-28 12:17:27 +01:00
Adolfo Gómez García
f79f659711 Added so if ipc is closed, and logout is sent (if already logged in ofc) 2019-03-28 11:58:28 +01:00
Adolfo Gómez García
fddc69e0ea Fixed UDS actor postins 2019-03-22 10:36:35 +01:00
Adolfo Gómez García
b795260e4d Fixed access from MAC 2019-03-06 10:08:04 +01:00
Adolfo Gómez García
66464df043 small fix. Now checks the ownership of a machine before letting an user access it 2019-02-26 16:50:12 +01:00
Adolfo Gómez García
5d4e05309a re-added simple ldap 2019-02-26 12:54:16 +01:00
Adolfo Gómez García
a288dd4df3 Removed optimizes (nonsense with innodb) 2019-02-26 12:27:41 +01:00
Adolfo Gómez García
6156f2c6f6 Fix 2019-02-25 10:08:37 +01:00
Adolfo Gómez García
b1f3407299 Fixes 2019-02-24 11:47:50 +01:00
Adolfo Gómez García
2b653b8b05 upgrades 2019-02-24 11:30:44 +01:00
Adolfo Gómez García
cd7beac87e Fixes 2019-02-24 11:24:54 +01:00
Adolfo Gómez García
dbee05d1bc More fixes 2019-02-24 10:59:47 +01:00
Adolfo Gómez García
9f01f2c368 Fix 2019-02-24 10:40:02 +01:00
Adolfo Gómez García
9e26580af5 Fixed several concurrency issues (very rare, btw, but possible :) ) 2019-02-24 10:16:50 +01:00
Adolfo Gómez García
689b20ce2f A few more fixes for concurrency 2019-02-22 16:29:22 +01:00
Adolfo Gómez García
bac0b9755e Improved concurrency check on cache assignation 2019-02-22 15:37:42 +01:00
Adolfo Gómez García
33d2ca4ece Improved concurrency 2019-02-22 15:23:40 +01:00
Adolfo Gómez García
efb3965d76 Fixed calendar action can create "empty" actions 2019-01-17 18:20:51 +01:00
Adolfo Gómez García
4778f5f931 fix translations 2019-01-11 05:57:51 +01:00
Adolfo Gómez García
78656ed4a2 fixed 2019-01-10 12:40:23 +01:00
Adolfo Gómez García
278bcb87de Fixed translations 2019-01-10 11:06:38 +01:00
Adolfo Gómez García
690fd7c9cc Added datepicker for chinese 2019-01-09 20:16:25 +01:00
Adolfo Gómez García
7b51bb7985 Added chinese lang to translations on sample file 2019-01-09 20:10:16 +01:00
Adolfo Gómez García
6f81892324 renamed chinese to zh_hans (traditional chinese) 2019-01-09 20:03:41 +01:00
Adolfo Gómez García
a496fd97f1 added autotrasnlations for chinesse 2019-01-09 13:53:07 +01:00
Adolfo Gómez García
77bf401bd5 added eu autotranslations 2019-01-09 13:31:29 +01:00
Adolfo Gómez García
812e863e7f translations 2019-01-09 12:29:39 +01:00
Adolfo Gómez García
cd2d529524 Fixed IOS crash 2019-01-08 10:04:37 +01:00
Adolfo Gómez García
8f76363b27 realname fixed on creation 2018-12-21 13:53:52 +01:00
Adolfo Gómez García
6a05403464 Added realname 2018-12-21 10:52:42 +01:00
Adolfo Gómez García
43398cd125 Fixed user edition (user assignation) 2018-12-20 16:38:00 +01:00
Adolfo Gómez García
481eca7d16 Removed unused imports 2018-12-03 10:46:05 +01:00
Adolfo Gómez García
e382bfa19b Removed transaction from calendar actions execution. Not needed in fact. 2018-12-03 10:45:31 +01:00
Adolfo Gómez García
9094547f22 Testing windows 8 idle 2018-12-03 08:27:02 +01:00
Adolfo Gómez García
c2bd664545 translations 2018-11-29 10:18:59 +01:00
Adolfo Gómez García
8b4485d533 translations 2018-11-27 11:57:22 +01:00
Adolfo Gómez García
682ce422b6 Translations 2018-11-27 11:17:49 +01:00
Adolfo Gómez García
906824cafc Fixups 2018-11-26 13:44:48 +01:00
Adolfo Gómez García
eb6065b203 Fixed so now services with no transports are not provided to template 2018-11-26 11:44:07 +01:00
Adolfo Gómez García
9451889bb3 Removed execution of "reg", using "winreg" instead (_winreg on 2.7) 2018-11-23 11:39:45 +01:00
Adolfo Gómez García
8120ae5c92 Final fix for no check redirections 2018-11-23 11:09:06 +01:00
Adolfo Gómez García
d1bb018dff Added "no warning for redirections' to remote connection 2018-11-23 11:07:58 +01:00
Adolfo Gómez García
43cb02b177 Now on access error, reloads page 2018-11-22 12:09:58 +01:00
Adolfo Gómez García
3e3ea59261 Updated translations 2018-11-20 11:02:58 +01:00
Adolfo Gómez García
688a91325c Added servicepool add/remove from groups/transports 2018-11-20 10:50:18 +01:00
Adolfo Gómez García
97e8c42b2f Fix on None notifyComms 2018-11-13 20:12:04 +01:00
Adolfo Gómez García
e814c47a24 Another fix for actor 2018-11-13 19:46:15 +01:00
Adolfo Gómez García
9fb19c6b9d Fix actor typ 2018-11-13 19:28:03 +01:00
Adolfo Gómez García
4375ee6d50 Fixups for actor 2018-11-13 18:38:35 +01:00
Adolfo Gómez García
ff2f508240 Fix on windows actor to check if lastInputInfo is valid or not... 2018-11-13 10:52:11 +01:00
Adolfo Gómez García
68a58b7c73 removed /compression from freerdp (used -compression now) 2018-11-08 10:41:08 +01:00
Adolfo Gómez García
4441286c89 removed /compression from freerdp (used -compression now) 2018-11-08 10:40:14 +01:00
Adolfo Gómez García
c9410fbbac fixed removing user on case of error 2018-11-05 11:28:19 +01:00
Adolfo Gómez García
43157d722f Fixed Calendar Actions L2 2018-10-26 13:20:16 +02:00
Adolfo Gómez García
861258532d Python3 compat 2018-10-24 08:35:08 +02:00
Adolfo Gómez García
ba45e4c6d6 Fixed bug on case of exception on service saving 2018-10-23 14:13:36 +02:00
Adolfo Gómez García
8088efa225 Some adjustements to OpenStack connector 2018-10-23 09:28:05 +02:00
Adolfo Gómez García
300f2f3333 Fixed OpenStack for newer versions. Now has 2 providers for "legacy" and for "new" versions 2018-10-22 12:28:26 +02:00
Adolfo Gómez García
c1e2e12dd9 Fixed "meta group" pools on information 2018-10-22 10:54:24 +02:00
Adolfo Gómez García
265d2d8702 translations 2018-10-15 10:55:48 +02:00
Adolfo Gómez García
95e99670ee Changed names on openstack for older releases 2018-10-15 10:47:23 +02:00
Adolfo Gómez García
73be005f6a changed error message on case of change user password error 2018-10-05 12:41:23 +02:00
Adolfo Gómez García
4c30e9d45e actor fix for python3/ubuntu16 (due to requests/json) 2018-10-04 13:08:26 +02:00
Adolfo Gómez García
68fbaf7ae0 removev python3-prctl as dependency and marqued as "recommended" 2018-10-04 12:54:49 +02:00
Adolfo Gómez García
cf7e8f19e2 updated translations 2018-10-03 13:45:05 +02:00
Adolfo Gómez García
256a7f2584 Now RDP defaults credssp to "TRUE" 2018-10-03 13:43:58 +02:00
Adolfo Gómez García
7f1a776f69 Upgraded debian actor to python 3 2018-10-02 15:24:34 +02:00
Adolfo Gómez García
d5be31f1d9 * Fixed version
* Moved actors to python3
2018-10-02 13:08:13 +02:00
Adolfo Gómez García
a47df49ca8 Changed order so now on mac CorD is tried first 2018-09-28 10:03:04 +02:00
Adolfo Gómez García
2e0972b71e Added loging on action over service pool 2018-09-24 18:57:11 +02:00
Adolfo Gómez García
ee9a062201 added log in case of max services reached on service pool 2018-09-24 15:54:08 +02:00
Adolfo Gómez García
8384a43699 Updated trans, need revision already 2018-09-24 11:24:05 +02:00
Adolfo Gómez García
8c882852db added assigned service edition. Now we can change the onwership of a machine 2018-09-24 11:12:36 +02:00
Adolfo Gómez García
cbdd61cfa8 Small fixes 2018-09-21 11:07:39 +02:00
Adolfo Gómez García
e642f4df18 Updated about 2018-09-21 07:56:36 +02:00
Adolfo Gómez García
8c85639dfe Fixed so UDS now works correctly with versiones newer that OCATA 2018-09-18 23:40:16 +02:00
Adolfo Gómez García
5fbdb6834d fixed so concurrent creation storm is better handled now 2018-09-13 10:17:28 +02:00
Adolfo Gómez García
6bcf8a832f updated translations 2018-09-10 11:11:27 +02:00
Adolfo Gómez García
aa4bbcbeb3 Removed simple ldap from authenticators list. Its not used and its use
is very limited
2018-09-07 04:11:09 +02:00
Adolfo Gómez García
5863962e8a Added "display connection bar" option to transport 2018-09-06 13:45:21 +02:00
Adolfo Gómez García
c2a95711da Fixed trying to remove VM when LCM_STATE was "boot" or prior for
OpenNebula
2018-08-20 13:38:54 +02:00
Adolfo Gómez García
eb3188c6c1 Changed AES to CBC 2018-08-02 11:13:37 +02:00
Adolfo Gómez García
34b8902c71 Added AEScrypt y AESdecrypt 2018-08-02 01:58:49 +02:00
Adolfo Gómez García
3f76f6c1ab Some refactoring to easy indentify algorithm 2018-08-02 01:00:24 +02:00
Adolfo Gómez García
6cee32d680 fix lambda 2018-07-31 23:53:58 +02:00
Adolfo Gómez García
cce00adc51 Fixed string for "access denied" on logon 2018-07-26 12:52:08 +02:00
Adolfo Gómez García
4b75be2f02 fully fixed password issue on HTML5 2018-07-19 21:14:08 +02:00
Adolfo Gómez García
34046c8a7e removed unsecure logging on exception on code 2018-07-19 11:22:04 +02:00
Adolfo Gómez García
f177b98c78 Fixed tickets auth 2018-07-18 23:11:55 +02:00
Adolfo Gómez García
4777b7e8f9 Fixes for passwords 2018-07-18 22:44:23 +02:00
Adolfo Gómez García
fbbdc529fe Fixes & DEBUG log fix 2018-07-17 21:16:17 +02:00
Adolfo Gómez García
e9f61b6a94 fixed single quote on passwords 2018-07-17 12:03:03 +02:00
Adolfo Gómez García
f333f2f71f Fixed "keep on new publication" 2018-07-02 13:28:28 +02:00
Adolfo Gómez García
6c2a7ff6e6 Added basic metadata info to openstack server 2018-06-29 14:15:06 +02:00
Adolfo Gómez García
1936a02cf7 * Fixed get name from authCallback
* added convenient "comments" to some parts
* Fixed login, to cicly session only when logged in
2018-06-27 11:12:08 +02:00
Adolfo Gómez García
d8eb440b34 Advanced a lot on azure ad integration 2018-06-26 17:20:05 +02:00
Adolfo Gómez García
4570d79645 Added "hint" return type for managers helpers 2018-06-25 09:35:20 +02:00
Adolfo Gómez García
6936f6994d Updated random uuid method 2018-06-25 09:32:20 +02:00
Adolfo Gómez García
d60f1d82db Fixed datetime.now 2018-06-21 12:55:17 +02:00
Adolfo Gómez García
c72a6a32dc Fixed cache geting local datetime instead of 2018-06-21 12:47:20 +02:00
Adolfo Gómez García
7bdb3d77ba Fixed LogManager so now "cuts" correctly before writing log text
Removed debug that was "anoying
2018-06-12 13:25:54 +02:00
Adolfo Gómez García
f211f3482f small fixes & added open link on new window instead of current 2018-06-11 13:33:50 +02:00
Adolfo Gómez García
ff226dd8e5 included decorator 2018-06-11 01:30:32 +02:00
Adolfo Gómez García
b0268346e5 Created Azure publication 2018-06-08 01:36:35 +02:00
Adolfo Gómez García
9906b80702 Added russian 2018-06-06 15:29:52 +02:00
Adolfo Gómez García
be4a4a5b09 Fixed non asceii characters on user home for windows 2018-06-05 14:06:32 +02:00
Adolfo Gómez García
070088909e Fixed "idle checker" logout 2018-05-31 12:20:14 +02:00
Adolfo Gómez García
e9d23cf170 * Added "HIDDEN_FIELD" and "READ_ONLY_FIELD" to Configuration
* Added a "default" UDS Server (cluster) ID, so we can use it for a lot
of things :)
2018-05-28 09:54:23 +02:00
Adolfo Gómez García
b5ebd1f1fb Added RDP option to allow only "new local drives" from windows 2018-05-18 10:55:47 +02:00
Adolfo Gómez García
0e99f53f0d Added "early client IP log" on dashboard 2018-05-16 13:36:17 +02:00
Adolfo Gómez García
2bbc74a87c Fixed domain deletion when a machine has child nodes 2018-05-10 12:19:54 +02:00
Adolfo Gómez García
9e270f6f7e fixed requirements 2018-05-09 00:22:30 +02:00
Adolfo Gómez García
ba09bab8a1 fix Actor for newer Oss 2018-05-02 10:40:57 +02:00
Adolfo Gómez García
4c4a54e50b Fixed debian to use first freerdp2 instead freerdp. Also, removed rdesktop 2018-04-27 16:52:09 +02:00
Adolfo Gómez García
0648a27daf Fixed for machine deletion on ou change 2018-04-27 10:53:04 +02:00
Adolfo Gómez García
6db1fdb86d Added content type to error responses on REST api 2018-04-14 18:53:26 +02:00
Adolfo Gómez García
b1223f623b Fixed RDP connection 2018-04-12 13:02:24 +02:00
Adolfo Gómez García
22a78d8e69 addedd freerdp2-x11 as optional dependency 2018-03-30 19:00:24 +02:00
Adolfo Gómez García
39b6a59538 Fixed end date 2018-03-28 12:42:50 +02:00
Adolfo Gómez García
772a31da37 added fix for shotname 2018-03-28 12:02:57 +02:00
Adolfo Gómez García
0070b1618b Fixed for getByAttr1 to return FIRST element in case of error 2018-03-26 12:34:12 +02:00
Adolfo Gómez García
3b04e2a180 translations 2 2018-03-23 10:52:54 +01:00
Adolfo Gómez García
5d43c1dbff translations 1 2018-03-23 10:42:34 +01:00
Adolfo Gómez García
9c65db5c25 X2go fixes 2018-03-23 10:39:34 +01:00
Adolfo Gómez García
a6397e9ff6 Fixed X2Go 2018-03-22 18:36:43 +01:00
Adolfo Gómez García
5909533161 Fixed "smooth fonts".Now works on linux xfreerdp clients also. 2018-03-21 18:40:32 +01:00
Adolfo Gómez García
f3420a50ac translations 2018-03-16 10:56:57 +01:00
Adolfo Gómez García
1bd56debb9 Added reset to vdi 2018-03-16 09:51:55 +01:00
Adolfo Gómez García
4641fde008 Added reset to vdi 2018-03-16 09:37:04 +01:00
Adolfo Gómez García
1d3d79c562 fix for "reset" capacity 2018-03-16 06:15:19 +01:00
Adolfo Gómez García
33600bda6e Fixed rootless 2018-03-14 15:23:44 +01:00
Adolfo Gómez García
e4807cf648 sets "allow_users_reset" to false on save if service does not supports
it.
2018-03-14 09:51:17 +01:00
Adolfo Gómez García
92de41d410 * Added support for "reset" services that support it.
Right now, we will test on enterprise this feature.
2018-03-14 08:03:23 +01:00
Adolfo Gómez García
ee72b65534 Merge from master guacamole updates 2018-03-12 07:21:22 +01:00
Adolfo Gómez García
dba5e4267a Fixed translations 2018-03-09 11:19:01 +01:00
Adolfo Gómez García
f6d78201cb fixed cache in case of use in middle of "lock tables"... 2018-03-08 20:29:28 +01:00
Adolfo Gómez García
8162230f23 Fixed lazy trans on os managers 2018-03-06 11:06:40 +01:00
Adolfo Gómez García
bfb96b6d9f Translations 2018-03-05 13:59:20 +01:00
Adolfo Gómez García
b37c87afb5 Added new "on logout" state 2018-03-05 13:38:40 +01:00
Adolfo Gómez García
df98efc2da translations 2018-03-02 18:35:02 +01:00
Adolfo Gómez García
7320ba0e6d Added to "use" log the serviceName & the pool name 2018-03-02 18:09:12 +01:00
Adolfo Gómez García
4271be9340 Added a couple of logs due to the fact that maybe in some moment we
change a datetime from db/uds and may "force" tasks
2018-03-02 06:00:04 +01:00
Adolfo Gómez García
b56b3ef6d8 Fixed & remove "auto_add_time" etc.. 2018-03-02 05:25:42 +01:00
Adolfo Gómez García
5f74c7fca8 * Changed the "persitent" management on case of new publication.
Now, if a machine is considered "persistent", on a new publication, UDS
Will not remove assigned services, nor will remove services of old
publications on logout.
2018-03-02 00:51:16 +01:00
Adolfo Gómez García
0bd09e70bf fixed README.txt 2018-02-28 19:50:27 +01:00
Adolfo Gómez García
9e991eebb2 Locales to date 2018-02-20 14:29:59 +01:00
Adolfo Gómez García
ab8faf3ebb Small 2.2 fixes 2018-02-20 13:11:46 +01:00
Adolfo Gómez García
752b84a17a added xscreensaver dependencie to udsactor 2018-02-12 10:49:44 +01:00
Adolfo Gómez García
4ffdd4b882 Removed check of systray existance... 2018-02-12 10:43:34 +01:00
Adolfo Gómez García
d59661eb05 Fix report 2018-02-07 16:42:59 +01:00
Adolfo Gómez García
a03f86f031 Fixed report to blob generation 2018-02-07 10:21:46 +01:00
Adolfo Gómez García
cc033105e2 fixed translations 2018-02-06 15:19:21 +01:00
Adolfo Gómez García
a1bf0b92bb Enhanced "search user/groups" response on too many results 2018-02-01 04:59:20 +01:00
Adolfo Gómez García
b6e5aeac29 Fixed string translation from coffeescript 2018-01-26 10:32:08 +01:00
Adolfo Gómez García
3420ac44be Fixed net to use only integers on py3 2018-01-18 08:32:16 +01:00
Adolfo Gómez García
d0fb880880 Removed annoying debug 2018-01-16 10:38:23 +01:00
Adolfo Gómez García
f43a65e026 Fixed connection for not checking connections 2018-01-16 10:33:51 +01:00
Adolfo Gómez García
ea0727993e legacy bug, in a methond not in fact used (but maybe usable) 2018-01-15 23:32:55 +01:00
Adolfo Gómez García
8d00883213 Added "Chrome OS", and fixed the "unknown os" bug 2018-01-12 11:51:08 +01:00
Adolfo Gómez García
8ad77c736d Set that unknown os to be recognized right now as android 2018-01-12 11:25:24 +01:00
Adolfo Gómez García
34ff0259ce Fixed icon for service inside providers (on dashboard) 2018-01-10 12:18:28 +01:00
Adolfo Gómez García
b4ba79cad2 Fixed look up user names on Windows Actor on lowercase 2018-01-10 12:10:53 +01:00
Adolfo Gómez García
5a13f52d8f Fixed translations 2018-01-09 22:50:46 +01:00
Adolfo Gómez García
0242eaaa54 fixed a couple of literals 2018-01-09 22:36:26 +01:00
Adolfo Gómez García
7ec92d6005 translations 2018-01-09 14:35:42 +01:00
Adolfo Gómez García
dbc2bc1754 Fixed string 2018-01-09 14:33:38 +01:00
Adolfo Gómez García
18f75ecb37 Updated translations 2018-01-08 18:48:41 +01:00
Adolfo Gómez García
033a6265a8 Fixed ticket validity description on HTML5 2018-01-06 19:29:46 +01:00
Adolfo Gómez García
02ab1a8c7c Removed unused field 2018-01-06 19:27:17 +01:00
Adolfo Gómez García
89155d6db9 HTML5 addin so we can define the duration of HTML5 tickets (for
disconnections, or F5 allowance)
2018-01-04 14:44:31 +01:00
Adolfo Gómez García
9b35224fc5 Removed "storage_type" from 4.1. It's not used, so not needed 2018-01-04 14:43:40 +01:00
199 changed files with 29389 additions and 18394 deletions

3
.gitignore vendored
View File

@@ -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

View File

@@ -1 +1 @@
2.2.0
2.2.1

View File

@@ -1,3 +1,9 @@
udsactor (2.2.1) stable; urgency=medium
* Upgraded to 2.2.1 release
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 2 Oct 2018 12:44:12 +0200
udsactor (2.2.0) stable; urgency=medium
* Upgraded to 2.2.0 release

View File

@@ -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

View File

@@ -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

View File

@@ -3,4 +3,4 @@
FOLDER=/usr/share/UDSActor
cd $FOLDER
python2.7 -m udsactor.linux.UDSActorService $@
exec python3 -m udsactor.linux.UDSActorService $@

View File

@@ -1,4 +1,6 @@
build
dist
*.spec
.idea
*_enterprise*
/samples/

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2.7
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
@@ -33,6 +33,7 @@
from __future__ import unicode_literals
import sys
import os
from PyQt4 import QtCore, QtGui
import six
@@ -92,7 +93,13 @@ class UDSConfigDialog(QtGui.QDialog):
store.writeConfig(cfg)
self.close()
if __name__ == "__main__":
# If to be run as "sudo" on linux, we will need this to avoid problems
if 'linux' in sys.platform:
os.environ['QT_X11_NO_MITSHM'] = '1'
app = QtGui.QApplication(sys.argv)
if store.checkPermissions() is False:

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2.7
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
@@ -39,6 +39,7 @@ import pickle
import time
import datetime
import signal
import six
from udsactor import ipc
from udsactor import utils
from udsactor.log import logger
@@ -53,14 +54,17 @@ trayIcon = None
doLogoff = False
TIMER_TIMEOUT = 5 # In seconds
def sigTerm(sigNo, stackFrame):
if trayIcon:
trayIcon.quit()
trayIcon.quit(extra=" (by sigterm)")
# About dialog
class UDSAboutDialog(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.ui = Ui_UDSAboutDialog()
@@ -72,6 +76,7 @@ class UDSAboutDialog(QtGui.QDialog):
class UDSMessageDialog(QtGui.QDialog):
def __init__(self, parent=None):
QtGui.QDialog.__init__(self, parent)
self.ui = Ui_UDSMessageDialog()
@@ -88,13 +93,13 @@ class UDSMessageDialog(QtGui.QDialog):
class MessagesProcessor(QtCore.QThread):
logoff = QtCore.pyqtSignal(name='logoff')
displayMessage = QtCore.pyqtSignal(QtCore.QString, name='displayMessage')
script = QtCore.pyqtSignal(QtCore.QString, name='script')
displayMessage = QtCore.pyqtSignal(six.text_type, name='displayMessage')
script = QtCore.pyqtSignal(six.text_type, name='script')
exit = QtCore.pyqtSignal(name='exit')
information = QtCore.pyqtSignal(dict, name='information')
def __init__(self):
super(self.__class__, self).__init__()
super(MessagesProcessor, self).__init__()
# Retries connection for a while
for _ in range(10):
try:
@@ -145,11 +150,11 @@ class MessagesProcessor(QtCore.QThread):
msgId, data = msg
logger.debug('Got Message on User Space: {}:{}'.format(msgId, data))
if msgId == ipc.MSG_MESSAGE:
self.displayMessage.emit(QtCore.QString.fromUtf8(data))
self.displayMessage.emit(data)
elif msgId == ipc.MSG_LOGOFF:
self.logoff.emit()
elif msgId == ipc.MSG_SCRIPT:
self.script.emit(QtCore.QString.fromUtf8(data))
self.script.emit(data)
elif msgId == ipc.MSG_INFORMATION:
self.information.emit(pickle.loads(data))
except Exception as e:
@@ -165,6 +170,7 @@ class MessagesProcessor(QtCore.QThread):
class UDSSystemTray(QtGui.QSystemTrayIcon):
def __init__(self, app_, parent=None):
self.app = app_
@@ -205,20 +211,34 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
self.counter = 0
self.timer.start(5000) # Launch idle checking every 5 seconds
self.graceTimerShots = 6 # Start counting for idle after 30 seconds after login, got on windows some "instant" logout because of idle timer not being reset??
self.resetTimervars()
self.timer.start(TIMER_TIMEOUT * 1000) # Launch idle checking every 5 seconds
self.ipc.start()
# If this is running, it's because he have logged in
self.ipc.sendLogin(operations.getCurrentUser())
def resetTimervars(self):
self.lastTimerTime = datetime.datetime.now()
self.graceTimerShots = 6 # Start counting for idle after 30 seconds after login, got on windows some "instant" logout because of idle timer not being reset??
def checkTimers(self):
# Check clock readjustment
# This is executed
elapsed_seconds = (datetime.datetime.now() - self.lastTimerTime).total_seconds()
if elapsed_seconds > TIMER_TIMEOUT * 4 or elapsed_seconds < 0:
# Clock has changed a lot, reset session variables, idle timer, etc..
self.resetTimervars()
return
self.lastTimerTime = datetime.datetime.now()
self.checkIdle()
self.checkMaxSession()
def checkMaxSession(self):
if self.maxSessionTime is None or self.maxSessionTime == 0:
logger.debug('Returning because maxSessionTime is cero')
logger.debug('Returning because maxSessionTime is zero')
return
remainingTime = self.maxSessionTime - (datetime.datetime.now() - self.sessionStart).total_seconds()
@@ -231,7 +251,7 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
if remainingTime <= 0:
logger.debug('Remaining time is less than cero, exiting')
self.quit()
self.quit(extra=" (max session time {} {})".format(self.maxSessionTime, self.sessionStart))
def checkIdle(self):
if self.maxIdleTime is None: # No idle check
@@ -255,7 +275,7 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
if remainingTime <= 0:
logger.info('User has been idle for too long, notifying Broker that service can be reclaimed')
self.quit(logoff=True)
self.quit(logoff=True, extra=' (idle: {} vs {})'.format(idleTime, self.maxIdleTime))
def displayMessage(self, message):
logger.debug('Displaying message')
@@ -292,14 +312,15 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
def about(self):
self.aboutDlg.exec_()
def quit(self, logoff=False):
global doLogoff
def quit(self, logoff=False, extra=''):
global doLogoff # pylint: disable=global-statement
logger.debug('Quit invoked')
if not self.stopped:
self.stopped = True
try:
# If we close Client, send Logoff to Broker
self.ipc.sendLogout(operations.getCurrentUser())
# if sys.platform != 'win32':
self.ipc.sendLogout(operations.getCurrentUser() + extra)
self.timer.stop()
self.ipc.stop()
except Exception:
@@ -310,12 +331,13 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
self.app.quit()
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
if not QtGui.QSystemTrayIcon.isSystemTrayAvailable():
# QtGui.QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.")
sys.exit(1)
# if not QtGui.QSystemTrayIcon.isSystemTrayAvailable():
# # QtGui.QMessageBox.critical(None, "Systray", "I couldn't detect any system tray on this system.")
# sys.exit(1)
# This is important so our app won't close on message windows
QtGui.QApplication.setQuitOnLastWindowClosed(False)
@@ -334,10 +356,11 @@ if __name__ == '__main__':
# Catch kill and logout user :)
signal.signal(signal.SIGTERM, sigTerm)
# app.aboutToQuit.connect()
res = app.exec_()
logger.debug('Exiting')
trayIcon.quit()
trayIcon.quit(logoff=doLogoff) # Pass existing doLogoff
if doLogoff:
try:
@@ -346,5 +369,4 @@ if __name__ == '__main__':
except Exception:
pass
sys.exit(res)

View File

@@ -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!

View File

@@ -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"

View File

@@ -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!

View File

@@ -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>

View File

@@ -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__":

View File

@@ -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']

View File

@@ -34,21 +34,22 @@ from __future__ import unicode_literals
# On centos, old six release does not includes byte2int, nor six.PY2
import six
VERSION = '2.2.0'
VERSION = '2.2.1'
__title__ = 'udsactor'
__version__ = VERSION
__build__ = 0x010755
__build__ = 0x010756
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
__license__ = "BSD 3-clause"
__copyright__ = "Copyright 2014-2017 VirtualCable S.L.U."
__copyright__ = "Copyright 2014-2018 VirtualCable S.L.U."
if not hasattr(six, 'byte2int'):
if six.PY3:
import operator
six.byte2int = operator.itemgetter(0)
else:
def _byte2int(bs):
return ord(bs[0])
six.byte2int = _byte2int

View File

@@ -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):

View File

@@ -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):

View File

@@ -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)

View File

@@ -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())

View File

@@ -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

View File

@@ -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

View File

@@ -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()

View File

@@ -78,6 +78,7 @@ def initCfg():
class CommonService(object):
def __init__(self):
self.isAlive = True
self.api = None
@@ -85,12 +86,13 @@ class CommonService(object):
self.httpServer = None
self.rebootRequested = False
self.knownIps = []
self.loggedIn = False
socket.setdefaulttimeout(20)
def reboot(self):
self.rebootRequested = True
def execute(self, cmdLine, section):
def execute(self, cmdLine, section): # pylint: disable=no-self-use
cmd = shlex.split(cmdLine, posix=False)
if os.path.isfile(cmd[0]):
@@ -259,13 +261,15 @@ class CommonService(object):
return
if msg == ipc.REQ_LOGIN:
self.loggedIn = True
res = self.api.login(data).split('\t')
# third parameter, if exists, sets maxSession duration to this.
# First & second parameters are ip & hostname of connection source
if len(res) >= 3:
self.api.maxSession = int(res[2]) # Third parameter is max session duration
msg = ipc.REQ_INFORMATION # Senf information, requested or not, to client on login notification
if msg == ipc.REQ_LOGOUT:
if msg == ipc.REQ_LOGOUT and self.loggedIn is True:
self.loggedIn = False
self.api.logout(data)
self.onLogout(data)
if msg == ipc.REQ_INFORMATION:
@@ -308,11 +312,14 @@ class CommonService(object):
def endAPI(self):
if self.api is not None:
try:
if self.loggedIn:
self.loggedIn = False
self.api.logout('service_stopped')
self.api.notifyComm(None)
except Exception:
logger.error('Couln\'t remove comms url from broker')
except Exception as e:
logger.error('Couln\'t remove comms url from broker: {}'.format(e))
self.notifyStop()
# self.notifyStop()
# ***************************************************
# Methods that ARE overriden by linux & windows Actor

View File

@@ -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
'''

View File

@@ -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

View File

@@ -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():

View File

@@ -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

View File

@@ -1,3 +1,9 @@
udsclient (2.2.1) stable; urgency=medium
* Upgraded to 2.2.1 release
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 2 Oct 2018 12:44:12 +0200
udsclient (2.2.0) stable; urgency=medium
* Updated release

View File

@@ -10,6 +10,6 @@ Package: udsclient
Section: admin
Priority: optional
Architecture: all
Depends: python-paramiko (>=0.8.2), python-qt4 (>=4.9), python-six(>=1.1), python (>=2.7), freerdp-x11 | rdesktop, desktop-file-utils, ${misc:Depends}
Depends: python-paramiko (>=0.8.2), python-qt4 (>=4.9), python-six(>=1.1), python (>=2.7), freerdp2-x11 | freerdp-x11, desktop-file-utils, ${misc:Depends}
Description: Client connector for Universal Desktop Services (UDS) Broker
This package provides the required components to allow this machine to connect to services provided by UDS Broker.

View File

@@ -1,2 +1,2 @@
udsclient_2.2.0_all.deb admin optional
udsclient_2.2.0_amd64.buildinfo admin optional
udsclient_2.2.1_all.deb admin optional
udsclient_2.2.1_amd64.buildinfo admin optional

View File

@@ -162,6 +162,7 @@ class UDSClient(QtGui.QMainWindow):
QtCore.QTimer.singleShot(1000, self.getVersion)
except Exception as e:
logger.exception('Version')
self.showError(e)
@@ -210,7 +211,7 @@ class UDSClient(QtGui.QMainWindow):
# if QtGui.QMessageBox.warning(None, 'ACCESS Warning', errorString, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) == QtGui.QMessageBox.No:
# raise Exception('Server not approved. Access denied.')
logger.debug('Script: {}'.format(script))
six.exec_(script, globals(), {'parent': self, 'sp': params})
except RetryException as e:

View File

@@ -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)

View File

@@ -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>

View File

@@ -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;

View File

@@ -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);
})();
/**

View File

@@ -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>

View File

@@ -1,4 +1,3 @@
ujson
xml_marshaller
six
pycrypto
@@ -13,3 +12,4 @@ MySQL-python
reportlab
bitarray
paramiko
Django==1.9.9

View File

@@ -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'

View File

@@ -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()

View File

@@ -195,7 +195,7 @@ class Handler(object):
session['REST'] = {
'auth': id_auth,
'username': username,
'password': cryptoManager().xor(password, scrambler), # Stores "bytes"
'password': cryptoManager().symCrypt(password, scrambler), # Stores "bytes"
'locale': locale,
'is_admin': is_admin,
'staff_member': staff_member

View File

@@ -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

View File

@@ -135,7 +135,7 @@ class Client(Handler):
res = userServiceManager().getService(self._request.user, self._request.ip, data['service'], data['transport'])
logger.debug('Res: {}'.format(res))
ip, userService, userServiceInstance, transport, transportInstance = res
password = cryptoManager().xor(data['password'], scrambler).decode('utf-8')
password = cryptoManager().symDecrpyt(data['password'], scrambler)
userService.setConnectionSource(srcIp, hostname) # Store where we are accessing from so we can notify Service

View File

@@ -52,7 +52,7 @@ import logging
logger = logging.getLogger(__name__)
# Enclosed methods under /actor path
# Enclosed methods under /connection path
class Connection(Handler):
'''
Processes actor requests
@@ -112,7 +112,7 @@ class Connection(Handler):
'not_accesible': not servicePool.isAccessAllowed(),
'to_be_replaced': False, # Manually assigned will not be autoremoved never
'transports': trans,
'in_use': svr.in_use})
'in_use': servicePool.in_use})
logger.debug(services)
@@ -162,7 +162,8 @@ class Connection(Handler):
'protocol': 'unknown',
'ip': ip
}
ci.update(itrans.getConnectionInfo(userService, self._user, 'UNKNOWN'))
if doNotCheck is False:
ci.update(itrans.getConnectionInfo(userService, self._user, 'UNKNOWN'))
return Connection.result(result=ci)
except ServiceNotReadyError as e:
# Refresh ticket and make this retrayable
@@ -181,7 +182,7 @@ class Connection(Handler):
res = userServiceManager().getService(self._user, self._request.ip, idService, idTransport)
logger.debug('Res: {}'.format(res))
ip, userService, userServiceInstance, transport, transportInstance = res
password = cryptoManager().xor(self.getValue('password'), scrambler).decode('utf-8')
password = cryptoManager().symDecrpyt(self.getValue('password'), scrambler)
userService.setConnectionSource(self._request.ip, hostname) # Store where we are accessing from so we can notify Service

View File

@@ -78,7 +78,7 @@ class Login(Handler):
if 'authId' not in self._params and 'authSmallName' not in self._params and 'auth' not in self._params:
raise RequestError('Invalid parameters (no auth)')
scrambler = ''.join(random.choice(string.letters + string.digits) for _ in range(32)) # @UndefinedVariable
scrambler = ''.join(random.SystemRandom().choice(string.letters + string.digits) for _ in range(32)) # @UndefinedVariable
authId = self._params.get('authId', None)
authSmallName = self._params.get('authSmallName', None)
authName = self._params.get('auth', None)

View File

@@ -62,6 +62,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
@staticmethod
def serviceInfo(item):
info = item.getType()
return {
'icon': info.icon().replace('\n', ''),
'needs_publication': info.publicationType is not None,
@@ -74,6 +75,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
'allowedProtocols': info.allowedProtocols,
'servicesTypeProvided': info.servicesTypeProvided,
'must_assign_manually': info.mustAssignManually,
'can_reset': info.canReset,
}
@staticmethod
@@ -157,7 +159,8 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
self._deleteIncompleteService(service)
raise RequestError(_('Input error: {0}'.format(unicode(e))))
except Exception as e:
self._deleteIncompleteService(service)
if item is None:
self._deleteIncompleteService(service)
logger.exception('Saving Service')
raise RequestError('incorrect invocation to PUT: {0}'.format(e))

View File

@@ -37,7 +37,6 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from uds.models import CalendarAccess, CalendarAction, Calendar
from uds.models.CalendarAction import CALENDAR_ACTION_DICT
from uds.core.util.State import State
@@ -60,6 +59,7 @@ class AccessCalendars(DetailHandler):
'''
Processes the transports detail requests of a Service Pool
'''
@staticmethod
def as_dict(item):
return {
@@ -80,7 +80,6 @@ class AccessCalendars(DetailHandler):
except Exception:
self.invalidItemException()
def getTitle(self, parent):
return _('Access restrictions by calendar')
@@ -109,10 +108,19 @@ class AccessCalendars(DetailHandler):
else:
CalendarAccess.objects.create(calendar=calendar, service_pool=parent, access=access, priority=priority)
log.doLog(parent, log.INFO, "Added access calendar {}/{} by {}".format(calendar.name, access, self._user.pretty_name), log.ADMIN)
return self.success()
def deleteItem(self, parent, item):
CalendarAccess.objects.get(uuid=processUuid(self._args[0])).delete()
calendarAccess = CalendarAccess.objects.get(uuid=processUuid(self._args[0]))
logStr = "Removed access calendar {} by {}".format(calendarAccess.calendar.name, self._user.pretty_name)
calendarAccess.delete()
log.doLog(parent, log.INFO, logStr, log.ADMIN)
return self.success()
class ActionsCalendars(DetailHandler):
@@ -128,7 +136,7 @@ class ActionsCalendars(DetailHandler):
'calendarId': item.calendar.uuid,
'calendar': item.calendar.name,
'action': item.action,
'actionDescription': CALENDAR_ACTION_DICT[item.action]['description'],
'actionDescription': CALENDAR_ACTION_DICT.get(item.action, {}).get('description', ''),
'atStart': item.at_start,
'eventsOffset': item.events_offset,
'params': json.loads(item.params),
@@ -146,7 +154,6 @@ class ActionsCalendars(DetailHandler):
except Exception:
self.invalidItemException()
def getTitle(self, parent):
return _('Scheduled actions')
@@ -167,11 +174,17 @@ class ActionsCalendars(DetailHandler):
calendar = Calendar.objects.get(uuid=processUuid(self._params['calendarId']))
action = self._params['action'].upper()
if action not in CALENDAR_ACTION_DICT:
self.invalidRequestException()
eventsOffset = int(self._params['eventsOffset'])
atStart = self._params['atStart'] not in ('false', False, '0', 0)
params = json.dumps(self._params['params'])
logger.debug('Got parameters: {} {} {} {} ----> {}'.format(calendar, action, eventsOffset, atStart, params))
# logger.debug('Got parameters: {} {} {} {} ----> {}'.format(calendar, action, eventsOffset, atStart, params))
logStr = "Added scheduled action \"{},{},{},{},{}\" by {}".format(
calendar.name, action, eventsOffset,
atStart and 'Start' or 'End', params, self._user.pretty_name
)
if uuid is not None:
calAction = CalendarAction.objects.get(uuid=uuid)
@@ -185,16 +198,36 @@ class ActionsCalendars(DetailHandler):
else:
CalendarAction.objects.create(calendar=calendar, service_pool=parent, action=action, at_start=atStart, events_offset=eventsOffset, params=params)
log.doLog(parent, log.INFO, logStr, log.ADMIN)
return self.success()
def deleteItem(self, parent, item):
CalendarAction.objects.get(uuid=processUuid(self._args[0])).delete()
calendarAction = CalendarAction.objects.get(uuid=processUuid(self._args[0]))
logStr = "Removed scheduled action \"{},{},{},{},{}\" by {}".format(
calendarAction.calendar.name, calendarAction.action,
calendarAction.events_offset, calendarAction.at_start and 'Start' or 'End', calendarAction.params,
self._user.pretty_name
)
calendarAction.delete()
log.doLog(parent, log.INFO, logStr, log.ADMIN)
return self.success()
def execute(self, parent, item):
self.ensureAccess(item, permissions.PERMISSION_MANAGEMENT)
logger.debug('Launching action')
uuid = processUuid(item)
calAction = CalendarAction.objects.get(uuid=uuid)
calAction.execute()
calendarAction = CalendarAction.objects.get(uuid=uuid)
logStr = "Launched scheduled action \"{},{},{},{},{}\" by {}".format(
calendarAction.calendar.name, calendarAction.action,
calendarAction.events_offset, calendarAction.at_start and 'Start' or 'End', calendarAction.params,
self._user.pretty_name
)
calendarAction.execute()
log.doLog(parent, log.INFO, logStr, log.ADMIN)
return self.success()

View File

@@ -72,7 +72,8 @@ class ServicesPools(ModelHandler):
save_fields = ['name', 'short_name', 'comments', 'tags', 'service_id',
'osmanager_id', 'image_id', 'servicesPoolGroup_id', 'initial_srvs',
'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs', 'show_transports',
'allow_users_remove', 'ignores_unused']
'allow_users_remove', 'allow_users_reset', 'ignores_unused']
remove_fields = ['osmanager_id', 'service_id']
table_title = _('Service Pools')
@@ -91,55 +92,65 @@ class ServicesPools(ModelHandler):
custom_methods = [('setFallbackAccess', True), ('actionsList', True)]
def getItems(self, *args, **kwargs):
return super(ServicesPools, self).getItems(overview=kwargs.get('overview', True), prefetch=['service', 'service__provider', 'servicesPoolGroup', 'image', 'tags'])
# return super(ServicesPools, self).getItems(overview)
def item_as_dict(self, item):
summary = 'summarize' in self._params
# if item does not have an associated service, hide it (the case, for example, for a removed service)
# Access from dict will raise an exception, and item will be skipped
poolGroupId = None
poolGroupName = _('Default')
poolGroupThumb = DEFAULT_THUMB_BASE64
if item.servicesPoolGroup is not None:
poolGroupId = item.servicesPoolGroup.uuid
poolGroupName = item.servicesPoolGroup.name
if item.servicesPoolGroup.image is not None:
poolGroupThumb = item.servicesPoolGroup.image.thumb64
state = item.state
if item.isInMaintenance():
state = State.MAINTENANCE
elif userServiceManager().canInitiateServiceFromDeployedService(item) is False:
state = State.SLOWED_DOWN
val = {
'id': item.uuid,
'name': item.name,
'short_name': item.short_name,
'tags': [tag.tag for tag in item.tags.all()],
'parent': item.service.name,
'parent_type': item.service.data_type,
'comments': item.comments,
'state': state,
'thumb': item.image.thumb64 if item.image is not None else DEFAULT_THUMB_BASE64,
'service_id': item.service.uuid,
'provider_id': item.service.provider.uuid,
'image_id': item.image.uuid if item.image is not None else None,
'servicesPoolGroup_id': poolGroupId,
'pool_group_name': poolGroupName,
'pool_group_thumb': poolGroupThumb,
'initial_srvs': item.initial_srvs,
'cache_l1_srvs': item.cache_l1_srvs,
'cache_l2_srvs': item.cache_l2_srvs,
'max_srvs': item.max_srvs,
'user_services_count': item.userServices.exclude(state__in=State.INFO_STATES).count(),
'user_services_in_preparation': item.userServices.filter(state=State.PREPARING).count(),
'restrained': item.isRestrained(),
'show_transports': item.show_transports,
'allow_users_remove': item.allow_users_remove,
'allow_users_reset': item.allow_users_reset,
'ignores_unused': item.ignores_unused,
'fallbackAccess': item.fallbackAccess,
'permission': permissions.getEffectivePermission(self._user, item),
'info': Services.serviceInfo(item.service),
}
# Extended info
if not summary:
state = item.state
if item.isInMaintenance():
state = State.MAINTENANCE
elif userServiceManager().canInitiateServiceFromDeployedService(item) is False:
state = State.SLOWED_DOWN
poolGroupId = None
poolGroupName = _('Default')
poolGroupThumb = DEFAULT_THUMB_BASE64
if item.servicesPoolGroup is not None:
poolGroupId = item.servicesPoolGroup.uuid
poolGroupName = item.servicesPoolGroup.name
if item.servicesPoolGroup.image is not None:
poolGroupThumb = item.servicesPoolGroup.image.thumb64
val['state'] = state
val['thumb'] = item.image.thumb64 if item.image is not None else DEFAULT_THUMB_BASE64
val['user_services_count'] = item.userServices.exclude(state__in=State.INFO_STATES).count()
val['user_services_in_preparation'] = item.userServices.filter(state=State.PREPARING).count()
val['tags'] = [tag.tag for tag in item.tags.all()]
val['restrained'] = item.isRestrained()
val['permission'] = permissions.getEffectivePermission(self._user, item)
val['info'] = Services.serviceInfo(item.service)
val['servicesPoolGroup_id'] = poolGroupId
val['pool_group_name'] = poolGroupName
val['pool_group_thumb'] = poolGroupThumb
if item.osmanager is not None:
val['osmanager_id'] = item.osmanager.uuid
@@ -155,98 +166,106 @@ class ServicesPools(ModelHandler):
g = self.addDefaultFields([], ['name', 'short_name', 'comments', 'tags'])
for f in [{
'name': 'service_id',
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.provider.name + '\\' + v.name) for v in Service.objects.all()]),
'label': ugettext('Base service'),
'tooltip': ugettext('Service used as base of this service pool'),
'type': gui.InputField.CHOICE_TYPE,
'rdonly': True,
'order': 100, # Ensueres is At end
}, {
'name': 'osmanager_id',
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.name) for v in OSManager.objects.all()]),
'label': ugettext('OS Manager'),
'tooltip': ugettext('OS Manager used as base of this service pool'),
'type': gui.InputField.CHOICE_TYPE,
'rdonly': True,
'order': 101,
}, {
'name': 'show_transports',
'value': True,
'label': ugettext('Show transports'),
'tooltip': ugettext('If active, alternative transports for user will be shown'),
'type': gui.InputField.CHECKBOX_TYPE,
'order': 110,
'tab': ugettext('Advanced'),
}, {
'name': 'allow_users_remove',
'value': False,
'label': ugettext('Allow removal by users'),
'tooltip': ugettext('If active, the user will be allowed to remove the service "manually". Be care with this, because the user will have the "poser" to delete it\'s own service'),
'type': gui.InputField.CHECKBOX_TYPE,
'order': 111,
'tab': ugettext('Advanced'),
}, {
'name': 'ignores_unused',
'value': False,
'label': ugettext('Ignores unused'),
'tooltip': ugettext('If active, UDS will not try to detect and remove assigned but not used user services.'),
'type': gui.InputField.CHECKBOX_TYPE,
'order': 112,
'tab': ugettext('Advanced'),
}, {
'name': 'image_id',
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]),
'label': ugettext('Associated Image'),
'tooltip': ugettext('Image assocciated with this service'),
'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 120,
'tab': ugettext('Display'),
}, {
'name': 'servicesPoolGroup_id',
'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in ServicesPoolGroup.objects.all()]),
'label': ugettext('Pool group'),
'tooltip': ugettext('Pool group for this pool (for pool clasify on display)'),
'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 121,
'tab': ugettext('Display'),
}, {
'name': 'initial_srvs',
'value': '0',
'minValue': '0',
'label': ugettext('Initial available services'),
'tooltip': ugettext('Services created initially for this service pool'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 130,
'tab': ugettext('Availability'),
}, {
'name': 'cache_l1_srvs',
'value': '0',
'minValue': '0',
'label': ugettext('Services to keep in cache'),
'tooltip': ugettext('Services kept in cache for improved user service assignation'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 131,
'tab': ugettext('Availability'),
}, {
'name': 'cache_l2_srvs',
'value': '0',
'minValue': '0',
'label': ugettext('Services to keep in L2 cache'),
'tooltip': ugettext('Services kept in cache of level2 for improved service generation'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 132,
'tab': ugettext('Availability'),
}, {
'name': 'max_srvs',
'value': '0',
'minValue': '1',
'label': ugettext('Maximum number of services to provide'),
'tooltip': ugettext('Maximum number of service (assigned and L1 cache) that can be created for this service'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 133,
'tab': ugettext('Availability'),
}]:
'name': 'service_id',
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.provider.name + '\\' + v.name) for v in Service.objects.all()]),
'label': ugettext('Base service'),
'tooltip': ugettext('Service used as base of this service pool'),
'type': gui.InputField.CHOICE_TYPE,
'rdonly': True,
'order': 100, # Ensueres is At end
}, {
'name': 'osmanager_id',
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.name) for v in OSManager.objects.all()]),
'label': ugettext('OS Manager'),
'tooltip': ugettext('OS Manager used as base of this service pool'),
'type': gui.InputField.CHOICE_TYPE,
'rdonly': True,
'order': 101,
}, {
'name': 'show_transports',
'value': True,
'label': ugettext('Show transports'),
'tooltip': ugettext('If active, alternative transports for user will be shown'),
'type': gui.InputField.CHECKBOX_TYPE,
'order': 110,
'tab': ugettext('Advanced'),
}, {
'name': 'allow_users_remove',
'value': False,
'label': ugettext('Allow removal by users'),
'tooltip': ugettext('If active, the user will be allowed to remove the service "manually". Be careful with this, because the user will have the "power" to delete it\'s own service'),
'type': gui.InputField.CHECKBOX_TYPE,
'order': 111,
'tab': ugettext('Advanced'),
}, {
'name': 'allow_users_reset',
'value': False,
'label': ugettext('Allow reset by users'),
'tooltip': ugettext('If active, the user will be allowed to reset the service'),
'type': gui.InputField.CHECKBOX_TYPE,
'order': 112,
'tab': ugettext('Advanced'),
}, {
'name': 'ignores_unused',
'value': False,
'label': ugettext('Ignores unused'),
'tooltip': ugettext('If the option is enabled, UDS will not attempt to detect and remove the user services assigned but not in use.'),
'type': gui.InputField.CHECKBOX_TYPE,
'order': 113,
'tab': ugettext('Advanced'),
}, {
'name': 'image_id',
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]),
'label': ugettext('Associated Image'),
'tooltip': ugettext('Image assocciated with this service'),
'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 120,
'tab': ugettext('Display'),
}, {
'name': 'servicesPoolGroup_id',
'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in ServicesPoolGroup.objects.all()]),
'label': ugettext('Pool group'),
'tooltip': ugettext('Pool group for this pool (for pool classify on display)'),
'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 121,
'tab': ugettext('Display'),
}, {
'name': 'initial_srvs',
'value': '0',
'minValue': '0',
'label': ugettext('Initial available services'),
'tooltip': ugettext('Services created initially for this service pool'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 130,
'tab': ugettext('Availability'),
}, {
'name': 'cache_l1_srvs',
'value': '0',
'minValue': '0',
'label': ugettext('Services to keep in cache'),
'tooltip': ugettext('Services kept in cache for improved user service assignation'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 131,
'tab': ugettext('Availability'),
}, {
'name': 'cache_l2_srvs',
'value': '0',
'minValue': '0',
'label': ugettext('Services to keep in L2 cache'),
'tooltip': ugettext('Services kept in cache of level2 for improved service generation'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 132,
'tab': ugettext('Availability'),
}, {
'name': 'max_srvs',
'value': '0',
'minValue': '1',
'label': ugettext('Maximum number of services to provide'),
'tooltip': ugettext('Maximum number of service (assigned and L1 cache) that can be created for this service'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 133,
'tab': ugettext('Availability'),
}]:
self.addField(g, f)
return g
@@ -266,6 +285,9 @@ class ServicesPools(ModelHandler):
if serviceType.publicationType is None:
self._params['publish_on_save'] = False
if serviceType.canReset is False:
self._params['allow_users_reset'] = False
if serviceType.needsManager is True:
osmanager = OSManager.objects.get(uuid=processUuid(fields['osmanager_id']))
fields['osmanager_id'] = osmanager.id

View File

@@ -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,

View File

@@ -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'])

View File

@@ -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,

View File

@@ -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:

View File

@@ -44,7 +44,6 @@ from uds.core.util import permissions
from uds.core.util.model import processUuid
from uds.models import Tag
import fnmatch
import re
import itertools
@@ -54,8 +53,7 @@ import logging
logger = logging.getLogger(__name__)
__updated__ = '2017-10-25'
__updated__ = '2018-11-20'
# a few constants
OVERVIEW = 'overview'
@@ -256,6 +254,10 @@ class BaseModelHandler(Handler):
message = _('Invalid Request') if message is None else message
raise RequestError('{} {}: {}'.format(message, self.__class__, self._args))
def invalidResponseException(self, message=None):
message = 'Invalid response' if message is None else message
raise ResponseError(message)
def invalidMethodException(self):
'''
Raises a NotFound exception with translated "Method not found" string to current locale
@@ -730,8 +732,28 @@ class ModelHandler(BaseModelHandler):
return method()
def getItems(self, overview=True, *args, **kwargs):
for item in self.model.objects.filter(*args, **kwargs):
def getItems(self, *args, **kwargs):
if 'overview' in kwargs:
overview = kwargs['overview']
del kwargs['overview']
else:
overview = False
if 'prefetch' in kwargs:
prefetch = kwargs['prefetch']
logger.debug('Prefetching %s', prefetch)
del kwargs['prefetch']
else:
prefetch = []
if 'query' in kwargs:
query = kwargs['query']
del kwargs['query']
else:
logger.debug('Args: %s, kwargs: %s', args, kwargs)
query = self.model.objects.filter(*args, **kwargs).prefetch_related(*prefetch)
for item in query:
try:
if permissions.checkPermissions(self._user, item, permissions.PERMISSION_READ) is False:
continue

View File

@@ -196,7 +196,7 @@ class Module(UserInterface, Environmentable, Serializable):
'''
return [True, _("No connection checking method is implemented.")]
def __init__(self, environment, values=None):
def __init__(self, environment, values=None, uuid=None):
'''
Do not forget to invoke this in your derived class using
"super(self.__class__, self).__init__(environment, values)".
@@ -223,6 +223,7 @@ class Module(UserInterface, Environmentable, Serializable):
UserInterface.__init__(self, values)
Environmentable.__init__(self, environment)
Serializable.__init__(self)
self._uuid = uuid if uuid is not None else ''
def __str__(self):
return "Base Module"
@@ -265,6 +266,9 @@ class Module(UserInterface, Environmentable, Serializable):
'''
return _("No check method provided.")
def getUuid(self):
return self._uuid
def destroy(self):
'''
Invoked before deleting an module from database.

View File

@@ -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

View File

@@ -39,5 +39,5 @@ from uds.core.Environment import Environmentable
from uds.core.Serializable import Serializable
from uds.core.BaseModule import Module
VERSION = '2.2.0-DEVEL'
VERSION_STAMP = '20170901-DEVEL'
VERSION = '2.2.1-DEVEL'
VERSION_STAMP = '20180901-DEVEL'

View File

@@ -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

View File

@@ -46,14 +46,14 @@ from uds.core.util import log
from uds.core.util.decorators import deprecated
from uds.core import auths
from uds.core.util.stats import events
from uds.core.managers.CryptoManager import CryptoManager
from uds.core.managers import cryptoManager
from uds.core.util.State import State
from uds.models import User
import logging
import six
__updated__ = '2017-11-22'
__updated__ = '2019-05-10'
logger = logging.getLogger(__name__)
authLogger = logging.getLogger('authLog')
@@ -70,7 +70,7 @@ def getUDSCookie(request, response=None, force=False):
if 'uds' not in request.COOKIES:
import random
import string
cookie = ''.join(random.choice(string.letters + string.digits) for _ in range(32)) # @UndefinedVariable
cookie = ''.join(random.SystemRandom().choice(string.letters + string.digits) for _ in range(32)) # @UndefinedVariable
if response is not None:
response.set_cookie('uds', cookie)
request.COOKIES['uds'] = cookie
@@ -165,7 +165,9 @@ def __registerUser(authenticator, authInstance, username):
request = getRequest()
usr = authenticator.getOrCreateUser(username, authInstance.getRealName(username))
usr = authenticator.getOrCreateUser(username, username)
usr.real_name = authInstance.getRealName(username)
usr.save()
if usr is not None and State.isActive(usr.state):
# Now we update database groups for this user
usr.getManager().recreateGroups(usr)
@@ -207,6 +209,7 @@ def authenticate(username, password, authenticator, useInternalAuthenticate=Fals
# If do not have any valid group
if gm.hasValidGroups() is False:
logger.info('User {} has been authenticated, but he does not belongs to any UDS know group')
return None
return __registerUser(authenticator, authInstance, username)
@@ -285,7 +288,7 @@ def webLogin(request, response, user, password):
user.updateLastAccess()
request.session.clear()
request.session[USER_KEY] = user.id
request.session[PASS_KEY] = CryptoManager.manager().xor(password, cookie) # Stores "bytes"
request.session[PASS_KEY] = cryptoManager().symCrypt(password, cookie) # Stores "bytes"
# Ensures that this user will have access through REST api if logged in through web interface
REST.Handler.storeSessionAuthdata(request.session, manager_id, user.name, password, get_language(), user.is_admin, user.staff_member, cookie)
return True
@@ -299,7 +302,7 @@ def webPassword(request):
@param request: DJango Request
@return: Unscrambled user password
'''
return CryptoManager.manager().xor(request.session.get(PASS_KEY, ''), getUDSCookie(request)).decode('utf-8') # recover as original unicode string
return cryptoManager().symDecrpyt(request.session.get(PASS_KEY, ''), getUDSCookie(request)) # recover as original unicode string
def webLogout(request, exit_url=None):

View File

@@ -45,7 +45,7 @@ import threading
import time
import logging
__updated__ = '2017-11-15'
__updated__ = '2018-03-02'
logger = logging.getLogger(__name__)
@@ -107,6 +107,8 @@ class DelayedTaskRunner(object):
try:
with transaction.atomic(): # Encloses
task = dbDelayedTask.objects.select_for_update().filter(filt).order_by('execution_time')[0] # @UndefinedVariable
if task.insert_date > now + timedelta(seconds=30):
logger.warn('EXecuted {} due to insert_date being in the future!'.format(task.type))
taskInstanceDump = encoders.decode(task.instance, 'base64')
task.delete()
taskInstance = loads(taskInstanceDump)

View File

@@ -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

View File

@@ -35,12 +35,13 @@ from __future__ import unicode_literals
from django.conf import settings
from uds.core.util import encoders
from Crypto.PublicKey import RSA
from Crypto.Cipher import AES
from OpenSSL import crypto
from Crypto.Random import atfork
import hashlib
import array
import uuid
import datetime
import struct
import random
import string
@@ -64,6 +65,23 @@ class CryptoManager(object):
self._namespace = uuid.UUID('627a37a5-e8db-431a-b783-73f7d20b4934')
self._counter = 0
@staticmethod
def AESKey(key, length):
if isinstance(key, six.text_type):
key = key.encode('utf8')
while len(key) < length:
key += key # Dup key
kl = [ord(v) for v in key]
pos = 0
while len(kl) > length:
kl[pos] ^= kl[length]
pos = (pos + 1) % length
del kl[length]
return b''.join([chr(v) for v in kl])
@staticmethod
def manager():
if CryptoManager.instance is None:
@@ -89,6 +107,28 @@ class CryptoManager(object):
# logger.error(inspect.stack())
return 'decript error'
def AESCrypt(self, text, key, base64=False):
# First, match key to 16 bytes. If key is over 16, create a new one based on key of 16 bytes length
cipher = AES.new(CryptoManager.AESKey(key, 16), AES.MODE_CBC, 'udsinitvectoruds')
rndStr = self.randomString(cipher.block_size)
paddedLength = ((len(text) + 4 + 15) // 16) * 16
toEncode = struct.pack('>i', len(text)) + text + rndStr[:paddedLength - len(text) - 4]
encoded = cipher.encrypt(toEncode)
if hex:
return encoders.encode(encoded, 'base64', True)
return encoded
def AESDecrypt(self, text, key, base64=False):
if base64:
text = encoders.decode(text, 'base64')
cipher = AES.new(CryptoManager.AESKey(key, 16), AES.MODE_CBC, 'udsinitvectoruds')
toDecode = cipher.decrypt(text)
return toDecode[4:4 + struct.unpack('>i', toDecode[:4])[0]]
return
def xor(self, s1, s2):
if isinstance(s1, six.text_type):
s1 = s1.encode('utf-8')
@@ -100,6 +140,12 @@ class CryptoManager(object):
# We must return bynary in xor, because result is in fact binary
return six.binary_type(array.array(six.binary_type('B'), (s1[i] ^ s2[i] for i in range(len(s1)))).tostring())
def symCrypt(self, text, key):
return self.xor(text, key)
def symDecrpyt(self, cryptText, key):
return self.xor(cryptText, key).decode('utf-8')
def loadPrivateKey(self, rsaKey):
try:
pk = RSA.importKey(rsaKey)
@@ -132,7 +178,7 @@ class CryptoManager(object):
If obj is None, returns an uuid based on current datetime + counter
'''
if obj is None:
obj = six.text_type(datetime.datetime.now()) + six.text_type(self._counter)
obj = self.randomString()
self._counter += 1
if isinstance(obj, six.text_type):
@@ -140,7 +186,7 @@ class CryptoManager(object):
else:
obj = six.binary_type(obj)
return six.text_type(uuid.uuid5(self._namespace, six.binary_type(obj))).lower() # I believe uuid returns a lowercase uuid always, but in case... :)
return six.text_type(uuid.uuid5(self._namespace, six.binary_type(obj))).lower() # uuid must return a lowercase uuid always?, just in case... :)
def randomString(self, length=40):
return ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(length))
return ''.join(random.SystemRandom().choice(string.ascii_lowercase + string.digits) for _ in range(length))

View File

@@ -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():

View File

@@ -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

View File

@@ -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):
'''

View File

@@ -51,7 +51,7 @@ import requests
import json
import logging
__updated__ = '2017-11-17'
__updated__ = '2019-05-08'
logger = logging.getLogger(__name__)
traceLogger = logging.getLogger('traceLog')
@@ -142,6 +142,12 @@ class UserServiceManager(object):
'''
Creates a new assigned deployed service for the publication and user indicated
'''
# First, honor maxPreparingServices
if self.canInitiateServiceFromDeployedService(ds) is False:
# Cannot create new
logger.warn('Too many preparing services. Creation of assigned service denied by max preparing services parameter. (login storm with insufficient cache?).')
raise ServiceNotReadyError()
if ds.service.getType().publicationType is not None:
dsp = ds.activePublication()
logger.debug('Creating a new assigned element for user {0} por publication {1}'.format(user, dsp))
@@ -182,6 +188,7 @@ class UserServiceManager(object):
ci = cache.getInstance()
state = ci.moveToCache(cacheLevel)
cache.cache_level = cacheLevel
cache.save(update_fields=['cache_level'])
logger.debug('Service State: {0} {1} {2}'.format(State.toString(state), State.toString(cache.state), State.toString(cache.os_state)))
if State.isRuning(state) and cache.isUsable():
cache.setState(State.PREPARING)
@@ -261,23 +268,31 @@ class UserServiceManager(object):
# Now try to locate 1 from cache already "ready" (must be usable and at level 1)
with transaction.atomic():
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE, os_state=State.USABLE)[:1]
if len(cache) == 0:
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE)[:1]
if len(cache) > 0:
if len(cache) != 0:
cache = cache[0]
cache.assignToUser(user)
cache.save() # Store assigned ASAP, we do not know how long assignToUser method of instance will take
# Ensure element is reserved correctly on DB
if ds.cachedUserServices().select_for_update().filter(user=None, uuid=cache.uuid).update(user=user, cache_level=0) != 1:
cache = None
else:
cache = None
if cache == None:
with transaction.atomic():
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE)[:1]
if len(cache) > 0:
cache = cache[0]
if ds.cachedUserServices().select_for_update().filter(user=None, uuid=cache.uuid).update(user=user, cache_level=0) != 1:
cache = None
else:
cache = None
if cache:
cache.assignToUser(user) # Store assigned ASAP, we do not know how long assignToUser method of instance will take
# Out of atomic transaction
if cache is not None:
logger.debug('Found a cached-ready service from {0} for user {1}, item {2}'.format(ds, user, cache))
events.addEvent(ds, events.ET_CACHE_HIT, fld1=ds.cachedUserServices().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE).count())
ci = cache.getInstance() # User Deployment instance
ci.assignToUser(user)
cache.updateData(ci)
cache.save()
return cache
# Cache missed
@@ -287,8 +302,10 @@ class UserServiceManager(object):
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.PREPARING)[:1]
if len(cache) > 0:
cache = cache[0]
cache.assignToUser(user)
cache.save()
if ds.cachedUserServices().select_for_update().filter(user=None, uuid=cache.uuid).update(user=user, cache_level=0) != 1:
cache = None
else:
cache.assignToUser(user) # Store assigned ASAP, we do not know how long assignToUser method of instance will take
else:
cache = None
@@ -296,10 +313,6 @@ class UserServiceManager(object):
if cache is not None:
logger.debug('Found a cached-preparing service from {0} for user {1}, item {2}'.format(ds, user, cache))
events.addEvent(ds, events.ET_CACHE_MISS, fld1=ds.cachedUserServices().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.PREPARING).count())
ci = cache.getInstance() # User Deployment instance
ci.assignToUser(user)
cache.updateData(ci)
cache.save()
return cache
# Can't assign directly from L2 cache... so we check if we can create e new service in the limits requested
@@ -309,6 +322,7 @@ class UserServiceManager(object):
inAssigned = ds.assignedUserServices().filter(UserServiceManager.getStateFilter()).count()
# totalL1Assigned = inCacheL1 + inAssigned
if inAssigned >= ds.max_srvs: # cacheUpdater will drop necesary L1 machines, so it's not neccesary to check against inCacheL1
log.doLog(ds, log.WARN, 'Max number of services reached: {}'.format(ds.max_srvs), log.INTERNAL)
raise MaxServicesReachedError()
# Can create new service, create it
events.addEvent(ds, events.ET_CACHE_MISS, fld1=0)
@@ -360,6 +374,20 @@ class UserServiceManager(object):
UserServiceOpChecker.makeUnique(uService, ui, state)
return False
def reset(self, uService):
UserService.objects.update()
uService = UserService.objects.get(id=uService.id)
if uService.deployed_service.service.getType().canReset is False:
return
logger.debug('Reseting'.format(uService))
ui = uService.getInstance()
try:
ui.reset()
except Exception:
logger.exception('Reseting service')
def notifyPreconnect(self, uService, userName, protocol):
url = uService.getCommsUrl()
if url is None:
@@ -455,12 +483,17 @@ class UserServiceManager(object):
This method is used by UserService when a request for setInUse(False) is made
This checks that the service can continue existing or not
'''
# uService = UserService.objects.get(id=uService.id)
if uService.publication is None:
return
if uService.publication.id != uService.deployed_service.activePublication().id:
logger.debug('Old revision of user service, marking as removable: {0}'.format(uService))
uService.remove()
osm = uService.deployed_service.osmanager
# If os manager says "machine is persistent", do not tray to delete "previous version" assigned machines
doPublicationCleanup = True if osm is None else not osm.getInstance().isPersistent()
if doPublicationCleanup:
if uService.publication is None:
return
if uService.publication.id != uService.deployed_service.activePublication().id:
logger.debug('Old revision of user service, marking as removable: {0}'.format(uService))
uService.remove()
def notifyReadyFromOsManager(self, uService, data):
try:
@@ -471,10 +504,10 @@ class UserServiceManager(object):
uService.updateData(ui)
if state == State.FINISHED:
logger.debug('Service is now ready')
uService.save()
elif uService.state in (State.USABLE, State.PREPARING): # We don't want to get active deleting or deleted machines...
uService.setState(State.PREPARING)
UserServiceOpChecker.makeUnique(uService, ui, state)
uService.save(update_fields=['os_state'])
except Exception as e:
logger.exception('Unhandled exception on notyfyReady: {}'.format(e))
uService.setState(State.ERROR)
@@ -488,7 +521,7 @@ class UserServiceManager(object):
if kind == 'A': # This is an assigned service
logger.debug('Getting A service {}'.format(idService))
userService = UserService.objects.get(uuid=idService)
userService = UserService.objects.get(uuid=idService, user=user)
userService.deployed_service.validateUser(user)
else:
ds = ServicePool.objects.get(uuid=idService)
@@ -511,6 +544,9 @@ class UserServiceManager(object):
'''
userService = self.locateUserService(user, idService, create=True)
# Early log of "access try" so we can imagine what is going on
userService.setConnectionSource(srcIp, 'unknown')
if userService.isInMaintenance() is True:
raise ServiceInMaintenanceMode()
@@ -534,7 +570,9 @@ class UserServiceManager(object):
# If transport is not available for the request IP...
if trans.validForIp(srcIp) is False:
raise InvalidServiceException()
msg = 'The requested transport {} is not valid for {}'.format(trans.name, srcIp)
logger.error(msg)
raise InvalidServiceException(msg)
if user is not None:
userName = user.name
@@ -564,7 +602,7 @@ class UserServiceManager(object):
serviceNotReadyCode = 0x0003
itrans = trans.getInstance()
if itrans.isAvailableFor(userService, ip):
userService.setConnectionSource(srcIp, 'unknown')
# userService.setConnectionSource(srcIp, 'unknown')
log.doLog(userService, log.INFO, "User service ready", log.WEB)
self.notifyPreconnect(userService, itrans.processedUser(userService, user), itrans.protocol)
traceLogger.info('READY on service "{}" for user "{}" with transport "{}" (ip:{})'.format(userService.name, userName, trans.name, ip))

View File

@@ -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()

View File

@@ -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')

View File

@@ -42,7 +42,7 @@ from uds.core import Module
import six
__updated__ = '2017-10-02'
__updated__ = '2019-02-24'
STORAGE_KEY = 'osmk'
@@ -177,11 +177,9 @@ class OSManager(Module):
def logKnownIp(self, userService, ip):
userService.logIP(ip)
def toReady(self, userService):
userService.setProperty('loginsCounter', '0')
def loggedIn(self, userService, userName=None, save=True):
'''
This method:
@@ -211,13 +209,13 @@ class OSManager(Module):
log.doLog(userService, log.INFO, "User {0} has logged in".format(userName), log.OSMANAGER)
log.useLog('login', uniqueId, serviceIp, userName, knownUserIP, fullUserName)
log.useLog('login', uniqueId, serviceIp, userName, knownUserIP, fullUserName, userService.friendly_name, userService.deployed_service.name)
counter = int(userService.getProperty('loginsCounter', '0')) + 1
userService.setProperty('loginsCounter', six.text_type(counter))
if save:
userService.save()
userService.save(update_fields=['in_use', 'in_use_date', 'data'])
def loggedOut(self, userService, userName=None, save=True):
'''
@@ -257,10 +255,17 @@ class OSManager(Module):
log.doLog(userService, log.INFO, "User {0} has logged out".format(userName), log.OSMANAGER)
log.useLog('logout', uniqueId, serviceIp, userName, knownUserIP, fullUserName)
log.useLog('logout', uniqueId, serviceIp, userName, knownUserIP, fullUserName, userService.friendly_name, userService.deployed_service.name)
if save:
userService.save()
userService.save(update_fields=['in_use', 'in_use_date', 'data'])
def isPersistent(self):
'''
When a publication if finished, old assigned machines will be removed if this value is True.
Defaults to False
'''
return False
def __str__(self):
return "Base OS Manager"

View File

@@ -36,7 +36,7 @@ from uds.core import Environmentable
from uds.core import Serializable
from uds.core.util.State import State
__updated__ = '2017-09-29'
__updated__ = '2018-06-11'
class UserDeployment(Environmentable, Serializable):
@@ -138,6 +138,10 @@ class UserDeployment(Environmentable, Serializable):
self._publication = kwargs.get('publication', None)
self._osmanager = kwargs.get('osmanager', None)
self._dbService = kwargs.get('dbservice', None)
self._uuid = kwargs.get('uuid', '')
# If it has dbService
if self._dbService:
self._uuid = self._dbService.uuid
self.initialize()
@@ -196,6 +200,9 @@ class UserDeployment(Environmentable, Serializable):
'''
return self._osmanager
def getUuid(self):
return self._uuid
def dbservice(self):
'''
Utility method to access database object for the object this represents.
@@ -574,6 +581,13 @@ class UserDeployment(Environmentable, Serializable):
'''
raise Exception('cancel method for class {0} not provided!'.format(self.__class__.__name__))
def reset(self):
'''
This method is invoked for "reset" an user service
This method is not intended to be a task right now, it's more like the
'''
raise Exception('reset method for class {0} not provided!'.format(self.__class__.__name__))
def __str__(self):
'''
Mainly used for debugging purposses

View File

@@ -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.

View File

@@ -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)

View File

@@ -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):

View File

@@ -74,6 +74,10 @@ class Cache(object):
Cache.misses += 1
logger.debug('key not found: {}'.format(skey))
return defValue
except Exception as e:
Cache.misses += 1
logger.debug('Cache inaccesible: {}:{}'.format(skey, e))
return defValue
def remove(self, skey):
'''
@@ -107,7 +111,7 @@ class Cache(object):
c.owner = self._owner
c.key = key
c.value = value
c.created = datetime.now()
c.created = now
c.validity = validity
c.save()

View File

@@ -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:

View File

@@ -39,7 +39,9 @@ from django.core.files.storage import Storage
from django.conf import settings
from six.moves.urllib import parse as urlparse # @UnresolvedImport
from uds.models.DBFile import DBFile
from uds.models import DBFile
from uds.models import getSqlDatetime
from .tools import DictAsObj
import six
@@ -131,9 +133,11 @@ class FileStorage(Storage):
try:
f = self._dbFileForReadWrite(name)
except DBFile.DoesNotExist:
f = DBFile.objects.create(owner=self.owner, name=name)
now = getSqlDatetime()
f = DBFile.objects.create(owner=self.owner, name=name, created=now, modified=now)
f.data = content.read()
f.modified = getSqlDatetime()
f.save()
# Store on cache also

View File

@@ -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))

View File

@@ -103,7 +103,7 @@ class Storage(object):
def getPickleByAttr1(self, attr1):
try:
return pickle.loads(encoders.decode(dbStorage.objects.get(owner=self._owner, attr1=attr1).data, 'base64')) # @UndefinedVariable
return pickle.loads(encoders.decode(dbStorage.objects.filter(owner=self._owner, attr1=attr1)[0].data, 'base64')) # @UndefinedVariable
except Exception:
return None

View File

@@ -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

View File

@@ -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):

View File

@@ -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):

View File

@@ -132,7 +132,7 @@ class DeployedServiceRemover(Job):
# First check if there is someone in "removable" estate
rems = DeployedService.objects.filter(state=State.REMOVABLE)[:10]
if len(rems) > 0:
logger.debug('Found a deployed service marked for removal. Starting removal of {0}'.format(rems))
# logger.debug('Found a deployed service marked for removal. Starting removal of {0}'.format(rems))
for ds in rems:
try:
# Skips checking deployed services in maintenance mode
@@ -145,10 +145,9 @@ class DeployedServiceRemover(Job):
except Exception as e2:
logger.error('Could not delete {}'.format(e2))
rems = DeployedService.objects.filter(state=State.REMOVING)[:10]
if len(rems) > 0:
logger.debug('Found a deployed service in removing state, continuing removal of {0}'.format(rems))
# logger.debug('Found a deployed service in removing state, continuing removal of {0}'.format(rems))
for ds in rems:
try:
# Skips checking deployed services in maintenance mode

View File

@@ -32,13 +32,8 @@
'''
from __future__ import unicode_literals
from django.db import transaction
from uds.core.util.Config import GlobalConfig
from uds.models import CalendarAction, getSqlDatetime
from uds.core.util.State import State
from uds.core.jobs.Job import Job
from datetime import timedelta
import logging
logger = logging.getLogger(__name__)
@@ -52,10 +47,9 @@ class ScheduledAction(Job):
super(ScheduledAction, self).__init__(environment)
def run(self):
with transaction.atomic():
for ca in CalendarAction.objects.select_for_update().filter(service_pool__service__provider__maintenance_mode=False, next_execution__lt=getSqlDatetime()).order_by('next_execution'):
logger.debug('Executing calendar action {}.{}'.format(ca.service_pool.name, ca.calendar.name))
try:
ca.execute()
except Exception as e:
logger.exception('Got an exception executing calendar access action: {}'.format(e))
for ca in CalendarAction.objects.filter(service_pool__service__provider__maintenance_mode=False, next_execution__lt=getSqlDatetime()).order_by('next_execution'):
logger.info('Executing calendar action {}.{} ({})'.format(ca.service_pool.name, ca.calendar.name, ca.action))
try:
ca.execute()
except Exception as e:
logger.exception('Got an exception executing calendar access action: {}'.format(e))

View File

@@ -234,7 +234,6 @@ class ServiceCacheUpdater(Job):
logger.debug('Starting cache checking')
# We need to get
servicesThatNeedsUpdate = self.servicesPoolsNeedingCacheUpdate()
logger.debug('**** Services That Needs Update: {}'.format(servicesThatNeedsUpdate))
for sp, cacheL1, cacheL2, assigned in servicesThatNeedsUpdate:
# We have cache to update??
logger.debug("Updating cache for {0}".format(sp))

View File

@@ -59,7 +59,7 @@ class CacheCleaner(Job):
class TicketStoreCleaner(Job):
frecuency = 3600 * 12 # every twelve hours
frecuency = 60 # every minute (60 seconds)
friendly_name = 'Ticket Storage Cleaner'
def __init__(self, environment):

View File

@@ -36,6 +36,7 @@ from django.http import HttpResponse
from uds.models import TicketStore
from uds.core.util import net
from uds.core.auths import auth
from uds.core.managers import cryptoManager
import logging
@@ -55,8 +56,11 @@ def dict2resp(dct):
def guacamole(request, tunnelId):
logger.debug('Received credentials request for tunnel id {0}'.format(tunnelId))
tunnelId, scrambler = tunnelId.split('.')
try:
val = TicketStore.get(tunnelId, invalidate=False)
val['password'] = cryptoManager().symDecrpyt(val['password'], scrambler)
response = dict2resp(val)
except Exception:

View File

@@ -27,9 +27,9 @@
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
"""
from __future__ import unicode_literals
from django.http import HttpResponseNotAllowed, HttpResponse
@@ -49,12 +49,21 @@ def pam(request):
return HttpResponseNotAllowed(['GET'])
if 'id' in request.GET and 'pass' in request.GET:
# This is an "auth" request
logger.debug("Auth request for user [{0}] and pass [{1}]".format(request.GET['id'], request.GET['pass']))
password = TicketStore.get(request.GET['id'])
ids = request.GET.getlist('id')
response = '0'
if password == request.GET['pass']:
response = '1'
if len(ids) == 1:
userId = ids[0]
logger.debug("Auth request for user [{0}] and pass [{1}]".format(request.GET['id'], request.GET['pass']))
try:
password = TicketStore.get(userId)
if password == request.GET['pass']:
response = '1'
except Exception:
# Non existing ticket, log it and stop
logger.info('Invalid access from {} using user {}'.format(request.ip, userId))
else:
logger.warn('Invalid request from {}: {}'.format(request.ip, [v for v in request.GET.lists()]))
elif 'uid' in request.GET:
# This is an "get name for id" call
logger.debug("NSS Request for id [{0}]".format(request.GET['uid']))

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More