();
+
public void init(ServletConfig servletConfig) throws ServletException {
super.init(servletConfig);
- domFactory = getDocumentBuilderFactory();
+ this.domFactory = getDocumentBuilderFactory();
+ this.requireAuthenticatedSessions =
+ Boolean.parseBoolean(
+ servletConfig.getInitParameter(REQUIRE_AUTHENTICATED_SESSIONS));
+ this.reuseSessionIDs =
+ Boolean.parseBoolean(
+ servletConfig.getInitParameter(REUSE_SESSION_IDS));
}
protected DocumentBuilderFactory getDocumentBuilderFactory() {
@@ -186,17 +213,20 @@
}
/**
- * See if there is a "mustUnderstand" header element.
+ * {@inheritDoc}
+ *
+ * See if there is a "mustUnderstand" header element.
* If there is a BeginSession element, then generate a session id and
- * add to context Map.
- *
- * Excel 2000 and Excel XP generate both a BeginSession, Session and
- * EndSession mustUnderstand==1
+ * add to context Map.
+ *
+ * Excel 2000 and Excel XP generate both a BeginSession, Session and
+ * EndSession mustUnderstand=1
* in the "urn:schemas-microsoft-com:xml-analysis" namespace
- * Header elements and a NamespaceCompatibility mustUnderstand==0
+ * Header elements and a NamespaceCompatibility mustUnderstand=0
* in the "http://schemas.microsoft.com/analysisservices/2003/xmla"
- * namespace. Here we handle only the session Header elements
+ * namespace. Here we handle only the session Header elements.
*
+ *
We also handle the Security element.
*/
protected void handleSoapHeader(
HttpServletResponse response,
@@ -215,93 +245,165 @@
NodeList nlst = hdrElem.getChildNodes();
int nlen = nlst.getLength();
+ boolean authenticatedSession = false;
+ boolean beginSession = false;
for (int i = 0; i < nlen; i++) {
Node n = nlst.item(i);
- if (n instanceof Element) {
- Element e = (Element) n;
+ if (!(n instanceof Element)) {
+ continue;
+ }
+ Element e = (Element) n;
+ String localName = e.getLocalName();
- // does the Element have a mustUnderstand attribute
- Attr attr = e.getAttributeNode(SOAP_MUST_UNDERSTAND_ATTR);
- if (attr == null) {
- continue;
+ if (localName.equals(XMLA_SECURITY)
+ && NS_SOAP_SECEXT.equals(e.getNamespaceURI()))
+ {
+ // Example:
+ //
+ //
+ //
+ // MICHELE
+ // ROSSI
+ //
+ //
+ //
+ NodeList childNodes = e.getChildNodes();
+ Element userNameToken = (Element) childNodes.item(1);
+ NodeList userNamePassword = userNameToken.getChildNodes();
+ Element username = (Element) userNamePassword.item(1);
+ Element password = (Element) userNamePassword.item(3);
+ String userNameStr =
+ username.getChildNodes().item(0).getNodeValue();
+ context.put(CONTEXT_XMLA_USERNAME, userNameStr);
+ String passwordStr = "";
+ if (password.getChildNodes().item(0) != null) {
+ passwordStr =
+ password.getChildNodes().item(0).getNodeValue();
+ context.put(CONTEXT_XMLA_PASSWORD, passwordStr);
}
- // Is its value "1"
- String mustUnderstandValue = attr.getValue();
- if ((mustUnderstandValue == null)
- || (!mustUnderstandValue.equals("1")))
- {
- continue;
+ // [MROSSI] TODO we'd need to inject the HttpServletRequest
+ // into this method context.put("remote_host",
+ // request.getRemoteHost());
+ if ("".equals(passwordStr) || null == passwordStr) {
+ LOGGER.error(
+ "Security header for user [" + userNameStr
+ + "] provided without password");
}
+ authenticatedSession = true;
+ continue;
+ }
- // We've got a mustUnderstand attribute
+ // Make sure Element has mustUnderstand=1 attribute.
+ Attr attr = e.getAttributeNode(SOAP_MUST_UNDERSTAND_ATTR);
+ boolean mustUnderstandValue =
+ attr != null
+ && attr.getValue() != null
+ && attr.getValue().equals("1");
+
+ if (!mustUnderstandValue) {
+ continue;
+ }
+
+ // Is it an XMLA element
+ if (!NS_XMLA.equals(e.getNamespaceURI())) {
+ continue;
+ }
+ // So, an XMLA mustUnderstand-er
+ // Do we know what to do with it
+ // We understand:
+ // BeginSession
+ // Session
+ // EndSession
- // Is it an XMLA element
- if (! NS_XMLA.equals(e.getNamespaceURI())) {
- continue;
- }
- // So, an XMLA mustUnderstand-er
- // Do we know what to do with it
- // We understand:
- // BeginSession
- // Session
- // EndSession
+ String sessionIdStr;
+ if (localName.equals(XMLA_BEGIN_SESSION)) {
+ // generate SessionId
+ sessionIdStr = generateSessionId(context);
- String sessionIdStr;
- String localName = e.getLocalName();
- if (localName.equals(XMLA_BEGIN_SESSION)) {
- // generate SessionId
+ context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
+ context.put(
+ CONTEXT_XMLA_SESSION_STATE,
+ CONTEXT_XMLA_SESSION_STATE_BEGIN);
- sessionIdStr = generateSessionId(context);
+ } else if (localName.equals(XMLA_SESSION)) {
+ // extract the SessionId attrs value and put into
+ // context
+ sessionIdStr = getSessionId(e, context);
- context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
- context.put(
- CONTEXT_XMLA_SESSION_STATE,
- CONTEXT_XMLA_SESSION_STATE_BEGIN);
+ context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
+ context.put(
+ CONTEXT_XMLA_SESSION_STATE,
+ CONTEXT_XMLA_SESSION_STATE_WITHIN);
- } else if (localName.equals(XMLA_SESSION)) {
- // extract the SessionId attrs value and put into
- // context
- sessionIdStr = getSessionId(e, context);
+ } else if (localName.equals(XMLA_END_SESSION)) {
+ // extract the SessionId attrs value and put into
+ // context
+ sessionIdStr = getSessionId(e, context);
- context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
+ SessionInfo sessionInfo =
+ getSessionInfo(sessionIdStr);
+ if (sessionInfo != null) {
+ context.put(CONTEXT_XMLA_USERNAME, sessionInfo.user);
context.put(
- CONTEXT_XMLA_SESSION_STATE,
- CONTEXT_XMLA_SESSION_STATE_WITHIN);
+ CONTEXT_XMLA_PASSWORD, sessionInfo.password);
+ }
+ context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
+ context.put(
+ CONTEXT_XMLA_SESSION_STATE,
+ CONTEXT_XMLA_SESSION_STATE_END);
+
+ // remove session info
+ removeSessionInfo(sessionIdStr);
+ } else {
+ // error
+ String msg =
+ "Invalid XML/A message: Unknown "
+ + "\"mustUnderstand\" XMLA Header element \""
+ + localName
+ + "\"";
+ throw new XmlaException(
+ MUST_UNDERSTAND_FAULT_FC,
+ HSH_MUST_UNDERSTAND_CODE,
+ HSH_MUST_UNDERSTAND_FAULT_FS,
+ new RuntimeException(msg));
+ }
- } else if (localName.equals(XMLA_END_SESSION)) {
- // extract the SessionId attrs value and put into
- // context
- sessionIdStr = getSessionId(e, context);
+ StringBuilder buf = new StringBuilder(100);
+ buf.append("");
+ bytes = buf.toString().getBytes(encoding);
- context.put(CONTEXT_XMLA_SESSION_ID, sessionIdStr);
- context.put(
- CONTEXT_XMLA_SESSION_STATE,
- CONTEXT_XMLA_SESSION_STATE_END);
+ if (authenticatedSession) {
+ String username =
+ (String) context.get(CONTEXT_XMLA_USERNAME);
+ String password =
+ (String) context.get(CONTEXT_XMLA_PASSWORD);
+ String sessionId =
+ (String) context.get(CONTEXT_XMLA_SESSION_ID);
- } else {
- // error
- String msg =
- "Invalid XML/A message: Unknown "
- + "\"mustUnderstand\" XMLA Header element \""
- + localName
- + "\"";
+ LOGGER.debug(
+ "New authenticated session; storing credentials ["
+ + username + "/********] for session id ["
+ + sessionId + "]");
+ saveSessionInfo(
+ username,
+ password,
+ sessionId);
+ } else {
+ if (beginSession && requireAuthenticatedSessions) {
throw new XmlaException(
- MUST_UNDERSTAND_FAULT_FC,
- HSH_MUST_UNDERSTAND_CODE,
- HSH_MUST_UNDERSTAND_FAULT_FS,
- new RuntimeException(msg));
+ XmlaConstants.CLIENT_FAULT_FC,
+ XmlaConstants.CHH_AUTHORIZATION_CODE,
+ XmlaConstants.CHH_AUTHORIZATION_FAULT_FS,
+ new Exception("Session Credentials NOT PROVIDED"));
}
-
- StringBuilder buf = new StringBuilder(100);
- buf.append("");
- bytes = buf.toString().getBytes(encoding);
}
}
responseSoapParts[0] = bytes;
@@ -316,6 +418,12 @@
}
}
+ private void removeSessionInfo(String sessionIdStr) {
+ synchronized (sessionInfos) {
+ sessionInfos.remove(sessionIdStr);
+ }
+ }
+
protected String generateSessionId(Map context) {
List callbacks = getCallbacks();
if (callbacks.size() > 0) {
@@ -323,10 +431,41 @@
XmlaRequestCallback callback = callbacks.get(0);
return callback.generateSessionId(context);
} else {
- // what to do here, should Mondrian generate a Session Id?
- // TODO: Maybe Mondrian ought to generate all Session Ids and
- // not the callback.
- return "";
+ if (reuseSessionIDs) {
+ // This is how we use the same session id (key to retrieve a
+ // connection) for requests with the same username coming from
+ // the same remote host. The password can't be part of the
+ // string because if you use Simba and you don't tick "save
+ // password" in the login dialog then Simba only sends the
+ // password with the first session that it opens - after which
+ // it sends something like the following:
+ //
+ //
+ //
+ //
+ // michele
+ //
+ //
+ //
+ //
+ //
+ //
+ // Note the Password element with no value in it.
+ String sessionString =
+ "session_"
+ + context.get(CONTEXT_XMLA_USERNAME)
+ + "_"
+ + "_"
+ + context.get("remote_host");
+ return Long.toString(sessionString.hashCode(), 35);
+ } else {
+ // Generate a semi-random new session ID.
+ return Long.toString(
+ -17L * System.nanoTime() + 11L * System.currentTimeMillis(),
+ 35);
+ }
}
}
@@ -364,7 +503,7 @@
{
try {
String encoding = response.getCharacterEncoding();
- Element hdrElem = requestSoapParts[0];
+ Element hdrElem = requestSoapParts[0]; // not used
Element bodyElem = requestSoapParts[1];
Element[] dreqs =
XmlaUtil.filterChildElements(bodyElem, NS_XMLA, "Discover");
@@ -388,7 +527,12 @@
// use context variable 'role_name' as this request's XML/A role
String roleName = (String) context.get(CONTEXT_ROLE_NAME);
- XmlaRequest xmlaReq = new DefaultXmlaRequest(xmlaReqElem, roleName);
+ String username = (String) context.get(CONTEXT_XMLA_USERNAME);
+ String password = (String) context.get(CONTEXT_XMLA_PASSWORD);
+ String sessionId = (String) context.get(CONTEXT_XMLA_SESSION_ID);
+ XmlaRequest xmlaReq =
+ new DefaultXmlaRequest(
+ xmlaReqElem, roleName, username, password, sessionId);
// "ResponseMimeType" may be in the context if the "Accept" HTTP
// header was specified. But override if the SOAP request has the
@@ -696,6 +840,63 @@
responseSoapParts[1] = osBuf.toByteArray();
}
+
+ private SessionInfo getSessionInfo(String sessionId) {
+ SessionInfo sessionInfo;
+ synchronized (sessionInfos) {
+ sessionInfo = sessionInfos.get(sessionId);
+ }
+ if (sessionInfo == null) {
+ LOGGER.error(
+ "No login credentials for found for session [" + sessionId
+ + "]");
+ } else {
+ LOGGER.debug(
+ "Found existing credentials for session id ["
+ + sessionId + "], username=[" + sessionInfo.user + "]");
+ }
+ return sessionInfo;
+ }
+
+ private SessionInfo saveSessionInfo(
+ String username,
+ String password,
+ String sessionId)
+ {
+ synchronized (sessionInfos) {
+ SessionInfo sessionInfo = sessionInfos.get(sessionId);
+ if (sessionInfo != null
+ && Olap4jUtil.equal(sessionInfo.user, username))
+ {
+ // Overwrite the password, but only if it is non-empty.
+ // (Sometimes Simba sends the credentials object again
+ // but without a password.)
+ if (password != null && password.length() > 0) {
+ sessionInfo =
+ new SessionInfo(sessionId, username, password);
+ sessionInfos.put(sessionId, sessionInfo);
+ }
+ } else {
+ // A credentials object was stored against the provided session
+ // ID but the username didn't match, so create a new holder.
+ sessionInfo = new SessionInfo(sessionId, username, password);
+ sessionInfos.put(sessionId, sessionInfo);
+ }
+ return sessionInfo;
+ }
+ }
+
+ private static class SessionInfo {
+ final String id;
+ final String user;
+ final String password;
+
+ public SessionInfo(String id, String user, String password) {
+ this.id = id;
+ this.user = user;
+ this.password = password;
+ }
+ }
}
// End DefaultXmlaServlet.java
==== //open/mondrian/testsrc/main/mondrian/rolap/NonEmptyTest.java#145 - /home/jhyde/open2/mondrian/testsrc/main/mondrian/rolap/NonEmptyTest.java ====
@@ -39,7 +39,7 @@
*
* @author av
* @since Nov 21, 2005
- * @version $Id: //open/mondrian/testsrc/main/mondrian/rolap/NonEmptyTest.java#145 $
+ * @version $Id$
*/
public class NonEmptyTest extends BatchTestCase {
private static Logger logger = Logger.getLogger(NonEmptyTest.class);
@@ -4813,6 +4813,47 @@
}
}
}
+
+ public void testBugY() throws Exception {
+ final String sqlOracle =
+ "select \"store\".\"store_country\" as \"c0\""
+ + " from \"store\" \"store\""
+ + " group by \"store\".\"store_country\" order by \"store\".\"store_country\" ASC";
+ SqlPattern[] patterns = {
+ new SqlPattern(
+ Dialect.DatabaseProduct.ORACLE,
+ sqlOracle,
+ sqlOracle.length())
+ };
+
+ final String query =
+ "With Set [*NATIVE_CJ_SET] as 'NonEmptyCrossJoin([*BASE_MEMBERS_Product],[*BASE_MEMBERS_Fact Attribute])'\n"
+ + "Set [*SORTED_ROW_AXIS] as 'Order([*CJ_ROW_AXIS],[Product].CurrentMember.OrderKey,BASC,[Store Country].CurrentMember.OrderKey,BASC)'\n"
+ + "Set [*BASE_MEMBERS_Product] as '[Product].[Product Family].Members'\n"
+ + "Set [*BASE_MEMBERS_Measures] as '{[Measures].[*FORMATTED_MEASURE_0]}'\n"
+ + "Set [*CJ_ROW_AXIS] as 'Generate([*NATIVE_CJ_SET], {([Product].currentMember,[Store Country].currentMember)})'\n"
+ + "Set [*BASE_MEMBERS_Fact Attribute] as '{[Store Country].[USA]}'\n"
+ + "Set [*CJ_COL_AXIS] as '[*NATIVE_CJ_SET]'\n"
+ + "Member [Measures].[*FORMATTED_MEASURE_0] as '[Measures].[Unit Sales]', FORMAT_STRING = 'Standard', SOLVE_ORDER=400\n"
+ + "Select [*BASE_MEMBERS_Measures] on columns, Non Empty [*SORTED_ROW_AXIS] on rows\n"
+ + "From [Sales]";
+
+ final TestContext context =
+ TestContext.createSubstitutingCube(
+ "Sales",
+ "",
+ null,
+ null,
+ null);
+
+ assertQuerySqlOrNot(
+ context,
+ query,
+ patterns,
+ true,
+ true,
+ true);
+ }
}
// End NonEmptyTest.java
==== //open/mondrian/testsrc/main/mondrian/xmla/test/XmlaTest.java#23 - /home/jhyde/open2/mondrian/testsrc/main/mondrian/xmla/test/XmlaTest.java ====
@@ -143,7 +143,8 @@
new XmlaHandler(
(XmlaHandler.ConnectionFactory) server,
"xmla");
- XmlaRequest request = new DefaultXmlaRequest(requestElem, null);
+ XmlaRequest request =
+ new DefaultXmlaRequest(requestElem, null, null, null, null);
XmlaResponse response =
new DefaultXmlaResponse(
resBuf, "UTF-8", Enumeration.ResponseMimeType.SOAP);