Skip to content

Commit

Permalink
Introduce filter_by and deprecate authorized_only (close #579)
Browse files Browse the repository at this point in the history
in the OAuth2ClientList web-service.

Also cleaned up dory access and refresh tokens for confidentialClientId
(see OAuth2TestBase)

Change-Id: I47e2416c0a6ce9fc3f500f5f30e3ca021f984142
  • Loading branch information
margaretha committed Aug 19, 2024
1 parent d802a2f commit 6475502
Show file tree
Hide file tree
Showing 11 changed files with 204 additions and 107 deletions.
2 changes: 2 additions & 0 deletions Changes
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# version 0.74.1-SNAPSHOT

- Switch Docker image to temurin (diewald).
- - Introduce filter_by and deprecate authorized_only in OAuth2
client list (close #579)

# version 0.74

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
*
*/
@JsonInclude(Include.NON_EMPTY)
public class OAuth2ClientInfoDto {
public class OAuth2ClientInfoDto implements Comparable<OAuth2ClientInfoDto>{
@JsonProperty("super")
private boolean isSuper;

Expand Down Expand Up @@ -84,6 +84,12 @@ public OAuth2ClientInfoDto (OAuth2Client client, boolean showAllInfo) throws Kus
}
}
}

@Override
public int compareTo (OAuth2ClientInfoDto o) {
return this.getClientName().compareTo(o.getClientName());
}


public boolean isSuper () {
return isSuper;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import de.ids_mannheim.korap.constant.OAuth2Scope;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.oauth2.dto.OAuth2ClientDto;
import de.ids_mannheim.korap.oauth2.dto.OAuth2ClientInfoDto;
import de.ids_mannheim.korap.oauth2.service.OAuth2ClientService;
Expand All @@ -16,7 +17,6 @@
import de.ids_mannheim.korap.web.filter.APIVersionFilter;
import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
import de.ids_mannheim.korap.web.filter.BlockingFilter;
import de.ids_mannheim.korap.web.filter.DemoFilter;
import de.ids_mannheim.korap.web.filter.DemoUserFilter;
import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
import de.ids_mannheim.korap.web.utils.ResourceFilters;
Expand Down Expand Up @@ -220,7 +220,8 @@ public List<OAuth2ClientInfoDto> listUserClients (
@Context SecurityContext context,
@FormParam("super_client_id") String superClientId,
@FormParam("super_client_secret") String superClientSecret,
@FormParam("authorized_only") boolean authorizedOnly) {
@FormParam("authorized_only") boolean authorizedOnly, // deprecated
@FormParam("filter_by") String filterBy) {

TokenContext tokenContext = (TokenContext) context.getUserPrincipal();
String username = tokenContext.getUsername();
Expand All @@ -230,12 +231,34 @@ public List<OAuth2ClientInfoDto> listUserClients (
OAuth2Scope.LIST_USER_CLIENT);

clientService.verifySuperClient(superClientId, superClientSecret);

List<OAuth2ClientInfoDto> clients = null;

if (authorizedOnly) {
return clientService.listUserAuthorizedClients(username);
clients = clientService.listUserAuthorizedClients(username);
}
else {
return clientService.listUserRegisteredClients(username);
if (filterBy !=null && !filterBy.isEmpty()) {
if (filterBy.equals("authorized_only")) {
clients = clientService.listUserAuthorizedClients(username);
}
else if (filterBy.equals("owned_only")) {
clients = clientService.listUserRegisteredClients(username);
}
else {
throw new KustvaktException(
StatusCodes.UNSUPPORTED_VALUE, "filter_by");
}
}
else {
// clients = clientService.listUserAuthorizedClients(username);
// clients.addAll(clientService.listUserRegisteredClients(username));

clients = clientService.listUserRegisteredClients(username);
}
}

return clients;
}
catch (KustvaktException e) {
throw responseHandler.throwit(e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,9 @@

import java.io.IOException;

import jakarta.ws.rs.client.Entity;
import jakarta.ws.rs.core.Form;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;

import org.apache.http.entity.ContentType;
import org.junit.jupiter.api.Test;

import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.net.HttpHeaders;
import com.nimbusds.oauth2.sdk.GrantType;

import de.ids_mannheim.korap.authentication.http.HttpAuthorizationHandler;
import de.ids_mannheim.korap.config.Attributes;
Expand All @@ -24,6 +17,8 @@
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.utils.JsonUtils;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status;

public class OAuth2AccessTokenTest extends OAuth2TestBase {

Expand All @@ -38,7 +33,7 @@ public OAuth2AccessTokenTest () throws KustvaktException {
.createBasicAuthorizationHeaderValue(confidentialClientId,
clientSecret);
}

@Test
public void testScopeWithSuperClient () throws KustvaktException {
Response response = requestTokenWithDoryPassword(superClientId,
Expand All @@ -65,6 +60,8 @@ public void testCustomScope () throws KustvaktException {
confidentialClientId, clientSecret, code);
JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
String token = node.at("/access_token").asText();
String refreshToken = node.at("/refresh_token").asText();

assertTrue(node.at("/scope").asText()
.contains(OAuth2Scope.VC_INFO.toString()));
// test list vc using the token
Expand All @@ -73,6 +70,11 @@ public void testCustomScope () throws KustvaktException {
assertEquals(Status.OK.getStatusCode(), response.getStatus());
node = JsonUtils.readTree(response.readEntity(String.class));
assertEquals(4, node.size());

revokeToken(token, confidentialClientId, clientSecret,
ACCESS_TOKEN_TYPE);
revokeToken(refreshToken, confidentialClientId, clientSecret,
REFRESH_TOKEN_TYPE);
}

@Test
Expand All @@ -84,9 +86,15 @@ public void testDefaultScope () throws KustvaktException, IOException {
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
String accessToken = node.at("/access_token").asText();
String refreshToken = node.at("/refresh_token").asText();
testScopeNotAuthorized(accessToken);
testScopeNotAuthorize2(accessToken);
testSearchWithOAuth2Token(accessToken);

revokeToken(accessToken, confidentialClientId, clientSecret,
ACCESS_TOKEN_TYPE);
revokeToken(refreshToken, confidentialClientId, clientSecret,
REFRESH_TOKEN_TYPE);
}

private void testScopeNotAuthorized (String accessToken)
Expand Down Expand Up @@ -140,17 +148,14 @@ public void testRevokeAccessTokenConfidentialClient ()
JsonNode node = requestTokenWithAuthorizationCodeAndHeader(
confidentialClientId, code, clientAuthHeader);
String accessToken = node.at("/access_token").asText();
Form form = new Form();
form.param("token", accessToken);
form.param("client_id", confidentialClientId);
form.param("client_secret", "secret");
Response response = target().path(API_VERSION).path("oauth2")
.path("revoke").request()
.header(HttpHeaders.CONTENT_TYPE,
ContentType.APPLICATION_FORM_URLENCODED)
.post(Entity.form(form));
assertEquals(Status.OK.getStatusCode(), response.getStatus());
String refreshToken = node.at("/refresh_token").asText();

revokeToken(accessToken, confidentialClientId, clientSecret,
ACCESS_TOKEN_TYPE);
testSearchWithRevokedAccessToken(accessToken);

revokeToken(refreshToken, confidentialClientId, clientSecret,
REFRESH_TOKEN_TYPE);
}

@Test
Expand All @@ -161,7 +166,7 @@ public void testRevokeAccessTokenPublicClientViaSuperClient ()
publicClientId, "", code);
JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
String accessToken = node.at("/access_token").asText();
testRevokeTokenViaSuperClient(accessToken, userAuthHeader);
revokeTokenViaSuperClient(accessToken, userAuthHeader);
testSearchWithRevokedAccessToken(accessToken);
}

Expand All @@ -174,23 +179,18 @@ public void testAccessTokenAfterRequestRefreshToken ()
confidentialClientId, code, clientAuthHeader);
String accessToken = node.at("/access_token").asText();
String refreshToken = node.at("/refresh_token").asText();
Form form = new Form();
form.param("grant_type", GrantType.REFRESH_TOKEN.toString());
form.param("client_id", confidentialClientId);
form.param("client_secret", "secret");
form.param("refresh_token", refreshToken);
Response response = target().path(API_VERSION).path("oauth2")
.path("token").request()
.header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
.header(HttpHeaders.CONTENT_TYPE,
ContentType.APPLICATION_FORM_URLENCODED)
.post(Entity.form(form));
String entity = response.readEntity(String.class);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
assertTrue(!refreshToken.equals(node.at("/refresh_token").asText()));

node = requestTokenWithRefreshToken(confidentialClientId, clientSecret,
refreshToken);
String newAccessToken = node.at("/access_token").asText();
String newRefreshToken = node.at("/refresh_token").asText();
assertTrue(!refreshToken.equals(newRefreshToken));

testSearchWithRevokedAccessToken(accessToken);
revokeToken(newAccessToken, confidentialClientId, clientSecret,
ACCESS_TOKEN_TYPE);
revokeToken(newRefreshToken, confidentialClientId, clientSecret,
REFRESH_TOKEN_TYPE);
}

@Test
Expand All @@ -201,6 +201,7 @@ public void testRequestAuthorizationWithBearerTokenUnauthorized ()
JsonNode node = requestTokenWithAuthorizationCodeAndHeader(
confidentialClientId, code, clientAuthHeader);
String userAuthToken = node.at("/access_token").asText();
String refreshToken = node.at("/refresh_token").asText();
Response response = requestAuthorizationCode("code",
confidentialClientId, "", "search", "",
"Bearer " + userAuthToken);
Expand All @@ -210,6 +211,11 @@ public void testRequestAuthorizationWithBearerTokenUnauthorized ()
node.at("/errors/0/0").asInt());
assertEquals(node.at("/errors/0/1").asText(),
"Scope authorize is not authorized");

revokeToken(userAuthToken, confidentialClientId, clientSecret,
ACCESS_TOKEN_TYPE);
revokeToken(refreshToken, confidentialClientId, clientSecret,
REFRESH_TOKEN_TYPE);
}

