Added service layer to SearchController, added OAuth2 scope handling,
fixed bugs.
Change-Id: Id6cfb5c264472d106314dbd4a485681460e67288
diff --git a/core/Changes b/core/Changes
index 687adb1..409fd0d 100644
--- a/core/Changes
+++ b/core/Changes
@@ -1,8 +1,13 @@
+version 0.60.5
+09/07/2018
+ - Fixed status codes (margaretha)
+ - Added KustvaktException for wrapping another exception (margaretha)
+
version 0.60.4
25/06/2018
- added the redirect URI property in KustvaktException (margaretha)
- added openid related status codes (margaretha)
-
+
version 0.60.3
30/05/2018
- added parameter checker for collection (margaretha)
diff --git a/core/pom.xml b/core/pom.xml
index 2525eff..0fa8b97 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>de.ids_mannheim.korap</groupId>
<artifactId>Kustvakt-core</artifactId>
- <version>0.60.4</version>
+ <version>0.60.5</version>
<properties>
<java.version>1.8</java.version>
diff --git a/core/src/main/java/de/ids_mannheim/korap/config/Attributes.java b/core/src/main/java/de/ids_mannheim/korap/config/Attributes.java
index 51e4dab..dd54f28 100644
--- a/core/src/main/java/de/ids_mannheim/korap/config/Attributes.java
+++ b/core/src/main/java/de/ids_mannheim/korap/config/Attributes.java
@@ -20,7 +20,7 @@
public static final String CLIENT_ID = "client_id";
public static final String CLIENT_SECRET = "client_secret";
- public static final String SCOPES = "scopes";
+ public static final String SCOPE = "scope";
public static final String PUBLIC_GROUP = "public";
diff --git a/core/src/main/java/de/ids_mannheim/korap/config/Scopes.java b/core/src/main/java/de/ids_mannheim/korap/config/Scopes.java
index 1cc07cf..20e5627 100644
--- a/core/src/main/java/de/ids_mannheim/korap/config/Scopes.java
+++ b/core/src/main/java/de/ids_mannheim/korap/config/Scopes.java
@@ -68,7 +68,7 @@
}
if (scopes.contains(Scope.profile.toString()))
m.values.putAll(Scopes.getProfileScopes(details).values);
- m.values.put(Attributes.SCOPES, scopes);
+ m.values.put(Attributes.SCOPE, scopes);
}
return m;
}
diff --git a/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java b/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
index 9a76e74..9123a5b 100644
--- a/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
+++ b/core/src/main/java/de/ids_mannheim/korap/exceptions/KustvaktException.java
@@ -107,6 +107,11 @@
+ public KustvaktException (String notification) {
+ this.notification = notification;
+ isNotification = true;
+ }
+
public String string () {
return "Excpt{" + "status=" + getStatusCode() + ", message="
+ getMessage() + ", args=" + getEntity() + ", userid=" + userid
diff --git a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
index 5bd0a8d..2166813 100644
--- a/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
+++ b/core/src/main/java/de/ids_mannheim/korap/exceptions/StatusCodes.java
@@ -26,17 +26,23 @@
public static final int HTTPS_REQUIRED = 110;
/**
+ * 200 status codes general JSON serialization error
+ */
+
+ public static final int SERIALIZATION_FAILED = 200;
+ public static final int DESERIALIZATION_FAILED = 201;
+ public static final int MISSING_ATTRIBUTE = 202;
+ public static final int INVALID_ATTRIBUTE = 203;
+ public static final int UNSUPPORTED_VALUE = 204;
+
+ /**
* 300 status codes for query language and serialization
*/
public static final int NO_QUERY = 301;
- // public static final int INVALID_TYPE = 302;
- public static final int MISSING_ATTRIBUTE = 303;
- public static final int INVALID_ATTRIBUTE = 304;
- public static final int UNSUPPORTED_VALUE = 305;
- public static final int SERIALIZATION_FAILED = 306;
- public static final int DESERIALIZATION_FAILED = 307;
-
+// public static final int INVALID_TYPE = 302;
+// public static final int SERIALIZATION_FAILED = 300;
+
/**
* 400 status codes for authorization and rewrite functions
*/
@@ -83,15 +89,6 @@
public static final int DB_ENTRY_EXISTS = 508;
- // User group and member
- public static final int GROUP_MEMBER_EXISTS = 601;
- public static final int GROUP_MEMBER_INACTIVE = 602;
- public static final int GROUP_MEMBER_DELETED = 603;
- public static final int GROUP_MEMBER_NOT_FOUND = 604;
- public static final int INVITATION_EXPIRED = 605;
- public static final int GROUP_NOT_FOUND = 606;
- public static final int GROUP_DELETED = 607;
-
// public static final int ARGUMENT_VALIDATION_FAILURE = 700;
// public static final int ARGUMENT_VALIDATION_FAILURE = 701;
@@ -112,6 +109,16 @@
public static final int REQUEST_INVALID = 1002;
public static final int ACCESS_DENIED = 1003;
+
+ // User group and member
+ public static final int GROUP_MEMBER_EXISTS = 1601;
+ public static final int GROUP_MEMBER_INACTIVE = 1602;
+ public static final int GROUP_MEMBER_DELETED = 1603;
+ public static final int GROUP_MEMBER_NOT_FOUND = 1604;
+ public static final int INVITATION_EXPIRED = 1605;
+ public static final int GROUP_NOT_FOUND = 1606;
+ public static final int GROUP_DELETED = 1607;
+
/**
* 1800 Oauth2 and OpenID
*/
diff --git a/core/src/main/java/de/ids_mannheim/korap/web/CoreResponseHandler.java b/core/src/main/java/de/ids_mannheim/korap/web/CoreResponseHandler.java
index d118ac2..2a398e7 100644
--- a/core/src/main/java/de/ids_mannheim/korap/web/CoreResponseHandler.java
+++ b/core/src/main/java/de/ids_mannheim/korap/web/CoreResponseHandler.java
@@ -19,11 +19,11 @@
public class CoreResponseHandler {
private AuditingIface auditing;
-
+
public CoreResponseHandler (AuditingIface iface) {
this.auditing = iface;
}
-
+
private void register (List<AuditRecord> records) {
if (auditing != null && !records.isEmpty())
auditing.audit(records);
@@ -34,11 +34,18 @@
public WebApplicationException throwit (KustvaktException e) {
Response s;
- if (e.hasNotification()){
- s = Response.status(getStatus(e.getStatusCode()))
- .entity(e.getNotification()).build();
+ if (e.hasNotification()) {
+ if (e.getStatusCode() != null) {
+ s = Response.status(getStatus(e.getStatusCode()))
+ .entity(e.getNotification()).build();
+ }
+ // KustvaktException just wraps another exception
+ else {
+ s=Response.status(Response.Status.BAD_REQUEST)
+ .entity(e.getNotification()).build();
+ }
}
- else{
+ else {
s = Response.status(getStatus(e.getStatusCode()))
.entity(buildNotification(e)).build();
}
@@ -58,10 +65,10 @@
}
public WebApplicationException throwit (int code, String notification) {
- return new WebApplicationException(Response.status(getStatus(code))
- .entity(notification).build());
+ return new WebApplicationException(
+ Response.status(getStatus(code)).entity(notification).build());
}
-
+
protected String buildNotification (KustvaktException e) {
register(e.getRecords());
return buildNotification(e.getStatusCode(), e.getMessage(),
@@ -78,9 +85,9 @@
protected Response.Status getStatus (int code) {
Response.Status status = Response.Status.BAD_REQUEST;
switch (code) {
-// case StatusCodes.NO_VALUE_FOUND:
-// status = Response.Status.NO_CONTENT;
-// break;
+ // case StatusCodes.NO_VALUE_FOUND:
+ // status = Response.Status.NO_CONTENT;
+ // break;
case StatusCodes.ILLEGAL_ARGUMENT:
status = Response.Status.NOT_ACCEPTABLE;
break;
diff --git a/full/Changes b/full/Changes
index 98b84e8..51c8844 100644
--- a/full/Changes
+++ b/full/Changes
@@ -1,3 +1,11 @@
+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)
+
+
version 0.60.4
05/07/2018
- implemented OAuth2 authorization code request with OpenID Authentication (margaretha)
diff --git a/full/pom.xml b/full/pom.xml
index 9f0b6cd..ce95d8d 100644
--- a/full/pom.xml
+++ b/full/pom.xml
@@ -3,7 +3,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>de.ids_mannheim.korap</groupId>
<artifactId>Kustvakt-full</artifactId>
- <version>0.60.4</version>
+ <version>0.60.5</version>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
@@ -150,7 +150,7 @@
<dependency>
<groupId>de.ids_mannheim.korap</groupId>
<artifactId>Kustvakt-core</artifactId>
- <version>0.60.4</version>
+ <version>0.60.5</version>
</dependency>
<!-- LDAP -->
<dependency>
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/BasicAuthentication.java b/full/src/main/java/de/ids_mannheim/korap/authentication/BasicAuthentication.java
index e272a34..b90406b 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/BasicAuthentication.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/BasicAuthentication.java
@@ -78,7 +78,7 @@
c.setToken(StringUtils.stripTokenType(authToken));
// fixme: you can make queries, but user sensitive data is
// off limits?!
- c.addContextParameter(Attributes.SCOPES,
+ c.addContextParameter(Attributes.SCOPE,
Scopes.Scope.search.toString());
return c;
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/authentication/OAuth2Authentication.java b/full/src/main/java/de/ids_mannheim/korap/authentication/OAuth2Authentication.java
index f490de0..ffee80a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/authentication/OAuth2Authentication.java
+++ b/full/src/main/java/de/ids_mannheim/korap/authentication/OAuth2Authentication.java
@@ -47,7 +47,7 @@
c.setExpirationTime(expiry.toInstant().toEpochMilli());
c.setToken(authToken);
c.setTokenType(TokenType.BEARER);
- c.addContextParameter(Attributes.SCOPES, scopes);
+ c.addContextParameter(Attributes.SCOPE, scopes);
c.setAuthenticationTime(accessToken.getUserAuthenticationTime());
return c;
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/Initializator.java b/full/src/main/java/de/ids_mannheim/korap/config/Initializator.java
index 6c04431..e730a0b 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/Initializator.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/Initializator.java
@@ -1,7 +1,10 @@
package de.ids_mannheim.korap.config;
+import java.util.Arrays;
+import java.util.HashSet;
import java.util.Set;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Scope;
import de.ids_mannheim.korap.oauth2.dao.AccessScopeDao;
/**
@@ -12,13 +15,9 @@
*/
public class Initializator {
- private FullConfiguration config;
private AccessScopeDao accessScopeDao;
-
- public Initializator (FullConfiguration config,
- AccessScopeDao accessScopeDao) {
- this.config = config;
+ public Initializator (AccessScopeDao accessScopeDao) {
this.accessScopeDao = accessScopeDao;
}
@@ -27,7 +26,11 @@
}
private void setAccessScope () {
- accessScopeDao.storeAccessScopes(config.getDefaultAccessScopes());
- accessScopeDao.storeAccessScopes(config.getClientCredentialsScopes());
+ OAuth2Scope[] enums = OAuth2Scope.values();
+ Set<String> scopes = new HashSet<>(enums.length);
+ for (OAuth2Scope s : enums) {
+ scopes.add(s.toString());
+ }
+ accessScopeDao.storeAccessScopes(scopes);
}
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java b/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
index a55a08c..49f8183 100644
--- a/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
+++ b/full/src/main/java/de/ids_mannheim/korap/config/JWTSigner.java
@@ -67,7 +67,7 @@
Builder csBuilder = new JWTClaimsSet.Builder();
csBuilder.issuer(this.issuer.toString());
- if ((scopes = (String) attr.get(Attributes.SCOPES)) != null) {
+ if ((scopes = (String) attr.get(Attributes.SCOPE)) != null) {
Userdata data = new GenericUserData();
data.readQuietly(attr, false);
Scopes claims = Scopes.mapScopes(scopes, data);
diff --git a/full/src/main/java/de/ids_mannheim/korap/handlers/OAuthDb.java b/full/src/main/java/de/ids_mannheim/korap/handlers/OAuthDb.java
index a1ae65c..838b1c0 100644
--- a/full/src/main/java/de/ids_mannheim/korap/handlers/OAuthDb.java
+++ b/full/src/main/java/de/ids_mannheim/korap/handlers/OAuthDb.java
@@ -212,8 +212,8 @@
c.setToken(token);
c.setTokenType(TokenType.BEARER);
// c.setTokenType(Attributes.OAUTH2_AUTHORIZATION);
- c.addContextParameter(Attributes.SCOPES,
- rs.getString(Attributes.SCOPES));
+ c.addContextParameter(Attributes.SCOPE,
+ rs.getString(Attributes.SCOPE));
return c;
}
});
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Scope.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Scope.java
new file mode 100644
index 0000000..7e6c973
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/constant/OAuth2Scope.java
@@ -0,0 +1,11 @@
+package de.ids_mannheim.korap.oauth2.constant;
+
+public enum OAuth2Scope {
+
+ OPENID, SEARCH, SERIALIZE_QUERY, MATCH_INFO, CREATE_VC, LIST_VC, EDIT_VC, VC_INFO, CLIENT_INFO;
+
+ @Override
+ public String toString () {
+ return super.toString().toLowerCase();
+ }
+}
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 4e89d5e..ec97923 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
@@ -21,6 +21,7 @@
import de.ids_mannheim.korap.oauth2.dao.AccessTokenDao;
import de.ids_mannheim.korap.oauth2.entity.AccessScope;
import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
@Service
@@ -39,21 +40,13 @@
String grantType = oAuthRequest.getGrantType();
if (grantType.equals(GrantType.AUTHORIZATION_CODE.toString())) {
- Authorization authorization =
- retrieveAuthorization(
- oAuthRequest.getCode(),
- oAuthRequest.getRedirectURI(),
- oAuthRequest.getClientId(),
- oAuthRequest.getClientSecret());
+ Authorization authorization = retrieveAuthorization(
+ oAuthRequest.getCode(), oAuthRequest.getRedirectURI(),
+ oAuthRequest.getClientId(), oAuthRequest.getClientSecret());
return createsAccessTokenResponse(authorization);
}
else if (grantType.equals(GrantType.PASSWORD.toString())) {
- ZonedDateTime authenticationTime = authenticateClientAndUser(
- oAuthRequest.getUsername(), oAuthRequest.getPassword(),
- oAuthRequest.getScopes(), oAuthRequest.getClientId(),
- oAuthRequest.getClientSecret());
- return createsAccessTokenResponse(oAuthRequest.getScopes(),
- oAuthRequest.getUsername(), authenticationTime);
+ return requestAccessTokenWithPassword(oAuthRequest);
}
else if (grantType.equals(GrantType.CLIENT_CREDENTIALS.toString())) {
ZonedDateTime authenticationTime =
@@ -75,6 +68,31 @@
}
+ private OAuthResponse requestAccessTokenWithPassword (
+ AbstractOAuthTokenRequest oAuthRequest)
+ throws KustvaktException, OAuthSystemException {
+
+ OAuth2Client client = clientService.authenticateClient(
+ oAuthRequest.getClientId(), oAuthRequest.getClientSecret());
+ if (!client.isNative()) {
+ throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
+ "Password grant is not allowed for third party clients",
+ OAuth2Error.UNAUTHORIZED_CLIENT);
+ }
+
+ Set<String> scopes = oAuthRequest.getScopes();
+ if (scopes == null || scopes.isEmpty()) {
+ scopes = config.getDefaultAccessScopes();
+ }
+
+ ZonedDateTime authenticationTime = authenticateUser(
+ oAuthRequest.getUsername(), oAuthRequest.getPassword(), scopes);
+
+ return createsAccessTokenResponse(scopes, oAuthRequest.getUsername(),
+ authenticationTime);
+
+ }
+
/**
* Creates an OAuthResponse containing an access token and a
* refresh token with type Bearer.
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
index eca6fa2..a61cbe1 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/openid/service/OpenIdTokenService.java
@@ -47,6 +47,7 @@
import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
import de.ids_mannheim.korap.oauth2.entity.AccessScope;
import de.ids_mannheim.korap.oauth2.entity.Authorization;
+import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
import de.ids_mannheim.korap.oauth2.service.OAuth2TokenService;
import de.ids_mannheim.korap.utils.TimeUtils;
@@ -98,6 +99,30 @@
return null;
}
+ /**
+ * Third party apps must not be allowed to use password grant.
+ * MH: password grant is only allowed for trusted clients (korap
+ * frontend)
+ *
+ * According to RFC 6749, client authentication is only required
+ * for confidential clients and whenever client credentials are
+ * provided. Moreover, client_id is optional for password grant,
+ * but without it, the authentication server cannot check the
+ * client type. To make sure that confidential clients
+ * authenticate, client_id is made required (similar to
+ * authorization code grant).
+ *
+ * @param username
+ * username, required
+ * @param password
+ * password, required
+ * @param scope
+ * scope, optional
+ * @param clientAuthentication
+ * @param clientId
+ * @return
+ * @throws KustvaktException
+ */
private AccessTokenResponse requestAccessTokenWithPassword (String username,
String password, Scope scope,
ClientAuthentication clientAuthentication, ClientID clientId)
@@ -111,6 +136,7 @@
ZonedDateTime authenticationTime;
String clientIdStr = null;
+ OAuth2Client client;
if (clientAuthentication == null) {
if (clientId == null) {
throw new KustvaktException(StatusCodes.MISSING_PARAMETER,
@@ -118,18 +144,29 @@
OAuth2Error.INVALID_REQUEST);
}
else {
- authenticationTime = authenticateClientAndUser(username,
- password, scopes, clientId.getValue(), null);
clientIdStr = clientId.getValue();
+ client = clientService.authenticateClient(clientIdStr,
+ null);
}
}
else {
String[] clientCredentials =
extractClientCredentials(clientAuthentication);
clientIdStr = clientCredentials[0];
- authenticationTime = authenticateClientAndUser(username, password,
- scopes, clientCredentials[0], clientCredentials[1]);
+ client = clientService.authenticateClient(clientCredentials[0],
+ clientCredentials[1]);
}
+
+ if (!client.isNative()) {
+ throw new KustvaktException(
+ StatusCodes.CLIENT_AUTHORIZATION_FAILED,
+ "Password grant is not allowed for third party clients",
+ OAuth2Error.UNAUTHORIZED_CLIENT);
+ }
+
+ authenticationTime =
+ authenticateUser(username, password, scopes);
+
return createsAccessTokenResponse(scope, clientIdStr, username,
authenticationTime, null);
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeService.java
index f47fa3c..cd4eec1 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2ScopeService.java
@@ -2,6 +2,7 @@
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@@ -9,11 +10,16 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
+import de.ids_mannheim.korap.config.Attributes;
+import de.ids_mannheim.korap.constant.TokenType;
+import de.ids_mannheim.korap.dao.AdminDao;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Scope;
import de.ids_mannheim.korap.oauth2.dao.AccessScopeDao;
import de.ids_mannheim.korap.oauth2.entity.AccessScope;
+import de.ids_mannheim.korap.security.context.TokenContext;
@Service
public class OAuth2ScopeService {
@@ -21,6 +27,9 @@
@Autowired
private AccessScopeDao accessScopeDao;
+ @Autowired
+ private AdminDao adminDao;
+
/**
* Converts a set of scope strings to a set of {@link AccessScope}
*
@@ -53,8 +62,9 @@
Set<String> set = convertAccessScopesToStringSet(scopes);
return String.join(" ", set);
}
-
- public Set<String> convertAccessScopesToStringSet (Set<AccessScope> scopes) {
+
+ public Set<String> convertAccessScopesToStringSet (
+ Set<AccessScope> scopes) {
Set<String> set = scopes.stream().map(scope -> scope.toString())
.collect(Collectors.toSet());
return set;
@@ -76,4 +86,17 @@
.collect(Collectors.toSet());
return filteredScopes;
}
+
+ public void verifyScope (TokenContext context, OAuth2Scope requestScope)
+ throws KustvaktException {
+ if (!adminDao.isAdmin(context.getUsername())
+ && context.getTokenType().equals(TokenType.BEARER)) {
+ Map<String, Object> parameters = context.getParameters();
+ String scope = (String) parameters.get(Attributes.SCOPE);
+ if (!scope.contains(requestScope.toString())) {
+ throw new KustvaktException(StatusCodes.AUTHORIZATION_FAILED,
+ "Scope " + requestScope + " is not authorized");
+ }
+ }
+ }
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java
index d36a077..d6c11d9 100644
--- a/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java
+++ b/full/src/main/java/de/ids_mannheim/korap/oauth2/service/OAuth2TokenService.java
@@ -6,6 +6,7 @@
import java.util.Map;
import java.util.Set;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -16,7 +17,6 @@
import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
import de.ids_mannheim.korap.oauth2.constant.OAuth2Error;
import de.ids_mannheim.korap.oauth2.entity.Authorization;
-import de.ids_mannheim.korap.oauth2.entity.OAuth2Client;
/**
* OAuth2TokenService manages business logic related to OAuth2
@@ -29,7 +29,7 @@
public class OAuth2TokenService {
@Autowired
- private OAuth2ClientService clientService;
+ protected OAuth2ClientService clientService;
@Autowired
private OAuth2AuthorizationService authorizationService;
@@ -107,21 +107,6 @@
* @throws KustvaktException
* @throws OAuthSystemException
*/
- protected ZonedDateTime authenticateClientAndUser (String username,
- String password, Set<String> scopes, String clientId,
- String clientSecret) throws KustvaktException {
-
- OAuth2Client client =
- clientService.authenticateClient(clientId, clientSecret);
- if (!client.isNative()) {
- throw new KustvaktException(StatusCodes.CLIENT_AUTHORIZATION_FAILED,
- "Password grant is not allowed for third party clients",
- OAuth2Error.UNAUTHORIZED_CLIENT);
- }
-
- return authenticateUser(username, password, scopes);
- // verify or limit scopes ?
- }
public ZonedDateTime authenticateUser (String username, String password,
Set<String> scopes) throws KustvaktException {
@@ -136,7 +121,7 @@
Map<String, Object> attributes = new HashMap<>();
if (scopes != null && !scopes.isEmpty()) {
- attributes.put(Attributes.SCOPES, scopes);
+ attributes.put(Attributes.SCOPE, scopes);
}
authenticationManager.authenticate(
config.getOAuth2passwordAuthentication(), username, password,
diff --git a/full/src/main/java/de/ids_mannheim/korap/security/context/TokenContext.java b/full/src/main/java/de/ids_mannheim/korap/security/context/TokenContext.java
index 851631a..b31c643 100644
--- a/full/src/main/java/de/ids_mannheim/korap/security/context/TokenContext.java
+++ b/full/src/main/java/de/ids_mannheim/korap/security/context/TokenContext.java
@@ -41,7 +41,7 @@
private String token;
private boolean secureRequired;
- @Getter(AccessLevel.PRIVATE)
+// @Getter(AccessLevel.PRIVATE)
@Setter(AccessLevel.PRIVATE)
private Map<String, Object> parameters;
private String hostAddress;
@@ -72,8 +72,7 @@
public Map<String, Object> params () {
return new HashMap<>(parameters);
}
-
-
+
public boolean match (TokenContext other) {
if (other.getToken().equals(this.token))
if (this.getHostAddress().equals(this.hostAddress))
diff --git a/full/src/main/java/de/ids_mannheim/korap/service/SearchService.java b/full/src/main/java/de/ids_mannheim/korap/service/SearchService.java
new file mode 100644
index 0000000..1aea369
--- /dev/null
+++ b/full/src/main/java/de/ids_mannheim/korap/service/SearchService.java
@@ -0,0 +1,231 @@
+package de.ids_mannheim.korap.service;
+
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+import javax.annotation.PostConstruct;
+import javax.ws.rs.core.HttpHeaders;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.UriBuilder;
+
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.sun.jersey.core.util.MultivaluedMapImpl;
+
+import de.ids_mannheim.korap.config.FullConfiguration;
+import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.query.serialize.MetaQueryBuilder;
+import de.ids_mannheim.korap.query.serialize.QuerySerializer;
+import de.ids_mannheim.korap.rewrite.FullRewriteHandler;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.user.User.CorpusAccess;
+import de.ids_mannheim.korap.web.ClientsHandler;
+import de.ids_mannheim.korap.web.SearchKrill;
+
+@Service
+public class SearchService {
+
+ private static Logger jlog = LogManager.getLogger(SearchService.class);
+
+ @Autowired
+ private FullConfiguration config;
+
+ @Autowired
+ private AuthenticationManagerIface authManager;
+
+ @Autowired
+ private FullRewriteHandler rewriteHandler;
+
+ @Autowired
+ private SearchKrill searchKrill;
+
+ private ClientsHandler graphDBhandler;
+
+ @PostConstruct
+ private void doPostConstruct () {
+ this.rewriteHandler.defaultRewriteConstraints();
+
+ UriBuilder builder = UriBuilder.fromUri("http://10.0.10.13").port(9997);
+ this.graphDBhandler = new ClientsHandler(builder.build());
+ }
+
+ public String serializeQuery (String q, String ql, String v, String cq,
+ Integer pageIndex, Integer startPage, Integer pageLength,
+ String context, Boolean cutoff) {
+ QuerySerializer ss = new QuerySerializer().setQuery(q, ql, v);
+ if (cq != null) ss.setCollection(cq);
+
+ MetaQueryBuilder meta = new MetaQueryBuilder();
+ if (pageIndex != null) meta.addEntry("startIndex", pageIndex);
+ if (pageIndex == null && startPage != null)
+ meta.addEntry("startPage", startPage);
+ if (pageLength != null) meta.addEntry("count", pageLength);
+ if (context != null) meta.setSpanContext(context);
+ meta.addEntry("cutOff", cutoff);
+
+ ss.setMeta(meta.raw());
+ return ss.toJSON();
+ }
+
+ private User createUser (String username, HttpHeaders headers)
+ throws KustvaktException {
+ User user = authManager.getUser(username);
+ authManager.setAccessAndLocation(user, headers);
+ jlog.debug("Debug: /getMatchInfo/: location=" + user.locationtoString()
+ + ", access=" + user.accesstoString());
+ return user;
+ }
+
+ public String search (String jsonld) {
+ // MH: todo: should be possible to add the meta part to
+ // the query serialization
+ // User user = controller.getUser(ctx.getUsername());
+ // jsonld = this.processor.processQuery(jsonld, user);
+ return searchKrill.search(jsonld);
+ }
+
+ public String search (String engine, String username, HttpHeaders headers,
+ String q, String ql, String v, String cq, Integer pageIndex,
+ Integer pageInteger, String ctx, Integer pageLength, Boolean cutoff)
+ throws KustvaktException {
+
+ KustvaktConfiguration.BACKENDS eng = this.config.chooseBackend(engine);
+ User user = createUser(username, headers);
+
+ QuerySerializer serializer = new QuerySerializer();
+ serializer.setQuery(q, ql, v);
+ if (cq != null) serializer.setCollection(cq);
+
+ MetaQueryBuilder meta = createMetaQuery(pageIndex, pageInteger, ctx,
+ pageLength, cutoff);
+ serializer.setMeta(meta.raw());
+
+ // There is an error in query processing
+ // - either query, corpus or meta
+ if (serializer.hasErrors()) {
+ throw new KustvaktException(serializer.toJSON());
+ }
+
+ String query = this.rewriteHandler.processQuery(serializer.toJSON(), user);
+ jlog.info("the serialized query " + query);
+
+ String result;
+ if (eng.equals(KustvaktConfiguration.BACKENDS.NEO4J)) {
+ result = searchNeo4J(query, pageLength, meta, false);
+ }
+ else {
+ result = searchKrill.search(query);
+ }
+ jlog.debug("Query result: " + result);
+ return result;
+
+ }
+
+ private MetaQueryBuilder createMetaQuery (Integer pageIndex,
+ Integer pageInteger, String ctx, Integer pageLength,
+ Boolean cutoff) {
+ MetaQueryBuilder meta = new MetaQueryBuilder();
+ meta.addEntry("startIndex", pageIndex);
+ meta.addEntry("startPage", pageInteger);
+ meta.setSpanContext(ctx);
+ meta.addEntry("count", pageLength);
+ // todo: what happened to cutoff?
+ meta.addEntry("cutOff", cutoff);
+ // meta.addMeta(pageIndex, pageInteger, pageLength, ctx,
+ // cutoff);
+ // fixme: should only apply to CQL queries per default!
+ // meta.addEntry("itemsPerResource", 1);
+ return meta;
+ }
+
+ private String searchNeo4J (String query, int pageLength,
+ MetaQueryBuilder meta, boolean raw) throws KustvaktException {
+
+ if (raw) {
+ throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
+ "raw not supported!");
+ }
+
+ MultivaluedMap<String, String> map = new MultivaluedMapImpl();
+ map.add("q", query);
+ map.add("count", String.valueOf(pageLength));
+ map.add("lctxs", String.valueOf(meta.getSpanContext().getLeftSize()));
+ map.add("rctxs", String.valueOf(meta.getSpanContext().getRightSize()));
+ return this.graphDBhandler.getResponse(map, "distKwic");
+
+ }
+
+ public String retrieveMatchInfo (String corpusId, String docId,
+ String textId, String matchId, Set<String> foundries,
+ String username, HttpHeaders headers, Set<String> layers,
+ boolean spans) throws KustvaktException {
+ String matchid =
+ searchKrill.getMatchId(corpusId, docId, textId, matchId);
+
+ User user = createUser(username, headers);
+ CorpusAccess corpusAccess = user.getCorpusAccess();
+ Pattern p;
+ switch (corpusAccess) {
+ case PUB:
+ p = config.getPublicLicensePattern();
+ break;
+ case ALL:
+ p = config.getAllLicensePattern();
+ break;
+ default: // FREE
+ p = config.getFreeLicensePattern();
+ break;
+ }
+
+ boolean match_only = foundries == null || foundries.isEmpty();
+ String results;
+ try {
+ if (!match_only) {
+
+ ArrayList<String> foundryList = new ArrayList<String>();
+ ArrayList<String> layerList = new ArrayList<String>();
+
+ // EM: now without user, just list all foundries and
+ // layers
+ if (foundries.contains("*")) {
+ foundryList = config.getFoundries();
+ layerList = config.getLayers();
+ }
+ else {
+ foundryList.addAll(foundries);
+ layerList.addAll(layers);
+ }
+
+ results = searchKrill.getMatch(matchid, foundryList, layerList,
+ spans, false, true, p);
+ }
+ else {
+ results = searchKrill.getMatch(matchid, p);
+ }
+ }
+ catch (Exception e) {
+ jlog.error("Exception in the MatchInfo service encountered!", e);
+ throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
+ e.getMessage());
+ }
+ jlog.debug("MatchInfo results: " + results);
+ return results;
+ }
+
+ public String retrieveDocMetadata (String corpusId, String docId,
+ String textId) {
+ String textSigle = searchKrill.getTextSigle(corpusId, docId, textId);
+ return searchKrill.getFields(textSigle);
+ }
+
+ public String getCollocationBase (String query) throws KustvaktException {
+ return graphDBhandler.getResponse("distCollo", "q", query);
+ }
+}
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/KustvaktResponseHandler.java b/full/src/main/java/de/ids_mannheim/korap/web/KustvaktResponseHandler.java
index f7bef6a..bce194c 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/KustvaktResponseHandler.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/KustvaktResponseHandler.java
@@ -12,7 +12,9 @@
import de.ids_mannheim.korap.exceptions.StatusCodes;
import de.ids_mannheim.korap.interfaces.db.AuditingIface;
-/** KustvaktResponseHandler includes exceptions regarding authorization.
+/**
+ * KustvaktResponseHandler includes exceptions regarding
+ * authorization.
*
* @author margaretha
*
@@ -27,7 +29,12 @@
public WebApplicationException throwit (KustvaktException e) {
Response r;
- if (e.getStatusCode() == StatusCodes.USER_REAUTHENTICATION_REQUIRED
+ // KustvaktException just wraps another exception
+ if (e.getStatusCode() == null && e.hasNotification()) {
+ r = Response.status(Response.Status.BAD_REQUEST)
+ .entity(e.getNotification()).build();
+ }
+ else if (e.getStatusCode() == StatusCodes.USER_REAUTHENTICATION_REQUIRED
|| e.getStatusCode() == StatusCodes.AUTHORIZATION_FAILED
|| e.getStatusCode() >= StatusCodes.AUTHENTICATION_FAILED) {
String notification = buildNotification(e.getStatusCode(),
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java
index 9c01fa5..0371c9e 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/AuthenticationController.java
@@ -245,7 +245,7 @@
Map<String, Object> attr = new HashMap<>();
if (scopes != null && !scopes.isEmpty())
- attr.put(Attributes.SCOPES, scopes);
+ attr.put(Attributes.SCOPE, scopes);
attr.put(Attributes.HOST, host);
attr.put(Attributes.USER_AGENT, agent);
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java
index 2d76a18..2c43acc 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/OAuthController.java
@@ -224,7 +224,7 @@
StringBuilder scopes = new StringBuilder();
for (String scope : oauthRequest.getScopes())
scopes.append(scope + " ");
- attr.put(Attributes.SCOPES, scopes.toString());
+ attr.put(Attributes.SCOPE, scopes.toString());
try {
user = controller.getUser(c.getUsername());
@@ -486,7 +486,7 @@
Map<String, Object> attr = new HashMap<>();
attr.put(Attributes.HOST, host);
attr.put(Attributes.USER_AGENT, agent);
- attr.put(Attributes.SCOPES,
+ attr.put(Attributes.SCOPE,
StringUtils.toString(oauthRequest.getScopes(), " "));
// support code (for external clients only) and password grant type
@@ -534,7 +534,7 @@
}
// todo: errors for invalid scopes or different scopes then during authorization request?
//todo ??
- attr.put(Attributes.SCOPES, codeInfo.getScopes());
+ attr.put(Attributes.SCOPE, codeInfo.getScopes());
}
else if (oauthRequest.getGrantType().equalsIgnoreCase(
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/ResourceController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/ResourceController.java
index a603489..0bc87a4 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/ResourceController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/ResourceController.java
@@ -6,7 +6,6 @@
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.Response;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
@@ -14,9 +13,7 @@
import com.sun.jersey.spi.container.ResourceFilters;
import de.ids_mannheim.korap.dto.ResourceDto;
-import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.service.ResourceService;
-import de.ids_mannheim.korap.utils.JsonUtils;
import de.ids_mannheim.korap.web.filter.PiwikFilter;
/**
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
index d78eaea..d3481ec 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/SearchController.java
@@ -1,15 +1,10 @@
package de.ids_mannheim.korap.web.controller;// package
// de.ids_mannheim.korap.ext.web;
-import java.util.ArrayList;
-import java.util.HashMap;
import java.util.HashSet;
import java.util.Locale;
-import java.util.Map;
import java.util.Set;
-import java.util.regex.Pattern;
-import javax.annotation.PostConstruct;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
@@ -19,10 +14,8 @@
import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MediaType;
-import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.SecurityContext;
-import javax.ws.rs.core.UriBuilder;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
@@ -30,93 +23,211 @@
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.sun.jersey.core.util.MultivaluedMapImpl;
import com.sun.jersey.spi.container.ResourceFilters;
-import de.ids_mannheim.korap.cache.ResourceCache;
-import de.ids_mannheim.korap.config.Attributes;
-import de.ids_mannheim.korap.config.FullConfiguration;
-import de.ids_mannheim.korap.config.KustvaktConfiguration;
-import de.ids_mannheim.korap.config.KustvaktConfiguration.BACKENDS;
import de.ids_mannheim.korap.exceptions.KustvaktException;
-import de.ids_mannheim.korap.exceptions.StatusCodes;
-import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
-import de.ids_mannheim.korap.query.serialize.MetaQueryBuilder;
-import de.ids_mannheim.korap.query.serialize.QuerySerializer;
-import de.ids_mannheim.korap.resources.Corpus;
-import de.ids_mannheim.korap.resources.KustvaktResource;
-import de.ids_mannheim.korap.resources.ResourceFactory;
-import de.ids_mannheim.korap.resources.VirtualCollection;
-import de.ids_mannheim.korap.rewrite.FullRewriteHandler;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Scope;
+import de.ids_mannheim.korap.oauth2.service.OAuth2ScopeService;
import de.ids_mannheim.korap.security.context.TokenContext;
-import de.ids_mannheim.korap.user.User;
-import de.ids_mannheim.korap.user.User.CorpusAccess;
-import de.ids_mannheim.korap.utils.JsonUtils;
-import de.ids_mannheim.korap.utils.KoralCollectionQueryBuilder;
-import de.ids_mannheim.korap.utils.StringUtils;
-import de.ids_mannheim.korap.web.ClientsHandler;
-import de.ids_mannheim.korap.web.CoreResponseHandler;
-import de.ids_mannheim.korap.web.SearchKrill;
+import de.ids_mannheim.korap.service.SearchService;
+import de.ids_mannheim.korap.web.KustvaktResponseHandler;
import de.ids_mannheim.korap.web.filter.AuthenticationFilter;
import de.ids_mannheim.korap.web.filter.DemoUserFilter;
import de.ids_mannheim.korap.web.filter.PiwikFilter;
/**
*
- * @author hanl, margaretha
+ * @author hanl, margaretha, diewald
* @date 29/01/2014
- * @lastUpdate 01/2018
+ * @lastUpdate 09/07/2018
*
- * removed deprecated codes
*/
@Controller
@Path("/")
@RequestMapping("/")
-@ResourceFilters({ AuthenticationFilter.class, DemoUserFilter.class, PiwikFilter.class })
+@ResourceFilters({ AuthenticationFilter.class, DemoUserFilter.class,
+ PiwikFilter.class })
@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
public class SearchController {
- private static Logger jlog =
- LogManager.getLogger(SearchController.class);
+ private static Logger jlog = LogManager.getLogger(SearchController.class);
@Autowired
- private CoreResponseHandler kustvaktResponseHandler;
- @Autowired
- private SearchKrill searchKrill;
- private ResourceCache resourceHandler;
- @Autowired
- private AuthenticationManagerIface controller;
- private ClientsHandler graphDBhandler;
- @Autowired
- private FullConfiguration config;
- @Autowired
- private FullRewriteHandler processor;
+ private KustvaktResponseHandler kustvaktResponseHandler;
+ @Autowired
+ private SearchService searchService;
+ @Autowired
+ private OAuth2ScopeService scopeService;
- public SearchController () {
- this.resourceHandler = new ResourceCache();
- UriBuilder builder = UriBuilder.fromUri("http://10.0.10.13").port(9997);
- this.graphDBhandler = new ClientsHandler(builder.build());
+ /**
+ * Builds a json query serialization from the given parameters.
+ *
+ * @param locale
+ * @param securityContext
+ * @param q
+ * query string
+ * @param ql
+ * query language
+ * @param v
+ * version
+ * @param context
+ * @param cutoff
+ * true if the number of results should be limited
+ * @param pageLength
+ * number of results per page
+ * @param pageIndex
+ * @param startPage
+ * @param cq
+ * corpus query
+ * @return
+ */
+ // ref query parameter removed!
+ @GET
+ @Path("query")
+ public Response serializeQuery (@Context Locale locale,
+ @Context SecurityContext securityContext, @QueryParam("q") String q,
+ @QueryParam("ql") String ql, @QueryParam("v") String v,
+ @QueryParam("context") String context,
+ @QueryParam("cutoff") Boolean cutoff,
+ @QueryParam("count") Integer pageLength,
+ @QueryParam("offset") Integer pageIndex,
+ @QueryParam("page") Integer startPage,
+ @QueryParam("cq") String cq) {
+ TokenContext ctx = (TokenContext) securityContext.getUserPrincipal();
+ try {
+ scopeService.verifyScope(ctx, OAuth2Scope.SERIALIZE_QUERY);
+ }
+ catch (KustvaktException e) {
+ kustvaktResponseHandler.throwit(e);
+ }
+
+ String result = searchService.serializeQuery(q, ql, v, cq, pageIndex,
+ startPage, pageLength, context, cutoff);
+ jlog.debug("Query: " + result);
+ return Response.ok(result).build();
}
- @PostConstruct
- private void doPostConstruct () {
- this.processor.defaultRewriteConstraints();
+ @POST
+ @Path("search")
+ public Response searchPost (@Context SecurityContext context,
+ @Context Locale locale, @QueryParam("engine") String engine,
+ String jsonld) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ try {
+ scopeService.verifyScope(ctx, OAuth2Scope.SEARCH);
+ }
+ catch (KustvaktException e) {
+ throw kustvaktResponseHandler.throwit(e);
+ }
+
+ jlog.debug("Serialized search: " + jsonld);
+ String result = searchService.search(jsonld);
+ jlog.debug("The result set: " + result);
+ return Response.ok(result).build();
+ }
+
+
+ @GET
+ @Path("search")
+ public Response searchGet (@Context SecurityContext securityContext,
+ @Context HttpHeaders headers, @Context Locale locale,
+ @QueryParam("q") String q, @QueryParam("ql") String ql,
+ @QueryParam("v") String v, @QueryParam("context") String ctx,
+ @QueryParam("cutoff") Boolean cutoff,
+ @QueryParam("count") Integer pageLength,
+ @QueryParam("offset") Integer pageIndex,
+ @QueryParam("page") Integer pageInteger,
+ @QueryParam("cq") String cq, @QueryParam("engine") String engine) {
+
+ TokenContext context =
+ (TokenContext) securityContext.getUserPrincipal();
+
+ String result;
+ try {
+ scopeService.verifyScope(context, OAuth2Scope.SEARCH);
+ result = searchService.search(engine, context.getUsername(),
+ headers, q, ql, v, cq, pageIndex, pageInteger, ctx,
+ pageLength, cutoff);
+ }
+ catch (KustvaktException e) {
+ throw kustvaktResponseHandler.throwit(e);
+ }
+
+ return Response.ok(result).build();
+ }
+
+ @GET
+ @Path("/corpus/{corpusId}/{docId}/{textId}/{matchId}/matchInfo")
+ public Response getMatchInfo (@Context SecurityContext ctx,
+ @Context HttpHeaders headers, @Context Locale locale,
+ @PathParam("corpusId") String corpusId,
+ @PathParam("docId") String docId,
+ @PathParam("textId") String textId,
+ @PathParam("matchId") String matchId,
+ @QueryParam("foundry") Set<String> foundries,
+ @QueryParam("layer") Set<String> layers,
+ @QueryParam("spans") Boolean spans) throws KustvaktException {
+
+ TokenContext tokenContext = (TokenContext) ctx.getUserPrincipal();
+ scopeService.verifyScope(tokenContext, OAuth2Scope.MATCH_INFO);
+ spans = spans != null ? spans : false;
+ if (layers == null || layers.isEmpty()) layers = new HashSet<>();
+
+ String results = searchService.retrieveMatchInfo(corpusId, docId,
+ textId, matchId, foundries, tokenContext.getUsername(), headers,
+ layers, spans);
+ return Response.ok(results).build();
+ }
+
+
+ /*
+ * Returns the meta data fields of a certain document
+ */
+ // This is currently identical to LiteService#getMeta(),
+ // but may need auth code to work following policies
+ @GET
+ @Path("/corpus/{corpusId}/{docId}/{textId}")
+ public Response getMetadata (@PathParam("corpusId") String corpusId,
+ @PathParam("docId") String docId, @PathParam("textId") String textId
+ // @QueryParam("fields") Set<String> fields
+ ) throws KustvaktException {
+ String results =
+ searchService.retrieveDocMetadata(corpusId, docId, textId);
+ return Response.ok(results).build();
+ }
+
+
+ @POST
+ @Path("colloc")
+ public Response getCollocationBase (@QueryParam("q") String query) {
+ String result;
+ try {
+ result = searchService.getCollocationBase(query);
+ }
+ catch (KustvaktException e) {
+ throw kustvaktResponseHandler.throwit(e);
+ }
+ return Response.ok(result).build();
}
// @GET
// @Path("colloc")
- // public Response getCollocationsAll(@Context SecurityContext ctx,
+ // public Response getCollocationsAll(@Context SecurityContext
+ // ctx,
// @Context Locale locale, @QueryParam("props") String properties,
// @QueryParam("sfskip") Integer sfs,
- // @QueryParam("sflimit") Integer limit, @QueryParam("q") String query,
- // @QueryParam("ql") String ql, @QueryParam("context") Integer context,
+ // @QueryParam("sflimit") Integer limit, @QueryParam("q") String
+ // query,
+ // @QueryParam("ql") String ql, @QueryParam("context") Integer
+ // context,
// @QueryParam("foundry") String foundry,
// @QueryParam("paths") Boolean wPaths) {
- // TokenContext tokenContext = (TokenContext) ctx.getUserPrincipal();
+ // TokenContext tokenContext = (TokenContext)
+ // ctx.getUserPrincipal();
// ColloQuery.ColloQueryBuilder builder;
- // KoralCollectionQueryBuilder cquery = new KoralCollectionQueryBuilder();
+ // KoralCollectionQueryBuilder cquery = new
+ // KoralCollectionQueryBuilder();
// String result;
// try {
// User user = controller.getUser(tokenContext.getUsername());
@@ -135,7 +246,8 @@
// }catch (KustvaktException e) {
// throw KustvaktResponseHandler.throwit(e);
// }catch (JsonProcessingException e) {
- // throw KustvaktResponseHandler.throwit(StatusCodes.ILLEGAL_ARGUMENT);
+ // throw
+ // KustvaktResponseHandler.throwit(StatusCodes.ILLEGAL_ARGUMENT);
// }
// return Response.ok(result).build();
// }
@@ -143,7 +255,8 @@
// /**
// * @param locale
- // * @param properties a json object string containing field, op and value
+ // * @param properties a json object string containing field, op
+ // and value
// for the query
// * @param query
// * @param context
@@ -154,18 +267,23 @@
// public Response getCollocations(@Context SecurityContext ctx,
// @Context Locale locale, @QueryParam("props") String properties,
// @QueryParam("sfskip") Integer sfs,
- // @QueryParam("sflimit") Integer limit, @QueryParam("q") String query,
- // @QueryParam("ql") String ql, @QueryParam("context") Integer context,
+ // @QueryParam("sflimit") Integer limit, @QueryParam("q") String
+ // query,
+ // @QueryParam("ql") String ql, @QueryParam("context") Integer
+ // context,
// @QueryParam("foundry") String foundry,
- // @QueryParam("paths") Boolean wPaths, @PathParam("id") String id,
+ // @QueryParam("paths") Boolean wPaths, @PathParam("id") String
+ // id,
// @PathParam("type") String type) {
// ColloQuery.ColloQueryBuilder builder;
// type = StringUtils.normalize(type);
// id = StringUtils.decodeHTML(id);
- // TokenContext tokenContext = (TokenContext) ctx.getUserPrincipal();
+ // TokenContext tokenContext = (TokenContext)
+ // ctx.getUserPrincipal();
// String result;
// try {
- // KoralCollectionQueryBuilder cquery = new KoralCollectionQueryBuilder();
+ // KoralCollectionQueryBuilder cquery = new
+ // KoralCollectionQueryBuilder();
// try {
// User user = controller.getUser(tokenContext.getUsername());
//
@@ -199,483 +317,13 @@
// .getResponse("distCollo", "q", builder.build().toJSON());
//
// }catch (JsonProcessingException e) {
- // throw KustvaktResponseHandler.throwit(StatusCodes.ILLEGAL_ARGUMENT);
+ // throw
+ // KustvaktResponseHandler.throwit(StatusCodes.ILLEGAL_ARGUMENT);
// }catch (KustvaktException e) {
// throw KustvaktResponseHandler.throwit(e);
// }
//
// return Response.ok(result).build();
// }
- @POST
- @Path("colloc")
- public Response getCollocationBase (@QueryParam("q") String query) {
- String result;
- try {
- result = graphDBhandler.getResponse("distCollo", "q", query);
- }
- catch (KustvaktException e) {
- throw kustvaktResponseHandler.throwit(e);
- }
- return Response.ok(result).build();
- }
-
-
- /** Builds a json query serialization from the given parameters.
- *
- * @param locale
- * @param securityContext
- * @param q query string
- * @param ql query language
- * @param v version
- * @param context
- * @param cutoff true if the number of results should be limited
- * @param pageLength number of results per page
- * @param pageIndex
- * @param startPage
- * @param cq corpus query
- * @return
- */
- // ref query parameter removed!
- // EM: change the HTTP method to from TRACE to GET
- // EM: change path from search to query
- @GET
- @Path("query")
- public Response serializeQuery (@Context Locale locale,
- @Context SecurityContext securityContext, @QueryParam("q") String q,
- @QueryParam("ql") String ql, @QueryParam("v") String v,
- @QueryParam("context") String context,
- @QueryParam("cutoff") Boolean cutoff,
- @QueryParam("count") Integer pageLength,
- @QueryParam("offset") Integer pageIndex,
- @QueryParam("page") Integer startPage,
- @QueryParam("cq") String cq) {
- TokenContext ctx = (TokenContext) securityContext.getUserPrincipal();
- QuerySerializer ss = new QuerySerializer().setQuery(q, ql, v);
- if (cq != null) ss.setCollection(cq);
-
- MetaQueryBuilder meta = new MetaQueryBuilder();
- if (pageIndex != null) meta.addEntry("startIndex", pageIndex);
- if (pageIndex == null && startPage != null)
- meta.addEntry("startPage", startPage);
- if (pageLength != null) meta.addEntry("count", pageLength);
- if (context != null) meta.setSpanContext(context);
- meta.addEntry("cutOff", cutoff);
-
- ss.setMeta(meta.raw());
- String result = ss.toJSON();
- jlog.debug("Query: " + result);
- return Response.ok(result).build();
- }
-
-
- /**
- * currently only supports either no reference at all in which
- * case all corpora are retrieved or a corpus name like "WPD".
- * No virtual collections supported!
- *
- * @param locale
- * @param q
- * @param ql
- * @param v
- * @param pageLength
- * @param pageIndex
- * @return
- */
-
- // todo: does cq have any sensible worth here? --> would say no! --> is
- // useful in non type/id scenarios
-
- /* EM: potentially an unused service! */
- // EM: build query using the given virtual collection id
- // EM: change the HTTP method to from TRACE to GET
- // EM: change path from search to query
- // EM: there is no need to check resource licenses since the service just serialize a query serialization
- @GET
- @Path("{type}/{id}/query")
- public Response serializeQueryWithResource (@Context Locale locale,
- @Context SecurityContext securityContext, @QueryParam("q") String q,
- @QueryParam("ql") String ql, @QueryParam("v") String v,
- @QueryParam("context") String context,
- @QueryParam("cutoff") Boolean cutoff,
- @QueryParam("count") Integer pageLength,
- @QueryParam("offset") Integer pageIndex,
- @QueryParam("page") Integer startPage,
- @PathParam("type") String type, @PathParam("id") String id) {
- TokenContext ctx = (TokenContext) securityContext.getUserPrincipal();
- type = StringUtils.normalize(type);
- id = StringUtils.decodeHTML(id);
-
- QuerySerializer ss = new QuerySerializer().setQuery(q, ql, v);
-
- MetaQueryBuilder meta = new MetaQueryBuilder();
- if (pageIndex != null) meta.addEntry("startIndex", pageIndex);
- if (pageIndex == null && startPage != null)
- meta.addEntry("startPage", startPage);
- if (pageLength != null) meta.addEntry("count", pageLength);
- if (context != null) meta.setSpanContext(context);
- if (cutoff != null) meta.addEntry("cutOff", cutoff);
-
- ss.setMeta(meta.raw());
-
- KoralCollectionQueryBuilder cquery = new KoralCollectionQueryBuilder();
- try {
- cquery.setBaseQuery(ss.toJSON());
- }
- catch (KustvaktException e1) {
- throw kustvaktResponseHandler.throwit(e1);
- }
-
- String query = "";
- // EM: is this necessary at all?
- KustvaktResource resource = isCollectionIdValid(ctx.getName(), id);
- if (resource != null) {
- try {
- if (resource instanceof VirtualCollection) {
- JsonNode node = cquery.and().mergeWith(resource.getData());
- query = JsonUtils.toJSON(node);
- }
- else if (resource instanceof Corpus) {
- cquery.and().with(Attributes.CORPUS_SIGLE, "=",
- resource.getPersistentID());
-
- query = cquery.toJSON();
- }
- }
- catch (KustvaktException e) {
- throw kustvaktResponseHandler.throwit(e);
- }
- }
-
- jlog.debug("Query: " + query);
- return Response.ok(query).build();
- }
-
- // EM: prototype
- private KustvaktResource isCollectionIdValid (String username,
- String collectionId) {
-
- // try {
- // if (ctx.isDemo()) {
- // // EM: FIX ME: Is there public VCs? set default username
- // for nonlogin user, change demo?
- // Set set = ResourceFinder.searchPublicFiltered(
- // ResourceFactory.getResourceClass(type), id);
- // resource = (KustvaktResource) set.toArray()[0];
- // }
- // else {
- // // EM: FIX ME: search in user VC
- // User user = controller.getUser(ctx.getUsername());
- // if (StringUtils.isInteger(id))
- // resource = this.resourceHandler
- // .findbyIntId(Integer.valueOf(id), user);
- // else
- // resource = this.resourceHandler.findbyStrId(id, user,
- // ResourceFactory.getResourceClass(type));
- // }
- // }
- // // todo: instead of throwing exception, build notification and rewrites
- // // into result query
- // catch (KustvaktException e) {
- // jlog.error("Exception encountered: {}", e.string());
- // throw KustvaktResponseHandler.throwit(e);
- // }
-
- return null;
- }
-
-
- @POST
- @Path("search")
- public Response queryRaw (@Context SecurityContext context,
- @Context Locale locale, @QueryParam("engine") String engine,
- String jsonld) {
- TokenContext ctx = (TokenContext) context.getUserPrincipal();
-
- // todo: should be possible to add the meta part to the query
- // serialization
- try {
- User user = controller.getUser(ctx.getUsername());
- // jsonld = this.processor.processQuery(jsonld, user);
- }
- catch (KustvaktException e) {
- throw kustvaktResponseHandler.throwit(e);
- }
- jlog.info("Serialized search: "+ jsonld);
-
- String result = searchKrill.search(jsonld);
- // todo: logging
- jlog.trace("The result set: "+ result);
- return Response.ok(result).build();
- }
-
-
- @GET
- @Path("search")
- public Response search (@Context SecurityContext securityContext,
- @Context HttpHeaders headers, @Context Locale locale,
- @QueryParam("q") String q, @QueryParam("ql") String ql,
- @QueryParam("v") String v, @QueryParam("context") String ctx,
- @QueryParam("cutoff") Boolean cutoff,
- @QueryParam("count") Integer pageLength,
- @QueryParam("offset") Integer pageIndex,
- @QueryParam("page") Integer pageInteger,
- @QueryParam("cq") String cq, @QueryParam("engine") String engine) {
-
- TokenContext context =
- (TokenContext) securityContext.getUserPrincipal();
- KustvaktConfiguration.BACKENDS eng = this.config.chooseBackend(engine);
- User user;
- try {
- user = controller.getUser(context.getUsername());
- controller.setAccessAndLocation(user, headers);
- // System.out.printf("Debug: /search/: location=%s, access='%s'.\n", user.locationtoString(), user.accesstoString());
- }
- catch (KustvaktException e) {
- jlog.error("Failed retrieving user in the search service: "+
- e.string());
- throw kustvaktResponseHandler.throwit(e);
- }
-
- QuerySerializer serializer = new QuerySerializer();
- serializer.setQuery(q, ql, v);
- if (cq != null) serializer.setCollection(cq);
-
- MetaQueryBuilder meta = createMetaQuery(pageIndex, pageInteger, ctx,
- pageLength, cutoff);
- serializer.setMeta(meta.raw());
-
- // There is an error in query processing
- // - either query, corpus or meta
- if (serializer.hasErrors()) {
-
- // Do not pass further to backend
- return Response.status(Response.Status.BAD_REQUEST).entity(serializer.toJSON()).build();
- };
-
- String query;
- try {
- query = this.processor.processQuery(serializer.toJSON(), user);
- jlog.info("the serialized query "+ query);
- }
- catch (KustvaktException e) {
- throw kustvaktResponseHandler.throwit(e);
- }
-
- String result = doSearch(eng, query, pageLength, meta);
- jlog.debug("Query result: " + result);
- return Response.ok(result).build();
- }
-
-
- private MetaQueryBuilder createMetaQuery (Integer pageIndex,
- Integer pageInteger, String ctx, Integer pageLength,
- Boolean cutoff) {
- MetaQueryBuilder meta = new MetaQueryBuilder();
- meta.addEntry("startIndex", pageIndex);
- meta.addEntry("startPage", pageInteger);
- meta.setSpanContext(ctx);
- meta.addEntry("count", pageLength);
- // todo: what happened to cutoff?
- meta.addEntry("cutOff", cutoff);
- // meta.addMeta(pageIndex, pageInteger, pageLength, ctx, cutoff);
- // fixme: should only apply to CQL queries per default!
- // meta.addEntry("itemsPerResource", 1);
- return meta;
- }
-
-
- private String doSearch (BACKENDS eng, String query, Integer pageLength,
- MetaQueryBuilder meta) {
- String result;
- if (eng.equals(KustvaktConfiguration.BACKENDS.NEO4J)) {
- result = searchNeo4J(query, pageLength, meta, false);
- }
- else {
- result = searchKrill.search(query);
- }
- jlog.trace("The result set: "+ result);
- return result;
-
- }
-
-
- private String searchNeo4J (String query, int pageLength,
- MetaQueryBuilder meta, boolean raw) {
-
- if (raw) {
- throw kustvaktResponseHandler.throwit(StatusCodes.ILLEGAL_ARGUMENT,
- "raw not supported!", null);
- }
-
- MultivaluedMap<String, String> map = new MultivaluedMapImpl();
- map.add("q", query);
- map.add("count", String.valueOf(pageLength));
- map.add("lctxs", String.valueOf(meta.getSpanContext().getLeftSize()));
- map.add("rctxs", String.valueOf(meta.getSpanContext().getRightSize()));
- try {
- return this.graphDBhandler.getResponse(map, "distKwic");
- }
- catch (KustvaktException e) {
- jlog.error("Failed searching in Neo4J: "+ e.string());
- throw kustvaktResponseHandler.throwit(e);
- }
-
- }
-
-
- /**
- * @param context
- * @param locale
- * @param json
- * @return
- */
- // todo: rename
- @POST
- @Path("collection_raw")
- public Response createRawCollection (@Context SecurityContext context,
- @Context Locale locale, String json) {
- TokenContext c = (TokenContext) context.getUserPrincipal();
- VirtualCollection cache = ResourceFactory.getCachedCollection(json);
- User user;
- try {
- user = controller.getUser(c.getUsername());
- }
- catch (KustvaktException e) {
- jlog.error("Exception encountered: "+ e.string());
- throw kustvaktResponseHandler.throwit(e);
- }
-
- VirtualCollection tmp = resourceHandler.getCache(cache.getId(),
- VirtualCollection.class);
- if (tmp == null) {
- String query;
- try {
- query = this.processor.processQuery(cache.getData(), user);
- String stats = searchKrill.getStatistics(query);
- cache.setStats(JsonUtils.convertToClass(stats, Map.class));
- }
- catch (KustvaktException e) {
- throw kustvaktResponseHandler.throwit(e);
- }
- resourceHandler.cache(cache);
- }
- else
- cache = tmp;
-
- Map vals = new HashMap();
- vals.put("id", cache.getId());
- vals.put("statistics", cache.getStats());
- try {
- return Response.ok(JsonUtils.toJSON(vals)).build();
- }
- catch (KustvaktException e) {
- throw kustvaktResponseHandler.throwit(e);
- }
- }
-
-
- @GET
- @Path("/corpus/{corpusId}/{docId}/{textId}/{matchId}/matchInfo")
- public Response getMatchInfo (@Context SecurityContext ctx,
- @Context HttpHeaders headers, @Context Locale locale,
- @PathParam("corpusId") String corpusId,
- @PathParam("docId") String docId,
- @PathParam("textId") String textId,
- @PathParam("matchId") String matchId,
- @QueryParam("foundry") Set<String> foundries,
- @QueryParam("layer") Set<String> layers,
- @QueryParam("spans") Boolean spans) throws KustvaktException {
-
- TokenContext tokenContext = (TokenContext) ctx.getUserPrincipal();
- spans = spans != null ? spans : false;
-
- String matchid =
- searchKrill.getMatchId(corpusId, docId, textId, matchId);
- if (layers == null || layers.isEmpty()) layers = new HashSet<>();
-
- boolean match_only = foundries == null || foundries.isEmpty();
-
- User user;
- try {
- user = controller.getUser(tokenContext.getUsername());
- controller.setAccessAndLocation(user, headers);
- System.out.printf(
- "Debug: /getMatchInfo/: location=%s, access='%s'.\n",
- user.locationtoString(), user.accesstoString());
- }
- catch (KustvaktException e) {
- jlog.error("Failed getting user in the matchInfo service: "+
- e.string());
- throw kustvaktResponseHandler.throwit(e);
- }
-
- CorpusAccess corpusAccess = user.getCorpusAccess();
- Pattern p;
- switch (corpusAccess) {
- case PUB:
- p = config.getPublicLicensePattern();
- break;
- case ALL:
- p = config.getAllLicensePattern();
- break;
- default: // FREE
- p = config.getFreeLicensePattern();
- break;
- }
-
- String results;
- try {
- if (!match_only) {
-
- ArrayList<String> foundryList = new ArrayList<String>();
- ArrayList<String> layerList = new ArrayList<String>();
-
- // EM: now without user, just list all foundries and layers
- if (foundries.contains("*")) {
- foundryList = config.getFoundries();
- layerList = config.getLayers();
- }
- else {
- foundryList.addAll(foundries);
- layerList.addAll(layers);
- }
-
- results = searchKrill.getMatch(matchid, foundryList, layerList,
- spans, false, true, p);
- }
- else {
- results = searchKrill.getMatch(matchid, p);
- }
- }
- catch (Exception e) {
- jlog.error("Exception in the MatchInfo service encountered!", e);
- throw kustvaktResponseHandler.throwit(StatusCodes.ILLEGAL_ARGUMENT,
- e.getMessage(), "");
- }
- jlog.debug("MatchInfo results: " + results);
- return Response.ok(results).build();
- }
-
-
- /*
- * Returns the meta data fields of a certain document
- */
- // This is currently identical to LiteService#getMeta(),
- // but may need auth code to work following policies
- @GET
- @Path("/corpus/{corpusId}/{docId}/{textId}")
- public Response getMeta (
- @PathParam("corpusId") String corpusId,
- @PathParam("docId") String docId,
- @PathParam("textId") String textId
- // @QueryParam("fields") Set<String> fields
- ) throws KustvaktException {
-
- String textSigle = searchKrill.getTextSigle(corpusId, docId, textId);
-
- String results = searchKrill.getFields(textSigle);
-
- return Response.ok(results).build();
- }
}
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
index dd34a1f..a0850ce 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/UserGroupController.java
@@ -38,7 +38,8 @@
* adding members to a group and subscribing (confirming an
* invitation) to a group.
*
- * These APIs are only available to logged-in users.
+ * These APIs are only available to logged-in users and not available
+ * via third-party apps.
*
* @author margaretha
*
@@ -54,9 +55,12 @@
@Autowired
private UserGroupService service;
- /** Returns all user-groups in which a user is an active or a pending member.
- * Not suitable for system-admin, instead use {@link UserGroupController#
- * getUserGroupBySystemAdmin(SecurityContext, String, UserGroupStatus)}
+ /**
+ * Returns all user-groups in which a user is an active or a
+ * pending member.
+ * Not suitable for system-admin, instead use
+ * {@link UserGroupController#
+ * getUserGroupBySystemAdmin(SecurityContext, String, UserGroupStatus)}
*
* @param securityContext
* @return a list of user-groups
@@ -78,13 +82,18 @@
}
- /** Lists user-groups for system-admin purposes. If username parameter
- * is not specified, list user-groups of all users. If status is not
- * specified, list user-groups of all statuses.
+ /**
+ * Lists user-groups for system-admin purposes. If username
+ * parameter
+ * is not specified, list user-groups of all users. If status is
+ * not
+ * specified, list user-groups of all statuses.
*
* @param securityContext
- * @param username username
- * @param status {@link UserGroupStatus}
+ * @param username
+ * username
+ * @param status
+ * {@link UserGroupStatus}
* @return a list of user-groups
*/
@GET
@@ -105,10 +114,12 @@
}
}
- /** Retrieves a specific user-group for system admins.
+ /**
+ * Retrieves a specific user-group for system admins.
*
* @param securityContext
- * @param groupId group id
+ * @param groupId
+ * group id
* @return a user-group
*/
@GET
@@ -128,23 +139,25 @@
}
- /** Creates a user group where the user in token context is the
- * group owner, and assigns the listed group members with status
- * GroupMemberStatus.PENDING.
+ /**
+ * Creates a user group where the user in token context is the
+ * group owner, and assigns the listed group members with status
+ * GroupMemberStatus.PENDING.
*
* Invitations must be sent to these proposed members. If a member
- * accepts the invitation, update his GroupMemberStatus to
- * GroupMemberStatus.ACTIVE by using
+ * accepts the invitation, update his GroupMemberStatus to
+ * GroupMemberStatus.ACTIVE by using
* {@link UserGroupController#subscribeToGroup(SecurityContext, String)}.
*
- * If he rejects the invitation, update his GroupMemberStatus
- * to GroupMemberStatus.DELETED using
+ * If he rejects the invitation, update his GroupMemberStatus
+ * to GroupMemberStatus.DELETED using
* {@link UserGroupController#unsubscribeFromGroup(SecurityContext, String)}.
*
- *
+ *
*
* @param securityContext
- * @param group UserGroupJson
+ * @param group
+ * UserGroupJson
* @return if successful, HTTP response status OK
*/
@POST
@@ -163,8 +176,10 @@
}
}
- /** Deletes a user-group specified by the group id. Only group owner
- * and system admins can delete groups.
+ /**
+ * Deletes a user-group specified by the group id. Only group
+ * owner
+ * and system admins can delete groups.
*
* @param securityContext
* @param groupId
@@ -185,11 +200,14 @@
}
}
- /** Deletes a user-group member. Group owner cannot be deleted.
+ /**
+ * Deletes a user-group member. Group owner cannot be deleted.
*
* @param securityContext
- * @param memberId a username of a group member
- * @param groupId a group id
+ * @param memberId
+ * a username of a group member
+ * @param groupId
+ * a group id
* @return if successful, HTTP response status OK
*/
@DELETE
@@ -209,12 +227,16 @@
}
}
- /** Invites group members to join a user-group specified in the JSON object.
- * Only user-group admins and system admins are allowed.
+ /**
+ * Invites group members to join a user-group specified in the
+ * JSON object.
+ * Only user-group admins and system admins are allowed.
*
* @param securityContext
- * @param group UserGroupJson containing groupId and usernames to be invited
- * as members
+ * @param group
+ * UserGroupJson containing groupId and usernames to be
+ * invited
+ * as members
* @return if successful, HTTP response status OK
*/
@POST
@@ -233,13 +255,18 @@
}
}
- /** Adds roles of an active member of a user-group. Only user-group admins
+ /**
+ * Adds roles of an active member of a user-group. Only user-group
+ * admins
* and system admins are allowed.
*
* @param securityContext
- * @param groupId a group id
- * @param memberUsername the username of a group member
- * @param roleIds list of role ids
+ * @param groupId
+ * a group id
+ * @param memberUsername
+ * the username of a group member
+ * @param roleIds
+ * list of role ids
* @return if successful, HTTP response status OK
*/
@POST
@@ -261,13 +288,18 @@
}
}
- /** Deletes roles of a member of a user-group. Only user-group admins
+ /**
+ * Deletes roles of a member of a user-group. Only user-group
+ * admins
* and system admins are allowed.
*
* @param securityContext
- * @param groupId a group id
- * @param memberUsername the username of a group member
- * @param roleIds list of role ids
+ * @param groupId
+ * a group id
+ * @param memberUsername
+ * the username of a group member
+ * @param roleIds
+ * list of role ids
* @return if successful, HTTP response status OK
*/
@POST
@@ -289,11 +321,14 @@
}
}
- /** Handles requests to accept membership invitation. Only invited users
- * can subscribe to the corresponding user-group.
+ /**
+ * Handles requests to accept membership invitation. Only invited
+ * users
+ * can subscribe to the corresponding user-group.
*
* @param securityContext
- * @param groupId a group id
+ * @param groupId
+ * a group id
* @return if successful, HTTP response status OK
*/
@POST
@@ -312,8 +347,10 @@
}
}
- /** Handles requests to reject membership invitation. A member can only
- * unsubscribe him/herself from a group.
+ /**
+ * Handles requests to reject membership invitation. A member can
+ * only
+ * unsubscribe him/herself from a group.
*
* Implemented identical to delete group member.
*
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java b/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
index f8198e5..05edea1 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/controller/VirtualCorpusController.java
@@ -26,6 +26,8 @@
import de.ids_mannheim.korap.dto.VirtualCorpusAccessDto;
import de.ids_mannheim.korap.dto.VirtualCorpusDto;
import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Scope;
+import de.ids_mannheim.korap.oauth2.service.OAuth2ScopeService;
import de.ids_mannheim.korap.security.context.TokenContext;
import de.ids_mannheim.korap.service.VirtualCorpusService;
import de.ids_mannheim.korap.web.KustvaktResponseHandler;
@@ -34,14 +36,15 @@
import de.ids_mannheim.korap.web.filter.PiwikFilter;
import de.ids_mannheim.korap.web.input.VirtualCorpusJson;
-/** VirtualCorpusController defines web APIs related to virtual corpus (VC)
- * such as creating, deleting and listing user virtual corpora.
+/**
+ * VirtualCorpusController defines web APIs related to virtual corpus
+ * (VC) such as creating, deleting and listing user virtual corpora.
*
- * This class also includes APIs related to virtual corpus access (VCA)
- * such as sharing and publishing VC. When a VC is published, it is shared
- * with all users, but not always listed like system VC. It is listed for
- * a user, once when he/she have searched for the VC. A VC can be published
- * by creating or editing the VC.
+ * This class also includes APIs related to virtual corpus access
+ * (VCA) such as sharing and publishing VC. When a VC is published,
+ * it is shared with all users, but not always listed like system
+ * VC. It is listed for a user, once when he/she have searched for the
+ * VC. A VC can be published by creating or editing the VC.
*
* All the APIs in this class are available to logged-in users.
*
@@ -58,13 +61,17 @@
private KustvaktResponseHandler kustvaktResponseHandler;
@Autowired
private VirtualCorpusService service;
+ @Autowired
+ private OAuth2ScopeService scopeService;
- /** Creates a user virtual corpus, also for system admins
+ /**
+ * Creates a user virtual corpus, also for system admins
*
* @see VirtualCorpusJson
*
* @param securityContext
- * @param vc a JSON object describing the virtual corpus
+ * @param vc
+ * a JSON object describing the virtual corpus
* @return HTTP Response OK if successful
*/
@POST
@@ -77,6 +84,7 @@
TokenContext context =
(TokenContext) securityContext.getUserPrincipal();
+ scopeService.verifyScope(context, OAuth2Scope.CREATE_VC);
service.storeVC(vc, context.getUsername());
}
catch (KustvaktException e) {
@@ -85,14 +93,16 @@
return Response.ok().build();
}
- /** Edits a virtual corpus attributes including name, type and corpus
- * query. Only the virtual corpus owner and system admins can edit
- * a virtual corpus.
+ /**
+ * Edits a virtual corpus attributes including name, type and
+ * corpus query. Only the virtual corpus owner and system admins
+ * can edit a virtual corpus.
*
* @see VirtualCorpusJson
*
* @param securityContext
- * @param vc a JSON object describing the virtual corpus
+ * @param vc
+ * a JSON object describing the virtual corpus
* @return HTTP Response OK if successful
* @throws KustvaktException
*/
@@ -105,6 +115,7 @@
(TokenContext) securityContext.getUserPrincipal();
try {
+ scopeService.verifyScope(context, OAuth2Scope.EDIT_VC);
service.editVC(vc, context.getUsername());
}
catch (KustvaktException e) {
@@ -113,20 +124,24 @@
return Response.ok().build();
}
- /** Searches for a specific VC given the VC id.
+ /**
+ * Searches for a specific VC given the VC id.
*
* @param securityContext
- * @param vcId a virtual corpus id
+ * @param vcId
+ * a virtual corpus id
* @return a list of virtual corpora
*/
@GET
@Path("{vcId}")
@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
- public VirtualCorpusDto retrieveVC (@Context SecurityContext securityContext,
+ public VirtualCorpusDto retrieveVC (
+ @Context SecurityContext securityContext,
@PathParam("vcId") int vcId) {
TokenContext context =
(TokenContext) securityContext.getUserPrincipal();
try {
+ scopeService.verifyScope(context, OAuth2Scope.VC_INFO);
return service.searchVCById(context.getUsername(), vcId);
}
catch (KustvaktException e) {
@@ -134,15 +149,17 @@
}
}
- /** Lists not only private virtual corpora but all virtual corpora
- * available to a user.
- *
- * Users, except system admins, cannot list virtual corpora of
- * other users. Thus, createdBy parameter is only relevant for
- * requests from system admins.
+ /**
+ * Lists not only private virtual corpora but all virtual corpora
+ * available to a user.
+ *
+ * Users, except system admins, cannot list virtual corpora of
+ * other users. Thus, createdBy parameter is only relevant for
+ * requests from system admins.
*
* @param securityContext
- * @param createdBy username of virtual corpus creator (optional)
+ * @param createdBy
+ * username of virtual corpus creator (optional)
* @return a list of virtual corpora
*/
@GET
@@ -154,6 +171,7 @@
TokenContext context =
(TokenContext) securityContext.getUserPrincipal();
try {
+ scopeService.verifyScope(context, OAuth2Scope.VC_INFO);
return service.listVCByUser(context.getUsername(), createdBy);
}
catch (KustvaktException e) {
@@ -161,11 +179,12 @@
}
}
- /** Lists all virtual corpora created by a user
+ /**
+ * Lists all virtual corpora created by a user
*
* @param securityContext
- * @return a list of virtual corpora created by the user
- * in the security context.
+ * @return a list of virtual corpora created by the user
+ * in the security context.
*/
@GET
@Path("list/user")
@@ -175,6 +194,7 @@
TokenContext context =
(TokenContext) securityContext.getUserPrincipal();
try {
+ scopeService.verifyScope(context, OAuth2Scope.VC_INFO);
return service.listOwnerVC(context.getUsername());
}
catch (KustvaktException e) {
@@ -182,16 +202,19 @@
}
}
- /** Lists virtual corpora by creator and type. This is a controller for
- * system admin requiring valid system admin authentication.
- *
- * If type is not specified, retrieves virtual corpora of all types.
- * If createdBy is not specified, retrieves virtual corpora of all
- * users.
- *
+ /**
+ * Lists virtual corpora by creator and type. This is a controller
+ * for system admin requiring valid system admin authentication.
+ *
+ * If type is not specified, retrieves virtual corpora of all
+ * types. If createdBy is not specified, retrieves virtual corpora
+ * of all users.
+ *
* @param securityContext
- * @param createdBy username of virtual corpus creator
- * @param type {@link VirtualCorpusType}
+ * @param createdBy
+ * username of virtual corpus creator
+ * @param type
+ * {@link VirtualCorpusType}
* @return a list of virtual corpora
*/
@GET
@@ -211,12 +234,14 @@
}
}
- /** Only the VC owner and system admins can delete VC. VCA admins
- * can delete VC-accesses e.g. of project VC, but not the VC
- * themselves.
+ /**
+ * Only the VC owner and system admins can delete VC. VCA admins
+ * can delete VC-accesses e.g. of project VC, but not the VC
+ * themselves.
*
* @param securityContext
- * @param vcId the id of the virtual corpus
+ * @param vcId
+ * the id of the virtual corpus
* @return HTTP status 200, if successful
*/
@DELETE
@@ -234,13 +259,19 @@
return Response.ok().build();
}
- /** VC can only be shared with a group, not individuals.
- * Only VCA admins are allowed to share VC and
- * the VC must have been created by themselves.
+ /**
+ * VC can only be shared with a group, not individuals.
+ * Only VCA admins are allowed to share VC and the VC must have
+ * been created by themselves.
+ *
+ * <br /><br />
+ * Not allowed via third-party apps.
*
* @param securityContext
- * @param vcId a virtual corpus id
- * @param groupId a user group id
+ * @param vcId
+ * a virtual corpus id
+ * @param groupId
+ * a user group id
* @return HTTP status 200, if successful
*/
@POST
@@ -259,7 +290,12 @@
return Response.ok().build();
}
- /** Only VCA Admins and system admins are allowed to delete a VC-access.
+ /**
+ * Only VCA Admins and system admins are allowed to delete a
+ * VC-access.
+ *
+ * <br /><br />
+ * Not allowed via third-party apps.
*
* @param securityContext
* @param accessId
@@ -281,14 +317,19 @@
}
- /** Lists active VC accesses to the specified VC.
- * Only available to VCA and system admins.
- * For system admins, lists all VCA of the VC.
+ /**
+ * Lists active VC accesses to the specified VC.
+ * Only available to VCA and system admins.
+ * For system admins, lists all VCA of the VC.
+ *
+ * <br /><br />
+ * Not allowed via third-party apps.
*
* @see VirtualCorpusAccessStatus
*
* @param securityContext
- * @param vcId virtual corpus id
+ * @param vcId
+ * virtual corpus id
* @return a list of access to the specified virtual corpus
*/
@GET
@@ -307,12 +348,17 @@
}
}
- /** Lists active VC-accesses available for a user-group.
- * Only available to VCA and system admins.
- * For system admins, list all VCA for the group.
+ /**
+ * Lists active VC-accesses available for a user-group.
+ * Only available to VCA and system admins.
+ * For system admins, list all VCA for the group.
+ *
+ * <br /><br />
+ * Not allowed via third-party apps.
*
* @param securityContext
- * @param groupId a group id
+ * @param groupId
+ * a group id
* @return a list of VC-access
*/
@GET
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 bdcc0cf..4059933 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
@@ -72,11 +72,14 @@
// OAuth2 authentication scheme
case BEARER:
-// if (request.getPath().equals("oauth2/authorize")) {
-// throw new KustvaktException(
-// StatusCodes.AUTHENTICATION_FAILED,
-// "Bearer is not supported for user authentication at oauth2/authorize");
-// }
+ if (request.getPath().startsWith("vc/access")
+ || request.getPath().startsWith("vc/delete")
+ || request.getPath().startsWith("group")
+ || request.getPath().startsWith("user")) {
+ throw new KustvaktException(
+ StatusCodes.AUTHENTICATION_FAILED,
+ "Token type Bearer is not allowed.");
+ }
context = authenticationManager.getTokenContext(
TokenType.BEARER, authData.getToken(), host,
diff --git a/full/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java b/full/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java
index 78138bb..e5e838a 100644
--- a/full/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java
+++ b/full/src/main/java/de/ids_mannheim/korap/web/filter/DemoUserFilter.java
@@ -6,6 +6,7 @@
import com.sun.jersey.spi.container.ResourceFilter;
import de.ids_mannheim.korap.config.BeansFactory;
import de.ids_mannheim.korap.config.KustvaktConfiguration;
+import de.ids_mannheim.korap.constant.TokenType;
import de.ids_mannheim.korap.security.context.KustvaktContext;
import de.ids_mannheim.korap.security.context.TokenContext;
import de.ids_mannheim.korap.user.User;
@@ -69,6 +70,7 @@
c.setExpirationTime(TimeUtils.plusSeconds(
config
.getShortTokenTTL()).getMillis());
+ c.setTokenType(TokenType.BASIC);
return c;
}
diff --git a/full/src/main/resources/default-config.xml b/full/src/main/resources/default-config.xml
index bb5ac1e..456ecff 100644
--- a/full/src/main/resources/default-config.xml
+++ b/full/src/main/resources/default-config.xml
@@ -167,7 +167,6 @@
<bean id="initializator" class="de.ids_mannheim.korap.config.Initializator"
init-method="init">
- <constructor-arg name="config" ref="kustvakt_config" />
<constructor-arg name="accessScopeDao" ref="accessScopeDao" />
</bean>
diff --git a/full/src/main/resources/kustvakt.conf b/full/src/main/resources/kustvakt.conf
index da7ec20..654a1ab 100644
--- a/full/src/main/resources/kustvakt.conf
+++ b/full/src/main/resources/kustvakt.conf
@@ -50,7 +50,7 @@
oauth2.native.client.host = korap.ids-mannheim.de
oauth2.max.attempts = 3
# -- scopes separated by space
-oauth2.default.scopes = username email
+oauth2.default.scopes = search match_info
oauth2.client.credentials.scopes = client_info
# JWT
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 8126843..911f8a9 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
@@ -6,6 +6,7 @@
import java.io.IOException;
import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response.Status;
import org.apache.http.entity.ContentType;
import org.junit.Test;
@@ -13,13 +14,13 @@
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.net.HttpHeaders;
import com.sun.jersey.api.client.ClientResponse;
-import com.sun.jersey.api.client.ClientResponse.Status;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import de.ids_mannheim.korap.config.Attributes;
import de.ids_mannheim.korap.config.SpringJerseyTest;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.oauth2.constant.OAuth2Scope;
import de.ids_mannheim.korap.utils.JsonUtils;
public class OAuth2AccessTokenTest extends SpringJerseyTest {
@@ -42,17 +43,50 @@
JsonNode node = JsonUtils.readTree(entity);
return node.at("/access_token").asText();
}
+
+ @Test
+ public void testListVCScope() throws KustvaktException {
+ MultivaluedMap<String, String> form = new MultivaluedMapImpl();
+ form.add("grant_type", "password");
+ form.add("client_id", "fCBbQkAyYzI4NzUxMg");
+ form.add("client_secret", "secret");
+ form.add("username", "dory");
+ form.add("password", "password");
+ form.add("scope", OAuth2Scope.VC_INFO.toString());
+
+ ClientResponse response = resource().path("oauth2").path("token")
+ .header(HttpHeaders.CONTENT_TYPE,
+ ContentType.APPLICATION_FORM_URLENCODED)
+ .entity(form).post(ClientResponse.class);
+
+ String entity = response.getEntity(String.class);
+ JsonNode node = JsonUtils.readTree(entity);
+ String token = node.at("/access_token").asText();
+
+ response = resource().path("vc").path("list")
+ .header(Attributes.AUTHORIZATION, "Bearer " + token)
+ .get(ClientResponse.class);
+
+ assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ entity = response.getEntity(String.class);
+ node = JsonUtils.readTree(entity);
+ assertEquals(4, node.size());
+ }
@Test
- public void testListVC () throws KustvaktException {
+ public void testListVCScopeNotAuthorized () throws KustvaktException {
ClientResponse response = resource().path("vc").path("list")
.header(Attributes.AUTHORIZATION, "Bearer " + requestToken())
.get(ClientResponse.class);
- assertEquals(Status.OK.getStatusCode(), response.getStatus());
+ assertEquals(ClientResponse.Status.UNAUTHORIZED.getStatusCode(),
+ response.getStatus());
String entity = response.getEntity(String.class);
JsonNode node = JsonUtils.readTree(entity);
- assertEquals(4, node.size());
+ assertEquals(StatusCodes.AUTHORIZATION_FAILED,
+ node.at("/errors/0/0").asInt());
+ assertEquals("Scope vc_info is not authorized",
+ node.at("/errors/0/1").asText());
}
@Test
@@ -64,10 +98,11 @@
.header(HttpHeaders.X_FORWARDED_FOR, "149.27.0.32")
.get(ClientResponse.class);
+ String ent = response.getEntity(String.class);
+
assertEquals(ClientResponse.Status.OK.getStatusCode(),
response.getStatus());
-
- String ent = response.getEntity(String.class);
+
JsonNode node = JsonUtils.readTree(ent);
assertNotNull(node);
assertEquals(25, node.at("/matches").size());
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 93666ea..cc821fe 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
@@ -172,7 +172,7 @@
MultivaluedMap<String, String> authForm = new MultivaluedMapImpl();
authForm.add("response_type", "code");
authForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
- authForm.add("scope", "username");
+ authForm.add("scope", "search");
ClientResponse response =
requestAuthorizationConfidentialClient(authForm);
@@ -182,7 +182,7 @@
String code = params.get("code").get(0);
String scopes = params.get("scope").get(0);
- assertEquals(scopes, "username");
+ assertEquals(scopes, "search");
MultivaluedMap<String, String> tokenForm = new MultivaluedMapImpl();
tokenForm.add("grant_type", "authorization_code");
@@ -242,7 +242,7 @@
MultivaluedMap<String, String> authForm = new MultivaluedMapImpl();
authForm.add("response_type", "code");
authForm.add("client_id", "fCBbQkAyYzI4NzUxMg");
- authForm.add("scope", "username");
+ authForm.add("scope", "search");
authForm.add("redirect_uri", uri);
ClientResponse response =
@@ -342,7 +342,6 @@
ContentType.APPLICATION_FORM_URLENCODED)
.entity(form).post(ClientResponse.class);
String entity = response.getEntity(String.class);
- System.out.println(entity);
JsonNode node = JsonUtils.readTree(entity);
assertNotNull(node.at("/access_token").asText());
assertNotNull(node.at("/refresh_token").asText());
diff --git a/full/src/test/resources/kustvakt-test.conf b/full/src/test/resources/kustvakt-test.conf
index 8416b6a..e09f9c4 100644
--- a/full/src/test/resources/kustvakt-test.conf
+++ b/full/src/test/resources/kustvakt-test.conf
@@ -50,7 +50,7 @@
oauth2.native.client.host = korap.ids-mannheim.de
oauth2.max.attempts = 2
# -- scopes separated by space
-oauth2.default.scopes = openid username email
+oauth2.default.scopes = openid search match_info
oauth2.client.credentials.scopes = client_info
## OpenId
diff --git a/full/src/test/resources/log4j2-test.properties b/full/src/test/resources/log4j2-test.properties
index 58a30cf..dc7dc8f 100644
--- a/full/src/test/resources/log4j2-test.properties
+++ b/full/src/test/resources/log4j2-test.properties
@@ -19,6 +19,7 @@
logger.console.level = info
logger.console.appenderRefs = stdout
logger.console.appenderRef.file.ref = STDOUT
+logger.console.additivity=false
#loggers=file
#logger.file.name=com.sun.jersey.test.framework.spi.container
diff --git a/full/src/test/resources/test-config.xml b/full/src/test/resources/test-config.xml
index cad93aa..28cd408 100644
--- a/full/src/test/resources/test-config.xml
+++ b/full/src/test/resources/test-config.xml
@@ -167,7 +167,6 @@
<bean id="initializator" class="de.ids_mannheim.korap.config.Initializator"
init-method="init">
- <constructor-arg name="config" ref="kustvakt_config" />
<constructor-arg name="accessScopeDao" ref="accessScopeDao" />
</bean>
diff --git a/lite/pom.xml b/lite/pom.xml
index f8d78bd..9b3e8dd 100644
--- a/lite/pom.xml
+++ b/lite/pom.xml
@@ -147,7 +147,7 @@
<dependency>
<groupId>de.ids_mannheim.korap</groupId>
<artifactId>Kustvakt-core</artifactId>
- <version>0.60.4</version>
+ <version>0.60.5</version>
</dependency>
<!-- Spring -->