Validation for CIBA binding_message parameter (#9470)

closes #9469
This commit is contained in:
Marek Posolda 2022-01-11 11:19:15 +01:00 committed by GitHub
parent b2e55762f1
commit 8f221bb21e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 57 additions and 1 deletions

View file

@ -53,6 +53,9 @@ public class OAuthErrorException extends Exception {
public static final String SLOW_DOWN = "slow_down"; public static final String SLOW_DOWN = "slow_down";
public static final String EXPIRED_TOKEN = "expired_token"; public static final String EXPIRED_TOKEN = "expired_token";
// CIBA
public static final String INVALID_BINDING_MESSAGE = "invalid_binding_message";
// Others // Others
public static final String INVALID_CLIENT = "invalid_client"; public static final String INVALID_CLIENT = "invalid_client";
public static final String INVALID_GRANT = "invalid_grant"; public static final String INVALID_GRANT = "invalid_grant";

View file

@ -54,6 +54,7 @@ import javax.ws.rs.core.Response;
import java.util.Collections; import java.util.Collections;
import java.util.Optional; import java.util.Optional;
import java.util.regex.Pattern;
import static org.keycloak.protocol.oidc.OIDCLoginProtocol.ID_TOKEN_HINT; import static org.keycloak.protocol.oidc.OIDCLoginProtocol.ID_TOKEN_HINT;
import static org.keycloak.protocol.oidc.OIDCLoginProtocol.LOGIN_HINT_PARAM; import static org.keycloak.protocol.oidc.OIDCLoginProtocol.LOGIN_HINT_PARAM;
@ -62,6 +63,8 @@ public class BackchannelAuthenticationEndpoint extends AbstractCibaEndpoint {
private final RealmModel realm; private final RealmModel realm;
private static final Pattern BINDING_MESSAGE_VALIDATION = Pattern.compile("^[a-zA-Z0-9-._+/!?#]{1,50}$");
public BackchannelAuthenticationEndpoint(KeycloakSession session, EventBuilder event) { public BackchannelAuthenticationEndpoint(KeycloakSession session, EventBuilder event) {
super(session, event); super(session, event);
this.realm = session.getContext().getRealm(); this.realm = session.getContext().getRealm();
@ -172,7 +175,10 @@ public class BackchannelAuthenticationEndpoint extends AbstractCibaEndpoint {
request.setScope(scope); request.setScope(scope);
// optional parameters // optional parameters
if (endpointRequest.getBindingMessage() != null) request.setBindingMessage(endpointRequest.getBindingMessage()); if (endpointRequest.getBindingMessage() != null) {
validateBindingMessage(endpointRequest.getBindingMessage());
request.setBindingMessage(endpointRequest.getBindingMessage());
}
if (endpointRequest.getAcr() != null) request.setAcrValues(endpointRequest.getAcr()); if (endpointRequest.getAcr() != null) request.setAcrValues(endpointRequest.getAcr());
CibaConfig policy = realm.getCibaPolicy(); CibaConfig policy = realm.getCibaPolicy();
@ -228,6 +234,13 @@ public class BackchannelAuthenticationEndpoint extends AbstractCibaEndpoint {
} }
} }
protected void validateBindingMessage(String bindingMessage) {
if (!BINDING_MESSAGE_VALIDATION.matcher(bindingMessage).matches()) {
throw new ErrorResponseException(OAuthErrorException.INVALID_BINDING_MESSAGE, "the binding_message value has to be max 50 characters in length and must contain only basic plain-text characters without spaces",
Response.Status.BAD_REQUEST);
}
}
private UserModel resolveUser(BackchannelAuthenticationEndpointRequest endpointRequest, String authRequestedUserHint) { private UserModel resolveUser(BackchannelAuthenticationEndpointRequest endpointRequest, String authRequestedUserHint) {
CIBALoginUserResolver resolver = session.getProvider(CIBALoginUserResolver.class); CIBALoginUserResolver resolver = session.getProvider(CIBALoginUserResolver.class);

View file

@ -28,6 +28,7 @@ import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.CloseableHttpClient;
import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.notNullValue;
import static org.hamcrest.Matchers.nullValue;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
@ -397,6 +398,45 @@ public class CIBATest extends AbstractClientPoliciesTest {
} }
} }
// Corresponds to the test fapi-ciba-id1-ensure-authorization-request-with-potentially-bad-binding-message from the FAPI CIBA conformance testsuite
@Test
public void testBadBindingMessage() throws Exception {
ClientResource clientResource = null;
ClientRepresentation clientRep = null;
try {
final String username = "nutzername-schwarz";
clientResource = ApiUtil.findClientByClientId(adminClient.realm(TEST_REALM_NAME), TEST_CLIENT_NAME);
clientRep = clientResource.toRepresentation();
prepareCIBASettings(clientResource, clientRep);
// Binding message with non plain-text characters
String bindingMessage = "1234 \uD83D\uDC4D\uD83C\uDFFF 品川 Lor";
AuthenticationRequestAcknowledgement response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null);
assertThat(response.getStatusCode(), is(equalTo(400)));
assertThat(response.getError(), is(OAuthErrorException.INVALID_BINDING_MESSAGE));
// Long binding message
bindingMessage = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud.";
response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null);
assertThat(response.getStatusCode(), is(equalTo(400)));
assertThat(response.getError(), is(OAuthErrorException.INVALID_BINDING_MESSAGE));
// Empty binding message
bindingMessage = "";
response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null);
assertThat(response.getStatusCode(), is(equalTo(400)));
assertThat(response.getError(), is(OAuthErrorException.INVALID_BINDING_MESSAGE));
// Valid binding message
bindingMessage = "Lorem_ipsum";
response = oauth.doBackchannelAuthenticationRequest(TEST_CLIENT_NAME, TEST_CLIENT_PASSWORD, username, bindingMessage, null);
assertThat(response.getStatusCode(), is(equalTo(200)));
assertThat(response.getError(), is(nullValue()));
} finally {
revertCIBASettings(clientResource, clientRep);
}
}
@Test @Test
@Ignore("Should never happen because the AD does not send any information about the user but only the status of the authentication") @Ignore("Should never happen because the AD does not send any information about the user but only the status of the authentication")
public void testDifferentUserAuthenticated() throws Exception { public void testDifferentUserAuthenticated() throws Exception {