From: Marcus Mängel Date: Thu, 9 Apr 2020 19:51:47 +0000 (+0000) Subject: Merge "add: enable user to download certificate with file extention crt or pem" X-Git-Url: https://code.wpia.club/?p=gigi.git;a=commitdiff_plain;h=c38c46e7b8c5482112fbabd82c654445166b7041;hp=71d767826ea9dbd16f0d6da92dfa321308ce3f9d Merge "add: enable user to download certificate with file extention crt or pem" --- diff --git a/config/generateTruststoreNRE.sh b/config/generateTruststoreNRE.sh index ca0c28a5..4955a1f9 100755 --- a/config/generateTruststoreNRE.sh +++ b/config/generateTruststoreNRE.sh @@ -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 diff --git a/src/club/wpia/gigi/Gigi.java b/src/club/wpia/gigi/Gigi.java index e76c5171..d33d546d 100644 --- a/src/club/wpia/gigi/Gigi.java +++ b/src/club/wpia/gigi/Gigi.java @@ -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); diff --git a/src/club/wpia/gigi/api/CreateCertificate.java b/src/club/wpia/gigi/api/CreateCertificate.java index 1890e4a9..41556d13 100644 --- a/src/club/wpia/gigi/api/CreateCertificate.java +++ b/src/club/wpia/gigi/api/CreateCertificate.java @@ -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; diff --git a/src/club/wpia/gigi/api/RevokeCertificate.java b/src/club/wpia/gigi/api/RevokeCertificate.java index 81e57cfb..e8e8b964 100644 --- a/src/club/wpia/gigi/api/RevokeCertificate.java +++ b/src/club/wpia/gigi/api/RevokeCertificate.java @@ -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; diff --git a/src/club/wpia/gigi/database/DatabaseConnection.java b/src/club/wpia/gigi/database/DatabaseConnection.java index b5e78eaa..55a597e8 100644 --- a/src/club/wpia/gigi/database/DatabaseConnection.java +++ b/src/club/wpia/gigi/database/DatabaseConnection.java @@ -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; diff --git a/src/club/wpia/gigi/database/tableStructure.sql b/src/club/wpia/gigi/database/tableStructure.sql index e9097fd1..ef7547ce 100644 --- a/src/club/wpia/gigi/database/tableStructure.sql +++ b/src/club/wpia/gigi/database/tableStructure.sql @@ -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 index 00000000..1ac2a4e3 --- /dev/null +++ b/src/club/wpia/gigi/database/upgrade/from_37.sql @@ -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 index 00000000..6886dc26 --- /dev/null +++ b/src/club/wpia/gigi/dbObjects/Contract.java @@ -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 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 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 index 00000000..e0e499d5 --- /dev/null +++ b/src/club/wpia/gigi/dbObjects/ContractNotice.templ @@ -0,0 +1,6 @@ +Subject: + + + + + diff --git a/src/club/wpia/gigi/dbObjects/Domain.java b/src/club/wpia/gigi/dbObjects/Domain.java index 9b356e60..1d3ba17d 100644 --- a/src/club/wpia/gigi/dbObjects/Domain.java +++ b/src/club/wpia/gigi/dbObjects/Domain.java @@ -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 revokes = new LinkedList(); + 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 + } + } } } diff --git a/src/club/wpia/gigi/dbObjects/Job.java b/src/club/wpia/gigi/dbObjects/Job.java index a505eb41..071b1b2e 100644 --- a/src/club/wpia/gigi/dbObjects/Job.java +++ b/src/club/wpia/gigi/dbObjects/Job.java @@ -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) { diff --git a/src/club/wpia/gigi/dbObjects/Name.java b/src/club/wpia/gigi/dbObjects/Name.java index fbcf10ee..ce8e7e2a 100644 --- a/src/club/wpia/gigi/dbObjects/Name.java +++ b/src/club/wpia/gigi/dbObjects/Name.java @@ -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; + } + } + } diff --git a/src/club/wpia/gigi/dbObjects/SupportedUser.java b/src/club/wpia/gigi/dbObjects/SupportedUser.java index 84c43ab8..1eae478f 100644 --- a/src/club/wpia/gigi/dbObjects/SupportedUser.java +++ b/src/club/wpia/gigi/dbObjects/SupportedUser.java @@ -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()); diff --git a/src/club/wpia/gigi/dbObjects/User.java b/src/club/wpia/gigi/dbObjects/User.java index 4612d033..834b6f68 100644 --- a/src/club/wpia/gigi/dbObjects/User.java +++ b/src/club/wpia/gigi/dbObjects/User.java @@ -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 revokes = new LinkedList(); + 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() { diff --git a/src/club/wpia/gigi/pages/MainPage.templ b/src/club/wpia/gigi/pages/MainPage.templ index 963208e8..690480ab 100644 --- a/src/club/wpia/gigi/pages/MainPage.templ +++ b/src/club/wpia/gigi/pages/MainPage.templ @@ -104,7 +104,7 @@
  • -

    'change to organisation administrator context.''?>

    +

    diff --git a/src/club/wpia/gigi/pages/account/History.templ b/src/club/wpia/gigi/pages/account/History.templ index 90f18929..c2c0f6d6 100644 --- a/src/club/wpia/gigi/pages/account/History.templ +++ b/src/club/wpia/gigi/pages/account/History.templ @@ -1,6 +1,6 @@ - + diff --git a/src/club/wpia/gigi/pages/account/MyContracts.java b/src/club/wpia/gigi/pages/account/MyContracts.java new file mode 100644 index 00000000..b2aa53fc --- /dev/null +++ b/src/club/wpia/gigi/pages/account/MyContracts.java @@ -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 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 index 00000000..c867d093 --- /dev/null +++ b/src/club/wpia/gigi/pages/account/MyContracts.templ @@ -0,0 +1,91 @@ +
    +
    +
    + +

    This contract concludes an agreement between

    +

    RA Agent:

    +

    and

    +

    World Privacy and Identity Association (WPIA) - Verein zur Foerderung von sicheren Technologien und Grundrechten im Internet (ZVR: 910115306)

    +

    c/o realraum

    +

    Brockmangasse 15

    +

    8010 Graz

    +

    Austria

    +

    - CA hereafter -

    +

    regarding the conduction of registration authority tasks.

    +

    § 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

    +
      +
    1. The RA Agent is entitled to conduct those tasks on behalf of the CA as long as the RA Agent fulfills the following requirements: +
        +
      • 100 Verification Points,
      • +
      • passed the RA Agent test,
      • +
      • agrees the CA's Terms and Conditions,
      • +
      • agrees the Dispute Resolution system, and
      • +
      • signed this contract.
      • +
    2. +
    3. The parties agree, that this contract does not establish an employment relationship.

    4. +
    5. 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.

    6. +
    +

    § 2 Terms and Definitions

    +
    ++++ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    TermDefinition

    Applicant

    Person who has an account on the systems of CA and requests to get the identity verified

    Certificate Authority (CA)

    Certificate Authority who issues S/MIME certificates to individuals and organisations

    RA Agent

    Person who conducts a identity verification on behalf of the CA

    Registration Authority (RA)

    Registration Authority who controls the correct entries of an applicant

    Verification

    Process to verify the identity of person

    Verification Points

    Points issued during the verification

    +

    § 3 Rights and Obligations of the RA Agent

    +
      +
    1. The RA Agent conducts the transferred duties independently according to the Verification Policy, i.e. free of instructions other than essential for the proper execution of the transferred tasks.

    2. +
    3. The RA Agent is entitled to use the brand and logo of the CA for her work as RA Agent based on this contract.

    4. +
    5. The RA Agent MUST identify herself to an Applicant as RA Agent with the RA Agent Card provided by the CA.

    6. +
    7. The RA Agent MUST NOT engage another person to do verifications on her behalf.

    8. +
    9. The RA Agent MUST NOT verify applicants that are spouses or life partners and first-degree relatives (next of kin).

    10. +
    11. 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.

    12. +
    13. 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.

    14. +
    +

    § 4 Payment and Fees

    +
      +
    1. 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.

    2. +
    3. The RA agent is entitled to request reimbursement of travel expenses or other costs if this has been previously agreed with the applicant.

    4. +
    +

    § 5 Dispute Resolution

    +

    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.

    +

    § 6 Termination

    +

    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.

    +

    § 7 Subsidiary agreements

    +

    Subsidiary or deviating agreements and amendments to the contract MUST be in written form.

    + +

    + + diff --git a/src/club/wpia/gigi/pages/account/MyContractsDE.templ b/src/club/wpia/gigi/pages/account/MyContractsDE.templ new file mode 100644 index 00000000..b0fff313 --- /dev/null +++ b/src/club/wpia/gigi/pages/account/MyContractsDE.templ @@ -0,0 +1,92 @@ +
    +
    +
    + +

    Dieser Vertrag schließt eine Vereinbarung zwischen

    +

    RA Agent:

    +

    und

    +

    World Privacy and Identity Association (WPIA) - Verein zur Förderung von sicheren Technologien und Grundrechten im Internet (ZVR: 910115306)

    +

    c/o realraum

    +

    Brockmangasse 15

    +

    8010 Graz

    +

    Austria

    +

    - folgend CA -

    +

    für die Durchführung von "Registration Authority"-Aufgaben.

    +

    § 1 Vertragsgegenstand

    +
      +
    1. Der RA Agent ist ermächtigt "Registration Authority"-Aufgaben, wie folgend aufgeführt, auf Antrag des Applicant und der vorgelegten Dokumente durchzuführen: +
        +
      • Validierung der Identität des Applicant
      • +
      • Validierung der persönlichen Daten des Applicant
      • +
    2. +
    3. Der RA Agent ist ermächtig diese Aufgaben im Auftrag der CA auszuführen sofern die folgenden Voraussetzungen erfüllt sind: +
        +
      • 100 Verification Points
      • +
      • bestandener RA Agent Test
      • +
      • Zustimmung zu den "Terms and Condition" der CA
      • +
      • Zustimmung zum Schiedsgerichtswesen
      • +
      • Unterschrift für diesen Vertrag
      • +
    4. +
    5. Die Parteien vereinbaren, dass aus diesem Vertrag keine Anstellungsverhältnis entsteht.

    6. +
    7. 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.

    8. +
    +

    § 2 Begriffe und Definitionen

    + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
    BegriffeDefinition

    Applicant

    Person die ein Konto bei der CA hat und ihre Identität verifiziert haben möchte.

    Certificate Authority (CA)

    Certificate Authority, die S/MIME Zertifikate für Personen und Organisationen austellt.

    RA Agent Registration Authority (RA)

    Person, die Verifications im Auftrag der CA durchführt. Registration Authority, die die Richtigkeit der Einträge eines Applicant sicherstellt.

    Verification

    Prozess die Identität einer Person festzustellen.

    Verification Points

    Punkte die während der verification vergeben werden.

    +

    § 3 Rechte und Pflichten des RA Agent

    +
      +
    1. Der RA Agent führt die auferlegten Pflichten gemäß der Verification Policy unabhängig durch, z.B. frei von Vorgaben anderer unter Berücksichtigung der ordnungsgemäßen Durchführung der Aufgaben.

    2. +
    3. 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.

    4. +
    5. 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.

    6. +
    7. Der RA Agent darf nicht (MUST NOT) andere Personen auffordern die Verification an seiner statt durchzuführen.

    8. +
    9. Der RA Agent darf nicht (MUST NOT) Ehe- und Lebenspartner und Verwandte ersten Grades als Applicant verifizieren.

    10. +
    11. 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.

    12. +
    13. 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.

    14. +
    +

    § 4 Bezahlung und Gebühren

    +
      +
    1. 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.

    2. +
    3. Der RA Agent darf Reise- oder ähnliche Kosten dem Applicant in Rechnung stellen, sofern dies im Vorfeld vereinbart wird.

    4. +
    +

    § 5 Schiedsgerichtsbarkeit

    +

    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.

    +

    § 6 Vertragsbeendigung

    +

    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.

    +

    § 7 Zusätzliche Bestimmungen

    +

    Zusätzliche oder abweichende Bestimmungen oder Vereinbarungen benötigen (MUST) die Schriftform.

    + +

    + +
    diff --git a/src/club/wpia/gigi/pages/account/MyDetailsContracts.templ b/src/club/wpia/gigi/pages/account/MyDetailsContracts.templ new file mode 100644 index 00000000..81e0b13e --- /dev/null +++ b/src/club/wpia/gigi/pages/account/MyDetailsContracts.templ @@ -0,0 +1,8 @@ +
    +
    +
    + + + +
    +
    diff --git a/src/club/wpia/gigi/pages/account/MyDetailsForm.java b/src/club/wpia/gigi/pages/account/MyDetailsForm.java index 13d03880..bf7cbcfd 100644 --- a/src/club/wpia/gigi/pages/account/MyDetailsForm.java +++ b/src/club/wpia/gigi/pages/account/MyDetailsForm.java @@ -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); } } diff --git a/src/club/wpia/gigi/pages/account/certs/CertificateIssueForm.java b/src/club/wpia/gigi/pages/account/certs/CertificateIssueForm.java index 81925716..68002958 100644 --- a/src/club/wpia/gigi/pages/account/certs/CertificateIssueForm.java +++ b/src/club/wpia/gigi/pages/account/certs/CertificateIssueForm.java @@ -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 vars2 = new HashMap(vars); vars2.put("csrf", getCSRFToken()); vars2.put("csrf_name", getCsrfFieldName()); - vars2.put("spkacChallenge", spkacChallenge); tIni.output(out, l, vars2); return; } else { diff --git a/src/club/wpia/gigi/pages/account/certs/CertificateIssueForm.templ b/src/club/wpia/gigi/pages/account/certs/CertificateIssueForm.templ index da45ca65..dafd1576 100644 --- a/src/club/wpia/gigi/pages/account/certs/CertificateIssueForm.templ +++ b/src/club/wpia/gigi/pages/account/certs/CertificateIssueForm.templ @@ -1,7 +1,7 @@

    -

    '.?>

    +

    '.?>

    @@ -55,7 +55,7 @@ - + diff --git a/src/club/wpia/gigi/pages/account/certs/CertificateModificationForm.java b/src/club/wpia/gigi/pages/account/certs/CertificateModificationForm.java index 7ca73eb0..dd616113 100644 --- a/src/club/wpia/gigi/pages/account/certs/CertificateModificationForm.java +++ b/src/club/wpia/gigi/pages/account/certs/CertificateModificationForm.java @@ -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 { diff --git a/src/club/wpia/gigi/pages/account/certs/CertificateRequest.java b/src/club/wpia/gigi/pages/account/certs/CertificateRequest.java index cdf4dd41..28a5b098 100644 --- a/src/club/wpia/gigi/pages/account/certs/CertificateRequest.java +++ b/src/club/wpia/gigi/pages/account/certs/CertificateRequest.java @@ -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) { diff --git a/src/club/wpia/gigi/pages/account/certs/RequestCertificate.templ b/src/club/wpia/gigi/pages/account/certs/RequestCertificate.templ index 89abb8df..274ad8eb 100644 --- a/src/club/wpia/gigi/pages/account/certs/RequestCertificate.templ +++ b/src/club/wpia/gigi/pages/account/certs/RequestCertificate.templ @@ -1,6 +1,6 @@

    - '.?> -

    + '.?>

    +

    ' is recommended and described in !(/kb/XCADocu)XCA usage documentation!''?>

    @@ -24,26 +24,4 @@
    -
    - - - - - - - - - - - - - - - -
    - : -
    - - -
    -
    + diff --git a/src/club/wpia/gigi/pages/account/certs/RevokeSingleCertForm.java b/src/club/wpia/gigi/pages/account/certs/RevokeSingleCertForm.java index 30e404c0..12a7daa9 100644 --- a/src/club/wpia/gigi/pages/account/certs/RevokeSingleCertForm.java +++ b/src/club/wpia/gigi/pages/account/certs/RevokeSingleCertForm.java @@ -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()); } diff --git a/src/club/wpia/gigi/pages/account/domain/DomainManagementForm.templ b/src/club/wpia/gigi/pages/account/domain/DomainManagementForm.templ index d022c271..c4507f2e 100644 --- a/src/club/wpia/gigi/pages/account/domain/DomainManagementForm.templ +++ b/src/club/wpia/gigi/pages/account/domain/DomainManagementForm.templ @@ -10,7 +10,7 @@ - + diff --git a/src/club/wpia/gigi/pages/account/mail/MailManagementForm.templ b/src/club/wpia/gigi/pages/account/mail/MailManagementForm.templ index 2449bca1..c71306f0 100644 --- a/src/club/wpia/gigi/pages/account/mail/MailManagementForm.templ +++ b/src/club/wpia/gigi/pages/account/mail/MailManagementForm.templ @@ -19,7 +19,7 @@ - + diff --git a/src/club/wpia/gigi/pages/admin/support/FindCertPage.java b/src/club/wpia/gigi/pages/admin/support/FindCertPage.java index ea5ac1e9..19064a66 100644 --- a/src/club/wpia/gigi/pages/admin/support/FindCertPage.java +++ b/src/club/wpia/gigi/pages/admin/support/FindCertPage.java @@ -57,8 +57,8 @@ public class FindCertPage extends Page { @Override public void apply(Certificate t, Language l, Map 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()); diff --git a/src/club/wpia/gigi/pages/admin/support/FindCertPage.templ b/src/club/wpia/gigi/pages/admin/support/FindCertPage.templ index 0e0fa25b..1648f0dd 100644 --- a/src/club/wpia/gigi/pages/admin/support/FindCertPage.templ +++ b/src/club/wpia/gigi/pages/admin/support/FindCertPage.templ @@ -1,11 +1,10 @@

    :

    - + - - + diff --git a/src/club/wpia/gigi/pages/main/KeyCompromiseForm.java b/src/club/wpia/gigi/pages/main/KeyCompromiseForm.java index e0690844..ae3e30e0 100644 --- a/src/club/wpia/gigi/pages/main/KeyCompromiseForm.java +++ b/src/club/wpia/gigi/pages/main/KeyCompromiseForm.java @@ -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) { diff --git a/src/club/wpia/gigi/pages/wot/Rules.templ b/src/club/wpia/gigi/pages/wot/Rules.templ index 4d370973..8ab1e623 100644 --- a/src/club/wpia/gigi/pages/wot/Rules.templ +++ b/src/club/wpia/gigi/pages/wot/Rules.templ @@ -25,7 +25,7 @@

    -

    +

    diff --git a/tests/club/wpia/gigi/dbObjects/TestContract.java b/tests/club/wpia/gigi/dbObjects/TestContract.java new file mode 100644 index 00000000..98648f0a --- /dev/null +++ b/tests/club/wpia/gigi/dbObjects/TestContract.java @@ -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 index 00000000..93afd635 --- /dev/null +++ b/tests/club/wpia/gigi/dbObjects/TestDomain.java @@ -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); + + } + +} diff --git a/tests/club/wpia/gigi/dbObjects/TestUser.java b/tests/club/wpia/gigi/dbObjects/TestUser.java index 6ec0abec..7d7b6dba 100644 --- a/tests/club/wpia/gigi/dbObjects/TestUser.java +++ b/tests/club/wpia/gigi/dbObjects/TestUser.java @@ -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 index 00000000..d0906ae0 --- /dev/null +++ b/tests/club/wpia/gigi/dbObjects/TestUserManaged.java @@ -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); + + } +} diff --git a/tests/club/wpia/gigi/dbObjects/TestVerifyName.java b/tests/club/wpia/gigi/dbObjects/TestVerifyName.java index 7f3af269..7ad2c7f4 100644 --- a/tests/club/wpia/gigi/dbObjects/TestVerifyName.java +++ b/tests/club/wpia/gigi/dbObjects/TestVerifyName.java @@ -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()); + } + } diff --git a/tests/club/wpia/gigi/pages/TestMain.java b/tests/club/wpia/gigi/pages/TestMain.java index be642f03..4c0ae786 100644 --- a/tests/club/wpia/gigi/pages/TestMain.java +++ b/tests/club/wpia/gigi/pages/TestMain.java @@ -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()); diff --git a/tests/club/wpia/gigi/pages/account/TestCertificateAdd.java b/tests/club/wpia/gigi/pages/account/TestCertificateAdd.java index 0d83f301..16594e1b 100644 --- a/tests/club/wpia/gigi/pages/account/TestCertificateAdd.java +++ b/tests/club/wpia/gigi/pages/account/TestCertificateAdd.java @@ -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("]*name='csrf' [^>]*value='([^']*)'>")); - String challenge = extractPattern(s, Pattern.compile("]*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("

    Challenge mismatch")); - } - return csrf; - } - private PKCS10Attributes buildAtts(ObjectIdentifier[] ekuOIDs, GeneralNameInterface... SANs) throws IOException { CertificateExtensions attributeValue = new CertificateExtensions(); GeneralNames names = new GeneralNames(); diff --git a/tests/club/wpia/gigi/pages/account/TestCertificateRequest.java b/tests/club/wpia/gigi/pages/account/TestCertificateRequest.java index 023be5ae..372f2bfa 100644 --- a/tests/club/wpia/gigi/pages/account/TestCertificateRequest.java +++ b/tests/club/wpia/gigi/pages/account/TestCertificateRequest.java @@ -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 index 00000000..4628ec86 --- /dev/null +++ b/tests/club/wpia/gigi/pages/account/TestContract.java @@ -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")); + } + +} diff --git a/tests/club/wpia/gigi/pages/admin/TestSEAdminPageCertSearch.java b/tests/club/wpia/gigi/pages/admin/TestSEAdminPageCertSearch.java index cf9b403b..0d556419 100644 --- a/tests/club/wpia/gigi/pages/admin/TestSEAdminPageCertSearch.java +++ b/tests/club/wpia/gigi/pages/admin/TestSEAdminPageCertSearch.java @@ -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(); diff --git a/tests/club/wpia/gigi/pages/admin/TestSEAdminPageDetails.java b/tests/club/wpia/gigi/pages/admin/TestSEAdminPageDetails.java index 9187c4f1..f2eff3a6 100644 --- a/tests/club/wpia/gigi/pages/admin/TestSEAdminPageDetails.java +++ b/tests/club/wpia/gigi/pages/admin/TestSEAdminPageDetails.java @@ -170,7 +170,7 @@ public class TestSEAdminPageDetails extends SEClientTest { } private int getLogEntryCount(String readURL) { - String s = "

    Id
    diff --git a/src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.java b/src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.java index 71909cf2..0ed43d9d 100644 --- a/src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.java +++ b/src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.java @@ -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()); diff --git a/src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.templ b/src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.templ index e357564e..5f5ec991 100644 --- a/src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.templ +++ b/src/club/wpia/gigi/pages/admin/support/SupportUserDetailsForm.templ @@ -28,7 +28,7 @@ - + ( )
    Support actions"; + String s = "
    Actions in user account"; int start = readURL.indexOf(s); int end = readURL.indexOf("
    ", 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:"))); + + } } diff --git a/tests/club/wpia/gigi/pages/wot/TestVerification.java b/tests/club/wpia/gigi/pages/wot/TestVerification.java index 81ec4df5..dcc61fc0 100644 --- a/tests/club/wpia/gigi/pages/wot/TestVerification.java +++ b/tests/club/wpia/gigi/pages/wot/TestVerification.java @@ -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); diff --git a/tests/club/wpia/gigi/testUtils/BusinessTest.java b/tests/club/wpia/gigi/testUtils/BusinessTest.java index 28da80f8..4566d0e1 100644 --- a/tests/club/wpia/gigi/testUtils/BusinessTest.java +++ b/tests/club/wpia/gigi/testUtils/BusinessTest.java @@ -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; + } } diff --git a/tests/club/wpia/gigi/testUtils/ClientBusinessTest.java b/tests/club/wpia/gigi/testUtils/ClientBusinessTest.java index 023d55ed..3a615694 100644 --- a/tests/club/wpia/gigi/testUtils/ClientBusinessTest.java +++ b/tests/club/wpia/gigi/testUtils/ClientBusinessTest.java @@ -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(); + } } diff --git a/tests/club/wpia/gigi/testUtils/ConfiguredTest.java b/tests/club/wpia/gigi/testUtils/ConfiguredTest.java index c0d6d4f7..43be01db 100644 --- a/tests/club/wpia/gigi/testUtils/ConfiguredTest.java +++ b/tests/club/wpia/gigi/testUtils/ConfiguredTest.java @@ -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()); + } } diff --git a/tests/club/wpia/gigi/util/TestNotary.java b/tests/club/wpia/gigi/util/TestNotary.java index 4028ebda..6c6780e9 100644 --- a/tests/club/wpia/gigi/util/TestNotary.java +++ b/tests/club/wpia/gigi/util/TestNotary.java @@ -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()); } } diff --git a/util-testing/club/wpia/gigi/pages/Manager.java b/util-testing/club/wpia/gigi/pages/Manager.java index c9dba62f..7dabe4f2 100644 --- a/util-testing/club/wpia/gigi/pages/Manager.java +++ b/util-testing/club/wpia/gigi/pages/Manager.java @@ -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.
    "); + 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 = "
    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:
    "); 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("
    Go back"); } + 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 mails = emails.get(mail); HashMap 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(); + } } diff --git a/util-testing/club/wpia/gigi/pages/Manager.templ b/util-testing/club/wpia/gigi/pages/Manager.templ index 857a6d18..f6e1519a 100644 --- a/util-testing/club/wpia/gigi/pages/Manager.templ +++ b/util-testing/club/wpia/gigi/pages/Manager.templ @@ -52,6 +52,17 @@ Email: + + + +Make RA Agent: + +Email: + +Current requirements: 100 VP, passed Assurer Challenge, signed RA Agent Contract
    + + + Add 100 Verification Points: @@ -59,7 +70,7 @@ Email: Verification Points to issue to preferred name:
    - +