KEYCLOAK-17300 Add a method to check if the token revocation request has duplicate parameters
This commit is contained in:
parent
3d76e2b775
commit
2605eddbe7
2 changed files with 63 additions and 0 deletions
|
@ -99,6 +99,8 @@ public class TokenRevocationEndpoint {
|
||||||
|
|
||||||
formParams = request.getDecodedFormParameters();
|
formParams = request.getDecodedFormParameters();
|
||||||
|
|
||||||
|
checkParameterDuplicated(formParams);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
session.clientPolicy().triggerOnEvent(new TokenRevokeContext(formParams));
|
session.clientPolicy().triggerOnEvent(new TokenRevokeContext(formParams));
|
||||||
} catch (ClientPolicyException cpe) {
|
} catch (ClientPolicyException cpe) {
|
||||||
|
@ -219,6 +221,14 @@ public class TokenRevocationEndpoint {
|
||||||
event.user(user);
|
event.user(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void checkParameterDuplicated(MultivaluedMap<String, String> formParams) {
|
||||||
|
for (String key : formParams.keySet()) {
|
||||||
|
if (formParams.get(key).size() != 1) {
|
||||||
|
throw new CorsErrorResponseException(cors, Errors.INVALID_REQUEST, "duplicated parameter", Response.Status.BAD_REQUEST);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void revokeClient() {
|
private void revokeClient() {
|
||||||
session.users().revokeConsentForClient(realm, user.getId(), client.getId());
|
session.users().revokeConsentForClient(realm, user.getId(), client.getId());
|
||||||
if (TokenUtil.TOKEN_TYPE_OFFLINE.equals(token.getType())) {
|
if (TokenUtil.TOKEN_TYPE_OFFLINE.equals(token.getType())) {
|
||||||
|
|
|
@ -25,19 +25,30 @@ import static org.junit.Assert.assertTrue;
|
||||||
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
import static org.keycloak.testsuite.admin.AbstractAdminTest.loadJson;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.UnsupportedEncodingException;
|
||||||
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.ws.rs.core.Response.Status;
|
import javax.ws.rs.core.Response.Status;
|
||||||
|
|
||||||
|
import org.apache.commons.io.output.ByteArrayOutputStream;
|
||||||
|
import org.apache.http.NameValuePair;
|
||||||
|
import org.apache.http.client.entity.UrlEncodedFormEntity;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.jboss.arquillian.graphene.page.Page;
|
import org.jboss.arquillian.graphene.page.Page;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.keycloak.OAuth2Constants;
|
import org.keycloak.OAuth2Constants;
|
||||||
|
import org.keycloak.OAuthErrorException;
|
||||||
import org.keycloak.admin.client.resource.RealmResource;
|
import org.keycloak.admin.client.resource.RealmResource;
|
||||||
import org.keycloak.admin.client.resource.UserResource;
|
import org.keycloak.admin.client.resource.UserResource;
|
||||||
|
import org.keycloak.representations.idm.OAuth2ErrorRepresentation;
|
||||||
import org.keycloak.representations.idm.RealmRepresentation;
|
import org.keycloak.representations.idm.RealmRepresentation;
|
||||||
import org.keycloak.representations.idm.UserSessionRepresentation;
|
import org.keycloak.representations.idm.UserSessionRepresentation;
|
||||||
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
import org.keycloak.representations.oidc.TokenMetadataRepresentation;
|
||||||
|
@ -211,6 +222,22 @@ public class TokenRevocationTest extends AbstractKeycloakTest {
|
||||||
isTokenDisabled(tokenResponse, "test-app");
|
isTokenDisabled(tokenResponse, "test-app");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KEYCLOAK-17300
|
||||||
|
@Test
|
||||||
|
public void testRevokeRequestParamsMoreThanOnce() throws Exception {
|
||||||
|
oauth.clientId("test-app");
|
||||||
|
OAuthClient.AccessTokenResponse tokenResponse = oauth.doGrantAccessTokenRequest("password", "test-user@localhost",
|
||||||
|
"password");
|
||||||
|
|
||||||
|
isTokenEnabled(tokenResponse, "test-app");
|
||||||
|
|
||||||
|
String revokeResponse = doTokenRevokeWithDuplicateParams(tokenResponse.getRefreshToken(), "refresh_token", "password");
|
||||||
|
|
||||||
|
OAuth2ErrorRepresentation errorRep = JsonSerialization.readValue(revokeResponse, OAuth2ErrorRepresentation.class);
|
||||||
|
assertEquals("duplicated parameter", errorRep.getErrorDescription());
|
||||||
|
assertEquals(OAuthErrorException.INVALID_REQUEST, errorRep.getError());
|
||||||
|
}
|
||||||
|
|
||||||
private AccessTokenResponse login(String clientId, String username, String password) {
|
private AccessTokenResponse login(String clientId, String username, String password) {
|
||||||
oauth.clientId(clientId);
|
oauth.clientId(clientId);
|
||||||
oauth.openLoginForm();
|
oauth.openLoginForm();
|
||||||
|
@ -248,4 +275,30 @@ public class TokenRevocationTest extends AbstractKeycloakTest {
|
||||||
TokenMetadataRepresentation rep = JsonSerialization.readValue(introspectionResponse, TokenMetadataRepresentation.class);
|
TokenMetadataRepresentation rep = JsonSerialization.readValue(introspectionResponse, TokenMetadataRepresentation.class);
|
||||||
assertFalse(rep.isActive());
|
assertFalse(rep.isActive());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String doTokenRevokeWithDuplicateParams(String token, String tokenTypeHint, String clientSecret)
|
||||||
|
throws IOException {
|
||||||
|
try (CloseableHttpClient client = HttpClientBuilder.create().build()) {
|
||||||
|
HttpPost post = new HttpPost(oauth.getTokenRevocationUrl());
|
||||||
|
|
||||||
|
List<NameValuePair> parameters = new LinkedList<>();
|
||||||
|
parameters.add(new BasicNameValuePair("token", token));
|
||||||
|
parameters.add(new BasicNameValuePair("token", "foo"));
|
||||||
|
parameters.add(new BasicNameValuePair("token_type_hint", tokenTypeHint));
|
||||||
|
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_ID, oauth.getClientId()));
|
||||||
|
parameters.add(new BasicNameValuePair(OAuth2Constants.CLIENT_SECRET, clientSecret));
|
||||||
|
|
||||||
|
UrlEncodedFormEntity formEntity;
|
||||||
|
try {
|
||||||
|
formEntity = new UrlEncodedFormEntity(parameters, "UTF-8");
|
||||||
|
} catch (UnsupportedEncodingException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
post.setEntity(formEntity);
|
||||||
|
|
||||||
|
ByteArrayOutputStream out = new ByteArrayOutputStream();
|
||||||
|
client.execute(post).getEntity().writeTo(out);
|
||||||
|
return new String(out.toByteArray());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue