Added revocation of access tokens and authorization code when
deregistering clients, and implemented reset client secret.
Change-Id: Ice92e3759678eac4d2322ff65a0997a003de357c
diff --git a/core/src/main/java/de/ids_mannheim/korap/config/KustvaktCacheable.java b/core/src/main/java/de/ids_mannheim/korap/config/KustvaktCacheable.java
index e905192..dfe6443 100644
--- a/core/src/main/java/de/ids_mannheim/korap/config/KustvaktCacheable.java
+++ b/core/src/main/java/de/ids_mannheim/korap/config/KustvaktCacheable.java
@@ -1,5 +1,8 @@
package de.ids_mannheim.korap.config;
+import java.io.InputStream;
+import java.util.Map;
+
import de.ids_mannheim.korap.utils.ServiceInfo;
import de.ids_mannheim.korap.utils.StringUtils;
import net.sf.ehcache.Cache;
@@ -9,8 +12,6 @@
import net.sf.ehcache.config.PersistenceConfiguration;
import net.sf.ehcache.store.MemoryStoreEvictionPolicy;
-import java.io.InputStream;
-
/**
* @author hanl
* @date 03/02/2016
@@ -105,4 +106,9 @@
private String createKey(String input) {
return StringUtils.toSHAHash(this.prefix+ "@" + input);
}
+
+ public Map<Object, Element> getAllCacheElements () {
+ Cache cache = getCache(name);
+ return cache.getAll(cache.getKeysWithExpiryCheck());
+ }
}
diff --git a/full/Changes b/full/Changes
index bba0520..ed71a4d 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,28 +1,46 @@
-version 0.61.0
-03/08/2018
- - Added VC referencing tests (margaretha)
- - Implemented loading and caching named VCs (margaretha)
- - Implemented OAuth2 revoke token (margaretha)
- - Updated OAuth2 refresh token implementation (margaretha)
+# version 0.61.0
-version 0.60.5
02/08/2018
+ - Added VC referencing tests (margaretha)
+ - Implemented loading and caching named VCs (margaretha)
+03/08/2018
+ - Implemented OAuth2 revoke token (margaretha)
+ - Updated OAuth2 refresh token implementation (margaretha)
+14/08/2018
+ - Disallow OAuth2 access token (type Bearer) usage for authentication
+ in OAuth2 controllers (margaretha)
+ - Implemented revoke all OAuth2 access tokens and authorization codes of client
+ users when deregistering/deleting a client (margaretha)
+ - Fixed update OAuth2 access token (margaretha)
+ - Implemented reset client secret (margaretha)
+
+
+# version 0.60.5
+
+09/07/2018
- Added service layer to the search controller (margaretha)
- Added OAuth2 scope checking in search and VC controllers (margaretha)
- Added handling OAuth2 bearer token for VC access and User group controllers (margaretha)
- Added default scope to password grant (margaretha)
+10/07/2018
- Made createBasicAuthorizationHeaderValue static (margaretha)
- Added store access token in openID token service (margaretha)
- Fixed empty scope in openID authorization and token service (margaretha)
- Implemented storing authorization code in cache (margaretha)
+11/07/2018
- Fixed authentication time in authentication controller (margaretha)
- Added OAuth2 access token tests (margaretha)
+12/07/2018
- Updated maven surefire setting for faster test suite runtime (margaretha)
- Implemented refreshing OAuth2 access token (margaretha)
+26/07/2018
- Fixed issue #27 (margaretha)
+02/08/2018
- Fixed clientId encoding in OAuth2ClientControllerTest (margaretha)
+
-version 0.60.4
+# version 0.60.4
+
05/07/2018
- implemented OAuth2 authorization code request with OpenID Authentication (margaretha)
- enabled OAuth2 authorization without OpenID authentication using Nimbus library (margaretha)
@@ -49,7 +67,8 @@
- added OAuth2 token request with client authentication via Authorization header (margaretha)
- added port checking in test suite (margaretha)
-version 0.60.3
+# version 0.60.3
+
06/06/2018
- improved user authentication by using authentication filter for authorization code request (margaretha)
- limited client authentication to client id checking in authorization code request (margaretha)
@@ -60,7 +79,8 @@
- added database tables for MySQL (margaretha)
- updated JWT library and related codes (margaretha)
-version 0.60.2
+# version 0.60.2
+
03/05/2018
- implemented OAuth2 client registration (margaretha)
- implemented OAuth2 client authentication (margaretha)
@@ -84,7 +104,8 @@
- added access scopes handling (margaretha)
- added tests about request token with authorization code (margaretha)
-version 0.60.1
+# version 0.60.1
+
28/03/2018
- added admin-related SQL codes (margaretha)
- updated AdminDao (margaretha)
@@ -106,7 +127,8 @@
- added member role removal after deleting members (margaretha)
- added add and delete member role controllers (margaretha)
-version 0.60
+# version 0.60
+
14/03/2018
- set up mail settings using localhost port 25 (margaretha)
- added mail template in kustvakt configuration (margaretha)
@@ -122,7 +144,8 @@
- fixed member invitation to join deleted group (margaretha)
- added checking deleted group (margaretha)
-version 0.59.10
+# version 0.59.10
+
20/02/2018
- added sort VC by id (margaretha)
- added test cases regarding VC sharing (margaretha)
@@ -145,7 +168,8 @@
- fixed unrecognized application/json (margaretha)
- fixed and updated velocity template (margaretha)
-version 0.59.9
+# version 0.59.9
+
19/01/2018
- restructured basic authentication (margaretha)
- fixed AuthenticationException to include authentication scheme (margaretha)
@@ -172,7 +196,8 @@
- removed PredefinedUserGroup.ALL and related codes (margaretha)
- implemented search for published VC (margaretha)
-version 0.59.8
+# version 0.59.8
+
21/09/2017
- restructured statistics service (margaretha)
- removed deprecated loader codes and tests (margaretha)
@@ -191,7 +216,8 @@
- implemented create/store VC (margaretha)
- fixed collection rewrite bug regarding availability with operation or (margaretha)
-version 0.59.7
+# version 0.59.7
+
13/10/2016
- MOD: updated search to use new siglen (diewald)
- MOD: fixed matchinfo retrieval in light service (diewald)
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
index 67503e1..5564ee7 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AccessTokenDao.java
@@ -74,6 +74,18 @@
entityManager.persist(accessToken);
}
+ public AccessToken updateAccessToken (AccessToken accessToken)
+ throws KustvaktException {
+ ParameterChecker.checkObjectValue(accessToken, "access_token");
+ AccessToken cachedToken =
+ (AccessToken) this.getCacheValue(accessToken.getToken());
+ if (cachedToken != null) {
+ this.removeCacheEntry(accessToken.getToken());
+ }
+
+ accessToken = entityManager.merge(accessToken);
+ return accessToken;
+ }
public AccessToken retrieveAccessToken (String accessToken)
throws KustvaktException {
@@ -121,19 +133,6 @@
return q.getResultList();
}
- public AccessToken updateAccessToken (AccessToken accessToken)
- throws KustvaktException {
- ParameterChecker.checkObjectValue(accessToken, "access_token");
- AccessToken cachedToken =
- (AccessToken) this.getCacheValue(accessToken.getId());
- if (cachedToken != null) {
- this.removeCacheEntry(cachedToken);
- }
-
- accessToken = entityManager.merge(accessToken);
- return accessToken;
- }
-
public AccessToken retrieveAccessTokenByAnynomousToken (String token)
throws KustvaktException {
ParameterChecker.checkObjectValue(token, "token");
@@ -165,4 +164,16 @@
"Access token is not found", OAuth2Error.INVALID_TOKEN);
}
}
+
+ @SuppressWarnings("unchecked")
+ public List<AccessToken> retrieveAccessTokenByClientId (String clientId) {
+ CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery<AccessToken> query =
+ builder.createQuery(AccessToken.class);
+ Root<AccessToken> root = query.from(AccessToken.class);
+ query.select(root);
+ query.where(builder.equal(root.get(AccessToken_.clientId), clientId));
+ Query q = entityManager.createQuery(query);
+ return q.getResultList();
+ }
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationCacheDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationCacheDao.java
index eed72d4..af83e16 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationCacheDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationCacheDao.java
@@ -1,6 +1,9 @@
package de.ids_mannheim.korap.oauth2.dao;
import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
import de.ids_mannheim.korap.config.KustvaktCacheable;
@@ -11,6 +14,7 @@
import de.ids_mannheim.korap.oauth2.entity.Authorization;
import de.ids_mannheim.korap.oauth2.interfaces.AuthorizationDaoInterface;
import de.ids_mannheim.korap.utils.ParameterChecker;
+import net.sf.ehcache.Element;
public class AuthorizationCacheDao extends KustvaktCacheable
implements AuthorizationDaoInterface {
@@ -69,4 +73,19 @@
return auth;
}
+ @Override
+ public List<Authorization> retrieveAuthorizationsByClientId (
+ String clientId) {
+ List<Authorization> authList = new ArrayList<>();
+
+ Map<Object, Element> map = getAllCacheElements();
+ for (Object key : map.keySet()){
+ Authorization auth = (Authorization) map.get(key).getObjectValue();
+ if (auth.getClientId().equals(clientId)){
+ authList.add(auth);
+ }
+ }
+ return authList;
+ }
+
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java
index c62b130..ff82d9c 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/AuthorizationDao.java
@@ -1,6 +1,7 @@
package de.ids_mannheim.korap.oauth2.dao;
import java.time.ZonedDateTime;
+import java.util.List;
import java.util.Set;
import javax.persistence.EntityManager;
@@ -25,14 +26,15 @@
@Transactional
@Repository
-public class AuthorizationDao implements AuthorizationDaoInterface{
+public class AuthorizationDao implements AuthorizationDaoInterface {
@PersistenceContext
private EntityManager entityManager;
public Authorization storeAuthorizationCode (String clientId, String userId,
String code, Set<AccessScope> scopes, String redirectURI,
- ZonedDateTime authenticationTime, String nonce) throws KustvaktException {
+ ZonedDateTime authenticationTime, String nonce)
+ throws KustvaktException {
ParameterChecker.checkStringValue(clientId, "client_id");
ParameterChecker.checkStringValue(userId, "userId");
ParameterChecker.checkStringValue(code, "authorization code");
@@ -85,4 +87,21 @@
authorization = entityManager.merge(authorization);
return authorization;
}
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public List<Authorization> retrieveAuthorizationsByClientId (String clientId) {
+ CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery<Authorization> query =
+ builder.createQuery(Authorization.class);
+ Root<Authorization> root = query.from(Authorization.class);
+
+ Predicate restrictions =
+ builder.equal(root.get(Authorization_.clientId), clientId);
+
+ query.select(root);
+ query.where(restrictions);
+ Query q = entityManager.createQuery(query);
+ return q.getResultList();
+ }
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
index 78c438c..2b8a2eb 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/OAuth2ClientDao.java
@@ -84,11 +84,17 @@
}
}
- public void deregisterClient (OAuth2Client client) {
+ public void deregisterClient (OAuth2Client client) throws KustvaktException {
+ ParameterChecker.checkObjectValue(client, "client");
if (!entityManager.contains(client)) {
client = entityManager.merge(client);
}
entityManager.remove(client);
}
+ public void updateClient (OAuth2Client client) throws KustvaktException {
+ ParameterChecker.checkObjectValue(client, "client");
+ client = entityManager.merge(client);
+ }
+
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/interfaces/AuthorizationDaoInterface.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/interfaces/AuthorizationDaoInterface.java
index f9c7280..8dcde58 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/interfaces/AuthorizationDaoInterface.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/interfaces/AuthorizationDaoInterface.java
@@ -1,6 +1,7 @@
package de.ids_mannheim.korap.oauth2.interfaces;
import java.time.ZonedDateTime;
+import java.util.List;
import java.util.Set;
import de.ids_mannheim.korap.exceptions.KustvaktException;
@@ -18,4 +19,6 @@
public Authorization updateAuthorization (Authorization authorization)
throws KustvaktException;
+
+ public List<Authorization> retrieveAuthorizationsByClientId (String clientId);
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
index 8b3a639..53027f4 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ClientService.java
@@ -5,6 +5,7 @@
import java.net.URISyntaxException;
import java.net.URL;
import java.sql.SQLException;
+import java.util.List;
import org.apache.commons.validator.routines.UrlValidator;
import org.springframework.beans.factory.annotation.Autowired;
@@ -19,8 +20,12 @@
import de.ids_mannheim.korap.interfaces.EncryptionIface;
import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
import de.ids_mannheim.korap.oauth2.dao.OAuth2ClientDao;
+import de.ids_mannheim.korap.oauth2.entity.AccessToken;
+import de.ids_mannheim.korap.oauth2.entity.Authorization;
import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
+import de.ids_mannheim.korap.oauth2.interfaces.AuthorizationDaoInterface;
import de.ids_mannheim.korap.web.input.OAuth2ClientJson;
/**
@@ -46,6 +51,10 @@
@Autowired
private OAuth2ClientDao clientDao;
@Autowired
+ private AccessTokenDao tokenDao;
+ @Autowired
+ private AuthorizationDaoInterface authorizationDao;
+ @Autowired
private AdminDao adminDao;
@Autowired
private UrlValidator redirectURIValidator;
@@ -172,7 +181,52 @@
if (adminDao.isAdmin(username)
|| client.getRegisteredBy().equals(username)) {
+
clientDao.deregisterClient(client);
+
+ // revoke all related authorization tokens
+ List<Authorization> authList = authorizationDao
+ .retrieveAuthorizationsByClientId(clientId);
+ for (Authorization authorization : authList){
+ authorization.setRevoked(true);
+ authorizationDao.updateAuthorization(authorization);
+ }
+
+ // revoke all related access tokens
+ List<AccessToken> tokens =
+ tokenDao.retrieveAccessTokenByClientId(clientId);
+ for (AccessToken token : tokens) {
+ token.setRevoked(true);
+ token.setRefreshTokenRevoked(true);
+ tokenDao.updateAccessToken(token);
+ }
+ }
+ else {
+ throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
+ "Unauthorized operation for user: " + username, username);
+ }
+ }
+
+ public OAuth2ClientDto resetSecret (String clientId, String clientSecret,
+ String username) throws KustvaktException {
+
+ OAuth2Client client = authenticateClient(clientId, clientSecret);
+ if (!client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
+ throw new KustvaktException(
+ StatusCodes.NOT_ALLOWED,
+ "Operation is not allowed for public clients",
+ OAuth2Error.INVALID_REQUEST);
+ }
+ if (adminDao.isAdmin(username)
+ || client.getRegisteredBy().equals(username)) {
+
+ String secret = codeGenerator.createRandomCode();
+ String secretHashcode = encryption.secureHash(secret,
+ config.getPasscodeSaltField());
+
+ client.setSecret(secretHashcode);
+ clientDao.updateClient(client);
+ return new OAuth2ClientDto(clientId, secret);
}
else {
throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
@@ -197,15 +251,7 @@
public void authenticateClient (OAuth2Client client, String clientSecret)
throws KustvaktException {
- if (clientSecret == null) {
- if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
- throw new KustvaktException(
- StatusCodes.CLIENT_AUTHENTICATION_FAILED,
- "Missing parameters: client_secret",
- OAuth2Error.INVALID_REQUEST);
- }
- }
- else if (clientSecret.isEmpty()) {
+ if (clientSecret == null || clientSecret.isEmpty()) {
if (client.getType().equals(OAuth2ClientType.CONFIDENTIAL)) {
throw new KustvaktException(
StatusCodes.CLIENT_AUTHENTICATION_FAILED,
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
index e78aa2a..61f8237 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuth2Controller.java
@@ -24,7 +24,6 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
-import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.spi.container.ResourceFilters;
import de.ids_mannheim.korap.exceptions.KustvaktException;
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
index 61295e7..293685e 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthClientController.java
@@ -29,8 +29,8 @@
/**
* Defines controllers for OAuth2 clients, namely applications
- * attempting
- * to access users' resources.
+ * performing actions such as searching and retrieving match
+ * information on behalf of users.
*
* @author margaretha
*
@@ -117,4 +117,35 @@
throw responseHandler.throwit(e);
}
}
+
+ /**
+ * Resets client secret of the given client. This controller
+ * requires client owner and client authentication. Only
+ * confidential clients are issued client secrets.
+ *
+ * @param securityContext
+ * @param clientId
+ * @param clientSecret
+ * @return a new client secret
+ */
+ @POST
+ @Path("reset")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+ @ResourceFilters({ AuthenticationFilter.class, BlockingFilter.class })
+ public OAuth2ClientDto resetClientSecret (
+ @Context SecurityContext securityContext,
+ @FormParam("client_id") String clientId,
+ @FormParam("client_secret") String clientSecret) {
+ TokenContext context =
+ (TokenContext) securityContext.getUserPrincipal();
+ try {
+ return clientService.resetSecret(clientId, clientSecret,
+ context.getUsername());
+ }
+ catch (KustvaktException e) {
+ throw responseHandler.throwit(e);
+ }
+ }
+
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java b/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
index 88603cc..d0a6cf6 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/filter/AuthenticationFilter.java
@@ -75,7 +75,8 @@
context = authenticationManager.getTokenContext(
TokenType.BEARER, authData.getToken(), host,
ua);
- if (request.getPath().startsWith("vc/access")
+ if (request.getPath().startsWith("oauth2")
+ || request.getPath().startsWith("vc/access")
|| request.getPath().startsWith("vc/delete")
|| request.getPath().startsWith("group")
|| request.getPath().startsWith("user")) {
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
index b5f89a5..ffecbb3 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ClientControllerTest.java
@@ -4,6 +4,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import java.net.URI;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
@@ -12,6 +13,8 @@
import org.apache.http.entity.ContentType;
import org.junit.Test;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.util.UriComponentsBuilder;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.net.HttpHeaders;
@@ -52,7 +55,7 @@
}
}
- private ClientResponse testRegisterConfidentialClient ()
+ private ClientResponse registerConfidentialClient ()
throws KustvaktException {
OAuth2ClientJson json = new OAuth2ClientJson();
@@ -71,8 +74,8 @@
}
@Test
- public void testRegisterClientNonUniqueURL () throws KustvaktException {
- ClientResponse response = testRegisterConfidentialClient();
+ public void testRegisterConfidentialClient () throws KustvaktException {
+ ClientResponse response = registerConfidentialClient();
String entity = response.getEntity(String.class);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(entity);
@@ -81,14 +84,22 @@
assertNotNull(clientId);
assertNotNull(clientSecret);
- response = testRegisterConfidentialClient();
- assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
- node = JsonUtils.readTree(response.getEntity(String.class));
- assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ testRegisterClientNonUniqueURL();
+
+ String newclientSecret =
+ testResetConfidentialClientSecret(clientId, clientSecret);
testDeregisterConfidentialClientMissingSecret(clientId);
- testDeregisterClientIncorrectCredentials(clientId);
- testDeregisterConfidentialClient(clientId, clientSecret);
+ testDeregisterClientIncorrectCredentials(clientId, clientSecret);
+ testDeregisterConfidentialClient(clientId, newclientSecret);
+ }
+
+
+ private void testRegisterClientNonUniqueURL () throws KustvaktException {
+ ClientResponse response = registerConfidentialClient();
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
}
@Test
@@ -122,29 +133,6 @@
}
@Test
- public void testRegisterNativeClient () throws UniformInterfaceException,
- ClientHandlerException, KustvaktException {
- OAuth2ClientJson json = new OAuth2ClientJson();
- json.setName("NativeClient");
- json.setType(OAuth2ClientType.PUBLIC);
- json.setUrl("http://korap.ids-mannheim.de/native");
- json.setRedirectURI("https://korap.ids-mannheim.de/native/redirect");
- json.setDescription("This is a native test client.");
-
- ClientResponse response = resource().path("oauth2").path("client")
- .path("register")
- .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
- .createBasicAuthorizationHeaderValue(username, "pass"))
- .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
- .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
- .entity(json).post(ClientResponse.class);
-
- assertEquals(Status.OK.getStatusCode(), response.getStatus());
-
- // EM: need to check native
- }
-
- @Test
public void testRegisterDesktopApp () throws UniformInterfaceException,
ClientHandlerException, KustvaktException {
OAuth2ClientJson json = new OAuth2ClientJson();
@@ -166,6 +154,117 @@
String clientId = node.at("/client_id").asText();
assertNotNull(clientId);
assertTrue(node.at("/client_secret").isMissingNode());
+
+ testResetPublicClientSecret(clientId);
+ }
+
+ @Test
+ public void testRegisterNativeClient () throws UniformInterfaceException,
+ ClientHandlerException, KustvaktException {
+ OAuth2ClientJson json = new OAuth2ClientJson();
+ json.setName("NativeClient");
+ json.setType(OAuth2ClientType.PUBLIC);
+ json.setUrl("http://korap.ids-mannheim.de/native");
+ json.setRedirectURI("https://korap.ids-mannheim.de/native/redirect");
+ json.setDescription("This is a native test client.");
+
+ ClientResponse response = resource().path("oauth2").path("client")
+ .path("register")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE, ContentType.APPLICATION_JSON)
+ .entity(json).post(ClientResponse.class);
+
+ String entity = response.getEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ String clientId = node.at("/client_id").asText();
+
+ // EM: need to check native
+
+ testAccessTokenAfterDeregistration(clientId);
+ }
+
+ private void testAccessTokenAfterDeregistration (String clientId)
+ throws KustvaktException {
+ String code = requestAuthorizationCode(clientId, "");
+ ClientResponse response = requestAccessToken(code, clientId, "");
+ JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+ String accessToken = node.at("/access_token").asText();
+
+ response = searchWithAccessToken(accessToken);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+ code = requestAuthorizationCode(clientId, "");
+ testDeregisterPublicClient(clientId);
+
+ response = requestAccessToken(code, clientId, "");
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(response.getEntity(String.class));
+ assertEquals(OAuth2Error.INVALID_CLIENT.toString(),
+ node.at("/error").asText());
+
+ response = searchWithAccessToken(accessToken);
+ assertEquals(Status.UNAUTHORIZED.getStatusCode(), response.getStatus());
+ node = JsonUtils.readTree(response.getEntity(String.class));
+ assertEquals(StatusCodes.INVALID_ACCESS_TOKEN,
+ node.at("/errors/0/0").asInt());
+ assertEquals("Access token has been revoked",
+ node.at("/errors/0/1").asText());
+
+ }
+
+ private String requestAuthorizationCode (String clientId,
+ String clientSecret) throws UniformInterfaceException,
+ ClientHandlerException, KustvaktException {
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("response_type", "code");
+ form.add("client_id", clientId);
+ form.add("client_secret", clientSecret);
+
+ ClientResponse response = resource().path("oauth2").path("authorize")
+ .header(Attributes.AUTHORIZATION,
+ HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue("dory",
+ "password"))
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .entity(form).post(ClientResponse.class);
+
+ assertEquals(Status.TEMPORARY_REDIRECT.getStatusCode(),
+ response.getStatus());
+ URI redirectUri = response.getLocation();
+ MultiValueMap<String, String> params = UriComponentsBuilder
+ .fromUri(redirectUri).build().getQueryParams();
+ return params.getFirst("code");
+ }
+
+ private ClientResponse requestAccessToken (String code, String clientId,
+ String clientSecret) throws KustvaktException {
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("grant_type", "authorization_code");
+ form.add("client_id", clientId);
+ form.add("client_secret", clientSecret);
+ form.add("code", code);
+
+ ClientResponse response = resource().path("oauth2").path("token")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .entity(form).post(ClientResponse.class);
+
+ return response;
+ }
+
+ private ClientResponse searchWithAccessToken (String accessToken) {
+ ClientResponse response = resource().path("search")
+ .queryParam("q", "Wasser").queryParam("ql", "poliqarp")
+ .header(Attributes.AUTHORIZATION, "Bearer " + accessToken)
+ .header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
+ .get(ClientResponse.class);
+
+ return response;
}
private void testDeregisterPublicClientMissingUserAuthentication (
@@ -247,12 +346,12 @@
node.at("/error_description").asText());
}
- private void testDeregisterClientIncorrectCredentials (String clientId)
- throws UniformInterfaceException, ClientHandlerException,
- KustvaktException {
+ private void testDeregisterClientIncorrectCredentials (String clientId,
+ String clientSecret) throws UniformInterfaceException,
+ ClientHandlerException, KustvaktException {
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
- form.add("client_secret", "xxx");
+ form.add("client_secret", clientSecret);
ClientResponse response = resource().path("oauth2").path("client")
.path("deregister").path(clientId)
@@ -272,4 +371,53 @@
checkWWWAuthenticateHeader(response);
}
+
+ private void testResetPublicClientSecret (String clientId)
+ throws UniformInterfaceException, ClientHandlerException,
+ KustvaktException {
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("client_id", clientId);
+
+ ClientResponse response = resource().path("oauth2").path("client")
+ .path("reset")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .entity(form).post(ClientResponse.class);
+
+ String entity = response.getEntity(String.class);
+ assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(OAuth2Error.INVALID_REQUEST, node.at("/error").asText());
+ assertEquals("Operation is not allowed for public clients",
+ node.at("/error_description").asText());
+ }
+
+ private String testResetConfidentialClientSecret (String clientId,
+ String clientSecret) throws UniformInterfaceException,
+ ClientHandlerException, KustvaktException {
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("client_id", clientId);
+ form.add("client_secret", clientSecret);
+
+ ClientResponse response = resource().path("oauth2").path("client")
+ .path("reset")
+ .header(Attributes.AUTHORIZATION, HttpAuthorizationHandler
+ .createBasicAuthorizationHeaderValue(username, "pass"))
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .entity(form).post(ClientResponse.class);
+
+ String entity = response.getEntity(String.class);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+ JsonNode node = JsonUtils.readTree(entity);
+ assertEquals(clientId, node.at("/client_id").asText());
+
+ String newClientSecret = node.at("/client_secret").asText();
+ assertTrue(!clientSecret.equals(newClientSecret));
+
+ return newClientSecret;
+ }
}