Added a config properties for a long-time access token expiry and
excluded refresh tokens for public clients in OAuth2 token responses.
Change-Id: Ie1cbf65bc605ab93202642030db9a1893a1cc9a8
diff --git a/full/Changes b/full/Changes
index cda6540..ff981f6 100644
--- a/full/Changes
+++ b/full/Changes
@@ -3,6 +3,10 @@
- Removed salt from config and updated config files (margaretha)
03/02/2020
- Added an admin API for clearing access token cache (margaretha)
+05/02/2020
+ - Added a config properties for a long-time access token expiry
+ and excluded refresh tokens for public clients in OAuth2 token
+ responses (margaretha)
# version 0.62.3
03/12/2019
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
index 8590db1..6da8f2a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/FullConfiguration.java
@@ -70,6 +70,7 @@
private Set<String> clientCredentialsScopes;
private int maxAuthenticationAttempts;
+ private int accessTokenLongExpiry;
private int accessTokenExpiry;
private int refreshTokenExpiry;
private int authorizationCodeExpiry;
@@ -251,6 +252,9 @@
properties.getProperty("oauth2.refresh.token.expiry", "90D"));
authorizationCodeExpiry = TimeUtils.convertTimeToSeconds(properties
.getProperty("oauth2.authorization.code.expiry", "10M"));
+
+ setAccessTokenLongExpiry(TimeUtils.convertTimeToSeconds(
+ properties.getProperty("oauth2.access.token.long.expiry", "365D")));
}
private void setMailConfiguration (Properties properties) {
@@ -636,4 +640,12 @@
public void setNamedVCPath (String namedVCPath) {
this.namedVCPath = namedVCPath;
}
+
+ public int getAccessTokenLongExpiry () {
+ return accessTokenLongExpiry;
+ }
+
+ public void setAccessTokenLongExpiry (int accessTokenLongExpiry) {
+ this.accessTokenLongExpiry = accessTokenLongExpiry;
+ }
}
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 1b1be95..b82819d 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
@@ -12,6 +12,7 @@
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
+import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import org.springframework.beans.factory.annotation.Autowired;
@@ -27,6 +28,7 @@
import de.ids_mannheim.korap.oauth2.entity.AccessScope;
import de.ids_mannheim.korap.oauth2.entity.AccessToken;
import de.ids_mannheim.korap.oauth2.entity.AccessToken_;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
import de.ids_mannheim.korap.utils.ParameterChecker;
@@ -44,6 +46,8 @@
private EntityManager entityManager;
@Autowired
private FullConfiguration config;
+ @Autowired
+ private OAuth2ClientDao clientDao;
public AccessTokenDao () {
super("access_token", "key:access_token");
@@ -53,7 +57,7 @@
Set<AccessScope> scopes, String userId, String clientId,
ZonedDateTime authenticationTime) throws KustvaktException {
ParameterChecker.checkStringValue(token, "access token");
- ParameterChecker.checkObjectValue(refreshToken, "refresh token");
+// ParameterChecker.checkObjectValue(refreshToken, "refresh token");
ParameterChecker.checkObjectValue(scopes, "scopes");
// ParameterChecker.checkStringValue(userId, "username");
ParameterChecker.checkStringValue(clientId, "client_id");
@@ -63,15 +67,25 @@
ZonedDateTime now =
ZonedDateTime.now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE));
+ ZonedDateTime expiry;
AccessToken accessToken = new AccessToken();
+
+ if (refreshToken != null) {
+ accessToken.setRefreshToken(refreshToken);
+ expiry = now.plusSeconds(config.getAccessTokenExpiry());
+ }
+ else {
+ expiry = now.plusSeconds(config.getAccessTokenLongExpiry());
+ }
+
+ OAuth2Client client = clientDao.retrieveClientById(clientId);
+
accessToken.setCreatedDate(now);
- accessToken
- .setExpiryDate(now.plusSeconds(config.getAccessTokenExpiry()));
+ accessToken.setExpiryDate(expiry);
accessToken.setToken(token);
- accessToken.setRefreshToken(refreshToken);
accessToken.setScopes(scopes);
accessToken.setUserId(userId);
- accessToken.setClientId(clientId);
+ accessToken.setClient(client);
accessToken.setUserAuthenticationTime(authenticationTime);
entityManager.persist(accessToken);
}
@@ -115,13 +129,56 @@
}
}
- public List<AccessToken> retrieveAccessTokenByClientId (String clientId) {
+ public AccessToken retrieveAccessToken (String accessToken, String username)
+ throws KustvaktException {
+ ParameterChecker.checkStringValue(accessToken, "access_token");
+ ParameterChecker.checkStringValue(username, "username");
+ AccessToken token = (AccessToken) this.getCacheValue(accessToken);
+ if (token != null) {
+ return token;
+ }
+
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<AccessToken> query =
builder.createQuery(AccessToken.class);
Root<AccessToken> root = query.from(AccessToken.class);
+
+ Predicate condition = builder.and(
+ builder.equal(root.get(AccessToken_.userId), username),
+ builder.equal(root.get(AccessToken_.token), accessToken));
+
query.select(root);
- query.where(builder.equal(root.get(AccessToken_.clientId), clientId));
+ query.where(condition);
+ Query q = entityManager.createQuery(query);
+ try {
+ token = (AccessToken) q.getSingleResult();
+ this.storeInCache(accessToken, token);
+ return token;
+ }
+ catch (NoResultException e) {
+ return null;
+ }
+ }
+
+
+ public List<AccessToken> retrieveAccessTokenByClientId (String clientId,
+ String username) throws KustvaktException {
+ ParameterChecker.checkStringValue(clientId, "client_id");
+ OAuth2Client client = clientDao.retrieveClientById(clientId);
+
+ CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery<AccessToken> query =
+ builder.createQuery(AccessToken.class);
+ Root<AccessToken> root = query.from(AccessToken.class);
+
+ Predicate condition = builder.equal(root.get(AccessToken_.client), client);
+ if (username != null && !username.isEmpty()){
+ condition = builder.and(condition,
+ builder.equal(root.get(AccessToken_.userId), username));
+ }
+
+ query.select(root);
+ query.where(condition);
TypedQuery<AccessToken> 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 ef44bfa..9c421fd 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
@@ -22,13 +22,16 @@
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.oauth2.constant.OAuth2ClientType;
+import de.ids_mannheim.korap.oauth2.entity.AccessToken;
+import de.ids_mannheim.korap.oauth2.entity.AccessToken_;
import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
import de.ids_mannheim.korap.oauth2.entity.OAuth2Client_;
import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
import de.ids_mannheim.korap.oauth2.entity.RefreshToken_;
import de.ids_mannheim.korap.utils.ParameterChecker;
-/** Manages database queries and transactions regarding OAuth2 clients.
+/**
+ * Manages database queries and transactions regarding OAuth2 clients.
*
* @author margaretha
*
@@ -133,6 +136,32 @@
return q.getResultList();
}
+ public List<OAuth2Client> retrieveClientsByAccessTokens (String username)
+ throws KustvaktException {
+ ParameterChecker.checkStringValue(username, "username");
+
+ CriteriaBuilder builder = entityManager.getCriteriaBuilder();
+ CriteriaQuery<OAuth2Client> query =
+ builder.createQuery(OAuth2Client.class);
+
+ Root<OAuth2Client> client = query.from(OAuth2Client.class);
+ Join<OAuth2Client, AccessToken> accessToken =
+ client.join(OAuth2Client_.accessTokens);
+ Predicate condition = builder.and(
+ builder.equal(accessToken.get(AccessToken_.userId), username),
+ builder.equal(accessToken.get(AccessToken_.isRevoked), false),
+ builder.greaterThan(
+ accessToken
+ .<ZonedDateTime> get(AccessToken_.expiryDate),
+ ZonedDateTime
+ .now(ZoneId.of(Attributes.DEFAULT_TIME_ZONE))));
+ query.select(client);
+ query.where(condition);
+ query.distinct(true);
+ TypedQuery<OAuth2Client> q = entityManager.createQuery(query);
+ return q.getResultList();
+ }
+
public List<OAuth2Client> retrieveUserRegisteredClients (String username)
throws KustvaktException {
ParameterChecker.checkStringValue(username, "username");
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/RefreshTokenDao.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/RefreshTokenDao.java
index 71bafb5..0bcc54d 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/RefreshTokenDao.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/dao/RefreshTokenDao.java
@@ -12,7 +12,6 @@
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
-import javax.persistence.criteria.Join;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
@@ -25,7 +24,6 @@
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.oauth2.entity.AccessScope;
import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
-import de.ids_mannheim.korap.oauth2.entity.OAuth2Client_;
import de.ids_mannheim.korap.oauth2.entity.RefreshToken;
import de.ids_mannheim.korap.oauth2.entity.RefreshToken_;
import de.ids_mannheim.korap.utils.ParameterChecker;
@@ -71,7 +69,7 @@
entityManager.persist(token);
return token;
}
-
+
public RefreshToken updateRefreshToken (RefreshToken token)
throws KustvaktException {
ParameterChecker.checkObjectValue(token, "refresh_token");
@@ -83,7 +81,7 @@
public RefreshToken retrieveRefreshToken (String token)
throws KustvaktException {
ParameterChecker.checkStringValue(token, "refresh token");
-
+
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<RefreshToken> query =
builder.createQuery(RefreshToken.class);
@@ -95,22 +93,22 @@
Query q = entityManager.createQuery(query);
return (RefreshToken) q.getSingleResult();
}
-
+
public RefreshToken retrieveRefreshToken (String token, String username)
throws KustvaktException {
-
+
ParameterChecker.checkStringValue(token, "refresh token");
ParameterChecker.checkStringValue(username, "username");
-
+
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<RefreshToken> query =
builder.createQuery(RefreshToken.class);
-
+
Root<RefreshToken> root = query.from(RefreshToken.class);
Predicate condition = builder.and(
builder.equal(root.get(RefreshToken_.userId), username),
builder.equal(root.get(RefreshToken_.token), token));
-
+
query.select(root);
query.where(condition);
TypedQuery<RefreshToken> q = entityManager.createQuery(query);
@@ -122,18 +120,25 @@
}
}
- public List<RefreshToken> retrieveRefreshTokenByClientId (String clientId)
- throws KustvaktException {
+ public List<RefreshToken> retrieveRefreshTokenByClientId (String clientId,
+ String username) throws KustvaktException {
ParameterChecker.checkStringValue(clientId, "client_id");
-
+ OAuth2Client client = clientDao.retrieveClientById(clientId);
+
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<RefreshToken> query =
builder.createQuery(RefreshToken.class);
Root<RefreshToken> root = query.from(RefreshToken.class);
- Join<RefreshToken, OAuth2Client> client =
- root.join(RefreshToken_.client);
+
+ Predicate condition =
+ builder.equal(root.get(RefreshToken_.client), client);
+ if (username != null && !username.isEmpty()) {
+ condition = builder.and(condition,
+ builder.equal(root.get(RefreshToken_.userId), username));
+ }
+
query.select(root);
- query.where(builder.equal(client.get(OAuth2Client_.id), clientId));
+ query.where(condition);
TypedQuery<RefreshToken> q = entityManager.createQuery(query);
return q.getResultList();
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java
index a94b081..17ee77d 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/AccessToken.java
@@ -45,8 +45,8 @@
private ZonedDateTime expiryDate;
@Column(name = "user_id")
private String userId;
- @Column(name = "client_id")
- private String clientId;
+// @Column(name = "client_id")
+// private String clientId;
@Column(name = "is_revoked")
private boolean isRevoked;
@Column(name = "user_auth_time", updatable = false)
@@ -69,4 +69,8 @@
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "refresh_token")
private RefreshToken refreshToken;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "client")
+ private OAuth2Client client;
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
index 6db32b1..1d7b200 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/entity/OAuth2Client.java
@@ -44,6 +44,9 @@
@OneToMany(fetch = FetchType.LAZY, mappedBy = "client")
private List<RefreshToken> refreshTokens;
+ @OneToMany(fetch = FetchType.LAZY, mappedBy = "client")
+ private List<AccessToken> accessTokens;
+
@Override
public String toString () {
return "id=" + id + ", name=" + name + ", secret=" + secret + ", type="
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java
index dcbcafe..bbf7f04 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/oltu/service/OltuTokenService.java
@@ -125,7 +125,7 @@
OAuth2Error.INVALID_REQUEST);
}
- clientService.authenticateClient(clientId, clientSecret);
+ OAuth2Client oAuth2Client = clientService.authenticateClient(clientId, clientSecret);
RefreshToken refreshToken;
try {
@@ -166,7 +166,8 @@
return createsAccessTokenResponse(scopes, requestedScopes, clientId,
refreshToken.getUserId(),
- refreshToken.getUserAuthenticationTime());
+ refreshToken.getUserAuthenticationTime(),
+ clientService.isPublicClient(oAuth2Client));
// without new refresh token
// return createsAccessTokenResponse(scopes, requestedScopes,
@@ -201,10 +202,11 @@
Set<String> scopes = scopeService
.convertAccessScopesToStringSet(authorization.getScopes());
+ OAuth2Client oAuth2Client = clientService.retrieveClient(clientId);
return createsAccessTokenResponse(scopes, authorization.getScopes(),
authorization.getClientId(), authorization.getUserId(),
- authorization.getUserAuthenticationTime());
-
+ authorization.getUserAuthenticationTime(),
+ clientService.isPublicClient(oAuth2Client));
}
/**
@@ -261,7 +263,8 @@
Set<AccessScope> accessScopes =
scopeService.convertToAccessScope(scopes);
return createsAccessTokenResponse(scopes, accessScopes, clientId,
- username, authenticationTime);
+ username, authenticationTime,
+ false);
}
/**
@@ -290,7 +293,7 @@
}
// OAuth2Client client =
- clientService.authenticateClient(clientId, clientSecret);
+ OAuth2Client oAuth2Client = clientService.authenticateClient(clientId, clientSecret);
// if (!client.isNative()) {
// throw new KustvaktException(
@@ -308,7 +311,7 @@
Set<AccessScope> accessScopes =
scopeService.convertToAccessScope(scopes);
return createsAccessTokenResponse(scopes, accessScopes, clientId, null,
- authenticationTime);
+ authenticationTime,clientService.isPublicClient(oAuth2Client));
}
/**
@@ -339,14 +342,20 @@
*/
private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
Set<AccessScope> accessScopes, String clientId, String userId,
- ZonedDateTime authenticationTime)
+ ZonedDateTime authenticationTime, boolean isPublicClient)
throws OAuthSystemException, KustvaktException {
String random = randomGenerator.createRandomCode();
- RefreshToken refreshToken = refreshDao.storeRefreshToken(random, userId,
- authenticationTime, clientId, accessScopes);
- return createsAccessTokenResponse(scopes, accessScopes, clientId,
- userId, authenticationTime, refreshToken);
+ if (isPublicClient){
+ return createsAccessTokenResponse(scopes, accessScopes, clientId,
+ userId, authenticationTime);
+ }
+ else {
+ RefreshToken refreshToken = refreshDao.storeRefreshToken(random, userId,
+ authenticationTime, clientId, accessScopes);
+ return createsAccessTokenResponse(scopes, accessScopes, clientId,
+ userId, authenticationTime, refreshToken);
+ }
}
private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
@@ -365,6 +374,22 @@
.setRefreshToken(refreshToken.getToken())
.setScope(String.join(" ", scopes)).buildJSONMessage();
}
+
+ private OAuthResponse createsAccessTokenResponse (Set<String> scopes,
+ Set<AccessScope> accessScopes, String clientId, String userId,
+ ZonedDateTime authenticationTime)
+ throws OAuthSystemException, KustvaktException {
+
+ String accessToken = randomGenerator.createRandomCode();
+ tokenDao.storeAccessToken(accessToken, null, accessScopes,
+ userId, clientId, authenticationTime);
+
+ return OAuthASResponse.tokenResponse(Status.OK.getStatusCode())
+ .setAccessToken(accessToken)
+ .setTokenType(TokenType.BEARER.toString())
+ .setExpiresIn(String.valueOf(config.getAccessTokenLongExpiry()))
+ .setScope(String.join(" ", scopes)).buildJSONMessage();
+ }
public void revokeToken (OAuth2RevokeTokenRequest revokeTokenRequest)
throws KustvaktException {
@@ -389,8 +414,7 @@
private boolean revokeAccessToken (String token) throws KustvaktException {
try {
AccessToken accessToken = tokenDao.retrieveAccessToken(token);
- accessToken.setRevoked(true);
- tokenDao.updateAccessToken(accessToken);
+ revokeAccessToken(accessToken);
return true;
}
catch (KustvaktException e) {
@@ -400,6 +424,14 @@
throw e;
}
}
+
+ private void revokeAccessToken (AccessToken accessToken)
+ throws KustvaktException {
+ if (accessToken != null){
+ accessToken.setRevoked(true);
+ tokenDao.updateAccessToken(accessToken);
+ }
+ }
private boolean revokeRefreshToken (String token) throws KustvaktException {
RefreshToken refreshToken = null;
@@ -410,11 +442,10 @@
return false;
}
- revokeRefreshToken(refreshToken);
- return true;
+ return revokeRefreshToken(refreshToken);
}
- private void revokeRefreshToken (RefreshToken refreshToken)
+ private boolean revokeRefreshToken (RefreshToken refreshToken)
throws KustvaktException {
if (refreshToken != null){
refreshToken.setRevoked(true);
@@ -425,7 +456,9 @@
accessToken.setRevoked(true);
tokenDao.updateAccessToken(accessToken);
}
+ return true;
}
+ return false;
}
public void revokeAllClientTokensViaSuperClient (String username,
@@ -442,11 +475,18 @@
}
String clientId = revokeTokenRequest.getClientId();
- List<RefreshToken> refreshTokens =
- refreshDao.retrieveRefreshTokenByClientId(clientId);
-
- for (RefreshToken r : refreshTokens) {
- if (r.getUserId().equals(username)){
+ OAuth2Client client = clientService.retrieveClient(clientId);
+ if (clientService.isPublicClient(client)) {
+ List<AccessToken> accessTokens =
+ tokenDao.retrieveAccessTokenByClientId(clientId, username);
+ for (AccessToken t : accessTokens) {
+ revokeAccessToken(t);
+ }
+ }
+ else {
+ List<RefreshToken> refreshTokens = refreshDao
+ .retrieveRefreshTokenByClientId(clientId, username);
+ for (RefreshToken r : refreshTokens) {
revokeRefreshToken(r);
}
}
@@ -466,7 +506,10 @@
String token = revokeTokenRequest.getToken();
RefreshToken refreshToken = refreshDao.retrieveRefreshToken(token, username);
- revokeRefreshToken(refreshToken);
+ if (!revokeRefreshToken(refreshToken)){
+ AccessToken accessToken = tokenDao.retrieveAccessToken(token, username);
+ revokeAccessToken(accessToken);
+ }
}
public List<OAuth2RefreshTokenDto> listUserRefreshToken (String username, 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 b6d7e2b..05280a2 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
@@ -217,14 +217,14 @@
// revoke all related access tokens
List<AccessToken> tokens =
- tokenDao.retrieveAccessTokenByClientId(clientId);
+ tokenDao.retrieveAccessTokenByClientId(clientId,null);
for (AccessToken token : tokens) {
token.setRevoked(true);
tokenDao.updateAccessToken(token);
}
List<RefreshToken> refreshTokens =
- refreshDao.retrieveRefreshTokenByClientId(clientId);
+ refreshDao.retrieveRefreshTokenByClientId(clientId,null);
for (RefreshToken token : refreshTokens) {
token.setRevoked(true);
refreshDao.updateRefreshToken(token);
@@ -358,10 +358,23 @@
"Only super client is allowed to list user authorized clients.",
OAuth2Error.UNAUTHORIZED_CLIENT);
}
+
List<OAuth2Client> userClients =
clientDao.retrieveUserAuthorizedClients(username);
- Collections.sort(userClients);
- return createClientDtos(userClients);
+ userClients.addAll(clientDao.retrieveClientsByAccessTokens(username));
+
+ List<String> clientIds = new ArrayList<>();
+ List<OAuth2Client> uniqueClients = new ArrayList<>();
+ for (OAuth2Client c : userClients){
+ String id = c.getId();
+ if (!clientIds.contains(id)){
+ clientIds.add(id);
+ uniqueClients.add(c);
+ }
+ }
+
+ Collections.sort(uniqueClients);
+ return createClientDtos(uniqueClients);
}
public List<OAuth2UserClientDto> listUserRegisteredClients (String username,
@@ -391,4 +404,8 @@
}
return dtoList;
}
+
+ public boolean isPublicClient (OAuth2Client oAuth2Client) {
+ return oAuth2Client.getType().equals(OAuth2ClientType.PUBLIC);
+ }
}
diff --git a/full/src/main/resources/db/sqlite/V1.4__oauth2_tables.sql b/full/src/main/resources/db/sqlite/V1.4__oauth2_tables.sql
index 00be974..abb9199 100644
--- a/full/src/main/resources/db/sqlite/V1.4__oauth2_tables.sql
+++ b/full/src/main/resources/db/sqlite/V1.4__oauth2_tables.sql
@@ -77,13 +77,13 @@
id INTEGER PRIMARY KEY AUTOINCREMENT,
token VARCHAR(255) NOT NULL,
user_id VARCHAR(100) DEFAULT NULL,
- client_id VARCHAR(100) DEFAULT NULL,
created_date TIMESTAMP NOT NULL,
expiry_date TIMESTAMP NOT NULL,
is_revoked BOOLEAN DEFAULT 0,
user_auth_time TIMESTAMP NOT NULL,
refresh_token INTEGER DEFAULT NULL,
- FOREIGN KEY (client_id)
+ client VARCHAR(100) DEFAULT NULL,
+ FOREIGN KEY (client)
REFERENCES oauth2_client(id)
ON DELETE CASCADE
FOREIGN KEY (refresh_token)
diff --git a/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql b/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
index 33330c1..d179fdc 100644
--- a/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
+++ b/full/src/main/resources/db/test/V3.5__insert_oauth2_clients.sql
@@ -21,7 +21,15 @@
"This is a test nonsuper confidential client.",
"http://third.party.com/confidential", 1712550103);
-
+INSERT INTO oauth2_client(id,name,secret,type,super,
+ redirect_uri,registered_by, description,url,url_hashcode)
+VALUES ("52atrL0ajex_3_5imd9Mgw","confidential client 2",
+ "$2a$08$vi1FbuN3p6GcI1tSxMAoeuIYL8Yw3j6A8wJthaN8ZboVnrQaTwLPq",
+ "CONFIDENTIAL", 0,
+ "https://example.client.de/redirect", "system",
+ "This is a test nonsuper confidential client.",
+ "http://example.client.de", 1535365678);
+
INSERT INTO oauth2_client(id,name,secret,type,super,
redirect_uri, registered_by, description, url,url_hashcode)
VALUES ("8bIDtZnH6NvRkW2Fq","third party client",null,
diff --git a/full/src/main/resources/kustvakt.conf b/full/src/main/resources/kustvakt.conf
index 68577ae..62546c6 100644
--- a/full/src/main/resources/kustvakt.conf
+++ b/full/src/main/resources/kustvakt.conf
@@ -60,6 +60,7 @@
oauth2.max.attempts = 1
# expiry in seconds (S), minutes (M), hours (H), days (D)
oauth2.access.token.expiry = 1D
+oauth2.access.token.long.expiry = 365D
oauth2.refresh.token.expiry = 90D
oauth2.authorization.code.expiry = 10M
# -- scopes separated by space
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java
index cc28512..f6768f1 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2AccessTokenTest.java
@@ -179,6 +179,20 @@
testSearchWithRevokedAccessToken(accessToken);
}
+
+ @Test
+ public void testRevokeAccessTokenPublicClientViaSuperClient()
+ throws KustvaktException {
+ String code = requestAuthorizationCode(publicClientId, "", null,
+ userAuthHeader);
+ ClientResponse response = requestTokenWithAuthorizationCodeAndForm(
+ publicClientId, "", code);
+
+ JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
+ String accessToken = node.at("/access_token").asText();
+ testRevokeTokenViaSuperClient(accessToken, userAuthHeader);
+ testSearchWithRevokedAccessToken(accessToken);
+ }
private void testSearchWithRevokedAccessToken (String accessToken)
throws KustvaktException {
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 c80ab36..2ee4bc2 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
@@ -515,14 +515,13 @@
assertEquals(Status.OK.getStatusCode(), response.getStatus());
// client 1
- String code = requestAuthorizationCode(publicClientId, clientSecret,
+ String code = requestAuthorizationCode(publicClientId, "",
null, userAuthHeader);
response = requestTokenWithAuthorizationCodeAndForm(publicClientId, "",
code);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
- String refreshToken = node.at("/refresh_token").asText();
String accessToken = node.at("/access_token").asText();
// client 2
@@ -530,30 +529,45 @@
null, userAuthHeader);
response = requestTokenWithAuthorizationCodeAndForm(
confidentialClientId, clientSecret, code);
+ String refreshToken = node.at("/refresh_token").asText();
assertEquals(Status.OK.getStatusCode(), response.getStatus());
requestAuthorizedClientList(userAuthHeader);
testListAuthorizedClientWithMultipleRefreshTokens(userAuthHeader);
- testListAuthorizedClientWithRefreshTokenFromAnotherUser(userAuthHeader);
+ testListAuthorizedClientWithMultipleAccessTokens(userAuthHeader);
+ testWithClientsFromAnotherUser(userAuthHeader);
// revoke client 1
testRevokeAllTokenViaSuperClient(publicClientId, userAuthHeader,
- accessToken, refreshToken);
- testRequestTokenWithRevokedRefreshToken(publicClientId, clientSecret,
- refreshToken);
+ accessToken);
// revoke client 2
node = JsonUtils.readTree(response.getEntity(String.class));
accessToken = node.at("/access_token").asText();
refreshToken = node.at("/refresh_token").asText();
testRevokeAllTokenViaSuperClient(confidentialClientId, userAuthHeader,
- accessToken, refreshToken);
+ accessToken);
+ testRequestTokenWithRevokedRefreshToken(confidentialClientId, clientSecret,
+ refreshToken);
}
private void testListAuthorizedClientWithMultipleRefreshTokens (
String userAuthHeader) throws KustvaktException {
+ // client 2
+ String code = requestAuthorizationCode(confidentialClientId, clientSecret,
+ null, userAuthHeader);
+ ClientResponse response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+ requestAuthorizedClientList(userAuthHeader);
+ }
+
+ private void testListAuthorizedClientWithMultipleAccessTokens (
+ String userAuthHeader) throws KustvaktException {
// client 1
- String code = requestAuthorizationCode(publicClientId, clientSecret,
+ String code = requestAuthorizationCode(publicClientId, "",
null, userAuthHeader);
ClientResponse response = requestTokenWithAuthorizationCodeAndForm(
publicClientId, "", code);
@@ -563,29 +577,44 @@
requestAuthorizedClientList(userAuthHeader);
}
- private void testListAuthorizedClientWithRefreshTokenFromAnotherUser (
+ private void testWithClientsFromAnotherUser (
String userAuthHeader) throws KustvaktException {
String aaaAuthHeader = HttpAuthorizationHandler
.createBasicAuthorizationHeaderValue("aaa", "pwd");
+
// client 1
- String code = requestAuthorizationCode(publicClientId, clientSecret,
+ String code = requestAuthorizationCode(publicClientId, "",
null, aaaAuthHeader);
ClientResponse response = requestTokenWithAuthorizationCodeAndForm(
publicClientId, "", code);
-
- requestAuthorizedClientList(userAuthHeader);
-
+
JsonNode node = JsonUtils.readTree(response.getEntity(String.class));
- String accessToken = node.at("/access_token").asText();
+ String accessToken1 = node.at("/access_token").asText();
+
+ // client 2
+ code = requestAuthorizationCode(confidentialClientId, clientSecret,
+ null, aaaAuthHeader);
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
+
+ node = JsonUtils.readTree(response.getEntity(String.class));
+ String accessToken2 = node.at("/access_token").asText();
String refreshToken = node.at("/refresh_token").asText();
+ requestAuthorizedClientList(aaaAuthHeader);
+ requestAuthorizedClientList(userAuthHeader);
+
testRevokeAllTokenViaSuperClient(publicClientId, aaaAuthHeader,
- accessToken, refreshToken);
+ accessToken1);
+ testRevokeAllTokenViaSuperClient(confidentialClientId, aaaAuthHeader,
+ accessToken2);
+ testRequestTokenWithRevokedRefreshToken(confidentialClientId, clientSecret,
+ refreshToken);
}
private void testRevokeAllTokenViaSuperClient (String clientId,
- String userAuthHeader, String accessToken, String refreshToken)
+ String userAuthHeader, String accessToken)
throws KustvaktException {
// check token before revoking
ClientResponse response = searchWithAccessToken(accessToken);
@@ -613,9 +642,6 @@
node.at("/errors/0/0").asInt());
assertEquals("Access token is invalid",
node.at("/errors/0/1").asText());
-
- testRequestTokenWithRevokedRefreshToken(clientId, clientSecret,
- refreshToken);
}
@Test
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
index b15d1e7..be97272 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2ControllerTest.java
@@ -176,22 +176,15 @@
JsonNode node = JsonUtils.readTree(entity);
String accessToken = node.at("/access_token").asText();
- String refreshToken = node.at("/refresh_token").asText();
assertEquals(TokenType.BEARER.toString(),
node.at("/token_type").asText());
- assertNotNull(node.at("/expires_in").asText());
+ assertEquals(31536000, node.at("/expires_in").asInt());
testRevokeToken(accessToken, publicClientId,null,
ACCESS_TOKEN_TYPE);
- testRequestRefreshTokenInvalidScope(publicClientId, refreshToken);
- testRequestRefreshTokenInvalidClient(refreshToken);
- testRequestRefreshTokenInvalidRefreshToken(publicClientId);
- testRequestRefreshTokenPublicClient(publicClientId, refreshToken);
-
- testRequestTokenWithRevokedRefreshToken(publicClientId, null,
- refreshToken);
+ assertTrue(node.at("/refresh_token").isMissingNode());
}
@Test
@@ -226,6 +219,11 @@
testRequestTokenWithUsedAuthorization(code);
String refreshToken = node.at("/refresh_token").asText();
+
+ testRequestRefreshTokenInvalidScope(confidentialClientId, refreshToken);
+ testRequestRefreshTokenInvalidClient(refreshToken);
+ testRequestRefreshTokenInvalidRefreshToken(confidentialClientId);
+
testRevokeToken(refreshToken, confidentialClientId,clientSecret,
REFRESH_TOKEN_TYPE);
testRequestTokenWithRevokedRefreshToken(confidentialClientId,
@@ -567,6 +565,7 @@
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
form.add("grant_type", GrantType.REFRESH_TOKEN.toString());
form.add("client_id", clientId);
+ form.add("client_secret", clientSecret);
form.add("refresh_token", refreshToken);
form.add("scope", "search serialize_query");
@@ -635,6 +634,7 @@
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
form.add("grant_type", GrantType.REFRESH_TOKEN.toString());
form.add("client_id", clientId);
+ form.add("client_secret", clientSecret);
form.add("refresh_token", "Lia8s8w8tJeZSBlaQDrYV8ion3l");
ClientResponse response = resource().path(API_VERSION).path("oauth2").path("token")
@@ -667,23 +667,6 @@
assertEquals("SUCCESS", response.getEntity(String.class));
}
- private void testRevokeTokenViaSuperClient (String token, String userAuthHeader) {
- MultivaluedMap<String, String> form = new MultivaluedMapImpl();
- form.add("token", token);
- form.add("super_client_id", superClientId);
- form.add("super_client_secret", clientSecret);
-
- ClientResponse response = resource().path(API_VERSION)
- .path("oauth2").path("revoke").path("super")
- .header(HttpHeaders.CONTENT_TYPE,
- ContentType.APPLICATION_FORM_URLENCODED)
- .header(Attributes.AUTHORIZATION, userAuthHeader)
- .entity(form).post(ClientResponse.class);
-
- assertEquals(Status.OK.getStatusCode(), response.getStatus());
- assertEquals("SUCCESS", response.getEntity(String.class));
- }
-
@Test
public void testListRefreshToken () throws KustvaktException {
String username = "gurgle";
@@ -699,41 +682,41 @@
String refreshToken1 = node.at("/refresh_token").asText();
// client 1
- String code = requestAuthorizationCode(publicClientId, clientSecret,
+ String code = requestAuthorizationCode(confidentialClientId, clientSecret,
null, userAuthHeader);
- response = requestTokenWithAuthorizationCodeAndForm(publicClientId, "",
- code);
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId, clientSecret, code);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
// client 2
+ code = requestAuthorizationCode(confidentialClientId2, clientSecret,
+ null, userAuthHeader);
+ response = requestTokenWithAuthorizationCodeAndForm(
+ confidentialClientId2, clientSecret, code);
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+
+ // list
+ node = requestRefreshTokenList(userAuthHeader);
+ assertEquals(2, node.size());
+ assertEquals(confidentialClientId, node.at("/0/clientId").asText());
+ assertEquals(confidentialClientId2, node.at("/1/clientId").asText());
+
+ // client 1
code = requestAuthorizationCode(confidentialClientId, clientSecret,
null, userAuthHeader);
response = requestTokenWithAuthorizationCodeAndForm(
confidentialClientId, clientSecret, code);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
- // list
- node = requestRefreshTokenList(userAuthHeader);
- assertEquals(2, node.size());
- assertEquals(publicClientId, node.at("/0/clientId").asText());
- assertEquals(confidentialClientId, node.at("/1/clientId").asText());
-
- // client 1
- code = requestAuthorizationCode(publicClientId, clientSecret,
- null, userAuthHeader);
- response = requestTokenWithAuthorizationCodeAndForm(
- publicClientId, "", code);
- assertEquals(Status.OK.getStatusCode(), response.getStatus());
-
// another user
String darlaAuthHeader = HttpAuthorizationHandler
.createBasicAuthorizationHeaderValue("darla", "pwd");
// client 1
- code = requestAuthorizationCode(publicClientId, clientSecret,
+ code = requestAuthorizationCode(confidentialClientId, clientSecret,
null, darlaAuthHeader);
assertEquals(Status.OK.getStatusCode(), response.getStatus());
response = requestTokenWithAuthorizationCodeAndForm(
- publicClientId, "", code);
+ confidentialClientId, clientSecret, code);
node = JsonUtils.readTree(response.getEntity(String.class));
String refreshToken5 = node.at("/refresh_token").asText();
@@ -744,9 +727,9 @@
testRevokeToken(refreshToken1, superClientId, clientSecret,
REFRESH_TOKEN_TYPE);
- testRevokeToken(node.at("/0/token").asText(), publicClientId, null,
- REFRESH_TOKEN_TYPE);
- testRevokeToken(node.at("/1/token").asText(), confidentialClientId,
+ testRevokeToken(node.at("/0/token").asText(), confidentialClientId,
+ clientSecret, REFRESH_TOKEN_TYPE);
+ testRevokeToken(node.at("/1/token").asText(), confidentialClientId2,
clientSecret, REFRESH_TOKEN_TYPE);
node = requestRefreshTokenList(userAuthHeader);
diff --git a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
index a30ca24..91604bb 100644
--- a/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
+++ b/full/src/test/java/de/ids_mannheim/korap/web/controller/OAuth2TestBase.java
@@ -42,6 +42,7 @@
protected String publicClientId = "8bIDtZnH6NvRkW2Fq";
protected String confidentialClientId = "9aHsGW6QflV13ixNpez";
+ protected String confidentialClientId2 = "9aHsGW6QflV13ixNpez";
protected String superClientId = "fCBbQkAyYzI4NzUxMg";
protected String clientSecret = "secret";
@@ -147,6 +148,7 @@
MultivaluedMap<String, String> form = new MultivaluedMapImpl();
form.add("grant_type", GrantType.REFRESH_TOKEN.toString());
form.add("client_id", clientId);
+ form.add("client_secret", clientSecret);
form.add("refresh_token", refreshToken);
if (clientSecret != null) {
form.add("client_secret", clientSecret);
@@ -187,5 +189,22 @@
.header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
.get(ClientResponse.class);
}
+
+ protected void testRevokeTokenViaSuperClient (String token, String userAuthHeader) {
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("token", token);
+ form.add("super_client_id", superClientId);
+ form.add("super_client_secret", clientSecret);
+
+ ClientResponse response = resource().path(API_VERSION)
+ .path("oauth2").path("revoke").path("super")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .header(Attributes.AUTHORIZATION, userAuthHeader)
+ .entity(form).post(ClientResponse.class);
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ assertEquals("SUCCESS", response.getEntity(String.class));
+ }
}