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

246 Commits
1.9 ... 2.0

Author SHA1 Message Date
Adolfo Gómez García
15a76f3b9b Small 2.0 fix 2017-04-07 10:47:57 +02:00
Adolfo Gómez García
a3110d4623 fixed tunneld cord access 2017-03-18 11:48:52 +01:00
Adolfo Gómez García
92c3fbd827 Update checkuuid timeout from 2 to 5 2016-10-14 10:58:41 +02:00
Adolfo Gómez García
ea49e18f80 Now in case of connection error on checkuuid, the result will be that the check is correcot 2016-10-14 06:51:10 +02:00
Adolfo Gómez García
393819bc94 backported fix from 2.1 2016-10-11 07:53:31 +02:00
Adolfo Gómez García
7ab956fa4a minor fixes 2016-10-10 07:37:41 +02:00
Adolfo Gómez García
17044d38bd Improvement on ready setting logged in counter to 0 2016-10-03 14:23:19 +02:00
Adolfo Gómez García
0dc461c30b Fixed setPropert 2016-10-03 13:42:24 +02:00
Adolfo Gómez García
ced5e06ff0 Added a counter posibility to login-logout 2016-09-19 16:14:54 +02:00
Adolfo Gómez García
5e848cbc8c Fixed defvalue from 4 to 3 for ovirt, so migrations works fine from 1.9 to 2.x 2016-09-11 23:02:57 +02:00
Adolfo Gómez García
f924604217 Backport for ovirt 4.0 2016-09-11 22:21:12 +02:00
Adolfo Gómez García
efc59c0124 fixed unresolved imports 2016-09-11 22:15:04 +02:00
Adolfo Gómez García
2eaa447155 Includede persistent machines check idle also. 2016-09-07 16:49:40 +02:00
Adolfo Gómez García
3528424892 Fixed meta gruoups edition 2016-08-25 13:21:16 +02:00
Adolfo Gómez García
2eef7222d5 Backported guacamole for 2.0 availability 2016-08-17 20:39:37 +02:00
Adolfo Gómez García
0d28a5b5f7 Backported guacamole for 2.0 availability 2016-08-17 20:38:56 +02:00
Adolfo Gómez García
e0e7e26234 Fixed Translations 2016-07-28 13:33:28 +02:00
Adolfo Gómez García
2674563535 Fixed template config removal on publication failure 2016-07-27 18:22:37 +02:00
Adolfo Gómez García
a864fbac96 Publication now will correctly fail if the template cannot be cloned 2016-07-27 17:33:45 +02:00
Adolfo Gómez García
51c32a4350 Fixed for OpenNebula 5 (seems to work fine with 4 & 5 right now) 2016-07-26 16:29:40 +02:00
Adolfo Gómez García
ec95f27ee6 Updated translations && added server-keyboard-layout to guacamole 2016-07-26 13:44:36 +02:00
Adolfo Gómez García
15ce5aa938 Added failsafe to html5 keyboard 2016-07-26 12:58:56 +02:00
Adolfo Gómez García
39b353fb24 More Fixes 2016-07-22 17:57:45 +02:00
Adolfo Gómez García
7a0c8aa977 More Fixes for OpenNebula 4.x (for new API implementation) 2016-07-22 17:40:03 +02:00
Adolfo Gómez García
664418d4dc More fixes for OpenNebula 2016-07-22 16:54:09 +02:00
Adolfo Gómez García
381cf47abf * Fixed RDP to allow some optional parameters on linux clients
* dixed OpenNebula support
2016-07-21 13:51:31 +02:00
Adolfo Gómez García
1578c92a88 Adding support for OpenNebula 5.0 2016-07-18 20:12:38 +02:00
Adolfo Gómez García
6827380e99 Added - to title for xfreerdp 2016-07-15 15:33:44 +02:00
Adolfo Gómez García
62f637790c Fixed changelog of publications not being displayed on dashboard 2016-07-12 11:33:50 +02:00
Adolfo Gómez García
bf5311b5af Added sessions cleanup to scheduled task list 2016-07-12 01:29:00 +02:00
Adolfo Gómez García
d0e0418ede Fixed check due 2.0 just works only with opennebula 4.x due to api
changes on 5.x. 2.1 will cover both vversions
2016-07-11 22:16:46 +02:00
Adolfo Gómez García
243cbbfd4b Fixed log removal for cache & assigned services admin tables 2016-07-11 22:04:58 +02:00
Adolfo Gómez García
e7984d5a8e Fixed javascript error 2016-07-11 14:14:10 +02:00
Adolfo Gómez García
eacce7adda fixed images on chrome 2016-07-11 13:44:27 +02:00
Adolfo Gómez García
622e8629ff Fixed image removal on service pool group 2016-07-11 12:43:22 +02:00
Adolfo Gómez García
50e368ee7a * Added owner to dbfile, so we can remove later them selectively
* Fixed samples
2016-07-10 20:56:32 +02:00
Adolfo Gómez García
6766fe41fe Signed-off-by: Adolfo Gómez García <dkmaster@dkmon.com> 2016-07-09 02:59:55 +02:00
Adolfo Gómez
ba35968e8d Forgotten comma 2016-07-07 14:22:35 +02:00
Adolfo Gómez García
4adfac1c02 added comments field to index view 2016-07-07 13:10:18 +02:00
Adolfo Gómez García
437865b278 * Fixed a bug on Services Pool Groups Images
* Fixed a couple bugs on OpenNebula"
2016-07-05 15:40:26 +02:00
Adolfo Gómez García
793b6c9004 Fixed RDP to allow freerdp from linux to select audio controller 2016-06-24 11:10:53 +02:00
Adolfo Gómez García
96bd117622 Comment fix 2016-06-15 00:58:33 +02:00
Adolfo Gómez García
8b2df76582 Small fix so HTML5 Transport remove leading/trailing whitespaces 2016-06-13 11:39:07 +02:00
Adolfo Gómez García
4196883bfb Fixed Timings on uds Actor
Fixed oVirt to allow USB Configuration on service (for spice). Defaults to not allow.
2016-06-04 20:02:34 +02:00
Adolfo Gómez García
8585d10623 Again, fixing deadline... 2016-06-02 09:57:43 +02:00
Adolfo Gómez García
c715b92ed0 fixed getDeadline 2016-06-01 11:12:12 +02:00
Adolfo Gómez García
a3726399f1 Fixed Calendars access restrictions 2016-05-31 12:04:53 +02:00
Adolfo Gómez García
8ce3efc7ee Fixed to allow 2.0 to work with 1.9 windows actor 2016-05-30 09:33:53 +02:00
Adolfo Gómez García
0bd3e1ac72 * Added alert icon for calender restricted services
* Fixed spice to add fullscreen support on transport definition & a bug
to allow using it.
* Fixed OS Detector to include "unknown" as an os on "all OSs"
2016-05-27 13:39:51 +02:00
Adolfo Gómez García
c2d4e995e7 Fixes & tests on UDS Actor for honoring max session duration (got from
calendars)
2016-05-24 07:40:53 +02:00
Adolfo Gómez García
1b71fef8b4 Fixed about to include build version && also upgraded it a bit 2016-05-20 13:34:53 +02:00
Adolfo Gómez García
074a4d525d Fixed up about dialog 2016-05-20 10:28:00 +02:00
Adolfo Gómez García
36e19574e0 Added version stamp 2016-05-20 09:50:53 +02:00
Adolfo Gómez García
ef9165b3a2 space :( 2016-05-20 09:39:19 +02:00
Adolfo Gómez García
14cddbb210 Forgot removing comments & try-catch block :) 2016-05-20 09:38:46 +02:00
Adolfo Gómez García
2d14884454 * Fixed time picker not working on chrome
* Added warn on service being replaced by new publication
2016-05-20 09:37:12 +02:00
Adolfo Gómez García
ba4eeffc77 * Fixed client to better show errors
* Adding new publication warn for currently logged in users
2016-05-19 13:50:52 +02:00
Adolfo Gómez García
59179b818e Added mechanics to recover in case of scheduler task that got stuck 2016-05-18 12:22:11 +02:00
Adolfo Gómez García
dbcdc84b3b * Now cleanUDS cleans up possible locks also.
This command is designed to be executed with UDS backend stopped, so be
care!! :)
* Added deadLine calculation to ServicePool based on access calendars
2016-05-18 10:31:43 +02:00
Adolfo Gómez García
c73dae361f Fixed headers replication on UDS Reports 2016-05-17 09:35:53 +02:00
Adolfo Gómez García
ec29371b41 Merge branch 'master' of github.com:dkmstr/openuds 2016-05-06 13:30:18 +02:00
Adolfo Gómez García
9365f5937b Advancend & sync 2016-05-06 13:30:06 +02:00
Adolfo Gómez García
971015d33a Advancend & sync 2016-05-06 13:29:29 +02:00
Adolfo Gómez García
7a742c043b Merge remote-tracking branch 'origin/v1.9' 2016-05-03 08:15:20 +02:00
Adolfo Gómez García
c08a0cb0ed Fixed a bug not allowing linux machines to register when using an case sensitive database0 2016-05-03 08:15:12 +02:00
Adolfo Gómez García
b5926dec6f Fixed a bug on free edition not allowing to register correctly linux machines 2016-05-03 08:14:14 +02:00
Adolfo Gómez García
2fdcdb014f Merge remote-tracking branch 'origin/v1.9' 2016-04-29 11:16:13 +02:00
Adolfo Gómez García
bd70a6290e fixed to work correctly on free 2016-04-29 11:15:07 +02:00
Adolfo Gómez García
3afa96f1c5 Fixed spice scripts 2016-04-28 12:50:30 +02:00
Adolfo Gómez García
5da12a8091 Fixed & Tested that now we can open multiple redirections over same
tunnel.
2016-04-28 12:49:23 +02:00
Adolfo Gómez García
08eeff5604 Fixed duplicate declaration 2016-04-28 10:55:34 +02:00
Adolfo Gómez García
169a946a03 Fixed small bug on return clone 2016-04-28 09:34:43 +02:00
Adolfo Gómez García
94842ce0ef A better implementation for shared connections 2016-04-28 09:34:03 +02:00
Adolfo Gómez García
0fb7d5ed1b * Fixed service pool group setup bug
* Fix to allow some kind of connections to (as spice), to open two
different tunnels (We have to test the fix)
2016-04-28 08:34:47 +02:00
Adolfo Gómez García
2021fd69ec *Merged fixes for UDS Client 2016-04-27 09:59:09 +02:00
Adolfo Gómez García
813764a100 Fixed UDS Client to allow host names with unicode characters 2016-04-27 09:43:08 +02:00
Adolfo Gómez García
cbd9330907 * Fixed Login with only one authenticator (not working before)
* Added cache flush on start for FileStorage to avoid problems
2016-04-27 06:59:34 +02:00
Adolfo Gómez García
a250cf4aef Fixed calendar access cache 2016-04-26 16:04:29 +02:00
Adolfo Gómez García
50bc3cd3ef Fixing up things 2016-04-26 15:13:54 +02:00
Adolfo Gómez García
4fb863cfa7 * Fixed several literals
* Added config type field for "choices"
* removed old version snippets
2016-04-25 12:39:58 +02:00
Adolfo Gómez García
3a1bd1eed3 Fixed query to obtain restraineds 2016-04-25 11:16:47 +02:00
Adolfo Gómez García
bce3e429cf Fixed ammend 2016-04-22 15:16:05 +02:00
Adolfo Gómez García
b5baea184f Merged 1.9 update for behind-proxy support 2016-04-22 15:12:28 +02:00
Adolfo Gómez García
42cbad4117 Merged 1.9 update for behind-proxy support 2016-04-22 15:04:36 +02:00
Adolfo Gómez García
09f329db62 Allow UDS behind a proxy 2016-04-22 15:03:06 +02:00
Adolfo Gómez García
6b5f9d266d Allow UDS behind a proxy 2016-04-22 14:59:34 +02:00
Adolfo Gómez García
242d9b5e6e Advancing with semantic 2016-04-22 07:05:04 +02:00
Adolfo Gómez García
99b17e573c adding semantic 2016-04-22 05:20:30 +02:00
Adolfo Gómez García
701edb91f1 adding semantic 2016-04-22 05:19:52 +02:00
Adolfo Gómez García
f0627db09f Removed legacy html templates.
Added select rendering to template so we can use whatever styles we like
2016-04-22 03:06:00 +02:00
Adolfo Gómez García
56a579e11b Fixed spanish translations 2016-04-21 10:40:36 +02:00
Adolfo Gómez García
4427448eca Updated translations & added proxy support for reverse auth, etc... 2016-04-20 15:25:41 +02:00
Adolfo Gómez García
203e2fcdd0 Added fast navigation to administration interface 2016-04-20 11:37:04 +02:00
Adolfo Gómez García
f7fa92e6c1 Added info to services 2016-04-19 11:17:34 +02:00
Adolfo Gómez García
425257a464 adding information to services, so we can figure whats going on from services list 2016-04-19 10:04:44 +02:00
Adolfo Gómez García
0acc07ebb3 Fixed log table 2016-04-19 05:11:34 +02:00
Adolfo Gómez García
030078a619 Added tabs to a bunch of forms on administration (forms are getting too
big right now)
2016-04-18 11:50:54 +02:00
Adolfo Gómez García
0e6ca4c188 Fixing translations 2016-04-18 07:09:03 +02:00
Adolfo Gómez García
c7d5a1c928 Updated translations 2016-04-18 07:01:51 +02:00
Adolfo Gómez García
e636a4afcd fixhed weblogout 2016-04-15 16:57:16 +02:00
Adolfo Gómez García
8b76324ffc Squashed migration from 1 to 16 (until v1.9). Will keep old migrations until 2.1 release 2016-04-14 14:17:20 +02:00
Adolfo Gómez García
8fe1e55770 Removed RGS migration from 2.0. You will need to at least upgrade to 1.7 before going to 2.0 2016-04-14 13:50:00 +02:00
Adolfo Gómez García
caae694628 Merge branch 'master' of github.com:dkmstr/openuds 2016-04-13 21:32:51 -07:00
Adolfo Gómez García
268e9d551a syntax fixes 2016-04-13 21:32:42 -07:00
Adolfo Gómez García
07137c2416 Several improvements to UDS Client connector 2016-04-14 06:01:41 +02:00
Adolfo Gómez García
273b2a59c4 fixed an error with edit list 2016-04-14 05:14:58 +02:00
Adolfo Gómez García
63364f4e72 added wait to forwarthread for rdp tunnel 2016-04-13 08:05:46 +02:00
Adolfo Gómez García
abc9622d53 Fixed 2.0 client bugs 2016-04-13 07:24:41 +02:00
Adolfo Gómez García
9cd7e2f67b Added a few adds to client & Updated required version for 2.0.0 server to 2.0.0 client 2016-04-12 11:22:29 +02:00
Adolfo Gómez García
9277d3b5fb ignoring binaries 2016-04-11 06:38:58 +02:00
Adolfo Gómez García
6f46e16be8 upgrade to use guacamole 0.9.9 2016-04-11 06:38:32 +02:00
Adolfo Gómez García
0b390e406a New generation of pam files for tunneler based on cmake 2016-04-08 09:44:19 +02:00
Adolfo Gómez García
f301e4654a Updating pam tunneler module 2016-04-08 07:45:52 +02:00
Adolfo Gómez García
62481899a1 fixed tunnel field order on admin interface 2016-04-07 10:28:23 +02:00
Adolfo Gómez García
869dfc8c06 * More translations update
* Added custom limits to providers that allow it
2016-04-07 07:16:15 +02:00
Adolfo Gómez García
939d456b9d Added memcached setting to sample file && minor fixes 2016-04-06 06:13:22 +02:00
Adolfo Gómez García
9c70fb3caf Added caching to FileStorage 2016-04-06 06:02:51 +02:00
Adolfo Gómez García
baf4a677dd Added shared files storage 2016-04-05 04:59:56 +02:00
Adolfo Gómez García
4c4820f166 Chaged a bit the default ssl behaviour 2016-04-04 19:30:23 +02:00
Adolfo Gómez García
2749bfc40c fixed an js translation 2016-04-04 18:33:54 +02:00
Adolfo Gómez García
704e0607eb * Fixed scheduler action to make these execute under a transaction
* Removed xmlrpc url
2016-04-04 18:27:38 +02:00
Adolfo Gómez García
401fbac63e Removed last remaining xmlrcp part 2016-04-04 11:00:51 +02:00
Adolfo Gómez García
7f1252a70a Updating translation & fixing translations errors 2016-04-04 09:42:51 +02:00
Adolfo Gómez García
2df103a348 fixed taglist template for production (javascript must be "nice" and cannot have comments on this templates) 2016-04-04 08:27:59 +02:00
Adolfo Gómez García
489bb44c92 Fixing up for 2.0 release 2016-04-04 07:24:30 +02:00
Adolfo Gómez García
c606b6f00e Fixing up for 2.0 release 2016-04-04 07:12:38 +02:00
Adolfo Gómez García
0b4b38abe7 Fixed model of Calandars && finished Scheduled calendar actions 2016-04-01 07:18:42 +02:00
Adolfo Gómez García
40b71fa983 Fixed to use new datatables 2016-03-31 10:47:59 +02:00
Adolfo Gómez García
6116db5147 basic scheduled actions working (needs backedn worker to be done) 2016-03-30 11:23:23 +02:00
Adolfo Gómez García
e979c6e1e2 Now we can create (but not edit) scheduled actions 2016-03-29 15:45:34 +02:00
Adolfo Gómez García
8f9d042cdd Now we can create (but not edit) scheduled actions 2016-03-29 15:42:41 +02:00
Adolfo Gómez García
fcf030e693 Advanfing on action editing for services pools 2016-03-29 05:30:05 +02:00
Adolfo Gómez García
31e6e01cad fixed deletion of service on validation error 2016-03-28 06:22:03 +02:00
Adolfo Gómez García
1aad0c85a4 Merge remote-tracking branch 'origin/v1.9' 2016-03-28 06:02:23 +02:00
Adolfo Gómez García
e6e16334b3 fixed deleting service on edition & not being valid 2016-03-28 06:02:04 +02:00
Adolfo Gómez García
8a285fddfa Merge remote-tracking branch 'origin/v1.9' 2016-03-19 02:30:34 +01:00
Adolfo Gómez García
c9a690fe8c Removed nonsense public folder from templates 2016-03-18 02:01:17 +01:00
Adolfo Gómez García
b5387d4922 * Fixed Actions Calendars migrations
* Started support for actions calendars edition on dashboard
2016-03-16 11:30:56 +01:00
Adolfo Gómez García
b0a6807ea4 Merge remote-tracking branch 'origin/v1.9'
# Conflicts:
#	VERSION
#	actors/linux/build-packages.sh
#	actors/linux/debian/changelog
#	actors/linux/debian/files
#	client/linux/debian/changelog
#	client/linux/debian/files
2016-03-14 12:16:52 +01:00
Adolfo Gómez García
2e96074961 Upgrades for 2.0
* Added support for /uuid REST method
* Now the http server is relocated on IP Change detection
2016-03-14 12:12:56 +01:00
Adolfo Gómez García
135392d245 Added get next event from a given date to calendar 2016-03-10 11:46:22 +01:00
Adolfo Gómez García
ff622bb9cd * Added optimization for serialization/deserialization (now it get cached with db object)
* Fixed model so now we invoke initGui before getting values on fillInstanceFields
* Upgraded Max creating & Max removing limits so now they can be tied to an specific provider instead of a global variable
* Tested & Fixed OpenStack. Now it seems to work as it should do.
* Several minor fixes/improvements
2016-03-09 11:42:44 +01:00
Adolfo Gómez García
608c1317d7 Updated OpenStack client & added caching features
Added counters to Cache (maybe somtime will store them on db to keep track on performance
2016-03-08 12:36:42 +01:00
Adolfo Gómez García
9f4ef20dc1 OpenStack working (initially) 2016-03-07 18:28:32 +01:00
Adolfo Gómez García
c45833c252 Advancing on openstack a bit more 2016-03-04 14:51:44 +01:00
Adolfo Gómez García
e260fc9790 Advancing toward Openstack provider at a nice pace.
We have decided finally to use directly the REST api instead of any
available API, because we need a fustfill of things only, and we have
this way a better control. (And it's not difficult to implement)
2016-03-04 13:33:36 +01:00
Adolfo Gómez García
cc6ba2ff41 Updated to start with identity api v3 instead of v2, and advanced a lot 2016-03-03 21:33:17 +01:00
Adolfo Gómez García
bec74ddc99 UDS Client for openstack 2016-03-03 19:35:19 +01:00
Adolfo Gómez García
7af7d11c8a * New Clients Versions
* Refactoring
* Minor fixes
2016-02-26 14:16:13 +01:00
Adolfo Gómez García
8934d978fe Almost finished calendars access 2016-02-23 11:23:49 +01:00
Adolfo Gómez García
8376e81532 Adding Access calendars admin 2016-02-23 09:44:40 +01:00
Adolfo Gómez García
6587d9ba2c Fixed downloads to point to 2.0 clients 2016-02-19 09:58:15 +01:00
Adolfo Gómez García
2654036fd2 fixed uds client to work correctly with firefox on kunbuntu 2016-02-19 09:46:22 +01:00
Adolfo Gómez García
f9226e7deb Fixed login fail messages for django 1.9 2016-02-19 08:37:22 +01:00
Adolfo Gómez García
56ac8aece9 Upgrade to Django 1.9 and advances on advances on states fixes (cosmetic fixes to better identify what we refer to) 2016-02-19 08:16:55 +01:00
Adolfo Gómez García
12737df530 Advancing on calendar access & scheduling 2016-02-18 11:03:38 +01:00
Adolfo Gómez García
7b9c835562 Advancing on calendar access & scheduling 2016-02-18 11:03:25 +01:00
Adolfo Gómez García
cbb809db77 * Improved cache for calendars checkings
* Added calentar access & actions models
* Started calendar-pool integration
2016-02-17 10:55:51 +01:00
Adolfo Gómez García
4f9085f0a2 Almost finished grouping services 2016-02-16 07:00:14 +01:00
Adolfo Gómez García
389cf62150 Experimenting & adavancing on services grouping 2016-02-15 11:04:21 +01:00
Adolfo Gómez García
44c367bf8f Addind public template for working on int 2016-02-15 02:34:46 +01:00
Adolfo Gómez García
efb0083161 removing dist from list 2016-02-15 01:38:02 +01:00
Adolfo Gómez García
16c1aba3e7 Added tags to search filter on admin, and also added "tabindex" to input fields 2016-02-12 10:58:57 +01:00
Adolfo Gómez García
07ed8b9762 Fixed Images & pools groups (added priority and enhaced visual on admin 2016-02-12 09:53:33 +01:00
Adolfo Gómez García
fbd0a59a70 Removed whitspaces from tags on addition 2016-02-12 04:52:23 +01:00
Adolfo Gómez García
a08fe53383 * Added "Service Pool Group" full logic, so new we can add services pools to groups, so we can visualzize them grouped. (already needs the visualizarion part)
* Finished basic taglist edition (fixed a bug & added the possibility of insert serveral comma separated values at once')
2016-02-12 04:47:44 +01:00
Adolfo Gómez García
e1b8c43cca Added "tag editing" admin control 2016-02-11 05:44:38 +01:00
Adolfo Gómez García
60f20e64f3 testing link on uds.css 2016-02-10 07:38:51 +01:00
Adolfo Gómez García
6bb1109fe1 Adding tags & tag edition to mayor elemets of uds administration (for filtering, etc..) 2016-02-10 03:53:51 +01:00
Adolfo Gómez García
134f2059dd Added tagging capability to most entities 2016-02-10 01:51:05 +01:00
Adolfo Gómez García
ec93e5c9cc Merge remote-tracking branch 'origin/v1.9' 2016-02-09 12:53:10 +01:00
Adolfo Gómez García
fd6b0c9458 Another big step forward for OpenNebula provider 2016-02-09 09:45:36 +01:00
Adolfo Gómez García
de4aef3a5c * Important advances on OpenNebula service provider
* several semantic changes
* Minor improvements & fixes
2016-02-08 09:33:17 +01:00
Adolfo Gómez García
d024d74529 Merged 1.9 fixes 2016-02-01 17:55:42 +01:00
Adolfo Gómez García
335fd338bd several fixes 2016-01-25 11:38:39 +01:00
Adolfo Gómez García
d7ac59f257 Merge remote-tracking branch 'origin/v1.9' 2016-01-20 10:05:29 +01:00
Adolfo Gómez García
ca54c2c099 Merge remote-tracking branch 'origin/v1.9' 2016-01-20 09:34:30 +01:00
Adolfo Gómez García
491a33421d csv test 2016-01-19 17:22:22 +01:00
Adolfo Gómez García
f1cbcf86e1 Adding report to see user access time/duration 2016-01-19 15:24:19 +01:00
Adolfo Gómez García
e2e0f96d3e Refactoring states for easier reading 2016-01-08 09:37:00 +01:00
Adolfo Gómez García
4be5d8d6e5 Added host verification to uds client (so urls are approved at least
first time by users)
2016-01-08 09:34:20 +01:00
Adolfo Gómez García
b9929566f6 Merge remote-tracking branch 'origin/v1.9' 2015-11-25 17:18:13 +01:00
Adolfo Gómez García
f6492256a8 Merge remote-tracking branch 'origin/v1.9' 2015-11-19 11:49:21 +01:00
Adolfo Gómez García
66d2c63a20 Merge remote-tracking branch 'origin/v1.9' 2015-11-18 14:59:20 +01:00
Adolfo Gómez García
b6f582d84e Merger 1.9 fixes 2015-11-16 20:25:06 +01:00
Adolfo Gómez García
d94cc70eff Merge remote-tracking branch 'origin/v1.9' 2015-11-16 12:45:12 +01:00
Adolfo Gómez García
163245401b Merging 1.9 fixes 2015-11-11 12:15:41 +01:00
Adolfo Gómez García
0add4b4321 Fixed new service operations checker. Improved & easier to understand with this new model 2015-11-10 08:04:39 +01:00
Adolfo Gómez García
19fdcadbcd Upgrading the UserServiceOpChecker to a new & clearer one 2015-11-06 12:04:44 +01:00
Adolfo Gómez García
7b85adaddf Updated version for master 2015-11-06 05:57:26 +01:00
Adolfo Gómez García
d8a0a2f80a Merged 2015-11-06 04:58:06 +01:00
Adolfo Gómez García
ae9a9534fc Merge remote-tracking branch 'origin/v1.9', cookie retrieval 2015-11-06 03:50:44 +01:00
Adolfo Gómez García
1c1003eb41 Added merged from v1.9 that did not goes as expected... :) 2015-11-05 10:34:16 +01:00
Adolfo Gómez García
4bc6d88006 fixing 2015-11-05 10:31:46 +01:00
Adolfo Gómez García
f08593881f Merge remote-tracking branch 'origin/v1.9' 2015-11-05 08:16:54 +01:00
Adolfo Gómez García
30cf167e5b Merge remote-tracking branch 'origin/v1.9' 2015-11-05 08:15:55 +01:00
Adolfo Gómez García
064d881c1e Checking up javascript stuff 2015-11-05 07:30:06 +01:00
Adolfo Gómez García
c3f9d673bd Merge remote-tracking branch 'origin/v1.9' 2015-11-05 07:28:43 +01:00
Adolfo Gómez García
4695dcaa0c Merge remote-tracking branch 'origin/v1.9' 2015-11-02 00:47:08 +01:00
Adolfo Gómez García
e23f22d92d Several fixes for dashboard && new command to clean up cache 2015-10-29 06:57:02 +01:00
Adolfo Gómez García
962f1fcc1c Merge remote-tracking branch 'origin/v1.9' 2015-10-27 01:53:44 +01:00
Adolfo Gómez García
6bc70ff4de Several minor fixes & aditions 2015-10-24 10:32:51 +02:00
Adolfo Gómez García
aaf4b539d5 Merge remote-tracking branch 'origin/v1.9' 2015-10-20 19:49:34 +02:00
Adolfo Gómez García
03d0fc2ba4 Merge remote-tracking branch 'origin/v1.9' 2015-10-19 06:36:57 +02:00
Adolfo Gómez García
407b0ebf55 Merge remote-tracking branch 'origin/v1.9' 2015-10-16 16:59:14 +02:00
Adolfo Gómez García
033ac0d1f6 Seems to finished updating tables. Need to check new working 2015-10-16 08:30:33 +02:00
Adolfo Gómez García
c9e9f60ed1 * Added a more aggresive cache for types, tables & guis (Less requests to server)
* Fixed ServicePools tables
* Fixed log tables
2015-10-16 07:43:03 +02:00
Adolfo Gómez García
162c84e21c Merge remote-tracking branch 'origin/v1.9' 2015-10-15 19:25:57 +02:00
Adolfo Gómez García
9f6d126484 Added images to memory cache instead of db cache 2015-10-15 19:25:48 +02:00
Adolfo Gómez García
0877cc01f4 Almost fixed all tables on dashboard 2015-10-06 11:32:47 +02:00
Adolfo Gómez García
00e85357cf Dashboard upgrade & enhacements 2015-10-06 07:14:40 +02:00
Adolfo Gómez García
cc72b3742f Addind multiselects & overviews to dashboard 2015-10-06 05:39:16 +02:00
Adolfo Gómez García
dff20bd6e1 Added several indexes to optimize some db queries\nFixed too many connections due to not being closed on threads termination\nAdvanced on new dashboard 2015-10-05 13:17:54 +02:00
Adolfo Gómez García
69b6f7d7d8 Merge remote-tracking branch 'origin/v1.9' 2015-10-05 11:48:22 +02:00
Adolfo Gómez García
9881df5117 Clean white squared admin with collapsable menu 2015-10-02 01:08:21 +02:00
Adolfo Gómez García
bc2f83c937 Clean white squared admin with collapsable menu 2015-10-02 00:56:46 +02:00
Adolfo Gómez García
fa78b4b295 Clean white squared admin with collapsable menu 2015-10-02 00:55:58 +02:00
Adolfo Gómez García
95230292c3 Experimenting with dashboard 2015-10-01 07:23:26 +02:00
Adolfo Gómez García
fdaf974009 Advancing on styling. Adding new functionality to dashboard 2015-09-25 17:27:46 +02:00
Adolfo Gómez García
26764c4cab Fixing datatables integration 2015-09-25 04:55:07 +02:00
Adolfo Gómez García
52d0e8977c Styling dashboard 2015-09-24 07:33:52 +02:00
Adolfo Gómez García
1d7df7814c Merge remote-tracking branch 'origin/v1.9' 2015-09-23 17:50:13 +02:00
Adolfo Gómez García
7e9a37e768 advancing & gui-element tables 2015-09-23 10:43:45 +02:00
Adolfo Gómez García
80bf6d77f1 Added "multiple deletions" feature 2015-09-23 03:31:26 +02:00
Adolfo Gómez García
2d7a2d6049 Advancing on new dashboard 2015-09-22 19:53:56 +02:00
Adolfo Gómez García
8363b41a68 Advancing on new dashboard look&feel 2015-09-22 09:46:28 +02:00
Adolfo Gómez García
9804678a30 Updating look&feel of admin dashboard 2015-09-22 04:13:15 +02:00
Adolfo Gómez García
1187c0aa5e * Added a template folder to work on admin design faster
* Updated Admin CSS (Testing new view)
* Minor fixes (typos)
2015-09-22 02:23:27 +02:00
Adolfo Gómez García
36adbe387c Bad resolve fixed 2015-09-22 01:04:32 +02:00
Adolfo Gómez García
df2dcf7acd Merge remote-tracking branch 'origin/v1.9'
# Conflicts:
#	server/src/uds/services/OVirt/OVirtProvider.py
#	server/src/uds/services/OVirt/client/oVirtClient.py

Conflicts resolved
2015-09-21 08:18:45 +02:00
Adolfo Gómez García
3fbb492921 Added summary when adding new calendar rule & merged migrations 2015-09-18 10:02:39 +02:00
Adolfo Gómez García
1b97e2015d More fixes to calendars 2015-09-18 08:06:07 +02:00
Adolfo Gómez García
3814986e41 Fixed calendar checking.\nUpdated CalendarRule model to allow different durations units 2015-09-18 00:47:21 +02:00
Adolfo Gómez García
037a4b523b Fixed calendar rules. Almost finished :-) 2015-09-17 06:53:35 +02:00
Adolfo Gómez García
522d493557 Advancing more on calendar rules edition 2015-09-15 10:43:26 +02:00
Adolfo Gómez García
2ac06f7345 Advancing more on calendar rules edition 2015-09-15 10:43:12 +02:00
Adolfo Gómez García
0745a7aa9a Advancing on rule editing 2015-09-13 04:43:59 +02:00
Adolfo Gómez García
7e419aed8d Updating components for 2.0 2015-09-13 03:30:48 +02:00
Adolfo Gómez García
baa9565140 Merge remote-tracking branch 'origin/v1.9' 2015-09-13 03:30:17 +02:00
Adolfo Gómez García
069dfb154a Updating components 2015-09-13 03:30:12 +02:00
Adolfo Gómez García
485ed4d60b Merge remote-tracking branch 'origin/v1.9' 2015-09-12 22:11:13 +02:00
Adolfo Gómez García
4de3e83ade * Upgraded bootstrap-select
* Upgraded bootstrap-switch
2015-09-11 10:18:21 +02:00
Adolfo Gómez García
73713eac69 * Adding calendar (models, permissions, adm access, ....)
* Upgraded fontawesome
2015-09-11 01:39:01 +02:00
Adolfo Gómez García
6ed924655d Adding calendar/rules models 2015-09-09 10:01:48 +02:00
Adolfo Gómez García
7706262702 Merge remote-tracking branch 'origin/v1.9' 2015-09-08 19:46:31 +02:00
Adolfo Gómez García
e0843179cd Fixing up several minor lang issues 2015-09-07 21:40:12 +02:00
573 changed files with 58783 additions and 47136 deletions

View File

@@ -1 +1 @@
1.9.1
2.0.0

View File

@@ -1,6 +1,6 @@
#!/bin/bash
VERSION=1.9.1
VERSION=`cat ../../VERSION`
RELEASE=1
top=`pwd`

View File

@@ -1,3 +1,9 @@
udsactor (2.0.0) stable; urgency=medium
* Upgrade for 2.0.0
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 01 Mar 2016 03:39:21 +0100
udsactor (1.9.1) stable; urgency=medium
* Upgrade for 1.9.1

View File

@@ -1,3 +1,3 @@
udsactor-nx_1.9.1_all.deb x11 optional
udsactor-xrdp_1.9.1_all.deb x11 optional
udsactor_1.9.1_all.deb admin optional
udsactor-nx_2.0.0_all.deb x11 optional
udsactor-xrdp_2.0.0_all.deb x11 optional
udsactor_2.0.0_all.deb admin optional

View File

@@ -33,10 +33,11 @@
from __future__ import unicode_literals
import sys
from PyQt4 import QtGui
from PyQt4 import QtCore
from PyQt4 import QtGui # @UnresolvedImport
from PyQt4 import QtCore # @UnresolvedImport
import pickle
import time
import datetime
import signal
from udsactor import ipc
from udsactor import utils
@@ -175,10 +176,13 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
exitAction.triggered.connect(self.about)
self.setContextMenu(self.menu)
self.ipc = MessagesProcessor()
self.sessionStart = datetime.datetime.now()
self.maxIdleTime = None
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.checkIdle)
self.showIdleWarn = True
self.maxSessionTime = None
self.showMaxSessionWarn = True
self.timer = QtCore.QTimer()
self.timer.timeout.connect(self.checkTimers)
if self.ipc.isAlive() is False:
raise Exception('No connection to service, exiting.')
@@ -206,6 +210,27 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
# If this is running, it's because he have logged in
self.ipc.sendLogin(operations.getCurrentUser())
def checkTimers(self):
self.checkIdle()
self.checkMaxSession()
def checkMaxSession(self):
if self.maxSessionTime is None or self.maxSessionTime == 0:
logger.debug('Returning because maxSessionTime is cero')
return
remainingTime = self.maxSessionTime - (datetime.datetime.now() - self.sessionStart).total_seconds()
logger.debug('Remaining time: {}'.format(remainingTime))
if self.showMaxSessionWarn is True and remainingTime < 300: # With five minutes, show a warning message
self.showMaxSessionWarn = False
self.msgDlg.displayMessage('Your session will expire in less that 5 minutes. Please, save your work and disconnect.')
return
if remainingTime <= 0:
logger.debug('Remaining time is less than cero, exiting')
self.quit()
def checkIdle(self):
if self.maxIdleTime is None: # No idle check
return
@@ -217,19 +242,19 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
idleTime = operations.getIdleDuration()
remainingTime = self.maxIdleTime - idleTime
if remainingTime > 300: # Reset show Warning dialog if we have more than 5 minutes left
if remainingTime > 120: # Reset show Warning dialog if we have more than 5 minutes left
self.showIdleWarn = True
logger.debug('User has been idle for: {}'.format(idleTime))
if remainingTime <= 0:
logger.info('User has been idle for too long, notifying Broker that service can be reclaimed')
self.quit()
if self.showIdleWarn is True and remainingTime < 120: # With two minutes, show a warning message
self.showIdleWarn = False
self.msgDlg.displayMessage("You have been idle for too long. The session will end if you don't resume operations")
if remainingTime <= 0:
logger.info('User has been idle for too long, notifying Broker that service can be reclaimed')
self.quit()
def displayMessage(self, message):
logger.debug('Displaying message')
self.msgDlg.displayMessage(message)
@@ -247,7 +272,7 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
'''
Invoked when received information from service
'''
logger.debug('Got information message: {}'.format(info))
logger.info('Got information message: {}'.format(info))
if 'idle' in info:
idle = int(info['idle'])
operations.initIdleDuration(idle)
@@ -256,6 +281,12 @@ class UDSSystemTray(QtGui.QSystemTrayIcon):
else:
self.maxIdleTime = None
if 'maxSession' in info:
maxSession = int(info['maxSession'])
# operations.initMaxSession(maxSession)
self.maxSessionTime = maxSession
logger.debug('Set maxsession to {}'.format(maxSession))
def about(self):
self.aboutDlg.exec_()

View File

@@ -88,7 +88,7 @@
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
<number>2</number>
</property>
<widget class="QWidget" name="aboutTab">
<attribute name="title">
@@ -109,7 +109,7 @@
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Verdana'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot;-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';&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; font-weight:600;&quot;&gt;(c) 2014, Virtual Cable S.L.U.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; font-weight:600;&quot;&gt;(c) 2012-2016, Virtual Cable S.L.U.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;a href=&quot;http://www.udsenterprise.com&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt; text-decoration: underline; color:#0000ff;&quot;&gt;http://www.udsenterprise.com&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;a href=&quot;http://www.openuds.org&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt; text-decoration: underline; color:#0000ff;&quot;&gt;http://www.openuds.org&lt;/span&gt;&lt;/a&gt;&lt;/p&gt;
@@ -167,7 +167,7 @@ p, li { white-space: pre-wrap; }
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'Verdana'; font-size:9pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;Copyright (c) 2014 Virtual Cable S.L.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;Copyright (c) 2012-2016 Virtual Cable S.L.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;span style=&quot; font-family:'MS Shell Dlg 2'; font-size:8pt;&quot;&gt;All rights reserved.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-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;&quot;&gt;&lt;br /&gt;&lt;/p&gt;

View File

@@ -109,6 +109,7 @@ class Api(object):
self.mac = None
self.url = "{}://{}/rest/actor/".format(('http', 'https')[self.useSSL], self.host)
self.idle = None
self.maxSession = None
self.secretKey = six.text_type(uuid.uuid4())
try:
self.newerRequestLib = requests.__version__.split('.')[0] >= '1'

View File

@@ -34,14 +34,14 @@ from __future__ import unicode_literals
# On centos, old six release does not includes byte2int, nor six.PY2
import six
VERSION = '1.9.0'
VERSION = '2.0.0'
__title__ = 'udsactor'
__version__ = VERSION
__build__ = 0x010750
__author__ = 'Adolfo Gómez'
__author__ = 'Adolfo Gómez <dkmaster@dkmon.com>'
__license__ = "BSD 3-clause"
__copyright__ = "Copyright 2014-2015 VirtualCable S.L.U."
__copyright__ = "Copyright 2014-2016 VirtualCable S.L.U."
if not hasattr(six, 'byte2int'):

View File

@@ -172,7 +172,10 @@ class HTTPServerHandler(BaseHTTPServer.BaseHTTPRequestHandler):
def get_information(self, params):
# TODO: Return something useful? :)
return 'Information'
return 'Up and running'
def get_uuid(self, params):
return self.service.api.uuid
def log_error(self, fmt, *args):
logger.error('HTTP ' + fmt % args)
@@ -188,9 +191,13 @@ class HTTPServerThread(threading.Thread):
if HTTPServerHandler.uuid is None:
HTTPServerHandler.uuid = ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(48))
self.certFile = createSelfSignedCert()
HTTPServerHandler.service = service
self.certFile = createSelfSignedCert()
self.initiateServer(address)
def initiateServer(self, address):
self.server = socketserver.TCPServer(address, HTTPServerHandler)
self.server.socket = ssl.wrap_socket(self.server.socket, certfile=self.certFile, server_side=True)
@@ -201,5 +208,14 @@ class HTTPServerThread(threading.Thread):
logger.debug('Stopping REST Service')
self.server.shutdown()
def restart(self, address=None):
if address is None:
address = self.server.server_address
self.stop()
self.initiateServer(address)
def run(self):
self.server.serve_forever()

