/*
* 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 static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URL;
import java.security.AccessController;
import java.util.Arrays;
import javax.json.Json;
import javax.json.JsonObjectBuilder;
import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.UnsupportedCallbackException;
import javax.security.sasl.SaslClient;
import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.wildfly.security.auth.client.AuthenticationConfiguration;
import org.wildfly.security.auth.client.AuthenticationContext;
import org.wildfly.security.auth.client.AuthenticationContextConfigurationClient;
import org.wildfly.security.auth.client.MatchRule;
import org.wildfly.security.auth.realm.token.TokenSecurityRealm;
import org.wildfly.security.auth.realm.token.validator.JwtValidator;
import org.wildfly.security.auth.server.RealmUnavailableException;
import org.wildfly.security.auth.server.SecurityRealm;
import org.wildfly.security.credential.source.OAuth2CredentialSource;
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 okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
/**
* @author <a href="mailto:psilva@redhat.com">Pedro Igor</a>
*/
public class OAuth2SaslClientTest extends BaseTestCase {
private MockWebServer server;
@Before
public void onBefore() throws Exception {
System.setProperty("wildfly.config.url", getClass().getResource("wildfly-oauth2-test-config.xml").toExternalForm());
server = new MockWebServer();
server.setDispatcher(createTokenEndpoint());
server.start(50831);
}
@After
public void onAfter() throws Exception {
if (server != null) {
server.shutdown();
}
}
@Test
public void testWithResourceOwnerCredentialsUsingConfiguration() throws Exception {
URI serverUri = URI.create("protocol://test1.org");
SaslClient saslClient = createSaslClientFromConfiguration(serverUri);
assertNotNull("OAuth2SaslClient is null", saslClient);
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
.setServerName("resourceserver.comn")
.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 testWithClientCredentialsUsingConfiguration() throws Exception {
URI serverUri = URI.create("protocol://test2.org");
SaslClient saslClient = createSaslClientFromConfiguration(serverUri);
assertNotNull("OAuth2SaslClient is null", saslClient);
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
.setServerName("resourceserver.comn")
.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 failedWithBearerTokenFromConfiguration() throws Exception {
SaslClient saslClient = createSaslClientFromConfiguration(URI.create("protocol://test3.org"));
assertNotNull("OAuth2SaslClient is null", saslClient);
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
.setServerName("resourceserver.comn")
.setProtocol("imap")
.addRealm("oauth-realm", createSecurityRealmMock())
.setDefaultRealmName("oauth-realm")
.build();
byte[] message = AbstractSaslParticipant.NO_BYTES;
message = saslClient.evaluateChallenge(message);
try {
message = saslServer.evaluateResponse(message);
} catch (SaslException e) {
assertTrue(e.getCause() instanceof RealmUnavailableException);
RealmUnavailableException cause = (RealmUnavailableException) e.getCause();
assertTrue(cause.getMessage().contains("ELY01114"));
}
}
@Test
public void failedInvalidClientCredentialsUsingConfiguration() throws Exception {
URI serverUri = URI.create("protocol://test4.org");
SaslClient saslClient = createSaslClientFromConfiguration(serverUri);
assertNotNull("OAuth2SaslClient is null", saslClient);
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
.setServerName("resourceserver.comn")
.setProtocol("imap")
.addRealm("oauth-realm", createSecurityRealmMock())
.setDefaultRealmName("oauth-realm")
.build();
byte[] message = AbstractSaslParticipant.NO_BYTES;
try {
do {
message = saslClient.evaluateChallenge(message);
if (message == null) break;
message = saslServer.evaluateResponse(message);
} while (message != null);
fail("Expected bad response from server");
} catch (Exception e) {
e.printStackTrace();
assertTrue(e.getCause().getMessage().contains("ELY05125"));
}
}
@Test
public void testWithResourceOwnerCredentials() throws Exception {
URI serverUri = URI.create("protocol://test5.org");
SaslClient saslClient = createSaslClientFromConfiguration(serverUri);
assertNotNull("OAuth2SaslClient is null", saslClient);
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
.setServerName("resourceserver.comn")
.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 testWithBearerTokenFromConfiguration() throws Exception {
SaslClient saslClient = createSaslClientFromConfiguration(URI.create("protocol://test5.org"));
assertNotNull("OAuth2SaslClient is null", saslClient);
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
.setServerName("resourceserver.comn")
.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 failedResourceOwnerCredentialsUsingConfiguration() throws Exception {
URI serverUri = URI.create("protocol://test6.org");
AuthenticationContext context = AuthenticationContext.getContextManager().get();
AuthenticationContextConfigurationClient contextConfigurationClient = AccessController.doPrivileged(AuthenticationContextConfigurationClient.ACTION);
AuthenticationConfiguration authenticationConfiguration = contextConfigurationClient.getAuthenticationConfiguration(serverUri, context);
SaslClient saslClient = contextConfigurationClient.createSaslClient(serverUri, authenticationConfiguration, Arrays.asList(SaslMechanismInformation.Names.OAUTHBEARER));
assertNotNull("OAuth2SaslClient is null", saslClient);
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
.setServerName("resourceserver.comn")
.setProtocol("imap")
.addRealm("oauth-realm", createSecurityRealmMock())
.setDefaultRealmName("oauth-realm")
.build();
byte[] message = AbstractSaslParticipant.NO_BYTES;
try {
do {
message = saslClient.evaluateChallenge(message);
if (message == null) break;
message = saslServer.evaluateResponse(message);
} while (message != null);
fail("Expected bad response from server");
} catch (Exception e) {
e.printStackTrace();
assertTrue(e.getCause().getMessage().contains("ELY09001"));
}
}
@Test
public void testResourceOwnerCredentialsUsingAPI() throws Exception {
AuthenticationContext context = AuthenticationContext.empty()
.with(MatchRule.ALL.matchHost("resourceserver.com"), AuthenticationConfiguration.EMPTY
.useCredentials(OAuth2CredentialSource.builder(new URL("http://localhost:50831/token"))
.clientCredentials("elytron-client", "dont_tell_me")
.useResourceOwnerPassword("alice", "dont_tell_me")
.build())
.allowSaslMechanisms("OAUTHBEARER"));
AuthenticationContextConfigurationClient contextConfigurationClient = AccessController.doPrivileged(AuthenticationContextConfigurationClient.ACTION);
AuthenticationConfiguration configuration = contextConfigurationClient.getAuthenticationConfiguration(URI.create("http://resourceserver.com"), context);
SaslClient saslClient = contextConfigurationClient.createSaslClient(URI.create("http://resourceserver.com"), configuration, Arrays.asList("OAUTHBEARER"));
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
.setServerName("resourceserver.comn")
.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 failedResourceOwnerCredentialsUsingAPI() throws Exception {
AuthenticationContext context = AuthenticationContext.empty()
.with(MatchRule.ALL.matchHost("resourceserver.com"), AuthenticationConfiguration.EMPTY
.useCredentials(OAuth2CredentialSource.builder(new URL("http://localhost:50831/token"))
.useResourceOwnerPassword("unknown", "dont_tell_me")
.clientCredentials("bad", "bad")
.build())
.allowSaslMechanisms("OAUTHBEARER"))
.with(MatchRule.ALL.matchHost("localhost").matchPort(50831).matchPath("/token"), AuthenticationConfiguration.EMPTY
.useName("elytron_client")
.usePassword("dont_tell_me"));
AuthenticationContextConfigurationClient contextConfigurationClient = AccessController.doPrivileged(AuthenticationContextConfigurationClient.ACTION);
AuthenticationConfiguration configuration = contextConfigurationClient.getAuthenticationConfiguration(URI.create("http://resourceserver.com"), context);
SaslClient saslClient = contextConfigurationClient.createSaslClient(URI.create("http://resourceserver.com"), configuration, Arrays.asList("OAUTHBEARER"));
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
.setServerName("resourceserver.comn")
.setProtocol("imap")
.addRealm("oauth-realm", createSecurityRealmMock())
.setDefaultRealmName("oauth-realm")
.build();
byte[] message = AbstractSaslParticipant.NO_BYTES;
try {
do {
message = saslClient.evaluateChallenge(message);
if (message == null) break;
message = saslServer.evaluateResponse(message);
} while (message != null);
fail("Expected bad response from server");
} catch (Exception e) {
e.printStackTrace();
assertTrue(e.getCause().getMessage().contains("ELY05125"));
}
}
@Test
public void testResourceOwnerCredentialsFromExternalCallback() throws Exception {
URI serverUri = URI.create("protocol://test7.org");
AuthenticationContext context = AuthenticationContext.getContextManager().get();
AuthenticationContextConfigurationClient contextConfigurationClient = AccessController.doPrivileged(AuthenticationContextConfigurationClient.ACTION);
AuthenticationConfiguration authenticationConfiguration = contextConfigurationClient.getAuthenticationConfiguration(serverUri, context);
SaslClient saslClient = contextConfigurationClient.createSaslClient(serverUri, authenticationConfiguration, Arrays.asList(SaslMechanismInformation.Names.OAUTHBEARER));
assertNotNull("OAuth2SaslClient is null", saslClient);
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
.setServerName("resourceserver.comn")
.setProtocol("imap")
.addRealm("oauth-realm", createSecurityRealmMock())
.setDefaultRealmName("oauth-realm")
.build();
AuthenticationContext externalContext = AuthenticationContext.empty().with(MatchRule.ALL.matchHost("localhost"), AuthenticationConfiguration.EMPTY.useCallbackHandler(new CallbackHandler() {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback.class.cast(callback).setName("alice");
} else if (callback instanceof PasswordCallback) {
PasswordCallback.class.cast(callback).setPassword("dont_tell_me".toCharArray());
} else {
throw new RuntimeException("Unexpected callback");
}
}
}
}));
externalContext.run(() -> {
try {
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());
} catch (Exception e) {
throw new RuntimeException(e);
}
});
}
@Test
public void failedResourceOwnerCredentialsFromExternalCallback() throws Exception {
URI serverUri = URI.create("protocol://test7.org");
AuthenticationContext context = AuthenticationContext.getContextManager().get();
AuthenticationContextConfigurationClient contextConfigurationClient = AccessController.doPrivileged(AuthenticationContextConfigurationClient.ACTION);
AuthenticationConfiguration authenticationConfiguration = contextConfigurationClient.getAuthenticationConfiguration(serverUri, context);
SaslClient saslClient = contextConfigurationClient.createSaslClient(serverUri, authenticationConfiguration, Arrays.asList(SaslMechanismInformation.Names.OAUTHBEARER));
assertNotNull("OAuth2SaslClient is null", saslClient);
SaslServer saslServer = new SaslServerBuilder(OAuth2SaslServerFactory.class, SaslMechanismInformation.Names.OAUTHBEARER)
.setServerName("resourceserver.comn")
.setProtocol("imap")
.addRealm("oauth-realm", createSecurityRealmMock())
.setDefaultRealmName("oauth-realm")
.build();
AuthenticationContext externalContext = AuthenticationContext.empty().with(MatchRule.ALL.matchHost("localhost"), AuthenticationConfiguration.EMPTY.useCallbackHandler(new CallbackHandler() {
@Override
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback.class.cast(callback).setName("alice");
} else if (callback instanceof PasswordCallback) {
PasswordCallback.class.cast(callback).setPassword("bad_password".toCharArray());
} else {
throw new RuntimeException("Unexpected callback");
}
}
}
}));
externalContext.run(() -> {
byte[] message = AbstractSaslParticipant.NO_BYTES;
try {
do {
message = saslClient.evaluateChallenge(message);
if (message == null) break;
message = saslServer.evaluateResponse(message);
} while (message != null);
fail("Expected bad response from server");
} catch (Exception e) {
e.printStackTrace();
assertTrue(e.getCause().getMessage().contains("ELY05125"));
}
});
}
private SecurityRealm createSecurityRealmMock() throws MalformedURLException {
return TokenSecurityRealm.builder().validator(JwtValidator.builder().build()).principalClaimName("preferred_username").build();
}
private Dispatcher createTokenEndpoint() {
return new Dispatcher() {
@Override
public MockResponse dispatch(RecordedRequest recordedRequest) throws InterruptedException {
String body = recordedRequest.getBody().readUtf8();
boolean resourceOwnerCredentials = body.contains("grant_type=password");
boolean clientCredentials = body.contains("grant_type=client_credentials");
if (resourceOwnerCredentials
&& (body.contains("client_id=elytron-client") && body.contains("client_secret=dont_tell_me"))
&& (body.contains("username=alice") || body.contains("username=jdoe"))
&& body.contains("password=dont_tell_me")) {
JsonObjectBuilder tokenBuilder = Json.createObjectBuilder();
tokenBuilder.add("access_token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaXNzIjoiYXV0aC5zZXJ2ZXIiLCJhdWQiOiJmb3JfbWUiLCJleHAiOjE3NjA5OTE2MzUsInByZWZlcnJlZF91c2VybmFtZSI6Impkb2UifQ.SoPW41_mOFnKXdkwVG63agWQ2k09dEnEtTBztnxHN64");
return new MockResponse().setBody(tokenBuilder.build().toString());
} else if (clientCredentials
&& (body.contains("client_id=elytron-client") && body.contains("client_secret=dont_tell_me"))
&& !body.contains("username=")) {
JsonObjectBuilder tokenBuilder = Json.createObjectBuilder();
tokenBuilder.add("access_token", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaXNzIjoiYXV0aC5zZXJ2ZXIiLCJhdWQiOiJmb3JfbWUiLCJleHAiOjE3NjA5OTE2MzUsInByZWZlcnJlZF91c2VybmFtZSI6Impkb2UifQ.SoPW41_mOFnKXdkwVG63agWQ2k09dEnEtTBztnxHN64");
return new MockResponse().setBody(tokenBuilder.build().toString());
}
return new MockResponse().setResponseCode(400);
}
};
}
private SaslClient createSaslClientFromConfiguration(URI serverUri) throws SaslException {
AuthenticationContext context = AuthenticationContext.getContextManager().get();
AuthenticationContextConfigurationClient contextConfigurationClient = AccessController.doPrivileged(AuthenticationContextConfigurationClient.ACTION);
AuthenticationConfiguration authenticationConfiguration = contextConfigurationClient.getAuthenticationConfiguration(serverUri, context);
return contextConfigurationClient.createSaslClient(serverUri, authenticationConfiguration, Arrays.asList(SaslMechanismInformation.Names.OAUTHBEARER));
}
}