/* * JBoss, Home of Professional Open Source. * Copyright 2016 Red Hat, Inc., and individual contributors * as indicated by the @author tags. * * 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 org.wildfly.security.sasl.oauth2; import mockit.Mock; import mockit.MockUp; import mockit.integration.junit4.JMockit; import org.junit.Test; import org.junit.runner.RunWith; import org.wildfly.security.auth.callback.CredentialCallback; import org.wildfly.security.auth.realm.token.TokenSecurityRealm; import org.wildfly.security.auth.realm.token.validator.OAuth2IntrospectValidator; import org.wildfly.security.auth.server.SecurityRealm; import org.wildfly.security.credential.BearerTokenCredential; import org.wildfly.security.mechanism.oauth2.OAuth2Server; import org.wildfly.security.sasl.test.BaseTestCase; import org.wildfly.security.sasl.test.SaslServerBuilder; import org.wildfly.security.sasl.util.AbstractSaslParticipant; import org.wildfly.security.sasl.util.SaslMechanismInformation; import org.wildfly.security.util.CodePointIterator; import javax.json.Json; import javax.json.JsonObject; import javax.json.JsonObjectBuilder; import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.sasl.SaslClient; import javax.security.sasl.SaslClientFactory; import javax.security.sasl.SaslServer; import javax.security.sasl.SaslServerFactory; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; import java.util.Collections; import java.util.HashMap; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; /** * @author <a href="mailto:psilva@redhat.com">Pedro Igor</a> */ @RunWith(JMockit.class) public class OAuth2SaslTest extends BaseTestCase { @Test public void testQueryMechanisms() { SaslServerFactory serverFactory = obtainSaslServerFactory(OAuth2SaslServerFactory.class); String[] mechanismNames = serverFactory.getMechanismNames(Collections.emptyMap()); assertEquals(1, mechanismNames.length); assertEquals("OAUTHBEARER", mechanismNames[0]); } /** * Tests the abstract message flow, accordingly with the RFC-7628. Here, the steps that interact with the OAuth2 Authorization Server * are omitted and an access token is passed directly to the OAuth2 SASL Server. * * @throws Exception */ @Test public void testAbstractMessageFlow() throws Exception { SaslClientFactory saslClientFactory = obtainSaslClientFactory(OAuth2SaslClientFactory.class); assertNotNull("OAuth2SaslClientFactory not found", saslClientFactory); SaslClient saslClient = saslClientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OAUTHBEARER}, "user", "imap", "resourceserver.com", Collections.emptyMap(), new CallbackHandler() { @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { if (callback instanceof CredentialCallback) { CredentialCallback credentialCallback = (CredentialCallback) callback; JsonObjectBuilder tokenBuilder = Json.createObjectBuilder(); tokenBuilder.add("active", true); tokenBuilder.add("username", "elytron@jboss.org"); credentialCallback.setCredential(new BearerTokenCredential(tokenBuilder.build().toString())); } } } }); assertNotNull("OAuth2SaslClient is null", saslClient); SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) .setServerName("resourceserver.com") .setProtocol("imap") .addRealm("oauth-realm", createSecurityRealmMock()) .setDefaultRealmName("oauth-realm") .build(); byte[] message = AbstractSaslParticipant.NO_BYTES; do { message = saslClient.evaluateChallenge(message); if (message == null) break; message = saslServer.evaluateResponse(message); } while (message != null); assertTrue(saslServer.isComplete()); assertTrue(saslClient.isComplete()); } @Test public void testSuccessfulWithoutAuthorizationId() throws Exception { SaslClientFactory saslClientFactory = obtainSaslClientFactory(OAuth2SaslClientFactory.class); assertNotNull("OAuth2SaslClientFactory not found", saslClientFactory); SaslClient saslClient = saslClientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OAUTHBEARER}, null, "imap", "resourceserver.com", Collections.emptyMap(), new CallbackHandler() { @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { if (callback instanceof CredentialCallback) { CredentialCallback credentialCallback = (CredentialCallback) callback; JsonObjectBuilder tokenBuilder = Json.createObjectBuilder(); tokenBuilder.add("active", true); tokenBuilder.add("username", "elytron@jboss.org"); credentialCallback.setCredential(new BearerTokenCredential(tokenBuilder.build().toString())); } } } }); assertNotNull("OAuth2SaslClient is null", saslClient); SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) .setServerName("resourceserver.com") .setProtocol("imap") .addRealm("oauth-realm", createSecurityRealmMock()) .setDefaultRealmName("oauth-realm") .build(); byte[] message = AbstractSaslParticipant.NO_BYTES; do { message = saslClient.evaluateChallenge(message); if (message == null) break; message = saslServer.evaluateResponse(message); } while (message != null); assertTrue(saslServer.isComplete()); assertTrue(saslClient.isComplete()); } /** * Tests error messages from server, usually after an usuccessful authentication. Where in this case, the server must * return a JSON with details about what it failed. * * @throws Exception */ @Test public void testFailedAuthenticationFlow() throws Exception { SaslClientFactory saslClientFactory = obtainSaslClientFactory(OAuth2SaslClientFactory.class); assertNotNull("OAuth2SaslClientFactory not found", saslClientFactory); SaslClient saslClient = saslClientFactory.createSaslClient(new String[]{SaslMechanismInformation.Names.OAUTHBEARER}, "user", "imap", "resourceserver.com", Collections.emptyMap(), new CallbackHandler() { @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { for (Callback callback : callbacks) { if (callback instanceof CredentialCallback) { CredentialCallback credentialCallback = (CredentialCallback) callback; JsonObjectBuilder tokenBuilder = Json.createObjectBuilder(); tokenBuilder.add("active", false); credentialCallback.setCredential(new BearerTokenCredential(tokenBuilder.build().toString())); } } } }); assertNotNull("OAuth2SaslClient is null", saslClient); HashMap<String, Object> saslServerConfig = new HashMap<>(); saslServerConfig.put(OAuth2Server.CONFIG_OPENID_CONFIGURATION_URL, "http://as.test.org/oauth2/.well-known/openid-configuration"); SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER) .setServerName("resourceserver.com") .setProperties(saslServerConfig) .setProtocol("imap") .addRealm("oauth-realm", createSecurityRealmMock()) .setDefaultRealmName("oauth-realm") .build(); byte[] message = AbstractSaslParticipant.NO_BYTES; byte[] serverErrorMessage = AbstractSaslParticipant.NO_BYTES; do { message = saslClient.evaluateChallenge(message); if (message == null) break; message = saslServer.evaluateResponse(message); if (message != null) { serverErrorMessage = message; } } while (message != null); assertFalse(saslServer.isComplete()); assertFalse(saslClient.isComplete()); assertEquals("{\"status\":\"invalid_token\",\"openid-configuration\":\"http://as.test.org/oauth2/.well-known/openid-configuration\"}", new String(CodePointIterator.ofUtf8Bytes(serverErrorMessage).base64Decode().drain())); } private SecurityRealm createSecurityRealmMock() throws MalformedURLException { configureReplayTokenIntrospectionEndpoint(); return TokenSecurityRealm.builder().validator(OAuth2IntrospectValidator.builder() .clientId("wildfly-elytron") .clientSecret("dont_tell_me") .tokenIntrospectionUrl(new URL("http://as.test.org/oauth2/token/introspect")).build()).build(); } private void configureReplayTokenIntrospectionEndpoint() { final Class<?> classToMock; try { classToMock = Class.forName("org.wildfly.security.auth.realm.token.validator.OAuth2IntrospectValidator", true, TokenSecurityRealm.class.getClassLoader()); } catch (ClassNotFoundException e) { throw new NoClassDefFoundError(e.getMessage()); } new MockUp<Object>(classToMock){ @Mock public JsonObject introspectAccessToken(URL tokenIntrospectionUrl, String clientId, String clientSecret, String token, SSLContext sslContext, HostnameVerifier hostnameVerifier) throws IOException { return Json.createReader(new ByteArrayInputStream(token.getBytes())).readObject(); } }; } }