View File

@@ -57,7 +57,7 @@ def _getMacAddr(ifname):
try:
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
info = bytearray(fcntl.ioctl(s.fileno(), 0x8927, struct.pack(str('256s'), ifname[:15])))
return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1])
return six.text_type(''.join(['%02x:' % char for char in info[18:24]])[:-1]).upper()
except Exception:
return None
@@ -194,8 +194,10 @@ try:
# Fix result type to XScreenSaverInfo Structure
xss.XScreenSaverQueryExtension.restype = ctypes.c_int
xss.XScreenSaverAllocInfo.restype = ctypes.POINTER(XScreenSaverInfo) # Result in a XScreenSaverInfo structure
display = xlib.XOpenDisplay(None)
info = xss.XScreenSaverAllocInfo()
except Exception: # Libraries not accesible, not found or whatever..
xlib = xss = None
xlib = xss = display = info = None
def initIdleDuration(atLeastSeconds):
@@ -219,20 +221,18 @@ def getIdleDuration():
if xlib is None or xss is None:
return 0 # Libraries not available
# production code might want to not hardcode the offset 16...
display = xlib.XOpenDisplay(None)
event_base = ctypes.c_int()
error_base = ctypes.c_int()
available = xss.XScreenSaverQueryExtension(display, ctypes.byref(event_base), ctypes.byref(error_base))
if available != 1:
return 0 # No screen saver is available, no way of getting idle
info = xss.XScreenSaverAllocInfo()
xss.XScreenSaverQueryInfo(display, xlib.XDefaultRootWindow(display), info)
if info.contents.state != 0:
# Centos seems to set state to 1?? (weird, but it's happening don't know why... will try this way)
if info.contents.state != 0 and 'centos' not in platform.linux_distribution()[0].lower().strip():
return 3600 * 100 * 1000 # If screen saver is active, return a high enough value
return info.contents.idle / 1000.0

View File

@@ -205,8 +205,17 @@ class CommonService(object):
try:
# Notifies all interfaces IPs
self.api.notifyIpChanges(((v.mac, v.ip) for v in netInfo))
# Regenerates Known ips
self.knownIps = dict(((v.mac, v.ip) for v in netInfo))
# And notify new listening address to broker
address = (self.knownIps[self.api.mac], random.randrange(43900, 44000))
# And new listening address
self.httpServer.restart(address)
# sends notification
self.api.notifyComm(self.httpServer.getServerUrl())
except Exception as e:
logger.warn('Got an error notifiying IPs to broker: {} (will retry in a bit)'.format(e.message.decode('windows-1250', 'ignore')))
@@ -217,14 +226,21 @@ class CommonService(object):
return
if msg == ipc.REQ_LOGIN:
self.api.login(data)
elif msg == ipc.REQ_LOGOUT:
res = self.api.login(data).split('\t')
# third parameter, if exists, sets maxSession duration to this.
# First & second parameters are ip & hostname of connection source
if len(res) >= 3:
self.api.maxSession = int(res[2]) # Third parameter is max session duration
msg = ipc.REQ_INFORMATION # Senf information, requested or not, to client on login notification
if msg == ipc.REQ_LOGOUT:
self.api.logout(data)
self.onLogout(data)
elif msg == ipc.REQ_INFORMATION:
if msg == ipc.REQ_INFORMATION:
info = {}
if self.api.idle is not None:
info['idle'] = self.api.idle
if self.api.maxSession is not None:
info['maxSession'] = self.api.maxSession
self.ipc.sendInformationMessage(info)
def initIPC(self):
@@ -236,7 +252,7 @@ class CommonService(object):
self.ipc.start()
if self.api.mac in self.knownIps:
address = (self.knownIps[self.api.mac], random.randrange(40000, 44000))
address = (self.knownIps[self.api.mac], random.randrange(43900, 44000))
logger.info('Starting REST listener at {}'.format(address))
self.httpServer = httpserver.HTTPServerThread(address, self)
self.httpServer.start()

View File

@@ -76,7 +76,7 @@ class SensLogon(win32com.server.policy.DesignatedWrapPolicy):
data = self.service.api.login(args[0])
logger.debug('Data received for login: {}'.format(data))
data = data.split('\t')
if len(data) == 2:
if len(data) >= 2:
logger.debug('Data is valid: {}'.format(data))
windir = os.environ['windir']
with open(os.path.join(windir, 'remoteip.txt'), 'w') as f:

View File

@@ -48,7 +48,7 @@ class LocalLogger(object):
filename=os.path.join(tempfile.gettempdir(), 'udsactor.log'),
filemode='a',
format='%(levelname)s %(asctime)s %(message)s',
level=logging.DEBUG
level=logging.INFO
)
self.logger = logging.getLogger('udsactor')
self.serviceLogger = False

View File

@@ -45,6 +45,7 @@ from udsactor.log import logger
def getErrorMessage(res=0):
# sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
msg = win32api.FormatMessage(res)
return msg.decode('windows-1250', 'ignore')

View File

@@ -1,3 +1,9 @@
udsclient (2.0.0) stable; urgency=medium
* Release upgrade
-- Adolfo Gómez García <agomez@virtualcable.es> Tue, 01 Mar 2016 09:33:18 +0100
udsclient (1.9.1) stable; urgency=medium
* Minor fixes & making version match UDS version

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

View File

@@ -1 +1 @@
udsclient_1.9.1_all.deb admin optional
udsclient_2.0.0_all.deb admin optional

View File

@@ -54,6 +54,9 @@ class UDSClient(QtGui.QMainWindow):
ticket = None
scrambler = None
withError = False
animTimer = None
anim = 0
animInverted = False
def __init__(self):
QtGui.QMainWindow.__init__(self)
@@ -73,8 +76,14 @@ class UDSClient(QtGui.QMainWindow):
vpos = (screen.height() - mysize.height() - mysize.height()) / 2
self.move(hpos, vpos)
self.animTimer = QtCore.QTimer()
QtCore.QObject.connect(self.animTimer, QtCore.SIGNAL('timeout()'), self.updateAnim)
self.activateWindow()
self.startAnim()
def closeWindow(self):
self.close()
@@ -90,21 +99,36 @@ class UDSClient(QtGui.QMainWindow):
# return
def showError(self, e):
self.ui.progressBar.setValue(100)
self.ui.info.setText('Error')
QtGui.QMessageBox.critical(self, 'Error', six.text_type(e), QtGui.QMessageBox.Ok)
logger.error('got error: {}'.format(e))
self.stopAnim()
self.ui.info.setText('UDS Plugin Error') # In fact, main window is hidden, so this is not visible... :)
self.closeWindow()
QtGui.QMessageBox.critical(None, 'UDS Plugin Error', '{}'.format(e), QtGui.QMessageBox.Ok)
self.withError = True
def cancelPushed(self):
self.close()
def _updateProgressBar(self, increment, maximum=100):
val = self.ui.progressBar.value()
val += increment
if val > maximum:
val = maximum
self.ui.progressBar.setValue(val)
@QtCore.pyqtSlot()
def updateAnim(self):
self.anim += 2
if self.anim > 99:
self.animInverted = not self.animInverted
self.ui.progressBar.setInvertedAppearance(self.animInverted)
self.anim = 0
self.ui.progressBar.setValue(self.anim)
def startAnim(self):
self.ui.progressBar.invertedAppearance = False
self.anim = 0
self.animInverted = False
self.ui.progressBar.setInvertedAppearance(self.animInverted)
self.animTimer.start(40)
def stopAnim(self):
self.ui.progressBar.invertedAppearance = False
self.animTimer.stop()
@QtCore.pyqtSlot()
def getVersion(self):
@@ -114,10 +138,7 @@ class UDSClient(QtGui.QMainWindow):
@QtCore.pyqtSlot(dict)
def version(self, data):
try:
self.ui.progressBar.setValue(20)
self.processError(data)
self.ui.info.setText('Processing...')
if data['result']['requiredVersion'] > VERSION:
@@ -128,17 +149,21 @@ class UDSClient(QtGui.QMainWindow):
self.getTransportData()
except RetryException as e:
self._updateProgressBar(5, 80)
self.ui.info.setText(six.text_type(e))
QtCore.QTimer.singleShot(1000, self.getVersion)
except Exception as e:
self.showError(e)
@QtCore.pyqtSlot()
def getTransportData(self):
self.req = RestRequest('/{}/{}'.format(self.ticket, self.scrambler), self, self.transportDataReceived, params={'hostname': tools.getHostName(), 'version': VERSION})
self.req.get()
try:
self.req = RestRequest('/{}/{}'.format(self.ticket, self.scrambler), self, self.transportDataReceived, params={'hostname': tools.getHostName(), 'version': VERSION})
self.req.get()
except Exception as e:
logger.exception('Got exception: {}'.format(e))
raise e
@QtCore.pyqtSlot(dict)
@@ -147,11 +172,10 @@ class UDSClient(QtGui.QMainWindow):
try:
self.processError(data)
self.ui.progressBar.setValue(80)
script = data['result'].decode('base64').decode('bz2')
self.ui.progressBar.setValue(100)
self.stopAnim()
if 'darwin' in sys.platform:
self.showMinimized()
@@ -161,7 +185,6 @@ class UDSClient(QtGui.QMainWindow):
six.exec_(script, globals(), {'parent': self})
except RetryException as e:
self._updateProgressBar(5, 80)
self.ui.info.setText(six.text_type(e) + ', retrying access...')
# Retry operation in ten seconds
QtCore.QTimer.singleShot(10000, self.getTransportData)
@@ -201,6 +224,23 @@ def done(data):
QtGui.QMessageBox.critical(None, 'Notice', six.text_type(data.data), QtGui.QMessageBox.Ok)
sys.exit(0)
# Ask user to aprobe endpoint
def approveHost(host, parentWindow=None):
settings = QtCore.QSettings()
settings.beginGroup('endpoints')
approved = settings.value(host, False).toBool()
errorString = '<p>The server <b>{}</b> must be approved:</p>'.format(host)
errorString += '<p>Only approve UDS servers that you trust to avoid security issues.</p>'
if approved or QtGui.QMessageBox.warning(parentWindow, 'ACCESS Warning', errorString, QtGui.QMessageBox.Yes | QtGui.QMessageBox.No) == QtGui.QMessageBox.Yes:
settings.setValue(host, True)
approved = True
settings.endGroup()
return approved
if __name__ == "__main__":
logger.debug('Initializing connector')
# Initialize app
@@ -222,6 +262,10 @@ if __name__ == "__main__":
# First parameter must be url
try:
uri = sys.argv[1]
if uri == '--test':
sys.exit(0)
logger.debug('URI: {}'.format(uri))
if uri[:6] != 'uds://' and uri[:7] != 'udss://':
raise Exception()
@@ -232,7 +276,7 @@ if __name__ == "__main__":
except Exception:
logger.debug('Detected execution without valid URI, exiting')
QtGui.QMessageBox.critical(None, 'Notice', 'This program is designed to be used by UDS', QtGui.QMessageBox.Ok)
QtGui.QMessageBox.critical(None, 'Notice', 'UDS Client Version {}'.format(VERSION), QtGui.QMessageBox.Ok)
sys.exit(1)
# Setup REST api endpoint
@@ -242,8 +286,15 @@ if __name__ == "__main__":
try:
logger.debug('Starting execution')
# Approbe before going on
if approveHost(host) is False:
raise Exception('Host {} was not approved'.format(host))
win = UDSClient()
win.show()
win.start()
exitVal = app.exec_()
@@ -257,12 +308,3 @@ if __name__ == "__main__":
logger.debug('Exiting')
sys.exit(exitVal)
# Build base REST
# v = RestRequest('', done)
# v.get()
# sys.exit(1)
# myapp = UDSConfigDialog(cfg)
# myapp.show()

