1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-10-07 15:33:51 +03:00

294 Commits
v2.1 ... 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
Adolfo Gómez García
a0797ac07f Updated translations 2017-12-21 19:10:36 +01:00
Adolfo Gómez García
bbcc06f503 Fixes on RDP for Linux.
On linux client freerdp 1.1, seems that /home-drive with
/drive:media,/media and /printer makes the client fail. (Works fine with
2.0).
We have replaced so:
1.- Now by default, on linux, redirect drives redirects /media
2.- You can check "redirect home" so /home is also redirected (take
care, /home, not home folder)
3.- If you want /home-drives, you can include it on "custom parameters"
2017-12-21 19:04:06 +01:00
Adolfo Gómez García
6fbf419064 Updated translations 2017-12-20 12:28:52 +01:00
Adolfo Gómez García
cabc906758 Fixed a lof of transports string literals 2017-12-20 12:17:17 +01:00
Adolfo Gómez García
0b7535e76e Fixed string on x2go transport 2017-12-20 00:10:24 +01:00
Adolfo Gómez García
f536112312 Updated translations 2017-12-19 22:42:50 +01:00
Adolfo Gómez García
ab2ca6d527 Fixed text & descriptions for transports 2017-12-19 22:33:05 +01:00
Adolfo Gómez García
442b78ef2e Fixed smartcard parameter when empty 2017-12-19 13:52:20 +01:00
Adolfo Gómez García
9cfc348ee6 RDP aditions. 2017-12-18 12:48:15 +01:00
Adolfo Gómez García
0f1d1af736 RDP Transport fixes
* Now preferences are not for user, that is not useful. Now screen size,
color deppth, etc.. is selected on the transports.
* Preferences for RDP has been removed from "user preferences"
2017-12-15 12:33:15 +01:00
Adolfo Gómez García
6830d8db4e Fixed Client plugin version 2017-12-15 11:16:22 +01:00
Adolfo Gómez García
d8db218c6d * Updated linux agents to include "udsvapp"
* Fixed reloading of master key, to do it "less sensitive" to database connection errros
* Removed nonsense info log from storage
* Added "customCmd" for UDS vApp on linux
2017-12-14 15:17:46 +01:00
Adolfo Gómez García
81dd4e3b8c a little optimization 2017-12-12 19:42:09 +01:00
Adolfo Gómez García
9519c7c95b More fixes to scheduled actions. Now all should work as expected with start/end dates 2017-12-12 18:42:44 +01:00
Adolfo Gómez García
afa7cb8f39 Fixed another calendar issue 2017-12-12 18:04:47 +01:00
Adolfo Gómez García
dc640fd400 Added support so a deployed service can "ignore unused but assigned"
state. That is, UDS now can enforce the check to be ignored.
2017-11-29 12:50:54 +01:00
Adolfo Gómez García
76c822b015 Fixed RDPFile to split linux command lines correctly 2017-11-29 12:49:49 +01:00
Adolfo Gómez García
c7513328eb Smaill fix for authenticators list (so we can determine type) 2017-11-23 06:38:12 +01:00
Adolfo Gómez García
e5f0fcce69 Fixed SHUTOFF on OpenStack 2017-11-21 12:48:44 +01:00
Adolfo Gómez García
004acbab9a Fixing up thin thin plugin 2017-11-20 14:56:19 +01:00
Adolfo Gómez García
c566ec47a2 Fixes for thin thin plugin 2017-11-20 13:56:28 +01:00
Adolfo Gómez García
3e1a31954a * Added "requestLogoff", to be invoked before "user removal" of a
service
2017-11-20 01:01:52 +01:00
Adolfo Gómez García
ec5473d99f Fixed autoatrributes && rdp 2017-11-15 15:36:36 +01:00
Adolfo Gómez García
4cf62de3fd Fixing up encoders 2017-11-15 14:21:45 +01:00
Adolfo Gómez García
a43d6af237 Fixing up encoders/decoders 2017-11-15 13:11:34 +01:00
Adolfo Gómez García
f41f431f38 Added "hex" encoder if needed 2017-11-15 13:02:13 +01:00
Adolfo Gómez García
c5af877fce Fixed DBfile new "encoding-decoding' 2017-11-15 13:01:47 +01:00
Adolfo Gómez García
88dd6e7494 Fixed encoding to bzip2 2017-11-15 12:41:40 +01:00
Adolfo Gómez García
e69800ccd2 Fixing up removing ".encode(bzip2, base64, zip)" from strings for Python 3 compatibility 2017-11-14 14:51:26 +01:00
Adolfo Gómez García
b1ba02c1f3 Added support or "ignore ssl" cert to openstack (to allow self signed) 2017-11-13 09:23:02 +01:00
Adolfo Gómez García
cb13ac1617 Fixing up REST api for client access 2017-11-10 14:10:26 +01:00
Adolfo Gómez García
de051d99ac Small fix 2017-11-09 08:20:03 +01:00
Adolfo Gómez García
fa1e6a69e8 Added "minValue" (cosmetic) to memory & storage sizes on oVirt Service 2017-11-07 11:09:14 +01:00
Adolfo Gómez García
417ddc6d8d * Fixed calendar, so next execution updates correctly on calendar rule
edition
2017-11-06 10:56:35 +01:00
Adolfo Gómez García
238f08b7dd Fixed id on auth list on REST api 2017-11-03 12:13:32 +01:00
Adolfo Gómez García
739ee728d3 upgraded to 2.2 2017-10-30 10:24:45 +01:00
Adolfo Gómez García
c5b9233d4a Lots of new things
* Now we can have a short name for correct visualization of UDS Services
* We now can allow release services
2017-10-26 13:35:53 +02:00
Adolfo Gómez García
22a8933e82 Added "release" icon (trash) 2017-10-26 10:47:55 +02:00
Adolfo Gómez García
a185baccdd fixed so /rest is allowed 2017-10-25 13:45:22 +02:00
Adolfo Gómez García
2521f41e76 fixed so now Redirect To Https will redirect ALWAYS :) 2017-10-25 13:26:08 +02:00
Adolfo Gómez García
815de57b86 fixed executing 2017-10-19 14:27:17 +02:00
Adolfo Gómez García
428ddd493c * Fixed actor version
* Fixed run
2017-10-19 10:38:41 +02:00
Adolfo Gómez García
4de93ddf1f adapted "run" windows registry key value, so it contains also arguments 2017-10-19 08:07:45 +02:00
Adolfo Gómez García
e6b75e3807 Now, run allows the use of a full command line with parameters 2017-10-18 15:27:56 +02:00
Adolfo Gómez García
e835c018b4 Logs for debugging 2017-10-18 13:18:37 +02:00
Adolfo Gómez García
098620cd05 Fix 2017-10-17 13:20:20 +02:00
Adolfo Gómez García
214f9c397b Improved "ensureResponseIsOk" to detect non json responses... 2017-10-17 13:14:56 +02:00
Adolfo Gómez García
47ae741952 Fixed exception on "notifyReadyFromOsManager' 2017-10-16 19:06:41 +02:00
Adolfo Gómez García
907fad4a55 On 2.2 and later, we force the python executable to be "python2.7" on
linux platforms (except for thin udsclient, that will execute python)
2017-10-16 13:08:47 +02:00
Adolfo Gómez García
7c3e289a6b * Deadline now on its own method
* Removed deadline from notifyUrls. It was a nonsense :).
2017-10-16 12:15:46 +02:00
Adolfo Gómez García
1f5a647ff3 Deadline & Fake set to false
* Added deadline to notifyURLS
* Now Fake = False
2017-10-16 12:02:01 +02:00
Adolfo Gómez García
0eebe6a0a5 Fixed Removal of service without os manager on "logout" 2017-10-16 11:10:39 +02:00
Adolfo Gómez García
8aa1607fee Fixed auth log 2017-10-13 13:05:45 +02:00
Adolfo Gómez García
0aa1311836 Fixed small bug on "useEmptyCreds". In fact, not used but "bug" exists 2017-10-13 12:16:32 +02:00
Adolfo Gómez García
6d49ae6667 Fixed IP Machines service && "execute" method of action calendars 2017-10-12 01:23:02 +02:00
Adolfo Gómez García
e60a4bc8fa Added "udsvapp" possibility 2017-10-09 15:18:59 +02:00
Adolfo Gómez García
6e5b73c3b7 Catalan up to date 2017-10-09 09:21:23 +02:00
Adolfo Gómez García
ae4330fc9d Fixed custom spice error message on dashboard 2017-10-05 11:08:43 +02:00
Adolfo Gómez García
b7147661e7 * Fixed several translations && translations upgrade
* Also removed nonsense check for AD
2017-10-05 10:58:25 +02:00
Adolfo Gómez García
3c07d2f667 Several improvements to messages for transports, and minor client
scripts fixed

* Added custom error message for SPICE
* Remover "import QT" from NX client scripts
2017-10-04 11:30:29 +02:00
Adolfo Gómez García
f54d87a295 Fixed NX legacy password generator. The $ must not be scaped to "\$" :) 2017-10-04 11:26:16 +02:00
Adolfo Gómez García
13f97248f6 Fixed OSManager to allow:
1.- Posting "POST"-only messages through "GET" method. (Only basic
messages)
 2.- Allow notify login/logout for user services without os manager
2017-10-02 10:36:45 +02:00
Adolfo Gómez García
786945fcbf Fixiung up og 2017-09-29 12:33:03 +02:00
Adolfo Gómez García
1070b716a2 Fixiung up og 2017-09-29 12:10:21 +02:00
Adolfo Gómez García
ad1bf0fe0b Fixiung up og 2017-09-29 11:49:37 +02:00
Adolfo Gómez García
df70dd4fd8 Fixiung up og 2017-09-29 11:37:37 +02:00
Adolfo Gómez García
3038c545ce Fixiung up og 2017-09-29 11:09:35 +02:00
Adolfo Gómez García
8d3b28e3cb Fixiung up og 2017-09-29 11:05:29 +02:00
Adolfo Gómez García
c431e39d4a Fixiung up og 2017-09-29 11:04:13 +02:00
Adolfo Gómez García
770f2eef09 Fixing up og 2017-09-29 11:01:28 +02:00
Adolfo Gómez García
e702ff6bca Fixing up OpenGnsys 2017-09-29 10:02:44 +02:00
Adolfo Gómez García
c6cc1f2b43 x2go transport fix 2017-09-27 08:07:43 +02:00
Adolfo Gómez García
06f5184ddd Fixed OpenGnsys 2017-09-25 14:18:19 +02:00
Adolfo Gómez García
d5688116e3 Merge remote-tracking branch 'origin/v2.1' into v2.2 2017-09-21 15:49:39 +02:00
Adolfo Gómez García
1ec1104356 Fixed advanced "alt class" setting to be optional 2017-09-21 09:26:34 +02:00
Adolfo Gómez García
234eb98f0c Included support for retrieving secondary LDAP records so the
information can be completed
2017-09-20 10:14:05 +02:00
Adolfo Gómez García
fa7ab534a3 Updated openGnsys 2017-09-19 08:33:24 +02:00
Adolfo Gómez García
ee661461a5 Fixed windows domain "release", so if domain is not found can be easyly
identified
2017-09-19 08:32:51 +02:00
Adolfo Gómez García
aecf6854a4 Fixed Domain Os Manager for 2017-09-18 14:13:49 +02:00
Adolfo Gómez García
34cd90f9a1 Fixed macosx CoRD transports && backported OpenGnsys 2017-09-18 08:33:17 +02:00
Adolfo Gómez García
201fb8ff9b A couple of cosmetic fixes 2017-09-13 14:31:41 +02:00
Adolfo Gómez García
d071f86c31 Updated filesaver version 2017-09-13 08:26:51 +02:00
Adolfo Gómez García
1c133eacd0 Fix for Windomain 2017-09-13 08:22:10 +02:00
Adolfo Gómez García
89ed019788 Fixed WinDomainOsManager to, in case of several servers actin as DC, look for the machine on all of them 2017-09-07 15:05:44 +02:00
Adolfo Gómez García
9930f4323c Small fix for windomain os manager, to better inform on errors adding to
group.
2017-09-04 18:12:03 +02:00
Adolfo Gómez García
115beefa8a Fixed RDP TRansport to leave enabled by default clipboard redirection, for backward consistence 2017-09-01 13:05:27 +02:00
Adolfo Gómez García
611f3590e6 Added clipboard enabling/disabling on transport 2017-09-01 12:37:13 +02:00
Adolfo Gómez García
59635839cf Added check so we cannot use "no credentials" with an sec that is not rdp and set default sec level to "rdp" for html5 transport 2017-09-01 12:36:39 +02:00
Adolfo Gómez García
4853729d7c Merge remote-tracking branch 'origin/v2.1' into v2.2 2017-08-04 15:19:58 +02:00
Adolfo Gómez García
a8b3b80f75 Merge remote-tracking branch 'origin/v2.1' into v2.2 2017-08-02 16:21:14 +02:00
Adolfo Gómez García
e5a6916109 Fixing up for 2.2 future release 2017-07-27 14:12:45 +02:00
Adolfo Gómez García
6a3093c49d Fix for miniamal server version to use "old method" 2017-07-27 14:04:36 +02:00
Adolfo Gómez García
498154ffb1 Updated version & Initial 2.2 uds client fix so it will support signed scripts 2017-07-27 14:01:04 +02:00
254 changed files with 44726 additions and 18623 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.1.0
2.2.1