@Test
Expand All @@ -227,5 +233,7 @@ public void testRequestAuthorizationWithBearerToken ()
String code = requestAuthorizationCode(superClientId,
"Bearer " + userAuthToken);
assertNotNull(code);

revokeTokenViaSuperClient(userAuthToken, userAuthHeader);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,7 @@ public void testCleanRevokedTokens () throws KustvaktException {
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
String accessToken = node.at("/access_token").asText();
testRevokeToken(accessToken, publicClientId, null, ACCESS_TOKEN_TYPE);
revokeToken(accessToken, publicClientId, null, ACCESS_TOKEN_TYPE);
int accessTokensAfter = accessDao.retrieveInvalidAccessTokens().size();
assertEquals(accessTokensAfter, accessTokensBefore + 1);
target().path(API_VERSION).path("admin").path("oauth2").path("token")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -78,10 +78,15 @@ public void testRequestTokenAuthorizationConfidential ()
confidentialClientId, clientSecret, code);
String entity = response.readEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
assertNotNull(node.at("/refresh_token").asText());
String token = node.at("/access_token").asText();
String refreshToken = node.at("/refresh_token").asText();
assertEquals(TokenType.BEARER.displayName(),
node.at("/token_type").asText());
assertNotNull(node.at("/expires_in").asText());

