]> WPIA git - gigi.git/commitdiff
Merge "add: enable user to download certificate with file extention crt or pem"
authorMarcus Mängel <m.maengel@inopiae.de>
Thu, 9 Apr 2020 19:51:47 +0000 (19:51 +0000)
committerGerrit Code Review <gigi-system@dogcraft.de>
Thu, 9 Apr 2020 19:51:47 +0000 (19:51 +0000)
53 files changed:
config/generateTruststoreNRE.sh
src/club/wpia/gigi/Gigi.java
src/club/wpia/gigi/api/CreateCertificate.java
src/club/wpia/gigi/api/RevokeCertificate.java
src/club/wpia/gigi/database/DatabaseConnection.java
src/club/wpia/gigi/database/tableStructure.sql
src/club/wpia/gigi/database/upgrade/from_37.sql [new file with mode: 0644]
src/club/wpia/gigi/dbObjects/Contract.java [new file with mode: 0644]
src/club/wpia/gigi/dbObjects/ContractNotice.templ [new file with mode: 0644]
src/club/wpia/gigi/dbObjects/Domain.java
src/club/wpia/gigi/dbObjects/Job.java
src/club/wpia/gigi/dbObjects/Name.java
src/club/wpia/gigi/dbObjects/SupportedUser.java
src/club/wpia/gigi/dbObjects/User.java
src/club/wpia/gigi/pages/MainPage.templ
src/club/wpia/gigi/pages/account/History.templ
src/club/wpia/gigi/pages/account/MyContracts.java [new file with mode: 0644]
src/club/wpia/gigi/pages/account/MyContracts.templ [new file with mode: 0644]
src/club/wpia/gigi/pages/account/MyContractsDE.templ [new file with mode: 0644]
src/club/wpia/gigi/pages/account/MyDetailsContracts.templ [new file with mode: 0644]
src/club/wpia/gigi/pages/account/MyDetailsForm.java
src/club/wpia/gigi/pages/account/certs/CertificateIssueForm.java
src/club/wpia/gigi/pages/account/certs/CertificateIssueForm.templ
src/club/wpia/gigi/pages/account/certs/CertificateModificationForm.java
src/club/wpia/gigi/pages/account/certs/CertificateRequest.java
src/club/wpia/gigi/pages/account/certs/RequestCertificate.templ
src/club/wpia/gigi/pages/account/certs/RevokeSingleCertForm.java
src/club/wpia/gigi/pages/account/domain/DomainManagementForm.templ
src/club/wpia/gigi/pages/account/mail/MailManagementForm.templ
src/club/wpia/gigi/pages/admin/support/FindCertPage.java
src/club/wpia/gigi/pages/admin/support/FindCertPage.templ
src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.java
src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.templ
src/club/wpia/gigi/pages/main/KeyCompromiseForm.java
src/club/wpia/gigi/pages/wot/Rules.templ
tests/club/wpia/gigi/dbObjects/TestContract.java [new file with mode: 0644]
tests/club/wpia/gigi/dbObjects/TestDomain.java [new file with mode: 0644]
tests/club/wpia/gigi/dbObjects/TestUser.java
tests/club/wpia/gigi/dbObjects/TestUserManaged.java [new file with mode: 0644]
tests/club/wpia/gigi/dbObjects/TestVerifyName.java
tests/club/wpia/gigi/pages/TestMain.java
tests/club/wpia/gigi/pages/account/TestCertificateAdd.java
tests/club/wpia/gigi/pages/account/TestCertificateRequest.java
tests/club/wpia/gigi/pages/account/TestContract.java [new file with mode: 0644]
tests/club/wpia/gigi/pages/admin/TestSEAdminPageCertSearch.java
tests/club/wpia/gigi/pages/admin/TestSEAdminPageDetails.java
tests/club/wpia/gigi/pages/wot/TestVerification.java
tests/club/wpia/gigi/testUtils/BusinessTest.java
tests/club/wpia/gigi/testUtils/ClientBusinessTest.java
tests/club/wpia/gigi/testUtils/ConfiguredTest.java
tests/club/wpia/gigi/util/TestNotary.java
util-testing/club/wpia/gigi/pages/Manager.java
util-testing/club/wpia/gigi/pages/Manager.templ

index ca0c28a5bb5bbfc5e6eb12c9053b703f22ee408b..4955a1f9c08435f6dceb21c6b440c98463b637d2 100755 (executable)
@@ -47,8 +47,8 @@ if [[ -f "$ca.key" ]] && ! [[ -f keystore.pkcs12 ]]; then
     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 the serial_base with extensions 1 and 2. These serials are long enough to probably not collide with the "simple signer"
-    openssl x509 -req -days 356 -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 -days 356 -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
+    openssl x509 -req -days 365 -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 -days 365 -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 e76c51710e6c1c664d19c72c176d238f9f05c2ee..d33d546d9558cf02882f21365ce030c4e80eb31b 100644 (file)
@@ -57,6 +57,7 @@ import club.wpia.gigi.pages.Verify;
 import club.wpia.gigi.pages.account.ChangePasswordPage;
 import club.wpia.gigi.pages.account.FindAgentAccess;
 import club.wpia.gigi.pages.account.History;
+import club.wpia.gigi.pages.account.MyContracts;
 import club.wpia.gigi.pages.account.MyDetails;
 import club.wpia.gigi.pages.account.UserTrainings;
 import club.wpia.gigi.pages.account.certs.CertificateAdd;