6
actors/.gitignore vendored
View File

@@ -3,8 +3,8 @@ bin
udsactor*.deb
udsactor*.build
udsactor*.changes
/udsactor_1.7.0.dsc
/udsactor_1.7.0.tar.xz
/udsactor_*.dsc
/udsactor_*.tar.xz
/udsactor_*_amd64.buildinfo
/udsactor*.rpm
/udsactor_2.1.0_amd64.buildinfo
linux/debian/files

View File

@@ -69,12 +69,14 @@ install-udsactor:
cp scripts/udsactor $(BINDIR)
cp scripts/UDSActorConfig-pkexec $(SBINDIR)
cp scripts/UDSActorTool-startup $(BINDIR)
cp scripts/udsvapp ${BINDIR}
# Policy to run as administrator
cp policy/org.openuds.pkexec.UDSActorConfig.policy $(POLKITDIR)
# Fix permissions
chmod 755 $(BINDIR)/udsactor
chmod 755 $(BINDIR)/udsvapp
chmod 755 $(BINDIR)/UDSActorTool-startup
chmod 755 $(SBINDIR)/UDSActorConfig-pkexec
chmod 755 $(LIBDIR)/UDSActorConfig.py
@@ -93,4 +95,4 @@ endif
uninstall:
rm -rf $(LIBDIR)
# rm -f $(BINDIR)/udsactor
rm -rf $(CFGDIR)
rm -rf $(CFGDIR)

View File

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

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
python -m udsactor.linux.UDSActorService $@
exec python3 -m udsactor.linux.UDSActorService $@

5
actors/linux/scripts/udsvapp Executable file
View File

@@ -0,0 +1,5 @@
#!/bin/sh
/usr/bin/udsactor login "$USER"
$@
/usr/bin/udsactor logout "$USER"

View File

@@ -60,6 +60,7 @@ This package provides the required components to allow this machine to work on a
/etc/init.d/udsactor
/usr/bin/UDSActorTool-startup
/usr/bin/udsactor
/usr/bin/udsvapp
/usr/bin/UDSActorTool
/usr/sbin/UDSActorConfig
/usr/sbin/UDSActorConfig-pkexec

View File

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

View File

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

View File

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

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.1.0'
VERSION = '2.2.1'
__title__ = 'udsactor'
__version__ = VERSION
__build__ = 0x010750
__build__ = 0x010756
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
__license__ = "BSD 3-clause"
__copyright__ = "Copyright 2014-2016 VirtualCable S.L.U."
__copyright__ = "Copyright 2014-2018 VirtualCable S.L.U."
if not hasattr(six, 'byte2int'):
if six.PY3:
import operator
six.byte2int = operator.itemgetter(0)
else:
def _byte2int(bs):
return ord(bs[0])
six.byte2int = _byte2int

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

@@ -44,6 +44,10 @@ from .utils import exceptionToMessage
import socket
import time
import random
import os
import subprocess
import shlex
import stat
IPC_PORT = 39188
@@ -74,6 +78,7 @@ def initCfg():
class CommonService(object):
def __init__(self):
self.isAlive = True
self.api = None
@@ -81,28 +86,31 @@ class CommonService(object):
self.httpServer = None
self.rebootRequested = False
self.knownIps = []
self.loggedIn = False
socket.setdefaulttimeout(20)
def reboot(self):
self.rebootRequested = True
def execute(self, cmd, section):
import os
import subprocess
import stat
def execute(self, cmdLine, section): # pylint: disable=no-self-use
cmd = shlex.split(cmdLine, posix=False)
if os.path.isfile(cmd):
if (os.stat(cmd).st_mode & stat.S_IXUSR) != 0:
subprocess.call([cmd, ])
if os.path.isfile(cmd[0]):
if (os.stat(cmd[0]).st_mode & stat.S_IXUSR) != 0:
try:
res = subprocess.check_call(cmd)
except Exception as e:
logger.error('Got exception executing: {} - {}'.format(cmdLine, e))
return False
logger.info('Result of executing cmd was {}'.format(res))
return True
else:
logger.info('{} file exists but it it is not executable (needs execution permission by admin/root)'.format(section))
logger.error('{} file exists but it it is not executable (needs execution permission by admin/root)'.format(section))
else:
logger.info('{} file not found & not executed'.format(section))
logger.error('{} file not found & not executed'.format(section))
return False
def setReady(self):
self.api.setReady([(v.mac, v.ip) for v in operations.getNetworkInfo()])
@@ -158,6 +166,7 @@ class CommonService(object):
# Now try to run the "runonce" element
runOnce = store.runApplication()
if runOnce is not None:
logger.info('Executing runOnce app: {}'.format(runOnce))
if self.execute(runOnce, 'RunOnce') is True:
# operations.reboot()
return False
@@ -252,13 +261,15 @@ class CommonService(object):
return
if msg == ipc.REQ_LOGIN:
self.loggedIn = True
res = self.api.login(data).split('\t')
# third parameter, if exists, sets maxSession duration to this.
# First & second parameters are ip & hostname of connection source
if len(res) >= 3:
self.api.maxSession = int(res[2]) # Third parameter is max session duration
msg = ipc.REQ_INFORMATION # Senf information, requested or not, to client on login notification
if msg == ipc.REQ_LOGOUT:
if msg == ipc.REQ_LOGOUT and self.loggedIn is True:
self.loggedIn = False
self.api.logout(data)
self.onLogout(data)
if msg == ipc.REQ_INFORMATION:
@@ -301,11 +312,14 @@ class CommonService(object):
def endAPI(self):
if self.api is not None:
try:
if self.loggedIn:
self.loggedIn = False
self.api.logout('service_stopped')
self.api.notifyComm(None)
except Exception:
logger.error('Couln\'t remove comms url from broker')
except Exception as e:
logger.error('Couln\'t remove comms url from broker: {}'.format(e))
self.notifyStop()
# self.notifyStop()
# ***************************************************
# Methods that ARE overriden by linux & windows Actor

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,15 @@
udsclient (2.2.1) stable; urgency=medium
* Upgraded to 2.2.1 release
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 2 Oct 2018 12:44:12 +0200
udsclient (2.2.0) stable; urgency=medium
* Updated release
-- Adolfo Gómez García <agomez@virtualcable.es> Thu, 27 Aug 2017 14:18:18 +0200
udsclient (2.1.0) stable; urgency=medium
* Updated release

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), rdesktop | freerdp-x11, desktop-file-utils, ${misc:Depends}
Depends: python-paramiko (>=0.8.2), python-qt4 (>=4.9), python-six(>=1.1), python (>=2.7), freerdp2-x11 | freerdp-x11, desktop-file-utils, ${misc:Depends}
Description: Client connector for Universal Desktop Services (UDS) Broker
This package provides the required components to allow this machine to connect to services provided by UDS Broker.

View File

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

View File

@@ -1,7 +1,7 @@
#!/usr/bin/env python
#!/usr/bin/env python2.7
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 Virtual Cable S.L.
# Copyright (c) 2014-2017 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@@ -43,9 +43,15 @@ from uds import tools
from uds import VERSION
import webbrowser
import json
import sys
import six
from UDSWindow import Ui_MainWindow
# Server before this version uses "unsigned" scripts
OLD_METHOD_VERSION = '2.4.0'
class RetryException(Exception):
pass
@@ -57,6 +63,7 @@ class UDSClient(QtGui.QMainWindow):
animTimer = None
anim = 0
animInverted = False
serverVersion = 'X.Y.Z' # Will be overwriten on getVersion
def __init__(self):
QtGui.QMainWindow.__init__(self)
@@ -146,6 +153,8 @@ class UDSClient(QtGui.QMainWindow):
webbrowser.open(data['result']['downloadUrl'])
self.closeWindow()
return
self.serverVersion = data['result']['requiredVersion']
self.getTransportData()
except RetryException as e:
@@ -153,6 +162,7 @@ class UDSClient(QtGui.QMainWindow):
QtCore.QTimer.singleShot(1000, self.getVersion)
except Exception as e:
logger.exception('Version')
self.showError(e)
@@ -172,7 +182,20 @@ class UDSClient(QtGui.QMainWindow):
try:
self.processError(data)
script = data['result'].decode('base64').decode('bz2')
params = None
if self.serverVersion <= OLD_METHOD_VERSION:
script = data['result'].decode('base64').decode('bz2')
else:
res = data['result']
# We have three elements on result:
# * Script
# * Signature
# * Script data
# We test that the Script has correct signature, and them execute it with the parameters
script, signature, params = res['script'].decode('base64').decode('bz2'), res['signature'], json.loads(res['params'].decode('base64').decode('bz2'))
if tools.verifySignature(script, signature) is False:
raise Exception('Invalid UDS code signature. Please, report to administrator')
self.stopAnim()
@@ -182,7 +205,14 @@ class UDSClient(QtGui.QMainWindow):
QtCore.QTimer.singleShot(3000, self.endScript)
self.hide()
six.exec_(script, globals(), {'parent': self})
# if self.serverVersion <= OLD_METHOD_VERSION:
# errorString = '<p>The server <b>{}</b> runs an old version of UDS:</p>'.format(host)
# errorString += '<p>To avoid security issues, you must approve old UDS Version access.</p>'
# if QtGui.QMessageBox.warning(None, 'ACCESS Warning', errorString, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) == QtGui.QMessageBox.No:
# raise Exception('Server not approved. Access denied.')
logger.debug('Script: {}'.format(script))
six.exec_(script, globals(), {'parent': self, 'sp': params})
except RetryException as e:
self.ui.info.setText(six.text_type(e) + ', retrying access...')
@@ -224,7 +254,7 @@ def done(data):
QtGui.QMessageBox.critical(None, 'Notice', six.text_type(data.data), QtGui.QMessageBox.Ok)
sys.exit(0)
# Ask user to aprobe endpoint
# Ask user to approve endpoint
def approveHost(host, parentWindow=None):
settings = QtCore.QSettings()
settings.beginGroup('endpoints')
@@ -241,6 +271,7 @@ def approveHost(host, parentWindow=None):
settings.endGroup()
return approved
if __name__ == "__main__":
logger.debug('Initializing connector')
# Initialize app

View File

@@ -1,6 +1,6 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# Copyright (c) 2014-2017 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@@ -34,11 +34,11 @@ from __future__ import unicode_literals
# On centos, old six release does not includes byte2int, nor six.PY2
import six
VERSION = '2.1.0'
VERSION = '2.2.0'
__title__ = 'udclient'
__version__ = VERSION
__build__ = 0x010750
__build__ = 0x010760
__author__ = 'Adolfo Gómez'
__license__ = "BSD 3-clause"
__copyright__ = "Copyright 2014-2015 VirtualCable S.L.U."
__copyright__ = "Copyright 2014-2017 VirtualCable S.L.U."

View File

