]> WPIA git - gigi.git/commitdiff
Merge "chg: format show history link"
authorLucas Werkmeister <mail@lucaswerkmeister.de>
Thu, 4 Jan 2018 21:47:57 +0000 (22:47 +0100)
committerGerrit Code Review <gigi-system@dogcraft.de>
Thu, 4 Jan 2018 21:47:57 +0000 (22:47 +0100)
39 files changed:
config/generateTruststoreNRE.sh
doc/jenkinsJob/config.xml
doc/scripts/genchangelog [new file with mode: 0755]
src/club/wpia/gigi/Gigi.java
src/club/wpia/gigi/database/DatabaseConnection.java
src/club/wpia/gigi/database/tableStructure.sql
src/club/wpia/gigi/database/upgrade/from_34.sql [new file with mode: 0644]
src/club/wpia/gigi/database/upgrade/from_35.sql [new file with mode: 0644]
src/club/wpia/gigi/dbObjects/CertificateOwner.java
src/club/wpia/gigi/dbObjects/Domain.java
src/club/wpia/gigi/dbObjects/DomainPingConfiguration.java
src/club/wpia/gigi/dbObjects/DomainPingExecution.java
src/club/wpia/gigi/dbObjects/Organisation.java
src/club/wpia/gigi/dbObjects/User.java
src/club/wpia/gigi/output/template/Template.java
src/club/wpia/gigi/pages/LoginPage.templ
src/club/wpia/gigi/pages/PasswordResetForm.templ
src/club/wpia/gigi/pages/account/ChangePasswordForm.templ
src/club/wpia/gigi/pages/account/certs/CertificateIssueForm.templ
src/club/wpia/gigi/pages/account/domain/DomainPinglogForm.java
src/club/wpia/gigi/pages/account/mail/MailAddForm.templ
src/club/wpia/gigi/pages/admin/support/SupportEnterTicketForm.java
src/club/wpia/gigi/pages/main/Signup.java
src/club/wpia/gigi/pages/main/Signup.templ
src/club/wpia/gigi/ping/DNSPinger.java
src/club/wpia/gigi/ping/DomainPinger.java
src/club/wpia/gigi/ping/EmailPinger.java
src/club/wpia/gigi/ping/HTTPFetch.java
src/club/wpia/gigi/ping/PingFailedWithActiveCertificates.templ [new file with mode: 0644]
src/club/wpia/gigi/ping/PingerDaemon.java
src/club/wpia/gigi/ping/SSLPinger.java
src/club/wpia/gigi/util/CalendarUtil.java
src/club/wpia/gigi/util/PasswordStrengthChecker.java
tests/club/wpia/gigi/pages/admin/TestSEAdminTicketSetting.java
tests/club/wpia/gigi/pages/main/RegisterPageTest.java
tests/club/wpia/gigi/ping/TestTiming.java [new file with mode: 0644]
tests/club/wpia/gigi/testUtils/ConfiguredTest.java
tests/club/wpia/gigi/testUtils/PingTest.java
util-testing/club/wpia/gigi/pages/Manager.java

index fbc5e6ed5c681ec8a85c41dfdb3a6587d602f38b..23b7b9895aadb174e1592bd260f87bb82af9b80a 100755 (executable)
@@ -30,6 +30,13 @@ cabasename=assured
 caname=${cabasename}_$(date +%Y)_1
 ca=../signer/ca/$caname/ca
 if [[ -f "$ca.key" ]] && ! [[ -f keystore.pkcs12 ]]; then
+    if [[ -f serial_base ]]; then
+        serial_base=$(< serial_base)
+    else
+        serial_base=100000
+    fi
+    serial_base=$((serial_base + 1))
+    printf '%d\n' "$serial_base" >| serial_base
     # when the domain is provided externally as environment variable, use it and do not prompt for it.
     [[ -z $DOMAIN ]] && read -rp "I need to generate gigi-certificates. I need your base domain: " DOMAIN
     # Assuming we have access to the CA-keys we generate two certificates and present them to gigi
@@ -39,9 +46,9 @@ if [[ -f "$ca.key" ]] && ! [[ -f keystore.pkcs12 ]]; then
     openssl req -newkey rsa:2048 -keyout www.key -out www.csr -nodes -subj "/CN=gigi server certificate"
     openssl req -newkey rsa:2048 -keyout mail.key -out mail.csr -nodes -subj "/CN=gigi system"
 
-    # Sign the two requests with the keys in the config of the simple signer. Use serials 1000001 and 1000002 to probably not collide with the "simple signer"
-    openssl x509 -req -in www.csr -out www.crt -CA $ca.crt -CAkey $ca.key -set_serial 1000001 -extfile <(printf "[ext]\nsubjectAltName=DNS:www.$DOMAIN,DNS:secure.$DOMAIN,DNS:static.$DOMAIN,DNS:api.$DOMAIN\nbasicConstraints=CA:FALSE\nextendedKeyUsage=serverAuth\nkeyUsage=digitalSignature,keyEncipherment\n") -extensions ext
-    openssl x509 -req -in mail.csr -out mail.crt -CA $ca.crt -CAkey $ca.key -set_serial 1000002 -extfile <(printf "[ext]\nsubjectAltName=email:support@$DOMAIN\nbasicConstraints=CA:FALSE\nextendedKeyUsage=emailProtection\nkeyUsage=digitalSignature,keyEncipherment\n") -extensions ext
+    # Sign the two requests with the keys in the config of the simple signer. Use the serial_base with extensions 1 and 2. These serials are long enough to probably not collide with the "simple signer"
+    openssl x509 -req -in www.csr -out www.crt -CA $ca.crt -CAkey $ca.key -set_serial ${serial_base}1 -extfile <(printf '[ext]\nsubjectAltName=DNS:www.%s,DNS:secure.%s,DNS:static.%s,DNS:api.%s\nbasicConstraints=CA:FALSE\nextendedKeyUsage=serverAuth\nkeyUsage=digitalSignature,keyEncipherment\n' "$DOMAIN" "$DOMAIN" "$DOMAIN" "$DOMAIN") -extensions ext
+    openssl x509 -req -in mail.csr -out mail.crt -CA $ca.crt -CAkey $ca.key -set_serial ${serial_base}2 -extfile <(printf '[ext]\nsubjectAltName=email:support@%s\nbasicConstraints=CA:FALSE\nextendedKeyUsage=emailProtection\nkeyUsage=digitalSignature,keyEncipherment\n' "$DOMAIN") -extensions ext
 
     # Store the webserver cert in 4 different pkcs12-keystores to have different "key aliases" and import them all into the "keystore.pkcs12" using the "importP"-method
     for t in www api secure static; do
index 00a23c16bcdd2a59de278a23698d84b457e268b3..852e725d9a44def7dcb6456b54609b575120651a 100644 (file)
           <name>BRANCH</name>
           <description>The branch to build from.</description>
           <defaultValue>$GERRIT_PATCHSET_REVISION</defaultValue>
+          <trim>false</trim>
         </hudson.model.StringParameterDefinition>
         <hudson.model.StringParameterDefinition>
           <name>JAVA_HOME</name>
           <description></description>
           <defaultValue>/usr/lib/jvm/openjdk-8-jdk-gigi</defaultValue>
+          <trim>false</trim>
         </hudson.model.StringParameterDefinition>
       </parameterDefinitions>
     </hudson.model.ParametersDefinitionProperty>
@@ -220,7 +222,9 @@ EOT
 localePath=$$$$LOCALE_FETCH_PATH$$$$</properties>
     </hudson.tasks.Ant>
     <hudson.tasks.Shell>
-      <command>cd gigi
+      <command>rm -v *.deb
+cd gigi
+[ -f doc/scripts/genchangelog ] &amp;&amp; doc/scripts/genchangelog
 dpkg-buildpackage -b -us -uc</command>
     </hudson.tasks.Shell>
   </builders>
