-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathXdgOpenSaml.java
executable file
·227 lines (183 loc) · 7.38 KB
/
XdgOpenSaml.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
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS info.picocli:picocli:4.6.3
//DEPS info.picocli:picocli-codegen:4.6.3
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.URI;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.net.http.HttpResponse.BodyHandlers;
import java.security.SecureRandom;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import picocli.CommandLine;
import picocli.CommandLine.Command;
import picocli.CommandLine.Option;
import picocli.CommandLine.Parameters;
/**
* @author filippor([email protected])
*/
@Command(name = "XdgOpenSaml", mixinStandardHelpOptions = true, version = "0.2", description = "retrieve saml token with xdg open")
class XdgOpenSaml implements Callable<Integer> {
@Parameters(index = "0", description = "The server to call")
private String server;
@Option(names = { "--port", "-p" }, description = "port to listen for redirect", defaultValue = "8020")
int port;
@Option(names = { "--realm", "-r" }, description = "The authentication realm.", required = false)
private Optional<String> realm;
@Option(names = { "--trust-all", "-t" }, description = "ignore ssl certificate validation", defaultValue = "false")
private boolean trustAllCertificate;
private final static String ID_PARAMETER_NAME = "id";
private static final String COOKIE_NAME = "SVPNCOOKIE";
private final static X509ExtendedTrustManager noTrustManager = createNoTrustManager();
public static void main(String... args) {
System.exit(new CommandLine(new XdgOpenSaml()).execute(args));
}
@Override
public Integer call() throws Exception { // your business logic goes here...
String serverUrl = "https://" + server;
String cookie = retrieveCookie(serverUrl);
System.out.println(cookie);
return 0;
}
private String retrieveCookie(String url) throws XdgOpenSaml.CannotRetrieveException, InterruptedException,
IOException, ExecutionException, TimeoutException {
InetAddress localAddress = InetAddress.getByName("127.0.0.1");
HttpServer server = HttpServer.create(new InetSocketAddress(localAddress, port), 0);
CompletableFuture<String> cookieResult = new CompletableFuture<>();
try {
server.createContext("/", new CookieRetrieverHttpHandler(cookieResult, url));
server.start();
Runtime.getRuntime().exec(new String[] { "xdg-open",
url + "/remote/saml/start?redirect=1" + realm.map(r -> "&realm=" + r).orElse("") });
return cookieResult.get(5, TimeUnit.MINUTES);
} finally {
server.stop(1);
}
}
private final class CookieRetrieverHttpHandler implements HttpHandler {
private final CompletableFuture<String> cookieResult;
private final String url;
private CookieRetrieverHttpHandler(CompletableFuture<String> cookieResult, String url) {
this.cookieResult = cookieResult;
this.url = url;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
try {
extractId(exchange.getRequestURI().getQuery()).ifPresentOrElse(id -> {
String cookie = retrieveCookieFromId(id);
sendResponse(exchange, 200,
XdgOpenSaml.class.getSimpleName() + " Retrieved Cookie! Connecting ...");
cookieResult.complete(cookie);
}, () -> {
String errorMessage = "ERROR: Redirect does not contain \"" + ID_PARAMETER_NAME + "\" parameter "
+ exchange.getRequestURI();
sendResponse(exchange, 500, errorMessage);
cookieResult.completeExceptionally(new CannotRetrieveException(errorMessage));
});
} catch (Exception e) {
sendResponse(exchange, 500, e.getMessage());
cookieResult.completeExceptionally(e);
}
}
private Optional<String> extractId(String requestQuery) {
return Arrays.stream(requestQuery.split("&")).filter(s -> s.startsWith(ID_PARAMETER_NAME)).findAny()
.map(s -> s.substring(s.indexOf('=')));
}
private void sendResponse(HttpExchange exchange, int code, String message) {
try {
exchange.sendResponseHeaders(code, message.length());
try (OutputStream stream = exchange.getResponseBody()) {
stream.write(message.getBytes());
}
} catch (IOException e) {
// error in sending response to browser try to not fail token retrieve
e.printStackTrace();
}
}
private String retrieveCookieFromId(String id) {
try {
HttpRequest httpRequest = HttpRequest.newBuilder().uri(new URI(url + "/remote/saml/auth_id?id=" + id))
.GET().build();
SSLContext sslContext = SSLContext.getDefault();
if (trustAllCertificate) {
sslContext = SSLContext.getInstance("TLS");
sslContext.init(null, new TrustManager[] { noTrustManager }, new SecureRandom());
}
HttpClient client = HttpClient.newBuilder().sslContext(sslContext).build();
HttpResponse<Stream<String>> response = client.send(httpRequest, BodyHandlers.ofLines());
if (response.statusCode() < 400) {
return extractCookie(response).orElseThrow(() -> new CannotRetrieveException(
"Missing " + COOKIE_NAME + " in response " + response.toString()));
} else {
throw new CannotRetrieveException("Error retrieving Cookie [" + response.statusCode() + "]\"\n"
+ response.body().collect(Collectors.joining("\n")));
}
} catch (Exception e) {
throw sneakyThrow(e);
}
}
private Optional<String> extractCookie(HttpResponse<Stream<String>> response) {
return response.headers().allValues("set-cookie").stream().filter(s -> s.startsWith(COOKIE_NAME)).findAny()
.map(s -> s.split(";")[0]);
}
}
private final static class CannotRetrieveException extends Exception {
public CannotRetrieveException(String message) {
super(message);
}
}
@SuppressWarnings("unchecked")
public static <E extends Throwable> E sneakyThrow(Throwable e) throws E {
throw (E) e;
}
private static X509ExtendedTrustManager createNoTrustManager() {
return new X509ExtendedTrustManager() {
@Override
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] {};
}
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType) {
}
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType, final Socket socket) {
}
@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType, final Socket socket) {
}
@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType,
final SSLEngine engine) {
}
@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType,
final SSLEngine engine) {
}
@Override
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
};
}
}