// Copyright 2007 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.enterprise.connector.servlet;
import com.google.enterprise.connector.logging.NDC;
import com.google.enterprise.connector.manager.Manager;
import com.google.enterprise.connector.servlet.AuthorizationParser.ConnectorQueries;
import com.google.enterprise.connector.servlet.AuthorizationParser.QueryResources;
import com.google.enterprise.connector.spi.AuthenticationIdentity;
import com.google.enterprise.connector.spi.AuthorizationResponse;
import com.google.enterprise.connector.spi.XmlUtils;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* This class does the real work for the authorization servlet
*/
public class AuthorizationHandler {
private static final Logger LOGGER =
Logger.getLogger(AuthorizationHandler.class.getName());
String xmlBody;
Manager manager;
PrintWriter out;
ConnectorMessageCode status;
Map<AuthorizationResource, AuthorizationResponse.Status> results;
AuthorizationHandler(String xmlBody, Manager manager, PrintWriter out) {
this.xmlBody = xmlBody;
this.manager = manager;
this.out = out;
results = new HashMap<AuthorizationResource, AuthorizationResponse.Status>();
}
/**
* Factory method for testing. Ensures that the results come back in a
* predictable order.
*/
static AuthorizationHandler makeAuthorizationHandlerForTest(String xmlBody,
Manager manager, PrintWriter out) {
AuthorizationHandler authorizationHandler = new AuthorizationHandler(
xmlBody, manager, out);
authorizationHandler.results =
new TreeMap<AuthorizationResource, AuthorizationResponse.Status>();
return authorizationHandler;
}
/**
* Writes an answer for each resource from the request.
*/
public void handleDoPost() {
NDC.pushAppend("AuthZ");
try {
AuthorizationParser authorizationParser =
new AuthorizationParser(xmlBody);
status = authorizationParser.getStatus();
if (status.getMessageId() ==
ConnectorMessageCode.ERROR_PARSING_XML_REQUEST) {
ServletUtil.writeResponse(out,status);
return;
}
computeResultSet(authorizationParser);
generateXml();
} finally {
NDC.pop();
}
}
private void generateXml() {
ServletUtil.writeRootTag(out, false);
if (results.size() > 0) {
ServletUtil.writeXMLTag(out, 1, ServletUtil.XMLTAG_AUTHZ_RESPONSE, false);
generateEachResultXml();
ServletUtil.writeXMLTag(out, 1, ServletUtil.XMLTAG_AUTHZ_RESPONSE, true);
}
ServletUtil.writeMessageCode(out, status);
ServletUtil.writeRootTag(out, true);
}
private void generateEachResultXml() {
for (Entry<AuthorizationResource, AuthorizationResponse.Status> e :
results.entrySet()) {
writeResultElement(e.getKey(), e.getValue());
}
}
private void writeResultElement(AuthorizationResource resource,
AuthorizationResponse.Status decision) {
ServletUtil.writeXMLTag(out, 2, ServletUtil.XMLTAG_ANSWER, false);
// Add the connector name attribute to the resource element.
try {
// TODO: Either fix ServletUtil XML code to XML escape attr values and
// element text bodies, or add the ability to append attributes to
// XmlUtils.appendStartTag().
out.write(ServletUtil.indentStr(3) + "<" + ServletUtil.XMLTAG_RESOURCE);
XmlUtils.xmlAppendAttr(ServletUtil.XMLTAG_CONNECTOR_NAME_ATTRIBUTE,
resource.getConnectorName(), out);
out.append('>');
XmlUtils.xmlAppendAttrValue(resource.getUrl(), out);
XmlUtils.xmlAppendEndTag(ServletUtil.XMLTAG_RESOURCE, out);
} catch (IOException e) {
// Can't happen with PrintWriter.
}
ServletUtil.writeXMLElement(out, 3, ServletUtil.XMLTAG_DECISION,
decision.toString());
ServletUtil.writeXMLTag(out, 2, ServletUtil.XMLTAG_ANSWER, true);
}
private void computeResultSet(AuthorizationParser authorizationParser) {
for (AuthenticationIdentity identity: authorizationParser.getIdentities()) {
NDC.pushAppend(identity.getUsername());
try {
ConnectorQueries queries =
authorizationParser.getConnectorQueriesForIdentity(identity);
runManagerQueries(identity, queries);
} finally {
NDC.pop();
}
}
}
private void runManagerQueries(AuthenticationIdentity identity,
ConnectorQueries urlsByConnector) {
for (String connectorName : urlsByConnector.getConnectors()) {
NDC.pushAppend(connectorName);
try {
// TODO [bmj]: It is conceivable that multiple URLs (resources) could
// map to the same repository document (docid). We should handle the
// duplicates, somehow?
QueryResources urlsByDocid = urlsByConnector.getQueryResources(connectorName);
List<String> docidList = new ArrayList<String>(urlsByDocid.getDocids());
Collection<AuthorizationResponse> answerSet =
manager.authorizeDocids(connectorName, docidList, identity);
if (answerSet != null) {
accumulateQueryResults(answerSet, urlsByDocid);
}
} finally {
NDC.pop();
}
}
}
private void accumulateQueryResults(Collection<AuthorizationResponse> answerSet,
QueryResources urlsByDocid) {
Set<String> docids = new HashSet<String>(urlsByDocid.getDocids());
for (AuthorizationResponse response : answerSet) {
String docid = response.getDocid();
AuthorizationResource resource = urlsByDocid.getResource(docid);
if (resource == null) {
LOGGER.warning("Received unexpected AuthorizationResponse for document "
+ docid);
} else {
results.put(resource, response.getStatus());
docids.remove(docid);
}
}
// Return DENY for documents not returned by connector.
for (String docid : docids) {
AuthorizationResource resource = urlsByDocid.getResource(docid);
results.put(resource, AuthorizationResponse.Status.DENY);
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.finest("AUTHORIZED " + docid + ": "
+ AuthorizationResponse.Status.DENY);
}
}
}
}