@@ -32,6 +32,8 @@
'''
from __future__ import unicode_literals
from base64 import b64decode
import tempfile
import string
import random
@@ -48,9 +50,25 @@ _unlinkFiles = []
_tasksToWait = []
_execBeforeExit = []
sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
# Public key for scripts
PUBLIC_KEY = '''-----BEGIN PUBLIC KEY-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAuNURlGjBpqbglkTTg2lh
dU5qPbg9Q+RofoDDucGfrbY0pjB9ULgWXUetUWDZhFG241tNeKw+aYFTEorK5P+g
ud7h9KfyJ6huhzln9eyDu3k+kjKUIB1PLtA3lZLZnBx7nmrHRody1u5lRaLVplsb
FmcnptwYD+3jtJ2eK9ih935DYAkYS4vJFi2FO+npUQdYBZHPG/KwXLjP4oGOuZp0
pCTLiCXWGjqh2GWsTECby2upGS/ZNZ1r4Ymp4V2A6DZnN0C0xenHIY34FWYahbXF
ZGdr4DFBPdYde5Rb5aVKJQc/pWK0CV7LK6Krx0/PFc7OGg7ItdEuC7GSfPNV/ANt
5BEQNF5w2nUUsyN8ziOrNih+z6fWQujAAUZfpCCeV9ekbwXGhbRtdNkbAryE5vH6
eCE0iZ+cFsk72VScwLRiOhGNelMQ7mIMotNck3a0P15eaGJVE2JV0M/ag/Cnk0Lp
wI1uJQRAVqz9ZAwvF2SxM45vnrBn6TqqxbKnHCeiwstLDYG4fIhBwFxP3iMH9EqV
2+QXqdJW/wLenFjmXfxrjTRr+z9aYMIdtIkSpADIlbaJyTtuQpEdWnrlDS2b1IGd
Okbm65EebVzOxfje+8dRq9Uqwip8f/qmzFsIIsx3wPSvkKawFwb0G5h2HX5oJrk0
nVgtClKcDDlSaBsO875WDR0CAwEAAQ==
-----END PUBLIC KEY-----'''
def saveTempFile(content, filename=None):
if filename is None:
filename = b''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(16))
@@ -68,6 +86,7 @@ def saveTempFile(content, filename=None):
logger.info('Returning filename')
return filename
def readTempFile(filename):
if 'win32' in sys.platform:
filename = filename.encode('utf-8')
@@ -79,6 +98,7 @@ def readTempFile(filename):
except Exception:
return None
def testServer(host, port, timeOut=4):
try:
sock = socket.create_connection((host, int(port)), timeOut)
@@ -160,3 +180,24 @@ def addExecBeforeExit(fnc):
def execBeforeExit():
for fnc in _execBeforeExit:
fnc.__call__()
def verifySignature(script, signature):
'''
Verifies with a public key from whom the data came that it was indeed
signed by their private key
param: public_key_loc Path to public key
param: signature String signature to be verified
return: Boolean. True if the signature is valid; False otherwise.
'''
# For signature checking
from Crypto.PublicKey import RSA
from Crypto.Signature import PKCS1_v1_5
from Crypto.Hash import SHA256
rsakey = RSA.importKey(PUBLIC_KEY)
signer = PKCS1_v1_5.new(rsakey)
digest = SHA256.new(script) # Script is "binary string" here
if signer.verify(digest, b64decode(signature)):
return True
return False

View File

@@ -39,9 +39,9 @@ import json
import six
import osDetector
from .log import logger
class RetryException(Exception):
pass
@@ -65,7 +65,7 @@ class RestRequest(object):
logger.debug('Requesting {}'.format(url))
try:
r = requests.get(url, headers={'Content-type': 'application/json', 'User-Agent': osDetector.getOs() + " - UDS Connector " + VERSION })
r = requests.get(url, headers={'Content-type': 'application/json', 'User-Agent': osDetector.getOs() + " - UDS Connector " + VERSION }, verify=False)
except requests.exceptions.ConnectionError as e:
raise Exception('Error connecting to UDS Server at {}'.format(self.restApiUrl[0:-11]))

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

@@ -1,8 +1,8 @@
#!/bin/sh
pip install paramiko requests six
pip install paramiko requests six pycrypto
rm -rf lib
mkdir -p lib/python2.7/site-packages
for a in requests paramiko pyasn1 cryptography packaging idna asn1crypto six enum ipaddress cffi ; do cp -r /usr/lib/python2.7/site-packages/$a* lib/python2.7/site-packages/; done
for a in requests paramiko pyasn1 cryptography packaging idna asn1crypto six enum ipaddress cffi Crypto; do cp -r /usr/lib/python2.7/site-packages/$a* lib/python2.7/site-packages/; done
cp src/udsclient bin
chmod 755 bin/udsclient
mkdir lib/UDSClient

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

@@ -65,6 +65,7 @@ def login():
return 0
def logout():
global headers
h = Http()

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'
@@ -164,6 +166,7 @@ MIDDLEWARE_CLASSES = (
'django.contrib.messages.middleware.MessageMiddleware',
'uds.core.util.request.GlobalRequestMiddleware',
'uds.core.util.middleware.XUACompatibleMiddleware',
'uds.core.util.middleware.RedirectMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
)

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

@@ -39,6 +39,7 @@ from django.contrib.sessions.backends.db import SessionStore
from uds.core.util.Config import GlobalConfig
from uds.core.auths.auth import getRootUser
from uds.models import Authenticator
from uds.core.managers import cryptoManager
import logging
@@ -178,7 +179,7 @@ class Handler(object):
return self._authToken
@staticmethod
def storeSessionAuthdata(session, id_auth, username, locale, is_admin, staff_member):
def storeSessionAuthdata(session, id_auth, username, password, locale, is_admin, staff_member, scrambler):
'''
Stores the authentication data inside current session
:param session: session handler (Djano user session object)
@@ -194,12 +195,13 @@ class Handler(object):
session['REST'] = {
'auth': id_auth,
'username': username,
'password': cryptoManager().symCrypt(password, scrambler), # Stores "bytes"
'locale': locale,
'is_admin': is_admin,
'staff_member': staff_member
}
def genAuthToken(self, id_auth, username, locale, is_admin, staf_member):
def genAuthToken(self, id_auth, username, password, locale, is_admin, staf_member, scrambler):
'''
Generates the authentication token from a session, that is basically
the session key itself
@@ -211,7 +213,7 @@ class Handler(object):
'''
session = SessionStore()
session.set_expiry(GlobalConfig.ADMIN_IDLE_TIME.getInt())
Handler.storeSessionAuthdata(session, id_auth, username, locale, is_admin, staf_member)
Handler.storeSessionAuthdata(session, id_auth, username, password, locale, is_admin, staf_member, scrambler)
session.save()
self._authToken = session.session_key
self._session = session

View File

