Saml2BearerGrantHandlerTest.java
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.apache.cxf.rs.security.oauth2.grants.saml;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import javax.security.auth.x500.X500Principal;
import org.apache.cxf.message.Message;
import org.apache.cxf.message.MessageImpl;
import org.apache.cxf.rs.security.oauth2.provider.OAuthServiceException;
import org.apache.cxf.security.transport.TLSSessionInfo;
import org.apache.wss4j.common.saml.SamlAssertionWrapper;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link Saml2BearerGrantHandler} Holder-of-Key binding validation.
* Tests the extraction and validation logic for X.509 certificate subjects and SAML assertion subjects.
*/
public class Saml2BearerGrantHandlerTest {
private final Saml2BearerGrantHandler handler = new Saml2BearerGrantHandler();
/**
* Test extracting CN from a standard X.500 DN
*/
@Test
public void testExtractCNValueStandard() {
String dn = "CN=alice@example.com,O=Acme,C=US";
String cn = handler.extractCNValue(dn);
assertEquals("alice@example.com", cn);
}
/**
* Test extracting CN when it's the only component
*/
@Test
public void testExtractCNValueOnly() {
String dn = "CN=bob@example.com";
String cn = handler.extractCNValue(dn);
assertEquals("bob@example.com", cn);
}
/**
* Test extracting CN when DN has spaces around values
*/
@Test
public void testExtractCNValueWithSpaces() {
String dn = "CN = charlie@example.com , O = Acme , C = US";
String cn = handler.extractCNValue(dn);
assertEquals("charlie@example.com", cn);
}
/**
* Test extracting CN when CN doesn't exist in DN
*/
@Test
public void testExtractCNValueNotFound() {
String cn = handler.extractCNValue("O=Acme,C=US");
assertNull("CN should not be found in DN without CN component", cn);
}
/**
* Test extracting CN from null DN
*/
@Test
public void testExtractCNValueNullDN() {
String result = handler.extractCNValue(null);
assertNull("Should return null for null DN", result);
}
/**
* Test extracting CN with empty DN
*/
@Test
public void testExtractCNValueEmptyDN() {
String result = handler.extractCNValue("");
assertNull("Should return null for empty DN", result);
}
/**
* Test extracting CN with complex DN that has multiple levels
*/
@Test
public void testExtractCNValueComplexDN() {
String dn = "CN=alice@example.com,OU=Engineering,O=Acme,C=US";
String cn = handler.extractCNValue(dn);
assertEquals("alice@example.com", cn);
}
/**
* Test extracting from DN with equals sign in value (edge case)
*/
@Test
public void testExtractCNValueWithEqualsInValue() {
// This is an edge case - the regex should extract up to the first comma
String dn = "CN=user=alice@example.com,O=Acme,C=US";
String cn = handler.extractCNValue(dn);
// The regex will match up to first comma
assertEquals("user=alice@example.com", cn);
}
/**
* Test extracting assertion subject from null wrapper
*/
@Test
public void testExtractAssertionSubjectNullWrapper() {
String subject = handler.extractAssertionSubject(null);
assertNull("Should return null for null assertion", subject);
}
/**
* Test extracting certificate subject identifier from null certificate
*/
@Test
public void testExtractCertificateSubjectIdentifierNullCertificate() {
String subject = handler.extractCertificateSubjectIdentifier(null);
assertNull("Should return null for null certificate", subject);
}
/**
* Test extracting certificate subject identifier with non-X509 certificate
*/
@Test
public void testExtractCertificateSubjectIdentifierNonX509Certificate() {
Certificate mockCert = new Certificate("MockType") {
@Override
public byte[] getEncoded() {
return new byte[0];
}
@Override
public void verify(java.security.PublicKey key) {
}
@Override
public void verify(java.security.PublicKey key, String sigProvider) {
}
@Override
public String toString() {
return "MockCertificate";
}
@Override
public java.security.PublicKey getPublicKey() {
return null;
}
};
String subject = handler.extractCertificateSubjectIdentifier(mockCert);
assertNull("Should return null for non-X509 certificate", subject);
}
@Test
public void testValidateUnsignedAssertionBindingMatchesTLSCertificate() {
Saml2BearerGrantHandler bindingHandler = createHandlerWithAssertionSubject("alice@example.com");
Message message = createMessageWithPeerCertificate("CN=alice@example.com,O=Acme,C=US");
SamlAssertionWrapper assertion = mock(SamlAssertionWrapper.class);
bindingHandler.validateUnsignedAssertionBinding(message, assertion);
}
@Test
public void testValidateUnsignedAssertionBindingRejectsSubjectMismatch() {
Saml2BearerGrantHandler bindingHandler = createHandlerWithAssertionSubject("mallory@example.com");
Message message = createMessageWithPeerCertificate("CN=alice@example.com,O=Acme,C=US");
SamlAssertionWrapper assertion = mock(SamlAssertionWrapper.class);
try {
bindingHandler.validateUnsignedAssertionBinding(message, assertion);
fail("Expected OAuthServiceException for mismatched SAML subject and TLS certificate CN");
} catch (OAuthServiceException ex) {
// expected
}
}
private Saml2BearerGrantHandler createHandlerWithAssertionSubject(String assertionSubject) {
return new Saml2BearerGrantHandler() {
@Override
protected String extractAssertionSubject(SamlAssertionWrapper assertion) {
return assertionSubject;
}
};
}
private Message createMessageWithPeerCertificate(String subjectDn) {
Message message = new MessageImpl();
X509Certificate cert = mock(X509Certificate.class);
when(cert.getSubjectX500Principal()).thenReturn(new X500Principal(subjectDn));
TLSSessionInfo tlsInfo = new TLSSessionInfo("TLS_FAKE", null, new Certificate[] {cert});
message.put(TLSSessionInfo.class, tlsInfo);
return message;
}
}