]> WPIA git - gigi.git/blob - tests/club/wpia/gigi/testUtils/ManagedTest.java
add: ensure that for support actions certificate login is used
[gigi.git] / tests / club / wpia / gigi / testUtils / ManagedTest.java
1 package club.wpia.gigi.testUtils;
2
3 import static org.junit.Assert.*;
4
5 import java.io.BufferedReader;
6 import java.io.DataOutputStream;
7 import java.io.IOException;
8 import java.io.InputStreamReader;
9 import java.io.OutputStream;
10 import java.io.UnsupportedEncodingException;
11 import java.net.HttpURLConnection;
12 import java.net.InetSocketAddress;
13 import java.net.MalformedURLException;
14 import java.net.Socket;
15 import java.net.URL;
16 import java.net.URLConnection;
17 import java.net.URLEncoder;
18 import java.nio.file.Files;
19 import java.nio.file.Paths;
20 import java.security.GeneralSecurityException;
21 import java.security.KeyManagementException;
22 import java.security.NoSuchAlgorithmException;
23 import java.security.Principal;
24 import java.security.PrivateKey;
25 import java.security.cert.X509Certificate;
26 import java.sql.SQLException;
27 import java.util.Locale;
28 import java.util.Properties;
29 import java.util.regex.Matcher;
30 import java.util.regex.Pattern;
31
32 import javax.net.ssl.HttpsURLConnection;
33 import javax.net.ssl.KeyManager;
34 import javax.net.ssl.SSLContext;
35 import javax.net.ssl.X509KeyManager;
36
37 import org.hamcrest.CoreMatchers;
38 import org.junit.After;
39 import org.junit.AfterClass;
40 import org.junit.BeforeClass;
41
42 import club.wpia.gigi.DevelLauncher;
43 import club.wpia.gigi.GigiApiException;
44 import club.wpia.gigi.database.GigiPreparedStatement;
45 import club.wpia.gigi.database.GigiResultSet;
46 import club.wpia.gigi.dbObjects.Certificate;
47 import club.wpia.gigi.dbObjects.EmailAddress;
48 import club.wpia.gigi.dbObjects.Group;
49 import club.wpia.gigi.dbObjects.Job;
50 import club.wpia.gigi.dbObjects.ObjectCache;
51 import club.wpia.gigi.dbObjects.User;
52 import club.wpia.gigi.pages.account.MyDetails;
53 import club.wpia.gigi.pages.main.RegisterPage;
54 import club.wpia.gigi.testUtils.TestEmailReceiver.TestMail;
55 import club.wpia.gigi.util.SimpleSigner;
56
57 /**
58  * Base class for test suites who require a launched Gigi instance. The instance
59  * is cleared once per test suite.
60  */
61 public class ManagedTest extends ConfiguredTest {
62
63     static {
64         System.setProperty("sun.net.http.allowRestrictedHeaders", "true");
65     }
66
67     private static TestEmailReceiver ter;
68
69     private static Process gigi;
70
71     private static String url = "localhost:4443";
72
73     private static String acceptLanguage = null;
74
75     protected static Certificate loginCertificate;
76
77     protected static PrivateKey loginPrivateKey;
78
79     public static void setAcceptLanguage(String acceptLanguage) {
80         ManagedTest.acceptLanguage = acceptLanguage;
81     }
82
83     public static String getServerName() {
84         return url.replaceFirst(":443$", "");
85     }
86
87     public static String getSecureServerName() {
88         return getServerName().replaceAll("^www\\.", "secure.");
89     }
90
91     static {
92         InitTruststore.run();
93         HttpURLConnection.setFollowRedirects(false);
94     }
95
96     @BeforeClass
97     public static void initEnvironmentHook() {
98         initEnvironment();
99     }
100
101     private static boolean inited = false;
102
103     public static Properties initEnvironment() {
104         try {
105             Properties mainProps = ConfiguredTest.initEnvironment();
106             if (inited) {
107                 return mainProps;
108             }
109             inited = true;
110             url = testProps.getProperty("name.www") + ":" + testProps.getProperty("serverPort.https");
111             purgeDatabase();
112             String type = testProps.getProperty("type");
113             generateMainProps(mainProps);
114             if (type.equals("local")) {
115                 String[] parts = testProps.getProperty("mail").split(":", 2);
116                 ter = new TestEmailReceiver(new InetSocketAddress(parts[0], Integer.parseInt(parts[1])));
117                 ter.start();
118                 if (testProps.getProperty("withSigner", "false").equals("true")) {
119                     SimpleSigner.runSigner();
120                 }
121                 return mainProps;
122             }
123             gigi = Runtime.getRuntime().exec(testProps.getProperty("java"));
124             DataOutputStream toGigi = new DataOutputStream(gigi.getOutputStream());
125             System.out.println("... starting server");
126
127             byte[] cacerts = Files.readAllBytes(Paths.get("config/cacerts.jks"));
128             byte[] keystore = Files.readAllBytes(Paths.get("config/keystore.pkcs12"));
129
130             DevelLauncher.writeGigiConfig(toGigi, "changeit".getBytes("UTF-8"), "changeit".getBytes("UTF-8"), mainProps, cacerts, keystore);
131             toGigi.flush();
132
133             final BufferedReader br = new BufferedReader(new InputStreamReader(gigi.getErrorStream(), "UTF-8"));
134             String line;
135             while ((line = br.readLine()) != null && !line.contains("System successfully started.")) {
136                 System.err.println(line);
137             }
138             new Thread() {
139
140                 @Override
141                 public void run() {
142                     String line;
143                     try {
144                         while ((line = br.readLine()) != null) {
145                             System.err.println(line);
146                         }
147                     } catch (IOException e) {
148                         e.printStackTrace();
149                     }
150                 }
151             }.start();
152             if (line == null) {
153                 throw new Error("Server startup failed");
154             }
155             ter = new TestEmailReceiver(new InetSocketAddress("localhost", 8473));
156             ter.start();
157             SimpleSigner.runSigner();
158             return mainProps;
159         } catch (IOException e) {
160             throw new Error(e);
161         } catch (SQLException e1) {
162             throw new Error(e1);
163         } catch (InterruptedException e) {
164             throw new Error(e);
165         }
166
167     }
168
169     protected static void await(Job j) throws InterruptedException {
170         SimpleSigner.ping();
171         j.waitFor(5000);
172     }
173
174     public static void purgeDatabase() throws SQLException, IOException {
175         purgeOnlyDB();
176         if (gigi != null) {
177             clearCaches();
178         }
179     }
180
181     public static void clearCaches() throws IOException {
182         ObjectCache.clearAllCaches();
183         // String type = testProps.getProperty("type");
184         URL u = new URL("https://" + getServerName() + "/manage");
185         URLConnection connection = u.openConnection();
186         connection.getHeaderField("Location");
187         connection.getInputStream().close();
188     }
189
190     private static void generateMainProps(Properties mainProps) {
191         mainProps.setProperty("testrunner", "true");
192         mainProps.setProperty("host", "127.0.0.1");
193
194         mainProps.setProperty("emailProvider", "club.wpia.gigi.email.TestEmailProvider");
195         mainProps.setProperty("emailProvider.port", "8473");
196         mainProps.setProperty("sql.driver", testProps.getProperty("sql.driver"));
197         mainProps.setProperty("sql.url", testProps.getProperty("sql.url"));
198         mainProps.setProperty("sql.user", testProps.getProperty("sql.user"));
199         mainProps.setProperty("sql.password", testProps.getProperty("sql.password"));
200         mainProps.setProperty("testing", "true");
201     }
202
203     @AfterClass
204     public static void tearDownServer() {
205         String type = testProps.getProperty("type");
206         ter.destroy();
207         if (type.equals("local")) {
208             if (testProps.getProperty("withSigner", "false").equals("true")) {
209                 try {
210                     SimpleSigner.stopSigner();
211                 } catch (InterruptedException e) {
212                     e.printStackTrace();
213                 }
214             }
215             inited = false;
216             return;
217         }
218         gigi.destroy();
219         try {
220             SimpleSigner.stopSigner();
221         } catch (InterruptedException e) {
222             e.printStackTrace();
223         }
224         inited = false;
225     }
226
227     public final String uniq = createUniqueName();
228
229     @After
230     public void removeMails() {
231         ter.reset();
232     }
233
234     @After
235     public void clearAcceptLanguage() {
236         ManagedTest.setAcceptLanguage(null);
237     }
238
239     @Override
240     public MailReceiver getMailReceiver() {
241         return ter;
242     }
243
244     public static String runRegister(String param) throws IOException {
245         URL regist = new URL("https://" + getServerName() + RegisterPage.PATH);
246         HttpURLConnection uc = (HttpURLConnection) regist.openConnection();
247         HttpURLConnection csrfConn = (HttpURLConnection) regist.openConnection();
248         if (acceptLanguage != null) {
249             csrfConn.setRequestProperty("Accept-Language", acceptLanguage);
250             uc.setRequestProperty("Accept-Language", acceptLanguage);
251         }
252
253         String headerField = csrfConn.getHeaderField("Set-Cookie");
254         headerField = stripCookie(headerField);
255
256         String csrf = getCSRF(csrfConn);
257         uc.addRequestProperty("Cookie", headerField);
258         uc.setDoOutput(true);
259         uc.getOutputStream().write((param + "&csrf=" + csrf).getBytes("UTF-8"));
260         if (uc.getResponseCode() == 302) {
261             return "";
262         }
263         String d = IOUtils.readURL(uc);
264         return d;
265     }
266
267     public static org.hamcrest.Matcher<String> hasError() {
268         return CoreMatchers.containsString("<div class='alert alert-danger error-msgs'>");
269     }
270
271     public static org.hamcrest.Matcher<String> hasNoError() {
272         return CoreMatchers.not(hasError());
273     }
274
275     public static String fetchStartErrorMessage(String d) throws IOException {
276         String formFail = "<div class='alert alert-danger error-msgs'>";
277         int idx = d.indexOf(formFail);
278         if (idx == -1) {
279             return null;
280         }
281         String startError = d.substring(idx + formFail.length(), idx + formFail.length() + 150).trim();
282         return startError;
283     }
284
285     public static void registerUser(String firstName, String lastName, String email, String password) {
286         try {
287             String query = "name-type=western&fname=" + URLEncoder.encode(firstName, "UTF-8") + "&lname=" + URLEncoder.encode(lastName, "UTF-8") + "&email=" + URLEncoder.encode(email, "UTF-8") + "&pword1=" + URLEncoder.encode(password, "UTF-8") + "&pword2=" + URLEncoder.encode(password, "UTF-8") + "&day=1&month=1&year=1910&tos_agree=1&dp_agree=1";
288             String data = fetchStartErrorMessage(runRegister(query));
289             assertNull(data);
290         } catch (UnsupportedEncodingException e) {
291             throw new Error(e);
292         } catch (IOException e) {
293             throw new Error(e);
294         }
295     }
296
297     public static int createVerifiedUser(String firstName, String lastName, String email, String password) {
298         registerUser(firstName, lastName, email, password);
299         try {
300             ter.receive(email).verify();
301
302             try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT `id` FROM `users` WHERE `email`=?")) {
303                 ps.setString(1, email);
304
305                 GigiResultSet rs = ps.executeQuery();
306                 if (rs.next()) {
307                     return rs.getInt(1);
308                 }
309             }
310
311             throw new Error();
312         } catch (IOException e) {
313             throw new Error(e);
314         }
315     }
316
317     public static void grant(User u, Group g) throws IOException, GigiApiException {
318         u.grantGroup(getSupporter(), g);
319         clearCaches();
320     }
321
322     /**
323      * Creates a new user with 100 Verification Points given by an (invalid)
324      * verification.
325      * 
326      * @param firstName
327      *            the first name
328      * @param lastName
329      *            the last name
330      * @param email
331      *            the email
332      * @param password
333      *            the password
334      * @return a new userid.
335      */
336     public static int createVerificationUser(String firstName, String lastName, String email, String password) {
337         int uid = createVerifiedUser(firstName, lastName, email, password);
338
339         makeAgent(uid);
340
341         return uid;
342     }
343
344     protected static String stripCookie(String headerField) {
345         return headerField.substring(0, headerField.indexOf(';'));
346     }
347
348     public static final String SECURE_REFERENCE = MyDetails.PATH;
349
350     public static boolean isLoggedin(String cookie) throws IOException {
351         URL u = new URL("https://" + getServerName() + SECURE_REFERENCE);
352         HttpURLConnection huc = (HttpURLConnection) u.openConnection();
353         huc.addRequestProperty("Cookie", cookie);
354         return huc.getResponseCode() == 200;
355     }
356
357     public static String login(String email, String pw) throws IOException {
358         URL u = new URL("https://" + getServerName() + "/login");
359         HttpURLConnection huc = (HttpURLConnection) u.openConnection();
360
361         String csrf = getCSRF(huc);
362         String headerField = stripCookie(huc.getHeaderField("Set-Cookie"));
363
364         huc = (HttpURLConnection) u.openConnection();
365         cookie(huc, headerField);
366         huc.setDoOutput(true);
367         OutputStream os = huc.getOutputStream();
368         String data = "username=" + URLEncoder.encode(email, "UTF-8") + "&password=" + URLEncoder.encode(pw, "UTF-8") + "&csrf=" + URLEncoder.encode(csrf, "UTF-8");
369         os.write(data.getBytes("UTF-8"));
370         os.flush();
371         headerField = huc.getHeaderField("Set-Cookie");
372         if (headerField == null) {
373             return "";
374         }
375         if (huc.getResponseCode() != 302) {
376             fail(fetchStartErrorMessage(IOUtils.readURL(huc)));
377         }
378         return stripCookie(headerField);
379     }
380
381     public static String login(final PrivateKey pk, final X509Certificate ce) throws NoSuchAlgorithmException, KeyManagementException, IOException, MalformedURLException {
382
383         HttpURLConnection connection = (HttpURLConnection) new URL("https://" + getSecureServerName() + "/login").openConnection();
384         authenticateClientCert(pk, ce, connection);
385         if (connection.getResponseCode() == 302) {
386             assertEquals("https://" + getSecureServerName() + "/", connection.getHeaderField("Location").replaceFirst(":443$", ""));
387             return stripCookie(connection.getHeaderField("Set-Cookie"));
388         } else {
389             return null;
390         }
391     }
392
393     public static void authenticateClientCert(final PrivateKey pk, final X509Certificate ce, HttpURLConnection connection) throws NoSuchAlgorithmException, KeyManagementException {
394         KeyManager km = new X509KeyManager() {
395
396             @Override
397             public String chooseClientAlias(String[] arg0, Principal[] arg1, Socket arg2) {
398                 return "client";
399             }
400
401             @Override
402             public String chooseServerAlias(String arg0, Principal[] arg1, Socket arg2) {
403                 return null;
404             }
405
406             @Override
407             public X509Certificate[] getCertificateChain(String arg0) {
408                 return new X509Certificate[] {
409                         ce
410                 };
411             }
412
413             @Override
414             public String[] getClientAliases(String arg0, Principal[] arg1) {
415                 return new String[] {
416                         "client"
417                 };
418             }
419
420             @Override
421             public PrivateKey getPrivateKey(String arg0) {
422                 if (arg0.equals("client")) {
423                     return pk;
424                 }
425                 return null;
426             }
427
428             @Override
429             public String[] getServerAliases(String arg0, Principal[] arg1) {
430                 return new String[] {
431                         "client"
432                 };
433             }
434         };
435         SSLContext sc = SSLContext.getInstance("TLS");
436         sc.init(new KeyManager[] {
437                 km
438         }, null, null);
439         if (connection instanceof HttpsURLConnection) {
440             ((HttpsURLConnection) connection).setSSLSocketFactory(sc.getSocketFactory());
441         }
442     }
443
444     public static String getCSRF(URLConnection u) throws IOException {
445         return getCSRF(u, 0);
446     }
447
448     public static String getCSRF(URLConnection u, int formIndex) throws IOException {
449         String content = IOUtils.readURL(u);
450         return getCSRF(formIndex, content);
451     }
452
453     public static String getCSRF(int formIndex, String content) throws Error {
454         Pattern p = Pattern.compile("<input type='hidden' name='csrf' value='([^']+)'>");
455         Matcher m = p.matcher(content);
456         for (int i = 0; i < formIndex + 1; i++) {
457             if ( !m.find()) {
458                 throw new Error("No CSRF Token:\n" + content);
459             }
460         }
461         return m.group(1);
462     }
463
464     public static String executeBasicWebInteraction(String cookie, String path, String query) throws MalformedURLException, UnsupportedEncodingException, IOException {
465         return executeBasicWebInteraction(cookie, path, query, 0);
466     }
467
468     public static String executeBasicWebInteraction(String cookie, String path, String query, int formIndex) throws IOException, MalformedURLException, UnsupportedEncodingException {
469         HttpURLConnection uc = post(cookie, path, query, formIndex);
470         if (uc.getResponseCode() == 302) {
471             return null;
472         }
473         String error = fetchStartErrorMessage(IOUtils.readURL(uc));
474         return error;
475     }
476
477     public static HttpURLConnection post(String cookie, String path, String query, int formIndex) throws IOException, MalformedURLException, UnsupportedEncodingException {
478         String server = getServerName();
479         if (loginCertificate != null) {
480             server = getSecureServerName();
481         }
482         URLConnection uc = new URL("https://" + server + path).openConnection();
483         authenticate((HttpURLConnection) uc, cookie);
484         String csrf = getCSRF(uc, formIndex);
485
486         uc = new URL("https://" + server + path).openConnection();
487         authenticate((HttpURLConnection) uc, cookie);
488         uc.setDoOutput(true);
489         OutputStream os = uc.getOutputStream();
490         os.write(("csrf=" + URLEncoder.encode(csrf, "UTF-8") + "&" //
491                 + query//
492         ).getBytes("UTF-8"));
493         os.flush();
494         return (HttpURLConnection) uc;
495     }
496
497     public static HttpURLConnection get(String cookie, String path) throws IOException {
498         String server = getServerName();
499         if (loginCertificate != null) {
500             server = getSecureServerName();
501         }
502         URLConnection uc = new URL("https://" + server + path).openConnection();
503         authenticate((HttpURLConnection) uc, cookie);
504         return (HttpURLConnection) uc;
505     }
506
507     public EmailAddress createVerifiedEmail(User u) throws InterruptedException, GigiApiException {
508         return createVerifiedEmail(u, createUniqueName() + "test@test.tld");
509     }
510
511     public EmailAddress createVerifiedEmail(User u, String email) throws InterruptedException, GigiApiException {
512         EmailAddress addr = new EmailAddress(u, email, Locale.ENGLISH);
513         TestMail testMail = getMailReceiver().receive(addr.getAddress());
514         String hash = testMail.extractLink().substring(testMail.extractLink().lastIndexOf('=') + 1);
515         addr.verify(hash);
516         getMailReceiver().assertEmpty();
517         return addr;
518     }
519
520     public static URLConnection cookie(URLConnection openConnection, String cookie) {
521         openConnection.setRequestProperty("Cookie", cookie);
522         return openConnection;
523     }
524
525     private static User supporter;
526
527     public static User getSupporter() throws GigiApiException, IOException {
528         if (supporter != null) {
529             return supporter;
530         }
531         int i = createVerifiedUser("fn", "ln", createUniqueName() + "@email.com", TEST_PASSWORD);
532         try (GigiPreparedStatement ps = new GigiPreparedStatement("INSERT INTO `user_groups` SET `user`=?, `permission`=?::`userGroup`, `grantedby`=?")) {
533             ps.setInt(1, i);
534             ps.setString(2, Group.SUPPORTER.getDBName());
535             ps.setInt(3, i);
536             ps.execute();
537         }
538         clearCaches();
539         supporter = User.getById(i);
540         return supporter;
541     }
542
543     protected static void authenticate(HttpURLConnection uc, String cookie) throws IOException {
544         uc.addRequestProperty("Cookie", cookie);
545         if (loginCertificate != null) {
546             try {
547                 authenticateClientCert(loginPrivateKey, loginCertificate.cert(), uc);
548             } catch (GeneralSecurityException | GigiApiException e) {
549                 throw new IOException(e);
550             }
551         }
552     }
553 }