@@ -39,12 +39,12 @@ from uds.core.util.State import State
from uds.core.util.model import processUuid
from uds.core.util import log
from uds.core.managers import cryptoManager
from uds.core.osmanagers import OSManager
from uds.models import TicketStore
from uds.REST import Handler
from uds.REST import RequestError
from uds.models import UserService
import datetime
import six
@@ -52,7 +52,6 @@ import logging
logger = logging.getLogger(__name__)
# Actor key, configurable in Security Section of administration interface
actorKey = Config.Config.section(Config.SECURITY_SECTION).value('Master Key',
cryptoManager().uuid(datetime.datetime.now()).replace('-', ''),
@@ -99,8 +98,8 @@ class Actor(Handler):
'''
# Ensures that key is first parameter
# Here, path will be .../actor/ACTION/KEY (probably /rest/actor/KEY/...)
logger.debug('{} == {}'.format(self._params.get('key'), actorKey.get(True)))
if self._params.get('key') != actorKey.get(True):
# logger.debug('{} == {}'.format(self._params.get('key'), actorKey.get()))
if self._params.get('key') != actorKey.get():
return Actor.result(_('Invalid key'), error=ERR_INVALID_KEY)
return None
@@ -145,6 +144,10 @@ class Actor(Handler):
if len(self._args) < 1:
raise RequestError('Invalid request')
if self._args[0] == 'PostThoughGet':
self._args = self._args[1:] # Remove first argument
return self.post()
if self._args[0] == 'ticket':
return self.getTicket()
@@ -211,8 +214,22 @@ class Actor(Handler):
logger.debug(self._params)
data = '\t'.join((self._params.get('message'), six.text_type(self._params.get('level', 10000))))
osmanager = service.getInstance().osmanager()
try:
res = service.getInstance().osmanager().process(service, message, data, options={'scramble': False})
if osmanager is None:
if message in ('login', 'logout'):
osm = OSManager(None, None) # Dummy os manager, just for using "logging" capability
if message == 'login':
osm.loggedIn(service)
else:
osm.loggedOut(service)
# Mark for removal...
service.release() # Release for removal
return 'ok'
raise Exception('Unknown message {} for an user service without os manager'.format(message))
else:
res = osmanager.process(service, message, data, options={'scramble': False})
except Exception as e:
return Actor.result(six.text_type(e), ERR_OSMANAGER_ERROR)

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

@@ -44,6 +44,7 @@ from uds.core.managers import cryptoManager, userServiceManager
from uds.core.util.Config import GlobalConfig
from uds.core.services.Exceptions import ServiceNotReadyError
from uds.core import VERSION as UDS_VERSION
from uds.core.util import encoders
import six
@@ -134,15 +135,13 @@ class Client(Handler):
res = userServiceManager().getService(self._request.user, self._request.ip, data['service'], data['transport'])
logger.debug('Res: {}'.format(res))
ip, userService, userServiceInstance, transport, transportInstance = res
password = cryptoManager().xor(data['password'], scrambler).decode('utf-8')
password = cryptoManager().symDecrpyt(data['password'], scrambler)
userService.setConnectionSource(srcIp, hostname) # Store where we are accessing from so we can notify Service
transportScript = transportInstance.getUDSTransportScript(userService, transport, ip, self._request.os, self._request.user, password, self._request)
transportScript = transportInstance.getEncodedTransportScript(userService, transport, ip, self._request.os, self._request.user, password, self._request)
logger.debug('Script:\n{}'.format(transportScript))
return Client.result(result=transportScript.encode('bz2').encode('base64'))
return Client.result(result=transportScript)
except ServiceNotReadyError as e:
# Refresh ticket and make this retrayable
TicketStore.revalidate(ticket, 20) # Retry will be in at most 5 seconds

View File

@@ -36,12 +36,13 @@ from django.utils.translation import ugettext as _
from uds.REST import Handler
from uds.REST import RequestError
from uds.models import UserService, DeployedService, Transport
from uds.core.util.model import processUuid
from uds.core.managers.UserServiceManager import UserServiceManager
from uds.core.util import log
from uds.core.util.stats import events
from uds.models import UserService, DeployedService, ServicesPoolGroup
from uds.core.managers import userServiceManager
from uds.core.managers import cryptoManager
from uds.core.ui.images import DEFAULT_THUMB_BASE64
from uds.core.util.Config import GlobalConfig
from uds.core.services.Exceptions import ServiceNotReadyError
from uds.web import errors
import datetime
import six
@@ -51,7 +52,7 @@ import logging
logger = logging.getLogger(__name__)
# Enclosed methods under /actor path
# Enclosed methods under /connection path
class Connection(Handler):
'''
Processes actor requests
@@ -61,7 +62,7 @@ class Connection(Handler):
needs_staff = False
@staticmethod
def result(result=None, error=None):
def result(result=None, error=None, errorCode=0, retryable=False):
'''
Helper method to create a "result" set for connection response
:param result: Result value to return (can be None, in which case it is converted to empty string '')
@@ -71,7 +72,14 @@ class Connection(Handler):
result = result if result is not None else ''
res = {'result': result, 'date': datetime.datetime.now()}
if error is not None:
if isinstance(error, int):
error = errors.errorString(error)
if errorCode != 0:
error += ' (code {0:04X})'.format(errorCode)
res['error'] = error
res['retryable'] = retryable and '1' or '0'
return res
def serviceList(self):
@@ -90,32 +98,50 @@ class Connection(Handler):
if t.validForIp(self._request.ip) and t.getType().providesConnetionInfo():
trans.append({'id': t.uuid, 'name': t.name})
servicePool = svr.deployed_service
services.append({'id': 'A' + svr.uuid,
'name': svr['name'],
'name': servicePool.name,
'description': servicePool.comments,
'visual_name': servicePool.visual_name,
'group': servicePool.servicesPoolGroup if servicePool.servicesPoolGroup is not None else ServicesPoolGroup.default().as_dict,
'thumb': servicePool.image.thumb64 if servicePool.image is not None else DEFAULT_THUMB_BASE64,
'show_transports': servicePool.show_transports,
'allow_users_remove': servicePool.allow_users_remove,
'maintenance': servicePool.isInMaintenance(),
'not_accesible': not servicePool.isAccessAllowed(),
'to_be_replaced': False, # Manually assigned will not be autoremoved never
'transports': trans,
'maintenance': svr.isInMaintenance(),
'in_use': svr.in_use})
'in_use': servicePool.in_use})
logger.debug(services)
# Now generic user service
for svr in availServices:
for servicePool in availServices:
trans = []
for t in svr.transports.all().order_by('priority'):
for t in servicePool.transports.all().order_by('priority'):
if t.validForIp(self._request.ip) and t.getType().providesConnetionInfo():
trans.append({'id': t.uuid, 'name': t.name})
# Locate if user service has any already assigned user service for this
ads = UserServiceManager.manager().getExistingAssignationForUser(svr, self._user)
ads = userServiceManager().getExistingAssignationForUser(servicePool, self._user)
if ads is None:
in_use = False
else:
in_use = ads.in_use
services.append({'id': 'F' + svr.uuid,
'name': svr.name,
services.append({'id': 'F' + servicePool.uuid,
'name': servicePool.name,
'description': servicePool.comments,
'visual_name': servicePool.visual_name,
'group': servicePool.servicesPoolGroup if servicePool.servicesPoolGroup is not None else ServicesPoolGroup.default().as_dict,
'thumb': servicePool.image.thumb64 if servicePool.image is not None else DEFAULT_THUMB_BASE64,
'show_transports': servicePool.show_transports,
'allow_users_remove': servicePool.allow_users_remove,
'maintenance': servicePool.isInMaintenance(),
'not_accesible': not servicePool.isAccessAllowed(),
'to_be_replaced': servicePool.toBeReplaced(),
'transports': trans,
'maintenance': svr.isInMaintenance(),
'in_use': in_use})
logger.debug('Services: {0}'.format(services))
@@ -125,71 +151,53 @@ class Connection(Handler):
return Connection.result(result=services)
def connection(self, doNotCheck=False):
kind, idService = self._args[0][0], self._args[0][1:]
idService = self._args[0]
idTransport = self._args[1]
try:
ip, userService, iads, trans, itrans = userServiceManager().getService(self._user, self._request.ip, idService, idTransport, not doNotCheck)
ci = {
'username': '',
'password': '',
'domain': '',
'protocol': 'unknown',
'ip': ip
}
if doNotCheck is False:
ci.update(itrans.getConnectionInfo(userService, self._user, 'UNKNOWN'))
return Connection.result(result=ci)
except ServiceNotReadyError as e:
# Refresh ticket and make this retrayable
return Connection.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True)
except Exception as e:
logger.exception("Exception")
return Connection.result(error=six.text_type(e))
logger.debug('Type: {}, Service: {}, Transport: {}'.format(kind, idService, idTransport))
def script(self):
idService = self._args[0]
idTransport = self._args[1]
scrambler = self._args[2]
hostname = self._args[3]
try:
logger.debug('Kind of service: {0}, idService: {1}'.format(kind, idService))
if kind == 'A': # This is an assigned service
ads = UserService.objects.get(uuid=processUuid(idService))
else:
ds = DeployedService.objects.get(uuid=processUuid(idService))
# We first do a sanity check for this, if the user has access to this service
# If it fails, will raise an exception
ds.validateUser(self._user)
# Now we have to locate an instance of the service, so we can assign it to user.
ads = UserServiceManager.manager().getAssignationForUser(ds, self._user)
res = userServiceManager().getService(self._user, self._request.ip, idService, idTransport)
logger.debug('Res: {}'.format(res))
ip, userService, userServiceInstance, transport, transportInstance = res
password = cryptoManager().symDecrpyt(self.getValue('password'), scrambler)
if ads.isInMaintenance() is True:
return Connection.result(error='Service in maintenance')
userService.setConnectionSource(self._request.ip, hostname) # Store where we are accessing from so we can notify Service
logger.debug('Found service: {0}'.format(ads))
trans = Transport.objects.get(uuid=processUuid(idTransport))
transportScript = transportInstance.getEncodedTransportScript(userService, transport, ip, self._request.os, self._user, password, self._request)
if trans.validForIp(self._request.ip) is False:
return Connection.result(error='Access denied')
# Test if the service is ready
if doNotCheck or ads.isReady():
log.doLog(ads, log.INFO, "User {0} from {1} has initiated access".format(self._user.name, self._request.ip), log.WEB)
# If ready, show transport for this service, if also ready ofc
iads = ads.getInstance()
ip = iads.getIp()
logger.debug('IP: {}'.format(ip))
events.addEvent(ads.deployed_service, events.ET_ACCESS, username=self._user.name, srcip=self._request.ip, dstip=ip, uniqueid=ads.unique_id)
if ip is not None:
itrans = trans.getInstance()
if itrans.providesConnetionInfo() and (doNotCheck or itrans.isAvailableFor(ads, ip)):
ads.setConnectionSource(self._request.ip, 'unknown')
log.doLog(ads, log.INFO, "User service ready, rendering transport", log.WEB)
ci = {
'username': '',
'password': '',
'domain': '',
'protocol': 'unknown',
'ip': ip
}
ci.update(itrans.getConnectionInfo(ads, self._user, 'UNKNOWN'))
UserServiceManager.manager().notifyPreconnect(ads, itrans.processedUser(ads, self._user), itrans.protocol)
return Connection.result(result=ci)
else:
log.doLog(ads, log.WARN, "User service is not accessible by REST (ip {0})".format(ip), log.TRANSPORT)
logger.debug('Transport {} is not accesible for user service {} from {}'.format(trans, ads, self._request.ip))
logger.debug("{}, {}".format(itrans.providesConnetionInfo(), itrans.isAvailableFor(ads, ip)))
else:
logger.debug('Ip not available from user service {0}'.format(ads))
else:
log.doLog(ads, log.WARN, "User {0} from {1} tried to access, but service was not ready".format(self._user.name, self._request.ip), log.WEB)
# Not ready, show message and return to this page in a while
return Connection.result(error='Service not ready')
return Connection.result(result=transportScript)
except ServiceNotReadyError as e:
# Refresh ticket and make this retrayable
return Connection.result(error=errors.SERVICE_IN_PREPARATION, errorCode=e.code, retryable=True)
except Exception as e:
logger.exception("Exception")
return Connection.result(error=six.text_type(e))
return password
def get(self):
'''
Processes get requests
@@ -207,7 +215,13 @@ class Connection(Handler):
# Return connection & validate access for service/transport
return self.connection()
if len(self._args) == 3 and self._args[2] == 'skipChecking':
return self.connection(True)
if len(self._args) == 3:
# /connection/idService/idTransport/skipChecking
if self._args[2] == 'skipChecking':
return self.connection(True)
if len(self._args) == 4:
# /connection/idService/idTransport/scrambler/hostname
return self.script()
raise RequestError('Invalid Request')

View File

@@ -36,11 +36,14 @@ from uds.core.util.Config import GlobalConfig
from uds.core.util.model import processUuid
from uds.models import Authenticator
from uds.core.auths.auth import authenticate
from uds.core import VERSION as UDS_VERSION
from uds.REST import RequestError
from uds.REST import Handler
import logging
import random
import string
logger = logging.getLogger(__name__)
@@ -75,6 +78,7 @@ class Login(Handler):
if 'authId' not in self._params and 'authSmallName' not in self._params and 'auth' not in self._params:
raise RequestError('Invalid parameters (no auth)')
scrambler = ''.join(random.SystemRandom().choice(string.letters + string.digits) for _ in range(32)) # @UndefinedVariable
authId = self._params.get('authId', None)
authSmallName = self._params.get('authSmallName', None)
authName = self._params.get('auth', None)
@@ -83,7 +87,7 @@ class Login(Handler):
locale = self._params.get('locale', 'en')
if authName == 'admin' or authSmallName == 'admin':
if GlobalConfig.SUPER_USER_LOGIN.get(True) == username and GlobalConfig.SUPER_USER_PASS.get(True) == password:
self.genAuthToken(-1, username, locale, True, True)
self.genAuthToken(-1, username, password, locale, True, True, scrambler)
return{'result': 'ok', 'token': self.getAuthToken()}
else:
raise Exception('Invalid credentials')
@@ -98,14 +102,14 @@ class Login(Handler):
auth = Authenticator.objects.get(small_name=authSmallName)
if password == '':
password = 'xdaf44tgas4xd5ñasdłe4g€@#½|«ð2' # Extrange password if credential leaved empty
password = 'xdaf44tgas4xd5ñasdłe4g€@#½|«ð2' # Extrange password if credential left empty
logger.debug('Auth obj: {0}'.format(auth))
user = authenticate(username, password, auth)
if user is None: # invalid credentials
raise Exception()
self.genAuthToken(auth.id, user.name, locale, user.is_admin, user.staff_member)
return{'result': 'ok', 'token': self.getAuthToken()}
self.genAuthToken(auth.id, user.name, password, locale, user.is_admin, user.staff_member, scrambler)
return{'result': 'ok', 'token': self.getAuthToken(), 'version': UDS_VERSION, 'scrambler': scrambler }
except:
logger.exception('Credentials ')
raise Exception('Invalid Credentials (invalid authenticator)')
@@ -138,11 +142,13 @@ class Auths(Handler):
def auths(self):
for a in Authenticator.objects.all():
if a.getType().isCustom() is False:
theType = a.getType()
if theType.isCustom() is False and theType.typeType not in ('IP',):
yield {
'authId': a.id,
'authId': a.uuid,
'authSmallName': str(a.small_name),
'auth': a.name,
'type': theType.typeType,
}
def get(self):

View File

@@ -158,7 +158,10 @@ class Providers(ModelHandler):
self.ensureAccess(spType, permissions.PERMISSION_MANAGEMENT, root=True)
logger.debug('spType: {}'.format(spType))
res = spType.test(Environment.getTempEnv(), self._params)
dct = self._params.copy()
dct['_request'] = self._request
res = spType.test(Environment.getTempEnv(), dct)
if res[0]:
return 'ok'
else:

View File

@@ -34,7 +34,6 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from uds.models import Service, UserService, Tag
from uds.core.services import Service as coreService
@@ -63,6 +62,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
@staticmethod
def serviceInfo(item):
info = item.getType()
return {
'icon': info.icon().replace('\n', ''),
'needs_publication': info.publicationType is not None,
@@ -75,6 +75,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
'allowedProtocols': info.allowedProtocols,
'servicesTypeProvided': info.servicesTypeProvided,
'must_assign_manually': info.mustAssignManually,
'can_reset': info.canReset,
}
@staticmethod
@@ -93,7 +94,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
'type': item.data_type,
'type_name': _(itemType.name()),
'deployed_services_count': item.deployedServices.count(),
'user_services_count': UserService.objects.filter(deployed_service__service=item).exclude(state__in=(State.REMOVED, State.ERROR)).count(),
'user_services_count': UserService.objects.filter(deployed_service__service=item).exclude(state__in=State.INFO_STATES).count(),
'maintenance_mode': item.provider.maintenance_mode,
'permission': perm
}
@@ -158,7 +159,8 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
self._deleteIncompleteService(service)
raise RequestError(_('Input error: {0}'.format(unicode(e))))
except Exception as e:
self._deleteIncompleteService(service)
if item is None:
self._deleteIncompleteService(service)
logger.exception('Saving Service')
raise RequestError('incorrect invocation to PUT: {0}'.format(e))

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,18 +108,26 @@ class AccessCalendars(DetailHandler):
else:
CalendarAccess.objects.create(calendar=calendar, service_pool=parent, access=access, priority=priority)
log.doLog(parent, log.INFO, "Added access calendar {}/{} by {}".format(calendar.name, access, self._user.pretty_name), log.ADMIN)
return self.success()
def deleteItem(self, parent, item):
CalendarAccess.objects.get(uuid=processUuid(self._args[0])).delete()
calendarAccess = CalendarAccess.objects.get(uuid=processUuid(self._args[0]))
logStr = "Removed access calendar {} by {}".format(calendarAccess.calendar.name, self._user.pretty_name)
calendarAccess.delete()
log.doLog(parent, log.INFO, logStr, log.ADMIN)
return self.success()
class ActionsCalendars(DetailHandler):
'''
Processes the transports detail requests of a Service Pool
'''
custom_methods = ('execute')
custom_methods = ('execute',)
@staticmethod
def as_dict(item):
@@ -129,7 +136,7 @@ class ActionsCalendars(DetailHandler):
'calendarId': item.calendar.uuid,
'calendar': item.calendar.name,
'action': item.action,
'actionDescription': CALENDAR_ACTION_DICT[item.action]['description'],
'actionDescription': CALENDAR_ACTION_DICT.get(item.action, {}).get('description', ''),
'atStart': item.at_start,
'eventsOffset': item.events_offset,
'params': json.loads(item.params),
@@ -147,7 +154,6 @@ class ActionsCalendars(DetailHandler):
except Exception:
self.invalidItemException()
def getTitle(self, parent):
return _('Scheduled actions')
@@ -168,11 +174,17 @@ class ActionsCalendars(DetailHandler):
calendar = Calendar.objects.get(uuid=processUuid(self._params['calendarId']))
action = self._params['action'].upper()
if action not in CALENDAR_ACTION_DICT:
self.invalidRequestException()
eventsOffset = int(self._params['eventsOffset'])
atStart = self._params['atStart'] not in ('false', False, '0', 0)
params = json.dumps(self._params['params'])
logger.debug('Got parameters: {} {} {} {} ----> {}'.format(calendar, action, eventsOffset, atStart, params))
# logger.debug('Got parameters: {} {} {} {} ----> {}'.format(calendar, action, eventsOffset, atStart, params))
logStr = "Added scheduled action \"{},{},{},{},{}\" by {}".format(
calendar.name, action, eventsOffset,
atStart and 'Start' or 'End', params, self._user.pretty_name
)
if uuid is not None:
calAction = CalendarAction.objects.get(uuid=uuid)
@@ -186,16 +198,36 @@ class ActionsCalendars(DetailHandler):
else:
CalendarAction.objects.create(calendar=calendar, service_pool=parent, action=action, at_start=atStart, events_offset=eventsOffset, params=params)
log.doLog(parent, log.INFO, logStr, log.ADMIN)
return self.success()
def deleteItem(self, parent, item):
CalendarAction.objects.get(uuid=processUuid(self._args[0])).delete()
calendarAction = CalendarAction.objects.get(uuid=processUuid(self._args[0]))
logStr = "Removed scheduled action \"{},{},{},{},{}\" by {}".format(
calendarAction.calendar.name, calendarAction.action,
calendarAction.events_offset, calendarAction.at_start and 'Start' or 'End', calendarAction.params,
self._user.pretty_name
)
calendarAction.delete()
log.doLog(parent, log.INFO, logStr, log.ADMIN)
return self.success()
def execute(self, parent, item):
self.ensureAccess(item, permissions.PERMISSION_MANAGEMENT)
logger.debug('Launching action')
uuid = processUuid(item)
calAction = CalendarAction.objects.get(uuid=uuid)
calAction.execute()
calendarAction = CalendarAction.objects.get(uuid=uuid)
logStr = "Launched scheduled action \"{},{},{},{},{}\" by {}".format(
calendarAction.calendar.name, calendarAction.action,
calendarAction.events_offset, calendarAction.at_start and 'Start' or 'End', calendarAction.params,
self._user.pretty_name
)
calendarAction.execute()
log.doLog(parent, log.INFO, logStr, log.ADMIN)
return self.success()

