@@ -35,9 +35,11 @@ import kotlinx.coroutines.launch
35
35
import net.schmizz.sshj.common.SSHException
36
36
import net.schmizz.sshj.connection.ConnectionException
37
37
import java.awt.Dimension
38
+ import java.net.HttpURLConnection
38
39
import java.net.URL
39
40
import java.time.Duration
40
41
import java.util.concurrent.TimeoutException
42
+ import javax.net.ssl.SSLHandshakeException
41
43
42
44
// CoderRemoteConnection uses the provided workspace SSH parameters to launch an
43
45
// IDE against the workspace. If successful the connection is added to recent
@@ -105,6 +107,33 @@ class CoderRemoteConnectionHandle {
105
107
companion object {
106
108
val logger = Logger .getInstance(CoderRemoteConnectionHandle ::class .java.simpleName)
107
109
110
+ /* *
111
+ * Generic function to ask for consent.
112
+ */
113
+ fun confirm (title : String , comment : String , details : String ): Boolean {
114
+ var inputFromUser = false
115
+ ApplicationManager .getApplication().invokeAndWait({
116
+ val panel = panel {
117
+ row {
118
+ label(comment)
119
+ }
120
+ row {
121
+ label(details)
122
+ }
123
+ }
124
+ AppIcon .getInstance().requestAttention(null , true )
125
+ if (! dialog(
126
+ title = title,
127
+ panel = panel,
128
+ ).showAndGet()
129
+ ) {
130
+ return @invokeAndWait
131
+ }
132
+ inputFromUser = true
133
+ }, ModalityState .defaultModalityState())
134
+ return inputFromUser
135
+ }
136
+
108
137
/* *
109
138
* Generic function to ask for input.
110
139
*/
@@ -209,5 +238,66 @@ class CoderRemoteConnectionHandle {
209
238
}
210
239
return Pair (tokenFromUser, tokenSource)
211
240
}
241
+
242
+ /* *
243
+ * Return if the URL is whitelisted, https, and the URL and its final
244
+ * destination, if it is a different host.
245
+ */
246
+ @JvmStatic
247
+ fun isWhitelisted (url : URL , deploymentURL : URL ): Triple <Boolean , Boolean , String > {
248
+ // TODO: Setting for the whitelist, and remember previously allowed
249
+ // domains.
250
+ val domainWhitelist = listOf (" intellij.net" , " jetbrains.com" , deploymentURL.host)
251
+
252
+ // Resolve any redirects.
253
+ val finalUrl = try {
254
+ resolveRedirects(url)
255
+ } catch (e: Exception ) {
256
+ when (e) {
257
+ is SSLHandshakeException ->
258
+ throw Exception (CoderGatewayBundle .message(
259
+ " gateway.connector.view.workspaces.connect.ssl-error" ,
260
+ url.host,
261
+ e.message ? : CoderGatewayBundle .message(" gateway.connector.view.workspaces.connect.no-reason" )
262
+ ))
263
+ else -> throw e
264
+ }
265
+ }
266
+
267
+ var linkWithRedirect = url.toString()
268
+ if (finalUrl.host != url.host) {
269
+ linkWithRedirect = " $linkWithRedirect (redirects to to $finalUrl )"
270
+ }
271
+
272
+ val whitelisted = domainWhitelist.any { url.host == it || url.host.endsWith(" .$it " ) }
273
+ && domainWhitelist.any { finalUrl.host == it || finalUrl.host.endsWith(" .$it " ) }
274
+ val https = url.protocol == " https" && finalUrl.protocol == " https"
275
+ return Triple (whitelisted, https, linkWithRedirect)
276
+ }
277
+
278
+ /* *
279
+ * Follow a URL's redirects to its final destination.
280
+ */
281
+ @JvmStatic
282
+ fun resolveRedirects (url : URL ): URL {
283
+ var location = url
284
+ val maxRedirects = 10
285
+ for (i in 1 .. maxRedirects) {
286
+ val conn = location.openConnection() as HttpURLConnection
287
+ conn.instanceFollowRedirects = false
288
+ conn.connect()
289
+ val code = conn.responseCode
290
+ val nextLocation = conn.getHeaderField(" Location" );
291
+ conn.disconnect()
292
+ // Redirects are triggered by any code starting with 3 plus a
293
+ // location header.
294
+ if (code < 300 || code >= 400 || nextLocation.isNullOrBlank()) {
295
+ return location
296
+ }
297
+ // Location headers might be relative.
298
+ location = URL (location, nextLocation)
299
+ }
300
+ throw Exception (" Too many redirects" )
301
+ }
212
302
}
213
303
}
0 commit comments