Only covers the basic functionality.
The full text of the contract and the email will be covered in a later
patch.
The data contract.id and contract.token will be used for pdf-output in a
later patch.
The implementation of the restrictions connected to signed contract will
be covered in a later patch.
Change-Id: I5b47d31458779d227a4f9702a9e7563ab210e7e5
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;
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);
}
- 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;
"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` (
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");
--- /dev/null
+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");
--- /dev/null
+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;
+ }
+ }
+}
--- /dev/null
+Subject: <?=_${actionsubject} of RA Agent Contract?>
+
+<?=_Hello ${user}, ?>
+
+<?=_you just ${actionbody} the RA Agent Contract.?>
+
return false;
}
+ if ( !Contract.hasSignedContract(this, Contract.ContractType.RA_AGENT_CONTRACT)) {
+ return false;
+ }
+
return hasPassedCATS();
}
--- /dev/null
+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.pages.LoginPage;
+import club.wpia.gigi.pages.Page;
+
+public class MyContracts extends Page {
+
+ public static final String PATH = "/account/contracts";
+
+ 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()));
+ }
+
+ getDefaultTemplate().output(out, getLanguage(req), vars);
+ }
+}
--- /dev/null
+<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>Name: <?=$raname?> - RA Agent hereafter -</p>
+
+ <p>and</p>
+
+ <p>SomeCA - CA hereafter -</p>
+ <p>SomeAddress</p>
+
+ <p>regarding the conduction of registration authority tasks.</p>
+
+ <p>signed <?=$csdate?></p>
+ </div>
+</div>
--- /dev/null
+<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>
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;
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;
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.");
}
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);
}
}
--- /dev/null
+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);
+ }
+
+}
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";
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.ContractType;
import club.wpia.gigi.dbObjects.Domain;
import club.wpia.gigi.dbObjects.DomainPingType;
import club.wpia.gigi.dbObjects.User;
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;
ps2.setInt(2, User.getById(uid).getPreferredName().getId());
ps2.execute();
}
+
+ // 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 MailReceiver getMailReceiver() {
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;
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 {
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());
}
}
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;
ps.setString(6, getRandomCountry().getCode());
ps.execute();
}
+ new Contract(u, ContractType.RA_AGENT_CONTRACT);
return u;
}
}