/*
* Copyright 2017 Analytical Graphics, Inc. and/or its affiliates
* and other 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.keycloak.testsuite.x509;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.keycloak.OAuth2Constants;
import org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel;
import org.keycloak.events.Details;
import org.keycloak.events.Errors;
import org.keycloak.representations.AccessToken;
import org.keycloak.representations.RefreshToken;
import org.keycloak.representations.idm.AuthenticatorConfigRepresentation;
import org.keycloak.representations.idm.UserRepresentation;
import org.keycloak.testsuite.util.OAuthClient;
import javax.ws.rs.core.Response;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.IdentityMapperType.USERNAME_EMAIL;
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.IdentityMapperType.USER_ATTRIBUTE;
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.ISSUERDN;
import static org.keycloak.authentication.authenticators.x509.X509AuthenticatorConfigModel.MappingSourceType.SUBJECTDN_EMAIL;
/**
* @author <a href="mailto:brat000012001@gmail.com">Peter Nalyvayko</a>
* @version $Revision: 1 $
* @since 10/28/2016
*/
public class X509DirectGrantTest extends AbstractX509AuthenticationTest {
@Test
public void loginFailedOnDuplicateUsers() throws Exception {
AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", createLoginIssuerDN_OU2CustomAttributeConfig().getConfig());
String cfgId = createConfig(directGrantExecution.getId(), cfg);
Assert.assertNotNull(cfgId);
// Set up the users so that the identity extracted from X509 client cert
// matches more than a single user to trigger DuplicateModelException.
UserRepresentation user = testRealm().users().get(userId2).toRepresentation();
Assert.assertNotNull(user);
user.singleAttribute("x509_certificate_identity", "Red Hat");
this.updateUser(user);
user = testRealm().users().get(userId).toRepresentation();
Assert.assertNotNull(user);
user.singleAttribute("x509_certificate_identity", "Red Hat");
this.updateUser(user);
events.clear();
oauth.clientId("resource-owner");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null);
assertEquals(401, response.getStatusCode());
assertEquals("invalid_request", response.getError());
Assert.assertThat(response.getErrorDescription(), containsString("X509 certificate authentication's failed."));
}
@Test
public void loginFailedOnInvalidUser() throws Exception {
AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", createLoginIssuerDN_OU2CustomAttributeConfig().getConfig());
String cfgId = createConfig(directGrantExecution.getId(), cfg);
Assert.assertNotNull(cfgId);
UserRepresentation user = testRealm().users().get(userId2).toRepresentation();
Assert.assertNotNull(user);
user.singleAttribute("x509_certificate_identity", "-");
this.updateUser(user);
events.clear();
oauth.clientId("resource-owner");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null);
events.expectLogin()
.user((String) null)
.session((String) null)
.error(Errors.INVALID_USER_CREDENTIALS)
.client("resource-owner")
.removeDetail(Details.CODE_ID)
.removeDetail(Details.USERNAME)
.removeDetail(Details.CONSENT)
.removeDetail(Details.REDIRECT_URI)
.assertEvent();
assertEquals(401, response.getStatusCode());
assertEquals("invalid_grant", response.getError());
assertEquals("Invalid user credentials", response.getErrorDescription());
}
@Test
public void loginFailedDisabledUser() throws Exception {
setUserEnabled("test-user@localhost", false);
try {
AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", createLoginSubjectEmail2UsernameOrEmailConfig().getConfig());
String cfgId = createConfig(directGrantExecution.getId(), cfg);
Assert.assertNotNull(cfgId);
oauth.clientId("resource-owner");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null);
events.expectLogin()
.user(userId)
.session((String) null)
.error(Errors.USER_DISABLED)
.client("resource-owner")
.detail(Details.USERNAME, "test-user@localhost")
.removeDetail(Details.CODE_ID)
.removeDetail(Details.CONSENT)
.removeDetail(Details.REDIRECT_URI)
.assertEvent();
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals("invalid_grant", response.getError());
assertEquals("Account disabled", response.getErrorDescription());
} finally {
setUserEnabled("test-user@localhost", true);
}
}
private void loginForceTemporaryAccountLock() throws Exception {
X509AuthenticatorConfigModel config = new X509AuthenticatorConfigModel()
.setMappingSourceType(ISSUERDN)
.setRegularExpression("OU=(.*?)(?:,|$)")
.setUserIdentityMapperType(USER_ATTRIBUTE)
.setCustomAttributeName("x509_certificate_identity");
AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", config.getConfig());
String cfgId = createConfig(directGrantExecution.getId(), cfg);
Assert.assertNotNull(cfgId);
UserRepresentation user = testRealm().users().get(userId).toRepresentation();
Assert.assertNotNull(user);
user.singleAttribute("x509_certificate_identity", "-");
this.updateUser(user);
events.clear();
oauth.clientId("resource-owner");
oauth.doGrantAccessTokenRequest("secret", "", "", null);
oauth.doGrantAccessTokenRequest("secret", "", "", null);
oauth.doGrantAccessTokenRequest("secret", "", "", null);
events.clear();
}
@Test
@Ignore
public void loginFailedTemporarilyDisabledUser() throws Exception {
loginForceTemporaryAccountLock();
AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", createLoginSubjectEmail2UsernameOrEmailConfig().getConfig());
String cfgId = createConfig(directGrantExecution.getId(), cfg);
Assert.assertNotNull(cfgId);
oauth.clientId("resource-owner");
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest("secret", "", "", null);
events.expectLogin()
.user(userId)
.session((String) null)
.error(Errors.USER_TEMPORARILY_DISABLED)
.detail(Details.USERNAME, "test-user@localhost")
.removeDetail(Details.CODE_ID)
.removeDetail(Details.CONSENT)
.removeDetail(Details.REDIRECT_URI)
.assertEvent();
assertEquals(Response.Status.BAD_REQUEST.getStatusCode(), response.getStatusCode());
assertEquals("invalid_grant", response.getError());
assertEquals("Account temporarily disabled", response.getErrorDescription());
}
private void doResourceOwnerCredentialsLogin(String clientId, String clientSecret, String login, String password) throws Exception {
oauth.clientId(clientId);
OAuthClient.AccessTokenResponse response = oauth.doGrantAccessTokenRequest(clientSecret, "", "", null);
assertEquals(200, response.getStatusCode());
AccessToken accessToken = oauth.verifyToken(response.getAccessToken());
RefreshToken refreshToken = oauth.verifyRefreshToken(response.getRefreshToken());
events.expectLogin()
.client(clientId)
.user(userId)
.session(accessToken.getSessionState())
.detail(Details.GRANT_TYPE, OAuth2Constants.PASSWORD)
.detail(Details.TOKEN_ID, accessToken.getId())
.detail(Details.REFRESH_TOKEN_ID, refreshToken.getId())
.detail(Details.USERNAME, login)
.removeDetail(Details.CODE_ID)
.removeDetail(Details.REDIRECT_URI)
.removeDetail(Details.CONSENT)
.assertEvent();
}
@Test
public void loginResourceOwnerCredentialsSuccess() throws Exception {
X509AuthenticatorConfigModel config =
new X509AuthenticatorConfigModel()
.setMappingSourceType(SUBJECTDN_EMAIL)
.setUserIdentityMapperType(USERNAME_EMAIL);
AuthenticatorConfigRepresentation cfg = newConfig("x509-directgrant-config", config.getConfig());
String cfgId = createConfig(directGrantExecution.getId(), cfg);
Assert.assertNotNull(cfgId);
doResourceOwnerCredentialsLogin("resource-owner", "secret", "test-user@localhost", "");
}
}