diff --git a/doc/scripts/genchangelog b/doc/scripts/genchangelog
new file mode 100755 (executable)
index 0000000..4a4e716
--- /dev/null
@@ -0,0 +1,9 @@
+#!/bin/bash
+cd "$(git rev-parse --show-toplevel)"
+cat > debian/changelog <<EOF
+wpia-gigi ($(git describe HEAD --tags --match "v*" | sed "s/^v//")) unstable; urgency=low
+
+  * Initial release
+
+ -- WPIA Software Team <software@wpia.club>  $(git show HEAD --pretty=format:%aD --no-patch)
+EOF
index 2c61c0717d38492a81101b9ba834a532bb2eab23..00993b52aa06fa889cc4185207ca87778479a2a2 100644 (file)
@@ -147,11 +147,11 @@ public final class Gigi extends HttpServlet {
             Menu certificates = createMenu("Certificates");
             putPage(Certificates.PATH + "/*", new Certificates(false), certificates);
             putPage(CertificateAdd.PATH, new CertificateAdd(), certificates);
-            putPage(MailOverview.DEFAULT_PATH, new MailOverview(), certificates);
-            putPage(DomainOverview.PATH, new DomainOverview(), certificates);
-            putPage(EditDomain.PATH + "*", new EditDomain(), null);
 
             Menu wot = createMenu("Verification");
+            putPage(MailOverview.DEFAULT_PATH, new MailOverview(), wot);
+            putPage(DomainOverview.PATH, new DomainOverview(), wot);
+            putPage(EditDomain.PATH + "*", new EditDomain(), null);
             putPage(VerifyPage.PATH + "/*", new VerifyPage(), wot);
             putPage(Points.PATH, new Points(false), wot);
             putPage(RequestTTPPage.PATH, new RequestTTPPage(), wot);
index 10e81e79df1f1119a05fc5e86606c7c794585c33..04351d1a38e8ab2af427c6f3c1473a5dfcac322a 100644 (file)
@@ -181,7 +181,7 @@ public class DatabaseConnection {
 
     }
 
-    public static final int CURRENT_SCHEMA_VERSION = 34;
+    public static final int CURRENT_SCHEMA_VERSION = 36;
 
     public static final int CONNECTION_TIMEOUT = 24 * 60 * 60;
 
index 440bdec788db8fee8de4e8c532dd4b6e439dd97f..8c697f78a93376352554274e245d03e40adbe21d 100644 (file)
@@ -103,9 +103,11 @@ CREATE TABLE "domainPinglog" (
   "configId" int NOT NULL,
   "state" "pingState" NOT NULL,
   "challenge" varchar(16),
-  "result" varchar(255)
+  "result" varchar(255),
+  "needsAction" boolean DEFAULT false
 );
 CREATE INDEX ON "domainPinglog" ("configId","when");
+CREATE INDEX ON "domainPinglog" ("when", "needsAction");
 
 DROP TABLE IF EXISTS "baddomains";
 CREATE TABLE "baddomains" (
@@ -113,16 +115,6 @@ CREATE TABLE "baddomains" (
 );
 
 
-DROP TABLE IF EXISTS "alerts";
-CREATE TABLE "alerts" (
-  "memid" int NOT NULL DEFAULT '0',
-  "general" boolean NOT NULL DEFAULT 'false',
-  "country" boolean NOT NULL DEFAULT 'false',
-  "regional" boolean NOT NULL DEFAULT 'false',
-  "radius" boolean NOT NULL DEFAULT 'false',
-  PRIMARY KEY ("memid")
-);
-
 DROP TABLE IF EXISTS "user_agreements";
 CREATE TABLE "user_agreements" (
   "id" serial NOT NULL,
@@ -378,7 +370,7 @@ CREATE TABLE "schemeVersion" (
   "version" smallint NOT NULL,
   PRIMARY KEY ("version")
 );
-INSERT INTO "schemeVersion" (version)  VALUES(34);
+INSERT INTO "schemeVersion" (version)  VALUES(36);
 
 DROP TABLE IF EXISTS `passwordResetTickets`;
 CREATE TABLE `passwordResetTickets` (
diff --git a/src/club/wpia/gigi/database/upgrade/from_34.sql b/src/club/wpia/gigi/database/upgrade/from_34.sql
new file mode 100644 (file)
index 0000000..0c809ad
--- /dev/null
@@ -0,0 +1,2 @@
+ALTER TABLE "domainPinglog" ADD COLUMN "needsAction" boolean DEFAULT false;
+CREATE INDEX ON "domainPinglog" ("when", "needsAction");
diff --git a/src/club/wpia/gigi/database/upgrade/from_35.sql b/src/club/wpia/gigi/database/upgrade/from_35.sql
new file mode 100644 (file)
index 0000000..53dcc4d
--- /dev/null
@@ -0,0 +1 @@
+DROP TABLE IF EXISTS "alerts";
index f608b2fc01bea94610a6e826af84bed7d573305e..007d98d3a1092c7d5401c9ba3014f0e676f62bef 100644 (file)
@@ -24,7 +24,15 @@ public abstract class CertificateOwner implements IdCachable, Serializable {
         this.id = id;
     }
 
-    protected CertificateOwner() {
+    /**
+     * This constructor has a dummy parameter to allow callers to do checks
+     * before invoking the super constructor.
+     * 
+     * @param dummy
+     *            a parameter that is not used to allow callers to do checks
+     *            before super constructor invocation.
+     */
+    protected CertificateOwner(Void dummy) {
         try (GigiPreparedStatement ps = new GigiPreparedStatement("INSERT INTO `certOwners` DEFAULT VALUES")) {
             ps.execute();
             id = ps.lastInsertId();
index e8accbcac1b545d4040bfbdbb4118403128e9c2f..9b356e6067f9d0364c32a23be5ee2f58f9628bde 100644 (file)
@@ -90,7 +90,7 @@ public class Domain implements IdCachable, Verifyable {
 
     private LinkedList<DomainPingConfiguration> configs = null;
 
-    public List<DomainPingConfiguration> getConfiguredPings() throws GigiApiException {
+    public List<DomainPingConfiguration> getConfiguredPings() {
         LinkedList<DomainPingConfiguration> configs = this.configs;
         if (configs == null) {
             configs = new LinkedList<>();
@@ -143,12 +143,26 @@ public class Domain implements IdCachable, Verifyable {
         }
     }
 
+    /**
+     * Determines current domain validity. A domain is valid, iff at least two
+     * configured pings are currently successful.
+     * 
+     * @return true, iff domain is valid
+     * @throws GigiApiException
+     */
     public boolean isVerified() {
-        try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT 1 FROM `domainPinglog` INNER JOIN `pingconfig` ON `pingconfig`.`id`=`domainPinglog`.`configId` WHERE `domainid`=? AND `state`='success'")) {
-            ps.setInt(1, id);
-            GigiResultSet rs = ps.executeQuery();
-            return rs.next();
+        int count = 0;
+        boolean[] used = new boolean[DomainPingType.values().length];
+        for (DomainPingConfiguration config : getConfiguredPings()) {
+            if (config.isValid() && !used[config.getType().ordinal()]) {
+                count++;
+                used[config.getType().ordinal()] = true;
+            }
+            if (count >= 2) {
+                return true;
+            }
         }
+        return false;
     }
 
     public DomainPingExecution[] getPings() throws GigiApiException {
@@ -195,4 +209,22 @@ public class Domain implements IdCachable, Verifyable {
         }
     }
 
+    public Certificate[] fetchActiveCertificates() {
+        try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT `certs`.`id` FROM `certs` INNER JOIN `subjectAlternativeNames` ON `subjectAlternativeNames`.`certId` = `certs`.`id` WHERE (`contents`=? OR RIGHT(`contents`,LENGTH(?)+1)=CONCAT('.',?::VARCHAR)) AND `type`='DNS' AND `revoked` IS NULL AND `expire` > CURRENT_TIMESTAMP AND `memid`=? GROUP BY `certs`.`id`", true)) {
+            ps.setString(1, suffix);
+            ps.setString(2, suffix);
+            ps.setString(3, suffix);
+            ps.setInt(4, owner.getId());
+            GigiResultSet rs = ps.executeQuery();
+            rs.last();
+            Certificate[] res = new Certificate[rs.getRow()];
+            rs.beforeFirst();
+            int i = 0;
+            while (rs.next()) {
+                res[i++] = Certificate.getById(rs.getInt(1));
+            }
+            return res;
+        }
+    }
+
 }
index bdaad75880a5acb1e0c9dd09428a9be7597df19e..59db66cda5e1623f3401537b0d208ae05c175584 100644 (file)
@@ -1,5 +1,6 @@
 package club.wpia.gigi.dbObjects;
 
+import java.sql.Timestamp;
 import java.util.Date;
 
 import club.wpia.gigi.Gigi;
@@ -92,4 +93,47 @@ public class DomainPingConfiguration implements IdCachable {
         }
         throw new GigiApiException(SprintfCommand.createSimple("Reping is only allowed after {0} minutes, yours end at {1}.", REPING_MINIMUM_DELAY / 60 / 1000, new Date(lastExecution.getTime() + REPING_MINIMUM_DELAY)));
     }
+
+    /**
+     * Return true when there was a last execution and it succeeded.
+     * 
+     * @return if this ping is currently valid.
+     */
+    public boolean isValid() {
+        try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT state='success' AS bool from `domainPinglog` WHERE `configId`=? ORDER BY `when` DESC LIMIT 1")) {
+            ps.setInt(1, id);
+            GigiResultSet rs = ps.executeQuery();
+            if ( !rs.next()) {
+                return false;
+            }
+            return rs.getBoolean(1);
+        }
+    }
+
+    /**
+     * Return true when this ping has not been successful within the last 2
+     * weeks.
+     * 
+     * @param time
+     *            the point in time for which the determination is carried out.
+     * @return the value for this ping.
+     */
+    public boolean isStrictlyInvalid(Date time) {
+        Date lastSuccess = getLastSuccess();
+        if (lastSuccess.getTime() == 0) {
+            // never a successful ping
+            return true;
+        }
+        try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT `when` AS stamp from `domainPinglog` WHERE `configId`=? AND state='failed' AND `when` > ? ORDER BY `when` ASC LIMIT 1")) {
+            ps.setInt(1, id);
+            ps.setTimestamp(2, new Timestamp(lastSuccess.getTime()));
+            GigiResultSet rs = ps.executeQuery();
+            if (rs.next()) {
+                Date turnedInvalid = new Date(rs.getTimestamp("stamp").getTime());
+                // turned invalid older than 2 weeks ago
+                return turnedInvalid.getTime() < time.getTime() - 2L * 7 * 24 * 60 * 60 * 1000;
+            }
+            return false;
+        }
+    }
 }
index 174b2a182825f1f61e6b30abfa1f5b89c66c168d..0f01c1d77ca5fda3e08e2327087b0d5f3a840a69 100644 (file)
@@ -3,24 +3,26 @@ package club.wpia.gigi.dbObjects;
 import java.sql.Timestamp;
 import java.util.Date;
 
+import club.wpia.gigi.database.GigiPreparedStatement;
 import club.wpia.gigi.database.GigiResultSet;
+import club.wpia.gigi.ping.DomainPinger.PingState;
 
 public class DomainPingExecution {
 
-    private String state;
+    private final PingState state;
 
-    private String type;
+    private final String type;
 
-    private String info;
+    private final String info;
 
-    private String result;
+    private final String result;
 
-    private DomainPingConfiguration config;
+    private final DomainPingConfiguration config;
 
-    private Timestamp date;
+    private final Timestamp date;
 
-    public DomainPingExecution(GigiResultSet rs) {
-        state = rs.getString(1);
+    protected DomainPingExecution(GigiResultSet rs) {
+        state = PingState.valueOf(rs.getString(1).toUpperCase());
         type = rs.getString(2);
         info = rs.getString(3);
         result = rs.getString(4);
@@ -28,7 +30,27 @@ public class DomainPingExecution {
         date = rs.getTimestamp(6);
     }
 
-    public String getState() {
+    public DomainPingExecution(PingState state, String result, DomainPingConfiguration config, String challenge) {
+        this.state = state;
+        this.type = config.getType().getDBName();
+        this.info = config.getInfo();
+        this.result = result;
+        this.config = config;
+        this.date = new Timestamp(System.currentTimeMillis());
+        try (GigiPreparedStatement enterPingResult = new GigiPreparedStatement("INSERT INTO `domainPinglog` SET `configId`=?, `state`=?::`pingState`, `result`=?, `challenge`=?, `when`=?, `needsAction`=?")) {
+            enterPingResult.setInt(1, config.getId());
+            enterPingResult.setEnum(2, state);
+            enterPingResult.setString(3, result);
+            enterPingResult.setString(4, challenge);
+            enterPingResult.setTimestamp(5, this.date);
+            // Ping results with current state "failed" need followup action in
+            // two weeks to revoke any remaining active certificates.
+            enterPingResult.setBoolean(6, state == PingState.FAILED);
+            enterPingResult.execute();
+        }
+    }
+
+    public PingState getState() {
         return state;
     }
 
index 4ee25d0ede565e6a9187bddc71a63377462d9e95..c9754565744b0d107baee2515f802e2e2cae9fbe 100644 (file)
@@ -68,12 +68,7 @@ public class Organisation extends CertificateOwner {
     private String postalAddress;
 
     public Organisation(String name, Country country, String province, String city, String email, String optionalName, String postalAddress, User creator) throws GigiApiException {
-        if ( !creator.isInGroup(Group.ORG_AGENT)) {
-            throw new GigiApiException("Only Organisation RA Agents may create organisations.");
-        }
-        if (country == null) {
-            throw new GigiApiException("Got country code of illegal type.");
-        }
+        super(validate(creator, country));
         this.name = name;
         this.country = country;
         this.province = province;
@@ -98,6 +93,16 @@ public class Organisation extends CertificateOwner {
         }
     }
 
+    private static Void validate(User creator, Country country) throws GigiApiException {
+        if ( !creator.isInGroup(Group.ORG_AGENT)) {
+            throw new GigiApiException("Only Organisation RA Agents may create organisations.");
+        }
+        if (country == null) {
+            throw new GigiApiException("Got country code of illegal type.");
+        }
+        return null;
+    }
+
     protected Organisation(GigiResultSet rs) throws GigiApiException {
         super(rs.getInt("id"));
         name = rs.getString("name");
index cdd00d6f3533b2ba7656203a960f4014aad426d7..3c2cd6b03f284d9b8ed71c14a14c59dc3a8400dd 100644 (file)
@@ -105,10 +105,7 @@ public class User extends CertificateOwner {
     }
 
     public User(String email, String password, DayDate dob, Locale locale, Country residenceCountry, NamePart... preferred) throws GigiApiException {
-        // Avoid storing information that obviously won't get through
-        if ( !EmailProvider.isValidMailAddress(email)) {
-            throw new IllegalArgumentException("Invalid email.");
-        }
+        super(validate(email));
 
         this.email = email;
         this.dob = dob;
@@ -128,6 +125,14 @@ public class User extends CertificateOwner {
         new EmailAddress(this, email, locale);
     }
 
+    private static Void validate(String email) {
+        // Avoid storing information that obviously won't get through
+        if ( !EmailProvider.isValidMailAddress(email)) {
+            throw new IllegalArgumentException("Invalid email.");
+        }
+        return null;
+    }
+
     public Name[] getNames() {
         try (GigiPreparedStatement gps = new GigiPreparedStatement("SELECT `id` FROM `names` WHERE `uid`=? AND `deleted` IS NULL", true)) {
             gps.setInt(1, getId());
index ad53ac2ed4492ccbff4ec54598896d328fdc41b7..65db37fe8314938d3b5237c644c47a9874d6a7e8 100644 (file)
@@ -264,10 +264,14 @@ public class Template implements Outputable {
             out.print(((Boolean) s) ? l.getTranslation("yes") : l.getTranslation("no"));
         } else if (s instanceof Date) {
             SimpleDateFormat sdfUI = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
-            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
-            out.print("<time datetime=\"" + sdf.format(s) + "\">");
-            out.print(sdfUI.format(s));
-            out.print(" UTC</time>");
+            if (vars.containsKey(Outputable.OUT_KEY_PLAIN)) {
+                out.print(sdfUI.format(s));
+            } else {
+                SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+                out.print("<time datetime=\"" + sdf.format(s) + "\">");
+                out.print(sdfUI.format(s));
+                out.print(" UTC</time>");
+            }
         } else {
             out.print(s == null ? "null" : (unescaped ? s.toString() : HTMLEncoder.encodeHTML(s.toString())));
         }
index f34ffb13077ecec53400161af521fe4acfc33d21..ad3060c555e07e2d797f8335990ecd022ddfe4b6 100644 (file)
@@ -2,7 +2,7 @@
 <h1><?=_Login?></h1>
 <p class='smalltext'><?=_Warning! This site requires cookies to be enabled to ensure your privacy and security. This site uses session cookies to store temporary values to prevent people from copying and pasting the session ID to someone else exposing their account, personal details and identity theft as a result.?></p>
 <label for="username"><?=_Email Address?>:</label><input class="form-control" type='text' name="username" required/><br />
-<label for="password"><?=_Pass Phrase?>:</label><input class="form-control" type='password' name='password' required/><br />
+<label for="password"><?=_Password?>:</label><input class="form-control" type='password' name='password' required/><br />
 <button type='submit' name="process" value="Login" class="btn btn-primary"><?=_Login?></button><br /><br />
 <p class='smalltext'><?=_If you are having trouble with your username or password, please visit our !(/wiki/lostPassword)wiki page!'</a>' for more information?></p>
 </div>
index a0e46a4f347ed372854542ffc6178ff3779ce6bc..9663547bb4b07ae04aff9ff1c6811fa0f4c94673 100644 (file)
@@ -1,7 +1,7 @@
 <table class="table">
   <thead>
   <tr>
-    <th colspan="2" class="title"><?=_Change Pass Phrase?></th>
+    <th colspan="2" class="title"><?=_Change Password?></th>
   </tr>
   </thead>
   <tbody>
     <td><input type="password" name="private_token" required></td>
   </tr>
   <tr>
-    <td><?=_New Pass Phrase?><span class="formMandatory">*</span>: </td>
+    <td><?=_New Password?><span class="formMandatory">*</span>: </td>
     <td><input type="password" name="pword1" required></td>
   </tr>
   <tr>
-    <td><?=_Pass Phrase Again?><span class="formMandatory">*</span>: </td>
+    <td><?=_Password Again?><span class="formMandatory">*</span>: </td>
     <td><input type="password" name="pword2" required></td>
   </tr>
   <tr>
     <td colspan="2"><span class="formMandatory">*</span><?=_Your password is one of many factors to protect your account from unauthorised access. A good password is hard to guess, long, and contains a diverse set of characters. For the current requirements and to learn more, visit our !(/wiki/goodPassword)FAQ!'</a>'.?></td>
   </tr>
   <tr>
-    <td colspan="2"><input type="submit" name="process" value="<?=_Update Pass Phrase?>"></td>
+    <td colspan="2"><input type="submit" name="process" value="<?=_Update Password?>"></td>
   </tr>
   </tbody>
 </table>
index 0fd8f76a88069bcd91aeb136b051cdedc7bd8d6d..1e9136eaa7506127dd99af5c856f8add15c783d5 100644 (file)
@@ -1,27 +1,27 @@
 <table class="table">
   <thead>
   <tr>
-    <th colspan="2" class="title"><?=_Change Pass Phrase?></th>
+    <th colspan="2" class="title"><?=_Change Password?></th>
   </tr>
   </thead>
   <tbody>
   <tr>
-    <td><?=_Old Pass Phrase?>: </td>
+    <td><?=_Old Password?>: </td>
     <td><input class="form-control" type="password" name="oldpassword" required></td>
   </tr>
   <tr>
-    <td><?=_New Pass Phrase?><span class="formMandatory">*</span>: </td>
+    <td><?=_New Password?><span class="formMandatory">*</span>: </td>
     <td><input class="form-control" type="password" name="pword1" required></td>
   </tr>
   <tr>
-    <td><?=_Pass Phrase Again?><span class="formMandatory">*</span>: </td>
+    <td><?=_Password Again?><span class="formMandatory">*</span>: </td>
     <td><input class="form-control" type="password" name="pword2" required></td>
   </tr>
   <tr>
     <td colspan="2"><span class="formMandatory">*</span><?=_Your password is one of many factors to protect your account from unauthorised access. A good password is hard to guess, long, and contains a diverse set of characters. For the current requirements and to learn more, visit our !(/wiki/goodPassword)FAQ!'</a>'.?></td>
   </tr>
   <tr>
-    <td colspan="2"><input type="submit" name="process" value="<?=_Update Pass Phrase?>"></td>
+    <td colspan="2"><input type="submit" name="process" value="<?=_Update Password?>"></td>
   </tr>
   </tbody>
 </table>
index 7b9c535376685e66ef7fdc606090dad6bb67387c..494a7ff504bb7a5c8dda91db599f7990495bd6df 100644 (file)
@@ -48,8 +48,8 @@
       <label for='SANs'>SANs</label>
     </td>
     <td align="left">
-      <textarea class="form-control" rows='5' name='SANs' placeholder="dns:my.domain.example.com, dns:*.example.com, email:my.email@example.com (or newline separated)"><?=$emails?></textarea><br />
-      <?=_Syntax for SAN?>: dns:my.domain.example.com, dns:*.example.com, email:my.email@example.com <?=_(or newline separated)?><br />
+      <textarea class="form-control" rows='5' name='SANs' placeholder="dns:my.domain.example.org, dns:*.example.org, email:my.email@example.org (or newline separated)"><?=$emails?></textarea><br />
+      <?=_Syntax for SAN?>: dns:my.domain.example.org, dns:*.example.org, email:my.email@example.org <?=_(or newline separated)?><br />
       <?=_Recommendation for inexperienced users: only use one email address for client certificates.?>
       </td>
   </tr>
index e752557ef6567ca464baddad763a1f22e6cb7f24..63f82d57f0594b8d4a03e2b95502a789af1dcd65 100644 (file)
@@ -64,7 +64,7 @@ public class DomainPinglogForm extends Form {
                 if (counter >= pings.length) {
                     return false;
                 }
-                vars.put("state", pings[counter].getState());
+                vars.put("state", pings[counter].getState().getDBName());
                 vars.put("type", pings[counter].getType());
                 vars.put("config", pings[counter].getInfo());
                 vars.put("date", pings[counter].getDate());
index c709391a9407b0bae20050e650c56322bb7da412..a8fbcb8d9916826fe0fc42fa52a3c21240611cf3 100644 (file)
@@ -1,6 +1,6 @@
 <table class="table">
   <tr>
-    <td><?=_Email Addresses?> </td>
+    <td><?=_Email Address?> </td>
     <td><input class="form-control" type="text" name="newemail" value="" required></td>
   </tr>
   <tr>
index 0a98dfd124a58c2791fa75b303b7074b85ded05f..58b2997e11ae9b84174d3bf9a5b8886a797db6bd 100644 (file)
@@ -12,11 +12,14 @@ import club.wpia.gigi.output.template.Form;
 import club.wpia.gigi.output.template.Template;
 import club.wpia.gigi.pages.LoginPage;
 import club.wpia.gigi.util.AuthorizationContext;
+import club.wpia.gigi.util.CalendarUtil;
 
 public class SupportEnterTicketForm extends Form {
 
     private static final Template t = new Template(SupportEnterTicketForm.class.getResource("SupportEnterTicketForm.templ"));
 
+    public static final String TICKET_PREFIX = "acdhi";
+
     public SupportEnterTicketForm(HttpServletRequest hsr) {
         super(hsr);
     }
@@ -24,9 +27,9 @@ public class SupportEnterTicketForm extends Form {
     @Override
     public SubmissionResult submit(HttpServletRequest req) throws GigiApiException {
         if (req.getParameter("setTicket") != null) {
-            // [asdmASDM]\d{8}\.\d+
-            String ticket = req.getParameter("ticketno");
-            if (ticket.matches("[asdmASDM]\\d{8}\\.\\d+")) {
+            // [acdhi]\d{8}\.\d+ according to numbering scheme
+            String ticket = req.getParameter("ticketno").toLowerCase();
+            if (ticket.matches("[" + TICKET_PREFIX + "]\\d{8}\\.\\d+") && CalendarUtil.isDateValid(ticket.substring(1, 9))) {
                 AuthorizationContext ac = LoginPage.getAuthorizationContext(req);
                 req.getSession().setAttribute(Gigi.AUTH_CONTEXT, new AuthorizationContext(ac.getActor(), ticket));
                 return new RedirectResult(SupportEnterTicketPage.PATH);
index 07d7f9126cf93606e0e88668b3327f93b6ee2f07..d9b2cc1f8de65fc0744c023a72e555fa370062cb 100644 (file)
@@ -37,8 +37,6 @@ public class Signup extends Form {
 
     private static final Template t = new Template(Signup.class.getResource("Signup.templ"));
 
-    private boolean general = true, country = true, regional = true, radius = true;
-
     private CountrySelector cs;
 
     public Signup(HttpServletRequest hsr) {
@@ -55,10 +53,6 @@ public class Signup extends Form {
         vars.put("name", ni);
         vars.put("dob", myDoB);
         vars.put("email", HTMLEncoder.encodeHTML(email));
-        vars.put("general", general ? " checked=\"checked\"" : "");
-        vars.put("country", country ? " checked=\"checked\"" : "");
-        vars.put("regional", regional ? " checked=\"checked\"" : "");
-        vars.put("radius", radius ? " checked=\"checked\"" : "");
         vars.put("helpOnNames", new SprintfCommand("Help on Names {0}in the wiki{1}", Arrays.asList("!(/wiki/names", "!'</a>")));
         vars.put("csrf", getCSRFToken());
         vars.put("dobmin", User.MINIMUM_AGE + "");
@@ -70,10 +64,6 @@ public class Signup extends Form {
         if (r.getParameter("email") != null) {
             email = r.getParameter("email");
         }
-        general = "1".equals(r.getParameter("general"));
-        country = "1".equals(r.getParameter("country"));
-        regional = "1".equals(r.getParameter("regional"));
-        radius = "1".equals(r.getParameter("radius"));
         GigiApiException problems = new GigiApiException();
         try {
             ni.update(r);
@@ -133,13 +123,13 @@ public class Signup extends Form {
         String pw1 = req.getParameter("pword1");
         String pw2 = req.getParameter("pword2");
         if (pw1 == null || pw1.equals("")) {
-            ga.mergeInto(new GigiApiException("Pass Phrases were blank"));
+            ga.mergeInto(new GigiApiException("Passwords were blank"));
         } else if ( !pw1.equals(pw2)) {
-            ga.mergeInto(new GigiApiException("Pass Phrases don't match"));
+            ga.mergeInto(new GigiApiException("Passwords don't match"));
         }
         int pwpoints = PasswordStrengthChecker.checkpw(pw1, ni.getNamePartsPlain(), email);
         if (pwpoints < 3) {
-            ga.mergeInto(new GigiApiException(new SprintfCommand("The Pass Phrase you submitted failed to contain enough differing characters and/or contained words from your name and/or email address. For the current requirements and to learn more, visit our {0}FAQ{1}.", Arrays.asList("!(/wiki/goodPassword", "!'</a>'"))));
+            ga.mergeInto(new GigiApiException(new SprintfCommand("The Password you submitted failed to contain enough differing characters and/or contained words from your name and/or email address. For the current requirements and to learn more, visit our {0}FAQ{1}.", Arrays.asList("!(/wiki/goodPassword", "!'</a>'"))));
         }
         if ( !ga.isEmpty()) {
             throw ga;
@@ -190,15 +180,6 @@ public class Signup extends Form {
 
     private void run(HttpServletRequest req, String password) throws GigiApiException {
         User u = new User(email, password, myDoB.getDate(), Page.getLanguage(req).getLocale(), cs.getCountry(), ni.getNameParts());
-
-        try (GigiPreparedStatement ps = new GigiPreparedStatement("INSERT INTO `alerts` SET `memid`=?," + " `general`=?, `country`=?, `regional`=?, `radius`=?")) {
-            ps.setInt(1, u.getId());
-            ps.setBoolean(2, general);
-            ps.setBoolean(3, country);
-            ps.setBoolean(4, regional);
-            ps.setBoolean(5, radius);
-            ps.execute();
-        }
         Notary.writeUserAgreement(u, "ToS", "account creation", "", true, 0);
     }
 
index 466eaaa31339c765d72545d5bfa884599c2501f4..bc602e78cd97997c2854da1944b502af9f13332c 100644 (file)
   </tr>
 
   <tr>
-    <td><?=_Pass Phrase?><font color="red">*</font>: </td>
+    <td><?=_Password?><font color="red">*</font>: </td>
     <td><input class="form-control" type="password" name="pword1" size="30" autocomplete="off"></td>
     <td rowspan="2">&nbsp;</td>
   </tr>
 
   <tr>
-    <td><?=_Pass Phrase Again?><font color="red">*</font>: </td>
+    <td><?=_Password Again?><font color="red">*</font>: </td>
     <td><input class="form-control" type="password" name="pword2" size="30" autocomplete="off"></td>
   </tr>
 
   <tr>
     <td colspan="3"><font color="red">*</font><?=_Your password is one of many factors to protect your account from unauthorised access. A good password is hard to guess, long, and contains a diverse set of characters. For the current requirements and to learn more, visit our !(/wiki/goodPassword)FAQ!'</a>'.?></td>
   </tr>
-  <tr>
-    <td colspan="3"><?=_It's possible to get notifications of up and coming events and even just general announcements, untick any notifications you don't wish to receive. For country, regional and radius notifications to work you must choose your location once you've verified your account and logged in.?></td>
-  </tr>
 
   <tr>
-    <td valign="top"><?=_Alert me if?>: </td>
-    <td align="left">
-        <input type="checkbox" name="general" value="1"<?=$!general?>><?=_General Announcements?><br>
-        <input type="checkbox" name="country" value="1"<?=$!country?>><?=_Country Announcements?><br>
-        <input type="checkbox" name="regional" value="1"<?=$!regional?>><?=_Regional Announcements?><br>
-        <input type="checkbox" name="radius" value="1"<?=$!radius?>><?=_Within 200km Announcements?></td>
-    <td>&nbsp;</td>
+    <td colspan="3"><input type="checkbox" name="tos_agree" value="1"><?=_I agree to the Terms of Service (!(/policy/ToS)ToS!'</a>').?></td>
   </tr>
 
   <tr>
     <td colspan="3"><?=_When you click on next, we will send a confirmation email to the email address you have entered above.?></td>
   </tr>
-  <tr>
-    <td colspan="3"><input type="checkbox" name="tos_agree" value="1"><?=_I agree to the Terms of Service (!(/policy/ToS)ToS!'</a>').?></td>
-  </tr>
 
   <tr>
     <td colspan="3"><input class="btn btn-primary" type="submit" name="process" value="<?=_Next?>"></td>
index aeb41e5f7418db1608676199e7383f0975201f35..a7c74bd2271f56587abf777e1ebb8e57ae873f49 100644 (file)
@@ -7,20 +7,21 @@ import javax.naming.NamingException;
 
 import club.wpia.gigi.dbObjects.CertificateOwner;
 import club.wpia.gigi.dbObjects.Domain;
+import club.wpia.gigi.dbObjects.DomainPingConfiguration;
+import club.wpia.gigi.dbObjects.DomainPingExecution;
 import club.wpia.gigi.util.DNSUtil;
 import club.wpia.gigi.util.SystemKeywords;
 
 public class DNSPinger extends DomainPinger {
 
     @Override
-    public void ping(Domain domain, String expToken, CertificateOwner u, int confId) {
+    public DomainPingExecution ping(Domain domain, String expToken, CertificateOwner u, DomainPingConfiguration conf) {
         String[] tokenParts = expToken.split(":", 2);
         List<String> nameservers;
         try {
             nameservers = Arrays.asList(DNSUtil.getNSNames(domain.getSuffix()));
         } catch (NamingException e) {
-            enterPingResult(confId, "error", "No authorative nameserver found.", null);
-            return;
+            return enterPingResult(conf, "error", "No authorative nameserver found.", null);
         }
         StringBuffer result = new StringBuffer();
         result.append("failed: ");
@@ -51,9 +52,9 @@ public class DNSPinger extends DomainPinger {
 
         }
         if ( !failed) {
-            enterPingResult(confId, PING_SUCCEDED, "", null);
+            return enterPingResult(conf, PING_SUCCEDED, "", null);
         } else {
-            enterPingResult(confId, "error", result.toString(), null);
+            return enterPingResult(conf, "error", result.toString(), null);
         }
     }
 }
index fcb8f12e93263170a6af2da39e556ad91882c926..7b1d404c5f61a5a5ce822c79d8c7e65e2463fd29 100644 (file)
@@ -4,6 +4,8 @@ import club.wpia.gigi.database.DBEnum;
 import club.wpia.gigi.database.GigiPreparedStatement;
 import club.wpia.gigi.dbObjects.CertificateOwner;
 import club.wpia.gigi.dbObjects.Domain;
+import club.wpia.gigi.dbObjects.DomainPingConfiguration;
+import club.wpia.gigi.dbObjects.DomainPingExecution;
 
 public abstract class DomainPinger {
 
@@ -20,24 +22,18 @@ public abstract class DomainPinger {
 
     public static final String PING_SUCCEDED = "";
 
-    public abstract void ping(Domain domain, String configuration, CertificateOwner target, int confId);
+    public abstract DomainPingExecution ping(Domain domain, String configuration, CertificateOwner target, DomainPingConfiguration conf);
 
-    protected static void enterPingResult(int configId, String state, String result, String token) {
+    protected static DomainPingExecution enterPingResult(DomainPingConfiguration config, String state, String result, String token) {
         PingState estate = DomainPinger.PING_STILL_PENDING == state ? PingState.OPEN : DomainPinger.PING_SUCCEDED.equals(state) ? PingState.SUCCESS : PingState.FAILED;
-        try (GigiPreparedStatement enterPingResult = new GigiPreparedStatement("INSERT INTO `domainPinglog` SET `configId`=?, `state`=?::`pingState`, `result`=?, `challenge`=?")) {
-            enterPingResult.setInt(1, configId);
-            enterPingResult.setEnum(2, estate);
-            enterPingResult.setString(3, result);
-            enterPingResult.setString(4, token);
-            enterPingResult.execute();
-        }
+        return new DomainPingExecution(estate, result, config, token);
     }
 
-    protected static void updatePingResult(int configId, String state, String result, String token) {
+    protected static void updatePingResult(DomainPingConfiguration config, String state, String result, String token) {
         try (GigiPreparedStatement updatePingResult = new GigiPreparedStatement("UPDATE `domainPinglog` SET `state`=?::`pingState`, `result`=? WHERE `configId`=? AND `challenge`=?")) {
             updatePingResult.setString(1, DomainPinger.PING_STILL_PENDING == state ? "open" : DomainPinger.PING_SUCCEDED.equals(state) ? "success" : "failed");
             updatePingResult.setString(2, result);
-            updatePingResult.setInt(3, configId);
+            updatePingResult.setInt(3, config.getId());
             updatePingResult.setString(4, token);
             updatePingResult.execute();
         }
index 454ba81d30929a18c7f06cc852836cd77ff43052..7468f94a3473c68157265d8e333f0f6d0e390be9 100644 (file)
@@ -5,6 +5,8 @@ import java.util.Locale;
 
 import club.wpia.gigi.dbObjects.CertificateOwner;
 import club.wpia.gigi.dbObjects.Domain;
+import club.wpia.gigi.dbObjects.DomainPingConfiguration;
+import club.wpia.gigi.dbObjects.DomainPingExecution;
 import club.wpia.gigi.dbObjects.User;
 import club.wpia.gigi.email.MailProbe;
 import club.wpia.gigi.localisation.Language;
@@ -13,11 +15,11 @@ import club.wpia.gigi.util.RandomToken;
 public class EmailPinger extends DomainPinger {
 
     @Override
-    public void ping(Domain domain, String configuration, CertificateOwner u, int confId) {
+    public DomainPingExecution ping(Domain domain, String configuration, CertificateOwner u, DomainPingConfiguration conf) {
         String mail = configuration + "@" + domain.getSuffix();
         String token = RandomToken.generateToken(16);
+        DomainPingExecution r = enterPingResult(conf, PING_STILL_PENDING, "", token);
         try {
-            enterPingResult(confId, PING_STILL_PENDING, "", token);
             Locale l = Locale.ENGLISH;
             if (u instanceof User) {
                 l = ((User) u).getPreferredLocale();
@@ -26,8 +28,9 @@ public class EmailPinger extends DomainPinger {
             MailProbe.sendMailProbe(Language.getInstance(l), "domain", domain.getId(), token, mail);
         } catch (IOException e) {
             e.printStackTrace();
-            updatePingResult(confId, "error", "Mail connection interrupted", token);
+            updatePingResult(conf, "error", "Mail connection interrupted", token);
         }
+        return r;
     }
 
 }
index 7ad08b55b0bd6ddcfe9ec2793be9e497ce624853..61f8b467e77e01b50084cf07ecedcce6a767f5c0 100644 (file)
@@ -8,36 +8,33 @@ import java.net.URL;
 
 import club.wpia.gigi.dbObjects.CertificateOwner;
 import club.wpia.gigi.dbObjects.Domain;
+import club.wpia.gigi.dbObjects.DomainPingConfiguration;
+import club.wpia.gigi.dbObjects.DomainPingExecution;
 import club.wpia.gigi.util.SystemKeywords;
 
 public class HTTPFetch extends DomainPinger {
 
     @Override
-    public void ping(Domain domain, String expToken, CertificateOwner user, int confId) {
+    public DomainPingExecution ping(Domain domain, String expToken, CertificateOwner user, DomainPingConfiguration conf) {
         try {
             String[] tokenParts = expToken.split(":", 2);
             URL u = new URL("http://" + domain.getSuffix() + "/" + SystemKeywords.HTTP_CHALLENGE_PREFIX + tokenParts[0] + ".txt");
             HttpURLConnection huc = (HttpURLConnection) u.openConnection();
             if (huc.getResponseCode() != 200) {
-                enterPingResult(confId, "error", "Invalid status code " + huc.getResponseCode() + ".", null);
-                return;
+                return enterPingResult(conf, "error", "Invalid status code " + huc.getResponseCode() + ".", null);
             }
             BufferedReader br = new BufferedReader(new InputStreamReader(huc.getInputStream(), "UTF-8"));
             String line = br.readLine();
             if (line == null) {
-                enterPingResult(confId, "error", "Empty document.", null);
-                return;
+                return enterPingResult(conf, "error", "Empty document.", null);
             }
             if (line.trim().equals(tokenParts[1])) {
-                enterPingResult(confId, PING_SUCCEDED, "", null);
-                return;
+                return enterPingResult(conf, PING_SUCCEDED, "", null);
             }
-            enterPingResult(confId, "error", "Challenge tokens differed.", null);
-            return;
+            return enterPingResult(conf, "error", "Challenge tokens differed.", null);
         } catch (IOException e) {
             e.printStackTrace();
-            enterPingResult(confId, "error", "Exception: connection closed.", null);
-            return;
+            return enterPingResult(conf, "error", "Exception: connection closed.", null);
         }
     }
 }
diff --git a/src/club/wpia/gigi/ping/PingFailedWithActiveCertificates.templ b/src/club/wpia/gigi/ping/PingFailedWithActiveCertificates.templ
new file mode 100644 (file)
index 0000000..2ff34d7
--- /dev/null
@@ -0,0 +1,20 @@
+Subject: <?=_Ping for domain '${domain}' failed.?>
+
+<?=_Hi,?>
+
+<?=_A check of the ownership for your domain '${domain}' failed.?> \
+<?=_This check might either have been triggered manually or automatically for routine housekeeping.?> \
+<?=_Seeing a check fail we assume you might have lost ownership of this domain.?>
+
+<? if($valid) { ?>
+<?=_However there are currently enough succeeding proofs so the state of your domain is not endangered yet.?> \
+<?=_You might however want to correct (or remove) this ping to ensure that your domain stays valid.?>
+<? } else { ?>
+<?=_If you keep lacking sufficient proof of ownership for this domain after the grace period of two weeks expired we are going to revoke all affected certificates.?>
+
+<?=_Affected certificates:?>
+<? foreach($certs){ ?>- <?=_serial: ${serial} issued by ${ca} valid from ${from} to ${to}.?>
+<? } ?>
+<? } ?>
+
+<?=_Visit ${domainLink} to check the current state of pings of your domain and to find out more information about which ping failed.?>
index d6016c677534bba383b1b2cfab615aa3ef2d95c6..895e8c831f099de7758efd1f3b590e604291e2bc 100644 (file)
@@ -1,17 +1,38 @@
 package club.wpia.gigi.ping;
 
+import java.io.IOException;
+import java.security.GeneralSecurityException;
 import java.security.KeyStore;
+import java.security.cert.X509Certificate;
+import java.sql.Timestamp;
+import java.util.Date;
 import java.util.HashMap;
 import java.util.LinkedList;
+import java.util.Locale;
+import java.util.Map;
 import java.util.Queue;
 
+import club.wpia.gigi.GigiApiException;
 import club.wpia.gigi.database.DatabaseConnection;
 import club.wpia.gigi.database.DatabaseConnection.Link;
 import club.wpia.gigi.database.GigiPreparedStatement;
 import club.wpia.gigi.database.GigiResultSet;
+import club.wpia.gigi.dbObjects.Certificate;
+import club.wpia.gigi.dbObjects.Certificate.RevocationType;
+import club.wpia.gigi.dbObjects.CertificateOwner;
 import club.wpia.gigi.dbObjects.Domain;
 import club.wpia.gigi.dbObjects.DomainPingConfiguration;
+import club.wpia.gigi.dbObjects.DomainPingExecution;
 import club.wpia.gigi.dbObjects.DomainPingType;
+import club.wpia.gigi.dbObjects.Organisation;
+import club.wpia.gigi.dbObjects.User;
+import club.wpia.gigi.localisation.Language;
+import club.wpia.gigi.output.ArrayIterable;
+import club.wpia.gigi.output.template.MailTemplate;
+import club.wpia.gigi.pages.account.domain.EditDomain;
+import club.wpia.gigi.ping.DomainPinger.PingState;
+import club.wpia.gigi.util.ServerConstants;
+import club.wpia.gigi.util.ServerConstants.Host;
 
 public class PingerDaemon extends Thread {
 
@@ -21,6 +42,8 @@ public class PingerDaemon extends Thread {
 
     private Queue<DomainPingConfiguration> toExecute = new LinkedList<>();
 
+    private final MailTemplate pingFailedMail = new MailTemplate(PingerDaemon.class.getResource("PingFailedWithActiveCertificates.templ"));
+
     public PingerDaemon(KeyStore truststore) {
         this.truststore = truststore;
     }
@@ -36,10 +59,7 @@ public class PingerDaemon extends Thread {
 
     public void runWithConnection() {
 
-        pingers.put(DomainPingType.EMAIL, new EmailPinger());
-        pingers.put(DomainPingType.SSL, new SSLPinger(truststore));
-        pingers.put(DomainPingType.HTTP, new HTTPFetch());
-        pingers.put(DomainPingType.DNS, new DNSPinger());
+        initializeConnectionUsage();
 
         while (true) {
             try {
@@ -53,22 +73,8 @@ public class PingerDaemon extends Thread {
                     }
                     notifyAll();
                 }
-                try (GigiPreparedStatement searchNeededPings = new GigiPreparedStatement("SELECT `pc`.`id`" //
-                        + " FROM `pingconfig` AS `pc`" //
-                        + " INNER JOIN `domains` AS `d` ON `d`.`id` = `pc`.`domainid`" //
-                        + " WHERE `d`.`deleted` IS NULL" //
-                        + "  AND `pc`.`deleted` IS NULL" //
-                        + "  AND NOT EXISTS (" //
-                        + "    SELECT 1 FROM `domainPinglog` AS `dpl`" //
-                        + "    WHERE `dpl`.`configId` = `pc`.`id`" //
-                        + "     AND `dpl`.`when` >= CURRENT_TIMESTAMP - interval '6 mons')")) {
-
-                    GigiResultSet rs = searchNeededPings.executeQuery();
-                    while (rs.next()) {
-                        worked = true;
-                        handle(DomainPingConfiguration.getById(rs.getInt("id")));
-                    }
-                }
+                long time = System.currentTimeMillis();
+                worked |= executeNeededPings(new Date(time));
                 try {
                     if ( !worked) {
                         Thread.sleep(5000);
@@ -85,7 +91,90 @@ public class PingerDaemon extends Thread {
         }
     }
 
-    private void handle(DomainPingConfiguration conf) {
+    protected void initializeConnectionUsage() {
+        pingers.put(DomainPingType.EMAIL, new EmailPinger());
+        pingers.put(DomainPingType.SSL, new SSLPinger(truststore));
+        pingers.put(DomainPingType.HTTP, new HTTPFetch());
+        pingers.put(DomainPingType.DNS, new DNSPinger());
+    }
+
+    public synchronized boolean executeNeededPings(Date time) {
+        boolean worked = false;
+        try (GigiPreparedStatement searchNeededPings = new GigiPreparedStatement("SELECT `d`.`id`, `dpl`.`configId`, `dpl`.`when`," //
+        // .. for all found pings we want to know, if we do not have a more
+        // recent successful ping
+                + "  NOT EXISTS (" //
+                + "    SELECT 1 FROM `domainPinglog` AS `dpl2`" //
+                + "    WHERE `dpl`.`configId` = `dpl2`.`configId`" //
+                + "     AND `dpl2`.state = 'success' AND `dpl2`.`when` > `dpl`.`when`) AS `resucceeded`" //
+                + " FROM `domainPinglog` AS `dpl`" //
+                // We search valid pings
+                + " INNER JOIN `pingconfig` AS `pc` ON `pc`.`id` = `dpl`.`configId` AND `pc`.`deleted` IS NULL" //
+                + " INNER JOIN `domains` AS `d` ON `d`.`id` = `pc`.`domainid` AND `d`.`deleted` IS NULL" //
+                // .. that failed, ..
+                + " WHERE `dpl`.`state` = 'failed'" //
+                // .. are older than 2 weeks
+                + " AND `dpl`.`when` <= ?::timestamp - interval '2 weeks'" //
+                // .. and are flagged for corrective action
+                + " AND `dpl`.`needsAction`" //
+        )) {
+            searchNeededPings.setTimestamp(1, new Timestamp(time.getTime()));
+            GigiResultSet rs = searchNeededPings.executeQuery();
+            try (GigiPreparedStatement updateDone = new GigiPreparedStatement("UPDATE `domainPinglog` SET `needsAction`=false WHERE `configId`=? AND `when`=?")) {
+                while (rs.next()) {
+                    worked = true;
+                    // Give this ping a last chance to succeed.
+                    handle(DomainPingConfiguration.getById(rs.getInt(2)));
+                    // We only consider revoking if this ping has not been
+                    // superseded by a following successful ping.
+                    if (rs.getBoolean(4)) {
+                        Domain d = Domain.getById(rs.getInt(1));
+                        int ct = 0;
+                        boolean[] used = new boolean[DomainPingType.values().length];
+                        // We only revoke, there are not 2 pings that are not
+                        // 'strictly invalid'
+                        for (DomainPingConfiguration cfg : d.getConfiguredPings()) {
+                            if ( !cfg.isStrictlyInvalid(time) && !used[cfg.getType().ordinal()]) {
+                                ct++;
+                                used[cfg.getType().ordinal()] = true;
+                            }
+                            if (ct >= 2) {
+                                break;
+                            }
+                        }
+                        if (ct < 2) {
+                            for (Certificate c : d.fetchActiveCertificates()) {
+                                // TODO notify user
+                                c.revoke(RevocationType.PING_TIMEOUT);
+                            }
+                        }
+                    }
+                    updateDone.setInt(1, rs.getInt(2));
+                    updateDone.setTimestamp(2, rs.getTimestamp(3));
+                    updateDone.executeUpdate();
+                }
+            }
+        }
+        try (GigiPreparedStatement searchNeededPings = new GigiPreparedStatement("SELECT `pc`.`id`" //
+                + " FROM `pingconfig` AS `pc`" //
+                + " INNER JOIN `domains` AS `d` ON `d`.`id` = `pc`.`domainid`" //
+                + " WHERE `d`.`deleted` IS NULL" //
+                + "  AND `pc`.`deleted` IS NULL" //
+                + "  AND NOT EXISTS (" //
+                + "    SELECT 1 FROM `domainPinglog` AS `dpl`" //
+                + "    WHERE `dpl`.`configId` = `pc`.`id`" //
+                + "     AND `dpl`.`when` >= ?::timestamp - interval '6 mons')")) {
+            searchNeededPings.setTimestamp(1, new Timestamp(time.getTime()));
+            GigiResultSet rs = searchNeededPings.executeQuery();
+            while (rs.next()) {
+                worked = true;
+                handle(DomainPingConfiguration.getById(rs.getInt("id")));
+            }
+        }
+        return worked;
+    }
+
+    protected void handle(DomainPingConfiguration conf) {
         DomainPingType type = conf.getType();
         String config = conf.getInfo();
         DomainPinger dp = pingers.get(type);
@@ -93,10 +182,53 @@ public class PingerDaemon extends Thread {
             Domain target = conf.getTarget();
             System.err.println("Executing " + dp + " on " + target + " (" + System.currentTimeMillis() + ")");
             try {
-                dp.ping(target, config, target.getOwner(), conf.getId());
+                DomainPingExecution x = dp.ping(target, config, target.getOwner(), conf);
+                if (x.getState() == PingState.FAILED) {
+                    Certificate[] cs = target.fetchActiveCertificates();
+                    if (cs.length != 0) {
+                        CertificateOwner o = target.getOwner();
+                        Locale l = Locale.ENGLISH;
+                        String contact;
+                        if (o instanceof User) {
+                            l = ((User) o).getPreferredLocale();
+                            contact = ((User) o).getEmail();
+                        } else if (o instanceof Organisation) {
+                            contact = ((Organisation) o).getContactEmail();
+
+                        } else {
+                            throw new Error();
+                        }
+                        HashMap<String, Object> vars = new HashMap<>();
+                        vars.put("valid", target.isVerified());
+                        vars.put("domain", target.getSuffix());
+                        vars.put("domainLink", "https://" + ServerConstants.getHostNamePortSecure(Host.WWW) + "/" + EditDomain.PATH + target.getId());
+                        vars.put("certs", new ArrayIterable<Certificate>(cs) {
+
+                            @Override
+                            public void apply(Certificate t, Language l, Map<String, Object> vars) {
+                                vars.put("serial", t.getSerial());
+                                vars.put("ca", t.getParent().getKeyname());
+                                try {
+                                    X509Certificate c = t.cert();
+                                    vars.put("from", c.getNotBefore());
+                                    vars.put("to", c.getNotAfter());
+                                } catch (IOException e) {
+                                    e.printStackTrace();
+                                } catch (GeneralSecurityException e) {
+                                    e.printStackTrace();
+                                } catch (GigiApiException e) {
+                                    e.printStackTrace();
+                                }
+                            }
+
+                        });
+                        pingFailedMail.sendMail(Language.getInstance(l), vars, contact);
+                        System.out.println("Ping failed with active certificates");
+                    }
+                }
             } catch (Throwable t) {
                 t.printStackTrace();
-                DomainPinger.enterPingResult(conf.getId(), "error", "exception", null);
+                DomainPinger.enterPingResult(conf, "error", "exception", null);
             }
             System.err.println("done (" + System.currentTimeMillis() + ")");
         }
index db0f0f52e45b04e3573add98d1b9680b1449df71..97fb30da00538095c10a90c2bf20659e6337b9de 100644 (file)
@@ -33,6 +33,8 @@ import club.wpia.gigi.dbObjects.CACertificate;
 import club.wpia.gigi.dbObjects.Certificate;
 import club.wpia.gigi.dbObjects.CertificateOwner;
 import club.wpia.gigi.dbObjects.Domain;
+import club.wpia.gigi.dbObjects.DomainPingConfiguration;
+import club.wpia.gigi.dbObjects.DomainPingExecution;
 import sun.security.x509.AVA;
 import sun.security.x509.X500Name;
 
@@ -51,7 +53,7 @@ public class SSLPinger extends DomainPinger {
     }
 
     @Override
-    public void ping(Domain domain, String configuration, CertificateOwner u, int confId) {
+    public DomainPingExecution ping(Domain domain, String configuration, CertificateOwner u, DomainPingConfiguration conf) {
         try (SocketChannel sch = SocketChannel.open()) {
             sch.socket().setSoTimeout(5000);
             String[] parts = configuration.split(":", 4);
@@ -76,11 +78,9 @@ public class SSLPinger extends DomainPinger {
             String key = parts[0];
             String value = parts[1];
             String res = test(sch, domain.getSuffix(), u, value);
-            enterPingResult(confId, res, res, null);
-            return;
+            return enterPingResult(conf, res, res, null);
         } catch (IOException e) {
-            enterPingResult(confId, "error", "connection Failed", null);
-            return;
+            return enterPingResult(conf, "error", "connection Failed", null);
         }
 
     }
index c7b2bb0faef15015a59545606417692db72f7a8b..9c4cf70ae66bf1392d8712ee5c6e2ac8927e1bca 100644 (file)
@@ -15,6 +15,17 @@ public class CalendarUtil {
 
     }
 
+    /**
+     * @param date
+     *            YYYYMMDD
+     */
+    public static boolean isDateValid(String date) {
+        int year = Integer.parseInt(date.substring(0, 4));
+        int month = Integer.parseInt(date.substring(4, 6));
+        int day = Integer.parseInt(date.substring(6, 8));
+        return isDateValid(year, month, day);
+    }
+
     public static boolean isOfAge(DayDate dob, int age) {
         return isYearsInFuture(dob.start(), age);
     }
index 0df62480ef3d28d5e47b65e16b323f6db73a20cc..112f71283441e568045f3e57708dfb2b3b126302 100644 (file)
@@ -80,7 +80,7 @@ public class PasswordStrengthChecker {
             }
         }
         if (checkpw(pw, parts.toArray(new String[parts.size()]), email) < 3) {
-            throw (new GigiApiException(new SprintfCommand("The Pass Phrase you submitted failed to contain enough differing characters and/or contained words from your name and/or email address. For the current requirements and to learn more, visit our {0}FAQ{1}.", Arrays.asList("!(/wiki/goodPassword", "!'</a>'"))));
+            throw (new GigiApiException(new SprintfCommand("The Password you submitted failed to contain enough differing characters and/or contained words from your name and/or email address. For the current requirements and to learn more, visit our {0}FAQ{1}.", Arrays.asList("!(/wiki/goodPassword", "!'</a>'"))));
         }
     }
 
index 59f042793af7dbb0033b446cea12d03fecaa1a27..e20b4944da0e88bdc7b145a5e9e15df4bb00d99f 100644 (file)
@@ -1,10 +1,13 @@
 package club.wpia.gigi.pages.admin;
 
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
 import java.io.UnsupportedEncodingException;
 import java.net.MalformedURLException;
+import java.util.Random;
 
 import org.junit.Test;
 
@@ -12,8 +15,10 @@ import club.wpia.gigi.GigiApiException;
 import club.wpia.gigi.dbObjects.Group;
 import club.wpia.gigi.pages.admin.support.FindUserByDomainPage;
 import club.wpia.gigi.pages.admin.support.FindUserByEmailPage;
+import club.wpia.gigi.pages.admin.support.SupportEnterTicketForm;
 import club.wpia.gigi.pages.admin.support.SupportEnterTicketPage;
 import club.wpia.gigi.testUtils.ClientTest;
+import club.wpia.gigi.testUtils.IOUtils;
 
 public class TestSEAdminTicketSetting extends ClientTest {
 
@@ -32,4 +37,66 @@ public class TestSEAdminTicketSetting extends ClientTest {
         assertEquals(403, get(FindUserByEmailPage.PATH).getResponseCode());
     }
 
+    @Test
+    public void testSetTicketNumberCharacter() throws MalformedURLException, UnsupportedEncodingException, IOException {
+        String ticket;
+        String alphabet = "abcdefghijklmnopqrstuvwxyz";
+
+        // test allowed character
+        for (char ch : SupportEnterTicketForm.TICKET_PREFIX.toCharArray()) {
+            ticket = ch + "20171212.1";
+            assertEquals(302, post(cookie, SupportEnterTicketPage.PATH, "ticketno=" + ticket + "&setTicket=action", 0).getResponseCode());
+            ticket = Character.toUpperCase(ch) + "20171212.1";
+            assertEquals(302, post(cookie, SupportEnterTicketPage.PATH, "ticketno=" + ticket + "&setTicket=action", 0).getResponseCode());
+            alphabet = alphabet.replaceAll(Character.toString(ch), "");
+        }
+
+        // test not allowed character
+        Random rnd = new Random();
+        char ch = alphabet.charAt(rnd.nextInt(alphabet.length()));
+        assertWrongTicketNumber(ch + "20171212.1");
+    }
+
+    @Test
+    public void testSetTicketNumberDatepart() throws MalformedURLException, UnsupportedEncodingException, IOException {
+        char ch = getValidCharacter();
+
+        assertWrongTicketNumber(ch + "220171212.1");
+
+        assertWrongTicketNumber(ch + "0171212.1");
+
+        assertWrongTicketNumber(ch + "20171512.1");
+
+        assertWrongTicketNumber(ch + "20170229.1");
+
+        assertWrongTicketNumber(ch + ch + "20171212.1");
+
+        assertWrongTicketNumber("20171212.1");
+
+        assertWrongTicketNumber(ch + "20171212" + ch + ".1");
+
+        assertWrongTicketNumber(ch + "201721" + ch + "21.1");
+    }
+
+    @Test
+    public void testSetTicketNumberNumberpart() throws MalformedURLException, UnsupportedEncodingException, IOException {
+        char ch = getValidCharacter();
+
+        assertWrongTicketNumber(ch + "20171212.");
+
+        assertWrongTicketNumber(ch + "20171212");
+
+        assertWrongTicketNumber(ch + "20171212.1" + ch);
+
+    }
+
+    private char getValidCharacter() {
+        Random rnd = new Random();
+        return SupportEnterTicketForm.TICKET_PREFIX.charAt(rnd.nextInt(SupportEnterTicketForm.TICKET_PREFIX.length()));
+    }
+
+    private void assertWrongTicketNumber(String ticket) throws IOException {
+        String res = IOUtils.readURL(post(SupportEnterTicketPage.PATH, "ticketno=" + ticket + "&setTicket=action"));
+        assertThat(res, containsString("Ticket format malformed"));
+    }
 }
index d9de3a910d1095f4999fd023687f3dbd80902c0b..85e34b92e97072220d3cf12560f850a140dc9e56 100644 (file)
@@ -49,14 +49,14 @@ public class RegisterPageTest extends ManagedTest {
 
         String defaultSignup = "fname=" + URLEncoder.encode("ab", "UTF-8") + "&lname=" + URLEncoder.encode("b", "UTF-8") + "&pword1=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") + "&pword2=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") + "&day=1&month=1&year=1910&tos_agree=1&mname=mn&suffix=sf&email=";
 
-        String query = defaultSignup + URLEncoder.encode("correct3_" + uniq + "@email.de", "UTF-8") + "&general=1&country=1&regional=1&radius=1&name-type=western";
+        String query = defaultSignup + URLEncoder.encode("correct3_" + uniq + "@email.de", "UTF-8") + "&name-type=western";
         String data = fetchStartErrorMessage(runRegister(query));
         assertNull(data);
         assertSuccessfullRegMail("correct3_" + uniq + "@email.de");
 
         getMailReceiver().setEmailCheckError("400 Greylisted");
         getMailReceiver().setApproveRegex(Pattern.compile("a"));
-        query = defaultSignup + URLEncoder.encode("correct4_" + uniq + "@email.de", "UTF-8") + "&general=1&country=1&regional=1&radius=1";
+        query = defaultSignup + URLEncoder.encode("correct4_" + uniq + "@email.de", "UTF-8");
         data = fetchStartErrorMessage(runRegister(query));
         assertNotNull(data);
 
@@ -170,20 +170,6 @@ public class RegisterPageTest extends ManagedTest {
         assertThat(run, containsString("<option selected=\"selected\">28</option>"));
     }
 
-    @Test
-    public void testCheckboxesStay() throws IOException {
-        String run2 = runRegister("general=1&country=a&regional=1&radius=0");
-        assertThat(run2, containsString("name=\"general\" value=\"1\" checked=\"checked\">"));
-        assertThat(run2, containsString("name=\"country\" value=\"1\">"));
-        assertThat(run2, containsString("name=\"regional\" value=\"1\" checked=\"checked\">"));
-        assertThat(run2, containsString("name=\"radius\" value=\"1\">"));
-        run2 = runRegister("general=0&country=1&radius=1");
-        assertThat(run2, containsString("name=\"general\" value=\"1\">"));
-        assertThat(run2, containsString("name=\"country\" value=\"1\" checked=\"checked\">"));
-        assertThat(run2, containsString("name=\"regional\" value=\"1\">"));
-        assertThat(run2, containsString("name=\"radius\" value=\"1\" checked=\"checked\">"));
-    }
-
     @Test
     public void testDoubleMail() throws IOException {
         long uniq = System.currentTimeMillis();
@@ -221,7 +207,7 @@ public class RegisterPageTest extends ManagedTest {
 
         String defaultSignup = "fname=" + URLEncoder.encode("ab", "UTF-8") + "&lname=" + URLEncoder.encode("b", "UTF-8") + "&pword1=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") + "&pword2=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") + "&day=1&month=1&year=1910&tos_agree=1&mname=mn&suffix=sf&email=";
 
-        String query = defaultSignup + URLEncoder.encode(email, "UTF-8") + "&general=1&country=1&regional=1&radius=1&name-type=western&residenceCountry=DE";
+        String query = defaultSignup + URLEncoder.encode(email, "UTF-8") + "&name-type=western&residenceCountry=DE";
         String data = fetchStartErrorMessage(runRegister(query));
         assertNull(data);
         User u = User.getByEmail(email);
@@ -236,7 +222,7 @@ public class RegisterPageTest extends ManagedTest {
 
         String defaultSignup = "fname=" + URLEncoder.encode("ab", "UTF-8") + "&lname=" + URLEncoder.encode("b", "UTF-8") + "&pword1=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") + "&pword2=" + URLEncoder.encode(TEST_PASSWORD, "UTF-8") + "&day=1&month=1&year=1910&tos_agree=1&mname=mn&suffix=sf&email=";
 
-        String query = defaultSignup + URLEncoder.encode(email, "UTF-8") + "&general=1&country=1&regional=1&radius=1&name-type=western&residenceCountry=invalid";
+        String query = defaultSignup + URLEncoder.encode(email, "UTF-8") + "&name-type=western&residenceCountry=invalid";
         String data = fetchStartErrorMessage(runRegister(query));
         assertNull(data);
         User u = User.getByEmail(email);
diff --git a/tests/club/wpia/gigi/ping/TestTiming.java b/tests/club/wpia/gigi/ping/TestTiming.java
new file mode 100644 (file)
index 0000000..f2b6fe1
--- /dev/null
@@ -0,0 +1,164 @@
+package club.wpia.gigi.ping;
+
+import static org.junit.Assert.*;
+import static org.junit.Assume.*;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.util.Date;
+import java.util.List;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.dbObjects.Certificate;
+import club.wpia.gigi.dbObjects.Certificate.CSRType;
+import club.wpia.gigi.dbObjects.Certificate.CertificateStatus;
+import club.wpia.gigi.dbObjects.Certificate.SANType;
+import club.wpia.gigi.dbObjects.CertificateProfile;
+import club.wpia.gigi.dbObjects.Digest;
+import club.wpia.gigi.dbObjects.Domain;
+import club.wpia.gigi.dbObjects.DomainPingConfiguration;
+import club.wpia.gigi.dbObjects.DomainPingExecution;
+import club.wpia.gigi.dbObjects.DomainPingType;
+import club.wpia.gigi.ping.DomainPinger.PingState;
+import club.wpia.gigi.testUtils.PingTest;
+import club.wpia.gigi.testUtils.TestEmailReceiver.TestMail;
+import club.wpia.gigi.util.RandomToken;
+import club.wpia.gigi.util.SimpleSigner;
+
+public class TestTiming extends PingTest {
+
+    @Test
+    public void httpAndMailSuccessCert() throws GigiApiException, IOException, InterruptedException, GeneralSecurityException {
+        httpAndMailSuccess(true, false);
+    }
+
+    @Test
+    public void httpAndMailSuccessCertAndCorrect() throws GigiApiException, IOException, InterruptedException, GeneralSecurityException {
+        httpAndMailSuccess(true, true);
+    }
+
+    @Test
+    public void httpAndMailSuccessNoCert() throws GigiApiException, IOException, InterruptedException, GeneralSecurityException {
+        httpAndMailSuccess(false, false);
+    }
+
+    public void httpAndMailSuccess(boolean certs, boolean correct) throws GigiApiException, IOException, InterruptedException, GeneralSecurityException {
+        String test = getTestProps().getProperty("domain.http");
+        assumeNotNull(test);
+
+        // When we have a domain.
+        Domain d = new Domain(u, u, test);
+        String token = RandomToken.generateToken(16);
+        String value = RandomToken.generateToken(16);
+
+        // If we run the sub case that we have certificates on the domain,
+        // create a certificate now.
+        Certificate c = null;
+        if (certs) {
+            KeyPair kp = generateKeypair();
+            String key = generatePEMCSR(kp, "CN=testmail@example.com");
+            c = new Certificate(u, u, Certificate.buildDN("CN", "testmail@example.com"), Digest.SHA256, key, CSRType.CSR, CertificateProfile.getByName("server"), new Certificate.SubjectAlternateName(SANType.DNS, test));
+            await(c.issue(null, "2y", u));
+        }
+
+        // Register HTTP and Email pings.
+        updateService(token, value, "http");
+        d.addPing(DomainPingType.EMAIL, "postmaster");
+        d.addPing(DomainPingType.HTTP, token + ":" + value);
+
+        // Two successful pings
+        getMailReceiver().receive("postmaster@" + test).verify();
+        waitForPings(2);
+
+        assertEquals(0, countFailed(d.getPings(), 2));
+
+        // An own Pinger Daemon to control ping execution locally.
+        PingerDaemon pd = new PingerDaemon(null);
+        pd.initializeConnectionUsage();
+
+        // After 6 months the pings are executed again
+        pd.executeNeededPings(new Date(System.currentTimeMillis() + 6 * 31 * 24 * 60 * 60L * 1000));
+        getMailReceiver().receive("postmaster@" + test).verify();
+        waitForPings(4);
+        assertEquals(0, countFailed(d.getPings(), 4));
+
+        // After 6 months the pings are executed again, but when the HTTP file
+        // is wrong, that ping fails.
+        updateService(token, value + "broken", "http");
+        // Note that the time is still 6 months in the future, as the pings from
+        // before were still executed (and logged)
+        // as executed now.
+        pd.executeNeededPings(new Date(System.currentTimeMillis() + 6 * 31 * 24 * 60 * 60L * 1000));
+        getMailReceiver().receive("postmaster@" + test).verify();
+        waitForPings(6);
+        assertEquals(1, countFailed(d.getPings(), 6));
+        // Which renders the domain invalid
+        assertFalse(d.isVerified());
+
+        if (certs) {
+            // And the user gets a warning-mail if there was a cert
+            TestMail mail = getMailReceiver().receive(u.getEmail());
+            assertThat(mail.getMessage(), CoreMatchers.containsString(d.getSuffix()));
+            assertThat(mail.getMessage(), CoreMatchers.containsString(c.getSerial()));
+            if ( !correct) {
+                // If the user ignores the warning, after two weeks
+                pd.executeNeededPings(new Date(System.currentTimeMillis() + 15 * 24 * 60 * 60L * 1000));
+                // The user receives another warning mail.
+                mail = getMailReceiver().receive(u.getEmail());
+                assertThat(mail.getMessage(), CoreMatchers.containsString(d.getSuffix()));
+                assertThat(mail.getMessage(), CoreMatchers.containsString(c.getSerial()));
+                // And when the revocation is carried out
+                SimpleSigner.ping();
+                // ... and the certificate gets revoked.
+                assertEquals(CertificateStatus.REVOKED, c.getStatus());
+            } else {
+                // But if the user corrects the ping, ...
+                updateService(token, value, "http");
+                // ... and the ping is re-executed,
+                pd.handle(getPing(d.getConfiguredPings(), DomainPingType.HTTP));
+                waitForPings(7);
+                assertEquals(1, countFailed(d.getPings(), 7));
+
+                // Even after two weeks
+                pd.executeNeededPings(new Date(System.currentTimeMillis() + 15 * 24 * 60 * 60L * 1000));
+                // and all resulting jobs are executed
+                SimpleSigner.ping();
+                // ... the certificate stays valid.
+                assertEquals(CertificateStatus.ISSUED, c.getStatus());
+            }
+        } else {
+            // otherwise there is no mail
+        }
+
+    }
+
+    private DomainPingConfiguration getPing(List<DomainPingConfiguration> cp, DomainPingType tp) {
+        for (DomainPingConfiguration d : cp) {
+            if (d.getType() == tp) {
+                return d;
+            }
+        }
+        throw new Error("Type not found.");
+    }
+
+    private int countFailed(DomainPingExecution[] pg, int count) {
+        assertEquals(count, pg.length);
+        int fld = 0;
+        for (DomainPingExecution e : pg) {
+            PingState state = e.getState();
+            if (e.getConfig().getType() == DomainPingType.HTTP) {
+                if (state == PingState.FAILED) {
+                    fld++;
+                    continue;
+                }
+            }
+            assertEquals(PingState.SUCCESS, state);
+        }
+        return fld;
+    }
+
+}
index 16abb06cf2623c36de562da1dbbba8b354e51419..3ece611c8686c17bc011835a8036463d379cbd07 100644 (file)
@@ -210,6 +210,16 @@ public abstract class ConfiguredTest {
             int keySize = 4096;
             long r_lv = 7331;
 
+            // The generated numbers p q and r fall into the
+            // following ranges:
+            // - p: 2^(lp-1) < p < 2^lp
+            // - q: 2^(lq-1) < q < 2^lq
+            // - r: 2^12 < r < 2^13
+            // Thus the generated number has at least lp+lq+11 bit and
+            // can have at most lp+lq+13 bit.
+            // Thus for random selection of p and q the algorithm will
+            // at some point select a number of length n=n/2+lr+(n-n/2-lr)=>n
+            // bit.
             int lp = (keySize + 1) >> 1;
             int lr = BigInteger.valueOf(r_lv).bitLength();
             int lq = keySize - lp - lr;
@@ -221,24 +231,17 @@ public abstract class ConfiguredTest {
                 // generate two random primes of size lp/lq
                 BigInteger p, q, r, n;
 
-                p = BigInteger.probablePrime(lp, random);
                 r = BigInteger.valueOf(r_lv);
                 do {
+                    p = BigInteger.probablePrime(lp, random);
                     q = BigInteger.probablePrime(lq, random);
 
-                    // convention is for p > q > r
-                    if (p.compareTo(q) < 0) {
-                        BigInteger tmp = p;
-                        p = q;
-                        q = tmp;
-                    }
-
                     // modulus n = p * q * r
                     n = p.multiply(q).multiply(r);
 
                     // even with correctly sized p, q and r, there is a chance
-                    // that n will be one bit short. re-generate the smaller
-                    // prime if so.
+                    // that n will be one bit short. re-generate the
+                    // primes if so.
                 } while (n.bitLength() < keySize);
 
                 // phi = (p - 1) * (q - 1) * (r - 1) must be relative prime to e
@@ -348,6 +351,12 @@ public abstract class ConfiguredTest {
             d.addPing(DomainPingType.EMAIL, "admin");
             TestMail testMail = getMailReceiver().receive("admin@" + d.getSuffix());
             testMail.verify();
+            // Enforce successful ping :-)
+            d.addPing(DomainPingType.HTTP, "a:b");
+            try (GigiPreparedStatement gps = new GigiPreparedStatement("INSERT INTO `domainPinglog` SET `configId`=(SELECT `id` FROM `pingconfig` WHERE `domainid`=? AND `type`='http'), state='success', needsAction=false")) {
+                gps.setInt(1, d.getId());
+                gps.execute();
+            }
             assertTrue(d.isVerified());
         } catch (GigiApiException e) {
             throw new Error(e);
index 87ea982f5dc35c3095dda59062fb5f54da0fc737..06a9444115ad429d17aecd43f2ea9d104400a8a1 100644 (file)
@@ -21,7 +21,7 @@ import club.wpia.gigi.util.SystemKeywords;
 
 /**
  * Base class for test suites that check extensively if the domain-ping
- * functionality wroks as expected.
+ * functionality works as expected.
  */
 public abstract class PingTest extends ClientTest {
 
@@ -34,7 +34,7 @@ public abstract class PingTest extends ClientTest {
         assertEquals(200, ((HttpURLConnection) new URL(url).openConnection()).getResponseCode());
     }
 
-    protected void waitForPings(int count) throws SQLException, InterruptedException {
+    protected void waitForPings(int count) throws InterruptedException {
         try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT COUNT(*) FROM `domainPinglog`")) {
             long start = System.currentTimeMillis();
             while (System.currentTimeMillis() - start < 10000) {
@@ -77,6 +77,12 @@ public abstract class PingTest extends ClientTest {
         return m;
     }
 
+    /**
+     * Needs to be @After class. In order to init {@link ClientTest#id}
+     * correctly, this needs to be after test. Before test this might be
+     * executed after the init of {@link ClientTest#id} and make that value
+     * invalid.
+     */
     @After
     public void purgeDbAfterTest() throws SQLException, IOException {
         purgeDatabase();
index 26aeec068ec2a209be903d825478cd9039cd84a8..f48c5bed3d836c1d2a8542047bdfe9d30007eadc 100644 (file)
@@ -39,6 +39,8 @@ import club.wpia.gigi.dbObjects.CertificateOwner;
 import club.wpia.gigi.dbObjects.Country;
 import club.wpia.gigi.dbObjects.Digest;
 import club.wpia.gigi.dbObjects.Domain;
+import club.wpia.gigi.dbObjects.DomainPingConfiguration;
+import club.wpia.gigi.dbObjects.DomainPingExecution;
 import club.wpia.gigi.dbObjects.DomainPingType;
 import club.wpia.gigi.dbObjects.EmailAddress;
 import club.wpia.gigi.dbObjects.Group;
@@ -233,14 +235,14 @@ public class Manager extends Page {
         }
 
         @Override
-        public void ping(Domain domain, String configuration, CertificateOwner target, int confId) {
+        public DomainPingExecution ping(Domain domain, String configuration, CertificateOwner target, DomainPingConfiguration conf) {
             System.err.println("TestManager: " + domain.getSuffix());
             if (pingExempt.contains(domain.getSuffix())) {
-                enterPingResult(confId, DomainPinger.PING_SUCCEDED, "Succeeded by TestManager pass-by", null);
+                return enterPingResult(conf, DomainPinger.PING_SUCCEDED, "Succeeded by TestManager pass-by", null);
             } else {
                 DomainPinger pinger = dps.get(dpt);
                 System.err.println("Forward to old pinger: " + pinger);
-                pinger.ping(domain, configuration, target, confId);
+                return pinger.ping(domain, configuration, target, conf);
             }
         }