View File

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

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-02-02'
__updated__ = '2018-11-20'
# a few constants
OVERVIEW = 'overview'
@@ -123,7 +121,7 @@ class BaseModelHandler(Handler):
'label': _('Tags'),
'type': 'taglist',
'tooltip': _('Tags for this element'),
'order': 0 - 101,
'order': 0 - 105,
})
if 'name' in flds:
self.addField(gui, {
@@ -134,13 +132,23 @@ class BaseModelHandler(Handler):
'tooltip': _('Name of this element'),
'order': 0 - 100,
})
if 'short_name' in flds:
self.addField(gui, {
'name': 'short_name',
'type': 'text',
'label': _('Short name'),
'tooltip': _('Short name for user service visualization'),
'required': False,
'length': 16,
'order': 0 - 95,
})
if 'comments' in flds:
self.addField(gui, {
'name': 'comments',
'label': _('Comments'),
'tooltip': _('Comments for this element'),
'length': 256,
'order': 0 - 99,
'order': 0 - 90,
})
if 'priority' in flds:
self.addField(gui, {
@@ -151,7 +159,7 @@ class BaseModelHandler(Handler):
'required': True,
'value': 1,
'length': 4,
'order': 0 - 97,
'order': 0 - 85,
})
if 'small_name' in flds:
self.addField(gui, {
@@ -161,7 +169,7 @@ class BaseModelHandler(Handler):
'tooltip': _('Label for this element'),
'required': True,
'length': 128,
'order': 0 - 96,
'order': 0 - 80,
})
return gui
@@ -246,6 +254,10 @@ class BaseModelHandler(Handler):
message = _('Invalid Request') if message is None else message
raise RequestError('{} {}: {}'.format(message, self.__class__, self._args))
def invalidResponseException(self, message=None):
message = 'Invalid response' if message is None else message
raise ResponseError(message)
def invalidMethodException(self):
'''
Raises a NotFound exception with translated "Method not found" string to current locale
@@ -720,8 +732,28 @@ class ModelHandler(BaseModelHandler):
return method()
def getItems(self, overview=True, *args, **kwargs):
for item in self.model.objects.filter(*args, **kwargs):
def getItems(self, *args, **kwargs):
if 'overview' in kwargs:
overview = kwargs['overview']
del kwargs['overview']
else:
overview = False
if 'prefetch' in kwargs:
prefetch = kwargs['prefetch']
logger.debug('Prefetching %s', prefetch)
del kwargs['prefetch']
else:
prefetch = []
if 'query' in kwargs:
query = kwargs['query']
del kwargs['query']
else:
logger.debug('Args: %s, kwargs: %s', args, kwargs)
query = self.model.objects.filter(*args, **kwargs).prefetch_related(*prefetch)
for item in query:
try:
if permissions.checkPermissions(self._user, item, permissions.PERMISSION_READ) is False:
continue

View File

@@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Virtual Cable S.L.
# Copyright (c) 2012-2016 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@@ -43,8 +43,9 @@ import ldap
import ldap.filter
import re
import logging
import six
__updated__ = '2016-04-18'
__updated__ = '2017-10-05'
logger = logging.getLogger(__name__)
@@ -67,6 +68,8 @@ class RegexLdap(auths.Authenticator):
groupNameAttr = gui.TextField(length=640, label=_('Group Name Attr'), multiline=2, defvalue='cn', order=11, tooltip=_('Attribute that contains the group name'), required=True, tab=_('Ldap info'))
# regex = gui.TextField(length=64, label = _('Regular Exp. for groups'), defvalue = '^(.*)', order = 12, tooltip = _('Regular Expression to extract the group name'), required = True)
altClass = gui.TextField(length=64, label=_('Alt. class'), defvalue='', order=20, tooltip=_('Class for LDAP objects that will be also checked for groups retrieval (normally empty)'), required=False, tab=_('Advanced'))
typeName = _('Regex LDAP Authenticator')
typeType = 'RegexLdapAuthenticator'
typeDescription = _('Regular Expressions LDAP authenticator')
@@ -102,7 +105,7 @@ class RegexLdap(auths.Authenticator):
self._groupNameAttr = values['groupNameAttr']
# self._regex = values['regex']
self._userNameAttr = values['userNameAttr']
self._altClass = values['altClass']
else:
self._host = None
self._port = None
@@ -116,6 +119,8 @@ class RegexLdap(auths.Authenticator):
self._groupNameAttr = None
# self._regex = None
self._userNameAttr = None
self._altClass = None
self._connection = None
def __validateField(self, field, fieldLabel):
@@ -181,32 +186,43 @@ class RegexLdap(auths.Authenticator):
'username': self._username, 'password': self._password, 'timeout': self._timeout,
'ldapBase': self._ldapBase, 'userClass': self._userClass,
'userIdAttr': self._userIdAttr, 'groupNameAttr': self._groupNameAttr,
'userNameAttr': self._userNameAttr
'userNameAttr': self._userNameAttr, 'altClass': self._altClass,
}
def __str__(self):
return "Ldap Auth: {0}:{1}@{2}:{3}, base = {4}, userClass = {5}, userIdAttr = {6}, groupNameAttr = {7}, userName attr = {8}".format(
return "Ldap Auth: {}:{}@{}:{}, base = {}, userClass = {}, userIdAttr = {}, groupNameAttr = {}, userName attr = {}, altClass={}".format(
self._username, self._password, self._host, self._port, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr,
self._userNameAttr)
self._userNameAttr, self._altClass)
def marshal(self):
return '\t'.join([
'v2',
self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password, self._timeout,
self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._userNameAttr
'v3',
self._host, self._port, gui.boolToStr(self._ssl), self._username, self._password,
self._timeout, self._ldapBase, self._userClass, self._userIdAttr,
self._groupNameAttr, self._userNameAttr, self._altClass
])
def unmarshal(self, val):
data = val.split('\t')
if data[0] == 'v1':
logger.debug("Data: {0}".format(data[1:]))
self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, _regex, self._userNameAttr = data[1:]
self._host, self._port, self._ssl, self._username, self._password, \
self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \
self._groupNameAttr, _regex, self._userNameAttr = data[1:]
self._ssl = gui.strToBool(self._ssl)
self._groupNameAttr = self._groupNameAttr + '=' + _regex
self._userNameAttr = '\n'.join(self._userNameAttr.split(','))
elif data[0] == 'v2':
logger.debug("Data v2: {0}".format(data[1:]))
self._host, self._port, self._ssl, self._username, self._password, self._timeout, self._ldapBase, self._userClass, self._userIdAttr, self._groupNameAttr, self._userNameAttr = data[1:]
self._host, self._port, self._ssl, self._username, self._password, \
self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \
self._groupNameAttr, self._userNameAttr = data[1:]
self._ssl = gui.strToBool(self._ssl)
elif data[0] == 'v3':
logger.debug("Data v3: {0}".format(data[1:]))
self._host, self._port, self._ssl, self._username, self._password, \
self._timeout, self._ldapBase, self._userClass, self._userIdAttr, \
self._groupNameAttr, self._userNameAttr, self._altClass = data[1:]
self._ssl = gui.strToBool(self._ssl)
def __connection(self, username=None, password=None):
@@ -250,17 +266,52 @@ class RegexLdap(auths.Authenticator):
def __getUser(self, username):
try:
con = self.__connection()
filter_ = '(&(objectClass=%s)(%s=%s))' % (self._userClass, self._userIdAttr, ldap.filter.escape_filter_chars(username, 0))
filter_ = b'(&(objectClass=%s)(%s=%s))' % (self._userClass, self._userIdAttr, ldap.filter.escape_filter_chars(username, 0))
attrlist = [self._userIdAttr.encode('utf-8')] + self.__getAttrsFromField(self._userNameAttr) + self.__getAttrsFromField(self._groupNameAttr)
logger.debug('Getuser filter_: {0}, attr list: {1}'.format(filter_, attrlist))
logger.debug('Getuser filter_: {}, attr list: {}'.format(filter_, attrlist))
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE,
filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)[0]
usr = dict((k, '') for k in attrlist)
dct = {k.lower(): v for k, v in res[1].iteritems()}
usr.update(dct)
usr.update({'dn': res[0], '_id': username})
# If altClass
if self._altClass is not None and self._altClass != '':
logger.debug('Has alt class {}'.format(self._altClass))
filter_ = b'(&(objectClass=%s)(%s=%s))' % (self._altClass, self._userIdAttr, ldap.filter.escape_filter_chars(username, 0))
logger.debug('Get Alternate list filter: {}, attrlist: {}'.format(filter_, attrlist))
# Get alternate class objects
res = con.search_ext_s(base=self._ldapBase, scope=ldap.SCOPE_SUBTREE,
filterstr=filter_, attrlist=attrlist, sizelimit=LDAP_RESULT_LIMIT)
for r in res:
if r[0] is None:
continue
logger.debug('*** Item: {}'.format(r))
for k, v in six.iteritems(r[1]):
kl = k.lower()
# If already exists the field
if kl in usr:
# Convert existint to list, so we can add a new value
if not isinstance(usr[kl], (list, tuple)):
usr[kl] = [usr[kl]]
# Convert values to list, if not list
if not isinstance(v, (list, tuple)):
v = [v]
# Now append to existing values
for x in v:
usr[kl].append(x)
else:
usr[kl] = v
logger.debug('Usr: {0}'.format(usr))
return usr
except Exception:

View File

@@ -36,7 +36,7 @@ from django.utils.translation import ugettext as _
from uds.core.ui.UserInterface import UserInterface
from uds.core import Environmentable
from uds.core import Serializable
import base64
from uds.core.util import encoders
import os.path
import sys
import logging
@@ -95,9 +95,6 @@ class Module(UserInterface, Environmentable, Serializable):
module.
'''
# : Which coded to use to encode module by default.
# : This overrides the Environmentable and Serializable Attribute, but in all cases we are using 'base64'
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
# : Basic name used to provide the administrator an "huma readable" form for the module
typeName = 'Base Module'
# : Internal type name, used by system to locate this module
@@ -174,7 +171,7 @@ class Module(UserInterface, Environmentable, Serializable):
data = file_.read()
file_.close()
if inBase64 == True:
return base64.encodestring(data)
return encoders.encode(data, 'base64')
else:
return data
@@ -199,7 +196,7 @@ class Module(UserInterface, Environmentable, Serializable):
'''
return [True, _("No connection checking method is implemented.")]
def __init__(self, environment, values=None):
def __init__(self, environment, values=None, uuid=None):
'''
Do not forget to invoke this in your derived class using
"super(self.__class__, self).__init__(environment, values)".
@@ -226,6 +223,7 @@ class Module(UserInterface, Environmentable, Serializable):
UserInterface.__init__(self, values)
Environmentable.__init__(self, environment)
Serializable.__init__(self)
self._uuid = uuid if uuid is not None else ''
def __str__(self):
return "Base Module"
@@ -268,6 +266,9 @@ class Module(UserInterface, Environmentable, Serializable):
'''
return _("No check method provided.")
def getUuid(self):
return self._uuid
def destroy(self):
'''
Invoked before deleting an module from database.

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

