added services and filters
diff --git a/src/main/java/de/ids_mannheim/korap/config/Scopes.java b/src/main/java/de/ids_mannheim/korap/config/Scopes.java
new file mode 100644
index 0000000..9c4ef7a
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/config/Scopes.java
@@ -0,0 +1,70 @@
+package de.ids_mannheim.korap.config;
+
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.UserDetails;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * @author hanl
+ * @date 09/12/2014
+ */
+public class Scopes {
+
+ public enum Scope {
+ openid, profile, email, queries, account, preferences, search
+
+ }
+
+ private static final String[] profile = { Attributes.EMAIL,
+ Attributes.FIRSTNAME, Attributes.LASTNAME, Attributes.INSTITUTION,
+ Attributes.ADDRESS, Attributes.PHONE, Attributes.GENDER,
+ Attributes.COUNTRY };
+
+ private static final String[] OPENID_CONNECT = { Scope.profile.toString() };
+
+ private static final Enum[] SERVICE_DEFAULTS = { Scope.account,
+ Scope.preferences, Scope.search, Scope.queries };
+
+ public static Map<String, Object> getProfileScopes(
+ Map<String, Object> values) {
+ Map<String, Object> r = new HashMap<>();
+ for (String key : profile) {
+ Object v = values.get(key);
+ if (v != null)
+ r.put(key, v);
+ }
+ return r;
+ }
+
+ /**
+ * expects space separated values
+ *
+ * @param scopes
+ * @return
+ */
+ //todo: test
+ public static Enum[] mapScopes(String scopes) {
+ List<Enum> s = new ArrayList<>();
+ for (String value : scopes.split(" "))
+ s.add(Scope.valueOf(value.toLowerCase()));
+ return (Enum[]) s.toArray(new Enum[s.size()]);
+ }
+
+ public static Map<String, Object> mapOpenIDConnectScopes(String scopes,
+ UserDetails details) {
+ Map<String, Object> m = new HashMap<>();
+ if (scopes != null && !scopes.isEmpty()) {
+ scopes = scopes.toLowerCase();
+ if (scopes.contains(Scope.email.toString()))
+ m.put(Attributes.EMAIL, details.getEmail());
+ if (scopes.contains(Scope.profile.toString()))
+ m.putAll(Scopes.getProfileScopes(details.toMap()));
+ }
+ return m;
+ }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/PolicyHandlerIface.java b/src/main/java/de/ids_mannheim/korap/interfaces/PolicyHandlerIface.java
new file mode 100644
index 0000000..122970f
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/PolicyHandlerIface.java
@@ -0,0 +1,175 @@
+package de.ids_mannheim.korap.interfaces;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.resources.KustvaktResource;
+import de.ids_mannheim.korap.security.Parameter;
+import de.ids_mannheim.korap.security.PolicyCondition;
+import de.ids_mannheim.korap.security.SecurityPolicy;
+import de.ids_mannheim.korap.user.User;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.List;
+
+/**
+ * User: hanl
+ * Date: 10/31/13
+ * Time: 3:01 PM
+ */
+// todo: this still applicable?
+@Transactional
+public interface PolicyHandlerIface {
+
+ /**
+ * @param policy
+ * @param user
+ * @throws KustvaktException
+ */
+ int createPolicy(SecurityPolicy policy, User user) throws KustvaktException;
+
+ /**
+ * @param target
+ * @param user
+ * @param perm
+ * @return
+ */
+ List<SecurityPolicy>[] getPolicies(Integer target, User user, Byte perm);
+
+ /**
+ * @param policy
+ * @return
+ * @throws KustvaktException
+ */
+ void mapConstraints(SecurityPolicy policy) throws KustvaktException;
+
+ /**
+ * @param target
+ * @param user
+ * @param perm
+ * @return
+ */
+ List<SecurityPolicy>[] getPolicies(String target, User user, Byte perm);
+
+ /**
+ * @param path
+ * @param user
+ * @param perm
+ * @return
+ */
+ List<SecurityPolicy>[] findPolicies(String path, User user, Byte perm);
+
+ /**
+ * @param path
+ * @param user
+ * @param b
+ * @param clazz
+ * @return
+ * @throws KustvaktException
+ */
+ List<KustvaktResource.Container> getDescending(String path, User user,
+ Byte b, Class<? extends KustvaktResource> clazz)
+ throws KustvaktException;
+
+ /**
+ * @param path
+ * @param user
+ * @param b
+ * @param clazz
+ * @return
+ * @throws KustvaktException
+ */
+ List<KustvaktResource.Container> getAscending(String path, User user,
+ Byte b, Class<? extends KustvaktResource> clazz)
+ throws KustvaktException;
+
+ /**
+ * @param id
+ * @param user
+ */
+ //todo: test
+ void deleteResourcePolicies(String id, User user) throws KustvaktException;
+
+ /**
+ * @param policy
+ * @param user
+ * @return
+ * @throws KustvaktException
+ */
+ int deletePolicy(SecurityPolicy policy, User user) throws KustvaktException;
+
+ /**
+ * @param policy
+ * @param user
+ * @return
+ * @throws KustvaktException
+ */
+ int updatePolicy(SecurityPolicy policy, User user) throws KustvaktException;
+
+ /**
+ * checks if a similar policy already exists
+ *
+ * @param policy
+ * @return
+ * @throws KustvaktException
+ */
+ int checkPolicy(SecurityPolicy policy, User user) throws KustvaktException;
+
+ /**
+ * @param user
+ * @param name
+ * @param owner
+ * @return
+ * @throws KustvaktException
+ */
+ int matchCondition(User user, String name, boolean owner)
+ throws KustvaktException;
+
+ /**
+ * @param username
+ * @param condition
+ * @param admin
+ * @return
+ * @throws KustvaktException
+ */
+ int addToCondition(String username, PolicyCondition condition,
+ boolean admin) throws KustvaktException;
+
+ /**
+ * @param usernames
+ * @param condition
+ * @param status
+ * @throws KustvaktException
+ */
+
+ //todo: add a handler user id, to skip the matching step in the corpusmanagement segment!
+ int[] addToCondition(List<String> usernames, PolicyCondition condition,
+ boolean status) throws KustvaktException;
+
+ /**
+ * @param usernames
+ * @param condition
+ * @throws KustvaktException
+ */
+ void removeFromCondition(List<String> usernames, PolicyCondition condition)
+ throws KustvaktException;
+
+ /**
+ * @param param
+ * @throws KustvaktException
+ */
+ void createParamBinding(Parameter param) throws KustvaktException;
+
+ /**
+ * @param condition
+ * @return
+ * @throws KustvaktException
+ */
+ List<String> getUsersFromCondition(PolicyCondition condition)
+ throws KustvaktException;
+
+ /**
+ * @param policy
+ * @throws KustvaktException
+ */
+ void removeParamBinding(SecurityPolicy policy) throws KustvaktException;
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteNode.java b/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteNode.java
deleted file mode 100644
index cb0a06c..0000000
--- a/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteNode.java
+++ /dev/null
@@ -1,20 +0,0 @@
-package de.ids_mannheim.korap.resource.rewrite;
-
-/**
- * node rewrites get injected typically object nodes that are subject to altering.
- * Be aware that node rewrites are processed before query rewrites. Thus query rewrite may override previous node rewrites
- *
- * {@link de.ids_mannheim.korap.resource.rewrite.RewriteNode} rewrite support the deletion of the respective node by simply setting the node invalid in KoralNode
- *
- * @author hanl
- * @date 03/07/2015
- */
-public abstract class RewriteNode extends RewriteTask {
-
- public RewriteNode() {
- super();
- }
-
-
-
-}
diff --git a/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteQuery.java b/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteQuery.java
deleted file mode 100644
index 99bfb34..0000000
--- a/src/main/java/de/ids_mannheim/korap/resource/rewrite/RewriteQuery.java
+++ /dev/null
@@ -1,18 +0,0 @@
-package de.ids_mannheim.korap.resource.rewrite;
-
-/**
- * query rewrites get injected the entire query from root containing all child nodes
- * <p/>
- * {@link de.ids_mannheim.korap.resource.rewrite.RewriteQuery} does not allow the deletion of the root node or subnode through KoralNode.
- * The {@link de.ids_mannheim.korap.resource.rewrite.RewriteHandler} will igonore respecitve invalid requests
- *
- * @author hanl
- * @date 03/07/2015
- */
-public abstract class RewriteQuery extends RewriteTask {
-
- public RewriteQuery() {
- super();
- }
-
-}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder2.java b/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder2.java
deleted file mode 100644
index 1058f5b..0000000
--- a/src/main/java/de/ids_mannheim/korap/utils/CollectionQueryBuilder2.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package de.ids_mannheim.korap.utils;
-
-import com.fasterxml.jackson.databind.JsonNode;
-
-import java.io.IOException;
-import java.util.*;
-
-/**
- * @author hanl
- * @date 13/05/2014
- */
-@Deprecated
-public class CollectionQueryBuilder2 {
-
- private List<Map> rq;
- private Map groups;
- private CollectionTypes types;
- private boolean verbose;
-
- public CollectionQueryBuilder2() {
- this.verbose = false;
- this.rq = new ArrayList<>();
- this.groups = new HashMap();
- this.types = new CollectionTypes();
- }
-
- public CollectionQueryBuilder2(boolean verbose) {
- this();
- this.verbose = verbose;
- }
-
-
- public CollectionQueryBuilder2 addResource(String collections) {
- try {
- List v = JsonUtils.read(collections, LinkedList.class);
- this.rq.addAll(v);
- } catch (IOException e) {
- throw new IllegalArgumentException("Conversion went wrong!");
- }
- return this;
- }
-
- public CollectionQueryBuilder2 addResources(List<String> queries) {
- for (String query : queries)
- addResource(query);
- return this;
- }
-
-// public CollectionQueryBuilder2 setQuery(String query) {
-// CollectionQueryProcessor tree = new CollectionQueryProcessor();
-// tree.process(query);
-// this.groups = tree.getRequestMap();
-// return this;
-// }
-
- public List raw() {
- List list = new ArrayList(this.rq);
- list.add(types.createMetaFilter(this.groups));
- return list;
- }
-
- private Object raw2() {
- return this.groups;
- }
-
- public String toCollections() {
- Map value = new HashMap();
- value.put("collections", raw2());
- return JsonUtils.toJSON(value);
- }
-
- public JsonNode toNode() {
- return JsonUtils.valueToTree(raw2());
- }
-
- public String toJSON() {
- return JsonUtils.toJSON(raw2());
- }
-
-
- // add public filter to original query
- private void addToGroup() {
- Map first = this.rq.get(0);
-
- }
-}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/PropertyReader.java b/src/main/java/de/ids_mannheim/korap/utils/PropertyReader.java
new file mode 100644
index 0000000..292b019
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/PropertyReader.java
@@ -0,0 +1,36 @@
+package de.ids_mannheim.korap.utils;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author hanl
+ * @date 27/09/2014
+ */
+public abstract class PropertyReader {
+
+
+ protected Map<String, Properties> read(String path) throws IOException {
+ Map<String, Properties> res = new HashMap<>();
+ Properties s = new Properties();
+ s.load(new FileInputStream(new File(path)));
+ for (Map.Entry<Object, Object> e : s.entrySet()) {
+ String key = e.getKey().toString().split("\\.")[0];
+ Properties in = res.get(key);
+ if (in == null) {
+ in = new Properties();
+ res.put(key, in);
+ }
+ in.setProperty(e.getKey().toString(), e.getValue().toString());
+ }
+ return res;
+ }
+
+
+ public abstract void load();
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/utils/UserPropertyReader.java b/src/main/java/de/ids_mannheim/korap/utils/UserPropertyReader.java
new file mode 100644
index 0000000..b506d22
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/utils/UserPropertyReader.java
@@ -0,0 +1,111 @@
+package de.ids_mannheim.korap.utils;
+
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.interfaces.EncryptionIface;
+import de.ids_mannheim.korap.interfaces.EntityHandlerIface;
+import de.ids_mannheim.korap.user.KorAPUser;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.user.UserDetails;
+import de.ids_mannheim.korap.user.UserSettings;
+import org.slf4j.Logger;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.security.NoSuchAlgorithmException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * @author hanl
+ * @date 30/09/2014
+ */
+public class UserPropertyReader extends PropertyReader {
+
+ private Map<String, Properties> props;
+ private String path;
+ private EntityHandlerIface iface;
+ private EncryptionIface crypto;
+ private static Logger jlog = KustvaktLogger
+ .initiate(UserPropertyReader.class);
+
+ public UserPropertyReader(String path) {
+ this.path = path;
+ this.iface = BeanConfiguration.getBeans().getUserDBHandler();
+ this.crypto = BeanConfiguration.getBeans().getEncryption();
+ }
+
+ @Override
+ public void load() {
+ try {
+ props = super.read(this.path);
+ for (Map.Entry<String, Properties> e : props.entrySet()) {
+ try {
+ createUser(e.getKey(), e.getValue());
+ }catch (KustvaktException ex) {
+ jlog.error("KorAP-Exception: {} for user {}",
+ ex.getStatusCode(), e.getKey());
+ }
+ }
+ iface.createAccount(User.UserFactory.getDemoUser());
+ }catch (IOException e) {
+ jlog.error("Could not read from path {}", path);
+ }catch (KustvaktException e) {
+ jlog.error("KorAP-Exception: {}", e.getStatusCode());
+ }
+ }
+
+ private User createUser(String username, Properties p)
+ throws KustvaktException {
+ KorAPUser user;
+ if (username.equals(User.ADMINISTRATOR_NAME)) {
+ user = User.UserFactory.getAdmin();
+
+ String pass = p.getProperty(username + ".password", null);
+ if (pass == null)
+ throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
+
+ try {
+ pass = crypto.produceSecureHash(pass);
+ }catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+ throw new KustvaktException(StatusCodes.REQUEST_INVALID);
+ }
+ user.setPassword(pass);
+ }else {
+ user = User.UserFactory.getUser(username);
+ Map<String, Object> vals = new HashMap<>();
+ for (Map.Entry e : p.entrySet()) {
+ String key = e.getKey().toString().split("\\.", 2)[1];
+ vals.put(key, e.getValue().toString());
+ }
+ String pass = p.getProperty(username + ".password", null);
+ if (pass == null)
+ throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
+
+ try {
+ pass = crypto.produceSecureHash(pass);
+ }catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+ throw new KustvaktException(StatusCodes.REQUEST_INVALID);
+ }
+
+ user.setPassword(pass);
+ user.setAccountLocked(Boolean.valueOf(
+ p.getProperty(username + ".lock", "false")));
+ user.setAccountCreation(TimeUtils.getNow().getMillis());
+
+ //todo: make sure uri is set to 0, so sql queries work with the null value
+// user.setURIExpiration(0L);
+
+ UserDetails det = UserDetails.newDetailsIterator(vals);
+
+ user.setDetails(det);
+ user.setSettings(new UserSettings());
+ }
+ iface.createAccount(user);
+ jlog.info("successfully created account for user {}",
+ user.getUsername());
+ return user;
+ }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/filter/AuthFilter.java b/src/main/java/de/ids_mannheim/korap/web/filter/AuthFilter.java
new file mode 100644
index 0000000..8346e1f
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/filter/AuthFilter.java
@@ -0,0 +1,63 @@
+package de.ids_mannheim.korap.web.filter;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+import com.sun.jersey.spi.container.ResourceFilter;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.web.utils.KorAPContext;
+
+import javax.ws.rs.ext.Provider;
+
+/**
+ * @author hanl
+ * @date 28/01/2014
+ */
+@Provider
+public class AuthFilter implements ContainerRequestFilter, ResourceFilter {
+
+ private AuthenticationManagerIface userController;
+
+ public AuthFilter() {
+ this.userController = BeanConfiguration.getBeans()
+ .getAuthenticationManager();
+ }
+
+ @Override
+ public ContainerRequest filter(ContainerRequest request) {
+ String host = request.getHeaderValue(ContainerRequest.HOST);
+ String ua = request.getHeaderValue(ContainerRequest.USER_AGENT);
+
+ String authentication = request
+ .getHeaderValue(ContainerRequest.AUTHORIZATION);
+ if (authentication != null && !authentication.isEmpty()) {
+ TokenContext context;
+ try {
+ context = userController
+ .getTokenStatus(authentication, host, ua);
+
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+
+ if (context != null && (
+ (context.isSecureRequired() && request.isSecure())
+ | !context.isSecureRequired()))
+ request.setSecurityContext(new KorAPContext(context));
+ }
+ return request;
+ }
+
+ @Override
+ public ContainerRequestFilter getRequestFilter() {
+ return this;
+ }
+
+ @Override
+ public ContainerResponseFilter getResponseFilter() {
+ return null;
+ }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/filter/DefaultFilter.java b/src/main/java/de/ids_mannheim/korap/web/filter/DefaultFilter.java
new file mode 100644
index 0000000..a8ffe89
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/filter/DefaultFilter.java
@@ -0,0 +1,60 @@
+package de.ids_mannheim.korap.web.filter;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ContainerRequestFilter;
+import com.sun.jersey.spi.container.ContainerResponseFilter;
+import com.sun.jersey.spi.container.ResourceFilter;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import de.ids_mannheim.korap.web.utils.KorAPContext;
+
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.UriInfo;
+import javax.ws.rs.ext.Provider;
+
+/**
+ * Created by hanl on 7/15/14.
+ */
+@Provider
+public class DefaultFilter implements ContainerRequestFilter, ResourceFilter {
+
+ @Context
+ UriInfo info;
+
+ @Override
+ public ContainerRequest filter(ContainerRequest request) {
+ String host = request.getHeaderValue(ContainerRequest.HOST);
+ String ua = request.getHeaderValue(ContainerRequest.USER_AGENT);
+ String authentication = request
+ .getHeaderValue(ContainerRequest.AUTHORIZATION);
+
+ // means that this is the public service
+ if (authentication == null)
+ request.setSecurityContext(
+ new KorAPContext(createShorterToken(host, ua)));
+ return request;
+ }
+
+ private TokenContext createShorterToken(String host, String agent) {
+ User demo = User.UserFactory.getDemoUser();
+ TokenContext c = new TokenContext(demo.getUsername());
+ c.setHostAddress(host);
+ c.setUserAgent(agent);
+ c.setExpirationTime(TimeUtils.plusSeconds(
+ BeanConfiguration.getBeans().getConfiguration()
+ .getShortTokenTTL()).getMillis());
+ return c;
+ }
+
+ @Override
+ public ContainerRequestFilter getRequestFilter() {
+ return this;
+ }
+
+ @Override
+ public ContainerResponseFilter getResponseFilter() {
+ return null;
+ }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/service/AuthService.java b/src/main/java/de/ids_mannheim/korap/web/service/AuthService.java
new file mode 100644
index 0000000..5e76016
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/service/AuthService.java
@@ -0,0 +1,222 @@
+package de.ids_mannheim.korap.web.service;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+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.security.auth.BasicHttpAuth;
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.user.UserSettings;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.KustvaktLogger;
+import de.ids_mannheim.korap.utils.ServiceVersion;
+import de.ids_mannheim.korap.web.KustvaktServer;
+import de.ids_mannheim.korap.web.filter.AuthFilter;
+import de.ids_mannheim.korap.web.filter.DefaultFilter;
+import de.ids_mannheim.korap.web.filter.PiwikFilter;
+import org.slf4j.Logger;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.*;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+//import com.sun.xml.internal.messaging.saaj.util.Base64;
+
+/**
+ * @author hanl
+ * @date 24/01/2014
+ */
+@Path(KustvaktServer.API_VERSION + "/auth")
+@ResourceFilters({ PiwikFilter.class })
+@Produces(MediaType.TEXT_HTML + ";charset=utf-8")
+public class AuthService {
+
+ //todo: bootstrap function to transmit certain default configuration settings and examples (example user queries,
+ // default usersettings, etc.)
+ private static Logger jlog = KustvaktLogger.initiate(AuthService.class);
+
+ private AuthenticationManagerIface controller;
+ // private SendMail mail;
+
+ public AuthService() {
+ this.controller = BeanConfiguration.getBeans()
+ .getAuthenticationManager();
+ //todo: replace with real property values
+ // this.mail = new SendMail(ExtConfiguration.getMailProperties());
+ }
+
+ /**
+ * represents json string with data. All GUI clients can access this method to get certain default values
+ * --> security checks?
+ *
+ * @return String
+ */
+ @GET
+ @Path("bootstrap")
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response bootstrap() {
+ Map m = new HashMap();
+ m.put("settings", new UserSettings().toObjectMap());
+ m.put("ql", BeanConfiguration.getBeans().getConfiguration()
+ .getQueryLanguages());
+ m.put("SortTypes", null); // types of sorting that are supported!
+ m.put("version", ServiceVersion.getAPIVersion());
+ return Response.ok(JsonUtils.toJSON(m)).build();
+ }
+
+ @GET
+ @Path("status")
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class })
+ public Response getStatus(@Context SecurityContext context,
+ @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+ @HeaderParam(ContainerRequest.HOST) String host,
+ @Context Locale locale) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ return Response.ok(ctx.toJSON()).build();
+ }
+
+ @GET
+ @Path("apiToken")
+ public Response requestAPIToken(@Context HttpHeaders headers,
+ @Context Locale locale,
+ @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+ @HeaderParam(ContainerRequest.HOST) String host,
+ @HeaderParam("referer-url") String referer,
+ @QueryParam("scope") String scope) {
+ List<String> auth = headers
+ .getRequestHeader(ContainerRequest.AUTHORIZATION);
+
+ if (auth == null)
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.PERMISSION_DENIED);
+ String[] values = BasicHttpAuth.decode(auth.get(0));
+
+ // "Invalid syntax for username and password"
+ if (values == null)
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.PERMISSION_DENIED);
+
+ if (values[0].equalsIgnoreCase("null") | values[1]
+ .equalsIgnoreCase("null"))
+ // is actual an invalid request
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.REQUEST_INVALID);
+
+ Map<String, Object> attr = new HashMap<>();
+ if (scope != null && !scope.isEmpty())
+ attr.put(Attributes.SCOPES, scope);
+ attr.put(Attributes.HOST, host);
+ attr.put(Attributes.USER_AGENT, agent);
+ TokenContext context;
+ try {
+ User user = controller.authenticate(0, values[0], values[1], attr);
+ this.controller.getUserDetails(user);
+ context = controller.createTokenContext(user, attr,
+ Attributes.API_AUTHENTICATION);
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+
+ return Response.ok(context.toResponse()).build();
+ }
+
+ // todo:
+ @Deprecated
+ @GET
+ @Path("refresh")
+ public Response refresh(@Context SecurityContext context,
+ @Context Locale locale) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ TokenContext newContext;
+
+ // try {
+ // newContext = controller.refresh(ctx);
+ // }catch (KorAPException e) {
+ // KorAPLogger.ERROR_LOGGER.error("Exception encountered!", e);
+ // throw BeanConfiguration.getResponseHandler().throwit(e);
+ // }
+ // return Response.ok().entity(newContext.getToken()).build();
+ return null;
+ }
+
+ @GET
+ @Path("sessionToken")
+ public Response requestSession(@Context HttpHeaders headers,
+ @Context Locale locale,
+ @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+ @HeaderParam(ContainerRequest.HOST) String host) {
+ List<String> auth = headers
+ .getRequestHeader(ContainerRequest.AUTHORIZATION);
+
+ if (auth == null)
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.PERMISSION_DENIED);
+
+ String[] values = BasicHttpAuth.decode(auth.get(0));
+ // authentication = StringUtils.stripTokenType(authentication);
+ // String[] values = new String(
+ // DatatypeConverter.parseBase64Binary(authentication)).split(":");
+ // String[] values = Base64.base64Decode(authentication).split(":");
+
+ // "Invalid syntax for username and password"
+ if (values == null)
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.PERMISSION_DENIED);
+
+ if (values[0].equalsIgnoreCase("null") | values[1]
+ .equalsIgnoreCase("null"))
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.REQUEST_INVALID);
+
+ Map<String, Object> attr = new HashMap<>();
+ attr.put(Attributes.HOST, host);
+ attr.put(Attributes.USER_AGENT, agent);
+ TokenContext context;
+ try {
+ User user = controller.authenticate(0, values[0], values[1], attr);
+ context = controller.createTokenContext(user, attr,
+ Attributes.SESSION_AUTHENTICATION);
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok().entity(context.toJSON()).build();
+ }
+
+ // fixme: security issues: setup shibboleth compatible authentication system
+ // todo: will be purged with token authentication --> shib is client side
+ @POST
+ @Consumes("application/x-www-form-urlencoded")
+ @Produces("application/json")
+ @Path("shibboleth")
+ public Response loginshib(@Context HttpHeaders headers,
+ @Context Locale locale,
+ @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+ @HeaderParam(ContainerRequest.HOST) String host,
+ @QueryParam("client_id") String client_id) {
+
+ // the shibfilter decrypted the values
+ // define default provider for returned access token strategy?!
+
+ Map<String, Object> attr = new HashMap<>();
+ attr.put(Attributes.HOST, host);
+ attr.put(Attributes.USER_AGENT, agent);
+
+ TokenContext context;
+
+ try {
+ // todo: distinguish type KorAP/Shibusers
+ User user = controller.authenticate(1, null, null, attr);
+ context = controller.createTokenContext(user, attr, null);
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok().entity(context.toJSON()).build();
+ }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/service/OAuthService.java b/src/main/java/de/ids_mannheim/korap/web/service/OAuthService.java
new file mode 100644
index 0000000..525eca9
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/service/OAuthService.java
@@ -0,0 +1,551 @@
+package de.ids_mannheim.korap.web.service;
+
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.config.*;
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.handlers.OAuth2Handler;
+import de.ids_mannheim.korap.interfaces.AuthenticationManagerIface;
+import de.ids_mannheim.korap.interfaces.EncryptionIface;
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.TokenContext;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.StringUtils;
+import de.ids_mannheim.korap.web.KustvaktServer;
+import de.ids_mannheim.korap.web.filter.AuthFilter;
+import de.ids_mannheim.korap.web.filter.BlockFilter;
+import de.ids_mannheim.korap.web.filter.DefaultFilter;
+import de.ids_mannheim.korap.web.filter.PiwikFilter;
+import de.ids_mannheim.korap.web.utils.FormRequestWrapper;
+import org.apache.oltu.oauth2.as.issuer.MD5Generator;
+import org.apache.oltu.oauth2.as.issuer.OAuthIssuer;
+import org.apache.oltu.oauth2.as.issuer.OAuthIssuerImpl;
+import org.apache.oltu.oauth2.as.request.OAuthAuthzRequest;
+import org.apache.oltu.oauth2.as.request.OAuthTokenRequest;
+import org.apache.oltu.oauth2.as.response.OAuthASResponse;
+import org.apache.oltu.oauth2.common.OAuth;
+import org.apache.oltu.oauth2.common.error.OAuthError;
+import org.apache.oltu.oauth2.common.exception.OAuthProblemException;
+import org.apache.oltu.oauth2.common.exception.OAuthSystemException;
+import org.apache.oltu.oauth2.common.message.OAuthResponse;
+import org.apache.oltu.oauth2.common.message.types.GrantType;
+import org.apache.oltu.oauth2.common.message.types.ResponseType;
+import org.apache.oltu.oauth2.common.message.types.TokenType;
+import org.apache.oltu.oauth2.common.utils.OAuthUtils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import javax.ws.rs.*;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MultivaluedMap;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author hanl
+ * @date 07/06/2014
+ */
+//todo: only allow oauth2 access_token requests GET methods?
+//todo: allow refresh tokens
+@Path(KustvaktServer.API_VERSION + "/oauth2")
+//@ResourceFilters({ AccessLevelFilter.class, PiwikFilter.class })
+public class OAuthService {
+
+ private OAuth2Handler handler;
+ private AuthenticationManagerIface controller;
+ private EncryptionIface crypto;
+ private KustvaktConfiguration config;
+
+ public OAuthService() {
+ this.handler = new OAuth2Handler(
+ BeanConfiguration.getBeans().getPersistenceClient());
+ this.controller = BeanConfiguration.getBeans()
+ .getAuthenticationManager();
+ this.crypto = BeanConfiguration.getBeans().getEncryption();
+ this.config = BeanConfiguration.getBeans().getConfiguration();
+ }
+
+ @POST
+ @Path("unregister")
+ @ResourceFilters({ AuthFilter.class, BlockFilter.class })
+ public Response unregisterClient(@Context SecurityContext context,
+ @HeaderParam("Host") String host,
+ @QueryParam("client_secret") String secret,
+ @QueryParam("client_id") String client_id) {
+ ClientInfo info = new ClientInfo(client_id, secret);
+ info.setUrl(host);
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ try {
+ this.handler.removeClient(info,
+ this.controller.getUser(ctx.getUsername()));
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok().build();
+ }
+
+ @POST
+ @Path("register")
+ @ResourceFilters({ AuthFilter.class, BlockFilter.class })
+ public Response registerClient(@Context SecurityContext context,
+ @HeaderParam("Host") String host,
+ @QueryParam("redirect_url") String rurl) {
+ ClientInfo info = new ClientInfo(crypto.createID(),
+ crypto.createToken());
+ info.setUrl(host);
+ if (rurl == null)
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.ILLEGAL_ARGUMENT, "Missing parameter!",
+ "redirect_url");
+ info.setRedirect_uri(rurl);
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ try {
+ this.handler.registerClient(info,
+ this.controller.getUser(ctx.getUsername()));
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok(info.toJSON()).build();
+ }
+
+ @GET
+ @Path("info")
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+ PiwikFilter.class })
+ public Response getStatus(@Context SecurityContext context,
+ @QueryParam("scope") String scopes) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ User user;
+ try {
+ user = this.controller.getUser(ctx.getUsername());
+ this.controller.getUserDetails(user);
+ Set<String> base_scope = StringUtils
+ .toSet((String) ctx.getParameters().get(Attributes.SCOPES),
+ " ");
+ base_scope.retainAll(StringUtils.toSet(scopes));
+ scopes = StringUtils.toString(base_scope);
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ // json format with scope callback parameter
+ return Response.ok(JsonUtils.toJSON(Scopes
+ .mapOpenIDConnectScopes(scopes, user.getDetails()))).build();
+ }
+
+ @GET
+ @Path("authorizations")
+ @ResourceFilters({ AuthFilter.class, BlockFilter.class })
+ public Response getAuthorizations(@Context SecurityContext context,
+ @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+ @HeaderParam(ContainerRequest.HOST) String host) {
+ // works on all tokens, but access to native apps cannot be revoked!
+ // check secret and id and retrieve access tokens
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ try {
+ User user = this.controller.getUser(ctx.getUsername());
+ Collection auths = this.handler.getAuthorizedClients(user.getId());
+ if (auths.isEmpty())
+ return Response.noContent().build();
+ return Response.ok(JsonUtils.toJSON(auths)).build();
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ }
+
+ // todo: scopes for access_token are defined here
+ // todo: if user already has an access token registered for client and application, then redirect to token endpoint to retrive that token
+ // todo: demo account should be disabled for this function --> if authentication failed, client must redirect to login url (little login window)
+ @POST
+ @Path("authorize")
+ @Consumes("application/x-www-form-urlencoded")
+ @Produces("application/json")
+ @ResourceFilters({ BlockFilter.class })
+ public Response authorize(@Context HttpServletRequest request,
+ @Context SecurityContext context,
+ @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+ @HeaderParam(ContainerRequest.HOST) String host,
+ MultivaluedMap<String, String> form)
+ throws OAuthSystemException, URISyntaxException {
+ // user needs to be authenticated to this service!
+ TokenContext c = (TokenContext) context.getUserPrincipal();
+
+ try {
+ OAuthAuthzRequest oauthRequest = new OAuthAuthzRequest(
+ new FormRequestWrapper(request, form));
+ OAuthIssuerImpl oauthIssuerImpl = new OAuthIssuerImpl(
+ new MD5Generator());
+ User user;
+
+ Map<String, Object> attr = new HashMap<>();
+ attr.put(Attributes.HOST, host);
+ attr.put(Attributes.USER_AGENT, agent);
+ attr.put(Attributes.USERNAME, c.getUsername());
+ // also extractable via authorization header
+ attr.put(Attributes.CLIENT_ID, oauthRequest.getClientId());
+ attr.put(Attributes.CLIENT_SECRET, oauthRequest.getClientSecret());
+ StringBuilder scopes = new StringBuilder();
+ for (String scope : oauthRequest.getScopes())
+ scopes.append(scope + " ");
+ attr.put(Attributes.SCOPES, scopes.toString());
+
+ try {
+ user = controller.getUser(c.getUsername());
+ controller.getUserDetails(user);
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+
+ // register response according to response_type
+ String responseType = oauthRequest
+ .getParam(OAuth.OAUTH_RESPONSE_TYPE);
+
+ OAuthASResponse.OAuthAuthorizationResponseBuilder builder = OAuthASResponse
+ .authorizationResponse(request,
+ HttpServletResponse.SC_FOUND);
+
+ final String authorizationCode = oauthIssuerImpl
+ .authorizationCode();
+ ClientInfo info = this.handler
+ .getClient(oauthRequest.getClientId());
+
+ if (info == null || !info.getClient_secret()
+ .equals(oauthRequest.getClientSecret())) {
+ OAuthResponse res = OAuthASResponse
+ .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+ .setError(OAuthError.CodeResponse.UNAUTHORIZED_CLIENT)
+ .setErrorDescription("Unauthorized client!\n")
+ .buildJSONMessage();
+ return Response.status(res.getResponseStatus())
+ .entity(res.getBody()).build();
+ }
+
+ if (!info.getRedirect_uri()
+ .contains(oauthRequest.getRedirectURI())) {
+ OAuthResponse res = OAuthASResponse
+ .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+ .setError(OAuthError.CodeResponse.INVALID_REQUEST)
+ .setErrorDescription("Unauthorized redirect!\n")
+ .buildJSONMessage();
+ return Response.status(res.getResponseStatus())
+ .entity(res.getBody()).build();
+ }
+
+ String accessToken = this.handler
+ .getToken(oauthRequest.getClientId(), user.getId());
+ //todo: test this with parameters
+ if (accessToken != null) {
+ final OAuthResponse response = builder.location("/oauth2/token")
+ .setParam(OAuth.OAUTH_CLIENT_ID,
+ oauthRequest.getClientId())
+ .setParam(OAuth.OAUTH_CLIENT_SECRET,
+ oauthRequest.getClientSecret())
+ .buildQueryMessage();
+ return Response.status(response.getResponseStatus())
+ .location(new URI(response.getLocationUri())).build();
+ }
+
+ if (responseType.equals(ResponseType.CODE.toString())) {
+ try {
+ AuthCodeInfo codeInfo = new AuthCodeInfo(
+ info.getClient_id(), authorizationCode);
+ codeInfo.setScopes(StringUtils
+ .toString(oauthRequest.getScopes(), " "));
+ this.handler.authorize(codeInfo, user);
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ builder.setParam(OAuth.OAUTH_RESPONSE_TYPE,
+ ResponseType.CODE.toString());
+ builder.setCode(authorizationCode);
+
+ }else if (responseType.contains(ResponseType.TOKEN.toString())) {
+ try {
+ AuthCodeInfo codeInfo = new AuthCodeInfo(
+ info.getClient_id(), authorizationCode);
+ codeInfo.setScopes(StringUtils
+ .toString(oauthRequest.getScopes(), " "));
+ this.handler.authorize(codeInfo, user);
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+
+ builder.setParam(OAuth.OAUTH_RESPONSE_TYPE,
+ ResponseType.TOKEN.toString());
+ builder.setCode(authorizationCode);
+
+ String token = oauthIssuerImpl.accessToken();
+ this.handler.addToken(authorizationCode, token,
+ config.getLongTokenTTL());
+ builder.setAccessToken(token);
+ builder.setExpiresIn((long) config.getLongTokenTTL());
+
+ // skips authorization code type and returns id_token and access token directly
+ if (oauthRequest.getScopes().contains("openid")) {
+ try {
+ TokenContext new_context = this.controller
+ .createTokenContext(user, attr, null);
+ builder.setParam(new_context.getTokenType(),
+ new_context.getToken());
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ }
+ }else {
+ OAuthResponse res = OAuthASResponse
+ .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+ .setError(
+ OAuthError.CodeResponse.UNSUPPORTED_RESPONSE_TYPE)
+ .setErrorDescription("Unsupported Response type!\n")
+ .buildJSONMessage();
+ return Response.status(res.getResponseStatus())
+ .entity(res.getBody()).build();
+ }
+
+ String redirectURI = oauthRequest.getRedirectURI();
+
+ // enables state parameter to disable cross-site scripting attacks
+ final OAuthResponse response = builder.location(redirectURI)
+ .buildQueryMessage();
+ if (OAuthUtils.isEmpty(redirectURI)) {
+ throw new WebApplicationException(
+ Response.status(HttpServletResponse.SC_BAD_REQUEST)
+ .entity("OAuth callback url needs to be provided by client!!!\n")
+ .build());
+ }
+
+ return Response.status(response.getResponseStatus())
+ .location(new URI(response.getLocationUri())).build();
+ }catch (OAuthProblemException e) {
+ e.printStackTrace();
+ final Response.ResponseBuilder responseBuilder = Response
+ .status(HttpServletResponse.SC_BAD_REQUEST);
+ String redirectUri = e.getRedirectUri();
+
+ if (OAuthUtils.isEmpty(redirectUri))
+ throw new WebApplicationException(responseBuilder
+ .entity("OAuth callback url needs to be provided by client!!!\n")
+ .build());
+
+ final OAuthResponse response = OAuthASResponse
+ .errorResponse(HttpServletResponse.SC_BAD_REQUEST).error(e)
+ .location(redirectUri).buildQueryMessage();
+ final URI location = new URI(response.getLocationUri());
+ return responseBuilder.location(location).build();
+ }catch (OAuthSystemException | URISyntaxException | KustvaktException e) {
+ e.printStackTrace();
+ }
+ return Response.noContent().build();
+ }
+
+ @POST
+ @Path("revoke")
+ public Response revokeToken(@Context HttpServletRequest request,
+ @Context SecurityContext context,
+ @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+ @HeaderParam(ContainerRequest.HOST) String host)
+ throws OAuthSystemException, URISyntaxException {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ try {
+
+ if (!this.handler.revokeToken(ctx.getToken())) {
+ OAuthResponse res = OAuthASResponse
+ .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+ .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
+ .setErrorDescription("Invalid access token!\n")
+ .buildJSONMessage();
+ return Response.status(res.getResponseStatus())
+ .entity(res.getBody()).build();
+ }
+
+ }catch (KustvaktException e) {
+ e.printStackTrace();
+ // fixme: do something
+ /**
+ * final Response.ResponseBuilder responseBuilder = Response
+ .status(HttpServletResponse.SC_FOUND);
+ String redirectUri = e.getRedirectUri();
+
+ final OAuthResponse response = OAuthASResponse
+ .errorResponse(HttpServletResponse.SC_FOUND).error(e)
+ .location(redirectUri).buildQueryMessage();
+ final URI location = new URI(response.getLocationUri());
+ return responseBuilder.location(location).build();
+ */
+ }
+
+ return Response.ok().build();
+ }
+
+ @POST
+ @Consumes("application/x-www-form-urlencoded")
+ @Produces("application/json")
+ @Path("token")
+ public Response requestToken(@Context HttpServletRequest request,
+ @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+ @HeaderParam(ContainerRequest.HOST) String host,
+ MultivaluedMap form) throws OAuthSystemException {
+ boolean openid_valid = false;
+ User user = null;
+ OAuthTokenRequest oauthRequest;
+ OAuthASResponse.OAuthTokenResponseBuilder builder = OAuthASResponse
+ .tokenResponse(HttpServletResponse.SC_OK);
+
+ OAuthIssuer oauthIssuerImpl = new OAuthIssuerImpl(new MD5Generator());
+ ClientInfo info;
+ try {
+ oauthRequest = new OAuthTokenRequest(
+ new FormRequestWrapper(request, form));
+
+ if ((info = this.handler.getClient(oauthRequest.getClientId()))
+ == null) {
+ OAuthResponse res = OAuthASResponse
+ .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+ .setError(OAuthError.TokenResponse.INVALID_CLIENT)
+ .setErrorDescription("Invalid client id!\n")
+ .buildJSONMessage();
+ return Response.status(res.getResponseStatus())
+ .entity(res.getBody()).build();
+ }else if (!info.getClient_secret()
+ .equals(oauthRequest.getClientSecret())) {
+ OAuthResponse res = OAuthASResponse
+ .errorResponse(HttpServletResponse.SC_UNAUTHORIZED)
+ .setError(OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
+ .setErrorDescription("Invalid client secret!\n")
+ .buildJSONMessage();
+ return Response.status(res.getResponseStatus())
+ .entity(res.getBody()).build();
+ }
+
+ Map<String, Object> attr = new HashMap<>();
+ attr.put(Attributes.HOST, host);
+ attr.put(Attributes.USER_AGENT, agent);
+ attr.put(Attributes.SCOPES,
+ StringUtils.toString(oauthRequest.getScopes(), " "));
+
+ // support code (for external clients only) and password grant type
+ // password grant at this point is only allowed with trusted clients (korap frontend)
+ if (oauthRequest.getGrantType().equalsIgnoreCase(
+ GrantType.AUTHORIZATION_CODE.toString())) {
+ // validate auth code
+ AuthCodeInfo codeInfo;
+ try {
+ //can this be joined with the simple retrieval of access tokens?
+ // partially yes: auth code can be valid, even though no access token exists
+ // --> zero result set
+ codeInfo = this.handler
+ .getAuthorization(oauthRequest.getCode());
+ if (codeInfo == null) {
+ OAuthResponse res = OAuthASResponse.errorResponse(
+ HttpServletResponse.SC_UNAUTHORIZED).setError(
+ OAuthError.TokenResponse.INVALID_REQUEST)
+ .setErrorDescription(
+ "Invalid authorization code\n")
+ .buildJSONMessage();
+ return Response.status(res.getResponseStatus())
+ .entity(res.getBody()).build();
+ }else {
+ openid_valid = codeInfo.getScopes().contains("openid");
+ String accessToken = oauthIssuerImpl.accessToken();
+ // auth code posesses the user reference. native apps access_tokens are directly associated with the user
+ this.handler
+ .addToken(oauthRequest.getCode(), accessToken,
+ config.getTokenTTL());
+
+ builder.setTokenType(TokenType.BEARER.toString());
+ builder.setExpiresIn(
+ String.valueOf(config.getLongTokenTTL()));
+ builder.setAccessToken(accessToken);
+ }
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ // todo: errors for invalid scopes or different scopes then during authorization request?
+ //todo ??
+ attr.put(Attributes.SCOPES, codeInfo.getScopes());
+
+ }else if (oauthRequest.getGrantType()
+ .equalsIgnoreCase(GrantType.PASSWORD.toString())) {
+ //fixme: via https; as basic auth header and only if client is native!
+ if (!info.isConfidential()) {
+ OAuthResponse res = OAuthASResponse
+ .errorResponse(HttpServletResponse.SC_BAD_REQUEST)
+ .setError(
+ OAuthError.TokenResponse.UNAUTHORIZED_CLIENT)
+ .setErrorDescription(
+ "Grant type not supported for client!\n")
+ .buildJSONMessage();
+ return Response.status(res.getResponseStatus())
+ .entity(res.getBody()).build();
+ }
+
+ openid_valid = true;
+ try {
+ user = controller
+ .authenticate(0, oauthRequest.getUsername(),
+ oauthRequest.getPassword(), attr);
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+
+ try {
+ String accessToken = this.handler
+ .getToken(oauthRequest.getClientId(), user.getId());
+ if (accessToken == null) {
+ accessToken = oauthIssuerImpl.accessToken();
+ this.handler.addToken(accessToken, user.getId(),
+ oauthRequest.getClientId(), StringUtils
+ .toString(oauthRequest.getScopes(),
+ " "), config.getLongTokenTTL());
+ }
+ builder.setTokenType(TokenType.BEARER.toString());
+ builder.setExpiresIn(
+ String.valueOf(config.getLongTokenTTL()));
+ builder.setAccessToken(accessToken);
+
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ }
+
+ if (openid_valid && oauthRequest.getScopes()
+ .contains(Scopes.Scope.openid.toString())) {
+ try {
+ if (user == null)
+ user = controller
+ .authenticate(0, oauthRequest.getUsername(),
+ oauthRequest.getPassword(), attr);
+ controller.getUserDetails(user);
+ attr.put(Attributes.CLIENT_SECRET,
+ oauthRequest.getClientSecret());
+ TokenContext c = controller.createTokenContext(user, attr,
+ Attributes.OPENID_AUTHENTICATION);
+ builder.setParam(c.getTokenType(), c.getToken());
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ }
+
+ OAuthResponse r = builder.buildJSONMessage();
+ return Response.status(r.getResponseStatus()).entity(r.getBody())
+ .build();
+ }catch (OAuthProblemException ex) {
+ OAuthResponse r = OAuthResponse.errorResponse(401).error(ex)
+ .buildJSONMessage();
+ return Response.status(r.getResponseStatus()).entity(r.getBody())
+ .build();
+ }catch (OAuthSystemException e) {
+ e.printStackTrace();
+ // todo: throw error
+ }
+ return Response.noContent().build();
+ }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/service/UserService.java b/src/main/java/de/ids_mannheim/korap/web/service/UserService.java
new file mode 100644
index 0000000..befa736
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/service/UserService.java
@@ -0,0 +1,456 @@
+package de.ids_mannheim.korap.web.service;
+
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.sun.jersey.spi.container.ContainerRequest;
+import com.sun.jersey.spi.container.ResourceFilters;
+import de.ids_mannheim.korap.config.BeanConfiguration;
+import de.ids_mannheim.korap.config.Scopes;
+import de.ids_mannheim.korap.config.URIParam;
+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.security.ac.ResourceHandler;
+import de.ids_mannheim.korap.user.*;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import de.ids_mannheim.korap.utils.KustvaktLogger;
+import de.ids_mannheim.korap.utils.StringUtils;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import de.ids_mannheim.korap.web.KustvaktServer;
+import de.ids_mannheim.korap.web.filter.AuthFilter;
+import de.ids_mannheim.korap.web.filter.DefaultFilter;
+import de.ids_mannheim.korap.web.filter.PiwikFilter;
+import de.ids_mannheim.korap.web.utils.FormWrapper;
+import org.slf4j.Logger;
+
+import javax.ws.rs.*;
+import javax.ws.rs.core.*;
+import java.io.IOException;
+import java.util.*;
+
+/**
+ * @author hanl
+ * @date 29/01/2014
+ */
+@Path(KustvaktServer.API_VERSION + "/user")
+@Produces(MediaType.APPLICATION_JSON + ";charset=utf-8")
+@ResourceFilters({ PiwikFilter.class })
+public class UserService {
+
+ private static Logger error = KustvaktLogger
+ .initiate(KustvaktLogger.ERROR_LOG);
+ private static Logger jlog = KustvaktLogger
+ .initiate(KustvaktLogger.SECURITY_LOG);
+ private AuthenticationManagerIface controller;
+ private ResourceHandler resourceHandler;
+
+ private
+ @Context
+ UriInfo info;
+
+ public UserService() {
+ this.controller = BeanConfiguration.getBeans()
+ .getAuthenticationManager();
+ // this.resourceHandler = BeanConfiguration.getResourceHandler();
+ }
+
+ // fixme: json contains password in clear text. Encrypt request?
+ // fixme: should also collect service exception, not just db exception!
+ @POST
+ @Path("register")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public Response signUp(
+ @HeaderParam(ContainerRequest.USER_AGENT) String agent,
+ @HeaderParam(ContainerRequest.HOST) String host,
+ @Context Locale locale, MultivaluedMap form_values) {
+
+ FormWrapper wrapper = new FormWrapper(form_values);
+
+ wrapper.put(Attributes.HOST, host);
+ wrapper.put(Attributes.USER_AGENT, agent);
+ UriBuilder uriBuilder;
+ User user;
+ if (wrapper.get(Attributes.EMAIL) == null)
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.ILLEGAL_ARGUMENT, "parameter missing",
+ "email");
+
+ try {
+ uriBuilder = info.getBaseUriBuilder();
+ uriBuilder.path(KustvaktServer.API_VERSION).path("user")
+ .path("confirm");
+
+ user = controller.createUserAccount(wrapper);
+
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ URIParam uri = user.getField(URIParam.class);
+ if (uri.hasValues()) {
+ uriBuilder.queryParam(Attributes.QUERY_PARAM_URI,
+ uri.getUriFragment())
+ .queryParam(Attributes.QUERY_PARAM_USER,
+ user.getUsername());
+ jlog.info("registration was successful for user '{}'",
+ form_values.get(Attributes.USERNAME));
+ Map object = new HashMap();
+ object.put("confirm_uri", uriBuilder.build());
+ object.put("uri_expiration",
+ TimeUtils.format(uri.getUriExpiration()));
+ return Response.ok(JsonUtils.toJSON(object)).build();
+ }else {
+ // todo: return error or warning
+ return null;
+ }
+
+ }
+
+ //todo: password update in special function?
+ @POST
+ @Path("update")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+ PiwikFilter.class })
+ public Response updateAccount(@Context SecurityContext ctx, String json) {
+ TokenContext context = (TokenContext) ctx.getUserPrincipal();
+ try {
+ User user = controller.getUser(context.getUsername());
+
+ JsonNode node = JsonUtils.readTree(json);
+ KorAPUser ident = (KorAPUser) user;
+ KorAPUser values = User.UserFactory.toUser(json);
+ // user = controller
+ // .checkPasswordAllowance(ident, values.getPassword(),
+ // node.path("new_password").asText());
+ controller.updateAccount(user);
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("confirm")
+ @Produces(MediaType.TEXT_HTML)
+ public Response confirmRegistration(@QueryParam("uri") String uritoken,
+ @Context Locale locale, @QueryParam("user") String username) {
+ if (uritoken == null)
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.ILLEGAL_ARGUMENT, "parameter missing",
+ "Uri-Token");
+ if (username == null)
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.ILLEGAL_ARGUMENT, "parameter missing",
+ "Username");
+
+ try {
+ controller.confirmRegistration(uritoken, username);
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok("success").build();
+ }
+
+ // todo: auditing!
+ @POST
+ @Path("requestReset")
+ @Produces(MediaType.TEXT_HTML)
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public Response requestPasswordReset(@Context Locale locale, String json) {
+ JsonNode node = JsonUtils.readTree(json);
+ StringBuilder builder = new StringBuilder();
+ String username, email;
+ username = node.path(Attributes.USERNAME).asText();
+ email = node.path(Attributes.EMAIL).asText();
+
+ // deprecated --> depends on the client!
+ // String url = config.getMailProperties()
+ // .getProperty("korap.frontend.url", "");
+ // if (url.isEmpty())
+ // return Response.ok("URLException: Missing source URL").build();
+
+ // URIUtils utils = new URIUtils(info);
+ // may inject the actual REST url in a redirect request?!
+ // UriBuilder uriBuilder = UriBuilder.fromUri(url).fragment("reset");
+ Object[] objects;
+ try {
+ builder.append("?");
+ // just append the endpint fragment plus the query parameter.
+ // the address by which the data is handled depends on the frontend
+ objects = controller.validateResetPasswordRequest(username, email);
+ builder.append(Attributes.QUERY_PARAM_URI).append("=")
+ .append(objects[0]);
+ builder.append(Attributes.QUERY_PARAM_USER).append("=")
+ .append(username);
+ }catch (KustvaktException e) {
+ error.error("Eoxception encountered!", e);
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+
+ ObjectNode obj = JsonUtils.createObjectNode();
+ obj.put(Attributes.URI, builder.toString());
+ obj.put(Attributes.URI_EXPIRATION, String.valueOf(objects[1]));
+ return Response.ok(JsonUtils.toJSON(obj)).build();
+ }
+
+ @POST
+ @Path("reset")
+ @Produces(MediaType.TEXT_HTML)
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ public Response resetPassword(
+ @QueryParam(Attributes.QUERY_PARAM_URI) String uri,
+ @QueryParam(Attributes.QUERY_PARAM_USER) String username,
+ @Context HttpHeaders headers, String passphrase) {
+ try {
+ controller.resetPassword(uri, username, passphrase);
+ }catch (KustvaktException e) {
+ error.error("Exception encountered!", e);
+ return Response.notModified().build();
+ }
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("info")
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+ PiwikFilter.class })
+ public Response getStatus(@Context SecurityContext context,
+ @QueryParam("scope") String scope) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ User user;
+ try {
+ user = controller.getUser(ctx.getUsername());
+ controller.getUserDetails(user);
+ Set<String> base_scope = StringUtils
+ .toSet((String) ctx.getParameters().get(Attributes.SCOPES),
+ " ");
+ base_scope.retainAll(StringUtils.toSet(scope));
+ scope = StringUtils.toString(base_scope);
+ }catch (KustvaktException e) {
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok(JsonUtils.toJSON(Scopes
+ .mapOpenIDConnectScopes(scope, user.getDetails()))).build();
+ }
+
+ @GET
+ @Path("settings")
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+ PiwikFilter.class })
+ public Response getUserSettings(@Context SecurityContext context,
+ @Context Locale locale) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ User user;
+ try {
+ user = controller.getUser(ctx.getUsername());
+ controller.getUserSettings(user);
+
+ }catch (KustvaktException e) {
+ error.error("Exception encountered!", e);
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok(JsonUtils.toJSON(user.getSettings().toObjectMap()))
+ .build();
+ }
+
+ // todo: test
+ @POST
+ @Path("settings")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+ PiwikFilter.class })
+ public Response updateSettings(@Context SecurityContext context,
+ @Context Locale locale, String values) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ Map<String, Object> settings;
+ try {
+ settings = JsonUtils.read(values, Map.class);
+ }catch (IOException e) {
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.REQUEST_INVALID,
+ "Could not read parameters", values);
+ }
+
+ try {
+ User user = controller.getUser(ctx.getUsername());
+ UserSettings us = controller.getUserSettings(user);
+ // todo:
+ // SecurityManager.findbyId(us.getDefaultConstfoundry(), user, Foundry.class);
+ // SecurityManager.findbyId(us.getDefaultLemmafoundry(), user, Foundry.class);
+ // SecurityManager.findbyId(us.getDefaultPOSfoundry(), user, Foundry.class);
+ // SecurityManager.findbyId(us.getDefaultRelfoundry(), user, Foundry.class);
+ us.updateObjectSettings(settings);
+ controller.updateUserSettings(user, us);
+ if (user.isDemo())
+ return Response.notModified().build();
+ }catch (KustvaktException e) {
+ error.error("Exception encountered!", e);
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("details")
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+ PiwikFilter.class })
+ public Response getDetails(@Context SecurityContext context,
+ @Context Locale locale) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ User user;
+ try {
+ user = controller.getUser(ctx.getUsername());
+ controller.getUserDetails(user);
+ }catch (KustvaktException e) {
+ error.error("Exception encountered!", e);
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+
+ return Response.ok(JsonUtils.toJSON(user.getDetails().toMap())).build();
+ }
+
+ @POST
+ @Path("details")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+ PiwikFilter.class })
+ public Response updateDetails(@Context SecurityContext context,
+ @Context Locale locale, String values) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ System.out.println("TO BE UPDATED DATA " + values);
+ System.out.println("USER CONTEXT " + ctx);
+ Map<String, String> details;
+ try {
+ details = JsonUtils.read(values, Map.class);
+ }catch (IOException e) {
+ error.error("Exception encountered!", e);
+ throw BeanConfiguration.getResponseHandler()
+ .throwit(StatusCodes.REQUEST_INVALID,
+ "Could not read parameters", values);
+ }
+
+ try {
+ User user = controller.getUser(ctx.getUsername());
+ UserDetails det = controller.getUserDetails(user);
+ det.updateDetails(details);
+ controller.updateUserDetails(user, det);
+ if (user.isDemo())
+ return Response.notModified().build();
+ }catch (KustvaktException e) {
+ error.error("Exception encountered!", e);
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+
+ return Response.ok().build();
+ }
+
+ //fixme: if policy allows, foreign user might be allowed to change search!
+ @POST
+ @Path("queries")
+ @Consumes(MediaType.APPLICATION_FORM_URLENCODED)
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+ PiwikFilter.class })
+ public Response updateQueries(@Context SecurityContext context,
+ String json) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ Collection<UserQuery> add = new HashSet<>();
+ try {
+ User user = controller.getUser(ctx.getUsername());
+ List<UserQuery> userQuieres = new ArrayList<>();
+ JsonNode nodes = JsonUtils.readTree(json);
+ Iterator<JsonNode> node = nodes.elements();
+ while (node.hasNext()) {
+ JsonNode cursor = node.next();
+ UserQuery query = new UserQuery(cursor.path("id").asInt(),
+ user.getId());
+ query.setQueryLanguage(cursor.path("queryLanguage").asText());
+ query.setQuery(cursor.path("query").asText());
+ query.setDescription(cursor.path("description").asText());
+ userQuieres.add(query);
+ }
+
+ //1: add all that are new, update all that are retained, delete the rest
+ // Set<UserQuery> resources = ResourceFinder
+ // .search(user, UserQuery.class);
+ //
+ // add.addAll(userQuieres);
+ // add.removeAll(resources);
+ // Collection<UserQuery> update = new HashSet<>(userQuieres);
+ // update.retainAll(resources);
+ // resources.removeAll(userQuieres);
+ //
+ // if (!update.isEmpty()) {
+ // resourceHandler.updateResources(user,
+ // update.toArray(new UserQuery[update.size()]));
+ // }
+ // if (!add.isEmpty()) {
+ // resourceHandler.storeResources(user,
+ // add.toArray(new UserQuery[add.size()]));
+ // }
+ // if (!resources.isEmpty()) {
+ // resourceHandler.deleteResources(user,
+ // resources.toArray(new UserQuery[resources.size()]));
+ // }
+ }catch (KustvaktException e) {
+ error.error("Exception encountered!", e);
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok(JsonUtils.toJSON(add)).build();
+ }
+
+ @DELETE
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+ PiwikFilter.class })
+ public Response deleteUser(@Context SecurityContext context) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ try {
+ User user = controller.getUser(ctx.getUsername());
+ if (user.isDemo())
+ return Response.notModified().build();
+ controller.deleteAccount(user);
+ }catch (KustvaktException e) {
+ error.error("Exception encountered!", e);
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok().build();
+ }
+
+ @GET
+ @Path("queries")
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+ PiwikFilter.class })
+ public Response getQueries(@Context SecurityContext context,
+ @Context Locale locale) {
+ TokenContext ctx = (TokenContext) context.getUserPrincipal();
+ String queryStr;
+ try {
+ User user = controller.getUser(ctx.getUsername());
+ // Set<UserQuery> queries = ResourceFinder
+ // .search(user, UserQuery.class);
+ // queryStr = JsonUtils.toJSON(queries);
+ //todo:
+ queryStr = "";
+ }catch (KustvaktException e) {
+ error.error("Exception encountered!", e);
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok(queryStr).build();
+ }
+
+ @GET
+ @Path("logout")
+ @ResourceFilters({ AuthFilter.class, DefaultFilter.class,
+ PiwikFilter.class })
+ public Response logout(@Context SecurityContext ctx,
+ @Context Locale locale) {
+ TokenContext context = (TokenContext) ctx.getUserPrincipal();
+ try {
+ controller.logout(context);
+ }catch (KustvaktException e) {
+ error.error("Logout Exception", e);
+ throw BeanConfiguration.getResponseHandler().throwit(e);
+ }
+ return Response.ok().build();
+ }
+}
diff --git a/src/main/java/de/ids_mannheim/korap/web/utils/FormRequestWrapper.java b/src/main/java/de/ids_mannheim/korap/web/utils/FormRequestWrapper.java
new file mode 100644
index 0000000..143aa04
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/web/utils/FormRequestWrapper.java
@@ -0,0 +1,64 @@
+package de.ids_mannheim.korap.web.utils;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.ws.rs.core.MultivaluedMap;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Helper class to wrapp multivaluedmap into a hashmap. Depending on the strict parameter,
+ * list values are retained in the resulting wrapper map.
+ *
+ * @author hanl
+ * @date 25/04/2015
+ */
+public class FormRequestWrapper extends HttpServletRequestWrapper {
+
+ private MultivaluedMap<String, String> form;
+
+ /**
+ * Constructs a request object wrapping the given request.
+ *
+ * @param request
+ * @throws IllegalArgumentException if the request is null
+ */
+ public FormRequestWrapper(HttpServletRequest request,
+ MultivaluedMap<String, String> form) {
+ super(request);
+ this.form = form;
+ }
+
+ @Override
+ public String getParameter(String name) {
+ String value = super.getParameter(name);
+ if (value == null)
+ value = form.getFirst(name);
+ return value;
+ }
+
+ @Override
+ public String[] getParameterValues(String name) {
+ String[] values = super.getParameterValues(name);
+ if (values == null && form.get(name) != null) {
+ values = new String[form.get(name).size()];
+ values = form.get(name).toArray(values);
+ }
+ return values;
+ }
+
+ public HashMap<String, Object> toMap(boolean strict) {
+ HashMap<String, Object> map = new HashMap<>();
+ for (Map.Entry<String, List<String>> e : form.entrySet()) {
+ if (e.getValue().size() == 1)
+ map.put(e.getKey(), e.getValue().get(0));
+ else if (!strict)
+ map.put(e.getKey(), e.getValue());
+ }
+ return map;
+ }
+
+}
+
+
diff --git a/src/test/java/CollectionQueryBuilderTest.java b/src/test/java/CollectionQueryBuilderTest.java
new file mode 100644
index 0000000..de9a089
--- /dev/null
+++ b/src/test/java/CollectionQueryBuilderTest.java
@@ -0,0 +1,87 @@
+import com.fasterxml.jackson.databind.JsonNode;
+import de.ids_mannheim.korap.utils.CollectionQueryBuilder3;
+import de.ids_mannheim.korap.utils.JsonUtils;
+import org.junit.Test;
+
+/**
+ * @author hanl
+ * @date 12/08/2015
+ */
+public class CollectionQueryBuilderTest {
+
+ @Test
+ public void testsimpleAdd() {
+ CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+ b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "WPD");
+ // System.out.println(b.toJSON());
+ }
+
+ @Test
+ public void testSimpleConjunctive() {
+ CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+ b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "WPD").and()
+ .addSegment("textClass", CollectionQueryBuilder3.EQ.EQUAL,
+ "freizeit");
+ // System.out.println(b.toJSON());
+ }
+
+ @Test
+ public void testSimpleDisjunctive() {
+ CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+ b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "WPD").and()
+ .addSegment("textClass", CollectionQueryBuilder3.EQ.EQUAL,
+ "freizeit");
+ // System.out.println(b.toJSON());
+ }
+
+ @Test
+ public void testComplexSubQuery() {
+
+ CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+ b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "ADF").and()
+ .addSub("textClass=freizeit & corpusID=WPD");
+ // System.out.println(b.toJSON());
+ }
+
+ @Test // basically joining two or more resource queries
+ public void testAddResourceQueryAfter() {
+ CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+ b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "ADF").and()
+ .addSub("textClass=freizeit & corpusID=WPD");
+ // System.out.println(b.toJSON());
+
+ // join.addSegment("textClass", "politik");
+ }
+
+ @Test // basically joining two or more resource queries
+ public void testAddResourceQueryBefore() {
+ CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+ b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "ADF").and()
+ .addSub("textClass!=freizeit & corpusID=WPD");
+
+ // CollectionQueryBuilder3 join = new CollectionQueryBuilder3();
+ // join.addRaw(b.toJSON());
+ // join.addSegment("textClass", "politik");
+ // System.out.println("JOINED " + join.toJSON());
+ }
+
+ @Test
+ public void test1() {
+ CollectionQueryBuilder3 b = new CollectionQueryBuilder3();
+ b.addSegment("corpusID", CollectionQueryBuilder3.EQ.EQUAL, "ADF").or()
+ .addSub("textClass=freizeit & corpusID=WPD");
+
+ CollectionQueryBuilder3 c = new CollectionQueryBuilder3();
+ c.setBaseQuery(b.toJSON());
+ c.addSub("textClass=wissenschaft");
+
+ JsonNode node = JsonUtils.readTree(c.toJSON());
+
+ assert node != null;
+ assert node.at("/collection/operands/2/@type").asText()
+ .equals("koral:doc");
+ assert node.at("/collection/operands/2/value").asText()
+ .equals("wissenschaft");
+ }
+
+}