@@ -195,6 +196,7 @@ public final class Gigi extends HttpServlet {
             putPage(UserTrainings.SUPPORT_PATH, new UserTrainings(true), null);
             putPage(Points.SUPPORT_PATH, new Points(true), null);
             putPage(Certificates.SUPPORT_PATH + "/*", new Certificates(true), null);
+            putPage(MyContracts.PATH, new MyContracts(), null);
 
             putPage(PasswordResetPage.PATH, new PasswordResetPage(), null);
             putPage(LogoutPage.PATH, new LogoutPage(), null);
index 1890e4a9372d4eeb1e54faaa24f6758acea61d5b..41556d131a88052a2652e6adfa6b9682a2bb512e 100644 (file)
@@ -65,7 +65,7 @@ public class CreateCertificate extends APIPoint {
             CertificateRequest cr = new CertificateRequest(ctx, csr, cp);
             Certificate result = cr.draft();
             Job job = result.issue(null, "2y", u);
-            job.waitFor(60000);
+            job.waitFor(Job.WAIT_MIN);
             if (result.getStatus() != CertificateStatus.ISSUED) {
                 resp.sendError(510, "Error, issuing timed out");
                 return;
index 81e57cfbbf3ec23a184bf4f7710ffad0d960b4c1..e8e8b9643eb1e19b2ef781ba987dda97ee7edda4 100644 (file)
@@ -42,7 +42,7 @@ public class RevokeCertificate extends APIPoint {
         }
 
         Job job = c.revoke(RevocationType.USER);
-        job.waitFor(60000);
+        job.waitFor(Job.WAIT_MIN);
         if (c.getStatus() != CertificateStatus.REVOKED) {
             resp.sendError(510, "Error, issuing timed out");
             return;
index b5e78eaaffa902764e0a29f938d3744848511b67..55a597e8c245c63ff8b11b78ad5bcf0d3ba6f1fa 100644 (file)
@@ -181,7 +181,7 @@ public class DatabaseConnection {
 
     }
 
-    public static final int CURRENT_SCHEMA_VERSION = 37;
+    public static final int CURRENT_SCHEMA_VERSION = 38;
 
     public static final int CONNECTION_TIMEOUT = 24 * 60 * 60;
 
index e9097fd1a6eb0d22fc06b5b0cd19c2e040013a29..ef7547cedcd2907fd3f47929be290d5384568b53 100644 (file)
@@ -372,7 +372,7 @@ CREATE TABLE "schemeVersion" (
   "version" smallint NOT NULL,
   PRIMARY KEY ("version")
 );
-INSERT INTO "schemeVersion" (version)  VALUES(37);
+INSERT INTO "schemeVersion" (version)  VALUES(38);
 
 DROP TABLE IF EXISTS `passwordResetTickets`;
 CREATE TABLE `passwordResetTickets` (
@@ -698,3 +698,22 @@ CREATE TABLE "jobLog" (
   PRIMARY KEY ("jobid", "attempt")
 );
 CREATE INDEX ON "jobLog" ("jobid");
+
+DROP TABLE IF EXISTS "user_contracts";
+DROP TYPE IF EXISTS "contractType";
+CREATE TYPE "contractType" AS ENUM ('RA Agent Contract', 'Org RA Agent Contract');
+
+CREATE TABLE "user_contracts" (
+  "id" serial NOT NULL,
+  "token" varchar(32) NOT NULL,
+  "memid" int NOT NULL,
+  "document" "contractType" NOT NULL,
+  "agentname" varchar(255) NOT NULL,
+  "datesigned" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  "daterevoked" timestamp DEFAULT NULL,
+  PRIMARY KEY ("id")
+);
+CREATE INDEX ON "user_contracts" ("memid");
+CREATE INDEX ON "user_contracts" ("document");
+CREATE INDEX ON "user_contracts" ("datesigned");
+CREATE INDEX ON "user_contracts" ("daterevoked");
diff --git a/src/club/wpia/gigi/database/upgrade/from_37.sql b/src/club/wpia/gigi/database/upgrade/from_37.sql
new file mode 100644 (file)
index 0000000..1ac2a4e
--- /dev/null
@@ -0,0 +1,18 @@
+DROP TABLE IF EXISTS "user_contracts";
+DROP TYPE IF EXISTS "contractType";
+CREATE TYPE "contractType" AS ENUM ('RA Agent Contract', 'Org RA Agent Contract');
+
+CREATE TABLE "user_contracts" (
+  "id" serial NOT NULL,
+  "token" varchar(32) NOT NULL,
+  "memid" int NOT NULL,
+  "document" "contractType" NOT NULL,
+  "agentname" varchar(255) NOT NULL,
+  "datesigned" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
+  "daterevoked" timestamp DEFAULT NULL,
+  PRIMARY KEY ("id")
+);
+CREATE INDEX ON "user_contracts" ("memid");
+CREATE INDEX ON "user_contracts" ("document");
+CREATE INDEX ON "user_contracts" ("datesigned");
+CREATE INDEX ON "user_contracts" ("daterevoked");
diff --git a/src/club/wpia/gigi/dbObjects/Contract.java b/src/club/wpia/gigi/dbObjects/Contract.java
new file mode 100644 (file)
index 0000000..6886dc2
--- /dev/null
@@ -0,0 +1,188 @@
+package club.wpia.gigi.dbObjects;
+
+import java.io.IOException;
+import java.util.Date;
+import java.util.HashMap;
+
+import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.database.DBEnum;
+import club.wpia.gigi.database.GigiPreparedStatement;
+import club.wpia.gigi.database.GigiResultSet;
+import club.wpia.gigi.localisation.Language;
+import club.wpia.gigi.output.template.MailTemplate;
+import club.wpia.gigi.util.RandomToken;
+
+public class Contract {
+
+    public enum ContractType implements DBEnum {
+        RA_AGENT_CONTRACT("RA Agent Contract"), ORG_RA_AGENT_CONTRACT("Org RA Agent Contract");
+
+        private final String description;
+
+        private ContractType(String description) {
+            this.description = description;
+        }
+
+        public String getDBName() {
+            return description;
+        }
+    }
+
+    private final ContractType contractType;
+
+    private final User user;
+
+    private String agentname = "";
+
+    private String token = "";
+
+    private Date dateSigned = null;
+
+    private Date dateRevoked = null;
+
+    private int contractID;
+
+    private static final MailTemplate contractNotice = new MailTemplate(Contract.class.getResource("ContractNotice.templ"));
+
+    public Contract(User u, ContractType contractType) throws GigiApiException {
+        this.contractType = contractType;
+        this.user = u;
+        try (GigiPreparedStatement query = new GigiPreparedStatement("SELECT * FROM `user_contracts` WHERE `memid`=? AND `document`=?::`contractType` and `daterevoked` IS NULL ORDER BY `datesigned` DESC LIMIT 1")) {
+            query.setInt(1, user.getId());
+            query.setEnum(2, contractType);
+            GigiResultSet rs = query.executeQuery();
+            if (rs.next()) {
+                throw new GigiApiException("Contract exists");
+            } else {
+                signContract();
+            }
+        }
+
+    }
+
+    private void signContract() throws GigiApiException {
+        agentname = user.getPreferredName().toString();
+        token = RandomToken.generateToken(32);
+        try (GigiPreparedStatement ps = new GigiPreparedStatement("INSERT INTO `user_contracts` SET `memid`=?, `token`=?, `document`=?::`contractType`,`agentname`=?")) {
+            ps.setInt(1, user.getId());
+            ps.setString(2, token);
+            ps.setEnum(3, this.contractType);
+            ps.setString(4, agentname);
+            ps.execute();
+            contractID = ps.lastInsertId();
+            dateSigned = new Date();
+
+            HashMap<String, Object> vars = new HashMap<>();
+            Language l = Language.getInstance(user.getPreferredLocale());
+            vars.put("user", agentname);
+            vars.put("actionsubject", "Signing");
+            vars.put("actionbody", "signed");
+
+            try {
+                contractNotice.sendMail(l, vars, user.getEmail());
+            } catch (IOException e) {
+                throw new GigiApiException("Sending the notification mail failed.");
+            }
+        }
+    }
+
+    public void revokeContract() throws GigiApiException {
+        try (GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE `user_contracts` SET `daterevoked`=NOW() WHERE `id`=?")) {
+            ps.setInt(1, contractID);
+            ps.execute();
+        }
+        dateRevoked = new Date();
+        HashMap<String, Object> vars = new HashMap<>();
+        Language l = Language.getInstance(user.getPreferredLocale());
+        vars.put("user", user.getPreferredName());
+        vars.put("actionsubject", "Revoking");
+        vars.put("actionbody", "revoked");
+
+        try {
+            contractNotice.sendMail(l, vars, user.getEmail());
+        } catch (IOException e) {
+            throw new GigiApiException("Sending the notification mail failed.");
+        }
+    }
+
+    private Contract(GigiResultSet rs) {
+        contractID = rs.getInt("id");
+        user = User.getById(rs.getInt("memid"));
+        token = rs.getString("token");
+        contractType = ContractType.valueOf(rs.getString("document").toUpperCase().replace(" ", "_"));
+        dateSigned = rs.getDate("datesigned");
+        dateRevoked = rs.getDate("daterevoked");
+        agentname = rs.getString("agentname");
+    }
+
+    public static Contract getById(int id) {
+        try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT * FROM `user_contracts` WHERE `id` = ?")) {
+            ps.setInt(1, id);
+            GigiResultSet rs = ps.executeQuery();
+            if ( !rs.next()) {
+                return null;
+            }
+
+            Contract c = new Contract(rs);
+
+            return c;
+        }
+    }
+
+    public int getID() {
+        return contractID;
+    }
+
+    public Date getDateSigned() {
+        return dateSigned;
+    }
+
+    public Date getDateRevoked() {
+        return dateRevoked;
+    }
+
+    public String getRAAgentName() {
+        return agentname;
+    }
+
+    public ContractType getContractType() {
+        return contractType;
+    }
+
+    public String getToken() {
+        return token;
+    }
+
+    public static boolean hasSignedContract(User u, Contract.ContractType ct) {
+        return getContractByUser(u, ct) != null;
+    }
+
+    public static Contract getRAAgentContractByUser(User u) {
+        return getContractByUser(u, Contract.ContractType.RA_AGENT_CONTRACT);
+    }
+
+    public static Contract getContractByUser(User u, ContractType ct) {
+        try (GigiPreparedStatement query = new GigiPreparedStatement("SELECT * FROM `user_contracts` WHERE `memid`=? AND `document`=?::`contractType` and `daterevoked` IS NULL ORDER BY `datesigned` DESC LIMIT 1")) {
+            query.setInt(1, u.getId());
+            query.setEnum(2, ct);
+            GigiResultSet rs = query.executeQuery();
+            if ( !rs.next()) {
+                return null;
+            }
+            Contract c = new Contract(rs);
+            return c;
+        }
+    }
+
+    public static Contract getRAAgentContractByToken(String token) throws GigiApiException {
+        try (GigiPreparedStatement query = new GigiPreparedStatement("SELECT * FROM `user_contracts` WHERE `token`=? LIMIT 1")) {
+            query.setString(1, token);
+            GigiResultSet rs = query.executeQuery();
+            if ( !rs.next()) {
+                return null;
+            }
+            Contract c = new Contract(rs);
+            return c;
+        }
+    }
+}
diff --git a/src/club/wpia/gigi/dbObjects/ContractNotice.templ b/src/club/wpia/gigi/dbObjects/ContractNotice.templ
new file mode 100644 (file)
index 0000000..e0e499d
--- /dev/null
@@ -0,0 +1,6 @@
+Subject: <?=_${actionsubject} of RA Agent Contract?>
+
+<?=_Hello ${user}, ?>
+
+<?=_you just ${actionbody} the RA Agent Contract.?>
+
index 9b356e6067f9d0364c32a23be5ee2f58f9628bde..1d3ba17de35c62b1d65322946e7b3e1407879553 100644 (file)
@@ -7,6 +7,7 @@ import java.util.List;
 import club.wpia.gigi.GigiApiException;
 import club.wpia.gigi.database.GigiPreparedStatement;
 import club.wpia.gigi.database.GigiResultSet;
+import club.wpia.gigi.dbObjects.Certificate.RevocationType;
 import club.wpia.gigi.util.DomainAssessment;
 
 public class Domain implements IdCachable, Verifyable {
@@ -72,6 +73,19 @@ public class Domain implements IdCachable, Verifyable {
                 ps.setInt(1, id);
                 ps.execute();
             }
+            LinkedList<Job> revokes = new LinkedList<Job>();
+            for (Certificate cert : fetchActiveCertificates()) {
+                revokes.add(cert.revoke(RevocationType.USER));
+            }
+            long start = System.currentTimeMillis();
+            for (Job job : revokes) {
+                int toWait = (int) (60000 + start - System.currentTimeMillis());
+                if (toWait > 0) {
+                    job.waitFor(toWait);
+                } else {
+                    break; // canceled... waited too log
+                }
+            }
         }
     }
 
index a505eb41a05a7a70bbf5e3627361803be1185b14..071b1b2ef0ced65f3eb6bfb0d217bdde6f1f4aa6 100644 (file)
@@ -11,6 +11,8 @@ import club.wpia.gigi.output.CertificateValiditySelector;
 
 public class Job implements IdCachable {
 
+    public static int WAIT_MIN = 60000;
+
     private int id;
 
     private Job(int id) {
index fbcf10eed4c94fc76c8c975102e339e31c83f7a8..ce8e7e2a1ca465eceecc4a6e84117f43fa4a18eb 100644 (file)
@@ -1,6 +1,9 @@
 package club.wpia.gigi.dbObjects;
 
 import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
 import java.util.Map;
 
 import club.wpia.gigi.GigiApiException;
@@ -11,6 +14,7 @@ import club.wpia.gigi.dbObjects.NamePart.NamePartType;
 import club.wpia.gigi.localisation.Language;
 import club.wpia.gigi.output.template.Outputable;
 import club.wpia.gigi.util.HTMLEncoder;
+import club.wpia.gigi.util.TimeConditions;
 
 public class Name implements Outputable, IdCachable {
 
@@ -512,4 +516,26 @@ public class Name implements Outputable, IdCachable {
         }
         return initals.toString();
     }
+
+    public boolean isValidVerification() {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(System.currentTimeMillis());
+        c.add(Calendar.MONTH, -TimeConditions.getInstance().getVerificationMonths());
+        String date = sdf.format(new Date(c.getTimeInMillis()));
+        try (GigiPreparedStatement query = new GigiPreparedStatement("SELECT COUNT(id) FROM `notary` WHERE `to` = ? AND `deleted` IS NULL AND (`expire` IS NULL OR `expire` > CURRENT_TIMESTAMP) AND `date` > ?")) {
+            query.setInt(1, getId());
+            query.setString(2, date);
+            GigiResultSet rs = query.executeQuery();
+
+            if (rs.next()) {
+                if (rs.getInt(1) > 0) {
+                    return true;
+                }
+            }
+
+            return false;
+        }
+    }
+
 }
index 84c43ab8e0db5261d745d200d34d69663be20ef8..1eae478f6a0eb872a03f8a14682f84958ab1e529 100644 (file)
@@ -70,7 +70,7 @@ public class SupportedUser {
         // TODO Check for open jobs!
         if (cert.getStatus() == CertificateStatus.ISSUED) {
             writeSELog("SE Revoke certificate");
-            cert.revoke(RevocationType.SUPPORT).waitFor(60000);
+            cert.revoke(RevocationType.SUPPORT).waitFor(Job.WAIT_MIN);
             // send notification to support
             String subject = "Revoke certificate";
             Outputable message = SprintfCommand.createSimple("Certificate with serial number {0} for {1} <{2}> has been revoked.", cert.getSerial(), target.getPreferredName().toString(), target.getEmail());
index 4612d033f91d4d750700f77026913fc966417661..834b6f68bc7a65148c0fd45c74afed35d30e6f20 100644 (file)
@@ -17,6 +17,7 @@ import club.wpia.gigi.GigiApiException;
 import club.wpia.gigi.database.GigiPreparedStatement;
 import club.wpia.gigi.database.GigiResultSet;
 import club.wpia.gigi.dbObjects.CATS.CATSType;
+import club.wpia.gigi.dbObjects.Certificate.RevocationType;
 import club.wpia.gigi.dbObjects.Country.CountryCodeType;
 import club.wpia.gigi.dbObjects.Verification.VerificationType;
 import club.wpia.gigi.email.EmailProvider;
@@ -242,6 +243,10 @@ public class User extends CertificateOwner {
             return false;
         }
 
+        if ( !Contract.hasSignedContract(this, Contract.ContractType.RA_AGENT_CONTRACT)) {
+            return false;
+        }
+
         return hasPassedCATS();
 
     }
@@ -334,6 +339,15 @@ public class User extends CertificateOwner {
         return false;
     }
 
+    public boolean isValidNameVerification(String name) {
+        for (Name n : getNames()) {
+            if (n.matches(name) && n.isValidVerification()) {
+                return true;
+            }
+        }
+        return false;
+    }
+
     public void updateDefaultEmail(EmailAddress newMail) throws GigiApiException {
         for (EmailAddress email : getEmails()) {
             if (email.getAddress().equals(newMail.getAddress())) {
@@ -360,16 +374,51 @@ public class User extends CertificateOwner {
             throw new GigiApiException("Can't delete user's default e-mail.");
         }
 
+        deleteEmailCerts(delMail, RevocationType.USER);
+    }
+
+    private void deleteEmailCerts(EmailAddress delMail, RevocationType rt) throws GigiApiException {
         for (EmailAddress email : getEmails()) {
             if (email.getId() == delMail.getId()) {
                 try (GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE `emails` SET `deleted`=CURRENT_TIMESTAMP WHERE `id`=?")) {
                     ps.setInt(1, delMail.getId());
                     ps.execute();
                 }
+                LinkedList<Job> revokes = new LinkedList<Job>();
+                for (Certificate cert : fetchActiveEmailCertificates(delMail.getAddress())) {
+                    cert.revoke(RevocationType.USER).waitFor(Job.WAIT_MIN);
+                }
+                long start = System.currentTimeMillis();
+                for (Job job : revokes) {
+                    int toWait = (int) (60000 + start - System.currentTimeMillis());
+                    if (toWait > 0) {
+                        job.waitFor(toWait);
+                    } else {
+                        break; // canceled... waited too log
+                    }
+                }
                 return;
             }
+
         }
         throw new GigiApiException("Email not one of user's email addresses.");
+
+    }
+
+    public Certificate[] fetchActiveEmailCertificates(String email) {
+        try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT DISTINCT `certs`.`id` FROM `certs` INNER JOIN `subjectAlternativeNames` ON `subjectAlternativeNames`.`certId` = `certs`.`id` WHERE `contents`=?  AND `type`='email' AND `revoked` IS NULL AND `expire` > CURRENT_TIMESTAMP AND `memid`=?", true)) {
+            ps.setString(1, email);
+            ps.setInt(2, 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;
+        }
     }
 
     public synchronized Verification[] getReceivedVerifications() {
index 963208e803a8aecb46b78de23379740f5d68bfa9..690480abe1b64a5a0bf2c820d8174897bb8f52be 100644 (file)
   <li><?=$orgName?></li>
  <? } ?>
  </ul>
- <? if($certlogin) { ?><p><?=_!'<a href="/account/details">'change to organisation administrator context.'</a>'?><? } else { ?><p class="alert alert-warning"><?=_You need to be logged in via certificate to get access to the organisations.?><? } ?></p>
+ <? if($certlogin) { ?><p><a href="/account/details"><?=_Change to organisation administrator context.?></a><? } else { ?><p class="alert alert-warning"><?=_You need to be logged in via certificate to get access to the organisations.?><? } ?></p>
 </div>
 <? } ?>
 
index 90f1892941777fbeb09292568711599258fb97b8..c2c0f6d60bdd0691cb3343faca545680729df642 100644 (file)
@@ -1,6 +1,6 @@
 <table class="table">
 <tbody>
-<tr><th><? if($username) { ?><?=_Support actions for ${username}?><? } else { ?><?=_Support actions?><? } ?></th></tr>
+<tr><th><? if($username) { ?><?=_Actions in user account of ${username}?><? } else { ?><?=_Actions in user account?><? } ?></th></tr>
 <? foreach($entries) { ?>
 <tr><td><?=$entry?></td></tr>
 <? } ?>
diff --git a/src/club/wpia/gigi/pages/account/MyContracts.java b/src/club/wpia/gigi/pages/account/MyContracts.java
new file mode 100644 (file)
index 0000000..b2aa53f
--- /dev/null
@@ -0,0 +1,51 @@
+package club.wpia.gigi.pages.account;
+
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.text.SimpleDateFormat;
+import java.util.Map;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import club.wpia.gigi.dbObjects.Contract;
+import club.wpia.gigi.dbObjects.User;
+import club.wpia.gigi.localisation.Language;
+import club.wpia.gigi.output.template.Template;
+import club.wpia.gigi.pages.LoginPage;
+import club.wpia.gigi.pages.Page;
+
+public class MyContracts extends Page {
+
+    public static final String PATH = "/account/contracts";
+
+    private static final Template contractDE = new Template(MyDetailsForm.class.getResource("MyContractsDE.templ"));
+
+    public MyContracts() {
+        super("My Contracts");
+    }
+
+    @Override
+    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws IOException {
+        PrintWriter out = resp.getWriter();
+        Map<String, Object> vars = getDefaultVars(req);
+        Language l = LoginPage.getLanguage(req);
+        User u = getUser(req);
+        vars.put("raname", u.getPreferredName());
+        vars.put("csdate", l.getTranslation("not yet"));
+
+        Contract c = Contract.getRAAgentContractByUser(u);
+        if (c != null) {
+            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+            vars.put("csdate", sdf.format(c.getDateSigned()));
+        }
+
+        switch (l.getLocale().toString()) {
+        case "de":
+            contractDE.output(out, getLanguage(req), vars);
+            break;
+        default:
+            getDefaultTemplate().output(out, getLanguage(req), vars);
+        }
+    }
+}
diff --git a/src/club/wpia/gigi/pages/account/MyContracts.templ b/src/club/wpia/gigi/pages/account/MyContracts.templ
new file mode 100644 (file)
index 0000000..c867d09
--- /dev/null
@@ -0,0 +1,91 @@
+<div class="panel panel-default">
+  <div class="panel-heading"><?=_RA Agents Contract?></div>
+  <div class="panel-body">
+
+<p>This contract concludes an agreement between</p>
+<p>RA Agent: <?=$raname?></p>
+<p>and</p>
+<p>World Privacy and Identity Association (WPIA) - Verein zur Foerderung von sicheren Technologien und Grundrechten im Internet (ZVR: 910115306)</p>
+<p>c/o realraum</p>
+<p>Brockmangasse 15</p>
+<p>8010 Graz</p>
+<p>Austria</p>
+<p>- CA hereafter -</p>
+<p>regarding the conduction of registration authority tasks.</p>
+<p>§ 1 Contract matter 1. The RA Agent is entitled to conduct registration authority tasks as defined on the applicant's request and based on documents presented: * validation of an applicant's identity, and * validation of an applicant's personal data</p>
+<ol start="2" style="list-style-type: decimal">
+<li>The RA Agent is entitled to conduct those tasks on behalf of the CA as long as the RA Agent fulfills the following requirements:
+<ul>
+<li>100 Verification Points,</li>
+<li>passed the RA Agent test,</li>
+<li>agrees the CA's Terms and Conditions,</li>
+<li>agrees the Dispute Resolution system, and</li>
+<li>signed this contract.</li>
+</ul></li>
+<li><p>The parties agree, that this contract does not establish an employment relationship.</p></li>
+<li><p>This contract starts on the day it is signed and is valid until it is terminated by either side with a notification period of fourteen (14) days.</p></li>
+</ol>
+<p>§ 2 Terms and Definitions</p>
+<table>
+<colgroup>
+<col width="26%" />
+<col width="73%" />
+</colgroup>
+<thead>
+<tr class="header">
+<th>Term</th>
+<th>Definition</th>
+</tr>
+</thead>
+<tbody>
+<tr class="odd">
+<td><p>Applicant</p></td>
+<td><p>Person who has an account on the systems of CA and requests to get the identity verified</p></td>
+</tr>
+<tr class="even">
+<td><p>Certificate Authority (CA)</p></td>
+<td><p>Certificate Authority who issues S/MIME certificates to individuals and organisations</p></td>
+</tr>
+<tr class="odd">
+<td><p>RA Agent</p></td>
+<td><p>Person who conducts a identity verification on behalf of the CA</p></td>
+</tr>
+<tr class="even">
+<td><p>Registration Authority (RA)</p></td>
+<td><p>Registration Authority who controls the correct entries of an applicant</p></td>
+</tr>
+<tr class="odd">
+<td><p>Verification</p></td>
+<td><p>Process to verify the identity of person</p></td>
+</tr>
+<tr class="even">
+<td><p>Verification Points</p></td>
+<td><p>Points issued during the verification</p></td>
+</tr>
+</tbody>
+</table>
+<p>§ 3 Rights and Obligations of the RA Agent</p>
+<ol style="list-style-type: decimal">
+<li><p>The RA Agent conducts the transferred duties independently according to the <a href="https://policy.wpia.club/policies/verification_policy.html">Verification Policy</a>, i.e. free of instructions other than essential for the proper execution of the transferred tasks.</p></li>
+<li><p>The RA Agent is entitled to use the brand and logo of the CA for her work as RA Agent based on this contract.</p></li>
+<li><p>The RA Agent MUST identify herself to an Applicant as RA Agent with the RA Agent Card provided by the CA.</p></li>
+<li><p>The RA Agent MUST NOT engage another person to do verifications on her behalf.</p></li>
+<li><p>The RA Agent MUST NOT verify applicants that are spouses or life partners and first-degree relatives (next of kin).</p></li>
+<li><p>All data collected and processed during the work as RA Auditor are object to strict confidentiality. They SHOULD NOT shared with others than those parties entitled by the CA and MUST be kept in accordance to their confidentiality.</p></li>
+<li><p>The RA Agent is bound to the latest version of the data protection policy of the CA, in all regards of processing personal data of applicants. Personal data collected, used, and processed are titles, full names, date of birth, and email address of an applicant. The RA Agent is not allowed to transfer personal data to other entities than the CA, unless required to do so by the European Union or Member State law to which the RA Agent is subject to. In such a case, the RA Agent MUST to inform the CA of this legal requirement and the type of data transferred, unless that a law prohibits such information on important ground of public interest.</p></li>
+</ol>
+<p>§ 4 Payment and Fees</p>
+<ol style="list-style-type: decimal">
+<li><p>The RA Agent operates free of charge for the CA. This implies that the action is voluntary, honorary and unpaid without any liability to take an action and for the CA no obligation to register social security and labour legislation related data.</p></li>
+<li><p>The RA agent is entitled to request reimbursement of travel expenses or other costs if this has been previously agreed with the applicant.</p></li>
+</ol>
+<p>§ 5 Dispute Resolution</p>
+<p>Every dispute between the RA Agent and an applicant or the RA Agent and the CA MUST be filed to the CA's dispute resolution forum (arbitration). Each party is bound to the ruling of this forum.</p>
+<p>§ 6 Termination</p>
+<p>On termination of this contract all data that is older than thirty nine (39) months MUST be deleted in a secure manner. Data younger than thirty nine (39) months MUST be collected and made available to the CA unless otherwise requested by the CA. The RA Agent is accountable for the deletion or the handover to the CA.</p>
+<p>§ 7 Subsidiary agreements</p>
+<p>Subsidiary or deviating agreements and amendments to the contract MUST be in written form.</p>
+
+       <p><?=_Signed?> <?=$csdate?></p>
+
+</div>
diff --git a/src/club/wpia/gigi/pages/account/MyContractsDE.templ b/src/club/wpia/gigi/pages/account/MyContractsDE.templ
new file mode 100644 (file)
index 0000000..b0fff31
--- /dev/null
@@ -0,0 +1,92 @@
+<div class="panel panel-default">
+  <div class="panel-heading"><?=_RA Agents Contract?></div>
+  <div class="panel-body">
+
+    <p>Dieser Vertrag schließt eine Vereinbarung zwischen</p>
+    <p>RA Agent: <?=$raname?></p>
+    <p>und</p>
+    <p>World Privacy and Identity Association (WPIA) - Verein zur Förderung von sicheren Technologien und Grundrechten im Internet (ZVR: 910115306)</p>
+    <p>c/o realraum</p>
+    <p>Brockmangasse 15</p>
+    <p>8010 Graz</p>
+    <p>Austria</p>
+    <p>- folgend CA -</p>
+    <p>für die Durchführung von &quot;Registration Authority&quot;-Aufgaben.</p>
+    <p>§ 1 Vertragsgegenstand</p>
+    <ol style="list-style-type: decimal">
+      <li>Der RA Agent ist ermächtigt &quot;Registration Authority&quot;-Aufgaben, wie folgend aufgeführt, auf Antrag des Applicant und der vorgelegten Dokumente durchzuführen:
+        <ul>
+          <li>Validierung der Identität des Applicant</li>
+          <li>Validierung der persönlichen Daten des Applicant</li>
+        </ul></li>
+      <li>Der RA Agent ist ermächtig diese Aufgaben im Auftrag der CA auszuführen sofern die folgenden Voraussetzungen erfüllt sind:
+        <ul>
+          <li>100 Verification Points</li>
+          <li>bestandener RA Agent Test</li>
+          <li>Zustimmung zu den &quot;Terms and Condition&quot; der CA</li>
+          <li>Zustimmung zum Schiedsgerichtswesen</li>
+          <li>Unterschrift für diesen Vertrag</li>
+        </ul></li>
+      <li><p>Die Parteien vereinbaren, dass aus diesem Vertrag keine Anstellungsverhältnis entsteht.</p></li>
+      <li><p>Dieser Vertrag beginnt mit dem Tag der Unterzeichnung und endet mit der Terminierung von jeder Seite mit einer Frist von vierzehn (14) Tagen nach Ankündigung.</p></li>
+    </ol>
+    <p>§ 2 Begriffe und Definitionen</p>
+    <table>
+    <colgroup>
+    <col width="26%" />
+    <col width="73%" />
+    </colgroup>
+    <thead>
+      <tr class="header">
+        <th>Begriffe</th>
+        <th>Definition</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr class="odd">
+        <td><p>Applicant</p></td>
+        <td><p>Person die ein Konto bei der CA hat und ihre Identität verifiziert haben möchte.</p></td>
+      </tr>
+<tr class="even">
+<td><p>Certificate Authority (CA)</p></td>
+<td><p>Certificate Authority, die S/MIME Zertifikate für Personen und Organisationen austellt.</p></td>
+</tr>
+<tr class="odd">
+<td><p>RA Agent Registration Authority (RA)</p></td>
+<td><p>Person, die Verifications im Auftrag der CA durchführt. Registration Authority, die die Richtigkeit der Einträge eines Applicant sicherstellt.</p></td>
+</tr>
+<tr class="even">
+<td><p>Verification</p></td>
+<td><p>Prozess die Identität einer Person festzustellen.</p></td>
+</tr>
+<tr class="odd">
+<td><p>Verification Points</p></td>
+<td><p>Punkte die während der verification vergeben werden.</p></td>
+</tr>
+</tbody>
+</table>
+<p>§ 3 Rechte und Pflichten des RA Agent</p>
+<ol style="list-style-type: decimal">
+<li><p>Der RA Agent führt die auferlegten Pflichten gemäß der <a href="https://policy.wpia.club/policies/verification_policy.html">Verification Policy</a> unabhängig durch, z.B. frei von Vorgaben anderer unter Berücksichtigung der ordnungsgemäßen Durchführung der Aufgaben.</p></li>
+<li><p>Der RA Agent ist ermächtigt, die Marke und das Logo der CA für die Arbeit als RA Agent im Rahmen dieses Vertrages zu nutzen.</p></li>
+<li><p>Der RA Agent muss (MUST) sich gegenüber dem Applicant als RA Agent mit seiner von der CA zur Verfügung gestellten RA Agent Card ausweisen.</p></li>
+<li><p>Der RA Agent darf nicht (MUST NOT) andere Personen auffordern die Verification an seiner statt durchzuführen.</p></li>
+<li><p>Der RA Agent darf nicht (MUST NOT) Ehe- und Lebenspartner und Verwandte ersten Grades als Applicant verifizieren.</p></li>
+<li><p>Alle Informationen, die während der Arbeit als RA Agent gesammelt werden, sind als vertraulich zu behandeln. Diese sollten nicht (SHOULD NOT) an Dritte außer den von der CA bestimmten weitergeleitet werden und müssen (MUST) entsprechend gesichert aufbewahrt werden.</p></li>
+<li><p>Der RA Agent ist an die aktuell gültige Version der Datenschutzrichtlinie der CA in allen Belangen der Verarbeitung der Daten des Applicant gebunden. Die gesammelten und zu verarbeitenden personenbezogenen Daten sind Titel, voller Namen, Geburtsdatum und E-Mail-Adresse des Applicant. Der RA Agent ist nicht ermächtigt, personenbezogenen Daten an Dritte außer der CA weiterzuleiten es sei den das Recht der Europäischen Union oder des Landes des RA Agent verlangt dieses. In diesem Fall muss (MUST) der RA Agent die CA über den Vorfall mit Angabe der Begründung und der übergebenen Daten melden, es sei denn ein Gesetz verbietet die Offenlegung auf Grund von gesteigerten öffentlichen Interesses.</p></li>
+</ol>
+<p>§ 4 Bezahlung und Gebühren</p>
+<ol style="list-style-type: decimal">
+<li><p>Der RA Agent arbeitet ohne Bezahlung durch die CA. Dies bedeutet, dass es sich bei dieser Tätigkeit um eine freiwillige, ehrenamtliche und unentgeltliche Tätigkeit ohne jede Arbeitsleistungsverpflichtung handelt, und seitens der CA keinerlei sozialversicherungs- und arbeitsrechtliche Meldepflichten bestehen.</p></li>
+<li><p>Der RA Agent darf Reise- oder ähnliche Kosten dem Applicant in Rechnung stellen, sofern dies im Vorfeld vereinbart wird.</p></li>
+</ol>
+<p>§ 5 Schiedsgerichtsbarkeit</p>
+<p>Jede Unstimmigkeit zwischen dem RA Agent und dem Applicant oder dem RA Agent und der CA muss (MUST) bei dem Schiedsgericht der CA eingreicht werden. Jede Partei ist an den Schiedspruch gebunden.</p>
+<p>§ 6 Vertragsbeendigung</p>
+<p>Mit Vertragsbeendigung dieses Vertrages sind (MUST) alle Daten älter als neununddreißig (39) Monate zu vernichten. Daten jünger als neununddreißig (39) Monate müssen gesammelt und der CA zukommen gelassen werden, es sei denn anders von der CA bestimmt. Der RA Agent ist dafür verantwortlich, dass die Daten entsprechend vernichtet oder an die CA weitergereicht werden.</p>
+<p>§ 7 Zusätzliche Bestimmungen</p>
+<p>Zusätzliche oder abweichende Bestimmungen oder Vereinbarungen benötigen (MUST) die Schriftform.</p>
+
+       <p><?=_Signed?> <?=$csdate?></p>
+
+</div>
diff --git a/src/club/wpia/gigi/pages/account/MyDetailsContracts.templ b/src/club/wpia/gigi/pages/account/MyDetailsContracts.templ
new file mode 100644 (file)
index 0000000..81e0b13
--- /dev/null
@@ -0,0 +1,8 @@
+<div class="panel panel-default">
+  <div class="panel-heading"><?=_RA Agents Contract?></div>
+  <div class="panel-body">
+   <button class="btn btn-info" name="action" value="viewContract"><?=_View RA Agent Contract?></button>
+   <button class="btn btn-primary" name="action" value="signContract" <?=$contractsign?> ><?=_Sign RA Agent Contract?></button>
+   <button class="btn btn-danger" name="action" value="revokeContract" <?=$contractrevoke?> ><?=_Revoke RA Agent Contract?></button>
+  </div>
+</div>
index 13d038808fb5c050b32539cf076ce15cc63750ba..bf7cbcfd1bb0724485e7536044ddad8f07364479 100644 (file)
@@ -7,6 +7,7 @@ import java.util.Set;
 import javax.servlet.http.HttpServletRequest;
 
 import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.dbObjects.Contract;
 import club.wpia.gigi.dbObjects.Group;
 import club.wpia.gigi.dbObjects.Name;
 import club.wpia.gigi.dbObjects.User;
@@ -30,6 +31,8 @@ public class MyDetailsForm extends Form {
 
     private static final Template roles = new Template(MyDetailsForm.class.getResource("MyDetailsRoles.templ"));
 
+    private static final Template contracts = new Template(MyDetailsForm.class.getResource("MyDetailsContracts.templ"));
+
     private User target;
 
     private DateSelector ds;
@@ -110,6 +113,17 @@ public class MyDetailsForm extends Form {
                     target.revokeGroup(target, toMod);
                 }
                 return new RedirectResult(MyDetails.PATH);
+            } else if ("viewContract".equals(action)) {
+                return new RedirectResult(MyContracts.PATH);
+            } else if ("signContract".equals(action)) {
+                new Contract(target, Contract.ContractType.RA_AGENT_CONTRACT);
+                return new RedirectResult(MyDetails.PATH);
+            } else if ("revokeContract".equals(action)) {
+                Contract c = Contract.getRAAgentContractByUser(target);
+                if (c != null) {
+                    c.revokeContract();
+                }
+                return new RedirectResult(MyDetails.PATH);
             } else {
                 throw new GigiApiException("Invalid action.");
             }
@@ -161,6 +175,11 @@ public class MyDetailsForm extends Form {
         vars.put("groups", new GroupList(gr, false));
         vars.put("groupSelector", selectedGroup);
         roles.output(out, l, vars);
+
+        boolean hasSignedContract = Contract.hasSignedContract(target, Contract.ContractType.RA_AGENT_CONTRACT);
+        vars.put("contractsign", hasSignedContract ? "disabled" : "");
+        vars.put("contractrevoke", hasSignedContract ? "" : "disabled");
+        contracts.output(out, l, vars);
     }
 
 }
index 81925716caba4cc8376f47b312f0242f85473751..68002958909e42fa3689adbc982d7a59a9d0f9b6 100644 (file)
@@ -14,6 +14,7 @@ import club.wpia.gigi.dbObjects.Certificate.CertificateStatus;
 import club.wpia.gigi.dbObjects.Certificate.SubjectAlternateName;
 import club.wpia.gigi.dbObjects.CertificateProfile;
 import club.wpia.gigi.dbObjects.Domain;
+import club.wpia.gigi.dbObjects.Job;
 import club.wpia.gigi.dbObjects.Organisation;
 import club.wpia.gigi.dbObjects.User;
 import club.wpia.gigi.localisation.Language;
@@ -26,7 +27,6 @@ import club.wpia.gigi.output.template.Template;
 import club.wpia.gigi.pages.LoginPage;
 import club.wpia.gigi.util.AuthorizationContext;
 import club.wpia.gigi.util.HTMLEncoder;
-import club.wpia.gigi.util.RandomToken;
 import club.wpia.gigi.util.ServerConstants;
 import club.wpia.gigi.util.ServerConstants.Host;
 
@@ -42,14 +42,11 @@ public class CertificateIssueForm extends Form {
 
     private AuthorizationContext c;
 
-    private String spkacChallenge;
-
     private boolean login;
 
     public CertificateIssueForm(HttpServletRequest hsr) {
         super(hsr);
         c = LoginPage.getAuthorizationContext(hsr);
-        spkacChallenge = RandomToken.generateToken(16);
     }
 
     private Certificate result;
@@ -65,16 +62,11 @@ public class CertificateIssueForm extends Form {
     @Override
     public SubmissionResult submit(HttpServletRequest req) throws GigiApiException {
         String csr = req.getParameter("CSR");
-        String spkac = req.getParameter("SPKAC");
         try {
             if (csr != null) {
                 cr = new CertificateRequest(c, csr);
                 // TODO cr.checkKeyStrength(out);
                 return new FormContinue();
-            } else if (spkac != null) {
-                cr = new CertificateRequest(c, spkac, spkacChallenge);
-                // TODO cr.checkKeyStrength(out);
-                return new FormContinue();
             } else if (cr != null) {
                 login = "1".equals(req.getParameter("login"));
                 issueDate.update(req);
@@ -106,7 +98,7 @@ public class CertificateIssueForm extends Form {
                     }
                     result.setDescription(description);
                 }
-                result.issue(issueDate.getFrom(), issueDate.getTo(), c.getActor()).waitFor(60000);
+                result.issue(issueDate.getFrom(), issueDate.getTo(), c.getActor()).waitFor(Job.WAIT_MIN);
                 this.result = result;
                 Certificate c = result;
                 if (c.getStatus() != CertificateStatus.ISSUED) {
@@ -138,7 +130,6 @@ public class CertificateIssueForm extends Form {
             HashMap<String, Object> vars2 = new HashMap<String, Object>(vars);
             vars2.put("csrf", getCSRFToken());
             vars2.put("csrf_name", getCsrfFieldName());
-            vars2.put("spkacChallenge", spkacChallenge);
             tIni.output(out, l, vars2);
             return;
         } else {
index da45ca658dbc9950f5f74f20e2567a097bf1f68b..dafd1576e4c3592bcaab3e510d6bda90efd14884 100644 (file)
@@ -1,7 +1,7 @@
 <h3><?=_${appName} Acceptable Use Policy?></h3>
 <p><?=_I hereby represent that I am fully authorized by the owner of the information contained in the CSR sent to ${appName} to apply for an Digital Certificate for secure and authenticated electronic transactions. I understand that a digital certificate serves to identify the Subscriber for the purposes of electronic communication and that the management of the private keys associated with such certificates is the responsibility of the subscriber's technical staff and/or contractors.?></p>
 
-<p><?=_${appName}'s public certification services are governed by a CPS as amended from time to time which is incorporated into this Agreement by reference. The Subscriber will use the SSL Server Certificate in accordance with ${appName}'s CPS and supporting documentation published !(/policy/CPS)here!'</a>'.?></p>
+<p><?=_${appName}'s public certification services are governed by a CPS as amended from time to time which is incorporated into this Agreement by reference. The Subscriber will use client and server certificates in accordance with ${appName}'s CPS and supporting documentation published !(/policy/CPS)here!'</a>'.?></p>
 
 <p><?=_If the Subscriber's name and/or domain name registration change the subscriber will immediately inform ${appName} Inc. who shall revoke the digital certificate. When the Digital Certificate expires or is revoked the company will permanently remove the certificate from the server on which it is installed and will not use it for any purpose thereafter. The person responsible for key management and security is fully authorized to install and utilize the certificate to represent this organization's electronic presence.?></p>
 <span id="placeholderName" class="js-hint"><?=$placeholderName?></span>
@@ -55,7 +55,7 @@
   </tr>
   <? if($orga) { ?>
   <tr>
-    <td><?=_Department?></td>
+    <td><?=_Department (OU)?></td>
     <td align="left"><input type='text' name='OU' value='<?=$department?>'/></td>
   </tr>
   <? } ?>
index 7ca73eb05908c3bc3c4c999f70fa0e9194855312..dd6161132a368a4ec5f2d1d01cbb22fdc4dcf85c 100644 (file)
@@ -54,7 +54,7 @@ public class CertificateModificationForm extends Form {
         }
         long start = System.currentTimeMillis();
         for (Job job : revokes) {
-            int toWait = (int) (60000 + start - System.currentTimeMillis());
+            int toWait = (int) (Job.WAIT_MIN + start - System.currentTimeMillis());
             if (toWait > 0) {
                 job.waitFor(toWait);
             } else {
index cdf4dd41eed765b5959cb979d571a9462d0bd89f..28a5b098b75f73254884217743a7f3e4f4e2fddd 100644 (file)
@@ -371,7 +371,7 @@ public class CertificateRequest {
                 } else {
                     // remove
                     error.mergeInto(new GigiApiException(SprintfCommand.createSimple(//
-                            "The requested subject alternate name email address \"{0}\" needs an email ping within the past {1} months.", san.getType().toString().toLowerCase() + ":" + san.getName(), TimeConditions.getInstance().getEmailPingMonths())));
+                            "The requested subject alternate name email address \"{0}\" needs a verification via email ping within the past {1} months.", san.getType().toString().toLowerCase() + ":" + san.getName(), TimeConditions.getInstance().getEmailPingMonths())));
                     break;
                 }
             }
@@ -443,7 +443,7 @@ public class CertificateRequest {
                 subject.put("OU", ou);
             }
         }
-        System.out.println(subject);
+
         if ( !error.isEmpty()) {
             throw error;
         }
@@ -493,7 +493,11 @@ public class CertificateRequest {
             User u = (User) ctx.getTarget();
             if (name != null && u.isValidName(name)) {
                 if (realIsOK) {
-                    verifiedCN = name;
+                    if (u.isValidNameVerification(name)) {
+                        verifiedCN = name;
+                    } else {
+                        error.mergeInto(new GigiApiException(SprintfCommand.createSimple("The entered name needs a valid verification within the last {0} months.", TimeConditions.getInstance().getVerificationMonths())));
+                    }
                 } else {
                     error.mergeInto(new GigiApiException("Your real name is not allowed in this certificate."));
                     if (defaultIsOK) {
index 89abb8df8afff9304f8ffe40fbf2ee0c9bbe5d63..274ad8eb9b9c0bb3f77d014058155b36e1c7873d 100644 (file)
@@ -1,6 +1,6 @@
 <p><?=_${appName} offers two ways to create a certificate.?>
-<?=_One is to paste a certificate signing request (CSR) created from an existing or newly created private key.?> <?=_If you do not know what a CSR is or how to create one take a look at the !(/kb/CSR)FAQ!'</a>'.?>
-<?=_As an alternative you can generate the private key inside your browser and export it once the certificate has been issued.?></p>
+<?=_One is to paste a certificate signing request (CSR) created from an existing or newly created private key.?> <?=_If you do not know what a CSR is or how to create one take a look at the !(/kb/CSR)FAQ!'</a>'.?></p>
+<p><?=_For inexperienced users the usage of !(/kb/XCA)XCA!'</a>' is recommended and described in !(/kb/XCADocu)XCA usage documentation!'</a>'?></p>
 <form method="post">
 <table class="table">
   <thead>
   </tbody>
 </table>
 </form>
-<form method="post">
-<table class="table">
-  <thead>
-  <tr>
-    <th colspan="2" class="title"><?=_Create a fresh key in the browser (SPKAC)?></th>
-  </tr>
-  </thead>
-  <tbody>
-  <tr>
-    <td><?=_I do not have a CSR.?></td>
-    <td align="left">
-      <?=_key size (2048 recommended)?>: <keygen name="SPKAC" challenge="<?=$spkacChallenge?>"/>
-    </td>
-  </tr>
-  <tr>
-    <td colspan="2">
-     <input class="btn btn-primary" type="submit" name="process" value="<?=_Next?>" />
-     <input type='hidden' name='<?=$csrf_name?>' value='<?=$csrf?>'>
-    </td>
-  </tr>
-  </tbody>
-</table>
-</form>
+
index 30e404c0cc7b928b95de44eae5f394a6a6ca1c7a..12a7daa92cecac4b510b149e025a389451f9d0af 100644 (file)
@@ -8,6 +8,7 @@ import javax.servlet.http.HttpServletRequest;
 import club.wpia.gigi.GigiApiException;
 import club.wpia.gigi.dbObjects.Certificate;
 import club.wpia.gigi.dbObjects.Certificate.RevocationType;
+import club.wpia.gigi.dbObjects.Job;
 import club.wpia.gigi.dbObjects.SupportedUser;
 import club.wpia.gigi.localisation.Language;
 import club.wpia.gigi.output.template.Form;
@@ -32,7 +33,7 @@ public class RevokeSingleCertForm extends Form {
         if (target != null) {
             target.revokeCertificate(c);
         } else {
-            c.revoke(RevocationType.USER).waitFor(60000);
+            c.revoke(RevocationType.USER).waitFor(Job.WAIT_MIN);
         }
         return new RedirectResult(req.getPathInfo());
     }
index d022c27146501a08fa43c08358d163f5f56733b7..c4507f2e14844f9c463c624757b9260d20ce875f 100644 (file)
@@ -10,7 +10,7 @@
   </tr>
   <? foreach($domains) { ?>
   <tr>
-       <td><? if($buttonvisible) { ?><button class="btn btn-danger btn-confirm" data-confirm="<?=_Do you really want to delete this domain??>" data-reply="<?=_Cancel?>,<?=_Confirm?>" type="submit" name="delete" value="<?=$id?>">Delete</button><? } ?></td>
+       <td><? if($buttonvisible) { ?><button class="btn btn-danger btn-confirm" data-confirm="<?=_All certificates that reference the affected domain (including those where several different domains appear) will be revoked.?>" data-reply="<?=_Cancel?>,<?=_Confirm?>" type="submit" name="delete" value="<?=$id?>">Delete</button><? } ?></td>
        <td><?=$status?></td>
        <td><? if($domainhref) { ?><a href='<?=$domainhref?>'><?=$domain?><? } else { ?><?=$domain?><? } ?></a></td>
   </tr>
index 2449bca10f0f2ee6b6a0acf60cda9a7b62986a33..c71306f0a27b89c476b095c6b8af6b3ef6651e4e 100644 (file)
@@ -19,7 +19,7 @@
                <td><?=$verification?></td>
                <td><? if($last_verification) { ?><?=$last_verification?><? } else { ?><?=_N/A?><? } ?></td>
                <td><?=$address?></td>
-               <td><button class="btn btn-danger btn-confirm" data-confirm="<?=_Do you really want to delete this email address??>" data-reply="<?=_Cancel?>,<?=_Confirm?>" type="submit" name="delete" value="<?=$id?>"<?=$deletable?>><?=_Delete?></button></td>
+               <td><button class="btn btn-danger btn-confirm" data-confirm="<?=_Do you really want to delete this email address? ALL active certificates that reference the affected email address (including multiple emails, code signing, TLS and S/MIME) will be revoked.?>" data-reply="<?=_Cancel?>,<?=_Confirm?>" type="submit" name="delete" value="<?=$id?>"<?=$deletable?>><?=_Delete?></button></td>
                <td><button class="btn btn-primary" type="submit" name="reping" value="<?=$id?>"><?=_Request reping?></button></td>
        </tr>
  <? } ?>
index ea5ac1e9d8924ef512cf50103444f4919506b9d5..19064a66882f16fbbd97d60a3e59b6e694808f68 100644 (file)
@@ -57,8 +57,8 @@ public class FindCertPage extends Page {
 
                     @Override
                     public void apply(Certificate t, Language l, Map<String, Object> vars) {
-                        vars.put("id", t.getId());
                         vars.put("serial", t.getSerial());
+                        vars.put("draftinfo", t.getStatus() == CertificateStatus.DRAFT ? l.getTranslation("Draft") : "");
 
                         if (t.getStatus() == CertificateStatus.REVOKED) {
                             vars.put("revoked", t.getRevocationDate());
index 0e0fa25b0123e524fec64d6e792f8f789bf9f0d9..1648f0dd2c0bffd17b13ed1853eb23e546a51989 100644 (file)
@@ -1,11 +1,10 @@
 <p><?=_Multiple certificates?>: <?=$limit?></p>
 <table class="table">
 <tr>
-<th>Id</th><th><?=_Serial number?></th><th><?=_SAN?></th><th><?=_Revocation Date?></th></tr>
+<th><?=_Serial number?></th><th><?=_SAN?></th><th><?=_Revocation Date?></th></tr>
 <? foreach($certtable) {?>
   <tr>
-    <td><a href="/support/certs/<?=$serial?>"><?=$id?></a></td>
-    <td><a href="/support/certs/<?=$serial?>"><?=$serial?></a></td>
+    <td><a href="/support/certs/<?=$serial?>"><?=$serial?></a> <?=$draftinfo?></td>
     <td>
     <? foreach($san) {?>
        <?=$entry?>
index 71909cf24b48d0793262125897a96dc038c45db1..0ed43d9d589afd1d01f8f0bbf2a023856e0b56a0 100644 (file)
@@ -8,6 +8,7 @@ import javax.servlet.http.HttpServletRequest;
 
 import club.wpia.gigi.Gigi;
 import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.dbObjects.Contract;
 import club.wpia.gigi.dbObjects.Group;
 import club.wpia.gigi.dbObjects.Name;
 import club.wpia.gigi.dbObjects.SupportedUser;
@@ -112,6 +113,7 @@ public class SupportUserDetailsForm extends Form {
 
         });
         vars.put("agent", user.canVerify());
+        vars.put("agentcontract", Contract.hasSignedContract(user, Contract.ContractType.RA_AGENT_CONTRACT));
         vars.put("dob", dobSelector);
         vars.put("verificationPoints", user.getVerificationPoints());
         vars.put("exppoints", user.getExperiencePoints());
index e357564ef0e05f7f7375e5b0062266728fcd7c40..5f5ec991231f8e1e0c03d2641e372020bc0d7d22 100644 (file)
@@ -28,7 +28,7 @@
                 <? if($agent) { ?>
                 <?=_Yes?>
                 <? } else { ?>
-                <?=_No?>
+                <?=_No?> (<?=_RA Agent Contract signed:?> <? if($agentcontract) { ?><?=_Yes?><? } else { ?><?=_No?><?}?>)
                 <? } ?>
             </td>
         </tr>
index e0690844d69189034628fb5a4cc2ce571918b103..ae3e30e02e79db35a90d8c76a9e695a0016f5fe8 100644 (file)
@@ -166,7 +166,7 @@ public class KeyCompromiseForm extends Form {
             throw new GigiApiException("Sending the notification mail failed.");
         }
         Job j = c.revoke(challenge, Base64.getEncoder().encodeToString(signature), message);
-        if ( !j.waitFor(60000)) {
+        if ( !j.waitFor(Job.WAIT_MIN)) {
             throw new PermamentFormException(new GigiApiException("Revocation timed out."));
         }
         if (c.getStatus() != CertificateStatus.REVOKED) {
index 4d37097342d4c733ff914b943f56466af9e42874..8ab1e6238f0fe4fa76a81381f9335b70edc05974 100644 (file)
@@ -25,7 +25,7 @@
 <h4><?=_Fees?></h4>
 <p><?=_You may charge a fee for your expenses if the Applicant has been informed of such costs beforehand.?></p>
 
-<p><?=_Liability?></h4>
+<h4><?=_Liability?></h4>
 <?=_A ${appName} RA Agent who knowingly, or reasonably ought to have known, verifies the identity of an Applicant contrary to the underlying policy may be held liable.?></p>
 
 <h4><?=_Verification Points?></h4>
diff --git a/tests/club/wpia/gigi/dbObjects/TestContract.java b/tests/club/wpia/gigi/dbObjects/TestContract.java
new file mode 100644 (file)
index 0000000..98648f0
--- /dev/null
@@ -0,0 +1,91 @@
+package club.wpia.gigi.dbObjects;
+
+import static org.junit.Assert.*;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.testUtils.ClientBusinessTest;
+import club.wpia.gigi.testUtils.TestEmailReceiver.TestMail;
+import club.wpia.gigi.util.RandomToken;
+
+public class TestContract extends ClientBusinessTest {
+
+    @Test
+    public void testContract() throws GigiApiException {
+
+        assertEquals(Contract.getContractByUser(u, Contract.ContractType.RA_AGENT_CONTRACT), null);
+
+        assertFalse(Contract.hasSignedContract(u, Contract.ContractType.RA_AGENT_CONTRACT));
+
+        Contract c = Contract.getRAAgentContractByUser(u);
+        assertEquals(c, null);
+
+        c = new Contract(u, Contract.ContractType.RA_AGENT_CONTRACT);
+        TestMail rc = getMailReceiver().receive(u.getEmail());
+
+        assertEquals(u.getEmail(), rc.getTo());
+        assertThat(rc.getMessage(), CoreMatchers.containsString("signed the RA Agent Contract"));
+        assertEquals(u.getPreferredName().toString(), c.getRAAgentName());
+        assertTrue(Contract.hasSignedContract(u, Contract.ContractType.RA_AGENT_CONTRACT));
+
+        Contract c1 = null;
+        try {
+            c1 = new Contract(u, Contract.ContractType.RA_AGENT_CONTRACT);
+            fail("double add contract must fail");
+        } catch (GigiApiException e) {
+            assertEquals("Contract exists", e.getMessage());
+        }
+
+        c1 = Contract.getContractByUser(u, Contract.ContractType.RA_AGENT_CONTRACT);
+        assertEquals(c.getID(), c1.getID());
+
+        c1 = Contract.getRAAgentContractByUser(u);
+        assertEquals(c.getID(), c1.getID());
+
+        c1 = Contract.getRAAgentContractByToken(c.getToken());
+        assertEquals(c.getID(), c1.getID());
+
+        c1 = Contract.getRAAgentContractByToken(RandomToken.generateToken(16));
+        assertEquals(c1, null);
+
+    }
+
+    @Test
+    public void testRevokeContract() throws GigiApiException {
+        Contract c = new Contract(u, Contract.ContractType.RA_AGENT_CONTRACT);
+
+        TestMail rc = getMailReceiver().receive(u.getEmail());
+        assertThat(rc.getMessage(), CoreMatchers.containsString("signed the RA Agent Contract"));
+
+        c.revokeContract();
+
+        rc = getMailReceiver().receive(u.getEmail());
+        assertEquals(u.getEmail(), rc.getTo());
+        assertThat(rc.getMessage(), CoreMatchers.containsString("revoked the RA Agent Contract"));
+        assertFalse(Contract.hasSignedContract(u, Contract.ContractType.RA_AGENT_CONTRACT));
+
+        Contract c1 = new Contract(u, Contract.ContractType.RA_AGENT_CONTRACT);
+        rc = getMailReceiver().receive(u.getEmail());
+
+        assertNotEquals(c.getID(), c1.getID());
+    }
+
+    @Test
+    public void testContractInt() throws GigiApiException {
+        Contract c = new Contract(u, Contract.ContractType.RA_AGENT_CONTRACT);
+
+        TestMail rc = getMailReceiver().receive(u.getEmail());
+        assertThat(rc.getMessage(), CoreMatchers.containsString("signed the RA Agent Contract"));
+
+        Contract c1 = Contract.getById(c.getID());
+
+        assertEquals(c.getID(), c1.getID());
+        assertEquals(c.getContractType(), c1.getContractType());
+
+        c1 = Contract.getById(0);
+        assertEquals(null, c1);
+    }
+
+}
diff --git a/tests/club/wpia/gigi/dbObjects/TestDomain.java b/tests/club/wpia/gigi/dbObjects/TestDomain.java
new file mode 100644 (file)
index 0000000..93afd63
--- /dev/null
@@ -0,0 +1,43 @@
+package club.wpia.gigi.dbObjects;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+
+import org.junit.Test;
+
+import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.dbObjects.Certificate.CSRType;
+import club.wpia.gigi.dbObjects.Certificate.SANType;
+import club.wpia.gigi.testUtils.ManagedTest;
+
+public class TestDomain extends ManagedTest {
+
+    @Test
+    public void testDeleteDomainWithCertificate() throws GigiApiException, GeneralSecurityException, IOException, InterruptedException {
+        User u = User.getById(createVerificationUser("Kurti", "Hansel", createUniqueName() + "@email.com", TEST_PASSWORD));
+        String domain = createUniqueName() + ".org";
+        Domain d = new Domain(u, u, domain);
+        KeyPair kp = generateKeypair();
+        String key = generatePEMCSR(kp, "CN=" + domain);
+        Certificate c = new Certificate(u, u, Certificate.buildDN("CN", domain), Digest.SHA256, key, CSRType.CSR, getClientProfile(), new Certificate.SubjectAlternateName(SANType.DNS, domain));
+        c.issue(null, "2y", u).waitFor(60000);
+
+        c = new Certificate(u, u, Certificate.buildDN("CN", domain), Digest.SHA256, key, CSRType.CSR, getClientProfile(), new Certificate.SubjectAlternateName(SANType.DNS, "www." + domain));
+        c.issue(null, "2y", u).waitFor(60000);
+
+        Certificate[] certs = d.fetchActiveCertificates();
+        assertEquals(2, certs.length);
+
+        d.delete();
+
+        certs = u.getCertificates(false);
+        assertEquals(0, certs.length);
+        certs = d.fetchActiveCertificates();
+        assertEquals(0, certs.length);
+
+    }
+
+}
index 6ec0abecd260381db8a32a64601194072cc0114f..7d7b6dba93692942e61596b6651aa7fb9cefbbd5 100644 (file)
@@ -107,6 +107,19 @@ public class TestUser extends ClientBusinessTest {
         assertTrue(u.hasValidTTPAgentChallenge());
     }
 
+    @Test
+    public void testHasContract() throws GigiApiException {
+        assertEquals(false, Contract.hasSignedContract(u, Contract.ContractType.RA_AGENT_CONTRACT));
+
+        Contract c = new Contract(u, Contract.ContractType.RA_AGENT_CONTRACT);
+        getMailReceiver().receive(u.getEmail());
+        assertEquals(true, Contract.hasSignedContract(u, Contract.ContractType.RA_AGENT_CONTRACT));
+
+        c.revokeContract();
+        getMailReceiver().receive(u.getEmail());
+        assertEquals(false, Contract.hasSignedContract(u, Contract.ContractType.RA_AGENT_CONTRACT));
+    }
+
     @Test
     public void testWriteUserLog() throws GigiApiException {
         String type = "Log test";
@@ -121,4 +134,19 @@ public class TestUser extends ClientBusinessTest {
         assertThat(result, ArrayContains.contains(CoreMatchers.equalTo(type)));
     }
 
+    @Test
+    public void testValidVerification() throws GigiApiException {
+        User u0 = User.getById(createVerifiedUser("f", "l", createUniqueName() + "@email.com", TEST_PASSWORD));
+        assertFalse(u0.isValidNameVerification(u0.getPreferredName().toString()));
+
+        add100Points(u0.getId());
+        assertTrue(u0.isValidNameVerification(u0.getPreferredName().toString()));
+
+        setVerificationDateToPast(u0.getPreferredName());
+        assertFalse(u0.isValidNameVerification(u0.getPreferredName().toString()));
+
+        add100Points(u0.getId());
+        assertTrue(u0.isValidNameVerification(u0.getPreferredName().toString()));
+    }
+
 }
diff --git a/tests/club/wpia/gigi/dbObjects/TestUserManaged.java b/tests/club/wpia/gigi/dbObjects/TestUserManaged.java
new file mode 100644 (file)
index 0000000..d0906ae
--- /dev/null
@@ -0,0 +1,39 @@
+package club.wpia.gigi.dbObjects;
+
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+
+import org.junit.Test;
+
+import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.dbObjects.Certificate.CSRType;
+import club.wpia.gigi.dbObjects.Certificate.SANType;
+import club.wpia.gigi.testUtils.ManagedTest;
+
+public class TestUserManaged extends ManagedTest {
+
+    @Test
+    public void testDeleteEmailWithCertificate() throws GigiApiException, GeneralSecurityException, IOException, InterruptedException {
+
+        int id = createVerifiedUser("Test", "User", createUniqueName() + "test@test.tld", TEST_PASSWORD);
+        String email = createUniqueName() + "test@test.tld";
+        User u = User.getById(id);
+        Certificate[] certs = u.getCertificates(false);
+        int certCount = certs.length;
+        EmailAddress testAddress = createVerifiedEmail(u, email);
+        KeyPair kp = generateKeypair();
+        String key = generatePEMCSR(kp, "CN=" + email);
+        Certificate c = new Certificate(u, u, Certificate.buildDN("CN", email), Digest.SHA256, key, CSRType.CSR, getClientProfile(), new Certificate.SubjectAlternateName(SANType.EMAIL, email));
+        c.issue(null, "2y", u).waitFor(Job.WAIT_MIN);
+
+        u.deleteEmail(testAddress);
+
+        assertFalse(c.getRevocationDate().toString().isEmpty());
+        certs = u.getCertificates(false);
+        assertEquals(certCount, certs.length);
+
+    }
+}
index 7f3af269d484acbc4d1baaa252b8b2eebd664b2e..7ad2c7f4532919a35d8e241726519d6908478815 100644 (file)
@@ -5,13 +5,9 @@ import static org.junit.Assert.*;
 import org.junit.Test;
 
 import club.wpia.gigi.GigiApiException;
-import club.wpia.gigi.dbObjects.Country;
-import club.wpia.gigi.dbObjects.Name;
-import club.wpia.gigi.dbObjects.NamePart;
-import club.wpia.gigi.dbObjects.User;
-import club.wpia.gigi.dbObjects.Verification.VerificationType;
 import club.wpia.gigi.dbObjects.Country.CountryCodeType;
 import club.wpia.gigi.dbObjects.NamePart.NamePartType;
+import club.wpia.gigi.dbObjects.Verification.VerificationType;
 import club.wpia.gigi.testUtils.ClientBusinessTest;
 import club.wpia.gigi.util.Notary;
 
@@ -35,4 +31,20 @@ public class TestVerifyName extends ClientBusinessTest {
         assertEquals(10, n4.getVerificationPoints());
         assertEquals(10, u.getMaxVerifyPoints());
     }
+
+    @Test
+    public void testValidVerification() throws GigiApiException {
+        User u0 = User.getById(createVerifiedUser("f", "l", createUniqueName() + "@email.com", TEST_PASSWORD));
+        assertFalse(u0.getPreferredName().isValidVerification());
+
+        add100Points(u0.getId());
+        assertTrue(u0.getPreferredName().isValidVerification());
+
+        setVerificationDateToPast(u0.getPreferredName());
+        assertFalse(u0.getPreferredName().isValidVerification());
+
+        add100Points(u0.getId());
+        assertTrue(u0.getPreferredName().isValidVerification());
+    }
+
 }
index be642f03101825d549f33493e76a484cf58192a6..4c0ae78625a0028dfa5eae2025f90dbd42056d09 100644 (file)
@@ -81,7 +81,7 @@ public class TestMain extends ClientTest {
         authenticate((HttpURLConnection) uc);
         String content = IOUtils.readURL(uc);
 
-        assertThat(content, containsString("change to organisation administrator context"));
+        assertThat(content, containsString("Change to organisation administrator context"));
         assertThat(content, containsString("You are authenticated via certificate, so you will be able to perform all actions."));
     }
 
@@ -97,9 +97,10 @@ public class TestMain extends ClientTest {
 
     @Test
     public void testValidChallenges() throws GeneralSecurityException, IOException, GigiApiException, InterruptedException {
-
+        insertRAContract(u.getId());
         // test RA Agent challenge
         cookie = cookieWithCertificateLogin(u);
+
         testChallengeText("you need to pass the RA Agent Challenge", false);
 
         add100Points(u.getId());
index 0d83f301f0320db78b5717647f29609df5557017..16594e1b2efa834f952a478d898b48a277d51efa 100644 (file)
@@ -23,7 +23,6 @@ import java.security.cert.CertificateFactory;
 import java.security.cert.X509Certificate;
 import java.text.SimpleDateFormat;
 import java.util.Arrays;
-import java.util.Base64;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.TimeZone;
@@ -33,7 +32,6 @@ import java.util.regex.Pattern;
 
 import org.junit.Test;
 
-import club.wpia.gigi.crypto.SPKAC;
 import club.wpia.gigi.dbObjects.CertificateOwner;
 import club.wpia.gigi.dbObjects.Digest;
 import club.wpia.gigi.pages.account.certs.CertificateAdd;
@@ -56,7 +54,6 @@ import sun.security.x509.GeneralNameInterface;
 import sun.security.x509.GeneralNames;
 import sun.security.x509.RFC822Name;
 import sun.security.x509.SubjectAlternativeNameExtension;
-import sun.security.x509.X509Key;
 
 public class TestCertificateAdd extends ClientTest {
 
@@ -125,12 +122,6 @@ public class TestCertificateAdd extends ClientTest {
         }, res);
     }
 
-    @Test
-    public void testSPKAC() throws GeneralSecurityException, IOException {
-        testSPKAC(false);
-        testSPKAC(true);
-    }
-
     @Test
     public void testIssue() throws IOException, GeneralSecurityException {
         HttpURLConnection huc = sendCertificateForm("description");
@@ -312,32 +303,6 @@ public class TestCertificateAdd extends ClientTest {
         return uc;
     }
 
-    protected String testSPKAC(boolean correctChallenge) throws GeneralSecurityException, IOException {
-        HttpURLConnection uc = (HttpURLConnection) ncert.openConnection();
-        uc.setRequestProperty("Cookie", cookie);
-        String s = IOUtils.readURL(uc);
-
-        csrf = extractPattern(s, Pattern.compile("<input [^>]*name='csrf' [^>]*value='([^']*)'>"));
-        String challenge = extractPattern(s, Pattern.compile("<keygen [^>]*name=\"SPKAC\" [^>]*challenge=\"([^\"]*)\"/>"));
-
-        SPKAC spk = new SPKAC((X509Key) kp.getPublic(), challenge + (correctChallenge ? "" : "b"));
-        Signature sign = Signature.getInstance("SHA512WithRSA");
-        sign.initSign(kp.getPrivate());
-        try {
-            String[] res = fillOutFormDirect("SPKAC=" + URLEncoder.encode(Base64.getEncoder().encodeToString(spk.getEncoded(sign)), "UTF-8"));
-            if ( !correctChallenge) {
-                fail("Should not succeed with wrong challenge.");
-            }
-            assertArrayEquals(new String[] {
-                    "client", CertificateRequest.DEFAULT_CN, "", Digest.SHA512.toString()
-            }, res);
-        } catch (OnPageError e) {
-            String error = fetchStartErrorMessage(e.getMessage());
-            assertTrue(error, error.startsWith("<p>Challenge mismatch"));
-        }
-        return csrf;
-    }
-
     private PKCS10Attributes buildAtts(ObjectIdentifier[] ekuOIDs, GeneralNameInterface... SANs) throws IOException {
         CertificateExtensions attributeValue = new CertificateExtensions();
         GeneralNames names = new GeneralNames();
index 023be5ae543f307ba95376b116fd419bc1391f0e..372f2bfaba86144d4023d921a38e5d115ac71d3a 100644 (file)
@@ -15,6 +15,7 @@ import club.wpia.gigi.database.GigiPreparedStatement;
 import club.wpia.gigi.dbObjects.EmailAddress;
 import club.wpia.gigi.dbObjects.Group;
 import club.wpia.gigi.pages.account.certs.CertificateRequest;
+import club.wpia.gigi.testUtils.ClientBusinessTest;
 import club.wpia.gigi.testUtils.ClientTest;
 import club.wpia.gigi.util.AuthorizationContext;
 import club.wpia.gigi.util.TimeConditions;
@@ -108,7 +109,7 @@ public class TestCertificateRequest extends ClientTest {
             cr.draft();
             fail();
         } catch (GigiApiException e) {
-            assertThat(e.getMessage(), containsString("needs an email ping within the past"));
+            assertThat(e.getMessage(), containsString("needs a verification via email ping within the past"));
         }
 
     }
@@ -131,7 +132,22 @@ public class TestCertificateRequest extends ClientTest {
             cr.draft();
             fail();
         } catch (GigiApiException e) {
-            assertThat(e.getMessage(), containsString("needs an email ping within the past"));
+            assertThat(e.getMessage(), containsString("needs a verification via email ping within the past"));
+        }
+
+    }
+
+    @Test
+    public void testVerificationInPast() throws IOException, GeneralSecurityException, GigiApiException {
+
+        ClientBusinessTest.setVerificationDateToPast(u.getPreferredName());
+        try {
+            CertificateRequest cr = new CertificateRequest(ac, generatePEMCSR(kp, "CN=a ab"));
+            cr.update(u.getPreferredName().toString(), "SHA512", "client-a", null, null, "email:" + email);
+            cr.draft();
+            fail();
+        } catch (GigiApiException e) {
+            assertThat(e.getMessage(), containsString("The entered name needs a valid verification within the last"));
         }
 
     }
diff --git a/tests/club/wpia/gigi/pages/account/TestContract.java b/tests/club/wpia/gigi/pages/account/TestContract.java
new file mode 100644 (file)
index 0000000..4628ec8
--- /dev/null
@@ -0,0 +1,55 @@
+package club.wpia.gigi.pages.account;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.*;
+
+import java.io.IOException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+
+import org.junit.Test;
+
+import club.wpia.gigi.testUtils.ClientTest;
+import club.wpia.gigi.testUtils.IOUtils;
+
+public class TestContract extends ClientTest {
+
+    @Test
+    public void TestContractSignRevoke() throws IOException {
+        // empty contract
+        String res = IOUtils.readURL(get(MyContracts.PATH));
+        assertThat(res, containsString("This contract concludes an agreement between"));
+        assertThat(res, containsString("not yet"));
+
+        // sign contract
+        executeBasicWebInteraction(cookie, MyDetails.PATH, "action=signContract", 0);
+        getMailReceiver().receive(u.getEmail());
+        res = IOUtils.readURL(get(MyContracts.PATH));
+        assertThat(res, containsString("This contract concludes an agreement between"));
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        assertThat(res, containsString(sdf.format(new Date())));
+
+        // sign contract
+        executeBasicWebInteraction(cookie, MyDetails.PATH, "action=revokeContract", 0);
+        getMailReceiver().receive(u.getEmail());
+        res = IOUtils.readURL(get(MyContracts.PATH));
+        assertThat(res, containsString("This contract concludes an agreement between"));
+        assertThat(res, containsString("not yet"));
+    }
+
+    @Test
+    public void TestLanguageSwitch() throws IOException {
+
+        String res = IOUtils.readURL(get(MyContracts.PATH));
+        assertThat(res, containsString("This contract concludes an agreement between"));
+        // switch to German
+        executeBasicWebInteraction(cookie, MyDetails.PATH, "lang=de", 0);
+        res = IOUtils.readURL(get(MyContracts.PATH));
+        assertThat(res, containsString("Dieser Vertrag schließt eine Vereinbarung zwischen"));
+        // switch to Turkish, should return default language English
+        executeBasicWebInteraction(cookie, MyDetails.PATH, "lang=tr", 0);
+        res = IOUtils.readURL(get(MyContracts.PATH));
+        assertThat(res, containsString("This contract concludes an agreement between"));
+    }
+
+}
index cf9b403b253aa6438b7c38f0483e39832b0120ae..0d556419bbdd8095139b60928eda63925bce1f39 100644 (file)
@@ -1,5 +1,6 @@
 package club.wpia.gigi.pages.admin;
 
+import static org.hamcrest.CoreMatchers.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
@@ -89,6 +90,24 @@ public class TestSEAdminPageCertSearch extends SEClientTest {
         assertThat(result, CoreMatchers.containsString("N/A"));
     }
 
+    @Test
+    public void testShowDraft() throws GeneralSecurityException, IOException, GigiApiException, InterruptedException {
+        KeyPair kp = generateKeypair();
+        String key = generatePEMCSR(kp, "CN=" + certMail);
+        Certificate c1 = new Certificate(u, u, Certificate.buildDN("CN", certMail), Digest.SHA512, key, CSRType.CSR, getClientProfile(), new Certificate.SubjectAlternateName(SANType.EMAIL, certMail));
+        URLConnection uc = post(cookie, FindCertPage.PATH, "certType=email&process=Next&cert=" + URLEncoder.encode(certMail, "UTF-8"), 0);
+        String result = IOUtils.readURL(uc);
+        assertThat(result, CoreMatchers.containsString(c.getSerial()));
+        assertThat(result, CoreMatchers.containsString("Draft"));
+
+        await(c1.issue(null, "2y", u));
+        uc = post(cookie, FindCertPage.PATH, "certType=email&process=Next&cert=" + URLEncoder.encode(certMail, "UTF-8"), 0);
+        result = IOUtils.readURL(uc);
+        assertThat(result, CoreMatchers.containsString(c.getSerial()));
+        assertThat(result, CoreMatchers.containsString(c1.getSerial()));
+        assertThat(result, not(CoreMatchers.containsString("Draft")));
+    }
+
     private Certificate createCertificate() throws GeneralSecurityException, IOException, GigiApiException, InterruptedException {
         User u1 = User.getById(id);
         KeyPair kp = generateKeypair();
index 9187c4f18156eaa87ad033f8d92cdd18a4e8ac97..f2eff3a669c25715afb0a86abd85dc68091b6fc0 100644 (file)
@@ -170,7 +170,7 @@ public class TestSEAdminPageDetails extends SEClientTest {
     }
 
     private int getLogEntryCount(String readURL) {
-        String s = "<tr><th>Support actions";
+        String s = "<tr><th>Actions in user account";
         int start = readURL.indexOf(s);
         int end = readURL.indexOf("</table>", start);
         String logs = readURL.substring(start + s.length(), end);
@@ -183,4 +183,28 @@ public class TestSEAdminPageDetails extends SEClientTest {
         return c;
     }
 
+    @Test
+    public void testUserDetailsRAAgent() throws IOException, GigiApiException {
+        User u0 = User.getById(createVerifiedUser("Kurti", "Hansel", createUniqueName() + "@email.com", TEST_PASSWORD));
+
+        HttpURLConnection uc = get(cookie, SupportUserDetailsPage.PATH + u0.getId() + "/");
+        String res = IOUtils.readURL(uc);
+        assertThat(res, containsString("No (RA Agent Contract signed: No)"));
+
+        signRAContract(u0);
+        uc = get(cookie, SupportUserDetailsPage.PATH + u0.getId() + "/");
+        res = IOUtils.readURL(uc);
+        assertThat(res, containsString("No (RA Agent Contract signed: Yes)"));
+
+        insertPassedTest(u0.getId());
+        uc = get(cookie, SupportUserDetailsPage.PATH + u0.getId() + "/");
+        res = IOUtils.readURL(uc);
+        assertThat(res, containsString("No (RA Agent Contract signed: Yes)"));
+
+        insertVerificationPoints(u0.getId());
+        uc = get(cookie, SupportUserDetailsPage.PATH + u0.getId() + "/");
+        res = IOUtils.readURL(uc);
+        assertThat(res, not(containsString("RA Agent Contract signed:")));
+
+    }
 }
index 81ec4df561d2ab4c2adef673b2acab9531d4d5f3..dcc61fc070cf66d857ec173546001b527c415e01 100644 (file)
@@ -399,6 +399,7 @@ public class TestVerification extends ManagedTest {
     public void testVerifyWithoutValidChallenge() throws IOException, GigiApiException {
         cookie = cookieWithCertificateLogin(User.getById(applicantId));
         add100Points(applicantId);
+        insertRAContract(applicantId);
         addChallengeInPast(applicantId, CATSType.AGENT_CHALLENGE);
         assertEquals(403, get(cookie, VerifyPage.PATH).getResponseCode());
         addChallenge(applicantId, CATSType.AGENT_CHALLENGE);
index 28da80f828679c78113bcedec7408a928da49647..4566d0e1f34391f5e6b46533e041bd49381e46ca 100644 (file)
@@ -183,4 +183,13 @@ public abstract class BusinessTest extends ConfiguredTest {
         supporter.refreshGroups();
         return supporter;
     }
+
+    public EmailAddress createVerifiedEmail(User u, String email) throws InterruptedException, GigiApiException {
+        EmailAddress addr = new EmailAddress(u, email, Locale.ENGLISH);
+        TestMail testMail = getMailReceiver().receive(addr.getAddress());
+        String hash = testMail.extractLink().substring(testMail.extractLink().lastIndexOf('=') + 1);
+        addr.verify(hash);
+        getMailReceiver().assertEmpty();
+        return addr;
+    }
 }
index 023d55ed4b3ab43da9567346d26d03db72502409..3a6156946a2b72e77baa9a4fc5f5dab801566a50 100644 (file)
@@ -1,8 +1,14 @@
 package club.wpia.gigi.testUtils;
 
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+
 import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.database.GigiPreparedStatement;
 import club.wpia.gigi.dbObjects.Name;
 import club.wpia.gigi.dbObjects.User;
+import club.wpia.gigi.util.TimeConditions;
 
 public class ClientBusinessTest extends BusinessTest {
 
@@ -21,4 +27,18 @@ public class ClientBusinessTest extends BusinessTest {
             throw new Error(e);
         }
     }
+
+    public static void setVerificationDateToPast(Name name) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(System.currentTimeMillis());
+        c.add(Calendar.MONTH, -TimeConditions.getInstance().getVerificationMonths());
+        String date = sdf.format(new Date(c.getTimeInMillis()));
+        GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE `notary` SET `date`=? WHERE `to`=? AND `date`>?");
+        ps.setString(1, date);
+        ps.setInt(2, name.getId());
+        ps.setString(3, date);
+        ps.execute();
+        ps.close();
+    }
 }
index c0d6d4f74e4ee63a2c489d1ad76e812d029d6f98..43be01db7d2b64443c484d6ce751274fde334aa3 100644 (file)
@@ -42,6 +42,8 @@ import club.wpia.gigi.database.SQLFileManager.ImportType;
 import club.wpia.gigi.dbObjects.CATS;
 import club.wpia.gigi.dbObjects.CATS.CATSType;
 import club.wpia.gigi.dbObjects.CertificateProfile;
+import club.wpia.gigi.dbObjects.Contract;
+import club.wpia.gigi.dbObjects.Contract.ContractType;
 import club.wpia.gigi.dbObjects.Domain;
 import club.wpia.gigi.dbObjects.DomainPingType;
 import club.wpia.gigi.dbObjects.User;
@@ -52,6 +54,7 @@ import club.wpia.gigi.util.DomainAssessment;
 import club.wpia.gigi.util.Notary;
 import club.wpia.gigi.util.PEM;
 import club.wpia.gigi.util.PasswordHash;
+import club.wpia.gigi.util.RandomToken;
 import club.wpia.gigi.util.ServerConstants;
 import club.wpia.gigi.util.TimeConditions;
 import sun.security.pkcs10.PKCS10;
@@ -336,6 +339,7 @@ public abstract class ConfiguredTest {
     public static void makeAgent(int uid) {
         addChallenge(uid, CATSType.AGENT_CHALLENGE);
         add100Points(uid);
+        insertRAContract(uid);
     }
 
     public static void addChallenge(int uid, CATSType ct) {
@@ -356,6 +360,28 @@ public abstract class ConfiguredTest {
     }
 
     public static void add100Points(int uid) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        try (GigiPreparedStatement ps2 = new GigiPreparedStatement("INSERT INTO `notary` SET `from`=?, `to`=?, points='100', `date`=?")) {
+            ps2.setInt(1, uid);
+            ps2.setInt(2, User.getById(uid).getPreferredName().getId());
+            ps2.setString(3, sdf.format(new Date(System.currentTimeMillis())));
+            ps2.execute();
+        }
+    }
+
+    public static void insertRAContract(int uid) {
+        // insert signed RA Contract
+        try (GigiPreparedStatement ps = new GigiPreparedStatement("INSERT INTO `user_contracts` SET `memid`=?, `token`=?, `document`=?::`contractType`,`agentname`=?")) {
+            ps.setInt(1, uid);
+            ps.setString(2, RandomToken.generateToken(32));
+            ps.setEnum(3, ContractType.RA_AGENT_CONTRACT);
+            ps.setString(4, User.getById(uid).getPreferredName().toString());
+            ps.execute();
+        }
+    }
+
+    public static void insertVerificationPoints(int uid) {
+        // insert Verification Points
         try (GigiPreparedStatement ps2 = new GigiPreparedStatement("INSERT INTO `notary` SET `from`=?, `to`=?, points='100'")) {
             ps2.setInt(1, uid);
             ps2.setInt(2, User.getById(uid).getPreferredName().getId());
@@ -363,6 +389,15 @@ public abstract class ConfiguredTest {
         }
     }
 
+    public static void insertPassedTest(int uid) {
+        // insert passed test
+        try (GigiPreparedStatement ps1 = new GigiPreparedStatement("INSERT INTO cats_passed SET user_id=?, variant_id=?, language='en_EN', version='1'")) {
+            ps1.setInt(1, uid);
+            ps1.setInt(2, CATSType.AGENT_CHALLENGE.getId());
+            ps1.execute();
+        }
+    }
+
     public MailReceiver getMailReceiver() {
         throw new Error("Feature requires Business or ManagedTest.");
     }
@@ -406,4 +441,9 @@ public abstract class ConfiguredTest {
         c.add(Calendar.MONTH, -Notary.LIMIT_MAX_MONTHS_VERIFICATION + 1);
         return sdf.format(new Date(c.getTimeInMillis()));
     }
+
+    public void signRAContract(User u) throws GigiApiException {
+        new Contract(u, ContractType.RA_AGENT_CONTRACT);
+        getMailReceiver().receive(u.getEmail());
+    }
 }
index 4028ebdaea80451673e7b7f8ca732048c16462be..6c6780e98703da1bb66aa3f6d573fb2d37f3532d 100644 (file)
@@ -12,6 +12,7 @@ import club.wpia.gigi.GigiApiException;
 import club.wpia.gigi.database.GigiPreparedStatement;
 import club.wpia.gigi.dbObjects.CATS;
 import club.wpia.gigi.dbObjects.CATS.CATSType;
+import club.wpia.gigi.dbObjects.Contract;
 import club.wpia.gigi.dbObjects.Country;
 import club.wpia.gigi.dbObjects.Country.CountryCodeType;
 import club.wpia.gigi.dbObjects.Group;
@@ -23,6 +24,7 @@ import club.wpia.gigi.dbObjects.User;
 import club.wpia.gigi.dbObjects.Verification.VerificationType;
 import club.wpia.gigi.output.DateSelector;
 import club.wpia.gigi.testUtils.BusinessTest;
+import club.wpia.gigi.testUtils.TestEmailReceiver.TestMail;
 
 public class TestNotary extends BusinessTest {
 
@@ -167,6 +169,8 @@ public class TestNotary extends BusinessTest {
         assertEquals(100, applicant.getVerificationPoints());
         assertFalse(applicant.canVerify());
         CATS.enterResult(applicant, CATSType.AGENT_CHALLENGE, new Date(), "de", "1");
+        new Contract(applicant, Contract.ContractType.RA_AGENT_CONTRACT);
+        TestMail rc = getMailReceiver().receive(applicant.getEmail());
         assertTrue(applicant.canVerify());
     }
 }
index c9dba62f957dbd8462d835aedfa812d2b6fb5033..7dabe4f2720e1836dfa5e383df61a433ae80310f 100644 (file)
@@ -32,12 +32,13 @@ import club.wpia.gigi.Gigi;
 import club.wpia.gigi.GigiApiException;
 import club.wpia.gigi.crypto.SPKAC;
 import club.wpia.gigi.database.GigiPreparedStatement;
-import club.wpia.gigi.database.GigiResultSet;
 import club.wpia.gigi.dbObjects.CATS;
 import club.wpia.gigi.dbObjects.CATS.CATSType;
 import club.wpia.gigi.dbObjects.Certificate;
 import club.wpia.gigi.dbObjects.Certificate.CertificateStatus;
 import club.wpia.gigi.dbObjects.CertificateOwner;
+import club.wpia.gigi.dbObjects.Contract;
+import club.wpia.gigi.dbObjects.Contract.ContractType;
 import club.wpia.gigi.dbObjects.Country;
 import club.wpia.gigi.dbObjects.Digest;
 import club.wpia.gigi.dbObjects.Domain;
@@ -46,6 +47,7 @@ import club.wpia.gigi.dbObjects.DomainPingExecution;
 import club.wpia.gigi.dbObjects.DomainPingType;
 import club.wpia.gigi.dbObjects.EmailAddress;
 import club.wpia.gigi.dbObjects.Group;
+import club.wpia.gigi.dbObjects.Name;
 import club.wpia.gigi.dbObjects.NamePart;
 import club.wpia.gigi.dbObjects.NamePart.NamePartType;
 import club.wpia.gigi.dbObjects.User;
@@ -164,6 +166,9 @@ public class Manager extends Page {
                 ps.setString(6, getRandomCountry().getCode());
                 ps.execute();
             }
+            if ( !Contract.hasSignedContract(u, ContractType.RA_AGENT_CONTRACT)) {
+                new Contract(u, ContractType.RA_AGENT_CONTRACT);
+            }
             return u;
         }
     }
@@ -173,21 +178,14 @@ public class Manager extends Page {
     }
 
     private void expireCATS(User u, CATSType t) {
-        try (GigiPreparedStatement ps = new GigiPreparedStatement("SELECT `id` FROM `cats_passed` WHERE `user_id`=? AND `variant_id`=? AND `pass_date`>?")) {
-            ps.setInt(1, u.getId());
-            ps.setInt(2, t.getId());
-            ps.setTimestamp(3, new Timestamp(System.currentTimeMillis() - DayDate.MILLI_DAY * 366));
+        try (GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE `cats_passed` SET `pass_date`=? WHERE `user_id`=? AND `variant_id`=? AND `pass_date`>?")) {
+            ps.setTimestamp(1, new Timestamp(System.currentTimeMillis() - DayDate.MILLI_DAY * 367));
+            ps.setInt(2, u.getId());
+            ps.setInt(3, t.getId());
+            ps.setTimestamp(4, new Timestamp(System.currentTimeMillis() - DayDate.MILLI_DAY * 366));
             ps.execute();
-            GigiResultSet rs = ps.executeQuery();
-            while (rs.next()) {
-                GigiPreparedStatement ps1 = new GigiPreparedStatement("UPDATE `cats_passed` SET `pass_date`=? WHERE `id`=?");
-                ps1.setTimestamp(1, new Timestamp(System.currentTimeMillis() - DayDate.MILLI_DAY * 367));
-                ps1.setInt(2, rs.getInt(1));
-                ps1.execute();
-                ps1.close();
-            }
+            ps.close();
         }
-
     }
 
     private static Manager instance;
@@ -346,17 +344,17 @@ public class Manager extends Page {
                 throw new Error(e);
             }
         } else if (req.getParameter("addpriv") != null || req.getParameter("delpriv") != null) {
-            User u = User.getByEmail(req.getParameter("email"));
-            if (u == null) {
+            User userByEmail = User.getByEmail(req.getParameter("email"));
+            if (userByEmail == null) {
                 resp.getWriter().println("User not found.");
                 return;
             }
             try {
                 if (req.getParameter("addpriv") != null) {
-                    u.grantGroup(getSupporter(), Group.getByString(req.getParameter("priv")));
+                    userByEmail.grantGroup(getSupporter(), Group.getByString(req.getParameter("priv")));
                     resp.getWriter().println("Privilege granted");
                 } else {
-                    u.revokeGroup(getSupporter(), Group.getByString(req.getParameter("priv")));
+                    userByEmail.revokeGroup(getSupporter(), Group.getByString(req.getParameter("priv")));
                     resp.getWriter().println("Privilege revoked");
                 }
             } catch (GigiApiException e) {
@@ -367,84 +365,116 @@ public class Manager extends Page {
             fetchMails(req, resp, mail);
         } else if (req.getParameter("cats") != null) {
             String mail = req.getParameter("catsEmail");
-            String testId = req.getParameter("catsType");
+            String catsTypeId = req.getParameter("catsType");
             User byEmail = User.getByEmail(mail);
             if (byEmail == null) {
                 resp.getWriter().println("User not found.");
                 return;
             }
-            if (testId == null) {
+            if (catsTypeId == null) {
                 resp.getWriter().println("No test given.");
                 return;
             }
-            CATSType test = CATSType.values()[Integer.parseInt(testId)];
+            CATSType test = null;
+            try {
+                test = CATSType.values()[Integer.parseInt(catsTypeId)];
+            } catch (NumberFormatException e) {
+                resp.getWriter().println("No valid integer given.");
+                return;
+            }
             passCATS(byEmail, test);
             resp.getWriter().println("Test '" + test.getDisplayName() + "' was added to user account.");
         } else if (req.getParameter("catsexpire") != null) {
             String mail = req.getParameter("catsEmail");
-            String testId = req.getParameter("catsType");
-            User byEmail = User.getByEmail(mail);
-            if (byEmail == null) {
+            String catsTypeId = req.getParameter("catsType");
+            User userByEmail = User.getByEmail(mail);
+            if (userByEmail == null) {
                 resp.getWriter().println("User not found.");
                 return;
             }
-            if (testId == null) {
+            if (catsTypeId == null) {
                 resp.getWriter().println("No test given.");
                 return;
             }
-            CATSType test = CATSType.values()[Integer.parseInt(testId)];
-            expireCATS(byEmail, test);
+            CATSType test = null;
+            try {
+                test = CATSType.values()[Integer.parseInt(catsTypeId)];
+            } catch (NumberFormatException e) {
+                resp.getWriter().println("No valid integer given.");
+                return;
+            }
+            expireCATS(userByEmail, test);
             resp.getWriter().println("Test '" + test.getDisplayName() + "' is set expired for user account.");
         } else if (req.getParameter("verify") != null) {
             String mail = req.getParameter("verifyEmail");
             String verificationPoints = req.getParameter("verificationPoints");
-            User byEmail = User.getByEmail(mail);
+            User userByEmail = User.getByEmail(mail);
 
-            if (byEmail == null) {
+            if (userByEmail == null) {
                 resp.getWriter().println("User not found.");
                 return;
             }
 
             int vp = 0;
-            int agentNumber = 0;
+            int verifications = 0;
+            String info = "";
 
             try {
                 try {
                     vp = Integer.parseInt(verificationPoints);
                 } catch (NumberFormatException e) {
-                    throw new GigiApiException("No valid Verification Points entered.");
+                    resp.getWriter().println("The value for Verification Points must be an integer.</br>");
+                    vp = 0;
                 }
 
-                if (vp > 100) { // only allow max 100 Verification points
-                    vp = 100;
-                }
+                int agentNumber = addVerificationPoints(vp, userByEmail);
 
                 while (vp > 0) {
                     int currentVP = 10;
                     if (vp < 10) {
                         currentVP = vp;
                     }
-                    Notary.verify(getAgent(agentNumber), byEmail, byEmail.getPreferredName(), byEmail.getDoB(), currentVP, "Testmanager Verify up code", validVerificationDateString(), VerificationType.FACE_TO_FACE, getRandomCountry());
+                    if (Notary.checkVerificationIsPossible(getAgent(agentNumber), userByEmail.getPreferredName())) {
+
+                        Notary.verify(getAgent(agentNumber), userByEmail, userByEmail.getPreferredName(), userByEmail.getDoB(), currentVP, "Testmanager Verify up code", validVerificationDateString(), VerificationType.FACE_TO_FACE, getRandomCountry());
+                        vp -= currentVP;
+                        verifications += 1;
+
+                    }
                     agentNumber += 1;
-                    vp -= currentVP;
+                    if (agentNumber >= agents.length) {
+                        info = "<br/>The limit of agents is reached. You cannot add any more Verification Points to the preferred name of this user using this method.";
+                        break;
+                    }
                 }
 
             } catch (GigiApiException e) {
                 throw new Error(e);
             }
 
-            resp.getWriter().println("User has been verified " + agentNumber + " times.");
+            resp.getWriter().println("User has been verified " + verifications + " times." + info);
 
+        } else if (req.getParameter("verifyexpire") != null) {
+            String mail = req.getParameter("verifyEmail");
+            User byEmail = User.getByEmail(mail);
+            if (byEmail == null) {
+                resp.getWriter().println("User not found.");
+                return;
+            } else {
+                setVerificationDateToPast(byEmail.getPreferredName());
+            }
+
+            resp.getWriter().println("Verification set to time past the limit.");
         } else if (req.getParameter("letverify") != null) {
             String mail = req.getParameter("letverifyEmail");
-            User byEmail = User.getByEmail(mail);
-            if (byEmail == null || !byEmail.canVerify()) {
+            User userByEmail = User.getByEmail(mail);
+            if (userByEmail == null || !userByEmail.canVerify()) {
                 resp.getWriter().println("User not found, or found user is not allowed to verify.");
             } else {
                 try {
                     for (int i = 0; i < 25; i++) {
                         User a = getAgent(i);
-                        Notary.verify(byEmail, a, a.getNames()[0], a.getDoB(), 10, "Testmanager exp up code", validVerificationDateString(), VerificationType.FACE_TO_FACE, getRandomCountry());
+                        Notary.verify(userByEmail, a, a.getNames()[0], a.getDoB(), 10, "Testmanager exp up code", validVerificationDateString(), VerificationType.FACE_TO_FACE, getRandomCountry());
                     }
                     resp.getWriter().println("Successfully added experience points.");
                 } catch (GigiApiException e) {
@@ -452,9 +482,9 @@ public class Manager extends Page {
                 }
             }
         } else if (req.getParameter("addEmail") != null) {
-            User u = User.getByEmail(req.getParameter("addEmailEmail"));
+            User userByEmail = User.getByEmail(req.getParameter("addEmailEmail"));
             try {
-                EmailAddress ea = new EmailAddress(u, req.getParameter("addEmailNew"), Locale.ENGLISH);
+                EmailAddress ea = new EmailAddress(userByEmail, req.getParameter("addEmailNew"), Locale.ENGLISH);
                 verify(ea.getAddress(), ea);
             } catch (IllegalArgumentException e) {
                 e.printStackTrace();
@@ -463,7 +493,7 @@ public class Manager extends Page {
                 e.format(resp.getWriter(), Language.getInstance(Locale.ENGLISH), getDefaultVars(req));
             }
         } else if (req.getParameter("addCert") != null) {
-            User u = User.getByEmail(req.getParameter("addCertEmail"));
+            User userByEmail = User.getByEmail(req.getParameter("addCertEmail"));
             try {
                 KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA");
                 kpg.initialize(4096);
@@ -474,10 +504,10 @@ public class Manager extends Page {
 
                 byte[] res = s.getEncoded(sign);
 
-                CertificateRequest cr = new CertificateRequest(new AuthorizationContext(u, u, sessionAc.isStronglyAuthenticated()), Base64.getEncoder().encodeToString(res), "challenge");
-                cr.update(CertificateRequest.DEFAULT_CN, Digest.SHA512.toString(), "client", null, "", "email:" + u.getEmail());
+                CertificateRequest cr = new CertificateRequest(new AuthorizationContext(userByEmail, userByEmail, sessionAc.isStronglyAuthenticated()), Base64.getEncoder().encodeToString(res), "challenge");
+                cr.update(CertificateRequest.DEFAULT_CN, Digest.SHA512.toString(), "client", null, "", "email:" + userByEmail.getEmail());
                 Certificate draft = cr.draft();
-                draft.issue(null, "2y", u).waitFor(10000);
+                draft.issue(null, "2y", userByEmail).waitFor(10000);
                 if (draft.getStatus() == CertificateStatus.ISSUED) {
                     resp.getWriter().println("added certificate");
                 } else {
@@ -500,10 +530,54 @@ public class Manager extends Page {
             pingExempt.remove(dom);
             resp.getWriter().println("Updated domains exempt from pings. Current set: <br/>");
             resp.getWriter().println(HTMLEncoder.encodeHTML(pingExempt.toString()));
+        } else if (req.getParameter("makeAgent") != null) {
+            User userByEmail = User.getByEmail(req.getParameter("agentEmail"));
+            if (userByEmail == null) {
+                resp.getWriter().println("User not found, or found user is not allowed to verify.");
+            } else {
+                if (userByEmail.getVerificationPoints() < 100) {
+                    addVerificationPoints(100, userByEmail);
+                }
+                if ( !userByEmail.hasPassedCATS()) {
+                    passCATS(userByEmail, CATSType.AGENT_CHALLENGE);
+                }
+                if ( !Contract.hasSignedContract(userByEmail, Contract.ContractType.RA_AGENT_CONTRACT)) {
+                    try {
+                        new Contract(userByEmail, Contract.ContractType.RA_AGENT_CONTRACT);
+                    } catch (GigiApiException e) {
+                        throw new Error(e);
+                    }
+                }
+                resp.getWriter().println("User has all requirements to be an RA Agent");
+            }
         }
         resp.getWriter().println("<br/><a href='" + PATH + "'>Go back</a>");
     }
 
+    private int addVerificationPoints(int vp, User byEmail) throws Error {
+        int agentNumber = 0;
+
+        try {
+            if (vp > 100) { // only allow max 100 Verification points
+                vp = 100;
+            }
+
+            while (vp > 0) {
+                int currentVP = 10;
+                if (vp < 10) {
+                    currentVP = vp;
+                }
+                Notary.verify(getAgent(agentNumber), byEmail, byEmail.getPreferredName(), byEmail.getDoB(), currentVP, "Testmanager Verify up code", validVerificationDateString(), VerificationType.FACE_TO_FACE, getRandomCountry());
+                agentNumber += 1;
+                vp -= currentVP;
+            }
+
+        } catch (GigiApiException e) {
+            throw new Error(e);
+        }
+        return agentNumber;
+    }
+
     private void fetchMails(HttpServletRequest req, HttpServletResponse resp, String mail) throws IOException {
         final LinkedList<String> mails = emails.get(mail);
         HashMap<String, Object> vars = new HashMap<>();
@@ -572,4 +646,18 @@ public class Manager extends Page {
 
         form.output(resp.getWriter(), getLanguage(req), vars);
     }
+
+    private static void setVerificationDateToPast(Name name) {
+        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
+        Calendar c = Calendar.getInstance();
+        c.setTimeInMillis(System.currentTimeMillis());
+        c.add(Calendar.MONTH, -TimeConditions.getInstance().getVerificationMonths());
+        String date = sdf.format(new Date(c.getTimeInMillis()));
+        GigiPreparedStatement ps = new GigiPreparedStatement("UPDATE `notary` SET `date`=? WHERE `to`=? AND `date`>?");
+        ps.setString(1, date);
+        ps.setInt(2, name.getId());
+        ps.setString(3, date);
+        ps.execute();
+        ps.close();
+    }
 }
index 857a6d18da61656c2c7afda202bb2eee98792af2..f6e1519ab89c27d9084a85b07402a0efd21ff7b0 100644 (file)
@@ -52,6 +52,17 @@ Email: <input type="text" name="catsEmail"/>
 <input type="submit" value="Add Challenge" name="cats"/>
 <input type="submit" value="Set Challenge expired" name="catsexpire"/>
 </td></tr>
+
+</td></tr>
+<tr><td>
+Make RA Agent:
+</td><td>
+Email: <input type="text" name="agentEmail"/>
+</td><td>
+Current requirements: 100 VP, passed Assurer Challenge, signed RA Agent Contract</br>
+<input type="submit" value="Add requirements" name="makeAgent"/>
+</td></tr>
+
 <tr><td>
 Add 100 Verification Points:
 </td><td>
@@ -59,7 +70,7 @@ Email: <input type="text" name="verifyEmail"/>
 </td><td>
 Verification Points to issue to preferred name: </br>
 <input type="text" name="verificationPoints" value="100"/> <input type="submit" value="Add Points" name="verify"/>
-
+<input type="submit" value="Set Verification date past limit" name="verifyexpire"/>
 </td></tr>
 
 <tr><td>