@@ -32,6 +32,8 @@
'''
from __future__ import unicode_literals
from uds.core.util import encoders
class Serializable(object):
'''
@@ -42,8 +44,6 @@ class Serializable(object):
- Initialize the object with default values
- Read values from seralized data
'''
# Codify codec constant
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
def __init__(self):
pass
@@ -79,17 +79,11 @@ class Serializable(object):
def serialize(self):
'''
Serializes and "obfuscates' the data.
The codec used to encode the string is obtained from the instance CODEC, so derived classes can
overwrite this attribute to set another codec
'''
return self.marshal().encode(self.CODEC)
return encoders.encode(self.marshal(), 'base64')
def unserialize(self, str_):
'''
des-obfuscates the data and then de-serializes it via unmarshal method
The codec used to decode the string is obtained from the instance CODEC, so derived classes can
overwrite this attribute to set another codec
'''
return self.unmarshal(str_.decode(self.CODEC))
return self.unmarshal(encoders.decode(str_, 'base64'))

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.1.0-DEVEL'
VERSION_STAMP = '20161001-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__ = '2016-04-15'
__updated__ = '2019-05-10'
logger = logging.getLogger(__name__)
authLogger = logging.getLogger('authLog')
@@ -70,7 +70,7 @@ def getUDSCookie(request, response=None, force=False):
if 'uds' not in request.COOKIES:
import random
import string
cookie = ''.join(random.choice(string.letters + string.digits) for _ in range(32)) # @UndefinedVariable
cookie = ''.join(random.SystemRandom().choice(string.letters + string.digits) for _ in range(32)) # @UndefinedVariable
if response is not None:
response.set_cookie('uds', cookie)
request.COOKIES['uds'] = cookie
@@ -106,7 +106,9 @@ def webLoginRequired(admin=False):
Decorator to set protection to access page
Look for samples at uds.core.web.views
'''
def decorator(view_func):
@wraps(view_func, assigned=available_attrs(view_func))
def _wrapped_view(request, *args, **kwargs):
'''
@@ -124,7 +126,9 @@ def webLoginRequired(admin=False):
return HttpResponseForbidden(_('Forbidden'))
return view_func(request, *args, **kwargs)
return _wrapped_view
return decorator
@@ -134,6 +138,7 @@ def trustedSourceRequired(view_func):
Decorator to set protection to access page
look for sample at uds.dispatchers.pam
'''
@wraps(view_func)
def _wrapped_view(request, *args, **kwargs):
'''
@@ -143,6 +148,7 @@ def trustedSourceRequired(view_func):
if net.ipInNetwork(request.ip, GlobalConfig.TRUSTED_SOURCES.get(True)) is False:
return HttpResponseForbidden()
return view_func(request, *args, **kwargs)
return _wrapped_view
@@ -159,7 +165,9 @@ def __registerUser(authenticator, authInstance, username):
request = getRequest()
usr = authenticator.getOrCreateUser(username, authInstance.getRealName(username))
usr = authenticator.getOrCreateUser(username, username)
usr.real_name = authInstance.getRealName(username)
usr.save()
if usr is not None and State.isActive(usr.state):
# Now we update database groups for this user
usr.getManager().recreateGroups(usr)
@@ -201,6 +209,7 @@ def authenticate(username, password, authenticator, useInternalAuthenticate=Fals
# If do not have any valid group
if gm.hasValidGroups() is False:
logger.info('User {} has been authenticated, but he does not belongs to any UDS know group')
return None
return __registerUser(authenticator, authInstance, username)
@@ -253,7 +262,7 @@ def authInfoUrl(authenticator):
Helper method, so we can get the info url for an authenticator
'''
from django.core.urlresolvers import reverse
if isinstance(authenticator, unicode) or isinstance(authenticator, str):
if isinstance(authenticator, (six.text_type, six.binary_type)):
name = authenticator
else:
name = authenticator.name
@@ -279,9 +288,9 @@ def webLogin(request, response, user, password):
user.updateLastAccess()
request.session.clear()
request.session[USER_KEY] = user.id
request.session[PASS_KEY] = CryptoManager.manager().xor(password, cookie) # Stores "bytes"
request.session[PASS_KEY] = cryptoManager().symCrypt(password, cookie) # Stores "bytes"
# Ensures that this user will have access through REST api if logged in through web interface
REST.Handler.storeSessionAuthdata(request.session, manager_id, user.name, get_language(), user.is_admin, user.staff_member)
REST.Handler.storeSessionAuthdata(request.session, manager_id, user.name, password, get_language(), user.is_admin, user.staff_member, cookie)
return True
@@ -293,7 +302,7 @@ def webPassword(request):
@param request: DJango Request
@return: Unscrambled user password
'''
return CryptoManager.manager().xor(request.session.get(PASS_KEY, ''), getUDSCookie(request)).decode('utf-8') # recover as original unicode string
return cryptoManager().symDecrpyt(request.session.get(PASS_KEY, ''), getUDSCookie(request)) # recover as original unicode string
def webLogout(request, exit_url=None):
@@ -331,7 +340,7 @@ def authLogLogin(request, authenticator, userName, logStr=''):
try:
user = authenticator.users.get(name=userName)
log.doLog(user, level,
'{0} from {1} where os is {3}'.format(logStr, request.ip, request.os['OS']), log.WEB
'{} from {} where OS is {}'.format(logStr, request.ip, request.os['OS']), log.WEB
)
except Exception:
pass

View File

@@ -37,6 +37,7 @@ from django.db.models import Q
from uds.models import DelayedTask as dbDelayedTask
from uds.models import getSqlDatetime
from uds.core.Environment import Environment
from uds.core.util import encoders
from socket import gethostname
from pickle import loads, dumps
from datetime import timedelta
@@ -44,7 +45,7 @@ import threading
import time
import logging
__updated__ = '2016-03-07'
__updated__ = '2018-03-02'
logger = logging.getLogger(__name__)
@@ -53,6 +54,7 @@ class DelayedTaskThread(threading.Thread):
'''
Class responsible of executing a delayed task in its own thread
'''
def __init__(self, taskInstance):
super(DelayedTaskThread, self).__init__()
self._taskInstance = taskInstance
@@ -68,7 +70,6 @@ class DelayedTaskRunner(object):
'''
Delayed task runner class
'''
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
# How often tasks r checked
granularity = 2
@@ -106,7 +107,9 @@ class DelayedTaskRunner(object):
try:
with transaction.atomic(): # Encloses
task = dbDelayedTask.objects.select_for_update().filter(filt).order_by('execution_time')[0] # @UndefinedVariable
taskInstanceDump = task.instance.decode(self.CODEC)
if task.insert_date > now + timedelta(seconds=30):
logger.warn('EXecuted {} due to insert_date being in the future!'.format(task.type))
taskInstanceDump = encoders.decode(task.instance, 'base64')
task.delete()
taskInstance = loads(taskInstanceDump)
except Exception:
@@ -123,7 +126,7 @@ class DelayedTaskRunner(object):
now = getSqlDatetime()
exec_time = now + timedelta(seconds=delay)
cls = instance.__class__
instanceDump = dumps(instance).encode(self.CODEC)
instanceDump = encoders.encode(dumps(instance), 'base64')
typeName = str(cls.__module__ + '.' + cls.__name__)
logger.debug('Inserting delayed task {0} with {1} bytes ({2})'.format(typeName, len(instanceDump), exec_time))

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

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

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-03-22'
__updated__ = '2019-05-08'
logger = logging.getLogger(__name__)
traceLogger = logging.getLogger('traceLog')
@@ -142,6 +142,12 @@ class UserServiceManager(object):
'''
Creates a new assigned deployed service for the publication and user indicated
'''
# First, honor maxPreparingServices
if self.canInitiateServiceFromDeployedService(ds) is False:
# Cannot create new
logger.warn('Too many preparing services. Creation of assigned service denied by max preparing services parameter. (login storm with insufficient cache?).')
raise ServiceNotReadyError()
if ds.service.getType().publicationType is not None:
dsp = ds.activePublication()
logger.debug('Creating a new assigned element for user {0} por publication {1}'.format(user, dsp))
@@ -182,6 +188,7 @@ class UserServiceManager(object):
ci = cache.getInstance()
state = ci.moveToCache(cacheLevel)
cache.cache_level = cacheLevel
cache.save(update_fields=['cache_level'])
logger.debug('Service State: {0} {1} {2}'.format(State.toString(state), State.toString(cache.state), State.toString(cache.os_state)))
if State.isRuning(state) and cache.isUsable():
cache.setState(State.PREPARING)
@@ -261,23 +268,31 @@ class UserServiceManager(object):
# Now try to locate 1 from cache already "ready" (must be usable and at level 1)
with transaction.atomic():
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE, os_state=State.USABLE)[:1]
if len(cache) == 0:
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE)[:1]
if len(cache) > 0:
if len(cache) != 0:
cache = cache[0]
cache.assignToUser(user)
cache.save() # Store assigned ASAP, we do not know how long assignToUser method of instance will take
# Ensure element is reserved correctly on DB
if ds.cachedUserServices().select_for_update().filter(user=None, uuid=cache.uuid).update(user=user, cache_level=0) != 1:
cache = None
else:
cache = None
if cache == None:
with transaction.atomic():
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE)[:1]
if len(cache) > 0:
cache = cache[0]
if ds.cachedUserServices().select_for_update().filter(user=None, uuid=cache.uuid).update(user=user, cache_level=0) != 1:
cache = None
else:
cache = None
if cache:
cache.assignToUser(user) # Store assigned ASAP, we do not know how long assignToUser method of instance will take
# Out of atomic transaction
if cache is not None:
logger.debug('Found a cached-ready service from {0} for user {1}, item {2}'.format(ds, user, cache))
events.addEvent(ds, events.ET_CACHE_HIT, fld1=ds.cachedUserServices().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.USABLE).count())
ci = cache.getInstance() # User Deployment instance
ci.assignToUser(user)
cache.updateData(ci)
cache.save()
return cache
# Cache missed
@@ -287,8 +302,10 @@ class UserServiceManager(object):
cache = ds.cachedUserServices().select_for_update().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.PREPARING)[:1]
if len(cache) > 0:
cache = cache[0]
cache.assignToUser(user)
cache.save()
if ds.cachedUserServices().select_for_update().filter(user=None, uuid=cache.uuid).update(user=user, cache_level=0) != 1:
cache = None
else:
cache.assignToUser(user) # Store assigned ASAP, we do not know how long assignToUser method of instance will take
else:
cache = None
@@ -296,10 +313,6 @@ class UserServiceManager(object):
if cache is not None:
logger.debug('Found a cached-preparing service from {0} for user {1}, item {2}'.format(ds, user, cache))
events.addEvent(ds, events.ET_CACHE_MISS, fld1=ds.cachedUserServices().filter(cache_level=services.UserDeployment.L1_CACHE, state=State.PREPARING).count())
ci = cache.getInstance() # User Deployment instance
ci.assignToUser(user)
cache.updateData(ci)
cache.save()
return cache
# Can't assign directly from L2 cache... so we check if we can create e new service in the limits requested
@@ -309,6 +322,7 @@ class UserServiceManager(object):
inAssigned = ds.assignedUserServices().filter(UserServiceManager.getStateFilter()).count()
# totalL1Assigned = inCacheL1 + inAssigned
if inAssigned >= ds.max_srvs: # cacheUpdater will drop necesary L1 machines, so it's not neccesary to check against inCacheL1
log.doLog(ds, log.WARN, 'Max number of services reached: {}'.format(ds.max_srvs), log.INTERNAL)
raise MaxServicesReachedError()
# Can create new service, create it
events.addEvent(ds, events.ET_CACHE_MISS, fld1=0)
@@ -360,6 +374,20 @@ class UserServiceManager(object):
UserServiceOpChecker.makeUnique(uService, ui, state)
return False
def reset(self, uService):
UserService.objects.update()
uService = UserService.objects.get(id=uService.id)
if uService.deployed_service.service.getType().canReset is False:
return
logger.debug('Reseting'.format(uService))
ui = uService.getInstance()
try:
ui.reset()
except Exception:
logger.exception('Reseting service')
def notifyPreconnect(self, uService, userName, protocol):
url = uService.getCommsUrl()
if url is None:
@@ -428,17 +456,44 @@ class UserServiceManager(object):
# All done
def requestLogoff(self, uService):
'''
Ask client to logoff user
'''
url = uService.getCommsUrl()
if url is None:
logger.error('Can\'t connect with actor (no actor or legacy actor)')
return
url += '/logoff'
try:
r = requests.post(url, data=json.dumps({}), headers={'content-type': 'application/json'}, verify=False, timeout=4)
r = json.loads(r.content)
logger.debug('Sent logoff to client using {}: {}'.format(url, r))
# In fact we ignore result right now
except Exception as e:
# TODO: Right now, this is an "experimental" feature, not supported on Apps (but will)
pass
# logger.info('Logoff requested but service was not listening: {}'.format(e, url))
# All done
def checkForRemoval(self, uService):
'''
This method is used by UserService when a request for setInUse(False) is made
This checks that the service can continue existing or not
'''
# uService = UserService.objects.get(id=uService.id)
if uService.publication is None:
return
if uService.publication.id != uService.deployed_service.activePublication().id:
logger.debug('Old revision of user service, marking as removable: {0}'.format(uService))
uService.remove()
osm = uService.deployed_service.osmanager
# If os manager says "machine is persistent", do not tray to delete "previous version" assigned machines
doPublicationCleanup = True if osm is None else not osm.getInstance().isPersistent()
if doPublicationCleanup:
if uService.publication is None:
return
if uService.publication.id != uService.deployed_service.activePublication().id:
logger.debug('Old revision of user service, marking as removable: {0}'.format(uService))
uService.remove()
def notifyReadyFromOsManager(self, uService, data):
try:
@@ -449,35 +504,48 @@ class UserServiceManager(object):
uService.updateData(ui)
if state == State.FINISHED:
logger.debug('Service is now ready')
uService.save()
elif uService.state in (State.USABLE, State.PREPARING): # We don't want to get active deleting or deleted machines...
uService.setState(State.PREPARING)
UserServiceOpChecker.makeUnique(uService, ui, state)
uService.save(update_fields=['os_state'])
except Exception as e:
logger.exception('Unhandled exception on notyfyReady: {}'.format(e))
UserService.setState(State.ERROR)
uService.setState(State.ERROR)
return
def getService(self, user, srcIp, idService, idTransport, doTest=True):
'''
Get service info from
'''
def locateUserService(self, user, idService, create=False):
kind, idService = idService[0], idService[1:]
logger.debug('Kind of service: {0}, idService: {1}'.format(kind, idService))
userService = None
if kind == 'A': # This is an assigned service
logger.debug('Getting A service {}'.format(idService))
userService = UserService.objects.get(uuid=idService)
userService = UserService.objects.get(uuid=idService, user=user)
userService.deployed_service.validateUser(user)
else:
ds = ServicePool.objects.get(uuid=idService)
# We first do a sanity check for this, if the user has access to this service
# If it fails, will raise an exception
ds.validateUser(user)
# Now we have to locate an instance of the service, so we can assign it to user.
userService = self.getAssignationForUser(ds, user)
if create: # getAssignation, if no assignation is found, tries to create one
userService = self.getAssignationForUser(ds, user)
else: # Sometimes maybe we only need to locate the existint user service
userService = self.getExistingAssignationForUser(ds, user)
logger.debug('Found service: {0}'.format(userService))
return userService
def getService(self, user, srcIp, idService, idTransport, doTest=True):
'''
Get service info from
'''
userService = self.locateUserService(user, idService, create=True)
# Early log of "access try" so we can imagine what is going on
userService.setConnectionSource(srcIp, 'unknown')
if userService.isInMaintenance() is True:
raise ServiceInMaintenanceMode()
@@ -502,17 +570,17 @@ class UserServiceManager(object):
# If transport is not available for the request IP...
if trans.validForIp(srcIp) is False:
raise InvalidServiceException()
msg = 'The requested transport {} is not valid for {}'.format(trans.name, srcIp)
logger.error(msg)
raise InvalidServiceException(msg)
if user is not None:
userName = user.name
if doTest is False:
# traceLogger.info('GOT service "{}" for user "{}" with transport "{}" (NOT TESTED)'.format(userService.name, userName, trans.name))
return (None, userService, None, trans, None)
serviceNotReadyCode = 0x0001
ip = 'unknown'
# Test if the service is ready
@@ -522,6 +590,7 @@ class UserServiceManager(object):
# If ready, show transport for this service, if also ready ofc
iads = userService.getInstance()
ip = iads.getIp()
userService.logIP(ip) # Update known ip
if self.checkUuid(userService) is False: # Machine is not what is expected
serviceNotReadyCode = 0x0004
@@ -533,14 +602,15 @@ class UserServiceManager(object):
serviceNotReadyCode = 0x0003
itrans = trans.getInstance()
if itrans.isAvailableFor(userService, ip):
userService.setConnectionSource(srcIp, 'unknown')
# userService.setConnectionSource(srcIp, 'unknown')
log.doLog(userService, log.INFO, "User service ready", log.WEB)
self.notifyPreconnect(userService, itrans.processedUser(userService, user), itrans.protocol)
traceLogger.info('READY on service "{}" for user "{}" with transport "{}" (ip:{})'.format(userService.name, userName, trans.name, ip))
return (ip, userService, iads, trans, itrans)
else:
log.doLog(userService, log.WARN, "User service is not accessible (ip {0})".format(ip), log.TRANSPORT)
logger.debug('Transport is not ready for user service {0}'.format(userService))
message = itrans.getCustomAvailableErrorMsg(userService, ip)
log.doLog(userService, log.WARN, message, log.TRANSPORT)
logger.debug('Transport is not ready for user service {}: {}'.format(userService, message))
else:
logger.debug('Ip not available from user service {0}'.format(userService))
else:

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__ = '2016-10-03'
__updated__ = '2019-02-24'
STORAGE_KEY = 'osmk'
@@ -177,11 +177,9 @@ class OSManager(Module):
def logKnownIp(self, userService, ip):
userService.logIP(ip)
def toReady(self, userService):
userService.setProperty('loginsCounter', '0')
def loggedIn(self, userService, userName=None, save=True):
'''
This method:
@@ -204,17 +202,20 @@ class OSManager(Module):
knownUserIP = userService.src_ip + ':' + userService.src_hostname
knownUserIP = knownUserIP if knownUserIP != ':' else 'unknown'
if userName is None:
userName = 'unknown'
addEvent(userService.deployed_service, ET_LOGIN, fld1=userName, fld2=knownUserIP, fld3=serviceIp, fld4=fullUserName)
log.doLog(userService, log.INFO, "User {0} has logged in".format(userName), log.OSMANAGER)
log.useLog('login', uniqueId, serviceIp, userName, knownUserIP, fullUserName)
log.useLog('login', uniqueId, serviceIp, userName, knownUserIP, fullUserName, userService.friendly_name, userService.deployed_service.name)
counter = int(userService.getProperty('loginsCounter', '0')) + 1
userService.setProperty('loginsCounter', six.text_type(counter))
if save:
userService.save()
userService.save(update_fields=['in_use', 'in_use_date', 'data'])
def loggedOut(self, userService, userName=None, save=True):
'''
@@ -247,14 +248,24 @@ class OSManager(Module):
knownUserIP = userService.src_ip + ':' + userService.src_hostname
knownUserIP = knownUserIP if knownUserIP != ':' else 'unknown'
if userName is None:
userName = 'unknown'
addEvent(userService.deployed_service, ET_LOGOUT, fld1=userName, fld2=knownUserIP, fld3=serviceIp, fld4=fullUserName)
log.doLog(userService, log.INFO, "User {0} has logged out".format(userName), log.OSMANAGER)
log.useLog('logout', uniqueId, serviceIp, userName, knownUserIP, fullUserName)
log.useLog('logout', uniqueId, serviceIp, userName, knownUserIP, fullUserName, userService.friendly_name, userService.deployed_service.name)
if save:
userService.save()
userService.save(update_fields=['in_use', 'in_use_date', 'data'])
def isPersistent(self):
'''
When a publication if finished, old assigned machines will be removed if this value is True.
Defaults to False
'''
return False
def __str__(self):
return "Base OS Manager"

View File

@@ -35,13 +35,15 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext, ugettext_noop as _
from uds.core.ui.UserInterface import UserInterface
from uds.core.util import encoders
import datetime
import six
import logging
logger = logging.getLogger(__name__)
__updated__ = '2015-06-21'
__updated__ = '2017-11-15'
class Report(UserInterface):
@@ -126,7 +128,7 @@ class Report(UserInterface):
'''
data = self.generate()
if self.encoded:
return data.encode('base64').replace('\n', '')
return encoders.encode(data, 'base64', asText=True).replace('\n', '')
else:
return data

