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