From 85b9c7cfd0426eca278b609b96dd6b17b0a5144f Mon Sep 17 00:00:00 2001 From: =?utf8?q?Felix=20D=C3=B6rre?= Date: Sun, 17 Jul 2016 12:49:05 +0200 Subject: [PATCH] add: implement + test api for find-an-agent-system Change-Id: I7e90a6c4189107e07f06b9554e40699b7b9dfe16 --- src/org/cacert/gigi/Gigi.java | 10 ++ src/org/cacert/gigi/api/FindAgent.java | 105 ++++++++++++++++++ src/org/cacert/gigi/api/GigiAPI.java | 1 + .../gigi/database/DatabaseConnection.java | 2 +- .../cacert/gigi/database/tableStructure.sql | 4 +- .../cacert/gigi/database/upgrade/from_18.sql | 1 + src/org/cacert/gigi/dbObjects/Group.java | 2 +- .../gigi/pages/account/FindAgentAccess.java | 54 +++++++++ .../gigi/pages/account/FindAgentAccess.templ | 2 + tests/org/cacert/gigi/api/TestFindAgent.java | 87 +++++++++++++++ 10 files changed, 264 insertions(+), 4 deletions(-) create mode 100644 src/org/cacert/gigi/api/FindAgent.java create mode 100644 src/org/cacert/gigi/database/upgrade/from_18.sql create mode 100644 src/org/cacert/gigi/pages/account/FindAgentAccess.java create mode 100644 src/org/cacert/gigi/pages/account/FindAgentAccess.templ create mode 100644 tests/org/cacert/gigi/api/TestFindAgent.java diff --git a/src/org/cacert/gigi/Gigi.java b/src/org/cacert/gigi/Gigi.java index 37d0e62a..ab0e1b6f 100644 --- a/src/org/cacert/gigi/Gigi.java +++ b/src/org/cacert/gigi/Gigi.java @@ -31,6 +31,7 @@ import org.cacert.gigi.output.Menu; import org.cacert.gigi.output.MenuCollector; import org.cacert.gigi.output.PageMenuItem; import org.cacert.gigi.output.SimpleMenuItem; +import org.cacert.gigi.output.template.Form; import org.cacert.gigi.output.template.Form.CSRFException; import org.cacert.gigi.output.template.Outputable; import org.cacert.gigi.output.template.Template; @@ -39,6 +40,7 @@ import org.cacert.gigi.pages.HandlesMixedRequest; import org.cacert.gigi.pages.LoginPage; import org.cacert.gigi.pages.LogoutPage; import org.cacert.gigi.pages.MainPage; +import org.cacert.gigi.pages.OneFormPage; import org.cacert.gigi.pages.Page; import org.cacert.gigi.pages.PasswordResetPage; import org.cacert.gigi.pages.RootCertPage; @@ -46,6 +48,7 @@ import org.cacert.gigi.pages.StaticPage; import org.cacert.gigi.pages.TestSecure; import org.cacert.gigi.pages.Verify; import org.cacert.gigi.pages.account.ChangePasswordPage; +import org.cacert.gigi.pages.account.FindAgentAccess; import org.cacert.gigi.pages.account.History; import org.cacert.gigi.pages.account.MyDetails; import org.cacert.gigi.pages.account.UserTrainings; @@ -154,6 +157,13 @@ public final class Gigi extends HttpServlet { putPage(ChangePasswordPage.PATH, new ChangePasswordPage(), "My Account"); putPage(LogoutPage.PATH, new LogoutPage(), "My Account"); putPage(History.PATH, new History(false), "My Account"); + putPage(FindAgentAccess.PATH, new OneFormPage("Access to Find Agent", FindAgentAccess.class) { + + @Override + public String getSuccessPath(Form f) { + return FindAgentAccess.PATH; + } + }, "My Account"); putPage(History.SUPPORT_PATH, new History(true), null); putPage(UserTrainings.PATH, new UserTrainings(false), "My Account"); putPage(MyDetails.PATH, new MyDetails(), "My Account"); diff --git a/src/org/cacert/gigi/api/FindAgent.java b/src/org/cacert/gigi/api/FindAgent.java new file mode 100644 index 00000000..29e536e1 --- /dev/null +++ b/src/org/cacert/gigi/api/FindAgent.java @@ -0,0 +1,105 @@ +package org.cacert.gigi.api; + +import java.io.IOException; +import java.util.HashMap; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.cacert.gigi.dbObjects.Certificate; +import org.cacert.gigi.dbObjects.CertificateOwner; +import org.cacert.gigi.dbObjects.Group; +import org.cacert.gigi.dbObjects.Organisation; +import org.cacert.gigi.dbObjects.User; +import org.cacert.gigi.email.EmailProvider; +import org.cacert.gigi.pages.account.FindAgentAccess; +import org.cacert.gigi.util.ServerConstants; + +public class FindAgent extends APIPoint { + + public static final String PATH_RESOLVE = "/find-agent/resolve"; + + public static final String PATH_INFO = "/find-agent/info"; + + public static final String PATH_MAIL = "/find-agent/email"; + + public FindAgent() {} + + public static void register(HashMap api) { + APIPoint p = new FindAgent(); + api.put(PATH_RESOLVE, p); + api.put(PATH_INFO, p); + api.put(PATH_MAIL, p); + } + + @Override + public void process(HttpServletRequest req, HttpServletResponse resp, CertificateOwner u) throws IOException { + if ( !(u instanceof Organisation)) { + resp.sendError(500, "Error, invalid cert"); + return; + } + if ( !((Organisation) u).isSelfOrganisation()) { + resp.sendError(500, "Error, invalid cert"); + return; + } + String pi = req.getPathInfo(); + if (pi.equals(PATH_RESOLVE)) { + String serial = req.getParameter("serial"); + if (serial == null) { + resp.sendError(500, "Error, requires serial"); + return; + } + Certificate c = Certificate.getBySerial(serial); + if (c == null) { + resp.sendError(500, "Error, requires serial"); + return; + } + CertificateOwner co = c.getOwner(); + if ( !(co instanceof User)) { + resp.sendError(500, "Error, requires serial"); + return; + } + User us = (User) co; + if ( !us.isInGroup(Group.LOCATE_AGENT)) { + resp.setStatus(501); + resp.setContentType("text/plain; charset=UTF-8"); + resp.getWriter().println("https://" + ServerConstants.getSecureHostNamePort() + FindAgentAccess.PATH); + return; + } + resp.setContentType("text/plain; charset=UTF-8"); + resp.getWriter().print(us.getId()); + } else if (pi.equals(PATH_INFO)) { + resp.setContentType("text/plain; charset=UTF-8"); + String[] uids = req.getParameterValues("id"); + for (String i : uids) { + User u1 = User.getById(Integer.parseInt(i)); + if ( !u1.isInGroup(Group.LOCATE_AGENT)) { + continue; + } + // date, recheck(?), name + resp.getWriter().println(i + "," + u1.canAssure() + "," + u1.getName().toString()); + } + } else if (pi.equals(PATH_MAIL)) { + String id = req.getParameter("from"); + String rid = req.getParameter("to"); + String subject = req.getParameter("subject"); + String body = req.getParameter("body"); + if (id == null || rid == null || subject == null || body == null) { + resp.sendError(500, "Error, parameter missing"); + return; + } + User from = User.getById(Integer.parseInt(id)); + User to = User.getById(Integer.parseInt(rid)); + if (from == null || to == null) { + resp.sendError(500, "Error, user not found"); + return; + } + if ( !from.isInGroup(Group.LOCATE_AGENT) || !to.isInGroup(Group.LOCATE_AGENT)) { + resp.sendError(501, "Error, user needs to enable access"); + return; + + } + EmailProvider.getInstance().sendMail(to.getEmail(), "[Find Agent] " + subject, body, to.getEmail(), null, null, null, null, false); + } + } +} diff --git a/src/org/cacert/gigi/api/GigiAPI.java b/src/org/cacert/gigi/api/GigiAPI.java index dbac5a88..2f5e922c 100644 --- a/src/org/cacert/gigi/api/GigiAPI.java +++ b/src/org/cacert/gigi/api/GigiAPI.java @@ -24,6 +24,7 @@ public class GigiAPI extends HttpServlet { api.put(RevokeCertificate.PATH, new RevokeCertificate()); api.put(CATSImport.PATH, new CATSImport()); api.put(CATSResolve.PATH, new CATSResolve()); + FindAgent.register(api); } @Override diff --git a/src/org/cacert/gigi/database/DatabaseConnection.java b/src/org/cacert/gigi/database/DatabaseConnection.java index 0be7becd..39071a3d 100644 --- a/src/org/cacert/gigi/database/DatabaseConnection.java +++ b/src/org/cacert/gigi/database/DatabaseConnection.java @@ -122,7 +122,7 @@ public class DatabaseConnection { } - public static final int CURRENT_SCHEMA_VERSION = 18; + public static final int CURRENT_SCHEMA_VERSION = 19; public static final int CONNECTION_TIMEOUT = 24 * 60 * 60; diff --git a/src/org/cacert/gigi/database/tableStructure.sql b/src/org/cacert/gigi/database/tableStructure.sql index 0764954c..467160cd 100644 --- a/src/org/cacert/gigi/database/tableStructure.sql +++ b/src/org/cacert/gigi/database/tableStructure.sql @@ -330,7 +330,7 @@ CREATE TABLE IF NOT EXISTS "arbitrations" ( DROP TABLE IF EXISTS "user_groups"; DROP TYPE IF EXISTS "userGroup"; -CREATE TYPE "userGroup" AS enum('supporter','arbitrator','blockedassuree','blockedassurer','blockedlogin','ttp-assurer','ttp-applicant', 'codesigning', 'orgassurer', 'blockedcert', 'nucleus-assurer'); +CREATE TYPE "userGroup" AS enum('supporter','arbitrator','blockedassuree','blockedassurer','blockedlogin','ttp-assurer','ttp-applicant', 'codesigning', 'orgassurer', 'blockedcert', 'nucleus-assurer', 'locate-agent'); CREATE TABLE IF NOT EXISTS "user_groups" ( "id" serial NOT NULL, @@ -377,7 +377,7 @@ CREATE TABLE "schemeVersion" ( "version" smallint NOT NULL, PRIMARY KEY ("version") ); -INSERT INTO "schemeVersion" (version) VALUES(18); +INSERT INTO "schemeVersion" (version) VALUES(19); DROP TABLE IF EXISTS `passwordResetTickets`; CREATE TABLE `passwordResetTickets` ( diff --git a/src/org/cacert/gigi/database/upgrade/from_18.sql b/src/org/cacert/gigi/database/upgrade/from_18.sql new file mode 100644 index 00000000..9e340280 --- /dev/null +++ b/src/org/cacert/gigi/database/upgrade/from_18.sql @@ -0,0 +1 @@ +ALTER TYPE "userGroup" ADD VALUE 'locate-agent'; diff --git a/src/org/cacert/gigi/dbObjects/Group.java b/src/org/cacert/gigi/dbObjects/Group.java index 16cde6c1..d5d38efd 100644 --- a/src/org/cacert/gigi/dbObjects/Group.java +++ b/src/org/cacert/gigi/dbObjects/Group.java @@ -11,7 +11,7 @@ public enum Group { BLOCKEDLOGIN("blockedlogin", "may not login"), BLOCKEDCERT("blockedcert", "may not issue certificates"), // TTP_ASSURER("ttp-assurer", "may assure via TTP"), TTP_APPLICANT("ttp-applicant", "requests to be assured via ttp"), // CODESIGNING("codesigning", "may issue codesigning certificates"), ORGASSURER("orgassurer", "may assure organisations"), // - NUCLEUS_ASSURER("nucleus-assurer", "may issue nucleus assurances"); + NUCLEUS_ASSURER("nucleus-assurer", "may issue nucleus assurances"), LOCATE_AGENT("locate-agent", "wants access to the locate agent system"); private final String dbName; diff --git a/src/org/cacert/gigi/pages/account/FindAgentAccess.java b/src/org/cacert/gigi/pages/account/FindAgentAccess.java new file mode 100644 index 00000000..2f325ebb --- /dev/null +++ b/src/org/cacert/gigi/pages/account/FindAgentAccess.java @@ -0,0 +1,54 @@ +package org.cacert.gigi.pages.account; + +import java.io.PrintWriter; +import java.util.Map; + +import javax.servlet.http.HttpServletRequest; + +import org.cacert.gigi.GigiApiException; +import org.cacert.gigi.dbObjects.Group; +import org.cacert.gigi.dbObjects.User; +import org.cacert.gigi.localisation.Language; +import org.cacert.gigi.output.template.Form; +import org.cacert.gigi.output.template.Template; +import org.cacert.gigi.pages.LoginPage; + +public class FindAgentAccess extends Form { + + private User target; + + public FindAgentAccess(HttpServletRequest hsr) { + super(hsr); + target = LoginPage.getUser(hsr); + } + + public static final String PATH = "/account/find-agent"; + + private static Template t; + static { + t = new Template(ChangePasswordPage.class.getResource("FindAgentAccess.templ")); + } + + @Override + public boolean submit(PrintWriter out, HttpServletRequest req) throws GigiApiException { + String nv = req.getParameter("new-val"); + if (nv == null) { + return false; + } + if (nv.equals("enable")) { + target.grantGroup(target, Group.LOCATE_AGENT); + } else { + target.revokeGroup(target, Group.LOCATE_AGENT); + } + return true; + } + + @Override + protected void outputContent(PrintWriter out, Language l, Map vars) { + boolean inGroup = target.isInGroup(Group.LOCATE_AGENT); + vars.put("enable", inGroup ? " disabled" : ""); + vars.put("disable", !inGroup ? " disabled" : ""); + t.output(out, l, vars); + } + +} diff --git a/src/org/cacert/gigi/pages/account/FindAgentAccess.templ b/src/org/cacert/gigi/pages/account/FindAgentAccess.templ new file mode 100644 index 00000000..41456363 --- /dev/null +++ b/src/org/cacert/gigi/pages/account/FindAgentAccess.templ @@ -0,0 +1,2 @@ + + diff --git a/tests/org/cacert/gigi/api/TestFindAgent.java b/tests/org/cacert/gigi/api/TestFindAgent.java new file mode 100644 index 00000000..e889f1e8 --- /dev/null +++ b/tests/org/cacert/gigi/api/TestFindAgent.java @@ -0,0 +1,87 @@ +package org.cacert.gigi.api; + +import static org.hamcrest.CoreMatchers.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.HttpURLConnection; +import java.security.GeneralSecurityException; + +import org.cacert.gigi.GigiApiException; +import org.cacert.gigi.dbObjects.Certificate; +import org.cacert.gigi.dbObjects.Certificate.CSRType; +import org.cacert.gigi.dbObjects.Certificate.SANType; +import org.cacert.gigi.dbObjects.CertificateProfile; +import org.cacert.gigi.dbObjects.Digest; +import org.cacert.gigi.dbObjects.Group; +import org.cacert.gigi.dbObjects.User; +import org.cacert.gigi.pages.account.FindAgentAccess; +import org.cacert.gigi.testUtils.IOUtils; +import org.cacert.gigi.testUtils.RestrictedApiTest; +import org.cacert.gigi.testUtils.TestEmailReceiver.TestMail; +import org.junit.Test; + +public class TestFindAgent extends RestrictedApiTest { + + @Test + public void testResolve() throws GigiApiException, IOException, GeneralSecurityException, InterruptedException { + Certificate target2 = new Certificate(u, u, Certificate.buildDN("EMAIL", u.getEmail()), Digest.SHA256, generatePEMCSR(generateKeypair(), "EMAIL=" + u.getEmail()), CSRType.CSR, CertificateProfile.getByName("client"), new Certificate.SubjectAlternateName(SANType.EMAIL, "cats@cacert.org")); + await(target2.issue(null, "2y", u)); + + HttpURLConnection v = doApi(FindAgent.PATH_RESOLVE, "serial=" + target2.getSerial().toLowerCase()); + assertEquals(501, v.getResponseCode()); + assertThat(IOUtils.readURL(new InputStreamReader(v.getErrorStream(), "UTF-8")), containsString(FindAgentAccess.PATH)); + + grant(u.getEmail(), Group.LOCATE_AGENT); + v = doApi(FindAgent.PATH_RESOLVE, "serial=" + target2.getSerial().toLowerCase()); + assertEquals(u.getId(), Integer.parseInt(IOUtils.readURL(v))); + } + + @Test + public void testMailA() throws GigiApiException, IOException, GeneralSecurityException, InterruptedException { + testMail(true); + } + + @Test + public void testMailB() throws GigiApiException, IOException, GeneralSecurityException, InterruptedException { + testMail(false); + } + + public void testMail(boolean userUFirst) throws GigiApiException, IOException, GeneralSecurityException, InterruptedException { + int u2 = createVerifiedUser("f", "l", createUniqueName() + "@email.com", TEST_PASSWORD); + User us2 = User.getById(u2); + + // email sending fails + HttpURLConnection v = doApi(FindAgent.PATH_MAIL, "from=" + id + "&to=" + u2 + "&subject=the-subject&body=body"); + assertEquals(v.getResponseMessage(), 501, v.getResponseCode()); + assertThat(v.getResponseMessage(), containsString("needs to enable access")); + + // even if sender enables service + grant((userUFirst ? u : us2).getEmail(), Group.LOCATE_AGENT); + v = doApi(FindAgent.PATH_MAIL, "from=" + id + "&to=" + u2 + "&subject=the-subject&body=body"); + assertEquals(v.getResponseMessage(), 501, v.getResponseCode()); + assertThat(v.getResponseMessage(), containsString("needs to enable access")); + + // receiver needs to enable access as well + grant((userUFirst ? us2 : u).getEmail(), Group.LOCATE_AGENT); + v = doApi(FindAgent.PATH_MAIL, "from=" + id + "&to=" + u2 + "&subject=the-subject&body=body"); + assertEquals(v.getResponseMessage(), 200, v.getResponseCode()); + TestMail mail = getMailReceiver().receive(); + assertEquals("body", mail.getMessage()); + assertThat(mail.getSubject(), containsString("the-subject")); + assertEquals(us2.getEmail(), mail.getTo()); + } + + @Test + public void testLookupName() throws GigiApiException, IOException, GeneralSecurityException, InterruptedException { + int u2 = createVerifiedUser("f", "l", createUniqueName() + "@email.com", TEST_PASSWORD); + + String res = IOUtils.readURL(doApi(FindAgent.PATH_INFO, "id=" + id + "&id=" + u2)).replace("\r", ""); + assertEquals(res, ""); + grant(email, Group.LOCATE_AGENT); + grant(User.getById(u2).getEmail(), Group.LOCATE_AGENT); + res = IOUtils.readURL(doApi(FindAgent.PATH_INFO, "id=" + id + "&id=" + u2)).replace("\r", ""); + assertEquals(id + ",true," + u.getName().toString() + "\n" + u2 + ",false," + User.getById(u2).getName().toString() + "\n", res); + } +} -- 2.39.2