View File

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

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

@@ -37,16 +37,18 @@ from django.utils.translation import ugettext_noop as _
from uds.core.util import OsDetector
from uds.core import Module
from uds.core.transports import protocols
from uds.core.util import encoders
import logging
__updated__ = '2017-08-02'
__updated__ = '2017-11-15'
logger = logging.getLogger(__name__)
DIRECT_GROUP = _('Direct')
TUNNELED_GROUP = _('Tunneled')
class Transport(Module):
'''
An OS Manager is responsible for communication the service the different actions to take (i.e. adding a windows machine to a domain)
@@ -113,6 +115,13 @@ class Transport(Module):
'''
return False
def getCustomAvailableErrorMsg(self, userService, ip):
'''
Returns a customized error message, that will be used when a service fails to check "isAvailableFor"
Override this in yours transports if needed
'''
return "Not accessible (using service ip {0})".format(ip)
@classmethod
def supportsProtocol(cls, protocol):
if isinstance(protocol, (list, tuple)):
@@ -181,6 +190,14 @@ from __future__ import unicode_literals
raise Exception('The transport {transport.name} is not supported on your platform.')
'''.format(service=userService, transport=transport)
def getEncodedTransportScript(self, userService, transport, ip, os, user, password, request):
"""
Encodes the script so the client can understand it
"""
script = self.getUDSTransportScript(userService, transport, ip, os, user, password, request)
logger.debug('Transport script: {}'.format(script))
return encoders.encode(encoders.encode(script, 'bz2'), 'base64', asText=True).replace('\n', '')
def getLink(self, userService, transport, ip, os, user, password, request):
'''
Must override if transport does provides its own link

View File