View File

@@ -34,7 +34,7 @@ from __future__ import unicode_literals
# On centos, old six release does not includes byte2int, nor six.PY2
import six
VERSION = '1.9.0'
VERSION = '2.0.0'
__title__ = 'udclient'
__version__ = VERSION

View File

@@ -14,15 +14,15 @@ import time
from .log import logger
class ForwardServer (SocketServer.ThreadingTCPServer):
class ForwardServer(SocketServer.ThreadingTCPServer):
daemon_threads = True
allow_reuse_address = True
class Handler (SocketServer.BaseRequestHandler):
class Handler(SocketServer.BaseRequestHandler):
def handle(self):
self.thread.isConnected = True
self.thread.currentConnections += 1
try:
chan = self.ssh_transport.open_channel('direct-tcpip',
@@ -65,10 +65,11 @@ class Handler (SocketServer.BaseRequestHandler):
except Exception:
pass
if self.thread.stoppable is True:
self.thread.currentConnections -= 1
if self.thread.stoppable is True and self.thread.currentConnections == 0:
self.thread.stop()
self.thread.isConnected = False
class ForwardThread(threading.Thread):
status = 0 # Connecting
@@ -92,31 +93,49 @@ class ForwardThread(threading.Thread):
self.stopEvent = threading.Event()
self.timer = None
self.isConnected = False
self.currentConnections = 0
self.stoppable = False
self.client = None
def clone(self, redirectHost, redirectPort, localPort=None):
if localPort is None:
localPort = random.randrange(40000, 50000)
ft = ForwardThread(self.server, self.port, self.username, self.password, localPort, redirectHost, redirectPort, self.waitTime)
ft.client = self.client
self.client.useCount += 1 # One more using this client
ft.start()
while ft.status == 0:
time.sleep(0.1)
return (ft, localPort)
def _timerFnc(self):
self.timer = None
logger.debug('Timer fnc: {}'.format(self.isConnected))
logger.debug('Timer fnc: {}'.format(self.currentConnections))
self.stoppable = True
if self.isConnected is False:
if self.currentConnections <= 0:
self.stop()
def run(self):
self.client = paramiko.SSHClient()
self.client.load_system_host_keys()
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
if self.client is None:
self.client = paramiko.SSHClient()
self.client.useCount = 1 # Custom added variable, to keep track on when to close tunnel
self.client.load_system_host_keys()
self.client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
logger.debug('Connecting to ssh host %s:%d ...' % (self.server, self.port))
logger.debug('Connecting to ssh host %s:%d ...' % (self.server, self.port))
try:
self.client.connect(self.server, self.port, username=self.username, password=self.password, timeout=5)
except Exception as e:
logger.exception('Exception connecting: ')
self.status = 2 # Error
return
try:
self.client.connect(self.server, self.port, username=self.username, password=self.password, timeout=5)
except Exception as e:
logger.exception('Exception connecting: ')
self.status = 2 # Error
return
class SubHandler (Handler):
class SubHandler(Handler):
chain_host = self.redirectHost
chain_port = self.redirectPort
ssh_transport = self.client.get_transport()
@@ -141,7 +160,10 @@ class ForwardThread(threading.Thread):
self.fs.shutdown()
if self.client is not None:
self.client.close()
self.client.useCount -= 1
if self.client.useCount == 0:
self.client.close()
self.client = None # Clean up
except Exception:
logger.exception('Exception stopping')
pass

View File

@@ -39,7 +39,7 @@ logging.basicConfig(
filename=os.path.join(tempfile.gettempdir(), b'udsclient.log'),
filemode='a',
format='%(levelname)s %(asctime)s %(message)s',
level=logging.INFO
level=logging.DEBUG
)
logger = logging.getLogger('udsclient')

View File

@@ -56,7 +56,7 @@ class RestRequest(QObject):
# private
self._manager = QNetworkAccessManager()
if params is not None:
url += '?' + '&'.join('{}={}'.format(k, urllib.quote(six.text_type(v))) for k, v in params.iteritems())
url += '?' + '&'.join('{}={}'.format(k, urllib.quote(six.text_type(v).encode('utf8'))) for k, v in params.iteritems())
self.url = QUrl(RestRequest.restApiUrl + url)
@@ -91,6 +91,7 @@ class RestRequest(QObject):
@pyqtSlot(QNetworkReply, list)
def _sslError(self, reply, errors):
settings = QSettings()
settings.beginGroup('ssl')
cert = errors[0].certificate()
digest = six.text_type(cert.digest().toHex())
@@ -107,6 +108,8 @@ class RestRequest(QObject):
settings.setValue(digest, True)
reply.ignoreSslErrors()
settings.endGroup()
def get(self):
request = QNetworkRequest(self.url)
request.setRawHeader('User-Agent', osDetector.getOs() + " - UDS Connector " + VERSION)

View File

@@ -40,28 +40,57 @@ import socket
import stat
import six
import sys
import time
from log import logger
_unlinkFiles = []
_tasksToWait = []
_execBeforeExit = []
sys_fs_enc = sys.getfilesystemencoding() or 'mbcs'
def saveTempFile(content, filename=None):
if filename is None:
filename = ''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(16))
filename = b''.join(random.choice(string.ascii_lowercase + string.digits) for _ in range(16))
filename = filename + '.uds'
if 'win32' in sys.platform:
filename = filename.encode('utf-8')
logger.info('Fixing for win32')
filename = filename.encode(sys_fs_enc)
filename = os.path.join(tempfile.gettempdir(), filename)
with open(filename, 'w') as f:
f.write(content)
logger.info('Returning filename')
return filename
def readTempFile(filename):
if 'win32' in sys.platform:
filename = filename.encode('utf-8')
filename = os.path.join(tempfile.gettempdir(), filename)
try:
with open(filename, 'r') as f:
return f.read()
except Exception:
return None
def testServer(host, port, timeOut=4):
try:
sock = socket.create_connection((host, int(port)), timeOut)
sock.close()
except Exception:
return False
return True
def findApp(appName, extraPath=None):
if 'win32' in sys.platform and isinstance(appName, six.text_type):
appName = six.binary_type(appName)
appName = appName.encode(sys_fs_enc)
searchPath = os.environ['PATH'].split(os.pathsep)
if extraPath is not None:
searchPath += list(extraPath)
@@ -78,7 +107,13 @@ def getHostName():
Returns current host name
In fact, it's a wrapper for socket.gethostname()
'''
return six.text_type(socket.gethostname())
hostname = socket.gethostname()
if 'win32' in sys.platform:
hostname = hostname.decode(sys_fs_enc)
hostname = six.text_type(hostname)
logger.info('Hostname: {}'.format(hostname))
return hostname
# Queing operations (to be executed before exit)
@@ -94,6 +129,8 @@ def unlinkFiles():
'''
Removes all wait-and-unlink files
'''
if len(_unlinkFiles) > 0:
time.sleep(5) # Wait 5 seconds before deleting anything
for f in _unlinkFiles:
try:
os.unlink(f)
@@ -107,7 +144,13 @@ def addTaskToWait(taks):
def waitForTasks():
for t in _tasksToWait:
t.wait()
try:
if hasattr(t, 'join'):
t.join()
elif hasattr(t, 'wait'):
t.wait()
except Exception:
pass
def addExecBeforeExit(fnc):

5
guacamole-tunnel/NOTICE Normal file
View File

@@ -0,0 +1,5 @@
Apache Guacamole
Copyright 2016 The Apache Software Foundation
This product includes software developed at
The Apache Software Foundation (http://www.apache.org/).

View File

@@ -7,36 +7,43 @@
<groupId>org.openuds.server</groupId>
<artifactId>transport</artifactId>
<packaging>war</packaging>
<version>1.9.0</version>
<version>2.0.0</version>
<name>Guacamole Transport</name>
<url>http://openuds.org/</url>
<url>https://github.com/dkmstr/openuds</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<build>
<finalName>transport</finalName>
<build>
<finalName>transport</finalName>
<plugins>
<!-- Compile using Java 1.6 -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.3</version>
<configuration>
<source>1.6</source>
<target>1.6</target>
<compilerArgs>
<arg>-Xlint:all</arg>
<arg>-Werror</arg>
</compilerArgs>
<fork>true</fork>
</configuration>
</plugin>
<!-- Overlay guacamole-common-js (zip) -->
<!-- Overlay guacamole-common-js (zip) -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>2.6</version>
<configuration>
<overlays>
<overlay>
<groupId>org.glyptodon.guacamole</groupId>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-common-js</artifactId>
<type>zip</type>
</overlay>
@@ -46,9 +53,8 @@
</plugins>
</build>
<dependencies>
</build>
<dependencies>
<!-- Servlet API -->
<dependency>
@@ -58,22 +64,107 @@
<version>2.5</version>
</dependency>
<!-- SLF4J - logging -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.6.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jcl</artifactId>
<version>1.6.1</version>
<scope>runtime</scope>
</dependency>
<!-- Main Guacamole library -->
<dependency>
<groupId>org.glyptodon.guacamole</groupId>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-common</artifactId>
<version>0.9.7</version>
<version>0.9.9-incubating</version>
</dependency>
<!-- Guacamole JavaScript library -->
<dependency>
<groupId>org.glyptodon.guacamole</groupId>
<groupId>org.apache.guacamole</groupId>
<artifactId>guacamole-common-js</artifactId>
<version>0.9.8</version>
<version>0.9.9-incubating</version>
<type>zip</type>
<scope>runtime</scope>
</dependency>
<!-- JSR 356 WebSocket API -->
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<!-- Jetty 8 servlet API (websocket) -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-websocket</artifactId>
<version>8.1.1.v20120215</version>
<scope>provided</scope>
</dependency>
<!-- Jetty 9.0 servlet API (websocket) -->
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-parent</artifactId>
<version>20</version>
<scope>provided</scope>
<type>pom</type>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-api</artifactId>
<version>9.0.7.v20131107</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-servlet</artifactId>
<version>9.0.7.v20131107</version>
<scope>provided</scope>
</dependency>
<!-- Tomcat servlet API (websocket) -->
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-catalina</artifactId>
<version>7.0.37</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-coyote</artifactId>
<version>7.0.37</version>
<scope>provided</scope>
</dependency>
<!-- Jersey - Guice extension -->
<dependency>
<groupId>com.sun.jersey.contribs</groupId>
<artifactId>jersey-guice</artifactId>
<version>1.17.1</version>
</dependency>
<!-- Guice Servlet -->
<dependency>
<groupId>com.google.inject.extensions</groupId>
<artifactId>guice-servlet</artifactId>
<version>3.0</version>
</dependency>
<!-- Guice - Dependency Injection -->
<dependency>
<groupId>com.google.inject</groupId>
<artifactId>guice</artifactId>
<version>3.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,41 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel;
import com.google.inject.Module;
/**
* Generic means of loading a tunnel without adding explicit dependencies within
* the main ServletModule, as not all servlet containers may have the classes
* required by all tunnel implementations.
*
* @author Michael Jumper
*/
public interface TunnelLoader extends Module {
/**
* Checks whether this type of tunnel is supported by the servlet container.
*
* @return true if this type of tunnel is supported and can be loaded
* without errors, false otherwise.
*/
public boolean isSupported();
}

View File

@@ -0,0 +1,116 @@
/*
* This file has been modified from the original, upstream version to facilitate
* integration with OpenUDS.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel;
import org.apache.guacamole.tunnel.http.RestrictedGuacamoleHTTPTunnelServlet;
import com.google.inject.servlet.ServletModule;
import java.lang.reflect.InvocationTargetException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Module which loads tunnel implementations.
*
* @author Michael Jumper
*/
public class TunnelModule extends ServletModule {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(TunnelModule.class);
/**
* Classnames of all implementation-specific WebSocket tunnel modules.
*/
private static final String[] WEBSOCKET_MODULES = {
"org.apache.guacamole.tunnel.websocket.WebSocketTunnelModule",
"org.apache.guacamole.tunnel.websocket.jetty8.WebSocketTunnelModule",
"org.apache.guacamole.tunnel.websocket.jetty9.WebSocketTunnelModule",
"org.apache.guacamole.tunnel.websocket.tomcat.WebSocketTunnelModule"
};
private boolean loadWebSocketModule(String classname) {
try {
// Attempt to find WebSocket module
Class<?> module = Class.forName(classname);
// Create loader
TunnelLoader loader = (TunnelLoader) module.getConstructor().newInstance();
// Install module, if supported
if (loader.isSupported()) {
install(loader);
return true;
}
}
// If no such class or constructor, etc., then this particular
// WebSocket support is not present
catch (ClassNotFoundException e) {}
catch (NoClassDefFoundError e) {}
catch (NoSuchMethodException e) {}
// Log errors which indicate bugs
catch (InstantiationException e) {
logger.debug("Error instantiating WebSocket module.", e);
}
catch (IllegalAccessException e) {
logger.debug("Error instantiating WebSocket module.", e);
}
catch (InvocationTargetException e) {
logger.debug("Error instantiating WebSocket module.", e);
}
// Load attempt failed
return false;
}
@Override
protected void configureServlets() {
bind(TunnelRequestService.class);
// Set up HTTP tunnel
serve("/tunnel").with(RestrictedGuacamoleHTTPTunnelServlet.class);
// Try to load each WebSocket tunnel in sequence
for (String classname : WEBSOCKET_MODULES) {
if (loadWebSocketModule(classname)) {
logger.debug("WebSocket module loaded: {}", classname);
return;
}
}
// Warn of lack of WebSocket
logger.info("WebSocket support NOT present. Only HTTP will be used.");
}
}

View File

@@ -0,0 +1,371 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel;
import java.util.List;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleException;
/**
* A request object which provides only the functions absolutely required to
* retrieve and connect to a tunnel.
*
* @author Michael Jumper
*/
public abstract class TunnelRequest {
/**
* The name of the request parameter containing the user's authentication
* token.
*/
public static final String AUTH_TOKEN_PARAMETER = "token";
/**
* The name of the parameter containing the identifier of the
* AuthenticationProvider associated with the UserContext containing the
* object to which a tunnel is being requested.
*/
public static final String AUTH_PROVIDER_IDENTIFIER_PARAMETER = "GUAC_DATA_SOURCE";
/**
* The name of the parameter specifying the type of object to which a
* tunnel is being requested. Currently, this may be "c" for a Guacamole
* connection, or "g" for a Guacamole connection group.
*/
public static final String TYPE_PARAMETER = "GUAC_TYPE";
/**
* The name of the parameter containing the unique identifier of the object
* to which a tunnel is being requested.
*/
public static final String IDENTIFIER_PARAMETER = "GUAC_ID";
/**
* The name of the parameter containing the desired display width, in
* pixels.
*/
public static final String WIDTH_PARAMETER = "GUAC_WIDTH";
/**
* The name of the parameter containing the desired display height, in
* pixels.
*/
public static final String HEIGHT_PARAMETER = "GUAC_HEIGHT";
/**
* The name of the parameter containing the desired display resolution, in
* DPI.
*/
public static final String DPI_PARAMETER = "GUAC_DPI";
/**
* The name of the parameter specifying one supported audio mimetype. This
* will normally appear multiple times within a single tunnel request -
* once for each mimetype.
*/
public static final String AUDIO_PARAMETER = "GUAC_AUDIO";
/**
* The name of the parameter specifying one supported video mimetype. This
* will normally appear multiple times within a single tunnel request -
* once for each mimetype.
*/
public static final String VIDEO_PARAMETER = "GUAC_VIDEO";
/**
* The name of the parameter specifying one supported image mimetype. This
* will normally appear multiple times within a single tunnel request -
* once for each mimetype.
*/
public static final String IMAGE_PARAMETER = "GUAC_IMAGE";
/**
* All supported object types that can be used as the destination of a
* tunnel.
*/
public static enum Type {
/**
* A Guacamole connection.
*/
CONNECTION("c"),
/**
* A Guacamole connection group.
*/
CONNECTION_GROUP("g");
/**
* The parameter value which denotes a destination object of this type.
*/
final String PARAMETER_VALUE;
/**
* Defines a Type having the given corresponding parameter value.
*
* @param value
* The parameter value which denotes a destination object of this
* type.
*/
Type(String value) {
PARAMETER_VALUE = value;
}
};
/**
* Returns the value of the parameter having the given name.
*
* @param name
* The name of the parameter to return.
*
* @return
* The value of the parameter having the given name, or null if no such
* parameter was specified.
*/
public abstract String getParameter(String name);
/**
* Returns a list of all values specified for the given parameter.
*
* @param name
* The name of the parameter to return.
*
* @return
* All values of the parameter having the given name , or null if no
* such parameter was specified.
*/
public abstract List<String> getParameterValues(String name);
/**
* Returns the value of the parameter having the given name, throwing an
* exception if the parameter is missing.
*
* @param name
* The name of the parameter to return.
*
* @return
* The value of the parameter having the given name.
*
* @throws GuacamoleException
* If the parameter is not present in the request.
*/
public String getRequiredParameter(String name) throws GuacamoleException {
// Pull requested parameter, aborting if absent
String value = getParameter(name);
if (value == null)
throw new GuacamoleClientException("Parameter \"" + name + "\" is required.");
return value;
}
/**
* Returns the integer value of the parameter having the given name,
* throwing an exception if the parameter cannot be parsed.
*
* @param name
* The name of the parameter to return.
*
* @return
* The integer value of the parameter having the given name, or null if
* the parameter is missing.
*
* @throws GuacamoleException
* If the parameter is not a valid integer.
*/
public Integer getIntegerParameter(String name) throws GuacamoleException {
// Pull requested parameter
String value = getParameter(name);
if (value == null)
return null;
// Attempt to parse as an integer
try {
return Integer.parseInt(value);
}
// Rethrow any parsing error as a GuacamoleClientException
catch (NumberFormatException e) {
throw new GuacamoleClientException("Parameter \"" + name + "\" must be a valid integer.", e);
}
}
/**
* Returns the authentication token associated with this tunnel request.
*
* @return
* The authentication token associated with this tunnel request, or
* null if no authentication token is present.
*/
public String getAuthenticationToken() {
return getParameter(AUTH_TOKEN_PARAMETER);
}
/**
* Returns the identifier of the AuthenticationProvider associated with the
* UserContext from which the connection or connection group is to be
* retrieved when the tunnel is created. In the context of the REST API and
* the JavaScript side of the web application, this is referred to as the
* data source identifier.
*
* @return
* The identifier of the AuthenticationProvider associated with the
* UserContext from which the connection or connection group is to be
* retrieved when the tunnel is created.
*
* @throws GuacamoleException
* If the identifier was not present in the request.
*/
public String getAuthenticationProviderIdentifier()
throws GuacamoleException {
return getRequiredParameter(AUTH_PROVIDER_IDENTIFIER_PARAMETER);
}
/**
* Returns the type of object for which the tunnel is being requested.
*
* @return
* The type of object for which the tunnel is being requested.
*
* @throws GuacamoleException
* If the type was not present in the request, or if the type requested
* is in the wrong format.
*/
public Type getType() throws GuacamoleException {
String type = getRequiredParameter(TYPE_PARAMETER);
// For each possible object type
for (Type possibleType : Type.values()) {
// Match against defined parameter value
if (type.equals(possibleType.PARAMETER_VALUE))
return possibleType;
}
throw new GuacamoleClientException("Illegal identifier - unknown type.");
}
/**
* Returns the identifier of the destination of the tunnel being requested.
* As there are multiple types of destination objects available, and within
* multiple data sources, the associated object type and data source are
* also necessary to determine what this identifier refers to.
*
* @return
* The identifier of the destination of the tunnel being requested.
*
* @throws GuacamoleException
* If the identifier was not present in the request.
*/
public String getIdentifier() throws GuacamoleException {
return getRequiredParameter(IDENTIFIER_PARAMETER);
}
/**
* Returns the display width desired for the Guacamole session over the
* tunnel being requested.
*
* @return
* The display width desired for the Guacamole session over the tunnel
* being requested, or null if no width was given.
*
* @throws GuacamoleException
* If the width specified was not a valid integer.
*/
public Integer getWidth() throws GuacamoleException {
return getIntegerParameter(WIDTH_PARAMETER);
}
/**
* Returns the display height desired for the Guacamole session over the
* tunnel being requested.
*
* @return
* The display height desired for the Guacamole session over the tunnel
* being requested, or null if no width was given.
*
* @throws GuacamoleException
* If the height specified was not a valid integer.
*/
public Integer getHeight() throws GuacamoleException {
return getIntegerParameter(HEIGHT_PARAMETER);
}
/**
* Returns the display resolution desired for the Guacamole session over
* the tunnel being requested, in DPI.
*
* @return
* The display resolution desired for the Guacamole session over the
* tunnel being requested, or null if no resolution was given.
*
* @throws GuacamoleException
* If the resolution specified was not a valid integer.
*/
public Integer getDPI() throws GuacamoleException {
return getIntegerParameter(DPI_PARAMETER);
}
/**
* Returns a list of all audio mimetypes declared as supported within the
* tunnel request.
*
* @return
* A list of all audio mimetypes declared as supported within the
* tunnel request, or null if no mimetypes were specified.
*/
public List<String> getAudioMimetypes() {
return getParameterValues(AUDIO_PARAMETER);
}
/**
* Returns a list of all video mimetypes declared as supported within the
* tunnel request.
*
* @return
* A list of all video mimetypes declared as supported within the
* tunnel request, or null if no mimetypes were specified.
*/
public List<String> getVideoMimetypes() {
return getParameterValues(VIDEO_PARAMETER);
}
/**
* Returns a list of all image mimetypes declared as supported within the
* tunnel request.
*
* @return
* A list of all image mimetypes declared as supported within the
* tunnel request, or null if no mimetypes were specified.
*/
public List<String> getImageMimetypes() {
return getParameterValues(IMAGE_PARAMETER);
}
}

View File

@@ -0,0 +1,159 @@
/*
* This file has been modified from the original, upstream version to facilitate
* integration with OpenUDS.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.List;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleSocket;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.net.InetGuacamoleSocket;
import org.apache.guacamole.net.SimpleGuacamoleTunnel;
import org.apache.guacamole.protocol.ConfiguredGuacamoleSocket;
import org.apache.guacamole.protocol.GuacamoleClientInformation;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
import org.openuds.guacamole.connection.ConnectionService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility class that takes a standard request from the Guacamole JavaScript
* client and produces the corresponding GuacamoleTunnel. The implementation
* of this utility is specific to the form of request used by the upstream
* Guacamole web application, and is not necessarily useful to applications
* that use purely the Guacamole API.
*
* @author Michael Jumper
* @author Vasily Loginov
*/
@Singleton
public class TunnelRequestService {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(TunnelRequestService.class);
/**
* Service for retrieving remotely-maintained connection information.
*/
@Inject
private ConnectionService connectionService;
/**
* The hostname of the server hosting guacd.
*/
private static final String GUACD_HOSTNAME = "127.0.0.1";
/**
* The port that guacd will be listening on.
*/
private static final int GUACD_PORT = 4822;
/**
* Creates a new tunnel using the parameters and credentials present in
* the given request.
*
* @param request
* The HttpServletRequest describing the tunnel to create.
*
* @return
* The created tunnel, or null if the tunnel could not be created.
*
* @throws GuacamoleException
* If an error occurs while creating the tunnel.
*/
public GuacamoleTunnel createTunnel(TunnelRequest request) throws GuacamoleException {
// Pull OpenUDS-specific "data" parameter
String data = request.getParameter("data");
if (data == null || data.isEmpty()) {
logger.debug("No ID received in tunnel connect request.");
throw new GuacamoleClientException("Connection data not provided.");
}
logger.debug("Establishing tunnel and connection with data from \"{}\"...", data);
// Get connection from remote service
GuacamoleConfiguration config = connectionService.getConnectionConfiguration(data);
if (config == null)
throw new GuacamoleClientException("Connection configuration could not be retrieved.");
// Get client information
GuacamoleClientInformation info = new GuacamoleClientInformation();
// Set width if provided
String width = request.getParameter("GUAC_WIDTH");
if (width != null)
info.setOptimalScreenWidth(Integer.parseInt(width));
// Set height if provided
String height = request.getParameter("GUAC_HEIGHT");
if (height != null)
info.setOptimalScreenHeight(Integer.parseInt(height));
// Set resolution if provided
String dpi = request.getParameter("GUAC_DPI");
if (dpi != null)
info.setOptimalResolution(Integer.parseInt(dpi));
// Add audio mimetypes
List<String> audio_mimetypes = request.getParameterValues("GUAC_AUDIO");
if (audio_mimetypes != null)
info.getAudioMimetypes().addAll(audio_mimetypes);
// Add video mimetypes
List<String> video_mimetypes = request.getParameterValues("GUAC_VIDEO");
if (video_mimetypes != null)
info.getVideoMimetypes().addAll(video_mimetypes);
// Add image mimetypes
List<String> image_mimetypes = request.getParameterValues("GUAC_IMAGE");
if (image_mimetypes != null)
info.getImageMimetypes().addAll(image_mimetypes);
// Connect socket for connection
GuacamoleSocket socket;
try {
socket = new ConfiguredGuacamoleSocket(
new InetGuacamoleSocket(GUACD_HOSTNAME, GUACD_PORT),
config, info
);
}
// Log any errors during connection
catch (GuacamoleException e) {
logger.error("Unable to connect to guacd.", e);
throw e;
}
// Return corresponding tunnel
return new SimpleGuacamoleTunnel(socket);
}
}

View File

@@ -0,0 +1,88 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.http;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.tunnel.TunnelRequest;
/**
* HTTP-specific implementation of TunnelRequest.
*
* @author Michael Jumper
*/
public class HTTPTunnelRequest extends TunnelRequest {
/**
* A copy of the parameters obtained from the HttpServletRequest used to
* construct the HTTPTunnelRequest.
*/
private final Map<String, List<String>> parameterMap =
new HashMap<String, List<String>>();
/**
* Creates a HTTPTunnelRequest which copies and exposes the parameters
* from the given HttpServletRequest.
*
* @param request
* The HttpServletRequest to copy parameter values from.
*/
@SuppressWarnings("unchecked") // getParameterMap() is defined as returning Map<String, String[]>
public HTTPTunnelRequest(HttpServletRequest request) {
// For each parameter
for (Map.Entry<String, String[]> mapEntry : ((Map<String, String[]>)
request.getParameterMap()).entrySet()) {
// Get parameter name and corresponding values
String parameterName = mapEntry.getKey();
List<String> parameterValues = Arrays.asList(mapEntry.getValue());
// Store copy of all values in our own map
parameterMap.put(
parameterName,
new ArrayList<String>(parameterValues)
);
}
}
@Override
public String getParameter(String name) {
List<String> values = getParameterValues(name);
// Return the first value from the list if available
if (values != null && !values.isEmpty())
return values.get(0);
return null;
}
@Override
public List<String> getParameterValues(String name) {
return parameterMap.get(name);
}
}

View File

@@ -0,0 +1,70 @@
/*
* This file has been modified from the original, upstream version to facilitate
* integration with OpenUDS.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.http;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.servlet.GuacamoleHTTPTunnelServlet;
import org.apache.guacamole.tunnel.TunnelRequestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Connects users to a tunnel associated with the authorized connection
* having the given ID.
*
* @author Michael Jumper
*/
@Singleton
public class RestrictedGuacamoleHTTPTunnelServlet extends GuacamoleHTTPTunnelServlet {
/**
* Service for handling tunnel requests.
*/
@Inject
private TunnelRequestService tunnelRequestService;
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(RestrictedGuacamoleHTTPTunnelServlet.class);
@Override
protected GuacamoleTunnel doConnect(HttpServletRequest request) throws GuacamoleException {
// Attempt to create HTTP tunnel
GuacamoleTunnel tunnel = tunnelRequestService.createTunnel(new HTTPTunnelRequest(request));
// If successful, warn of lack of WebSocket
logger.info("Using HTTP tunnel (not WebSocket). Performance may be sub-optimal.");
return tunnel;
}
}

View File

@@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Classes which leverage Guacamole's built-in HTTP tunnel implementation.
*/
package org.apache.guacamole.tunnel.http;

View File

@@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Classes which are common to all tunnel implementations.
*/
package org.apache.guacamole.tunnel;

View File

@@ -0,0 +1,122 @@
/*
* This file has been modified from the original, upstream version to facilitate
* integration with OpenUDS.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket;
import com.google.inject.Provider;
import java.util.Map;
import javax.websocket.EndpointConfig;
import javax.websocket.HandshakeResponse;
import javax.websocket.Session;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.tunnel.TunnelRequest;
import org.apache.guacamole.websocket.GuacamoleWebSocketTunnelEndpoint;
import org.apache.guacamole.tunnel.TunnelRequestService;
/**
* Tunnel implementation which uses WebSocket as a tunnel backend, rather than
* HTTP, properly parsing connection IDs included in the connection request.
*/
public class RestrictedGuacamoleWebSocketTunnelEndpoint extends GuacamoleWebSocketTunnelEndpoint {
/**
* Unique string which shall be used to store the TunnelRequest
* associated with a WebSocket connection.
*/
private static final String TUNNEL_REQUEST_PROPERTY = "WS_GUAC_TUNNEL_REQUEST";
/**
* Unique string which shall be used to store the TunnelRequestService to
* be used for processing TunnelRequests.
*/
private static final String TUNNEL_REQUEST_SERVICE_PROPERTY = "WS_GUAC_TUNNEL_REQUEST_SERVICE";
/**
* Configurator implementation which stores the requested GuacamoleTunnel
* within the user properties. The GuacamoleTunnel will be later retrieved
* during the connection process.
*/
public static class Configurator extends ServerEndpointConfig.Configurator {
/**
* Provider which provides instances of a service for handling
* tunnel requests.
*/
private final Provider<TunnelRequestService> tunnelRequestServiceProvider;
/**
* Creates a new Configurator which uses the given tunnel request
* service provider to retrieve the necessary service to handle new
* connections requests.
*
* @param tunnelRequestServiceProvider
* The tunnel request service provider to use for all new
* connections.
*/
public Configurator(Provider<TunnelRequestService> tunnelRequestServiceProvider) {
this.tunnelRequestServiceProvider = tunnelRequestServiceProvider;
}
@Override
public void modifyHandshake(ServerEndpointConfig config,
HandshakeRequest request, HandshakeResponse response) {
super.modifyHandshake(config, request, response);
// Store tunnel request and tunnel request service for retrieval
// upon WebSocket open
Map<String, Object> userProperties = config.getUserProperties();
userProperties.clear();
userProperties.put(TUNNEL_REQUEST_PROPERTY, new WebSocketTunnelRequest(request));
userProperties.put(TUNNEL_REQUEST_SERVICE_PROPERTY, tunnelRequestServiceProvider.get());
}
}
@Override
protected GuacamoleTunnel createTunnel(Session session,
EndpointConfig config) throws GuacamoleException {
Map<String, Object> userProperties = config.getUserProperties();
// Get original tunnel request
TunnelRequest tunnelRequest = (TunnelRequest) userProperties.get(TUNNEL_REQUEST_PROPERTY);
if (tunnelRequest == null)
return null;
// Get tunnel request service
TunnelRequestService tunnelRequestService = (TunnelRequestService) userProperties.get(TUNNEL_REQUEST_SERVICE_PROPERTY);
if (tunnelRequestService == null)
return null;
// Create and return tunnel
return tunnelRequestService.createTunnel(tunnelRequest);
}
}

View File

@@ -0,0 +1,106 @@
/*
* This file has been modified from the original, upstream version to facilitate
* integration with OpenUDS.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket;
import com.google.inject.Provider;
import com.google.inject.servlet.ServletModule;
import java.util.Arrays;
import javax.websocket.DeploymentException;
import javax.websocket.server.ServerContainer;
import javax.websocket.server.ServerEndpointConfig;
import org.apache.guacamole.tunnel.TunnelLoader;
import org.apache.guacamole.tunnel.TunnelRequestService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Loads the JSR-356 WebSocket tunnel implementation.
*
* @author Michael Jumper
*/
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
@Override
public boolean isSupported() {
try {
// Attempt to find WebSocket servlet
Class.forName("javax.websocket.Endpoint");
// Support found
return true;
}
// If no such servlet class, this particular WebSocket support
// is not present
catch (ClassNotFoundException e) {}
catch (NoClassDefFoundError e) {}
// Support not found
return false;
}
@Override
public void configureServlets() {
logger.info("Loading JSR-356 WebSocket support...");
// Get container
ServerContainer container = (ServerContainer) getServletContext().getAttribute("javax.websocket.server.ServerContainer");
if (container == null) {
logger.warn("ServerContainer attribute required by JSR-356 is missing. Cannot load JSR-356 WebSocket support.");
return;
}
Provider<TunnelRequestService> tunnelRequestServiceProvider = getProvider(TunnelRequestService.class);
// Build configuration for WebSocket tunnel
ServerEndpointConfig config =
ServerEndpointConfig.Builder.create(RestrictedGuacamoleWebSocketTunnelEndpoint.class, "/websocket-tunnel")
.configurator(new RestrictedGuacamoleWebSocketTunnelEndpoint.Configurator(tunnelRequestServiceProvider))
.subprotocols(Arrays.asList(new String[]{"guacamole"}))
.build();
try {
// Add configuration to container
container.addEndpoint(config);
}
catch (DeploymentException e) {
logger.error("Unable to deploy WebSocket tunnel.", e);
}
}
}

View File

@@ -0,0 +1,67 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket;
import java.util.List;
import java.util.Map;
import javax.websocket.server.HandshakeRequest;
import org.apache.guacamole.tunnel.TunnelRequest;
/**
* WebSocket-specific implementation of TunnelRequest.
*
* @author Michael Jumper
*/
public class WebSocketTunnelRequest extends TunnelRequest {
/**
* All parameters passed via HTTP to the WebSocket handshake.
*/
private final Map<String, List<String>> handshakeParameters;
/**
* Creates a TunnelRequest implementation which delegates parameter and
* session retrieval to the given HandshakeRequest.
*
* @param request The HandshakeRequest to wrap.
*/
public WebSocketTunnelRequest(HandshakeRequest request) {
this.handshakeParameters = request.getParameterMap();
}
@Override
public String getParameter(String name) {
// Pull list of values, if present
List<String> values = getParameterValues(name);
if (values == null || values.isEmpty())
return null;
// Return first parameter value arbitrarily
return values.get(0);
}
@Override
public List<String> getParameterValues(String name) {
return handshakeParameters.get(name);
}
}

View File

@@ -0,0 +1,236 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.jetty8;
import java.io.IOException;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.eclipse.jetty.websocket.WebSocket;
import org.eclipse.jetty.websocket.WebSocket.Connection;
import org.eclipse.jetty.websocket.WebSocketServlet;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleConnectionClosedException;
import org.apache.guacamole.protocol.GuacamoleInstruction;
import org.apache.guacamole.tunnel.http.HTTPTunnelRequest;
import org.apache.guacamole.tunnel.TunnelRequest;
import org.apache.guacamole.protocol.GuacamoleStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
*
* @author Michael Jumper
*/
public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
/**
* The default, minimum buffer size for instructions.
*/
private static final int BUFFER_SIZE = 8192;
/**
* Sends the given status on the given WebSocket connection and closes the
* connection.
*
* @param connection The WebSocket connection to close.
* @param guac_status The status to send.
*/
public static void closeConnection(Connection connection,
GuacamoleStatus guac_status) {
connection.close(guac_status.getWebSocketCode(),
Integer.toString(guac_status.getGuacamoleStatusCode()));
}
@Override
public WebSocket doWebSocketConnect(HttpServletRequest request, String protocol) {
final TunnelRequest tunnelRequest = new HTTPTunnelRequest(request);
// Return new WebSocket which communicates through tunnel
return new WebSocket.OnTextMessage() {
/**
* The GuacamoleTunnel associated with the connected WebSocket. If
* the WebSocket has not yet been connected, this will be null.
*/
private GuacamoleTunnel tunnel = null;
@Override
public void onMessage(String string) {
// Ignore inbound messages if there is no associated tunnel
if (tunnel == null)
return;
GuacamoleWriter writer = tunnel.acquireWriter();
// Write message received
try {
writer.write(string.toCharArray());
}
catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e);
}
catch (GuacamoleException e) {
logger.debug("WebSocket tunnel write failed.", e);
}
tunnel.releaseWriter();
}
@Override
public void onOpen(final Connection connection) {
try {
tunnel = doConnect(tunnelRequest);
}
catch (GuacamoleException e) {
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
logger.debug("Error connecting WebSocket tunnel.", e);
closeConnection(connection, e.getStatus());
return;
}
// Do not start connection if tunnel does not exist
if (tunnel == null) {
closeConnection(connection, GuacamoleStatus.RESOURCE_NOT_FOUND);
return;
}
Thread readThread = new Thread() {
@Override
public void run() {
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
GuacamoleReader reader = tunnel.acquireReader();
char[] readMessage;
try {
// Send tunnel UUID
connection.sendMessage(new GuacamoleInstruction(
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
tunnel.getUUID().toString()
).toString());
try {
// Attempt to read
while ((readMessage = reader.read()) != null) {
// Buffer message
buffer.append(readMessage);
// Flush if we expect to wait or buffer is getting full
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
connection.sendMessage(buffer.toString());
buffer.setLength(0);
}
}
// No more data
closeConnection(connection, GuacamoleStatus.SUCCESS);
}
// Catch any thrown guacamole exception and attempt
// to pass within the WebSocket connection, logging
// each error appropriately.
catch (GuacamoleClientException e) {
logger.info("WebSocket connection terminated: {}", e.getMessage());
logger.debug("WebSocket connection terminated due to client error.", e);
closeConnection(connection, e.getStatus());
}
catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e);
closeConnection(connection, GuacamoleStatus.SUCCESS);
}
catch (GuacamoleException e) {
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
logger.debug("Internal error during connection to guacd.", e);
closeConnection(connection, e.getStatus());
}
}
catch (IOException e) {
logger.debug("WebSocket tunnel read failed due to I/O error.", e);
}
}
};
readThread.start();
}
@Override
public void onClose(int i, String string) {
try {
if (tunnel != null)
tunnel.close();
}
catch (GuacamoleException e) {
logger.debug("Unable to close connection to guacd.", e);
}
}
};
}
/**
* Called whenever the JavaScript Guacamole client makes a connection
* request. It it up to the implementor of this function to define what
* conditions must be met for a tunnel to be configured and returned as a
* result of this connection request (whether some sort of credentials must
* be specified, for example).
*
* @param request
* The TunnelRequest associated with the connection request received.
* Any parameters specified along with the connection request can be
* read from this object.
*
* @return
* A newly constructed GuacamoleTunnel if successful, null otherwise.
*
* @throws GuacamoleException
* If an error occurs while constructing the GuacamoleTunnel, or if the
* conditions required for connection are not met.
*/
protected abstract GuacamoleTunnel doConnect(TunnelRequest request)
throws GuacamoleException;
}

View File

@@ -0,0 +1,54 @@
/*
* This file has been modified from the original, upstream version to facilitate
* integration with OpenUDS.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.jetty8;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.tunnel.TunnelRequest;
import org.apache.guacamole.tunnel.TunnelRequestService;
/**
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
* rather than HTTP, properly parsing connection IDs included in the connection
* request.
*/
@Singleton
public class RestrictedGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
/**
* Service for handling tunnel requests.
*/
@Inject
private TunnelRequestService tunnelRequestService;
@Override
protected GuacamoleTunnel doConnect(TunnelRequest request)
throws GuacamoleException {
return tunnelRequestService.createTunnel(request);
}
}

View File

@@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.jetty8;
import com.google.inject.servlet.ServletModule;
import org.apache.guacamole.tunnel.TunnelLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Loads the Jetty 8 WebSocket tunnel implementation.
*
* @author Michael Jumper
*/
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
@Override
public boolean isSupported() {
try {
// Attempt to find WebSocket servlet
Class.forName("org.apache.guacamole.tunnel.websocket.jetty8.RestrictedGuacamoleWebSocketTunnelServlet");
// Support found
return true;
}
// If no such servlet class, this particular WebSocket support
// is not present
catch (ClassNotFoundException e) {}
catch (NoClassDefFoundError e) {}
// Support not found
return false;
}
@Override
public void configureServlets() {
logger.info("Loading Jetty 8 WebSocket support...");
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
}
}

View File

@@ -0,0 +1,23 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Jetty 8 WebSocket tunnel implementation. The classes here require Jetty 8.
*/
package org.apache.guacamole.tunnel.websocket.jetty8;

View File

@@ -0,0 +1,247 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.jetty9;
import java.io.IOException;
import org.eclipse.jetty.websocket.api.CloseStatus;
import org.eclipse.jetty.websocket.api.RemoteEndpoint;
import org.eclipse.jetty.websocket.api.Session;
import org.eclipse.jetty.websocket.api.WebSocketListener;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleConnectionClosedException;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.protocol.GuacamoleInstruction;
import org.apache.guacamole.protocol.GuacamoleStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* WebSocket listener implementation which provides a Guacamole tunnel
*
* @author Michael Jumper
*/
public abstract class GuacamoleWebSocketTunnelListener implements WebSocketListener {
/**
* The default, minimum buffer size for instructions.
*/
private static final int BUFFER_SIZE = 8192;
/**
* Logger for this class.
*/
private static final Logger logger = LoggerFactory.getLogger(RestrictedGuacamoleWebSocketTunnelServlet.class);
/**
* The underlying GuacamoleTunnel. WebSocket reads/writes will be handled
* as reads/writes to this tunnel.
*/
private GuacamoleTunnel tunnel;
/**
* Sends the given status on the given WebSocket connection and closes the
* connection.
*
* @param session The outbound WebSocket connection to close.
* @param guac_status The status to send.
*/
private void closeConnection(Session session, GuacamoleStatus guac_status) {
try {
int code = guac_status.getWebSocketCode();
String message = Integer.toString(guac_status.getGuacamoleStatusCode());
session.close(new CloseStatus(code, message));
}
catch (IOException e) {
logger.debug("Unable to close WebSocket connection.", e);
}
}
/**
* Returns a new tunnel for the given session. How this tunnel is created
* or retrieved is implementation-dependent.
*
* @param session The session associated with the active WebSocket
* connection.
* @return A connected tunnel, or null if no such tunnel exists.
* @throws GuacamoleException If an error occurs while retrieving the
* tunnel, or if access to the tunnel is denied.
*/
protected abstract GuacamoleTunnel createTunnel(Session session)
throws GuacamoleException;
@Override
public void onWebSocketConnect(final Session session) {
try {
// Get tunnel
tunnel = createTunnel(session);
if (tunnel == null) {
closeConnection(session, GuacamoleStatus.RESOURCE_NOT_FOUND);
return;
}
}
catch (GuacamoleException e) {
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
logger.debug("Error connecting WebSocket tunnel.", e);
closeConnection(session, e.getStatus());
return;
}
// Prepare read transfer thread
Thread readThread = new Thread() {
/**
* Remote (client) side of this connection
*/
private final RemoteEndpoint remote = session.getRemote();
@Override
public void run() {
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
GuacamoleReader reader = tunnel.acquireReader();
char[] readMessage;
try {
// Send tunnel UUID
remote.sendString(new GuacamoleInstruction(
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
tunnel.getUUID().toString()
).toString());
try {
// Attempt to read
while ((readMessage = reader.read()) != null) {
// Buffer message
buffer.append(readMessage);
// Flush if we expect to wait or buffer is getting full
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
remote.sendString(buffer.toString());
buffer.setLength(0);
}
}
// No more data
closeConnection(session, GuacamoleStatus.SUCCESS);
}
// Catch any thrown guacamole exception and attempt
// to pass within the WebSocket connection, logging
// each error appropriately.
catch (GuacamoleClientException e) {
logger.info("WebSocket connection terminated: {}", e.getMessage());
logger.debug("WebSocket connection terminated due to client error.", e);
closeConnection(session, e.getStatus());
}
catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e);
closeConnection(session, GuacamoleStatus.SUCCESS);
}
catch (GuacamoleException e) {
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
logger.debug("Internal error during connection to guacd.", e);
closeConnection(session, e.getStatus());
}
}
catch (IOException e) {
logger.debug("I/O error prevents further reads.", e);
}
}
};
readThread.start();
}
@Override
public void onWebSocketText(String message) {
// Ignore inbound messages if there is no associated tunnel
if (tunnel == null)
return;
GuacamoleWriter writer = tunnel.acquireWriter();
try {
// Write received message
writer.write(message.toCharArray());
}
catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e);
}
catch (GuacamoleException e) {
logger.debug("WebSocket tunnel write failed.", e);
}
tunnel.releaseWriter();
}
@Override
public void onWebSocketBinary(byte[] payload, int offset, int length) {
throw new UnsupportedOperationException("Binary WebSocket messages are not supported.");
}
@Override
public void onWebSocketError(Throwable t) {
logger.debug("WebSocket tunnel closing due to error.", t);
try {
if (tunnel != null)
tunnel.close();
}
catch (GuacamoleException e) {
logger.debug("Unable to close connection to guacd.", e);
}
}
@Override
public void onWebSocketClose(int statusCode, String reason) {
try {
if (tunnel != null)
tunnel.close();
}
catch (GuacamoleException e) {
logger.debug("Unable to close connection to guacd.", e);
}
}
}

View File

@@ -0,0 +1,74 @@
/*
* This file has been modified from the original, upstream version to facilitate
* integration with OpenUDS.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.jetty9;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.eclipse.jetty.websocket.api.UpgradeResponse;
import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
import org.apache.guacamole.tunnel.TunnelRequestService;
/**
* WebSocketCreator which selects the appropriate WebSocketListener
* implementation if the "guacamole" subprotocol is in use.
*
* @author Michael Jumper
*/
public class RestrictedGuacamoleWebSocketCreator implements WebSocketCreator {
/**
* Service for handling tunnel requests.
*/
private final TunnelRequestService tunnelRequestService;
/**
* Creates a new WebSocketCreator which uses the given TunnelRequestService
* to create new GuacamoleTunnels for inbound requests.
*
* @param tunnelRequestService The service to use for inbound tunnel
* requests.
*/
public RestrictedGuacamoleWebSocketCreator(TunnelRequestService tunnelRequestService) {
this.tunnelRequestService = tunnelRequestService;
}
@Override
public Object createWebSocket(UpgradeRequest request, UpgradeResponse response) {
// Validate and use "guacamole" subprotocol
for (String subprotocol : request.getSubProtocols()) {
if ("guacamole".equals(subprotocol)) {
response.setAcceptedSubProtocol(subprotocol);
return new RestrictedGuacamoleWebSocketTunnelListener(tunnelRequestService);
}
}
// Invalid protocol
return null;
}
}

View File

@@ -0,0 +1,61 @@
/*
* This file has been modified from the original, upstream version to facilitate
* integration with OpenUDS.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.jetty9;
import org.eclipse.jetty.websocket.api.Session;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.tunnel.TunnelRequestService;
/**
* WebSocket listener implementation which properly parses connection IDs
* included in the connection request.
*
* @author Michael Jumper
*/
public class RestrictedGuacamoleWebSocketTunnelListener extends GuacamoleWebSocketTunnelListener {
/**
* Service for handling tunnel requests.
*/
private final TunnelRequestService tunnelRequestService;
/**
* Creates a new WebSocketListener which uses the given TunnelRequestService
* to create new GuacamoleTunnels for inbound requests.
*
* @param tunnelRequestService The service to use for inbound tunnel
* requests.
*/
public RestrictedGuacamoleWebSocketTunnelListener(TunnelRequestService tunnelRequestService) {
this.tunnelRequestService = tunnelRequestService;
}
@Override
protected GuacamoleTunnel createTunnel(Session session) throws GuacamoleException {
return tunnelRequestService.createTunnel(new WebSocketTunnelRequest(session.getUpgradeRequest()));
}
}

View File

@@ -0,0 +1,56 @@
/*
* This file has been modified from the original, upstream version to facilitate
* integration with OpenUDS.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.jetty9;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
import org.apache.guacamole.tunnel.TunnelRequestService;
/**
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
*
* @author Michael Jumper
*/
@Singleton
public class RestrictedGuacamoleWebSocketTunnelServlet extends WebSocketServlet {
/**
* Service for handling tunnel requests.
*/
@Inject
private TunnelRequestService tunnelRequestService;
@Override
public void configure(WebSocketServletFactory factory) {
// Register WebSocket implementation
factory.setCreator(new RestrictedGuacamoleWebSocketCreator(tunnelRequestService));
}
}

View File

@@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.jetty9;
import com.google.inject.servlet.ServletModule;
import org.apache.guacamole.tunnel.TunnelLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Loads the Jetty 9 WebSocket tunnel implementation.
*
* @author Michael Jumper
*/
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
@Override
public boolean isSupported() {
try {
// Attempt to find WebSocket servlet
Class.forName("org.apache.guacamole.tunnel.websocket.jetty9.RestrictedGuacamoleWebSocketTunnelServlet");
// Support found
return true;
}
// If no such servlet class, this particular WebSocket support
// is not present
catch (ClassNotFoundException e) {}
catch (NoClassDefFoundError e) {}
// Support not found
return false;
}
@Override
public void configureServlets() {
logger.info("Loading Jetty 9 WebSocket support...");
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
}
}

View File

@@ -0,0 +1,73 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.jetty9;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import org.eclipse.jetty.websocket.api.UpgradeRequest;
import org.apache.guacamole.tunnel.TunnelRequest;
/**
* Jetty 9 WebSocket-specific implementation of TunnelRequest.
*
* @author Michael Jumper
*/
public class WebSocketTunnelRequest extends TunnelRequest {
/**
* All parameters passed via HTTP to the WebSocket handshake.
*/
private final Map<String, String[]> handshakeParameters;
/**
* Creates a TunnelRequest implementation which delegates parameter and
* session retrieval to the given UpgradeRequest.
*
* @param request The UpgradeRequest to wrap.
*/
public WebSocketTunnelRequest(UpgradeRequest request) {
this.handshakeParameters = request.getParameterMap();
}
@Override
public String getParameter(String name) {
// Pull list of values, if present
List<String> values = getParameterValues(name);
if (values == null || values.isEmpty())
return null;
// Return first parameter value arbitrarily
return values.get(0);
}
@Override
public List<String> getParameterValues(String name) {
String[] values = handshakeParameters.get(name);
if (values == null)
return null;
return Arrays.asList(values);
}
}

View File

@@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Jetty 9 WebSocket tunnel implementation. The classes here require at least
* Jetty 9, prior to Jetty 9.1 (when support for JSR 356 was implemented).
*/
package org.apache.guacamole.tunnel.websocket.jetty9;

View File

@@ -0,0 +1,24 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Standard WebSocket tunnel implementation. The classes here require a recent
* servlet container that supports JSR 356.
*/
package org.apache.guacamole.tunnel.websocket;

View File

@@ -0,0 +1,269 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.tomcat;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.io.GuacamoleReader;
import org.apache.guacamole.io.GuacamoleWriter;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.catalina.websocket.StreamInbound;
import org.apache.catalina.websocket.WebSocketServlet;
import org.apache.catalina.websocket.WsOutbound;
import org.apache.guacamole.GuacamoleClientException;
import org.apache.guacamole.GuacamoleConnectionClosedException;
import org.apache.guacamole.protocol.GuacamoleInstruction;
import org.apache.guacamole.tunnel.http.HTTPTunnelRequest;
import org.apache.guacamole.tunnel.TunnelRequest;
import org.apache.guacamole.protocol.GuacamoleStatus;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* A WebSocketServlet partial re-implementation of GuacamoleTunnelServlet.
*
* @author Michael Jumper
*/
public abstract class GuacamoleWebSocketTunnelServlet extends WebSocketServlet {
/**
* The default, minimum buffer size for instructions.
*/
private static final int BUFFER_SIZE = 8192;
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(GuacamoleWebSocketTunnelServlet.class);
/**
* Sends the given status on the given WebSocket connection and closes the
* connection.
*
* @param outbound The outbound WebSocket connection to close.
* @param guac_status The status to send.
*/
public void closeConnection(WsOutbound outbound, GuacamoleStatus guac_status) {
try {
byte[] message = Integer.toString(guac_status.getGuacamoleStatusCode()).getBytes("UTF-8");
outbound.close(guac_status.getWebSocketCode(), ByteBuffer.wrap(message));
}
catch (IOException e) {
logger.debug("Unable to close WebSocket tunnel.", e);
}
}
@Override
protected String selectSubProtocol(List<String> subProtocols) {
// Search for expected protocol
for (String protocol : subProtocols)
if ("guacamole".equals(protocol))
return "guacamole";
// Otherwise, fail
return null;
}
@Override
public StreamInbound createWebSocketInbound(String protocol,
HttpServletRequest request) {
final TunnelRequest tunnelRequest = new HTTPTunnelRequest(request);
// Return new WebSocket which communicates through tunnel
return new StreamInbound() {
/**
* The GuacamoleTunnel associated with the connected WebSocket. If
* the WebSocket has not yet been connected, this will be null.
*/
private GuacamoleTunnel tunnel = null;
@Override
protected void onTextData(Reader reader) throws IOException {
// Ignore inbound messages if there is no associated tunnel
if (tunnel == null)
return;
GuacamoleWriter writer = tunnel.acquireWriter();
// Write all available data
try {
char[] buffer = new char[BUFFER_SIZE];
int num_read;
while ((num_read = reader.read(buffer)) > 0)
writer.write(buffer, 0, num_read);
}
catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e);
}
catch (GuacamoleException e) {
logger.debug("WebSocket tunnel write failed.", e);
}
tunnel.releaseWriter();
}
@Override
public void onOpen(final WsOutbound outbound) {
try {
tunnel = doConnect(tunnelRequest);
}
catch (GuacamoleException e) {
logger.error("Creation of WebSocket tunnel to guacd failed: {}", e.getMessage());
logger.debug("Error connecting WebSocket tunnel.", e);
closeConnection(outbound, e.getStatus());
return;
}
// Do not start connection if tunnel does not exist
if (tunnel == null) {
closeConnection(outbound, GuacamoleStatus.RESOURCE_NOT_FOUND);
return;
}
Thread readThread = new Thread() {
@Override
public void run() {
StringBuilder buffer = new StringBuilder(BUFFER_SIZE);
GuacamoleReader reader = tunnel.acquireReader();
char[] readMessage;
try {
// Send tunnel UUID
outbound.writeTextMessage(CharBuffer.wrap(new GuacamoleInstruction(
GuacamoleTunnel.INTERNAL_DATA_OPCODE,
tunnel.getUUID().toString()
).toString()));
try {
// Attempt to read
while ((readMessage = reader.read()) != null) {
// Buffer message
buffer.append(readMessage);
// Flush if we expect to wait or buffer is getting full
if (!reader.available() || buffer.length() >= BUFFER_SIZE) {
outbound.writeTextMessage(CharBuffer.wrap(buffer));
buffer.setLength(0);
}
}
// No more data
closeConnection(outbound, GuacamoleStatus.SUCCESS);
}
// Catch any thrown guacamole exception and attempt
// to pass within the WebSocket connection, logging
// each error appropriately.
catch (GuacamoleClientException e) {
logger.info("WebSocket connection terminated: {}", e.getMessage());
logger.debug("WebSocket connection terminated due to client error.", e);
closeConnection(outbound, e.getStatus());
}
catch (GuacamoleConnectionClosedException e) {
logger.debug("Connection to guacd closed.", e);
closeConnection(outbound, GuacamoleStatus.SUCCESS);
}
catch (GuacamoleException e) {
logger.error("Connection to guacd terminated abnormally: {}", e.getMessage());
logger.debug("Internal error during connection to guacd.", e);
closeConnection(outbound, e.getStatus());
}
}
catch (IOException e) {
logger.debug("I/O error prevents further reads.", e);
}
}
};
readThread.start();
}
@Override
public void onClose(int i) {
try {
if (tunnel != null)
tunnel.close();
}
catch (GuacamoleException e) {
logger.debug("Unable to close connection to guacd.", e);
}
}
@Override
protected void onBinaryData(InputStream in) throws IOException {
throw new UnsupportedOperationException("Not supported yet.");
}
};
}
/**
* Called whenever the JavaScript Guacamole client makes a connection
* request. It it up to the implementor of this function to define what
* conditions must be met for a tunnel to be configured and returned as a
* result of this connection request (whether some sort of credentials must
* be specified, for example).
*
* @param request
* The TunnelRequest associated with the connection request received.
* Any parameters specified along with the connection request can be
* read from this object.
*
* @return
* A newly constructed GuacamoleTunnel if successful, null otherwise.
*
* @throws GuacamoleException
* If an error occurs while constructing the GuacamoleTunnel, or if the
* conditions required for connection are not met.
*/
protected abstract GuacamoleTunnel doConnect(TunnelRequest request)
throws GuacamoleException;
}

View File

@@ -0,0 +1,54 @@
/*
* This file has been modified from the original, upstream version to facilitate
* integration with OpenUDS.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.tomcat;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.net.GuacamoleTunnel;
import org.apache.guacamole.tunnel.TunnelRequest;
import org.apache.guacamole.tunnel.TunnelRequestService;
/**
* Tunnel servlet implementation which uses WebSocket as a tunnel backend,
* rather than HTTP, properly parsing connection IDs included in the connection
* request.
*/
@Singleton
public class RestrictedGuacamoleWebSocketTunnelServlet extends GuacamoleWebSocketTunnelServlet {
/**
* Service for handling tunnel requests.
*/
@Inject
private TunnelRequestService tunnelRequestService;
@Override
protected GuacamoleTunnel doConnect(TunnelRequest request)
throws GuacamoleException {
return tunnelRequestService.createTunnel(request);
};
}

View File

@@ -0,0 +1,70 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.guacamole.tunnel.websocket.tomcat;
import com.google.inject.servlet.ServletModule;
import org.apache.guacamole.tunnel.TunnelLoader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Loads the Jetty 9 WebSocket tunnel implementation.
*
* @author Michael Jumper
*/
public class WebSocketTunnelModule extends ServletModule implements TunnelLoader {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(WebSocketTunnelModule.class);
@Override
public boolean isSupported() {
try {
// Attempt to find WebSocket servlet
Class.forName("org.apache.guacamole.tunnel.websocket.tomcat.RestrictedGuacamoleWebSocketTunnelServlet");
// Support found
return true;
}
// If no such servlet class, this particular WebSocket support
// is not present
catch (ClassNotFoundException e) {}
catch (NoClassDefFoundError e) {}
// Support not found
return false;
}
@Override
public void configureServlets() {
logger.info("Loading Tomcat 7 WebSocket support...");
serve("/websocket-tunnel").with(RestrictedGuacamoleWebSocketTunnelServlet.class);
}
}

View File

@@ -0,0 +1,25 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* Tomcat WebSocket tunnel implementation. The classes here require at least
* Tomcat 7.0, and may change significantly as there is no common WebSocket
* API for Java yet.
*/
package org.apache.guacamole.tunnel.websocket.tomcat;

View File

@@ -1,148 +0,0 @@
package org.openuds.guacamole;
import java.io.BufferedReader;
import java.io.FileReader;
import java.net.URL;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Properties;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.net.GuacamoleSocket;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
import org.glyptodon.guacamole.net.SimpleGuacamoleTunnel;
import org.glyptodon.guacamole.net.InetGuacamoleSocket;
import org.glyptodon.guacamole.protocol.ConfiguredGuacamoleSocket;
import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
import org.glyptodon.guacamole.protocol.GuacamoleConfiguration;
import org.glyptodon.guacamole.servlet.GuacamoleHTTPTunnelServlet;
import org.glyptodon.guacamole.servlet.GuacamoleSession;
public class TunnelServlet
extends GuacamoleHTTPTunnelServlet {
/**
*
*/
private static final long serialVersionUID = 2010742981126080080L;
private static final String UDS_PATH = "/guacamole/";
private static final String UDSFILE = "udsfile";
private static final String UDS = "uds";
private static Properties config = null;
private String getConfigValue(String value) throws GuacamoleException {
if( config == null ) {
try {
config = new Properties();
config.load(getServletContext().getResourceAsStream("/WEB-INF/tunnel.properties"));
if( null != config.getProperty(UDSFILE)) {
BufferedReader bufferedReader = new BufferedReader(new FileReader(config.getProperty(UDSFILE)));
URL u = new URL(bufferedReader.readLine());
String uds = u.getProtocol() + "://" + u.getAuthority();
bufferedReader.close();
config.put(UDS, uds);
}
} catch( Exception e ) {
throw new GuacamoleException(e.getMessage(), e);
}
}
System.out.println("Getting value of " + value + ": " + config.getProperty(value));
return config.getProperty(value);
}
@Override
protected GuacamoleTunnel doConnect(HttpServletRequest request)
throws GuacamoleException {
String data = request.getParameter("data");
String width = request.getParameter("width");
String height = request.getParameter("height");
if( data == null || width == null || height == null)
throw new GuacamoleException("Can't read required parameters");
Hashtable<String,String> params = Util.readParameters( getConfigValue(UDS) + UDS_PATH + data);
if( params == null ) {
System.out.println("Invalid credentials");
throw new GuacamoleException("Can't access required user credentials");
}
System.out.println("Got parameters from remote server: " + data + ", " + width + "x" + height);
GuacamoleClientInformation info = new GuacamoleClientInformation();
info.setOptimalScreenWidth(Integer.parseInt(width));
info.setOptimalScreenHeight(Integer.parseInt(height));
System.out.println("Optiomal size: " + width + "x" + height);
// Add audio mimetypes
String[] audio_mimetypes = request.getParameterValues("audio");
if (audio_mimetypes != null)
info.getAudioMimetypes().addAll(Arrays.asList(audio_mimetypes));
// Add video mimetypes
String[] video_mimetypes = request.getParameterValues("video");
if (video_mimetypes != null)
info.getVideoMimetypes().addAll(Arrays.asList(video_mimetypes));
// Create our configuration
GuacamoleConfiguration config = new GuacamoleConfiguration();
config.setProtocol(params.get("protocol"));
System.out.println("Parsing parameters");
Enumeration<String> keys = params.keys();
while( keys.hasMoreElements() ) {
String key = keys.nextElement();
if( "protocol".equals(key) )
continue;
System.out.println("Parameter " + key + ": " + params.get(key));
config.setParameter(key, params.get(key));
}
System.out.println("Opening soket");
// Connect to guacd - everything is hard-coded here.
GuacamoleSocket socket = null;
try {
socket = new ConfiguredGuacamoleSocket(
new InetGuacamoleSocket("127.0.0.1", 4822),
config, info
);
} catch( Exception e ) {
System.out.print(e.getMessage());
System.out.print(e);
}
System.out.println("Initializing socket " + socket.toString());
// Establish the tunnel using the connected socket
GuacamoleTunnel tunnel = new SimpleGuacamoleTunnel(socket);
System.out.println("Initializing tunnel " + tunnel.toString());
// Attach tunnel to session
HttpSession httpSession = request.getSession(true);
GuacamoleSession session = new GuacamoleSession(httpSession);
session.attachTunnel(tunnel);
System.out.println("Returning tunnel");
// Return pre-attached tunnel
return tunnel;
}
}

View File

@@ -0,0 +1,56 @@
/*
* Copyright (c) 2015 Virtual Cable S.L.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.openuds.guacamole;
import com.google.inject.servlet.ServletModule;
import com.sun.jersey.guice.spi.container.servlet.GuiceContainer;
import org.apache.guacamole.tunnel.TunnelRequestService;
import org.openuds.guacamole.config.ConfigurationService;
import org.openuds.guacamole.connection.ConnectionService;
/**
* Guice module which binds classes required by the OpenUDS integration of
* Apache Guacamole.
*/
public class UDSModule extends ServletModule {
@Override
protected void configureServlets() {
// Serve servlets, etc. with Guice
bind(GuiceContainer.class);
// Bind UDS-specific services
bind(ConfigurationService.class);
bind(ConnectionService.class);
bind(TunnelRequestService.class);
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright (c) 2015 Virtual Cable S.L.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.openuds.guacamole;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.servlet.GuiceServletContextListener;
import org.apache.guacamole.tunnel.TunnelModule;
/**
* ServletContextListener implementation which initializes Guice and services
* specific to OpenUDS.
*/
public class UDSServletContextListener extends GuiceServletContextListener {
@Override
protected Injector getInjector() {
// Create an injector with OpenUDS- and Guacamole-specific services
// properly bound
return Guice.createInjector(
new UDSModule(),
new TunnelModule()
);
}
}

View File

@@ -1,185 +0,0 @@
package org.openuds.guacamole;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.security.cert.X509Certificate;
import java.util.Hashtable;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
public class Util {
//
public static Hashtable<String,String> readParameters(String url) {
//String url = unscramble(data);
//String params = getUrl(url);
//return parseParams(params);
//String params = Credentials.getAndRemove(data);
String params = getUrl(url);
if( params == null || params.equals("ERROR"))
return null;
return parseParams(params);
}
public static Hashtable<String,String> parseParams(String params)
{
Hashtable<String,String> res = new Hashtable<String, String>();
String[] parms = params.split("\n");
for( int i = 0; i < parms.length; i++) {
String[] val = parms[i].split("\t");
if( val.length == 1 )
res.put(val[0], "");
else
res.put(val[0], val[1]);
}
return res;
}
public static boolean download(String baseUrl, String id, String outputFileName)
{
return Util.download(baseUrl, id, outputFileName, true);
}
public static boolean download(String baseUrl, String id, String outputFileName, boolean ignoreCert)
{
// SSL Part got from sample at http://code.google.com/p/misc-utils/wiki/JavaHttpsUrl
try {
final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
@Override
public void checkClientTrusted( final X509Certificate[] chain, final String authType ) {
}
@Override
public void checkServerTrusted( final X509Certificate[] chain, final String authType ) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
} };
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance( "SSL" );
sslContext.init( null, trustAllCerts, new java.security.SecureRandom() );
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
java.net.URL u = new java.net.URL(baseUrl + id);
java.net.URLConnection uc = u.openConnection();
System.out.println(baseUrl);
System.out.println(uc);
// If ignoring server certificates, disable ssl certificate checking
if( ignoreCert && uc instanceof HttpsURLConnection) {
((HttpsURLConnection)uc).setSSLSocketFactory( sslSocketFactory );
}
String contentType = uc.getContentType();
int contentLength = uc.getContentLength();
if (contentType.startsWith("text/") || contentLength == -1) {
throw new IOException("This is not a binary file.");
}
InputStream raw = uc.getInputStream();
InputStream in = new BufferedInputStream(raw);
byte[] data = new byte[contentLength];
int bytesRead = 0;
int offset = 0;
while (offset < contentLength) {
bytesRead = in.read(data, offset, data.length - offset);
if (bytesRead == -1)
break;
offset += bytesRead;
}
in.close();
if (offset != contentLength) {
throw new IOException("Only read " + offset + " bytes; Expected " + contentLength + " bytes");
}
java.io.FileOutputStream out = new java.io.FileOutputStream(outputFileName);
out.write(data);
out.flush();
out.close();
} catch(Exception e) {
System.out.println("Unable to download file, already present or network error? " + e.getMessage());
return false;
}
return true;
}
public static String getUrl(String url) {
return Util.getUrl(url, true);
}
public static String getUrl(String url, boolean ignoreCert) {
try {
// SSL Part got from sample at http://code.google.com/p/misc-utils/wiki/JavaHttpsUrl
final TrustManager[] trustAllCerts = new TrustManager[] { new X509TrustManager() {
@Override
public void checkClientTrusted( final X509Certificate[] chain, final String authType ) {
}
@Override
public void checkServerTrusted( final X509Certificate[] chain, final String authType ) {
}
@Override
public X509Certificate[] getAcceptedIssuers() {
return null;
}
} };
// Install the all-trusting trust manager
final SSLContext sslContext = SSLContext.getInstance( "SSL" );
sslContext.init( null, trustAllCerts, new java.security.SecureRandom() );
// Create an ssl socket factory with our all-trusting manager
final SSLSocketFactory sslSocketFactory = sslContext.getSocketFactory();
java.net.URL u = new java.net.URL(url);
java.net.URLConnection uc = u.openConnection();
System.out.println(url);
System.out.println(uc instanceof HttpsURLConnection);
// If ignoring server certificates, disable ssl certificate checking and hostname checking
if( ignoreCert && uc instanceof HttpsURLConnection) {
((HttpsURLConnection)uc).setSSLSocketFactory( sslSocketFactory );
((HttpsURLConnection)uc).setHostnameVerifier(
new HostnameVerifier() {
@Override
public boolean verify(String arg0, SSLSession arg1) {
return true;
}
});
}
BufferedReader in = new BufferedReader(new InputStreamReader(uc.getInputStream()));
StringBuilder data = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null) {
data.append(inputLine);
data.append("\n");
}
in.close();
return data.toString();
} catch(Exception e) {
System.out.println("Unable to get url. Network error? " + e.getMessage());
return null;
}
}
}

View File

@@ -0,0 +1,246 @@
/*
* Copyright (c) 2015 Virtual Cable S.L.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.openuds.guacamole.config;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Properties;
import javax.servlet.ServletContext;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service that provides access to configuration information stored within
* OpenUDS' tunnel.properties file.
*/
@Singleton
public class ConfigurationService {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(ConfigurationService.class);
/**
* The name of the property within tunnel.properties which defines the file
* whose content dictates the base URL of the service providing connection
* configuration information.
*/
private static final String UDSFILE_PROPERTY = "udsfile";
/**
* The path beneath the OpenUDS service base URI (scheme + hostname) at
* which the connection configuration service can be found. Currently, this
* is hard-coded as "/guacamole/".
*/
private static final String UDS_CONNECTION_PATH = "/guacamole/";
/**
* The base URI (scheme + hostname) where OpenUDS is being served.
*/
private final URI udsBaseURI;
/**
* Parses the contents of the given file, reading the URI of the OpenUDS
* service contained therein. The file is expected to define this URI on
* the first line, and only the first line is read.
*
* @param udsFile
* The file from which the URI of the OpenUDS service should be read.
*
* @return
* The URI of the OpenUDS service.
*
* @throws GuacamoleException
* If the file could not be opened or read for any reason, or if the
* line read from the file is not a valid URI.
*/
private URI readServiceURI(String udsFile) throws GuacamoleException {
// Open UDS file
BufferedReader input;
try {
input = new BufferedReader(new FileReader(udsFile));
}
catch (IOException e) {
throw new GuacamoleServerException("Failed to open UDS file.", e);
}
// Parse the first line (and only the first line) assuming it contains
// the URL of the OpenUDS service
try {
return new URI(input.readLine());
}
// Rethrow general failure to read from the file
catch (IOException e) {
throw new GuacamoleServerException("Failed to read UDS service URI from file.", e);
}
// Rethrow failure to parse the URL
catch (URISyntaxException e) {
throw new GuacamoleServerException("Failed to parse UDS service URI from file.", e);
}
// Always close the file
finally {
try {
input.close();
}
catch (IOException e) {
logger.warn("Closure of OpenUDS file failed. Resource may leak.", e);
}
}
}
/**
* Given an arbitrary URI, returns a new URI which contains only the scheme
* and host. The path, fragment, etc. of the given URI, if any, are
* discarded.
*
* @param uri
* An arbitrary URI from which a base URI should be derived.
*
* @return
* A new URI containing only the scheme and host of the provided URI.
*
* @throws GuacamoleException
* If the new URI could not be generated because the result is not a
* valid URI.
*/
private URI getBaseURI(URI uri) throws GuacamoleException {
// Build base URI from only the scheme and host of the given URI
try {
return new URI(uri.getScheme(), null, uri.getHost(), uri.getPort(),
null, null, null);
}
catch (URISyntaxException e) {
throw new GuacamoleServerException("Failed to derive base URI.", e);
}
}
/**
* Creates a new ConfigurationService which provides access to the
* configuration information stored within the "/WEB-INF/tunnel.properties"
* file in the classpath. This file will be parsed immediately, but any
* resulting errors will simply be logged. If configuration information
* cannot be read, attempts to retrieve this information later through calls
* to the getters of this service will fail with appropriate exceptions.
*
* @param context
* The ServletContext associated with the servlet container which is
* serving this web application.
*/
@Inject
public ConfigurationService(ServletContext context) {
// Read tunnel.properties
Properties config = new Properties();
try {
config.load(context.getResourceAsStream("/WEB-INF/tunnel.properties"));
}
catch (IOException e) {
logger.error("Unable to read tunnel.properties.", e);
}
// Parse URI from the UDS file (if defined)
URI parsedURI = null;
String udsFile = config.getProperty(UDSFILE_PROPERTY);
if (udsFile != null) {
// Attempt to parse base URI from the UDS file, logging any failures
try {
parsedURI = getBaseURI(readServiceURI(udsFile));
}
catch (GuacamoleException e) {
logger.error("OpenUDS service URI could not be parsed. This "
+ "web application WILL NOT FUNCTION.", e);
}
}
// If no UDS file is defined, web application startup has failed
else
logger.error("Property \"{}\" not found within tunnel.properties. "
+ "This web application WILL NOT FUNCTION.", UDSFILE_PROPERTY);
// Assign the parsed URI, which may be null
udsBaseURI = parsedURI;
}
/**
* Returns the base URI of the OpenUDS service. All services providing data
* to this Guacamole integration are hosted beneath this base URI.
*
* @return
* The base URI of the OpenUDS service.
*
* @throws GuacamoleException
* If the base URI of the OpenUDS service is not defined because the
* tunnel.properties file could not be parsed when the web application
* started.
*/
public URI getUDSBaseURI() throws GuacamoleException {
// Explicitly fail if the configuration was not successfully read
if (udsBaseURI == null)
throw new GuacamoleServerException("The UDS base URI is not defined.");
return udsBaseURI;
}
/**
* Returns the path beneath the OpenUDS base URI at which the connection
* configuration service can be found. This service is expected to respond
* to HTTP GET requests, returning the configuration of requested
* connections.
*
* @return
* The path beneath the OpenUDS base URI at which the connection
* configuration service can be found.
*/
public String getUDSConnectionPath() {
return UDS_CONNECTION_PATH;
}
}

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2015 Virtual Cable S.L.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Classes used to retrieve OpenUDS-specific configuration information.
*/
package org.openuds.guacamole.config;

View File

@@ -0,0 +1,200 @@
/*
* Copyright (c) 2015 Virtual Cable S.L.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package org.openuds.guacamole.connection;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URI;
import java.net.URLConnection;
import java.util.HashMap;
import java.util.Map;
import javax.ws.rs.core.UriBuilder;
import org.apache.guacamole.GuacamoleException;
import org.apache.guacamole.GuacamoleServerException;
import org.apache.guacamole.protocol.GuacamoleConfiguration;
import org.openuds.guacamole.config.ConfigurationService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Service which communicates with the remote OpenUDS connection service,
* providing access to the underlying connection configuration.
*/
@Singleton
public class ConnectionService {
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(ConnectionService.class);
/**
* The name of the parameter returned by the OpenUDS connection
* configuration service which will contain the protocol that Guacamole
* should use to initiate the remote desktop connection.
*/
private static final String PROTOCOL_PARAMETER = "protocol";
/**
* Service for retrieving configuration information.
*/
@Inject
private ConfigurationService configService;
/**
* Makes an HTTP GET request to the OpenUDS service running at the given
* URI, parsing the response into connection configuration data. The
* response MUST be simple text, one line per connection parameter, with the
* name of the connection parameter separated from the corresponding value
* by a tab character. If the OpenUDS service encounters an error, it is
* expected to return the single word "ERROR" on one line. Lines which do
* not match these expectations will be skipped.
*
* @param uri
* The URI of the OpenUDS service to which the HTTP GET request should
* be made.
*
* @return
* A map of all parameter name/value pairs returned by the OpenUDS
* service.
*
* @throws GuacamoleException
* If the OpenUDS service returns an error, or the response from the
* service cannot be read.
*/
private Map<String, String> readConnectionConfiguration(URI uri)
throws GuacamoleException {
BufferedReader response;
// Connect to OpenUDS
try {
URLConnection connection = uri.toURL().openConnection();
response = new BufferedReader(new InputStreamReader(connection.getInputStream()));
}
catch (IOException e) {
throw new GuacamoleServerException("Unable to open connection to OpenUDS service.", e);
}
Map<String, String> parameters = new HashMap<String, String>();
// Read and parse each line of the response
try {
String inputLine;
while ((inputLine = response.readLine()) != null) {
// Abort upon error
if (inputLine.equals("ERROR"))
throw new GuacamoleServerException("OpenUDS service returned an error.");
// Determine separation between each line's key and value
int tab = inputLine.indexOf('\t');
if (tab == -1)
continue;
// Add key/value pair from either side of the tab
parameters.put(
inputLine.substring(0, tab),
inputLine.substring(tab + 1)
);
}
}
// Rethrow any error which occurs during reading
catch (IOException e) {
throw new GuacamoleServerException("Failed to read response from OpenUDS service.", e);
}
// Always close the stream
finally {
try {
response.close();
}
catch (IOException e) {
logger.warn("Closure of connection to OpenUDS failed. Resource may leak.", e);
}
}
// Parameters have been successfully parsed
return parameters;
}
/**
* Queries OpenUDS for the connection configuration for the connection
* associated with the given data. This data is an opaque value provided
* via the "data" parameter to the Guacamole tunnel.
*
* @param data
* The OpenUDS-specific data which defines the connection whose
* configuration should be retrieved.
*
* @return
* The configuration of the connection associated with the provided
* OpenUDS-specific data.
*
* @throws GuacamoleException
* If the connection configuration could not be retrieved from OpenUDS,
* of the response from OpenUDS was missing required information.
*/
public GuacamoleConfiguration getConnectionConfiguration(String data)
throws GuacamoleException {
// Build URI of remote service from the base URI and given data
URI serviceURI = UriBuilder.fromUri(configService.getUDSBaseURI())
.path(configService.getUDSConnectionPath())
.path(data)
.build();
// Pull connection configuration from remote service
Map<String, String> params = readConnectionConfiguration(serviceURI);
// Pull the protocol from the parameters
String protocol = params.remove(PROTOCOL_PARAMETER);
if (protocol == null)
throw new GuacamoleServerException("Protocol missing from OpenUDS response.");
// Create our configuration
GuacamoleConfiguration config = new GuacamoleConfiguration();
config.setProtocol(protocol);
config.setParameters(params);
return config;
}
}

View File

@@ -0,0 +1,33 @@
/*
* Copyright (c) 2015 Virtual Cable S.L.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Classes used to communicate with OpenUDS' connection configuration web
* service.
*/
package org.openuds.guacamole.connection;

View File

@@ -0,0 +1,32 @@
/*
* Copyright (c) 2015 Virtual Cable S.L.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* * Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* * Neither the name of Virtual Cable S.L. nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Classes associated with the OpenUDS integration of Apache Guacamole.
*/
package org.openuds.guacamole;

View File

@@ -1,5 +1,31 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
Copyright (c) 2015 Virtual Cable S.L.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of Virtual Cable S.L. nor the names of its contributors
may be used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
@@ -11,31 +37,19 @@
<welcome-file>index.xhtml</welcome-file>
</welcome-file-list>
<!-- Guacamole Tunnel Servlet -->
<servlet>
<description>Tunnel Servlet</description>
<servlet-name>Tunnel</servlet-name>
<servlet-class>
org.openuds.guacamole.TunnelServlet
</servlet-class>
</servlet>
<!-- Initialization ServletContextListener -->
<listener>
<listener-class>org.openuds.guacamole.UDSServletContextListener</listener-class>
</listener>
<servlet>
<description>Credentials Servlet</description>
<servlet-name>Credentials</servlet-name>
<servlet-class>
org.openuds.guacamole.CredentialsServlet
</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Tunnel</servlet-name>
<url-pattern>/tunnel</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Credentials</servlet-name>
<url-pattern>/creds</url-pattern>
</servlet-mapping>
<!-- Guice -->
<filter>
<filter-name>guiceFilter</filter-name>
<filter-class>com.google.inject.servlet.GuiceFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>guiceFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

View File

@@ -1,5 +1,8 @@
This portion of UDS (HTML5 tunnel) is based on guacamole. As such, the code used for creating this is fully GPL compliant.
This portion of UDS (HTML5 tunnel) is based on Apache Guacamole. The source
code that produced this war file can be obtained from:
To obtain the code that produced this war file, you can do it from http://openuds.org.
http://openuds.org/
Guacamole source code is accesible from http://guac-dev.org/
The Apache Guacamole source code is available from:
http://guacamole.incubator.apache.org/

View File

@@ -964,14 +964,14 @@ GuacUI.Client.connect = function() {
var tunnel;
// If WebSocket available, try to use it.
/*if (window.WebSocket)
if (window.WebSocket)
tunnel = new Guacamole.ChainedTunnel(
new Guacamole.WebSocketTunnel("websocket-tunnel"),
new Guacamole.HTTPTunnel("tunnel")
)
);
// If no WebSocket, then use HTTP.
else*/
else
tunnel = new Guacamole.HTTPTunnel("tunnel");
// Instantiate client
@@ -1005,18 +1005,18 @@ GuacUI.Client.connect = function() {
var connect_string =
queryArr.join('&')
+ "&width=" + Math.floor(optimal_width)
+ "&height=" + Math.floor(optimal_height)
+ "&dpi=" + Math.floor(optimal_dpi);
+ "&GUAC_WIDTH=" + Math.floor(optimal_width)
+ "&GUAC_HEIGHT=" + Math.floor(optimal_height)
+ "&GUAC_DPI=" + Math.floor(optimal_dpi);
// Add audio mimetypes to connect_string
GuacUI.Audio.supported.forEach(function(mimetype) {
connect_string += "&audio=" + encodeURIComponent(mimetype);
connect_string += "&GUAC_AUDIO=" + encodeURIComponent(mimetype);
});
// Add video mimetypes to connect_string
GuacUI.Video.supported.forEach(function(mimetype) {
connect_string += "&video=" + encodeURIComponent(mimetype);
connect_string += "&GUAC_VIDEO=" + encodeURIComponent(mimetype);
});
// Show connection errors from tunnel

View File

@@ -9,6 +9,6 @@ ovirt-engine-sdk-python
pycurl
pyOpenSSL
python-ldap
six
MySQL-python
reportlab
reportlab
bitarray

View File

@@ -122,8 +122,12 @@ CACHES = {
'CULL_FREQUENCY': 3, # 0 = Entire cache will be erased once MAX_ENTRIES is reached, this is faster on DB. if other value, will remove 1/this number items fromm cache
}
},
# 'memory': {
# 'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
# }
'memory': {
'BACKEND': 'django.core.cache.backends.locmem.LocMemCache',
'BACKEND': 'django.core.cache.backends.memcached.MemcachedCache',
'LOCATION': '127.0.0.1:11211',
}
}
@@ -145,18 +149,17 @@ TEMPLATE_LOADERS = (
)
# Own context processors plus django's own
TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + (
TEMPLATE_CONTEXT_PROCESSORS = DEFAULT_SETTINGS.TEMPLATE_CONTEXT_PROCESSORS + [
'uds.core.util.Config.context_processor',
'uds.core.util.html.context',
'django.core.context_processors.request',
)
]
MIDDLEWARE_CLASSES = (
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'uds.core.util.request.GlobalRequestMiddleware',
'uds.core.util.middleware.XUACompatibleMiddleware',
@@ -203,6 +206,12 @@ COMPRESS_PRECOMPILERS = (
)
if DEBUG:
COMPRESS_DEBUG_TOGGLE = 'debug'
#
# Enable this if you need to allow round robin load balancing of web server
# This is so because we need to share the files between servers
# Another options is put /var/server/static on a shared nfs forder for all servers
#
# COMPRESS_STORAGE = 'uds.core.util.FileStorage.CompressorFileStorage'
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
@@ -245,7 +254,7 @@ LOGGING = {
'handlers': {
'null': {
'level':'DEBUG',
'class':'django.utils.log.NullHandler',
'class':'logging.NullHandler',
},
'file':{

View File

@@ -111,7 +111,7 @@ class Actor(Handler):
logger.debug('Getting User services from ids: {}'.format(self._params.get('id')))
try:
clientIds = self._params.get('id').split(',')[:5]
clientIds = [i.upper() for i in self._params.get('id').split(',')[:5]]
except Exception:
raise RequestError('Invalid request: (no id found)')
@@ -124,7 +124,7 @@ class Actor(Handler):
def getTicket(self):
'''
Processes get requests in order to obtain a ticket content
GET /rest/ticket/[ticketId]
GET /rest/actor/ticket/[ticketId]
'''
logger.debug("Ticket args for GET: {0}".format(self._args))

View File

@@ -52,7 +52,7 @@ class Authenticators(ModelHandler):
# Custom get method "search" that requires authenticator id
custom_methods = [('search', True)]
detail = {'users': Users, 'groups': Groups}
save_fields = ['name', 'comments', 'priority', 'small_name']
save_fields = ['name', 'comments', 'tags', 'priority', 'small_name']
table_title = _('Current authenticators')
table_fields = [
@@ -60,7 +60,8 @@ class Authenticators(ModelHandler):
{'comments': {'title': _('Comments')}},
{'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '5em'}},
{'small_name': {'title': _('Tag')}},
{'users_count': {'title': _('Users'), 'type': 'numeric', 'width': '5em'}}
{'users_count': {'title': _('Users'), 'type': 'numeric', 'width': '5em'}},
{'tags': {'title': _('tags'), 'visible': False}},
]
def enum_types(self):
@@ -80,7 +81,7 @@ class Authenticators(ModelHandler):
def getGui(self, type_):
try:
return self.addDefaultFields(auths.factory().lookup(type_).guiDescription(), ['name', 'comments', 'priority', 'small_name'])
return self.addDefaultFields(auths.factory().lookup(type_).guiDescription(), ['name', 'comments', 'tags', 'priority', 'small_name'])
except:
raise NotFound('type not found')
@@ -89,6 +90,7 @@ class Authenticators(ModelHandler):
return {
'id': auth.uuid,
'name': auth.name,
'tags': [tag.tag for tag in auth.tags.all()],
'comments': auth.comments,
'priority': auth.priority,
'small_name': auth.small_name,

View File

@@ -0,0 +1,152 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from uds.models.CalendarRule import freqs, CalendarRule
from uds.models.Util import getSqlDatetime
from uds.core.util import log
from uds.core.util import permissions
from uds.core.util.model import processUuid
from uds.core.Environment import Environment
from uds.REST.model import DetailHandler
from uds.REST import NotFound, ResponseError, RequestError
from django.db import IntegrityError
import six
import logging
import datetime
logger = logging.getLogger(__name__)
class CalendarRules(DetailHandler): # pylint: disable=too-many-public-methods
'''
Detail handler for Services, whose parent is a Provider
'''
@staticmethod
def ruleToDict(item, perm):
'''
Convert a calRule db item to a dict for a rest response
:param item: Service item (db)
:param full: If full is requested, add "extra" fields to complete information
'''
retVal = {
'id': item.uuid,
'name': item.name,
'comments': item.comments,
'start': item.start,
'end': item.end,
'frequency': item.frequency,
'interval': item.interval,
'duration': item.duration,
'duration_unit': item.duration_unit,
'permission': perm
}
return retVal
def getItems(self, parent, item):
# Check what kind of access do we have to parent provider
perm = permissions.getEffectivePermission(self._user, parent)
try:
if item is None:
return [CalendarRules.ruleToDict(k, perm) for k in parent.rules.all()]
else:
k = parent.rules.get(uuid=processUuid(item))
return CalendarRules.ruleToDict(k, perm)
except Exception:
logger.exception('itemId {}'.format(item))
self.invalidItemException()
def getFields(self, parent):
return [
{'name': {'title': _('Rule name')}},
{'start': {'title': _('Starts'), 'type': 'datetime'}},
{'end': {'title': _('Ends'), 'type': 'date'}},
{'frequency': {'title': _('Repeats'), 'type': 'dict', 'dict': dict((v[0], six.text_type(v[1])) for v in freqs) }},
{'interval': {'title': _('Every'), 'type': 'callback'}},
{'duration': {'title': _('Duration'), 'type': 'callback'}},
{'comments': {'title': _('Comments')}},
]
def saveItem(self, parent, item):
# Extract item db fields
# We need this fields for all
logger.debug('Saving rule {0} / {1}'.format(parent, item))
fields = self.readFieldsFromParams(['name', 'comments', 'frequency', 'start', 'end', 'interval', 'duration', 'duration_unit'])
# Convert timestamps to datetimes
fields['start'] = datetime.datetime.fromtimestamp(fields['start'])
if fields['end'] != None:
fields['end'] = datetime.datetime.fromtimestamp(fields['end'])
calRule = None
try:
if item is None: # Create new
calRule = parent.rules.create(**fields)
else:
calRule = parent.rules.get(uuid=processUuid(item))
calRule.__dict__.update(fields)
calRule.save()
except CalendarRule.DoesNotExist:
self.invalidItemException()
except IntegrityError: # Duplicate key probably
raise RequestError(_('Element already exists (duplicate key error)'))
except Exception as e:
logger.exception('Saving calendar')
raise RequestError('incorrect invocation to PUT: {0}'.format(e))
return self.getItems(parent, calRule.uuid)
def deleteItem(self, parent, item):
logger.debug('Deleting rule {} from {}'.format(item, parent))
try:
calRule = parent.rules.get(uuid=processUuid(item))
calRule.calendar.modified = getSqlDatetime()
calRule.calendar.save()
calRule.delete()
except Exception:
logger.exception('Exception')
self.invalidItemException()
return 'deleted'
def getTitle(self, parent):
try:
return _('Rules of {0}').format(parent.name)
except Exception:
return _('Current rules')

View File

@@ -0,0 +1,77 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@itemor: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _, ugettext
from uds.models import Calendar
from uds.core.util import permissions
from uds.REST.model import ModelHandler
from .calendarrules import CalendarRules
import logging
logger = logging.getLogger(__name__)
# Enclosed methods under /item path
class Calendars(ModelHandler):
'''
Processes REST requests about calendars
'''
model = Calendar
detail = {'rules': CalendarRules}
save_fields = ['name', 'comments', 'tags']
table_title = _('Calendars')
table_fields = [
{'name': {'title': _('Name'), 'visible': True, 'type': 'icon', 'icon': 'fa fa-calendar text-success'}},
{'comments': {'title': _('Comments')}},
{'modified': {'title': _('Modified'), 'type': 'datetime'}},
{'tags': {'title': _('tags'), 'visible': False}},
]
def item_as_dict(self, calendar):
return {
'id': calendar.uuid,
'name': calendar.name,
'tags': [tag.tag for tag in calendar.tags.all()],
'comments': calendar.comments,
'modified': calendar.modified,
'permission': permissions.getEffectivePermission(self._user, calendar)
}
def getGui(self, type_):
return self.addDefaultFields([], ['name', 'comments', 'tags'])

View File

@@ -51,8 +51,8 @@ import logging
logger = logging.getLogger(__name__)
CLIENT_VERSION = '1.9.1'
REQUIRED_CLIENT_VERSION = '1.9.0'
CLIENT_VERSION = UDS_VERSION
REQUIRED_CLIENT_VERSION = '2.0.0'
# Enclosed methods under /actor path

View File

@@ -60,7 +60,8 @@ class Config(Handler):
'value': cfg.get(),
'crypt': cfg.isCrypted(),
'longText': cfg.isLongText(),
'type': cfg.getType()
'type': cfg.getType(),
'params': cfg.getParams()
}
logger.debug('Configuration: {0}'.format(res))
return res

View File

@@ -49,7 +49,7 @@ class Images(ModelHandler):
'''
Handles the gallery REST interface
'''
needs_admin = True
# needs_admin = True
path = 'gallery'
model = Image
@@ -57,9 +57,9 @@ class Images(ModelHandler):
table_title = _('Image Gallery')
table_fields = [
{'thumb': {'title': _('Image'), 'visible': True, 'type': 'image', 'width': '96px' }},
{'name': {'title': _('Name')}},
{'size': {'title': _('Size')}},
{'thumb': {'title': _('Image'), 'visible': True, 'type': 'image'}},
]
def beforeSave(self, fields):
@@ -78,7 +78,7 @@ class Images(ModelHandler):
'value': '',
'label': ugettext('Image'),
'tooltip': ugettext('Image object'),
'type': gui.InputField.IMAGE_TYPE,
'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 100, # At end
}
)

View File

@@ -53,13 +53,14 @@ class Networks(ModelHandler):
Implements specific handling for network related requests using GUI
'''
model = Network
save_fields = ['name', 'net_string']
save_fields = ['name', 'net_string', 'tags']
table_title = _('Current Networks')
table_fields = [
{'name': {'title': _('Name'), 'visible': True, 'type': 'icon', 'icon': 'fa fa-globe text-success'}},
{'net_string': {'title': _('Range')}},
{'networks_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}}
{'networks_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}},
{'tags': {'title': _('tags'), 'visible': False}},
]
def beforeSave(self, fields):
@@ -74,7 +75,7 @@ class Networks(ModelHandler):
def getGui(self, type_):
return self.addField(
self.addDefaultFields([], ['name']), {
self.addDefaultFields([], ['name', 'tags']), {
'name': 'net_string',
'value': '',
'label': ugettext('Network range'),
@@ -88,6 +89,7 @@ class Networks(ModelHandler):
return {
'id': item.uuid,
'name': item.name,
'tags': [tag.tag for tag in item.tags.all()],
'net_string': item.net_string,
'networks_count': item.transports.count(),
'permission': permissions.getEffectivePermission(self._user, item)

View File

@@ -50,13 +50,14 @@ logger = logging.getLogger(__name__)
class OsManagers(ModelHandler):
model = OSManager
save_fields = ['name', 'comments']
save_fields = ['name', 'comments', 'tags']
table_title = _('Current OS Managers')
table_fields = [
{'name': {'title': _('Name'), 'visible': True, 'type': 'iconType'}},
{'comments': {'title': _('Comments')}},
{'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}}
{'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}},
{'tags': {'title': _('tags'), 'visible': False}},
]
def osmToDict(self, osm):
@@ -64,6 +65,7 @@ class OsManagers(ModelHandler):
return {
'id': osm.uuid,
'name': osm.name,
'tags': [tag.tag for tag in osm.tags.all()],
'deployed_count': osm.deployedServices.count(),
'type': type_.type(),
'servicesTypes': type_.servicesType,
@@ -88,6 +90,6 @@ class OsManagers(ModelHandler):
# Gui related
def getGui(self, type_):
try:
return self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments'])
return self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments', 'tags'])
except:
raise NotFound('type not found')

View File

@@ -38,7 +38,7 @@ from uds.REST import Handler
from uds.REST import RequestError
from uds.core.util import permissions
from uds.models import Provider, Service, Authenticator, OSManager, Transport, Network, ServicePool
from uds.models import Provider, Service, Authenticator, OSManager, Transport, Network, ServicePool, Calendar
from uds.models import User, Group
import six
@@ -64,7 +64,8 @@ class Permissions(Handler):
'osmanagers': OSManager,
'transports': Transport,
'networks': Network,
'servicespools': ServicePool
'servicespools': ServicePool,
'calendars': Calendar
}.get(arg, None)
if cls is None:

View File

@@ -34,11 +34,12 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext, ugettext_lazy as _
from uds.models import Provider, Service, UserService
from uds.REST.methods.services import Services as DetailServices
from uds.core import services
from uds.core.util import permissions
from uds.core.util.model import processUuid
from .services import Services as DetailServices
from uds.REST import NotFound, RequestError
from uds.REST.model import ModelHandler
@@ -55,7 +56,7 @@ class Providers(ModelHandler):
detail = {'services': DetailServices}
custom_methods = [('allservices', False), ('service', False), ('maintenance', True)]
save_fields = ['name', 'comments']
save_fields = ['name', 'comments', 'tags']
table_title = _('Service providers')
@@ -64,8 +65,9 @@ class Providers(ModelHandler):
{'name': {'title': _('Name'), 'type': 'iconType'}},
{'comments': {'title': _('Comments')}},
{'maintenance_state': {'title': _('Status')}},
{'services_count': {'title': _('Services'), 'type': 'numeric', 'width': '5em'}},
{'user_services_count': {'title': _('User Services'), 'type': 'numeric', 'width': '8em'}},
{'services_count': {'title': _('Services'), 'type': 'numeric'}},
{'user_services_count': {'title': _('User Services'), 'type': 'numeric'}}, # , 'width': '132px'
{'tags': {'title': _('tags'), 'visible': False}},
]
# Field from where to get "class" and prefix for that class, so this will generate "row-state-A, row-state-X, ....
table_row_style = {'field': 'maintenance_mode', 'prefix': 'row-maintenance-'}
@@ -83,6 +85,7 @@ class Providers(ModelHandler):
return {
'id': provider.uuid,
'name': provider.name,
'tags': [tag.vtag for tag in provider.tags.all()],
'services_count': provider.services.count(),
'user_services_count': UserService.objects.filter(deployed_service__service__provider=provider).count(),
'maintenance_mode': provider.maintenance_mode,
@@ -103,7 +106,7 @@ class Providers(ModelHandler):
# Gui related
def getGui(self, type_):
try:
return self.addDefaultFields(services.factory().lookup(type_).guiDescription(), ['name', 'comments'])
return self.addDefaultFields(services.factory().lookup(type_).guiDescription(), ['name', 'comments', 'tags'])
except Exception:
raise NotFound('type not found')

View File

@@ -35,7 +35,7 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from uds.models import Service, UserService
from uds.models import Service, UserService, Tag
from uds.core.services import Service as coreService
from uds.core.util import log
@@ -45,6 +45,7 @@ from uds.core.Environment import Environment
from uds.REST.model import DetailHandler
from uds.REST import NotFound, ResponseError, RequestError
from django.db import IntegrityError
from uds.core.ui.images import DEFAULT_THUMB_BASE64
import logging
@@ -56,6 +57,8 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
Detail handler for Services, whose parent is a Provider
'''
custom_methods = ['servicesPools']
@staticmethod
def serviceInfo(item):
info = item.getType()
@@ -84,6 +87,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
retVal = {
'id': item.uuid,
'name': item.name,
'tags': [tag.tag for tag in item.tags.all()],
'comments': item.comments,
'type': item.data_type,
'type_name': _(itemType.name()),
@@ -129,7 +133,9 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
# Extract item db fields
# We need this fields for all
logger.debug('Saving service {0} / {1}'.format(parent, item))
fields = self.readFieldsFromParams(['name', 'comments', 'data_type'])
fields = self.readFieldsFromParams(['name', 'comments', 'data_type', 'tags'])
tags = fields['tags']
del fields['tags']
service = None
try:
if item is None: # Create new
@@ -138,14 +144,16 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
service = parent.services.get(uuid=processUuid(item))
service.__dict__.update(fields)
service.data = service.getInstance(self._params).serialize()
service.tags = [Tag.objects.get_or_create(tag=val)[0] for val in tags]
service.data = service.getInstance(self._params).serialize() # This may launch an validation exception (the getInstance(...) part)
service.save()
except Service.DoesNotExist:
self.invalidItemException()
except IntegrityError: # Duplicate key probably
raise RequestError(_('Element already exists (duplicate key error)'))
except coreService.ValidationException as e:
if item is None:
if item is None: # Only remove partially saved element if creating new (if editing, ignore this)
self._deleteIncompleteService(service)
raise RequestError(_('Input error: {0}'.format(unicode(e))))
except Exception as e:
@@ -159,14 +167,15 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
try:
service = parent.services.get(uuid=processUuid(item))
if service.deployedServices.count() != 0:
raise RequestError('Item has associated deployed services')
if service.deployedServices.count() == 0:
service.delete()
return 'deleted'
service.delete()
except Exception:
logger.exception('Deleting service')
self.invalidItemException()
return 'deleted'
raise RequestError('Item has associated deployed services')
def getTitle(self, parent):
try:
@@ -179,8 +188,9 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
{'name': {'title': _('Service name'), 'visible': True, 'type': 'iconType'}},
{'comments': {'title': _('Comments')}},
{'type_name': {'title': _('Type')}},
{'deployed_services_count': {'title': _('Deployed services'), 'type': 'numeric', 'width': '7em'}},
{'user_services_count': {'title': _('User services'), 'type': 'numeric', 'width': '7em'}},
{'deployed_services_count': {'title': _('Deployed services'), 'type': 'numeric'}},
{'user_services_count': {'title': _('User services'), 'type': 'numeric'}},
{'tags': {'title': _('tags'), 'visible': False}},
]
def getTypes(self, parent, forType):
@@ -208,7 +218,7 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
parentInstance = parent.getInstance()
serviceType = parentInstance.getServiceByType(forType)
service = serviceType(Environment.getTempEnv(), parentInstance) # Instantiate it so it has the opportunity to alter gui description based on parent
return self.addDefaultFields(service.guiDescription(service), ['name', 'comments'])
return self.addDefaultFields(service.guiDescription(service), ['name', 'comments', 'tags'])
except Exception as e:
logger.exception('getGui')
raise ResponseError(unicode(e))
@@ -220,3 +230,20 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
return log.getLogs(item)
except Exception:
self.invalidItemException()
def servicesPools(self, parent, item):
self.ensureAccess(item, permissions.PERMISSION_READ)
logger.debug('Got parameters for servicepools: {}, {}'.format(parent, item))
uuid = processUuid(item)
service = parent.services.get(uuid=uuid)
res = []
for i in service.deployedServices.all():
res.append({
'id': i.uuid,
'name': i.name,
'thumb': i.image.thumb64 if i.image is not None else DEFAULT_THUMB_BASE64,
'user_services_count': i.userServices.count(),
'state': _('With errors') if i.isRestrained() else _('Ok'),
})
return res

View File

@@ -0,0 +1,201 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@author: Adolfo Gómez, dkmaster at dkmon dot com
'''
# pylint: disable=too-many-public-methods
from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from uds.models import CalendarAccess, CalendarAction, Calendar
from uds.models.CalendarAction import CALENDAR_ACTION_DICT
from uds.core.util.State import State
from uds.core.util.model import processUuid
from uds.core.util import log
from uds.REST.model import DetailHandler
from uds.REST import ResponseError
from uds.core.util import permissions
import json
import logging
logger = logging.getLogger(__name__)
ALLOW = 'ALLOW'
DENY = 'DENY'
class AccessCalendars(DetailHandler):
'''
Processes the transports detail requests of a Service Pool
'''
@staticmethod
def as_dict(item):
return {
'id': item.uuid,
'calendarId': item.calendar.uuid,
'calendar': item.calendar.name,
'access': item.access,
'priority': item.priority,
}
def getItems(self, parent, item):
try:
if item is None:
return [AccessCalendars.as_dict(i) for i in parent.calendaraccess_set.all()]
else:
i = CalendarAccess.objects.get(uuid=processUuid(item))
return AccessCalendars.as_dict(i)
except Exception:
self.invalidItemException()
def getTitle(self, parent):
return _('Access restrictions by calendar')
def getFields(self, parent):
return [
{'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}},
{'calendar': {'title': _('Calendar')}},
{'access': {'title': _('Access')}},
]
def saveItem(self, parent, item):
# If already exists
uuid = processUuid(item) if item is not None else None
calendar = Calendar.objects.get(uuid=processUuid(self._params['calendarId']))
access = self._params['access'].upper()
priority = int(self._params['priority'])
if uuid is not None:
calAccess = CalendarAccess.objects.get(uuid=uuid)
calAccess.calendar = calendar
calAccess.service_pool = parent
calAccess.access = access
calAccess.priority = priority
calAccess.save()
else:
CalendarAccess.objects.create(calendar=calendar, service_pool=parent, access=access, priority=priority)
return self.success()
def deleteItem(self, parent, item):
CalendarAccess.objects.get(uuid=processUuid(self._args[0])).delete()
class ActionsCalendars(DetailHandler):
'''
Processes the transports detail requests of a Service Pool
'''
custom_methods = ('execute')
@staticmethod
def as_dict(item):
return {
'id': item.uuid,
'calendarId': item.calendar.uuid,
'calendar': item.calendar.name,
'action': item.action,
'actionDescription': CALENDAR_ACTION_DICT[item.action]['description'],
'atStart': item.at_start,
'eventsOffset': item.events_offset,
'params': json.loads(item.params),
'nextExecution': item.next_execution,
'lastExecution': item.last_execution
}
def getItems(self, parent, item):
try:
if item is None:
return [ActionsCalendars.as_dict(i) for i in parent.calendaraction_set.all()]
else:
i = CalendarAction.objects.get(uuid=processUuid(item))
return ActionsCalendars.as_dict(i)
except Exception:
self.invalidItemException()
def getTitle(self, parent):
return _('Scheduled actions')
def getFields(self, parent):
return [
{'calendar': {'title': _('Calendar')}},
{'actionDescription': {'title': _('Action')}},
{'params': {'title': _('Parameters')}},
{'atStart': {'title': _('Relative to')}},
{'eventsOffset': {'title': _('Time offset')}},
{'nextExecution': {'title': _('Next execution'), 'type': 'datetime'}},
{'lastExecution': {'title': _('Last execution'), 'type': 'datetime'}},
]
def saveItem(self, parent, item):
# If already exists
uuid = processUuid(item) if item is not None else None
calendar = Calendar.objects.get(uuid=processUuid(self._params['calendarId']))
action = self._params['action'].upper()
eventsOffset = int(self._params['eventsOffset'])
atStart = self._params['atStart'] not in ('false', False, '0', 0)
params = json.dumps(self._params['params'])
logger.debug('Got parameters: {} {} {} {} ----> {}'.format(calendar, action, eventsOffset, atStart, params))
if uuid is not None:
calAction = CalendarAction.objects.get(uuid=uuid)
calAction.calendar = calendar
calAction.service_pool = parent
calAction.action = action
calAction.at_start = atStart
calAction.events_offset = eventsOffset
calAction.params = params
calAction.save()
else:
CalendarAction.objects.create(calendar=calendar, service_pool=parent, action=action, at_start=atStart, events_offset=eventsOffset, params=params)
return self.success()
def deleteItem(self, parent, item):
CalendarAction.objects.get(uuid=processUuid(self._args[0])).delete()
def execute(self, parent, item):
self.ensureAccess(item, permissions.PERMISSION_MANAGEMENT)
logger.debug('Launching action')
uuid = processUuid(item)
calAction = CalendarAction.objects.get(uuid=uuid)
calAction.execute()
return self.success()

View File

@@ -0,0 +1,111 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2014 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
@itemor: Adolfo Gómez, dkmaster at dkmon dot com
'''
from __future__ import unicode_literals
from django.utils.translation import ugettext_lazy as _, ugettext
from uds.models import ServicesPoolGroup, Image
from uds.core.util.model import processUuid
from uds.core.ui.UserInterface import gui
from uds.core.ui.images import DEFAULT_THUMB_BASE64
from uds.REST.model import ModelHandler
import logging
logger = logging.getLogger(__name__)
# Enclosed methods under /item path
class ServicesPoolGroups(ModelHandler):
'''
Handles the gallery REST interface
'''
# needs_admin = True
path = 'gallery'
model = ServicesPoolGroup
save_fields = ['name', 'comments', 'image_id', 'priority']
table_title = _('Services Pool Groups')
table_fields = [
{'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}},
{'thumb': {'title': _('Image'), 'visible': True, 'type': 'image', 'width': '96px' }},
{'name': {'title': _('Name')}},
{'comments': {'title': _('Comments')}},
]
def beforeSave(self, fields):
imgId = fields['image_id']
fields['image_id'] = None
logger.debug('Image id: {}'.format(imgId))
try:
if imgId != '-1':
image = Image.objects.get(uuid=processUuid(imgId))
fields['image_id'] = image.id
except Exception:
logger.exception('At image recovering')
# Gui related
def getGui(self, type_):
g = self.addDefaultFields([], ['name', 'comments', 'priority'])
for f in [{
'name': 'image_id',
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]),
'label': ugettext('Associated Image'),
'tooltip': ugettext('Image assocciated with this service'),
'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 102,
}]:
self.addField(g, f)
return g
def item_as_dict(self, item):
return {
'id': item.uuid,
'priority': item.priority,
'name': item.name,
'comments': item.comments,
'image_id': item.image.uuid if item.image is not None else None,
}
def item_as_dict_overview(self, item):
return {
'id': item.uuid,
'priority': item.priority,
'name': item.name,
'comments': item.comments,
'thumb': item.thumb64,
}

View File

@@ -33,7 +33,8 @@
from __future__ import unicode_literals
from django.utils.translation import ugettext, ugettext_lazy as _
from uds.models import DeployedService, OSManager, Service, Image
from uds.models import DeployedService, OSManager, Service, Image, ServicesPoolGroup
from uds.models.CalendarAction import CALENDAR_ACTION_INITIAL, CALENDAR_ACTION_MAX, CALENDAR_ACTION_CACHE_L1, CALENDAR_ACTION_CACHE_L2, CALENDAR_ACTION_PUBLISH
from uds.core.ui.images import DEFAULT_THUMB_BASE64
from uds.core.util.State import State
from uds.core.util.model import processUuid
@@ -43,6 +44,7 @@ from uds.REST.model import ModelHandler
from uds.REST import RequestError, ResponseError
from uds.core.ui.UserInterface import gui
from .user_services import AssignedService, CachedService, Groups, Transports, Publications, Changelog
from .services_pool_calendars import AccessCalendars, ActionsCalendars
from .services import Services
import logging
@@ -61,29 +63,44 @@ class ServicesPools(ModelHandler):
'groups': Groups,
'transports': Transports,
'publications': Publications,
'changelog': Changelog
'changelog': Changelog,
'access': AccessCalendars,
'actions': ActionsCalendars
}
save_fields = ['name', 'comments', 'service_id', 'osmanager_id', 'image_id', 'initial_srvs', 'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs', 'show_transports']
save_fields = ['name', 'comments', 'tags', 'service_id', 'osmanager_id', 'image_id', 'servicesPoolGroup_id', 'initial_srvs', 'cache_l1_srvs', 'cache_l2_srvs', 'max_srvs', 'show_transports']
remove_fields = ['osmanager_id', 'service_id']
table_title = _('Service Pools')
table_fields = [
{'name': {'title': _('Name')}},
{'parent': {'title': _('Parent Service')}}, # Will process this field on client in fact, not sent by server
{'parent': {'title': _('Parent Service')}},
{'state': {'title': _('status'), 'type': 'dict', 'dict': State.dictionary()}},
{'show_transports': {'title': _('Shows transports')}}, # Will process this field on client in fact, not sent by server
{'comments': {'title': _('Comments')}},
{'show_transports': {'title': _('Shows transports'), 'type': 'callback'}},
{'pool_group_name': {'title': _('Pool Group')}},
{'tags': {'title': _('tags'), 'visible': False}},
]
# Field from where to get "class" and prefix for that class, so this will generate "row-state-A, row-state-X, ....
table_row_style = {'field': 'state', 'prefix': 'row-state-'}
custom_methods = [('setFallbackAccess', True), ('actionsList', True)]
def item_as_dict(self, item):
# if item does not have an associated service, hide it (the case, for example, for a removed service)
# Access from dict will raise an exception, and item will be skipped
poolGroupId = None
poolGroupName = _('Default')
poolGroupThumb = DEFAULT_THUMB_BASE64
if item.servicesPoolGroup is not None:
poolGroupId = item.servicesPoolGroup.uuid
poolGroupName = item.servicesPoolGroup.name
if item.servicesPoolGroup.image is not None:
poolGroupThumb = item.servicesPoolGroup.image.thumb64
val = {
'id': item.uuid,
'name': item.name,
'tags': [tag.tag for tag in item.tags.all()],
'parent': item.service.name,
'parent_type': item.service.data_type,
'comments': item.comments,
@@ -92,6 +109,9 @@ class ServicesPools(ModelHandler):
'service_id': item.service.uuid,
'provider_id': item.service.provider.uuid,
'image_id': item.image.uuid if item.image is not None else None,
'servicesPoolGroup_id': poolGroupId,
'pool_group_name': poolGroupName,
'pool_group_thumb': poolGroupThumb,
'initial_srvs': item.initial_srvs,
'cache_l1_srvs': item.cache_l1_srvs,
'cache_l2_srvs': item.cache_l2_srvs,
@@ -99,6 +119,7 @@ class ServicesPools(ModelHandler):
'user_services_count': item.userServices.count(),
'restrained': item.isRestrained(),
'show_transports': item.show_transports,
'fallbackAccess': item.fallbackAccess,
'permission': permissions.getEffectivePermission(self._user, item),
'info': Services.serviceInfo(item.service),
}
@@ -115,7 +136,7 @@ class ServicesPools(ModelHandler):
if Service.objects.count() < 1:
raise ResponseError(ugettext('Create at least a service before creating a new service pool'))
g = self.addDefaultFields([], ['name', 'comments'])
g = self.addDefaultFields([], ['name', 'comments', 'tags'])
for f in [{
'name': 'service_id',
@@ -135,11 +156,20 @@ class ServicesPools(ModelHandler):
'order': 101,
}, {
'name': 'image_id',
'values': [gui.choiceItem(-1, '')] + gui.sortedChoices([gui.choiceItem(v.uuid, v.name) for v in Image.objects.all()]),
'values': [gui.choiceImage(-1, '--------', DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in Image.objects.all()]),
'label': ugettext('Associated Image'),
'tooltip': ugettext('Image assocciated with this service'),
'type': gui.InputField.CHOICE_TYPE,
'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 102,
'tab': ugettext('Display'),
}, {
'name': 'servicesPoolGroup_id',
'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in ServicesPoolGroup.objects.all()]),
'label': ugettext('Pool group'),
'tooltip': ugettext('Pool group for this pool (for pool clasify on display)'),
'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 103,
'tab': ugettext('Display'),
}, {
'name': 'initial_srvs',
'value': '0',
@@ -147,7 +177,8 @@ class ServicesPools(ModelHandler):
'label': ugettext('Initial available services'),
'tooltip': ugettext('Services created initially for this service pool'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 103,
'order': 110,
'tab': ugettext('Availability'),
}, {
'name': 'cache_l1_srvs',
'value': '0',
@@ -155,7 +186,8 @@ class ServicesPools(ModelHandler):
'label': ugettext('Services to keep in cache'),
'tooltip': ugettext('Services kept in cache for improved user service assignation'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 104,
'order': 111,
'tab': ugettext('Availability'),
}, {
'name': 'cache_l2_srvs',
'value': '0',
@@ -163,7 +195,8 @@ class ServicesPools(ModelHandler):
'label': ugettext('Services to keep in L2 cache'),
'tooltip': ugettext('Services kept in cache of level2 for improved service generation'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 105,
'order': 112,
'tab': ugettext('Availability'),
}, {
'name': 'max_srvs',
'value': '0',
@@ -171,14 +204,15 @@ class ServicesPools(ModelHandler):
'label': ugettext('Maximum number of services to provide'),
'tooltip': ugettext('Maximum number of service (assigned and L1 cache) that can be created for this service'),
'type': gui.InputField.NUMERIC_TYPE,
'order': 106,
'order': 113,
'tab': ugettext('Availability'),
}, {
'name': 'show_transports',
'value': True,
'label': ugettext('Show transports'),
'tooltip': ugettext('If active, alternative transports for user will be shown'),
'type': gui.InputField.CHECKBOX_TYPE,
'order': 107,
'order': 120,
}]:
self.addField(g, f)
@@ -212,6 +246,9 @@ class ServicesPools(ModelHandler):
except Exception:
raise RequestError(ugettext('This service requires an OS Manager'))
# If max < initial or cache_1 or cache_l2
fields['max_srvs'] = max((int(fields['initial_srvs']), int(fields['cache_l1_srvs']), int(fields['max_srvs'])))
imgId = fields['image_id']
fields['image_id'] = None
logger.debug('Image id: {}'.format(imgId))
@@ -222,6 +259,17 @@ class ServicesPools(ModelHandler):
except Exception:
logger.exception('At image recovering')
# Servicepool Group
spgrpId = fields['servicesPoolGroup_id']
fields['servicesPoolGroup_id'] = None
logger.debug('servicesPoolGroup_id: {}'.format(spgrpId))
try:
if spgrpId != '-1':
spgrp = ServicesPoolGroup.objects.get(uuid=processUuid(spgrpId))
fields['servicesPoolGroup_id'] = spgrp.id
except Exception:
logger.exception('At service pool group recovering')
except (RequestError, ResponseError):
raise
except Exception as e:
@@ -240,3 +288,27 @@ class ServicesPools(ModelHandler):
return log.getLogs(item)
except Exception:
return []
# Set fallback status
def setFallbackAccess(self, item):
self.ensureAccess(item, permissions.PERMISSION_MANAGEMENT)
fallback = self._params.get('fallbackAccess')
logger.debug('Setting fallback of {} to {}'.format(item.name, fallback))
item.fallbackAccess = fallback
item.save()
return ''
# Returns the action list based on current element, for calendar
def actionsList(self, item):
validActions = ()
itemInfo = item.service.getType()
if itemInfo.usesCache is True:
validActions += (CALENDAR_ACTION_INITIAL, CALENDAR_ACTION_CACHE_L1, CALENDAR_ACTION_MAX)
if itemInfo.usesCache_L2 is True:
validActions += (CALENDAR_ACTION_CACHE_L2,)
if itemInfo.publicationType is not None:
validActions += (CALENDAR_ACTION_PUBLISH,)
return validActions

View File

@@ -37,7 +37,7 @@ from uds.models import User, Service, UserService, DeployedService, getSqlDateti
from uds.core.util.stats import counters
from uds.core.util.Cache import Cache
from uds.REST import Handler, RequestError, ResponseError
import cPickle
import pickle
from datetime import timedelta
import logging
@@ -70,11 +70,11 @@ def getServicesPoolsCounters(servicePool, counter_type):
for x in counters.getCounters(us, counter_type, since=since, to=to, limit=POINTS, use_max=USE_MAX, all=complete):
val.append({'stamp': x[0], 'value': int(x[1])})
if len(val) > 2:
cache.put(cacheKey, cPickle.dumps(val).encode('zip'), 600)
cache.put(cacheKey, pickle.dumps(val).encode('zip'), 600)
else:
val = [{'stamp': since, 'value': 0}, {'stamp': to, 'value': 0}]
else:
val = cPickle.loads(val.decode('zip'))
val = pickle.loads(val.decode('zip'))
return val
except:

View File

@@ -48,14 +48,15 @@ logger = logging.getLogger(__name__)
class Transports(ModelHandler):
model = Transport
save_fields = ['name', 'comments', 'priority', 'nets_positive']
save_fields = ['name', 'comments', 'tags', 'priority', 'nets_positive']
table_title = _('Current Transports')
table_fields = [
{'priority': {'title': _('Priority'), 'type': 'numeric', 'width': '6em'}},
{'name': {'title': _('Name'), 'visible': True, 'type': 'iconType'}},
{'comments': {'title': _('Comments')}},
{'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}}
{'deployed_count': {'title': _('Used by'), 'type': 'numeric', 'width': '8em'}},
{'tags': {'title': _('tags'), 'visible': False}},
]
def enum_types(self):
@@ -64,7 +65,7 @@ class Transports(ModelHandler):
def getGui(self, type_):
try:
return self.addField(
self.addField(self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments', 'priority']), {
self.addField(self.addDefaultFields(factory().lookup(type_).guiDescription(), ['name', 'comments', 'tags', 'priority']), {
'name': 'nets_positive',
'value': True,
'label': ugettext('Network access'),
@@ -88,6 +89,7 @@ class Transports(ModelHandler):
return {
'id': item.uuid,
'name': item.name,
'tags': [tag.tag for tag in item.tags.all()],
'comments': item.comments,
'priority': item.priority,
'nets_positive': item.nets_positive,

View File

@@ -86,6 +86,10 @@ class AssignedService(DetailHandler):
else:
val.update({
'owner': item.user.manager.name + "-" + item.user.name,
'owner_info': {
'auth_id': item.user.manager.uuid,
'user_id': item.user.uuid
},
'in_use': item.in_use,
'in_use_date': item.in_use_date,
'source_host': item.src_hostname,
@@ -143,10 +147,12 @@ class AssignedService(DetailHandler):
self.invalidItemException()
logger.debug('Deleting assigned service')
if service.state == State.USABLE:
if service.state in (State.USABLE, State.REMOVING):
service.remove()
elif service.state == State.PREPARING:
service.cancel()
elif service.state == State.REMOVABLE:
self.invalidItemException(_('Item already being removed'))
else:
self.invalidItemException(_('Item is not removable'))
@@ -202,6 +208,7 @@ class Groups(DetailHandler):
def getItems(self, parent, item):
return [{
'id': i.uuid,
'auth_id': i.manager.uuid,
'name': i.name,
'comments': i.comments,
'state': i.state,

View File

@@ -42,6 +42,8 @@ from uds.REST.handlers import Handler, HandlerError
from uds.core.util import log
from uds.core.util import permissions
from uds.core.util.model import processUuid
from uds.models import Tag
import fnmatch
import re
@@ -52,7 +54,7 @@ import logging
logger = logging.getLogger(__name__)
__updated__ = '2015-04-28'
__updated__ = '2016-04-18'
# a few constants
@@ -86,7 +88,7 @@ class BaseModelHandler(Handler):
:param gui: List of "gui" items where the field will be added
:param field: Field to be added (dictionary)
'''
gui.append({
v = {
'name': field.get('name', ''),
'value': '',
'gui': {
@@ -103,7 +105,10 @@ class BaseModelHandler(Handler):
'order': field.get('order', 0),
'values': field.get('values', [])
}
})
}
if 'tab' in field:
v['gui']['tab'] = field['tab']
gui.append(v)
return gui
def addDefaultFields(self, gui, flds):
@@ -112,6 +117,14 @@ class BaseModelHandler(Handler):
:param gui: Gui list where the "default" fielsds will be added
:param flds: List of fields names requested to be added. Valid values are 'name', 'comments', 'priority' and 'small_name'
'''
if 'tags' in flds:
self.addField(gui, {
'name': 'tags',
'label': _('Tags'),
'type': 'taglist',
'tooltip': _('Tags for this element'),
'order': 0 - 101,
})
if 'name' in flds:
self.addField(gui, {
'name': 'name',
@@ -137,17 +150,17 @@ class BaseModelHandler(Handler):
'required': True,
'value': 1,
'length': 4,
'order': 0 - 98,
'order': 0 - 97,
})
if 'small_name' in flds:
self.addField(gui, {
'name': 'small_name',
'type': 'text',
'label': _('Tag'),
'tooltip': _('Tag for this element'),
'label': _('Label'),
'tooltip': _('Label for this element'),
'required': True,
'length': 128,
'order': 0 - 97,
'order': 0 - 96,
})
return gui
@@ -212,7 +225,9 @@ class BaseModelHandler(Handler):
:param res: Dictionary to "extend" with instance key-values pairs
'''
if hasattr(item, 'getInstance'):
for key, value in item.getInstance().valuesDict().iteritems():
i = item.getInstance()
i.initGui() # Defaults & stuff
for key, value in i.valuesDict().iteritems():
if type(value) in (unicode, str):
value = {"true": True, "false": False}.get(value, value) # Translate "true" & "false" to True & False (booleans)
logger.debug('{0} = {1}'.format(key, value))
@@ -238,8 +253,9 @@ class BaseModelHandler(Handler):
'''
Raises a NotFound exception, with location info
'''
message = _('Item not found') if message is None else None
raise NotFound('{} {}: {}'.format(message, self.__class__, self._args))
message = _('Item not found') if message is None else message
raise NotFound(message)
# raise NotFound('{} {}: {}'.format(message, self.__class__, self._args))
def accessDenied(self, message=None):
raise AccessDenied(message or _('Access denied'))
@@ -832,6 +848,13 @@ class ModelHandler(BaseModelHandler):
args = self.readFieldsFromParams(self.save_fields)
logger.debug('Args: {}'.format(args))
self.beforeSave(args)
# If tags is in save fields, treat it "specially"
if 'tags' in self.save_fields:
tags = args['tags']
del args['tags']
else:
tags = None
deleteOnError = False
if len(self._args) == 0: # create new
item = self.model.objects.create(**args)
@@ -843,6 +866,12 @@ class ModelHandler(BaseModelHandler):
if v in args:
del args[v]
item.__dict__.update(args) # Update fields from args
# Now if tags, update them
if tags is not None:
logger.debug('Updating tags: {}'.format(tags))
item.tags = [ Tag.objects.get_or_create(tag=val)[0] for val in tags]
except self.model.DoesNotExist:
raise NotFound('Item not found')
except IntegrityError: # Duplicate key probably

View File

@@ -33,7 +33,8 @@
from __future__ import unicode_literals
# from django.utils import simplejson as json
import ujson as json
#import ujson as json
import json
from xml_marshaller import xml_marshaller
import datetime
import time
@@ -94,7 +95,9 @@ class ContentProcessor(object):
'''
Helper for renderers. Alters some types so they can be serialized correctly (as we want them to be)
'''
if isinstance(obj, (bool, int, float, six.text_type)):
if obj is None:
return None
elif isinstance(obj, (bool, int, float, six.text_type)):
return obj
elif isinstance(obj, long):
return int(obj)
@@ -108,7 +111,7 @@ class ContentProcessor(object):
for v in obj:
res.append(ContentProcessor.procesForRender(v))
return res
elif isinstance(obj, datetime.datetime):
elif isinstance(obj, (datetime.datetime, datetime.date)):
return int(time.mktime(obj.timetuple()))
elif isinstance(obj, six.binary_type):
return obj.decode('utf-8')

View File

@@ -32,20 +32,11 @@
'''
# Make sure that all services are "available" at service startup
from . import services # to make sure that the packages are initialized at this point
from . import auths # To make sure that the packages are initialized at this point
from . import osmanagers # To make sure that packages are initialized at this point
from . import transports # To make sure that packages are initialized at this point
from . import dispatchers
from . import models
from . import plugins # To make sure plugins are loaded on memory
from . import REST # To make sure REST initializes all what it needs
import uds.xmlrpc # To make actor live
from django.db.backends.signals import connection_created
from django.dispatch import receiver
import math
import ssl
from django.apps import AppConfig
@@ -55,7 +46,17 @@ import logging
logger = logging.getLogger(__name__)
__updated__ = '2015-06-09'
__updated__ = '2016-04-04'
# Default ssl context is unverified, as MOST servers that we will connect will be with self signed certificates...
try:
_create_unverified_https_context = ssl._create_unverified_context
ssl._create_default_https_context = _create_unverified_https_context
except AttributeError:
# Legacy Python that doesn't verify HTTPS certificates by default
pass
class UDSAppConfig(AppConfig):
@@ -67,6 +68,16 @@ class UDSAppConfig(AppConfig):
# with ANY command from manage.
logger.debug('Initializing app (ready) ***************')
# Now, ensures that all dynamic elements are loadad and present
from . import services # to make sure that the packages are initialized at this point
from . import auths # To make sure that the packages are initialized at this point
from . import osmanagers # To make sure that packages are initialized at this point
from . import transports # To make sure that packages are initialized at this point
from . import dispatchers # Ensure all dischatchers all also available
from . import plugins # To make sure plugins are loaded on memory
from . import REST # To make sure REST initializes all what it needs
default_app_config = 'uds.UDSAppConfig'

View File

@@ -43,13 +43,13 @@ from uds.core.ui.UserInterface import gui
import logging
__updated__ = '2015-01-21'
__updated__ = '2016-04-18'
logger = logging.getLogger(__name__)
class IPAuth(Authenticator):
acceptProxy = gui.CheckBoxField(label=_('Accept proxy'), order=3, tooltip=_('If checked, requests via proxy will get FORWARDED ip address (take care with this bein checked, can take internal IP addresses from internet)'))
acceptProxy = gui.CheckBoxField(label=_('Accept proxy'), order=3, tooltip=_('If checked, requests via proxy will get FORWARDED ip address (take care with this bein checked, can take internal IP addresses from internet)'), tab=gui.ADVANCED_TAB)
typeName = _('IP Authenticator')
typeType = 'IPAuth'

View File

@@ -40,10 +40,11 @@ from uds.models import Authenticator as dbAuthenticator
from uds.core.ui import gui
from uds.core.managers import cryptoManager
from uds.core.util.State import State
from uds.core.util.request import getRequest
import dns
import logging
__updated__ = '2015-02-02'
__updated__ = '2016-04-20'
logger = logging.getLogger(__name__)
@@ -60,14 +61,16 @@ class InternalDBAuth(Authenticator):
# This is the only internal source
isExternalSource = False
differentForEachHost = gui.CheckBoxField(label=_('Different user for each host'), order=1, tooltip=_('If checked, each host will have a different user name'), defvalue="false", rdonly=True)
reverseDns = gui.CheckBoxField(label=_('Reverse DNS'), order=2, tooltip=_('If checked, the host will be reversed dns'), defvalue="false", rdonly=True)
differentForEachHost = gui.CheckBoxField(label=_('Different user for each host'), order=1, tooltip=_('If checked, each host will have a different user name'), defvalue="false", rdonly=True, tab=gui.ADVANCED_TAB)
reverseDns = gui.CheckBoxField(label=_('Reverse DNS'), order=2, tooltip=_('If checked, the host will be reversed dns'), defvalue="false", rdonly=True, tab=gui.ADVANCED_TAB)
acceptProxy = gui.CheckBoxField(label=_('Accept proxy'), order=3, tooltip=_('If checked, requests via proxy will get FORWARDED ip address (take care with this bein checked, can take internal IP addresses from internet)'), tab=gui.ADVANCED_TAB)
def initialize(self, values):
if values is None:
return
def getIp(self, ip):
def getIp(self):
ip = getRequest().ip_proxy if self.acceptProxy.isTrue() else getRequest().ip # pylint: disable=maybe-no-member
if self.reverseDns.isTrue():
try:
return str(dns.resolver.query(dns.reversename.from_address(ip), 'PTR')[0])
@@ -76,9 +79,8 @@ class InternalDBAuth(Authenticator):
return ip
def transformUsername(self, username):
from uds.core.util.request import getRequest
if self.differentForEachHost.isTrue():
newUsername = self.getIp(getRequest().ip) + '-' + username # pylint: disable=maybe-no-member
newUsername = self.getIp() + '-' + username # pylint: disable=maybe-no-member
# Duplicate basic user into username.
auth = self.dbAuthenticator()
# "Derived" users will belong to no group at all, because we will extract groups from "base" user

View File

@@ -44,7 +44,7 @@ import ldap.filter
import re
import logging
__updated__ = '2015-02-02'
__updated__ = '2016-04-18'
logger = logging.getLogger(__name__)
@@ -56,14 +56,15 @@ class RegexLdap(auths.Authenticator):
host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Ldap Server Host'), required=True)
port = gui.NumericField(length=5, label=_('Port'), defvalue='389', order=2, tooltip=_('Ldap port (usually 389 for non ssl and 636 for ssl)'), required=True)
ssl = gui.CheckBoxField(label=_('Use SSL'), order=3, tooltip=_('If checked, the connection will be ssl, using port 636 instead of 389'))
username = gui.TextField(length=64, label=_('Ldap User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True)
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True)
username = gui.TextField(length=64, label=_('User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True, tab=gui.CREDENTIALS_TAB)
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True, tab=gui.CREDENTIALS_TAB)
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to LDAP'), required=True, minValue=1)
ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True)
userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True)
userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True)
userNameAttr = gui.TextField(length=640, label=_('User Name Attr'), multiline=2, defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True)
groupNameAttr = gui.TextField(length=640, label=_('Group Name Attr'), multiline=2, defvalue='cn', order=11, tooltip=_('Attribute that contains the group name'), required=True)
ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True, tab=_('Ldap info'))
userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True, tab=_('Ldap info'))
userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True, tab=_('Ldap info'))
userNameAttr = gui.TextField(length=640, label=_('User Name Attr'), multiline=2, defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True, tab=_('Ldap info'))
groupNameAttr = gui.TextField(length=640, label=_('Group Name Attr'), multiline=2, defvalue='cn', order=11, tooltip=_('Attribute that contains the group name'), required=True, tab=_('Ldap info'))
# regex = gui.TextField(length=64, label = _('Regular Exp. for groups'), defvalue = '^(.*)', order = 12, tooltip = _('Regular Expression to extract the group name'), required = True)
typeName = _('Regex LDAP Authenticator')

View File

@@ -45,7 +45,7 @@ import ldap
import logging
import six
__updated__ = '2015-02-02'
__updated__ = '2016-04-18'
logger = logging.getLogger(__name__)
@@ -57,16 +57,16 @@ class SimpleLDAPAuthenticator(Authenticator):
host = gui.TextField(length=64, label=_('Host'), order=1, tooltip=_('Ldap Server IP or Hostname'), required=True)
port = gui.NumericField(length=5, label=_('Port'), defvalue='389', order=2, tooltip=_('Ldap port (usually 389 for non ssl and 636 for ssl)'), required=True)
ssl = gui.CheckBoxField(label=_('Use SSL'), order=3, tooltip=_('If checked, the connection will be ssl, using port 636 instead of 389'))
username = gui.TextField(length=64, label=_('Ldap User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True)
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True)
username = gui.TextField(length=64, label=_('Ldap User'), order=4, tooltip=_('Username with read privileges on the base selected'), required=True, tab=gui.CREDENTIALS_TAB)
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the ldap user'), required=True, tab=gui.CREDENTIALS_TAB)
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to LDAP'), required=True, minValue=1)
ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True)
userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True)
userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True)
userNameAttr = gui.TextField(length=64, label=_('User Name Attr'), defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True)
groupClass = gui.TextField(length=64, label=_('Group class'), defvalue='posixGroup', order=11, tooltip=_('Class for LDAP groups (normally poxisGroup)'), required=True)
groupIdAttr = gui.TextField(length=64, label=_('Group Id Attr'), defvalue='cn', order=12, tooltip=_('Attribute that contains the group id'), required=True)
memberAttr = gui.TextField(length=64, label=_('Group membership attr'), defvalue='memberUid', order=13, tooltip=_('Attribute of the group that contains the users belonging to it'), required=True)
ldapBase = gui.TextField(length=64, label=_('Base'), order=7, tooltip=_('Common search base (used for "users" and "groups")'), required=True, tab=_('Ldap info'))
userClass = gui.TextField(length=64, label=_('User class'), defvalue='posixAccount', order=8, tooltip=_('Class for LDAP users (normally posixAccount)'), required=True, tab=_('Ldap info'))
userIdAttr = gui.TextField(length=64, label=_('User Id Attr'), defvalue='uid', order=9, tooltip=_('Attribute that contains the user id'), required=True, tab=_('Ldap info'))
userNameAttr = gui.TextField(length=64, label=_('User Name Attr'), defvalue='uid', order=10, tooltip=_('Attributes that contains the user name (list of comma separated values)'), required=True, tab=_('Ldap info'))
groupClass = gui.TextField(length=64, label=_('Group class'), defvalue='posixGroup', order=11, tooltip=_('Class for LDAP groups (normally poxisGroup)'), required=True, tab=_('Ldap info'))
groupIdAttr = gui.TextField(length=64, label=_('Group Id Attr'), defvalue='cn', order=12, tooltip=_('Attribute that contains the group id'), required=True, tab=_('Ldap info'))
memberAttr = gui.TextField(length=64, label=_('Group membership attr'), defvalue='memberUid', order=13, tooltip=_('Attribute of the group that contains the users belonging to it'), required=True, tab=_('Ldap info'))
typeName = _('SimpleLDAP Authenticator')
typeType = 'SimpleLdapAuthenticator'

View File

@@ -207,8 +207,8 @@ class Module(UserInterface, Environmentable, Serializable):
We want to use the env, cache and storage methods outside class.
If not called, you must implement your own methods.
cache and storage are "convenient" methods to access _env.cache() and
_env.storage()
cache and storage are "convenient" methods to access _env.cache and
_env.storage
The values param is passed directly to UserInterface base.

View File

@@ -59,6 +59,7 @@ class Environment(object):
self._storage = Storage(uniqueKey)
self._idGenerators = idGenerators
@property
def cache(self):
'''
Method to acces the cache of the environment.
@@ -66,6 +67,7 @@ class Environment(object):
'''
return self._cache
@property
def storage(self):
'''
Method to acces the cache of the environment.
@@ -83,6 +85,7 @@ class Environment(object):
'''
return self._idGenerators.get(generatorId, None)
@property
def key(self):
'''
@return: the key used for this environment
@@ -156,15 +159,7 @@ class Environmentable(object):
'''
self._env = environment
def setEnv(self, environment):
'''
Assigns a new environment
Args:
environment: Environment to assign
'''
self._env = environment
@property
def env(self):
'''
Utility method to access the envionment contained by this object
@@ -174,6 +169,18 @@ class Environmentable(object):
'''
return self._env
@env.setter
def env(self, environment):
'''
Assigns a new environment
Args:
environment: Environment to assign
'''
self._env = environment
@property
def cache(self):
'''
Utility method to access the cache of the environment containe by this object
@@ -181,8 +188,9 @@ class Environmentable(object):
Returns:
Cache for the object
'''
return self._env.cache()
return self._env.cache
@property
def storage(self):
'''
Utility method to access the storage of the environment containe by this object
@@ -190,7 +198,7 @@ class Environmentable(object):
Returns:
Storage for the object
'''
return self._env.storage()
return self._env.storage
def idGenerators(self, generatorId):
'''

View File

@@ -38,8 +38,6 @@ from __future__ import unicode_literals
from uds.core.Environment import Environmentable
from uds.core.Serializable import Serializable
from uds.core.BaseModule import Module
from uds.core import services
from uds.core import auths
from uds.core import transports
VERSION = '1.9.1'
VERSION = '2.0.0-DEVEL'
VERSION_STAMP = '20160501-DEVEL'

View File

@@ -53,7 +53,7 @@ from uds.models import User
import logging
import six
__updated__ = '2015-11-06'
__updated__ = '2016-04-15'
logger = logging.getLogger(__name__)
authLogger = logging.getLogger('authLog')
@@ -303,7 +303,7 @@ def webLogout(request, exit_url=None):
'''
# Invoke exit for authenticator
if request.user.id != ROOT_ID:
if request.user is not None and request.user.id != ROOT_ID:
events.addEvent(request.user.manager, events.ET_LOGOUT, username=request.user.name, srcip=request.ip)
request.session.clear()

View File

@@ -44,7 +44,7 @@ import threading
import time
import logging
__updated__ = '2016-02-01'
__updated__ = '2016-03-07'
logger = logging.getLogger(__name__)
@@ -60,8 +60,8 @@ class DelayedTaskThread(threading.Thread):
def run(self):
try:
self._taskInstance.execute()
except Exception, e:
logger.debug("Exception in thread {0}: {1}".format(e.__class__, e))
except Exception as e:
logger.exception("Exception in thread {0}: {1}".format(e.__class__, e))
class DelayedTaskRunner(object):
@@ -116,8 +116,7 @@ class DelayedTaskRunner(object):
if taskInstance is not None:
logger.debug('Executing delayedTask:>{0}<'.format(task))
env = Environment.getEnvForType(taskInstance.__class__)
taskInstance.setEnv(env)
taskInstance.env = Environment.getEnvForType(taskInstance.__class__)
DelayedTaskThread(taskInstance).start()
def __insert(self, instance, delay, tag):

View File

@@ -43,7 +43,7 @@ import threading
import time
import logging
__updated__ = '2015-10-15'
__updated__ = '2016-05-18'
logger = logging.getLogger(__name__)
@@ -175,6 +175,7 @@ class Scheduler(object):
logger.debug('Releasing all owned scheduled tasks')
with transaction.atomic():
dbScheduler.objects.select_for_update().filter(owner_server=platform.node()).update(owner_server='') # @UndefinedVariable
dbScheduler.objects.select_for_update().filter(last_execution__lt=getSqlDatetime() - timedelta(minutes=15), state=State.RUNNING).update(owner_server='', state=State.FOR_EXECUTE) # @UndefinedVariable
dbScheduler.objects.select_for_update().filter(owner_server='').update(state=State.FOR_EXECUTE) # @UndefinedVariable
def run(self):

View File

@@ -35,7 +35,7 @@ from __future__ import unicode_literals
import os
import uuid
from django.http import HttpResponse, Http404
from django.core.servers.basehttp import FileWrapper
from wsgiref.util import FileWrapper
from uds.core.managers import cryptoManager

View File

@@ -41,6 +41,8 @@ from uds.models import DeployedServicePublication, getSqlDatetime
from uds.core.util.State import State
from uds.core.util import log
import logging
import datetime
import pickle
logger = logging.getLogger(__name__)
@@ -91,6 +93,7 @@ class PublicationLauncher(DelayedTask):
state = pi.publish()
deployedService = servicePoolPub.deployed_service
deployedService.current_pub_revision += 1
deployedService.storeValue('toBeReplacedIn', pickle.dumps(datetime.datetime.now() + datetime.timedelta(hours=GlobalConfig.SESSION_EXPIRE_TIME.getInt(True))))
deployedService.save()
PublicationFinishChecker.checkAndUpdateState(servicePoolPub, pi, state)
except Exception:

View File

@@ -35,140 +35,26 @@ from __future__ import unicode_literals
from django.utils.translation import ugettext as _
from django.db.models import Q
from django.db import transaction
from uds.core.jobs.DelayedTask import DelayedTask
from uds.core.jobs.DelayedTaskRunner import DelayedTaskRunner
from uds.core.services.Exceptions import OperationException
from uds.core.util.State import State
from uds.core.util import log
from uds.core.util.Config import GlobalConfig
from uds.core.services.Exceptions import MaxServicesReachedError, ServiceInMaintenanceMode, InvalidServiceException, ServiceNotReadyError
from uds.core.services.Exceptions import MaxServicesReachedError, ServiceInMaintenanceMode, InvalidServiceException, ServiceNotReadyError, ServiceAccessDeniedByCalendar
from uds.models import ServicePool, UserService, getSqlDatetime, Transport
from uds.core import services
from uds.core.services import Service
from uds.core.util.stats import events
from .userservice.opchecker import UserServiceOpChecker
import requests
import json
import logging
__updated__ = '2015-11-11'
__updated__ = '2016-10-14'
logger = logging.getLogger(__name__)
USERSERVICE_TAG = 'cm-'
class UserServiceOpChecker(DelayedTask):
def __init__(self, service):
super(UserServiceOpChecker, self).__init__()
self._svrId = service.id
self._state = service.state
@staticmethod
def makeUnique(userService, userServiceInstance, state):
'''
This method ensures that there will be only one delayedtask related to the userService indicated
'''
DelayedTaskRunner.runner().remove(USERSERVICE_TAG + str(userService.id))
UserServiceOpChecker.checkAndUpdateState(userService, userServiceInstance, state)
@staticmethod
def checkAndUpdateState(userService, userServiceInstance, state):
'''
Checks the value returned from invocation to publish or checkPublishingState, updating the servicePoolPub database object
Return True if it has to continue checking, False if finished
'''
try:
prevState = userService.state
userService.unique_id = userServiceInstance.getUniqueId() # Updates uniqueId
userService.friendly_name = userServiceInstance.getName() # And name, both methods can modify serviceInstance, so we save it later
if State.isFinished(state):
checkLater = False
userServiceInstance.finish()
if State.isPreparing(prevState):
if userServiceInstance.service().publicationType is None or userService.publication == userService.deployed_service.activePublication():
userService.setState(State.USABLE)
# and make this usable if os manager says that it is usable, else it pass to configuring state
if userServiceInstance.osmanager() is not None and userService.os_state == State.PREPARING: # If state is already "Usable", do not recheck it
stateOs = userServiceInstance.osmanager().checkState(userService)
# If state is finish, we need to notify the userService again that os has finished
if State.isFinished(stateOs):
state = userServiceInstance.notifyReadyFromOsManager('')
userService.updateData(userServiceInstance)
else:
stateOs = State.FINISHED
if State.isRuning(stateOs):
userService.setOsState(State.PREPARING)
else:
userService.setOsState(State.USABLE)
else:
# We ignore OsManager info and if userService don't belong to "current" publication, mark it as removable
userService.setState(State.REMOVABLE)
elif State.isRemoving(prevState):
if userServiceInstance.osmanager() is not None:
userServiceInstance.osmanager().release(userService)
userService.setState(State.REMOVED)
else:
# Canceled,
logger.debug("Canceled us {2}: {0}, {1}".format(prevState, State.toString(state), State.toString(userService)))
userService.setState(State.CANCELED)
userServiceInstance.osmanager().release(userService)
userService.updateData(userServiceInstance)
elif State.isErrored(state):
checkLater = False
userService.updateData(userServiceInstance)
userService.setState(State.ERROR)
else:
checkLater = True # The task is running
userService.updateData(userServiceInstance)
userService.save()
if checkLater:
UserServiceOpChecker.checkLater(userService, userServiceInstance)
except Exception as e:
logger.exception('Checking service state')
log.doLog(userService, log.ERROR, 'Exception: {0}'.format(e), log.INTERNAL)
userService.setState(State.ERROR)
userService.save()
@staticmethod
def checkLater(userService, ci):
'''
Inserts a task in the delayedTaskRunner so we can check the state of this publication
@param dps: Database object for DeployedServicePublication
@param pi: Instance of Publication manager for the object
'''
# Do not add task if already exists one that updates this service
if DelayedTaskRunner.runner().checkExists(USERSERVICE_TAG + str(userService.id)):
return
DelayedTaskRunner.runner().insert(UserServiceOpChecker(userService), ci.suggestedTime, USERSERVICE_TAG + str(userService.id))
def run(self):
logger.debug('Checking user service finished {0}'.format(self._svrId))
uService = None
try:
uService = UserService.objects.get(pk=self._svrId)
if uService.state != self._state:
logger.debug('Task overrided by another task (state of item changed)')
# This item is no longer valid, returning will not check it again (no checkLater called)
return
ci = uService.getInstance()
logger.debug("uService instance class: {0}".format(ci.__class__))
state = ci.checkState()
UserServiceOpChecker.checkAndUpdateState(uService, ci, state)
except UserService.DoesNotExist, e:
logger.error('User service not found (erased from database?) {0} : {1}'.format(e.__class__, e))
except Exception, e:
# Exception caught, mark service as errored
logger.exception("Error {0}, {1} :".format(e.__class__, e))
if uService is not None:
log.doLog(uService, log.ERROR, 'Exception: {0}'.format(e), log.INTERNAL)
try:
uService.setState(State.ERROR)
uService.save()
except Exception:
logger.error('Can\'t update state of uService object')
class UserServiceManager(object):
_manager = None
@@ -436,9 +322,11 @@ class UserServiceManager(object):
def canRemoveServiceFromDeployedService(self, ds):
'''
checks if we can do a "remove" from a deployed service
serviceIsntance is just a helper, so if we already have unserialized deployedService
'''
removing = self.getServicesInStateForProvider(ds.service.provider_id, State.REMOVING)
if removing >= GlobalConfig.MAX_REMOVING_SERVICES.getInt() and GlobalConfig.IGNORE_LIMITS.getBool() is False:
serviceInstance = ds.service.getInstance()
if removing >= serviceInstance.parent().getMaxRemovingServices() and serviceInstance.parent().getIgnoreLimits() is False:
return False
return True
@@ -447,7 +335,8 @@ class UserServiceManager(object):
Checks if we can start a new service
'''
preparing = self.getServicesInStateForProvider(ds.service.provider_id, State.PREPARING)
if preparing >= GlobalConfig.MAX_PREPARING_SERVICES.getInt() and GlobalConfig.IGNORE_LIMITS.getBool() is False:
serviceInstance = ds.service.getInstance()
if preparing >= serviceInstance.parent().getMaxPreparingServices() and serviceInstance.parent().getIgnoreLimits() is False:
return False
return True
@@ -489,6 +378,34 @@ class UserServiceManager(object):
except Exception as e:
logger.info('preConnection failed: {}. Check connection on destination machine: {}'.format(e, url))
def checkUuid(self, uService):
url = uService.getCommsUrl()
if url is None:
logger.debug('No uuid to retrieve because agent does not supports notifications')
return True # UUid is valid because it is not supported checking it
if uService.getProperty('actor_version', '') < '2.0.0': # Just for 2.0 or newer, previous actors will not support this method
return True
url += '/uuid'
try:
r = requests.get(url, verify=False, timeout=5)
uuid = json.loads(r.content)
if uuid != uService.uuid:
logger.info('The requested machine has uuid {} and the expected was {}'.format(uuid, uService.uuid))
return False
logger.debug('Got uuid from machine: {} {} {}'.format(url, uuid, uService.uuid))
# In fact we ignore result right now
except Exception as e:
logger.info('Get uuid failed: {}. Check connection on destination machine: {}'.format(e, url))
# return True
return True
def sendScript(self, uService, script):
'''
If allowed, send script to user service
@@ -553,10 +470,13 @@ class UserServiceManager(object):
# Now we have to locate an instance of the service, so we can assign it to user.
userService = self.getAssignationForUser(ds, user)
logger.debug('Found service: {0}'.format(userService))
if userService.isInMaintenance() is True:
raise ServiceInMaintenanceMode()
logger.debug('Found service: {0}'.format(userService))
if userService.deployed_service.isAccessAllowed() is False:
raise ServiceAccessDeniedByCalendar()
if idTransport is None or idTransport == '': # Find a suitable transport
for v in userService.deployed_service.transports.order_by('priority'):
@@ -589,20 +509,26 @@ class UserServiceManager(object):
# If ready, show transport for this service, if also ready ofc
iads = userService.getInstance()
ip = iads.getIp()
events.addEvent(userService.deployed_service, events.ET_ACCESS, username=user.name, srcip=srcIp, dstip=ip, uniqueid=userService.unique_id)
if ip is not None:
serviceNotReadyCode = 0x0003
itrans = trans.getInstance()
if itrans.isAvailableFor(userService, ip):
userService.setConnectionSource(srcIp, 'unknown')
log.doLog(userService, log.INFO, "User service ready", log.WEB)
UserServiceManager.manager().notifyPreconnect(userService, itrans.processedUser(userService, user), itrans.protocol)
return (ip, userService, iads, trans, itrans)
else:
log.doLog(userService, log.WARN, "User service is not accessible (ip {0})".format(ip), log.TRANSPORT)
logger.debug('Transport is not ready for user service {0}'.format(userService))
if self.checkUuid(userService) is False: # Machine is not what is expected
serviceNotReadyCode = 0x0004
log.doLog(userService, log.WARN, "User service is not accessible (ip {0})".format(ip), log.TRANSPORT)
logger.debug('Transport is not ready for user service {0}'.format(userService))
else:
logger.debug('Ip not available from user service {0}'.format(userService))
events.addEvent(userService.deployed_service, events.ET_ACCESS, username=user.name, srcip=srcIp, dstip=ip, uniqueid=userService.unique_id)
if ip is not None:
serviceNotReadyCode = 0x0003
itrans = trans.getInstance()
if itrans.isAvailableFor(userService, ip):
userService.setConnectionSource(srcIp, 'unknown')
log.doLog(userService, log.INFO, "User service ready", log.WEB)
self.notifyPreconnect(userService, itrans.processedUser(userService, user), itrans.protocol)
return (ip, userService, iads, trans, itrans)
else:
log.doLog(userService, log.WARN, "User service is not accessible (ip {0})".format(ip), log.TRANSPORT)
logger.debug('Transport is not ready for user service {0}'.format(userService))
else:
logger.debug('Ip not available from user service {0}'.format(userService))
else:
log.doLog(userService, log.WARN, "User {0} from {1} tried to access, but service was not ready".format(user.name, srcIp), log.WEB)

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