| // Copyright 2011 Google Inc. All Rights Reserved. |
| // |
| // 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.adaptor; |
| |
| import static org.junit.Assert.*; |
| |
| import org.junit.*; |
| |
| import java.io.*; |
| import java.nio.charset.Charset; |
| import java.util.*; |
| |
| /** |
| * Test cases for {@link SamlBatchAuthzHandler}. |
| */ |
| public class SamlBatchAuthzHandlerTest { |
| private static final String SOAP_HEADER |
| = "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" |
| + "<soap11:Envelope " |
| + "xmlns:soap11=\"http://schemas.xmlsoap.org/soap/envelope/\">" |
| + "<soap11:Body>"; |
| private static final String SOAP_FOOTER |
| = "</soap11:Body>" |
| + "</soap11:Envelope>"; |
| |
| private AuthzAuthority adaptor = new MockAdaptor(); |
| private SamlMetadata samlMetadata = new SamlMetadata("localhost", 80, |
| "localhost", "http://google.com/enterprise/gsa/security-manager", |
| "http://google.com/enterprise/gsa/adaptor"); |
| private SamlBatchAuthzHandler handler = new SamlBatchAuthzHandler( |
| adaptor, new MockDocIdCodec(), samlMetadata); |
| private MockHttpExchange ex = new MockHttpExchange("POST", "/", |
| new MockHttpContext(handler, "/")); |
| private Charset charset = Charset.forName("UTF-8"); |
| |
| @BeforeClass |
| public static void initSaml() { |
| GsaCommunicationHandler.bootstrapOpenSaml(); |
| } |
| |
| @Test |
| public void testGet() throws Exception { |
| MockHttpExchange ex = new MockHttpExchange("GET", "/", |
| new MockHttpContext(handler, "/")); |
| handler.handle(ex); |
| assertEquals(405, ex.getResponseCode()); |
| } |
| |
| @Test |
| public void testWrongPath() throws Exception { |
| MockHttpExchange ex = new MockHttpExchange("POST", "/wrong", |
| new MockHttpContext(handler, "/")); |
| handler.handle(ex); |
| assertEquals(404, ex.getResponseCode()); |
| } |
| |
| @Test |
| public void testPermitAuthz() throws Exception { |
| String request |
| = SOAP_HEADER |
| + generateAuthzDecisionQuery("http://localhost/doc/1234", |
| "aoeuaoeu") |
| + SOAP_FOOTER; |
| String goldenResponse |
| = SOAP_HEADER |
| + generateGoldenResponse("http://localhost/doc/1234", |
| "aoeuaoeu", |
| "Permit") |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(200, ex.getResponseCode()); |
| String response = new String(ex.getResponseBytes(), charset); |
| response = massageResponse(response); |
| assertEquals(goldenResponse, response); |
| } |
| |
| @Test |
| public void testDenyAuthz() throws Exception { |
| SamlBatchAuthzHandler handler = new SamlBatchAuthzHandler( |
| new PrivateMockAdaptor(), new MockDocIdCodec(), samlMetadata); |
| MockHttpExchange ex = new MockHttpExchange("POST", "/", |
| new MockHttpContext(handler, "/")); |
| String request |
| = SOAP_HEADER |
| + generateAuthzDecisionQuery("http://localhost/doc/1234", |
| "aoeuaoeu") |
| + SOAP_FOOTER; |
| String goldenResponse |
| = SOAP_HEADER |
| + generateGoldenResponse("http://localhost/doc/1234", |
| "aoeuaoeu", |
| "Deny") |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(200, ex.getResponseCode()); |
| String response = new String(ex.getResponseBytes(), charset); |
| response = massageResponse(response); |
| assertEquals(goldenResponse, response); |
| } |
| |
| @Test |
| public void testBrokenAdaptor() throws Exception { |
| AuthzAuthority adaptor = new AuthzAuthority() { |
| @Override |
| public Map<DocId, AuthzStatus> isUserAuthorized(AuthnIdentity identity, |
| Collection<DocId> ids) { |
| return null; |
| } |
| }; |
| SamlBatchAuthzHandler handler = new SamlBatchAuthzHandler( |
| adaptor, new MockDocIdCodec(), samlMetadata); |
| MockHttpExchange ex = new MockHttpExchange("POST", "/", |
| new MockHttpContext(handler, "/")); |
| String request |
| = SOAP_HEADER |
| + generateAuthzDecisionQuery("http://localhost/doc/1234", |
| "aoeuaoeu") |
| + SOAP_FOOTER; |
| String goldenResponse |
| = SOAP_HEADER |
| + generateGoldenResponse("http://localhost/doc/1234", |
| "aoeuaoeu", |
| "Deny") |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(200, ex.getResponseCode()); |
| String response = new String(ex.getResponseBytes(), charset); |
| response = massageResponse(response); |
| assertEquals(goldenResponse, response); |
| } |
| |
| @Test |
| public void testIndeterminateAdaptor() throws Exception { |
| AuthzAuthority adaptor = new AuthzAuthority() { |
| @Override |
| public Map<DocId, AuthzStatus> isUserAuthorized(AuthnIdentity identity, |
| Collection<DocId> ids) { |
| assertEquals(1, ids.size()); |
| DocId docId = (DocId) ids.toArray()[0]; |
| return Collections.singletonMap(docId, AuthzStatus.INDETERMINATE); |
| } |
| }; |
| SamlBatchAuthzHandler handler = new SamlBatchAuthzHandler( |
| adaptor, new MockDocIdCodec(), samlMetadata); |
| MockHttpExchange ex = new MockHttpExchange("POST", "/", |
| new MockHttpContext(handler, "/")); |
| String request |
| = SOAP_HEADER |
| + generateAuthzDecisionQuery("http://localhost/doc/1234", |
| "aoeuaoeu") |
| + SOAP_FOOTER; |
| String goldenResponse |
| = SOAP_HEADER |
| + generateGoldenResponse("http://localhost/doc/1234", |
| "aoeuaoeu", |
| "Deny") |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(200, ex.getResponseCode()); |
| String response = new String(ex.getResponseBytes(), charset); |
| response = massageResponse(response); |
| assertEquals(goldenResponse, response); |
| } |
| |
| @Test |
| public void testErroringAdaptor() throws Exception { |
| AuthzAuthority adaptor = new AuthzAuthority() { |
| @Override |
| public Map<DocId, AuthzStatus> isUserAuthorized(AuthnIdentity identity, |
| Collection<DocId> ids) { |
| throw new RuntimeException("something happened"); |
| } |
| }; |
| SamlBatchAuthzHandler handler = new SamlBatchAuthzHandler( |
| adaptor, new MockDocIdCodec(), samlMetadata); |
| MockHttpExchange ex = new MockHttpExchange("POST", "/", |
| new MockHttpContext(handler, "/")); |
| String request |
| = SOAP_HEADER |
| + generateAuthzDecisionQuery("http://localhost/doc/1234", |
| "aoeuaoeu") |
| + SOAP_FOOTER; |
| String goldenResponse |
| = SOAP_HEADER |
| + generateGoldenResponse("http://localhost/doc/1234", |
| "aoeuaoeu", |
| "Deny") |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(200, ex.getResponseCode()); |
| String response = new String(ex.getResponseBytes(), charset); |
| response = massageResponse(response); |
| assertEquals(goldenResponse, response); |
| } |
| |
| @Test |
| public void testUnknownResourceHost() throws Exception { |
| String request |
| = SOAP_HEADER |
| + generateAuthzDecisionQuery("http://wronghost/doc/1234", |
| "aoeuaoeu") |
| + SOAP_FOOTER; |
| String goldenResponse |
| = SOAP_HEADER |
| + generateGoldenResponse("http://wronghost/doc/1234", |
| "aoeuaoeu", |
| "Indeterminate") |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(200, ex.getResponseCode()); |
| String response = new String(ex.getResponseBytes(), charset); |
| response = massageResponse(response); |
| assertEquals(goldenResponse, response); |
| } |
| |
| @Test |
| public void testUnknownResourcePort() throws Exception { |
| String request |
| = SOAP_HEADER |
| + generateAuthzDecisionQuery("http://localhost:81/doc/1234", |
| "aoeuaoeu") |
| + SOAP_FOOTER; |
| String goldenResponse |
| = SOAP_HEADER |
| + generateGoldenResponse("http://localhost:81/doc/1234", |
| "aoeuaoeu", |
| "Indeterminate") |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(200, ex.getResponseCode()); |
| String response = new String(ex.getResponseBytes(), charset); |
| response = massageResponse(response); |
| assertEquals(goldenResponse, response); |
| } |
| |
| @Test |
| public void testUnknownResourceScheme() throws Exception { |
| String request |
| = SOAP_HEADER |
| + generateAuthzDecisionQuery("https://localhost/doc/1234", |
| "aoeuaoeu") |
| + SOAP_FOOTER; |
| String goldenResponse |
| = SOAP_HEADER |
| + generateGoldenResponse("https://localhost/doc/1234", |
| "aoeuaoeu", |
| "Indeterminate") |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(200, ex.getResponseCode()); |
| String response = new String(ex.getResponseBytes(), charset); |
| response = massageResponse(response); |
| assertEquals(goldenResponse, response); |
| } |
| |
| @Test |
| public void testMultiRequest() throws Exception { |
| String request |
| = SOAP_HEADER |
| + generateAuthzDecisionQuery("http://localhost/doc/1234", |
| "aoeuaoeu1") |
| + generateAuthzDecisionQuery("http://localhost/doc/1235", |
| "aoeuaoeu2") |
| + generateAuthzDecisionQuery("http://localhost/doc/1236", |
| "aoeuaoeu3") |
| + SOAP_FOOTER; |
| String goldenResponse |
| = SOAP_HEADER |
| + generateGoldenResponse("http://localhost/doc/1234", |
| "aoeuaoeu1", |
| "Permit") |
| + generateGoldenResponse("http://localhost/doc/1235", |
| "aoeuaoeu2", |
| "Permit") |
| + generateGoldenResponse("http://localhost/doc/1236", |
| "aoeuaoeu3", |
| "Permit") |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(200, ex.getResponseCode()); |
| String response = new String(ex.getResponseBytes(), charset); |
| response = massageResponse(response); |
| assertEquals(goldenResponse, response); |
| } |
| |
| @Test |
| public void testMultiRequestWithDifferentSubjects() throws Exception { |
| String request |
| = SOAP_HEADER |
| + "<samlp:AuthzDecisionQuery " |
| + "ID=\"aoeuaoeu1\" " |
| + "IssueInstant=\"2009-10-20T17:52:29Z\" " |
| + "Version=\"2.0\" " |
| + "Resource=\"http://localhost/doc/1234\" " |
| + "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" " |
| + "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">" |
| + "<saml:Subject>" |
| + "<saml:NameID>Polly Hedra</saml:NameID>" |
| + "</saml:Subject>" |
| + "<saml:Action " |
| + "Namespace=\"urn:oasis:names:tc:SAML:1.0:action:ghpp\">" |
| + "GET" |
| + "</saml:Action>" |
| + "</samlp:AuthzDecisionQuery>" |
| + "<samlp:AuthzDecisionQuery " |
| + "ID=\"aoeuaoeu2\" " |
| + "IssueInstant=\"2009-10-20T17:52:29Z\" " |
| + "Version=\"2.0\" " |
| + "Resource=\"http://localhost/doc/1234\" " |
| + "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" " |
| + "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">" |
| + "<saml:Subject>" |
| + "<saml:NameID>Some Other Polly Hedra</saml:NameID>" |
| + "</saml:Subject>" |
| + "<saml:Action " |
| + "Namespace=\"urn:oasis:names:tc:SAML:1.0:action:ghpp\">" |
| + "GET" |
| + "</saml:Action>" |
| + "</samlp:AuthzDecisionQuery>" |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(400, ex.getResponseCode()); |
| } |
| |
| @Test |
| public void testBadXml() throws Exception { |
| String request |
| = SOAP_HEADER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(400, ex.getResponseCode()); |
| } |
| |
| @Test |
| public void testNoQueries() throws Exception { |
| String request |
| = SOAP_HEADER + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| assertEquals(400, ex.getResponseCode()); |
| } |
| |
| @Test |
| public void testNoResource() throws Exception { |
| String request |
| = SOAP_HEADER |
| + "<samlp:AuthzDecisionQuery " |
| + "ID=\"aoeuaoeu\" " |
| + "IssueInstant=\"2009-10-20T17:52:29Z\" " |
| + "Version=\"2.0\" " |
| + "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" " |
| + "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">" |
| + "<saml:Subject>" |
| + "<saml:NameID>Polly Hedra</saml:NameID>" |
| + "</saml:Subject>" |
| + "<saml:Action " |
| + "Namespace=\"urn:oasis:names:tc:SAML:1.0:action:ghpp\">" |
| + "GET" |
| + "</saml:Action>" |
| + "</samlp:AuthzDecisionQuery>" |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| System.out.println(new String(ex.getResponseBytes(), charset)); |
| assertEquals(400, ex.getResponseCode()); |
| } |
| |
| @Test |
| public void testNoSubject() throws Exception { |
| String request |
| = SOAP_HEADER |
| + "<samlp:AuthzDecisionQuery " |
| + "ID=\"aoeuaoeu\" " |
| + "IssueInstant=\"2009-10-20T17:52:29Z\" " |
| + "Version=\"2.0\" " |
| + "Resource=\"http://localhost/doc/1234\" " |
| + "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" " |
| + "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">" |
| + "<saml:Action " |
| + "Namespace=\"urn:oasis:names:tc:SAML:1.0:action:ghpp\">" |
| + "GET" |
| + "</saml:Action>" |
| + "</samlp:AuthzDecisionQuery>" |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| System.out.println(new String(ex.getResponseBytes(), charset)); |
| assertEquals(400, ex.getResponseCode()); |
| } |
| |
| @Test |
| public void testNoSubjectNameId() throws Exception { |
| String request |
| = SOAP_HEADER |
| + "<samlp:AuthzDecisionQuery " |
| + "ID=\"aoeuaoeu\" " |
| + "IssueInstant=\"2009-10-20T17:52:29Z\" " |
| + "Version=\"2.0\" " |
| + "Resource=\"http://localhost/doc/1234\" " |
| + "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" " |
| + "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">" |
| + "<saml:Subject/>" |
| + "<saml:Action " |
| + "Namespace=\"urn:oasis:names:tc:SAML:1.0:action:ghpp\">" |
| + "GET" |
| + "</saml:Action>" |
| + "</samlp:AuthzDecisionQuery>" |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| System.out.println(new String(ex.getResponseBytes(), charset)); |
| assertEquals(400, ex.getResponseCode()); |
| } |
| |
| @Test |
| public void testEmptySubjectNameId() throws Exception { |
| String request |
| = SOAP_HEADER |
| + "<samlp:AuthzDecisionQuery " |
| + "ID=\"aoeuaoeu\" " |
| + "IssueInstant=\"2009-10-20T17:52:29Z\" " |
| + "Version=\"2.0\" " |
| + "Resource=\"http://localhost/doc/1234\" " |
| + "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" " |
| + "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">" |
| + "<saml:Subject>" |
| + "<saml:NameID/>" |
| + "</saml:Subject>" |
| + "<saml:Action " |
| + "Namespace=\"urn:oasis:names:tc:SAML:1.0:action:ghpp\">" |
| + "GET" |
| + "</saml:Action>" |
| + "</samlp:AuthzDecisionQuery>" |
| + SOAP_FOOTER; |
| ex.setRequestBody(stringToStream(request)); |
| handler.handle(ex); |
| System.out.println(new String(ex.getResponseBytes(), charset)); |
| assertEquals(400, ex.getResponseCode()); |
| } |
| |
| private String massageResponse(String response) { |
| return response.replaceAll("ID=\"[^\"]+\"", "ID=\"someid\"") |
| .replaceAll("IssueInstant=\"[^\"]+\"", "IssueInstant=\"sometime\""); |
| } |
| |
| private String generateAuthzDecisionQuery(String resource, String id) { |
| return "<samlp:AuthzDecisionQuery " |
| + "ID=\"" + id + "\" " |
| + "IssueInstant=\"2009-10-20T17:52:29Z\" " |
| + "Version=\"2.0\" " |
| + "Resource=\"" + resource + "\" " |
| + "xmlns:saml=\"urn:oasis:names:tc:SAML:2.0:assertion\" " |
| + "xmlns:samlp=\"urn:oasis:names:tc:SAML:2.0:protocol\">" |
| + "<saml:Subject>" |
| + "<saml:NameID>Polly Hedra</saml:NameID>" |
| + "</saml:Subject>" |
| + "<saml:Action " |
| + "Namespace=\"urn:oasis:names:tc:SAML:1.0:action:ghpp\">" |
| + "GET" |
| + "</saml:Action>" |
| + "</samlp:AuthzDecisionQuery>"; |
| } |
| |
| private String generateGoldenResponse(String resource, String requestId, |
| String decision) { |
| return "<saml2p:Response " |
| + "ID=\"someid\" " |
| + "InResponseTo=\"" + requestId + "\" " |
| + "IssueInstant=\"sometime\" " |
| + "Version=\"2.0\" " |
| + "xmlns:saml2p=\"urn:oasis:names:tc:SAML:2.0:protocol\">" |
| + "<saml2:Issuer " |
| + "xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">" |
| + "http://google.com/enterprise/gsa/adaptor" |
| + "</saml2:Issuer>" |
| + "<saml2p:Status>" |
| + "<saml2p:StatusCode " |
| + "Value=\"urn:oasis:names:tc:SAML:2.0:status:Success\"/>" |
| + "</saml2p:Status>" |
| + "<saml2:Assertion " |
| + "ID=\"someid\" " |
| + "IssueInstant=\"sometime\" " |
| + "Version=\"2.0\" " |
| + "xmlns:saml2=\"urn:oasis:names:tc:SAML:2.0:assertion\">" |
| + "<saml2:Issuer>" |
| + "http://google.com/enterprise/gsa/adaptor" |
| + "</saml2:Issuer>" |
| + "<saml2:Subject>" |
| + "<saml2:NameID>Polly Hedra</saml2:NameID>" |
| + "</saml2:Subject>" |
| + "<saml2:AuthzDecisionStatement " |
| + "Decision=\"" + decision + "\" " |
| + "Resource=\"" + resource + "\">" |
| + "<saml2:Action " |
| + "Namespace=\"urn:oasis:names:tc:SAML:1.0:action:ghpp\">" |
| + "GET" |
| + "</saml2:Action>" |
| + "</saml2:AuthzDecisionStatement>" |
| + "</saml2:Assertion>" |
| + "</saml2p:Response>"; |
| } |
| |
| private InputStream stringToStream(String str) { |
| return new ByteArrayInputStream(str.getBytes(charset)); |
| } |
| } |