This repository has been archived by the owner on Jul 17, 2023. It is now read-only.
/
TunnelRequestService.java
316 lines (265 loc) · 11.5 KB
/
TunnelRequestService.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
/*
* Copyright (C) 2013 Glyptodon LLC
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.glyptodon.guacamole.net.basic;
import com.google.inject.Inject;
import com.google.inject.Singleton;
import java.util.List;
import org.glyptodon.guacamole.GuacamoleClientException;
import org.glyptodon.guacamole.GuacamoleException;
import org.glyptodon.guacamole.GuacamoleSecurityException;
import org.glyptodon.guacamole.environment.Environment;
import org.glyptodon.guacamole.net.DelegatingGuacamoleTunnel;
import org.glyptodon.guacamole.net.GuacamoleTunnel;
import org.glyptodon.guacamole.net.auth.Connection;
import org.glyptodon.guacamole.net.auth.ConnectionGroup;
import org.glyptodon.guacamole.net.auth.Directory;
import org.glyptodon.guacamole.net.auth.UserContext;
import org.glyptodon.guacamole.net.basic.rest.auth.AuthenticationService;
import org.glyptodon.guacamole.protocol.GuacamoleClientInformation;
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 {
/**
* The Guacamole server environment.
*/
@Inject
private Environment environment;
/**
* Logger for this class.
*/
private final Logger logger = LoggerFactory.getLogger(TunnelRequestService.class);
/**
* A service for authenticating users from auth tokens.
*/
@Inject
private AuthenticationService authenticationService;
/**
* Reads and returns the client information provided within the given
* request.
*
* @param request
* The request describing tunnel to create.
*
* @return GuacamoleClientInformation
* An object containing information about the client sending the tunnel
* request.
*/
protected GuacamoleClientInformation getClientInformation(TunnelRequest request) {
// Get client information
GuacamoleClientInformation info = new GuacamoleClientInformation();
// Set width if provided
String width = request.getParameter("width");
if (width != null)
info.setOptimalScreenWidth(Integer.parseInt(width));
// Set height if provided
String height = request.getParameter("height");
if (height != null)
info.setOptimalScreenHeight(Integer.parseInt(height));
// Set resolution if provided
String dpi = request.getParameter("dpi");
if (dpi != null)
info.setOptimalResolution(Integer.parseInt(dpi));
// Add audio mimetypes
List<String> audio_mimetypes = request.getParameterValues("audio");
if (audio_mimetypes != null)
info.getAudioMimetypes().addAll(audio_mimetypes);
// Add video mimetypes
List<String> video_mimetypes = request.getParameterValues("video");
if (video_mimetypes != null)
info.getVideoMimetypes().addAll(video_mimetypes);
return info;
}
/**
* Creates a new tunnel using which is connected to the connection or
* connection group identifier by the given ID. Client information
* is specified in the {@code info} parameter.
*
* @param context
* The UserContext associated with the user for whom the tunnel is
* being created.
*
* @param idType
* The type of object being connected to (connection or group).
*
* @param id
* The id of the connection or group being connected to.
*
* @param info
* Information describing the connected Guacamole client.
*
* @return
* A new tunnel, connected as required by the request.
*
* @throws GuacamoleException
* If an error occurs while creating the tunnel.
*/
protected GuacamoleTunnel createConnectedTunnel(UserContext context,
final TunnelRequest.IdentifierType idType, String id,
GuacamoleClientInformation info)
throws GuacamoleException {
// Create connected tunnel from identifier
GuacamoleTunnel tunnel = null;
switch (idType) {
// Connection identifiers
case CONNECTION: {
// Get connection directory
Directory<Connection> directory = context.getConnectionDirectory();
// Get authorized connection
Connection connection = directory.get(id);
if (connection == null) {
logger.info("Connection \"{}\" does not exist for user \"{}\".", id, context.self().getIdentifier());
throw new GuacamoleSecurityException("Requested connection is not authorized.");
}
// Connect tunnel
tunnel = connection.connect(info);
logger.info("User \"{}\" connected to connection \"{}\".", context.self().getIdentifier(), id);
break;
}
// Connection group identifiers
case CONNECTION_GROUP: {
// Get connection group directory
Directory<ConnectionGroup> directory = context.getConnectionGroupDirectory();
// Get authorized connection group
ConnectionGroup group = directory.get(id);
if (group == null) {
logger.info("Connection group \"{}\" does not exist for user \"{}\".", id, context.self().getIdentifier());
throw new GuacamoleSecurityException("Requested connection group is not authorized.");
}
// Connect tunnel
tunnel = group.connect(info);
logger.info("User \"{}\" connected to group \"{}\".", context.self().getIdentifier(), id);
break;
}
// Type is guaranteed to be one of the above
default:
assert(false);
}
return tunnel;
}
/**
* Associates the given tunnel with the given session, returning a wrapped
* version of the same tunnel which automatically handles closure and
* removal from the session.
*
* @param tunnel
* The connected tunnel to wrap and monitor.
*
* @param session
* The Guacamole session to associate the tunnel with.
*
* @param idType
* The type of object being connected to (connection or group).
*
* @param id
* The id of the connection or group being connected to.
*
* @return
* A new tunnel, associated with the given session, which delegates all
* functionality to the given tunnel while monitoring and automatically
* handling closure.
*
* @throws GuacamoleException
* If an error occurs while obtaining the tunnel.
*/
protected GuacamoleTunnel createAssociatedTunnel(final GuacamoleSession session,
GuacamoleTunnel tunnel, final TunnelRequest.IdentifierType idType,
final String id) throws GuacamoleException {
// Monitor tunnel closure and data
GuacamoleTunnel monitoredTunnel = new DelegatingGuacamoleTunnel(tunnel) {
/**
* The time the connection began, measured in milliseconds since
* midnight, January 1, 1970 UTC.
*/
private final long connectionStartTime = System.currentTimeMillis();
@Override
public void close() throws GuacamoleException {
long connectionEndTime = System.currentTimeMillis();
long duration = connectionEndTime - connectionStartTime;
// Log closure
switch (idType) {
// Connection identifiers
case CONNECTION:
logger.info("User \"{}\" disconnected from connection \"{}\". Duration: {} milliseconds",
session.getAuthenticatedUser().getIdentifier(), id, duration);
break;
// Connection group identifiers
case CONNECTION_GROUP:
logger.info("User \"{}\" disconnected from connection group \"{}\". Duration: {} milliseconds",
session.getAuthenticatedUser().getIdentifier(), id, duration);
break;
// Type is guaranteed to be one of the above
default:
assert(false);
}
// Close and clean up tunnel
session.removeTunnel(getUUID().toString());
super.close();
}
};
// Associate tunnel with session
session.addTunnel(monitoredTunnel);
return monitoredTunnel;
}
/**
* Creates a new tunnel using the parameters and credentials present in
* the given request.
*
* @param request
* The request 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 {
// Get auth token and session
final String authToken = request.getParameter("authToken");
final GuacamoleSession session = authenticationService.getGuacamoleSession(authToken);
// Get client information and connection ID from request
String id = request.getParameter("id");
final GuacamoleClientInformation info = getClientInformation(request);
// Determine ID type
TunnelRequest.IdentifierType idType = TunnelRequest.IdentifierType.getType(id);
if (idType == null)
throw new GuacamoleClientException("Illegal identifier - unknown type.");
// Remove prefix
id = id.substring(idType.PREFIX.length());
// Create connected tunnel using provided connection ID and client information
final GuacamoleTunnel tunnel = createConnectedTunnel(session.getUserContext(), idType, id, info);
// Associate tunnel with session
return createAssociatedTunnel(session, tunnel, idType, id);
}
}