revokeToken(token, confidentialClientId, clientSecret,
ACCESS_TOKEN_TYPE);
revokeToken(refreshToken, confidentialClientId, clientSecret,
REFRESH_TOKEN_TYPE);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public void testRegisterConfidentialClient () throws KustvaktException {
assertFalse(clientId.contains("a"));
testListConfidentialClient(username, clientId);
testConfidentialClientInfo(clientId, username);
// testListAllUserClients(username);
testResetConfidentialClientSecret(clientId, clientSecret);
deregisterClient(username, clientId);
}
Expand Down Expand Up @@ -292,7 +293,7 @@ public void testRegisterPublicClient ()
assertNotNull(clientId);
assertTrue(node.at("/client_secret").isMissingNode());

node = listUserClients(username);
node = listUserClients(username,"owned_only");
assertFalse(node.at("/0/client_redirect_uri").isMissingNode());
assertFalse(node.at("/0/registration_date").isMissingNode());
assertEquals(username,
Expand Down Expand Up @@ -508,7 +509,10 @@ private String testResetConfidentialClientSecret (String clientId,
private void testListAuthorizedClients (String userAuthHeader)
throws KustvaktException {
Form form = getSuperClientForm();
form.param("authorized_only", "true");
// deprecated, use filter_by = authorized_only instead
// form.param("authorized_only", "true");

form.param("filter_by", "authorized_only");
Response response = target().path(API_VERSION).path("oauth2")
.path("client").path("list").request()
.header(Attributes.AUTHORIZATION, userAuthHeader)
Expand All @@ -535,7 +539,7 @@ public void testListPublicClient () throws KustvaktException {
OAuth2ClientJson json = createOAuth2ClientJson(clientName,
OAuth2ClientType.PUBLIC, "Dory's client.");
registerClient("dory", json);
JsonNode node = listUserClients("dory");
JsonNode node = listUserClients("dory","owned_only");
assertEquals(1, node.size());
assertEquals(clientName, node.at("/0/client_name").asText());
assertEquals(OAuth2ClientType.PUBLIC.name(),
Expand All @@ -544,12 +548,16 @@ public void testListPublicClient () throws KustvaktException {
assertFalse(node.at("/0/registration_date").isMissingNode());
assertTrue(node.at("/refresh_token_expiry").isMissingNode());
String clientId = node.at("/0/client_id").asText();

// testListAllUserClients("dory");
testDeregisterPublicClient(clientId, "dory");
}

private void testListConfidentialClient (String username, String clientId)
throws ProcessingException, KustvaktException {
JsonNode node = listUserClients(username);
// means authorized_only = false
// this is deprecated, filter_by = owned_only should be use instead.
JsonNode node = listUserClients(username,"");
assertEquals(1, node.size());
assertEquals(clientId, node.at("/0/client_id").asText());
assertEquals(node.at("/0/client_name").asText(), "OAuth2ClientTest");
Expand All @@ -566,8 +574,27 @@ private void testListConfidentialClient (String username, String clientId)
assertTrue(node.at("/0/source").isMissingNode());
}

// not ready until the behavior for (filterBy=null || filterBy.isEmpty) is set
private void testListAllUserClients (String username) throws KustvaktException {
// authorize
String userAuthHeader = HttpAuthorizationHandler
.createBasicAuthorizationHeaderValue(username, "password");
String code = requestAuthorizationCode(confidentialClientId, userAuthHeader);
Response response = requestTokenWithAuthorizationCodeAndForm(
confidentialClientId, this.clientSecret, code);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(response.readEntity(String.class));
String accessToken = node.at("/access_token").asText();

node = listUserClients(username,null);
assertEquals(2, node.size());

testRevokeAllTokenViaSuperClient(confidentialClientId, userAuthHeader,
accessToken);
}

@Test
public void testListUserClients () throws KustvaktException {
public void testListAuthorizedUserClients () throws KustvaktException {
String username = "pearl";
String password = "pwd";
userAuthHeader = HttpAuthorizationHandler
Expand Down
Loading

0 comments on commit 6475502

Please sign in to comment.