]> WPIA git - gigi.git/commitdiff
Merge changes If5eed01f,I88c94e39,If36f5b0a
authorMarcus Mängel <m.maengel@inopiae.de>
Thu, 19 Mar 2020 05:41:54 +0000 (05:41 +0000)
committerGerrit Code Review <gigi-system@dogcraft.de>
Thu, 19 Mar 2020 05:41:54 +0000 (05:41 +0000)
* changes:
  upd: introduce constant for waiting time for jobs
  add: ensure to revoke certificates if email address is deleted
  add: ensure to revoke certificates if domain is deleted

44 files changed:
build.xml
config/generateTruststoreNRE.sh
debian/control
src/club/wpia/gigi/Gigi.java
src/club/wpia/gigi/database/DatabaseConnection.java
src/club/wpia/gigi/database/tableStructure.sql
src/club/wpia/gigi/database/upgrade/from_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/Name.java
src/club/wpia/gigi/dbObjects/User.java
src/club/wpia/gigi/pages/MainPage.templ
src/club/wpia/gigi/pages/account/ChangeForm.java
src/club/wpia/gigi/pages/account/ChangePasswordForm.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/CertificateRequest.java
src/club/wpia/gigi/pages/account/certs/RequestCertificate.templ
src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.java
src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.templ
src/club/wpia/gigi/pages/orga/AffiliationForm.java
src/club/wpia/gigi/pages/orga/AffiliationForm.templ
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/TestUser.java
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/TestChangePassword.java
tests/club/wpia/gigi/pages/account/TestContract.java [new file with mode: 0644]
tests/club/wpia/gigi/pages/admin/TestSEAdminPageDetails.java
tests/club/wpia/gigi/pages/wot/TestVerification.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 abf1e3b1f9f2c434ab1fe755acc7ec940b0a278c..f03eca76c781f71abd573cbf1601af4f393197b1 100644 (file)
--- a/build.xml
+++ b/build.xml
        </target>
        <target name="update-effective-tlds">
                <mkdir dir="bin/club/wpia/gigi/util"/>
-               <exec executable="wget" dir="bin/club/wpia/gigi/util">
-                       <arg value="-N"/>
-                       <arg value="-q"/>
-                       <arg value="https://publicsuffix.org/list/effective_tld_names.dat"/>
-               </exec>
+               <copy file="/usr/share/publicsuffix/effective_tld_names.dat" tofile="bin/club/wpia/gigi/util/effective_tld_names.dat"/>
        </target>
 </project>
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 fc5499d13aa64d801034b128a92d856ef1a43828..dae85c04837b00b587feeebd23d6557adb053ec4 100644 (file)
@@ -2,7 +2,7 @@ Source: wpia-gigi
 Section: java
 Priority: optional
 Maintainer: unknown <software@wpia.club>
-Build-Depends: debhelper (>= 8.0.0), dh-systemd, java7-sdk-headless, ant, wget
+Build-Depends: debhelper (>= 8.0.0), dh-systemd, java7-sdk-headless, ant, wget, publicsuffix
 Standards-Version: 3.9.4
 Homepage: https://wpia.club
 #Vcs-Git: git://git.debian.org/collab-maint/gigi.git
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 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 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 ab75628ba8f9bc2bc79968c8f2139fc8e25ab93a..834b6f68bc7a65148c0fd45c74afed35d30e6f20 100644 (file)
@@ -210,7 +210,7 @@ public class User extends CertificateOwner {
         setPassword(newPass);
     }
 
-    private void setPassword(String newPass) throws GigiApiException {
+    public void setPassword(String newPass) throws GigiApiException {
         Name[] names = getNames();
         TreeSet<String> nameParts = new TreeSet<>();
         for (int i = 0; i < names.length; i++) {
@@ -243,6 +243,10 @@ public class User extends CertificateOwner {
             return false;
         }
 
+        if ( !Contract.hasSignedContract(this, Contract.ContractType.RA_AGENT_CONTRACT)) {
+            return false;
+        }
+
         return hasPassedCATS();
 
     }
@@ -335,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())) {
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 de2a182d6bd7aa1348670f91321a3b412fb28750..590597b44653920c333f2859bf01f079ecbff930 100644 (file)
@@ -11,20 +11,28 @@ import club.wpia.gigi.localisation.Language;
 import club.wpia.gigi.output.template.Form;
 import club.wpia.gigi.output.template.Template;
 import club.wpia.gigi.output.template.TranslateCommand;
+import club.wpia.gigi.pages.LoginPage;
+import club.wpia.gigi.util.AuthorizationContext;
 
 public class ChangeForm extends Form {
 
     private User target;
 
+    private AuthorizationContext c;
+
     public ChangeForm(HttpServletRequest hsr, User target) {
         super(hsr);
         this.target = target;
+        c = LoginPage.getAuthorizationContext(hsr);
     }
 
     private static final Template t = new Template(ChangePasswordPage.class.getResource("ChangePasswordForm.templ"));
 
     @Override
     public void outputContent(PrintWriter out, Language l, Map<String, Object> vars) {
+        if ( !c.isStronglyAuthenticated()) {
+            vars.put("currentLoginMethod", "pw");
+        }
         t.output(out, l, vars);
     }
 
@@ -34,14 +42,18 @@ public class ChangeForm extends Form {
         String p1 = req.getParameter("pword1");
         String p2 = req.getParameter("pword2");
         GigiApiException error = new GigiApiException();
-        if (oldpassword == null || p1 == null || p2 == null) {
+        if ((oldpassword == null && !c.isStronglyAuthenticated()) || p1 == null || p2 == null) {
             throw new GigiApiException("All fields are required.");
         }
         if ( !p1.equals(p2)) {
             throw new GigiApiException("New passwords do not match.");
         }
         try {
-            target.changePassword(oldpassword, p1);
+            if (c.isStronglyAuthenticated()) {
+                target.setPassword(p1);
+            } else {
+                target.changePassword(oldpassword, p1);
+            }
             target.writeUserLog(target, "User triggered password reset");
         } catch (GigiApiException e) {
             error.mergeInto(e);
index 4c350c560ce331c17bac58bdd4466b891309d5ea..8725dc3b0da702ffedf7b4580e4eb2081fd70f65 100644 (file)
@@ -5,10 +5,12 @@
   </tr>
   </thead>
   <tbody>
+  <? if($currentLoginMethod){ ?>
   <tr>
     <td><?=_Old Password?>: </td>
     <td><input class="form-control" type="password" name="oldpassword" required></td>
   </tr>
+  <? } ?>
   <tr>
     <td><?=_New Password?><span class="formMandatory">*</span>: </td>
     <td><input class="form-control" type="password" name="pword1" required></td>
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 fe52149540c14a67c85d23fc4b1d3f501a18e302..68002958909e42fa3689adbc982d7a59a9d0f9b6 100644 (file)
@@ -27,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;
 
@@ -43,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;
@@ -66,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);
@@ -139,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 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 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 1fd1c010ff09eb102f23263aba1a610500e06638..c99486277eaf6609d8abfc4969fddf21f19d7df2 100644 (file)
@@ -8,6 +8,7 @@ import java.util.Map;
 import javax.servlet.http.HttpServletRequest;
 
 import club.wpia.gigi.GigiApiException;
+import club.wpia.gigi.dbObjects.Name;
 import club.wpia.gigi.dbObjects.Organisation;
 import club.wpia.gigi.dbObjects.Organisation.Affiliation;
 import club.wpia.gigi.dbObjects.User;
@@ -64,7 +65,9 @@ public class AffiliationForm extends Form {
                     return false;
                 }
                 Affiliation aff = iter.next();
-                vars.put("name", aff.getTarget().getPreferredName());
+                Name n = aff.getTarget().getPreferredName();
+                vars.put("name", n);
+                vars.put("nameString", n.toString());
                 vars.put("master", aff.isMaster() ? l.getTranslation("Master") : "");
                 vars.put("e-mail", aff.getTarget().getEmail());
                 return true;
index de3969b05c160b93f2616c2b560e5b47162e5b11..5d204b47b95dc2641e474267fac348bb47d98aa4 100644 (file)
@@ -10,7 +10,8 @@
     <td><?=$name?></td>
     <td><?=$e-mail?></td>
     <td><?=$master?></td>
-    <td><button class="btn btn-warning btn-confirm" data-confirm="<?=_Do you really want to delete the affiliation of ${name} (${e-mail}) to this organisation??>" data-reply="<?=_Cancel?>,<?=_Delete?>" type="submit" name="del" value="<?=$e-mail?>">X</button> </td>
+    <td><button class="btn btn-warning btn-confirm" data-confirm="<?=_Do you really want to delete the affiliation of ${nameString} (${e-mail}) to this organisation??>" data-reply="<?=_Cancel?>,<?=_Delete?>" type="submit" name="del" value="<?=$e-mail?>">X</button>
+    </td>
   </tr>
 <? } ?>
   <tr>
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);
+    }
+
+}
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()));
+    }
+
 }
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"));
         }
 
     }
