Merge branch 'master' of https://code.google.com/p/plexi
Conflicts:
src/com/google/enterprise/adaptor/GsaCommunicationHandler.java
diff --git a/src/com/google/enterprise/adaptor/Config.java b/src/com/google/enterprise/adaptor/Config.java
index 2fc1f9e..30c6625 100644
--- a/src/com/google/enterprise/adaptor/Config.java
+++ b/src/com/google/enterprise/adaptor/Config.java
@@ -36,6 +36,10 @@
* <td><b>name</b></td><td><b>meaning</b></td>
* <tr><td> </td><td>adaptor.autoUnzip </td><td> expand zip files and send
* each file inside separatly. Defaults to false
+ * <tr><td> </td><td>adaptor.sendDocControlsHeader </td><td>use
+ * X-Gsa-Doc-Controls HTTP header with namespaced ACLs.
+ * Otherwise ACLs are sent without namespace and as metadata.
+ * Defaults to false
* <tr><td> </td><td>adaptor.fullListingSchedule </td><td> when to invoke
* {@link Adaptor#getDocIds Adaptor.getDocIds}, in cron format (minute,
* hour, day of month, month, day of week). Defaults to 0 3 * * *
@@ -207,6 +211,7 @@
addKey("transform.maxDocumentBytes", "1048576");
addKey("transform.required", "false");
addKey("journal.reducedMem", "true");
+ addKey("adaptor.sendDocControlsHeader", "false");
}
public Set<String> getAllKeys() {
@@ -394,6 +399,10 @@
return Boolean.parseBoolean(getValue("server.useCompression"));
}
+ public boolean sendDocControlsHeader() {
+ return Boolean.parseBoolean(getValue("adaptor.sendDocControlsHeader"));
+ }
+
/**
* Optional (default false): Adds no-recrawl bit with sent records in feed
* file. If connector handles updates and deletes then GSA does not have to
diff --git a/src/com/google/enterprise/adaptor/DocumentHandler.java b/src/com/google/enterprise/adaptor/DocumentHandler.java
index c7ffb56..265165e 100644
--- a/src/com/google/enterprise/adaptor/DocumentHandler.java
+++ b/src/com/google/enterprise/adaptor/DocumentHandler.java
@@ -16,10 +16,15 @@
import static java.util.Map.Entry;
+import com.google.common.annotations.VisibleForTesting;
+
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpsExchange;
+import org.json.simple.JSONArray;
+import org.json.simple.JSONObject;
+
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
@@ -61,6 +66,7 @@
private final int transformMaxBytes;
private final boolean transformRequired;
private final boolean useCompression;
+ private final boolean sendDocControls;
/**
* {@code authnHandler} and {@code transform} may be {@code null}.
@@ -72,7 +78,8 @@
SessionManager<HttpExchange> sessionManager,
TransformPipeline transform, int transformMaxBytes,
boolean transformRequired, boolean useCompression,
- Watchdog watchdog, AsyncPusher pusher) {
+ Watchdog watchdog, AsyncPusher pusher,
+ boolean sendDocControls) {
if (docIdDecoder == null || docIdEncoder == null || journal == null
|| adaptor == null || sessionManager == null || watchdog == null
|| pusher == null) {
@@ -90,6 +97,7 @@
this.useCompression = useCompression;
this.watchdog = watchdog;
this.pusher = pusher;
+ this.sendDocControls = sendDocControls;
initFullAccess(gsaHostname, fullAccessHosts);
}
@@ -292,7 +300,8 @@
return (sb.length() == 0) ? "" : sb.substring(0, sb.length() - 1);
}
- static String formAclHeader(Acl acl, DocIdEncoder docIdEncoder) {
+ @VisibleForTesting
+ static String formUnqualifiedAclHeader(Acl acl, DocIdEncoder docIdEncoder) {
if (acl == null) {
return "";
}
@@ -333,6 +342,62 @@
return sb.substring(0, sb.length() - 1);
}
+ @VisibleForTesting
+ static String formNamespacedAclHeader(Acl acl, DocIdEncoder enc) {
+ if (null == acl) {
+ return "";
+ }
+ if (Acl.EMPTY.equals(acl)) {
+ acl = Acl.FAKE_EMPTY;
+ }
+ Map<String, Object> gsaAcl = new TreeMap<String, Object>();
+ List<Map<String, String>> gsaAclEntries = makeGsaAclEntries(acl);
+ if (!gsaAclEntries.isEmpty()) {
+ gsaAcl.put("entries", gsaAclEntries);
+ }
+ if (null != acl.getInheritFrom()) {
+ URI from = enc.encodeDocId(acl.getInheritFrom());
+ gsaAcl.put("inherit_from", "" + from);
+ }
+ if (acl.getInheritanceType() != Acl.InheritanceType.LEAF_NODE) {
+ String type = "" + acl.getInheritanceType();
+ gsaAcl.put("inheritance_type", "" + type);
+ }
+ return JSONObject.toJSONString(gsaAcl);
+ }
+
+ private static List<Map<String, String>> makeGsaAclEntries(Acl acl) {
+ List<Map<String, String>> princ = new ArrayList<Map<String, String>>();
+ for (Principal p : acl.getPermitGroups()) {
+ princ.add(makeGsaAclEntry("permit", acl, p));
+ }
+ for (Principal p : acl.getDenyGroups()) {
+ princ.add(makeGsaAclEntry("deny", acl, p));
+ }
+ for (Principal p : acl.getPermitUsers()) {
+ princ.add(makeGsaAclEntry("permit", acl, p));
+ }
+ for (Principal p : acl.getDenyUsers()) {
+ princ.add(makeGsaAclEntry("deny", acl, p));
+ }
+ return princ;
+ }
+
+ private static Map<String, String> makeGsaAclEntry(String access,
+ Acl acl, Principal p) {
+ Map<String, String> gsaEntry = new TreeMap<String, String>();
+ gsaEntry.put("access", access);
+ gsaEntry.put("scope", p.isUser() ? "user" : "group");
+ gsaEntry.put("name", p.getName());
+ if (!Principal.DEFAULT_NAMESPACE.equals(p.getNamespace())) {
+ gsaEntry.put("namespace", p.getNamespace());
+ }
+ if (!acl.isEverythingCaseSensitive()) {
+ gsaEntry.put("case_sensitivity_type", "everything_case_insensitive");
+ }
+ return gsaEntry;
+ }
+
/**
* Format the GSA-specific anchor header value for extra crawl-time anchors.
*/
@@ -714,20 +779,39 @@
private void startSending(boolean hasContent) throws IOException {
if (requestIsFromFullyTrustedClient(ex)) {
- if (displayUrl != null || crawlOnce || lock) {
- // Emulate these crawl-time values by sending them in feeds since they
- // aren't supported at crawl-time on GSA 7.0.
- pusher.asyncPushItem(new DocIdPusher.Record.Builder(docId)
- .setResultLink(displayUrl).setCrawlOnce(crawlOnce).setLock(lock)
- .build());
- }
// Always specify metadata and ACLs, even when empty, to replace
// previous values.
ex.getResponseHeaders().add("X-Gsa-External-Metadata",
- formMetadataHeader(metadata));
- acl = checkAndWorkaroundGsa70Acl(acl);
- ex.getResponseHeaders().add("X-Gsa-External-Metadata",
- formAclHeader(acl, docIdEncoder));
+ formMetadataHeader(metadata));
+ if (sendDocControls) {
+ ex.getResponseHeaders().add("X-Gsa-Doc-Controls", "acl="
+ + percentEncode(formNamespacedAclHeader(acl, docIdEncoder)));
+ if (null != displayUrl) {
+ String link = "display_url=" + percentEncode("" + displayUrl);
+ ex.getResponseHeaders().add("X-Gsa-Doc-Controls", link);
+ }
+ /*
+ TODO(ejona): enable once sending crawl-once at crawl time is possible
+ ex.getResponseHeaders().add("X-Gsa-Doc-Controls",
+ "crawl-once=" + crawlOnce);
+ */
+ /*
+ TODO(ejona): enable once sending lock at crawl time is possible
+ ex.getResponseHeaders().add("X-Gsa-Doc-Controls", "lock=" + lock);
+ */
+ } else {
+ acl = checkAndWorkaroundGsa70Acl(acl);
+ ex.getResponseHeaders().add("X-Gsa-External-Metadata",
+ formUnqualifiedAclHeader(acl, docIdEncoder));
+ if (displayUrl != null || crawlOnce || lock) {
+ // Emulate these crawl-time values by sending them in feeds
+ // since they aren't supported at crawl-time on GSA 7.0.
+ pusher.asyncPushItem(new DocIdPusher.Record.Builder(docId)
+ .setResultLink(displayUrl).setCrawlOnce(crawlOnce).setLock(lock)
+ .build());
+ // TODO: figure out how to notice that a true went false
+ }
+ }
if (!anchorUris.isEmpty()) {
ex.getResponseHeaders().add("X-Gsa-External-Anchor",
formAnchorHeader(anchorUris, anchorTexts));
diff --git a/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java b/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java
index 9ae49d0..7ef85c4 100644
--- a/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java
+++ b/src/com/google/enterprise/adaptor/GsaCommunicationHandler.java
@@ -268,17 +268,20 @@
5 /* max latency */, TimeUnit.MINUTES,
2 * config.getFeedMaxUrls() /* queue size */);
backgroundExecutor.execute(waiter.runnable(asyncDocIdSender.worker()));
- addFilters(scope.createContext(config.getServerBaseUri().getPath()
- + config.getServerDocIdPath(),
- new DocumentHandler(docIdCodec, docIdCodec, journal, adaptor,
- config.getGsaHostname(),
- config.getServerFullAccessHosts(),
- authnHandler, sessionManager,
- createTransformPipeline(),
- config.getTransformMaxDocumentBytes(),
- config.isTransformRequired(),
- config.isServerToUseCompression(), watchdog,
- asyncDocIdSender)));
+ DocumentHandler docHandler = new DocumentHandler(
+ docIdCodec, docIdCodec, journal, adaptor,
+ config.getGsaHostname(),
+ config.getServerFullAccessHosts(),
+ authnHandler, sessionManager,
+ createTransformPipeline(),
+ config.getTransformMaxDocumentBytes(),
+ config.isTransformRequired(),
+ config.isServerToUseCompression(), watchdog,
+ asyncDocIdSender,
+ config.sendDocControlsHeader());
+ String handlerPath = config.getServerBaseUri().getPath()
+ + config.getServerDocIdPath();
+ addFilters(scope.createContext(handlerPath, docHandler));
// Start communicating with other services. As a general rule, by this time
// we want all services we provide to be up and running. However, note that
diff --git a/src/com/google/enterprise/adaptor/examples/AdaptorWithCrawlTimeMetadataTemplate.java b/src/com/google/enterprise/adaptor/examples/AdaptorWithCrawlTimeMetadataTemplate.java
index 6fd109e..feab60e 100644
--- a/src/com/google/enterprise/adaptor/examples/AdaptorWithCrawlTimeMetadataTemplate.java
+++ b/src/com/google/enterprise/adaptor/examples/AdaptorWithCrawlTimeMetadataTemplate.java
@@ -16,6 +16,9 @@
import com.google.enterprise.adaptor.AbstractAdaptor;
import com.google.enterprise.adaptor.Acl;
+import com.google.enterprise.adaptor.AuthnIdentity;
+import com.google.enterprise.adaptor.AuthzStatus;
+import com.google.enterprise.adaptor.Config;
import com.google.enterprise.adaptor.DocId;
import com.google.enterprise.adaptor.DocIdPusher;
import com.google.enterprise.adaptor.GroupPrincipal;
@@ -24,6 +27,7 @@
import com.google.enterprise.adaptor.UserPrincipal;
import java.io.*;
+import java.net.*;
import java.nio.charset.Charset;
import java.util.*;
import java.util.logging.Logger;
@@ -32,7 +36,9 @@
* Demonstrates what code is necessary for putting restricted
* content onto a GSA. The key operations are:
* <ol><li> providing document ids
- * <li> providing document bytes and ACLs given a document id</ol>
+ * <li> providing document bytes and ACLs given a document id
+ * <li> restricting access to documents
+ * </ol>
*/
public class AdaptorWithCrawlTimeMetadataTemplate extends AbstractAdaptor {
private static final Logger log
@@ -44,8 +50,9 @@
public void getDocIds(DocIdPusher pusher) throws InterruptedException {
ArrayList<DocId> mockDocIds = new ArrayList<DocId>();
/* Replace this mock data with code that lists your repository. */
- mockDocIds.add(new DocId("1001"));
- mockDocIds.add(new DocId("1002"));
+ mockDocIds.add(new DocId("7007"));
+ mockDocIds.add(new DocId("7007-parent"));
+ mockDocIds.add(new DocId("8008"));
pusher.pushDocIds(mockDocIds);
}
@@ -54,29 +61,22 @@
public void getDocContent(Request req, Response resp) throws IOException {
DocId id = req.getDocId();
String str;
- if ("1001".equals(id.getUniqueId())) {
- str = "Document 1001 says hello and apple orange";
- List<UserPrincipal> users1001 = Arrays.asList(
- new UserPrincipal("peter"),
- new UserPrincipal("bart"),
- new UserPrincipal("simon")
- );
- List<GroupPrincipal> groups1001 = Arrays.asList(
- new GroupPrincipal("support"),
- new GroupPrincipal("sales")
- );
+ if ("7007".equals(id.getUniqueId())) {
+ str = "Document 7007 is for surviving members of magnificent 7";
// Add custom meta items.
resp.addMetadata("my-special-key", "my-custom-value");
resp.addMetadata("date", "not soon enough");
- // Must set metadata before getting OutputStream
- resp.setAcl(new Acl.Builder()
- // Add user ACL.
- .setPermitUsers(users1001)
- // Add group ACL.
- .setPermitGroups(groups1001)
- .build());
- } else if ("1002".equals(id.getUniqueId())) {
- str = "Document 1002 says hello and banana strawberry";
+ // Add custom acl.
+ resp.setAcl(makeAclFor7007());
+ // Add other attributes.
+ resp.setDisplayUrl(URI.create("https://www.google.com/"));
+ } else if ("7007-parent".equals(id.getUniqueId())) {
+ str = "I have a child named 7007";
+ resp.setAcl(makeAclFor7007Parent());
+ // Add custom meta items.
+ resp.addMetadata("my-day", "parent's day");
+ } else if ("8008".equals(id.getUniqueId())) {
+ str = "Document 8008 says hello and banana strawberry";
// Must add metadata before getting OutputStream
resp.addMetadata("date", "never than late");
} else {
@@ -91,4 +91,81 @@
public static void main(String[] args) {
AbstractAdaptor.main(new AdaptorWithCrawlTimeMetadataTemplate(), args);
}
+
+ private Acl makeAclFor7007() {
+ List<UserPrincipal> users7007 = Arrays.asList(
+ new UserPrincipal("chris@seven7.google.com"),
+ new UserPrincipal("vin"),
+ new UserPrincipal("chico")
+ );
+ List<GroupPrincipal> groups7007 = Arrays.asList(
+ new GroupPrincipal("magnificent@seven7.google.com"),
+ new GroupPrincipal("cowboys@seven7.google.com")
+ );
+ List<UserPrincipal> deniedUsers7007 = Arrays.asList(
+ new UserPrincipal("britt"),
+ new UserPrincipal("harry@seven7.google.com"),
+ new UserPrincipal("lee"),
+ new UserPrincipal("bernardo@seven7.google.com"),
+ new UserPrincipal("calvera")
+ );
+ List<GroupPrincipal> deniedGroups7007 = Arrays.asList(
+ new GroupPrincipal("dead"),
+ new GroupPrincipal("samurai@seven7.google.com", "kurosawa"),
+ new GroupPrincipal("dead", "kurosawa")
+ );
+ return new Acl.Builder()
+ .setPermitUsers(users7007)
+ .setDenyUsers(deniedUsers7007)
+ .setPermitGroups(groups7007)
+ .setDenyGroups(deniedGroups7007)
+ .setInheritFrom(new DocId("7007-parent"))
+ .setEverythingCaseInsensitive()
+ .build();
+ }
+
+ private Acl makeAclFor7007Parent() {
+ return new Acl.Builder()
+ .setInheritanceType(Acl.InheritanceType.PARENT_OVERRIDES)
+ .setPermitUsers(Arrays.asList(new UserPrincipal("vin")))
+ .setDenyUsers(Arrays.asList(new UserPrincipal("chico")))
+ .build();
+ }
+
+ @Override
+ public Map<DocId, AuthzStatus> isUserAuthorized(AuthnIdentity userIdentity,
+ Collection<DocId> ids) throws IOException {
+ Map<DocId, AuthzStatus> result
+ = new HashMap<DocId, AuthzStatus>(ids.size() * 2);
+ for (DocId id : ids) {
+ String uid = id.getUniqueId();
+ if ("7007".equals(uid)) {
+ if (null == userIdentity) {
+ // null for userIdentity means anonymous. To get non-null identity:
+ // 1) Follow instructions for secure mode in src/overview.html
+ // 2) Set config property "server.secure" to "true"
+ // 3) Perform secure search on your GSA (triggers authentication)
+ log.info("no authenticated user found");
+ result.put(id, AuthzStatus.DENY);
+ } else {
+ List<Acl> acl = Arrays.asList(
+ makeAclFor7007Parent(), makeAclFor7007());
+ result.put(id, Acl.isAuthorized(userIdentity, acl));
+ }
+ } else if ("7007-parent".equals(uid)) {
+ if (null == userIdentity) {
+ log.info("no authenticated user found");
+ result.put(id, AuthzStatus.DENY);
+ } else {
+ List<Acl> acl = Arrays.asList(makeAclFor7007Parent());
+ result.put(id, Acl.isAuthorized(userIdentity, acl));
+ }
+ } else if ("8008".equals(id.getUniqueId())) {
+ result.put(id, AuthzStatus.PERMIT);
+ } else {
+ result.put(id, AuthzStatus.INDETERMINATE);
+ }
+ }
+ return result;
+ }
}
diff --git a/src/overview.html b/src/overview.html
index a407811..368514d 100644
--- a/src/overview.html
+++ b/src/overview.html
@@ -101,7 +101,8 @@
<h4>Creating Self-Signed Certificates</h4>
<p>In the GSA's Admin Console, go to <b>Administration > SSL Settings</b>.
Under the <b>Create a New SSL Certificate</b> heading change <b>Host
- Name</b> to the hostname to access the GSA with. Then click <b>Create
+ Name</b> to GSA's hostname written exactly as the adaptor will use.
+ Then click <b>Create
Self-Signed Certificate</b> and wait for the operation to complete.
Then click <b>Install SSL Certificate</b> and wait for that operation
to complete (about 1 minute).
diff --git a/test/com/google/enterprise/adaptor/DocumentHandlerTest.java b/test/com/google/enterprise/adaptor/DocumentHandlerTest.java
index 179e80f..ad8c464 100644
--- a/test/com/google/enterprise/adaptor/DocumentHandlerTest.java
+++ b/test/com/google/enterprise/adaptor/DocumentHandlerTest.java
@@ -24,6 +24,7 @@
import java.io.*;
import java.net.URI;
+import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicReference;
@@ -941,10 +942,18 @@
return new UserPrincipal(name);
}
+ private static UserPrincipal U(String name, String ns) {
+ return new UserPrincipal(name, ns);
+ }
+
private static GroupPrincipal G(String name) {
return new GroupPrincipal(name);
}
+ private static GroupPrincipal G(String name, String ns) {
+ return new GroupPrincipal(name, ns);
+ }
+
@Test
public void testFormAclHeader() {
final String golden
@@ -958,7 +967,7 @@
// by percentEncode to give %2520.
+ "google%3Aaclinheritfrom=http%3A%2F%2Flocalhost%2Fsome%2520docId,"
+ "google%3Aaclinheritancetype=parent-overrides";
- String result = DocumentHandler.formAclHeader(new Acl.Builder()
+ String result = DocumentHandler.formUnqualifiedAclHeader(new Acl.Builder()
.setPermitUsers(Arrays.asList(U("pu1"), U("uid=pu2,dc=com")))
.setPermitGroups(Arrays.asList(G("pg1"), G("gid=pg2,dc=com")))
.setDenyUsers(Arrays.asList(U("du1"), U("uid=du2,dc=com")))
@@ -970,14 +979,186 @@
}
@Test
- public void testFormAclHeaderNull() {
- assertEquals("", DocumentHandler.formAclHeader(null, new MockDocIdCodec()));
+ public void testFormUnqualifiedAclHeaderNull() {
+ assertEquals("", DocumentHandler
+ .formUnqualifiedAclHeader(null, new MockDocIdCodec()));
+ }
+
+ @Test
+ public void testFormUnqualifiedAclHeaderEmpty() {
+ DocIdEncoder enc = new MockDocIdCodec();
+ assertEquals("google%3Aacldenyusers=google%3AfakeUserToPreventMissingAcl",
+ DocumentHandler.formUnqualifiedAclHeader(Acl.EMPTY, enc));
}
@Test
- public void testFormAclHeaderEmpty() {
- assertEquals("google%3Aacldenyusers=google%3AfakeUserToPreventMissingAcl",
- DocumentHandler.formAclHeader(Acl.EMPTY, new MockDocIdCodec()));
+ public void testFormNamespacedAclHeaderNull() {
+ assertEquals("", DocumentHandler
+ .formNamespacedAclHeader(null, new MockDocIdCodec()));
+ }
+
+ @Test
+ public void testFormNamespacedAclHeaderEmpty() {
+ DocIdEncoder enc = new MockDocIdCodec();
+ String golden = "{\"entries\":[{"
+ + "\"access\":\"deny\""
+ + ","
+ + "\"name\":\"google:fakeUserToPreventMissingAcl\""
+ + ","
+ + "\"scope\":\"user\""
+ + "}]}";
+ String aclHeader = DocumentHandler.formNamespacedAclHeader(Acl.EMPTY, enc);
+ assertEquals(golden, aclHeader);
+ }
+
+ @Test
+ public void testFormNamespacedAclHeaderBusy() {
+ DocIdEncoder enc = new MockDocIdCodec();
+ String golden = "{"
+ + "\"entries\":["
+ + "{\"access\":\"permit\","
+ + "\"case_sensitivity_type\":\"everything_case_insensitive\","
+ + "\"name\":\"pg1@d.g\",\"scope\":\"group\"},"
+ + "{\"access\":\"permit\","
+ + "\"case_sensitivity_type\":\"everything_case_insensitive\","
+ + "\"name\":\"gid=pg2,dc=m\",\"namespace\":\"ns\","
+ + "\"scope\":\"group\"},"
+ + "{\"access\":\"deny\","
+ + "\"case_sensitivity_type\":\"everything_case_insensitive\","
+ + "\"name\":\"gid=dg2,dc=com\",\"scope\":\"group\"},"
+ + "{\"access\":\"deny\","
+ + "\"case_sensitivity_type\":\"everything_case_insensitive\","
+ + "\"name\":\"dg1@d.g\",\"namespace\":\"ns\","
+ + "\"scope\":\"group\"},"
+ + "{\"access\":\"permit\","
+ + "\"case_sensitivity_type\":\"everything_case_insensitive\","
+ + "\"name\":\"uid=pu2,dc=m\",\"scope\":\"user\"},"
+ + "{\"access\":\"permit\","
+ + "\"case_sensitivity_type\":\"everything_case_insensitive\","
+ + "\"name\":\"pu1@d.g\",\"namespace\":\"ns\","
+ + "\"scope\":\"user\"},"
+ + "{\"access\":\"deny\","
+ + "\"case_sensitivity_type\":\"everything_case_insensitive\","
+ + "\"name\":\"du1@d.g\",\"scope\":\"user\"},"
+ + "{\"access\":\"deny\","
+ + "\"case_sensitivity_type\":\"everything_case_insensitive\","
+ + "\"name\":\"uid=du2,dc=com\",\"namespace\":\"ns\","
+ + "\"scope\":\"user\"}"
+ + "],"
+ + "\"inherit_from\":\"http:\\/\\/localhost\\/some%20docId\","
+ + "\"inheritance_type\":\"PARENT_OVERRIDES\""
+ + "}";
+
+ Acl busyAcl = new Acl.Builder()
+ .setPermitUsers(Arrays.asList(U("pu1@d.g", "ns"), U("uid=pu2,dc=m")))
+ .setPermitGroups(Arrays.asList(G("pg1@d.g"), G("gid=pg2,dc=m", "ns")))
+ .setDenyUsers(Arrays.asList(U("du1@d.g"), U("uid=du2,dc=com", "ns")))
+ .setDenyGroups(Arrays.asList(G("dg1@d.g", "ns"), G("gid=dg2,dc=com")))
+ .setInheritFrom(new DocId("some docId"))
+ .setInheritanceType(Acl.InheritanceType.PARENT_OVERRIDES)
+ .setEverythingCaseInsensitive()
+ .build();
+ String aclHeader = DocumentHandler.formNamespacedAclHeader(busyAcl, enc);
+ assertEquals(golden, aclHeader);
+ }
+
+ @Test
+ public void testDisplayUrlHeader() throws Exception {
+ MockAdaptor adaptor = new MockAdaptor() {
+ @Override
+ public void getDocContent(Request request, Response response)
+ throws IOException {
+ try {
+ response.setDisplayUrl(new URI("http://www.google.com"));
+ } catch (URISyntaxException urie) {
+ throw new RuntimeException(urie);
+ }
+ response.getOutputStream();
+ }
+ };
+ String remoteIp = ex.getRemoteAddress().getAddress().getHostAddress();
+ DocumentHandler handler = createHandlerBuilder()
+ .setAdaptor(adaptor)
+ .setFullAccessHosts(new String[] {remoteIp, "someUnknownHost!@#$"})
+ .setSendDocControls(true)
+ .build();
+ handler.handle(ex);
+ assertEquals(200, ex.getResponseCode());
+ assertTrue(ex.getResponseHeaders().get("X-Gsa-Doc-Controls")
+ .contains("display_url=http%3A%2F%2Fwww.google.com"));
+ }
+
+/*
+ TODO: enable once sending lock is possible at crawl time
+ @Test
+ public void testLockHeaderSent() throws Exception {
+ MockAdaptor adaptor = new MockAdaptor() {
+ @Override
+ public void getDocContent(Request request, Response response)
+ throws IOException {
+ response.setLock(true);
+ response.getOutputStream();
+ }
+ };
+ String remoteIp = ex.getRemoteAddress().getAddress().getHostAddress();
+ DocumentHandler handler = createHandlerBuilder()
+ .setAdaptor(adaptor)
+ .setFullAccessHosts(new String[] {remoteIp, "someUnknownHost!@#$"})
+ .setSendDocControls(true)
+ .build();
+ handler.handle(ex);
+ assertEquals(200, ex.getResponseCode());
+ assertTrue(ex.getResponseHeaders().get("X-Gsa-Doc-Controls")
+ .contains("lock=true"));
+ }
+*/
+
+/*
+ TODO: enable once sending crawl-once is possible at crawl time
+ @Test
+ public void testCrawlOnceHeaderSent() throws Exception {
+ MockAdaptor adaptor = new MockAdaptor() {
+ @Override
+ public void getDocContent(Request request, Response response)
+ throws IOException {
+ response.setCrawlOnce(true);
+ response.getOutputStream();
+ }
+ };
+ String remoteIp = ex.getRemoteAddress().getAddress().getHostAddress();
+ DocumentHandler handler = createHandlerBuilder()
+ .setAdaptor(adaptor)
+ .setFullAccessHosts(new String[] {remoteIp, "someUnknownHost!@#$"})
+ .setSendDocControls(true)
+ .build();
+ handler.handle(ex);
+ assertEquals(200, ex.getResponseCode());
+ assertTrue(ex.getResponseHeaders().get("X-Gsa-Doc-Controls")
+ .contains("crawl-once=true"));
+ }
+*/
+
+ @Test
+ public void testAclMeansServeSecurity() throws Exception {
+ MockAdaptor adaptor = new MockAdaptor() {
+ @Override
+ public void getDocContent(Request request, Response response)
+ throws IOException {
+ response.setAcl(new Acl.Builder()
+ .setInheritFrom(new DocId("testing")).build());
+ response.setSecure(false);
+ response.getOutputStream();
+ }
+ };
+ String remoteIp = ex.getRemoteAddress().getAddress().getHostAddress();
+ DocumentHandler handler = createHandlerBuilder()
+ .setAdaptor(adaptor)
+ .setFullAccessHosts(new String[] {remoteIp, "someUnknownHost!@#$"})
+ .build();
+ handler.handle(ex);
+ assertEquals(200, ex.getResponseCode());
+ assertEquals("secure",
+ ex.getResponseHeaders().getFirst("X-Gsa-Serve-Security"));
}
@Test
@@ -1013,29 +1194,6 @@
}
@Test
- public void testAclMeansServeSecurity() throws Exception {
- String remoteIp = ex.getRemoteAddress().getAddress().getHostAddress();
- MockAdaptor adaptor = new MockAdaptor() {
- @Override
- public void getDocContent(Request request, Response response)
- throws IOException {
- response.setAcl(new Acl.Builder()
- .setInheritFrom(new DocId("testing")).build());
- response.setSecure(false);
- response.getOutputStream();
- }
- };
- DocumentHandler handler = createHandlerBuilder()
- .setAdaptor(adaptor)
- .setFullAccessHosts(new String[] {remoteIp, "someUnknownHost!@#$"})
- .build();
- handler.handle(ex);
- assertEquals(200, ex.getResponseCode());
- assertEquals("secure",
- ex.getResponseHeaders().getFirst("X-Gsa-Serve-Security"));
- }
-
- @Test
public void testEmulatedAcls() throws Exception {
String remoteIp = ex.getRemoteAddress().getAddress().getHostAddress();
final AtomicReference<Acl> providedAcl = new AtomicReference<Acl>();
@@ -1228,6 +1386,7 @@
private boolean useCompression;
private Watchdog watchdog;
private DocumentHandler.AsyncPusher pusher;
+ private boolean sendDocControls;
public DocumentHandlerBuilder setDocIdDecoder(DocIdDecoder docIdDecoder) {
this.docIdDecoder = docIdDecoder;
@@ -1302,11 +1461,86 @@
return this;
}
+ public DocumentHandlerBuilder setSendDocControls(boolean sendDocControls) {
+ this.sendDocControls = sendDocControls;
+ return this;
+ }
+
public DocumentHandler build() {
return new DocumentHandler(docIdDecoder, docIdEncoder, journal, adaptor,
gsaHostname, fullAccessHosts, authnHandler, sessionManager, transform,
transformMaxBytes, transformRequired, useCompression, watchdog,
- pusher);
+ pusher, sendDocControls);
}
}
+
+ /** percentDecode method is defined in this test file */
+ @Test
+ public void testPercentDecoder() {
+ assertEquals("" + ((char)(10)), percentDecode("%0A"));
+ for (char c = 0; c < 256; c++) {
+ String encoded = DocumentHandler.percentEncode("" + c);
+ String decoded = percentDecode(encoded);
+ if (!decoded.equals("" + c)) {
+ int n = c;
+ throw new AssertionError("failed to encode/decode `" + n
+ + "' `" + c + "' `" + encoded + "' `" + decoded + "'");
+ }
+ }
+ String decoded = percentDecode("AaZz09-_.~"
+ + "%60%3D%2F%3F%2B%27%3B%5C%2F%22%21%40%23%24%25%5E%26"
+ + "%2A%28%29%5B%5D%7B%7D%C3%AB%01");
+ assertEquals("AaZz09-_.~`=/?+';\\/\"!@#$%^&*()[]{}ë\u0001", decoded);
+ }
+
+ private static int hexToInt(byte b) {
+ if (b >= '0' && b <= '9') {
+ return (byte)(b - '0');
+ } else if (b >= 'a' && b <= 'f') {
+ return (byte)(b - 'a') + 10;
+ } else if (b >= 'A' && b <= 'F') {
+ return (byte)(b - 'A') + 10;
+ } else {
+ throw new IllegalArgumentException("invalid hex byte: " + b);
+ }
+ }
+
+ private static String percentDecode(String encoded) {
+ try {
+ byte bytes[] = encoded.getBytes("ASCII");
+ ByteArrayOutputStream decoded = percentDecode(bytes);
+ return decoded.toString("UTF-8");
+ } catch (UnsupportedEncodingException uee) {
+ throw new RuntimeException(uee);
+ }
+ }
+
+ private static ByteArrayOutputStream percentDecode(byte encoded[]) {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ int i = 0;
+ while (i < encoded.length) {
+ byte b = encoded[i];
+ if (b == '%') {
+ int iNeeded = i + 2; // need two more bytes
+ if (iNeeded >= encoded.length) {
+ throw new IllegalArgumentException("ends too early");
+ }
+ int highOrder = hexToInt(encoded[i + 1]);
+ int lowOrder = hexToInt(encoded[i + 2]);
+ int byteInInt = (highOrder << 4) | lowOrder;
+ b = (byte) byteInInt; // chops top bytes; could make negative
+ i += 3;
+ } else if ((b >= 'a' && b <= 'z')
+ || (b >= 'A' && b <= 'Z')
+ || (b >= '0' && b <= '9')
+ || b == '-' || b == '_' || b == '.' || b == '~') {
+ // pass through
+ i++;
+ } else {
+ throw new IllegalArgumentException("not percent encoded");
+ }
+ out.write(b);
+ }
+ return out;
+ }
}