package io.apiman.plugins.keycloak_oauth_policy; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.verify; import io.apiman.gateway.engine.beans.ApiRequest; import io.apiman.gateway.engine.components.IPolicyFailureFactoryComponent; import io.apiman.gateway.engine.components.ISharedStateComponent; import io.apiman.gateway.engine.impl.DefaultPolicyFailureFactoryComponent; import io.apiman.gateway.engine.impl.InMemorySharedStateComponent; import io.apiman.gateway.engine.policy.IPolicyChain; import io.apiman.gateway.engine.policy.IPolicyContext; import io.apiman.plugins.keycloak_oauth_policy.beans.ForwardAuthInfo; import io.apiman.plugins.keycloak_oauth_policy.beans.ForwardRoles; import io.apiman.plugins.keycloak_oauth_policy.beans.KeycloakOauthConfigBean; import java.io.IOException; import java.io.StringWriter; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.KeyPair; import java.security.KeyPairGenerator; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.Security; import java.security.SignatureException; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Date; import javax.security.auth.x500.X500Principal; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.util.io.pem.PemObject; import org.bouncycastle.util.io.pem.PemWriter; import org.bouncycastle.x509.X509V1CertificateGenerator; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.keycloak.common.util.Time; import org.keycloak.jose.jws.JWSBuilder; import org.keycloak.representations.AccessToken; import org.keycloak.representations.AccessToken.Access; import org.mockito.Mock; import org.mockito.MockitoAnnotations; /** * Test the {@link KeycloakOauthPolicy}. * * With thanks to the Keycloak project for their RSAVerifierTest whose setup procedures are adapted here for * our requirements. * * @author Marc Savy {@literal <msavy@redhat.com>} */ @SuppressWarnings({ "nls", "deprecation" }) public class KeycloakOauthPolicyLegacyTest { private static X509Certificate[] idpCertificates; private static KeyPair idpPair; private AccessToken token; private KeycloakOauthPolicy keycloakOauthPolicy; private KeycloakOauthConfigBean config; private ApiRequest apiRequest; @Mock private IPolicyChain<ApiRequest> mChain; @Mock private IPolicyContext mContext; private ForwardRoles forwardRoles; static { if (Security.getProvider("BC") == null) Security.addProvider(new BouncyCastleProvider()); } public static X509Certificate generateTestCertificate(String subject, String issuer, KeyPair pair) throws InvalidKeyException, NoSuchProviderException, SignatureException { X509V1CertificateGenerator certGen = new X509V1CertificateGenerator(); certGen.setSerialNumber(BigInteger.valueOf(System.currentTimeMillis())); certGen.setIssuerDN(new X500Principal(issuer)); certGen.setNotBefore(new Date(System.currentTimeMillis() - 10000)); certGen.setNotAfter(new Date(System.currentTimeMillis() + 10000)); certGen.setSubjectDN(new X500Principal(subject)); certGen.setPublicKey(pair.getPublic()); certGen.setSignatureAlgorithm("SHA256WithRSAEncryption"); return certGen.generateX509Certificate(pair.getPrivate(), "BC"); } @BeforeClass public static void setupCerts() throws NoSuchAlgorithmException, InvalidKeyException, NoSuchProviderException, SignatureException { idpPair = KeyPairGenerator.getInstance("RSA").generateKeyPair(); idpCertificates = new X509Certificate[] { generateTestCertificate("CN=IDP", "CN=IDP", idpPair) }; } @Before public void initTest() { MockitoAnnotations.initMocks(this); token = new AccessToken(); AccessToken realm = token.type("Bearer").subject("CN=Client").issuer("apiman-realm"); // KC seems to use issuer for realm? realm.addAccess("apiman-api").addRole("apiman-gateway-user-role").addRole("a-nother-role"); realm.setRealmAccess(new Access().addRole("lets-use-a-realm-role")); keycloakOauthPolicy = new KeycloakOauthPolicy(); config = new KeycloakOauthConfigBean(); config.setRequireOauth(true); config.setStripTokens(false); config.setBlacklistUnsafeTokens(false); config.setRequireTransportSecurity(false); forwardRoles = new ForwardRoles(); config.setForwardRoles(forwardRoles); apiRequest = new ApiRequest(); // Set up components. // Failure factory given(mContext.getComponent(IPolicyFailureFactoryComponent.class)). willReturn(new DefaultPolicyFailureFactoryComponent()); // Data store given(mContext.getComponent(ISharedStateComponent.class)). willReturn(new InMemorySharedStateComponent()); } private String generateAndSerializeToken() throws CertificateEncodingException, IOException { token.notBefore(Time.currentTime() - 100); config.setRealm("apiman-realm"); config.setRealmCertificateString(certificateAsPem(idpCertificates[0])); return new JWSBuilder().jsonContent(token).rsa256(idpPair.getPrivate()); } @Test public void subjectToSub() throws CertificateEncodingException, IOException { ForwardAuthInfo authInfo = new ForwardAuthInfo(); authInfo.setHeaders("X-TEST"); authInfo.setField("subject"); config.getForwardAuthInfo().add(authInfo); token.setSubject("anse-georgette"); String encoded = generateAndSerializeToken(); apiRequest.getHeaders().put("Authorization", "Bearer " + encoded); keycloakOauthPolicy.apply(apiRequest, mContext, config, mChain); verify(mChain).doApply(apiRequest); Assert.assertEquals("anse-georgette", apiRequest.getHeaders().get("X-TEST")); } @Test public void usernameToPreferredUsername() throws CertificateEncodingException, IOException { ForwardAuthInfo authInfo = new ForwardAuthInfo(); authInfo.setHeaders("X-TEST"); authInfo.setField("username"); config.getForwardAuthInfo().add(authInfo); token.setPreferredUsername("anse-lazio"); String encoded = generateAndSerializeToken(); apiRequest.getHeaders().put("Authorization", "Bearer " + encoded); keycloakOauthPolicy.apply(apiRequest, mContext, config, mChain); verify(mChain).doApply(apiRequest); Assert.assertEquals("anse-lazio", apiRequest.getHeaders().get("X-TEST")); } private String certificateAsPem(X509Certificate x509) throws CertificateEncodingException, IOException { StringWriter sw = new StringWriter(); PemWriter writer = new PemWriter(sw); PemObject pemObject = new PemObject("CERTIFICATE", x509.getEncoded()); try { writer.writeObject(pemObject); writer.flush(); } catch (IOException e) { throw new RuntimeException(e); } finally { writer.close(); } return sw.toString(); } }