@@ -87,6 +87,7 @@ class gui(object):
PARAMETERS_TAB = ugettext_noop('Parameters')
CREDENTIALS_TAB = ugettext_noop('Credentials')
TUNNEL_TAB = ugettext_noop('Tunnel')
DISPLAY_TAB = ugettext_noop('Display')
# : Static Callbacks simple registry
callbacks = {}
@@ -347,6 +348,7 @@ class gui(object):
tooltip = _('Other info'), rdonly = True)
'''
def __init__(self, **options):
super(self.__class__, self).__init__(**options)
self._type(gui.InputField.TEXT_TYPE)
@@ -375,6 +377,7 @@ class gui(object):
defvalue = '443', order = 1, tooltip = _('Port (usually 443)'),
required = True)
'''
def __init__(self, **options):
super(self.__class__, self).__init__(**options)
minValue = options.get('minValue', '987654321')
@@ -463,6 +466,7 @@ class gui(object):
required = True)
'''
def __init__(self, **options):
super(self.__class__, self).__init__(**options)
self._type(gui.InputField.PASSWORD_TYPE)
@@ -498,6 +502,7 @@ class gui(object):
self.hidden.setDefValue(self.parent().serialize())
'''
def __init__(self, **options):
super(self.__class__, self).__init__(**options)
self._isSerializable = options.get('serializable', '') != ''
@@ -525,6 +530,7 @@ class gui(object):
tooltip = _('If checked, will use a ssl connection'))
'''
def __init__(self, **options):
super(self.__class__, self).__init__(**options)
self._type(gui.InputField.CHECKBOX_TYPE)
@@ -628,6 +634,7 @@ class gui(object):
ev = gui.HiddenField() # ....
'''
def __init__(self, **options):
super(self.__class__, self).__init__(**options)
self._data['values'] = options.get('values', [])
@@ -647,6 +654,7 @@ class gui(object):
self._data['values'] = values
class ImageChoiceField(InputField):
def __init__(self, **options):
super(self.__class__, self).__init__(**options)
self._data['values'] = options.get('values', [])
@@ -692,6 +700,7 @@ class gui(object):
{'id': '1', 'text': 'datastore1' } ]
)
'''
def __init__(self, **options):
super(self.__class__, self).__init__(**options)
self._data['values'] = options.get('values', [])
@@ -750,6 +759,7 @@ class gui(object):
'''
Image field
'''
def __init__(self, **options):
super(self.__class__, self).__init__(**options)
self._type(gui.InputField.TEXT_TYPE)
@@ -758,6 +768,7 @@ class gui(object):
'''
Informational field (no input is done)
'''
def __init__(self, **options):
super(self.__class__, self).__init__(**options)
self._type(gui.InputField.INFO_TYPE)
@@ -768,6 +779,7 @@ class UserInterfaceType(type):
Metaclass definition for moving the user interface descriptions to a usable
better place
'''
def __new__(cls, classname, bases, classDict):
newClassDict = {}
_gui = {}

View File

@@ -32,6 +32,7 @@
'''
from uds.core.Serializable import Serializable
from uds.core.util import encoders
import pickle
import timeit
@@ -67,11 +68,6 @@ class AutoAttributes(Serializable):
Access attrs as "self._attr1, self._attr2"
'''
# : This codec is not intended to override Serializable codec
# : Serializable codec is for encoding marshaled data,
# : while this codec is for encoding pickled data from autoattributes
ACODEC = 'zip'
def __init__(self, **kwargs):
self.declare(**kwargs)
@@ -93,13 +89,17 @@ class AutoAttributes(Serializable):
self.dict = d
def marshal(self):
return '\2'.join(['%s\1%s' % (k, pickle.dumps(v)) for k, v in self.dict.iteritems()]).encode(AutoAttributes.ACODEC)
return encoders.encode('\2'.join(['%s\1%s' % (k, pickle.dumps(v)) for k, v in self.dict.iteritems()]), 'bz2')
def unmarshal(self, data):
if data == '': # Can be empty
return
# We keep original data (maybe incomplete)
for pair in data.decode(AutoAttributes.ACODEC).split('\2'):
try:
data = encoders.decode(data, 'bz2')
except Exception: # With old zip encoding
data = encoders.decode(data, 'zip')
for pair in data.split('\2'):
k, v = pair.split('\1')
self.dict[k] = pickle.loads(str(v))

View File

@@ -34,6 +34,7 @@ from __future__ import unicode_literals
from django.db import transaction
import uds.models.Cache
from uds.models import getSqlDatetime
from uds.core.util import encoders
from datetime import datetime, timedelta
import hashlib
import logging
@@ -47,7 +48,6 @@ class Cache(object):
misses = 0
DEFAULT_VALIDITY = 60
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
def __init__(self, owner):
self._owner = owner.encode('utf-8')
@@ -67,13 +67,17 @@ class Cache(object):
expired = now > c.created + timedelta(seconds=c.validity)
if expired:
return defValue
val = pickle.loads(c.value.decode(Cache.CODEC))
val = pickle.loads(encoders.decode(c.value, 'base64'))
Cache.hits += 1
return val
except uds.models.Cache.DoesNotExist: # @UndefinedVariable
Cache.misses += 1
logger.debug('key not found: {}'.format(skey))
return defValue
except Exception as e:
Cache.misses += 1
logger.debug('Cache inaccesible: {}:{}'.format(skey, e))
return defValue
def remove(self, skey):
'''
@@ -97,7 +101,7 @@ class Cache(object):
if validity is None:
validity = Cache.DEFAULT_VALIDITY
key = self.__getKey(skey)
value = pickle.dumps(value).encode(Cache.CODEC)
value = encoders.encode(pickle.dumps(value), 'base64')
now = getSqlDatetime()
try:
uds.models.Cache.objects.create(owner=self._owner, key=key, value=value, created=now, validity=validity) # @UndefinedVariable
@@ -107,7 +111,7 @@ class Cache(object):
c.owner = self._owner
c.key = key
c.value = value
c.created = datetime.now()
c.created = now
c.validity = validity
c.save()

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
@@ -50,6 +52,7 @@ logger = logging.getLogger(__name__)
class FileStorage(Storage):
def __init__(self, *args, **kwargs):
self._base_url = getattr(settings, 'FILE_STORAGE', '/files')
if self._base_url[-1] != '/':
@@ -60,6 +63,7 @@ class FileStorage(Storage):
try:
cache = caches[cacheName]
except:
logger.info('No cache for FileStorage configured.')
cache = None
self.cache = cache
@@ -73,7 +77,6 @@ class FileStorage(Storage):
Storage.__init__(self, *args, **kwargs)
def get_valid_name(self, name):
return name.replace('\\', os.path.sep)
@@ -119,7 +122,6 @@ class FileStorage(Storage):
return
self.cache.delete(self._getKey(name))
def _open(self, name, mode='rb'):
f = six.BytesIO(self._dbFileForReadOnly(name).data)
f.name = name
@@ -131,9 +133,11 @@ class FileStorage(Storage):
try:
f = self._dbFileForReadWrite(name)
except DBFile.DoesNotExist:
f = DBFile.objects.create(owner=self.owner, name=name)
now = getSqlDatetime()
f = DBFile.objects.create(owner=self.owner, name=name, created=now, modified=now)
f.data = content.read()
f.modified = getSqlDatetime()
f.save()
# Store on cache also
@@ -173,7 +177,9 @@ class FileStorage(Storage):
except DBFile.DoesNotExist:
return None
class CompressorFileStorage(FileStorage):
def __init__(self, *args, **kwargs):
FileStorage.__init__(self, *args, **dict(kwargs, owner='compressor'))

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

@@ -34,6 +34,7 @@ from __future__ import unicode_literals
from django.db import transaction
from uds.models import Storage as dbStorage
from uds.core.util import encoders
import hashlib
import logging
import pickle
@@ -42,7 +43,6 @@ logger = logging.getLogger(__name__)
class Storage(object):
CODEC = 'base64' # Can be zip, hez, bzip, base64, uuencoded
def __init__(self, owner):
self._owner = owner.encode('utf-8')
@@ -57,7 +57,7 @@ class Storage(object):
key = self.__getKey(skey)
if isinstance(data, unicode):
data = data.encode('utf-8')
data = data.encode(Storage.CODEC)
data = encoders.encode(data, 'base64')
attr1 = '' if attr1 is None else attr1
try:
dbStorage.objects.create(owner=self._owner, key=key, data=data, attr1=attr1) # @UndefinedVariable
@@ -79,7 +79,7 @@ class Storage(object):
key = self.__getKey(skey)
logger.debug('Accesing to {0} {1}'.format(skey, key))
c = dbStorage.objects.get(pk=key) # @UndefinedVariable
val = c.data.decode(Storage.CODEC)
val = encoders.decode(c.data, 'base64')
if fromPickle:
return val
@@ -103,7 +103,7 @@ class Storage(object):
def getPickleByAttr1(self, attr1):
try:
return pickle.loads(dbStorage.objects.get(owner=self._owner, attr1=attr1).data.decode(Storage.CODEC)) # @UndefinedVariable
return pickle.loads(encoders.decode(dbStorage.objects.filter(owner=self._owner, attr1=attr1)[0].data, 'base64')) # @UndefinedVariable
except Exception:
return None
@@ -133,7 +133,7 @@ class Storage(object):
query = dbStorage.objects.filter(owner=self._owner, attr1=attr1) # @UndefinedVariable
for v in query:
yield v.data.decode(Storage.CODEC)
yield encoders.decode(v.data, 'base64')
def filter(self, attr1):
if attr1 is None:
@@ -142,7 +142,7 @@ class Storage(object):
query = dbStorage.objects.filter(owner=self._owner, attr1=attr1) # @UndefinedVariable
for v in query: # @UndefinedVariable
yield (v.key, v.data.decode(Storage.CODEC), v.attr1)
yield (v.key, encoders.decode(v.data, 'base64'), v.attr1)
def filterPickle(self, attr1=None):
for v in self.filter(attr1):
@@ -150,7 +150,6 @@ class Storage(object):
@staticmethod
def delete(owner=None):
logger.info("Deleting storage items")
if owner is None:
objects = dbStorage.objects.all() # @UndefinedVariable
else:

View File

@@ -46,8 +46,7 @@ import six
import bitarray
import logging
__updated__ = '2016-10-31'
__updated__ = '2017-12-12'
logger = logging.getLogger(__name__)
@@ -113,14 +112,14 @@ class CalendarChecker(object):
return data
def _updateEvents(self, checkFrom, startEvent=True):
next_event = None
for rule in self.calendar.rules.all():
if rule.start > checkFrom or (rule.end is not None and rule.end < checkFrom.date()):
# logger.debug('RULE: start = {}, checkFrom = {}, end'.format(rule.start.date(), checkFrom.date()))
if rule.end is not None and rule.end < checkFrom.date():
continue
# logger.debug('Rule in check interval...')
if startEvent:
event = rule.as_rrule().after(checkFrom) # At start
else:
@@ -162,7 +161,7 @@ class CalendarChecker(object):
Returns next event for this interval
Returns a list of two elements. First is datetime of event begining, second is timedelta of duration
'''
logger.debug('Obtainint nextEvent')
logger.debug('Obtaining nextEvent')
if checkFrom is None:
checkFrom = getSqlDatetime()
@@ -173,7 +172,7 @@ class CalendarChecker(object):
next_event = CalendarChecker.cache.get(cacheKey, None)
if next_event is None:
logger.debug('Regenerating cached nextEvent')
next_event = self._updateEvents(checkFrom - offset, startEvent) # We substract on checkin, so we can take into account for next execution the "offset" on start & end (just the inverse of current, so we substract it)
next_event = self._updateEvents(checkFrom + offset, startEvent) # We substract on checkin, so we can take into account for next execution the "offset" on start & end (just the inverse of current, so we substract it)
if next_event is not None:
next_event += offset
CalendarChecker.cache.put(cacheKey, next_event, 3600)
@@ -183,7 +182,5 @@ class CalendarChecker(object):
return next_event
def debug(self):
return "Calendar checker for {}".format(self.calendar)

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

@@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2017 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
import six
import codecs
def __toBinary(data):
if isinstance(data, six.text_type):
return data.encode('utf8')
return data
def encode(data, encoder, asText=False):
res = codecs.encode(__toBinary(data), encoder)
if asText:
return res.decode('utf8')
return res
def decode(data, encoder, asText=False):
res = codecs.decode(__toBinary(data), encoder)
if asText:
return res.decode('utf8')
return res

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

@@ -27,6 +27,8 @@
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from __future__ import unicode_literals
from uds.core.util.Config import GlobalConfig
from django.http import HttpResponseRedirect
import logging
logger = logging.getLogger(__name__)
@@ -42,3 +44,31 @@ class XUACompatibleMiddleware(object):
if response.get('content-type', '').startswith('text/html'):
response['X-UA-Compatible'] = 'IE=edge'
return response
class RedirectMiddleware(object):
NO_REDIRECT = [
'rest',
'pam',
'guacamole',
]
def process_request(self, request):
full_path = request.get_full_path()
redirect = True
for nr in RedirectMiddleware.NO_REDIRECT:
if full_path.startswith('/' + nr):
redirect = False
break
if GlobalConfig.REDIRECT_TO_HTTPS.getBool() and request.is_secure() is False and redirect:
if request.method == 'POST':
url = request.build_absolute_uri(GlobalConfig.LOGIN_URL.get())
else:
url = request.build_absolute_uri(full_path)
url = url.replace('http://', 'https://')
return HttpResponseRedirect(url)
@staticmethod
def registerException(path):
RedirectMiddleware.NO_REDIRECT.append(path)

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

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