index e18ec02a5ea5be4f108d1423fda46e0a3bc23f99..73d23c56e8e3e99403da209b1d267446787e5c1e 100644 (file)
@@ -107,4 +107,19 @@ public class TestChangePassword extends ClientTest {
 
     }
 
+    @Test
+    public void testChangePasswordCertLogin() throws IOException, GigiApiException {
+        // no cert login
+        String np = URLEncoder.encode(TEST_PASSWORD + "v1", "UTF-8");
+        String error = executeBasicWebInteraction(cookie, path, "pword1=" + np + "&pword2=" + np);
+        assertNotNull(error);
+
+        // cert login
+        cookie = cookieWithCertificateLogin(u);
+        error = executeBasicWebInteraction(cookie, path, "pword1=" + np + "&pword2=" + np);
+        assertNull(error);
+
+        cookie = login(u.getEmail(), TEST_PASSWORD);
+        loginCertificate = null;
+    }
 }
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 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 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 cdde36f25f2ff82cfbd52d575f06838fd74d5f5a..0ca611994798cedbadfd8868aad45791ff0e2b24 100644 (file)
@@ -7,6 +7,7 @@ import java.security.GeneralSecurityException;
 import java.security.KeyPair;
 import java.security.KeyPairGenerator;
 import java.security.Signature;
+import java.sql.Timestamp;
 import java.text.SimpleDateFormat;
 import java.util.Base64;
 import java.util.Calendar;
@@ -31,11 +32,14 @@ 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;
@@ -44,6 +48,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;
@@ -162,6 +167,7 @@ public class Manager extends Page {
                 ps.setString(6, getRandomCountry().getCode());
                 ps.execute();
             }
+            new Contract(u, ContractType.RA_AGENT_CONTRACT);
             return u;
         }
     }
@@ -170,6 +176,24 @@ public class Manager extends Page {
         CATS.enterResult(u, t, new Date(System.currentTimeMillis()), "en_EN", "1");
     }
 
+    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));
+            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();
+            }
+        }
+
+    }
+
     private static Manager instance;
 
     private static final Template t = new Template(Manager.class.getResource("ManagerMails.templ"));
@@ -360,6 +384,21 @@ public class Manager extends Page {
             CATSType test = CATSType.values()[Integer.parseInt(testId)];
             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) {
+                resp.getWriter().println("User not found.");
+                return;
+            }
+            if (testId == null) {
+                resp.getWriter().println("No test given.");
+                return;
+            }
+            CATSType test = CATSType.values()[Integer.parseInt(testId)];
+            expireCATS(byEmail, 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");
@@ -371,35 +410,55 @@ public class Manager extends Page {
             }
 
             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, byEmail);
 
                 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), byEmail.getPreferredName())) {
+
+                        Notary.verify(getAgent(agentNumber), byEmail, byEmail.getPreferredName(), byEmail.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);
@@ -465,10 +524,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 u = User.getByEmail(req.getParameter("agentEmail"));
+            if (u == null) {
+                resp.getWriter().println("User not found, or found user is not allowed to verify.");
+            } else {
+                if (u.getVerificationPoints() < 100) {
+                    addVerificationPoints(100, u);
+                }
+                if ( !u.hasPassedCATS()) {
+                    passCATS(u, CATSType.AGENT_CHALLENGE);
+                }
+                if ( !Contract.hasSignedContract(u, Contract.ContractType.RA_AGENT_CONTRACT)) {
+                    try {
+                        new Contract(u, 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<>();
@@ -537,4 +640,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 19f7e6bd58977d7389a41dedf76f3a61e5e84b21..f6e1519ab89c27d9084a85b07402a0efd21ff7b0 100644 (file)
@@ -50,7 +50,19 @@ Email: <input type="text" name="catsEmail"/>
 <? } ?>
 </select>
 <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>
@@ -58,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>