refactoring and test run without errors
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/AuditingIface.java b/src/main/java/de/ids_mannheim/korap/interfaces/db/AuditingIface.java
similarity index 97%
rename from src/main/java/de/ids_mannheim/korap/interfaces/AuditingIface.java
rename to src/main/java/de/ids_mannheim/korap/interfaces/db/AuditingIface.java
index 18a616f..ed21e60 100644
--- a/src/main/java/de/ids_mannheim/korap/interfaces/AuditingIface.java
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/db/AuditingIface.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.interfaces;
+package de.ids_mannheim.korap.interfaces.db;
import de.ids_mannheim.korap.auditing.AuditRecord;
import de.ids_mannheim.korap.user.User;
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/EntityHandlerIface.java b/src/main/java/de/ids_mannheim/korap/interfaces/db/EntityHandlerIface.java
similarity index 96%
rename from src/main/java/de/ids_mannheim/korap/interfaces/EntityHandlerIface.java
rename to src/main/java/de/ids_mannheim/korap/interfaces/db/EntityHandlerIface.java
index aed2871..f349f23 100644
--- a/src/main/java/de/ids_mannheim/korap/interfaces/EntityHandlerIface.java
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/db/EntityHandlerIface.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.interfaces;
+package de.ids_mannheim.korap.interfaces.db;
import de.ids_mannheim.korap.exceptions.EmptyResultException;
import de.ids_mannheim.korap.exceptions.KustvaktException;
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/PersistenceClient.java b/src/main/java/de/ids_mannheim/korap/interfaces/db/PersistenceClient.java
similarity index 95%
rename from src/main/java/de/ids_mannheim/korap/interfaces/PersistenceClient.java
rename to src/main/java/de/ids_mannheim/korap/interfaces/db/PersistenceClient.java
index 07d53c7..c21d941 100644
--- a/src/main/java/de/ids_mannheim/korap/interfaces/PersistenceClient.java
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/db/PersistenceClient.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.interfaces;
+package de.ids_mannheim.korap.interfaces.db;
import lombok.Getter;
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/PolicyHandlerIface.java b/src/main/java/de/ids_mannheim/korap/interfaces/db/PolicyHandlerIface.java
similarity index 98%
rename from src/main/java/de/ids_mannheim/korap/interfaces/PolicyHandlerIface.java
rename to src/main/java/de/ids_mannheim/korap/interfaces/db/PolicyHandlerIface.java
index 122970f..15e4296 100644
--- a/src/main/java/de/ids_mannheim/korap/interfaces/PolicyHandlerIface.java
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/db/PolicyHandlerIface.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.interfaces;
+package de.ids_mannheim.korap.interfaces.db;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.resources.KustvaktResource;
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/ResourceOperationIface.java b/src/main/java/de/ids_mannheim/korap/interfaces/db/ResourceOperationIface.java
similarity index 96%
rename from src/main/java/de/ids_mannheim/korap/interfaces/ResourceOperationIface.java
rename to src/main/java/de/ids_mannheim/korap/interfaces/db/ResourceOperationIface.java
index fa0bf35..260e928 100644
--- a/src/main/java/de/ids_mannheim/korap/interfaces/ResourceOperationIface.java
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/db/ResourceOperationIface.java
@@ -1,4 +1,4 @@
-package de.ids_mannheim.korap.interfaces;
+package de.ids_mannheim.korap.interfaces.db;
import de.ids_mannheim.korap.exceptions.KustvaktException;
import de.ids_mannheim.korap.resources.KustvaktResource;
diff --git a/src/main/java/de/ids_mannheim/korap/interfaces/defaults/KustvaktEncryption.java b/src/main/java/de/ids_mannheim/korap/interfaces/defaults/KustvaktEncryption.java
new file mode 100644
index 0000000..a21d767
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/interfaces/defaults/KustvaktEncryption.java
@@ -0,0 +1,483 @@
+package de.ids_mannheim.korap.interfaces.defaults;
+
+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.EncryptionIface;
+import de.ids_mannheim.korap.user.User;
+import edu.emory.mathcs.backport.java.util.Collections;
+import org.apache.commons.codec.EncoderException;
+import org.apache.commons.codec.binary.Base64;
+import org.mindrot.jbcrypt.BCrypt;
+import org.owasp.esapi.ESAPI;
+import org.owasp.esapi.Randomizer;
+import org.owasp.esapi.Validator;
+import org.owasp.esapi.errors.ValidationException;
+import org.owasp.esapi.reference.DefaultValidator;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class KustvaktEncryption implements EncryptionIface {
+
+ private static final String ALGORITHM = "SHA-256";
+ private static Logger jlog = LoggerFactory
+ .getLogger(KustvaktEncryption.class);
+ // todo: disable this
+ private static final String PASSWORD_SALT_FIELD = "accountCreation";
+
+ private final boolean nullable;
+ private final Validator validator;
+ private final Randomizer randomizer;
+ private KustvaktConfiguration config;
+
+ public KustvaktEncryption(KustvaktConfiguration config) {
+ jlog.info("initializing KorAPEncryption implementation");
+ this.nullable = false;
+ this.validator = DefaultValidator.getInstance();
+ this.randomizer = ESAPI.randomizer();
+ this.config = config;
+ }
+
+ public static boolean matchTokenByteCode(Object param) {
+ if (!(param instanceof String))
+ return false;
+ String token = (String) param;
+ byte[] bytes = token.getBytes();
+ return 64 == bytes.length;
+ }
+
+ private String encodeBase(byte[] bytes) throws EncoderException {
+ return Base64.encodeBase64String(bytes);
+ }
+
+ @Override
+ public String encodeBase() {
+ try {
+ return encodeBase(this.createSecureRandom(24));
+ }catch (EncoderException e) {
+ return "";
+ }
+ }
+
+ public String produceSecureHash(String input) {
+ return produceSecureHash(input, "");
+ }
+
+ @Override
+ public String produceSecureHash(String input, String salt) {
+ String hashString = "";
+ switch (config.getEncryption()) {
+ case ESAPICYPHER:
+ try {
+ hashString = hash(input, salt);
+ }catch (NoSuchAlgorithmException e) {
+ jlog.error("there was an encryption error!", e);
+ return null;
+ }catch (Exception e) {
+ jlog.error("there was an error!", e);
+ return null;
+ }
+ break;
+ case SIMPLE:
+ try {
+ MessageDigest md = MessageDigest.getInstance("SHA-256");
+ md.update(input.getBytes("UTF-8"));
+ byte[] digest = md.digest();
+
+ for (byte b : digest)
+ hashString += String.format("%02x", b);
+ }catch (UnsupportedEncodingException | NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ break;
+ case BCRYPT:
+ hashString = bcryptHash(input, salt);
+ break;
+ default:
+ jlog.warn("Invalid value: {}", config.getEncryption());
+ break;
+ }
+ return hashString;
+ }
+
+ public String hash(String text, String salt) throws Exception {
+ byte[] bytes;
+
+ MessageDigest md = MessageDigest.getInstance(ALGORITHM);
+ md.reset();
+ md.update(ESAPI.securityConfiguration().getMasterSalt());
+ md.update(salt.getBytes());
+ md.update(text.getBytes());
+
+ bytes = md.digest();
+ for (int i = 0; i < 234; ++i) {
+ md.reset();
+ bytes = md.digest(bytes);
+ }
+ String coding = ESAPI.encoder().encodeForBase64(bytes, false);
+ return coding;
+ }
+
+ @Override
+ public String hash(String input) {
+ String hashString = "";
+ MessageDigest md;
+ try {
+ md = MessageDigest.getInstance("SHA-256");
+ md.update(input.getBytes("UTF-8"));
+ }catch (NoSuchAlgorithmException e) {
+ return "";
+ }catch (UnsupportedEncodingException e) {
+ return "";
+ }
+
+ byte[] digest = md.digest();
+
+ for (byte b : digest) {
+ hashString += String.format("%02x", b);
+ }
+ return hashString;
+ }
+
+ /**
+ * // some sort of algorithm to create token and isSystem regularly the integrity
+ * // of the token
+ * public String createAuthToken() {
+ * final byte[] rNumber = SecureRGenerator
+ * .getNextSecureRandom(SecureRGenerator.TOKEN_RANDOM_SIZE);
+ * String hash;
+ * try {
+ * hash = produceSimpleHash(SecureRGenerator.toHex(rNumber));
+ * } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
+ * return "";
+ * }
+ * return hash;
+ * }
+ */
+
+ private byte[] createSecureRandom(int size) {
+ return SecureRGenerator.getNextSecureRandom(size);
+ }
+
+ @Override
+ public String createToken(boolean hash, Object... obj) {
+ StringBuffer b = new StringBuffer();
+ try {
+ for (Object o : obj) {
+ b.append(" | ");
+ b.append(o);
+ }
+ if (hash)
+ return encodeBase(hash(b.toString().trim()).getBytes());
+ else
+ return encodeBase(b.toString().trim().getBytes());
+ }catch (EncoderException e) {
+ return "";
+ }
+
+ }
+
+ @Override
+ public String createToken() {
+ String encoded;
+ String v = randomizer
+ .getRandomString(SecureRGenerator.TOKEN_RANDOM_SIZE,
+ SecureRGenerator.toHex(createSecureRandom(64))
+ .toCharArray());
+ encoded = hash(v);
+ jlog.trace("creating new token {}", encoded);
+ return encoded;
+ }
+
+ @Override
+ public String createID(Object... obj) {
+ final byte[] rNumber = SecureRGenerator
+ .getNextSecureRandom(SecureRGenerator.CORPUS_RANDOM_SIZE);
+ if (obj.length != 0) {
+ ArrayList s = new ArrayList();
+ Collections.addAll(s, obj);
+ obj = s.toArray();
+ }else {
+ obj = new Object[1];
+ obj[0] = rNumber;
+ }
+ return createToken(false, obj);
+ }
+
+ @Override
+ public boolean checkHash(String plain, String hash, String salt) {
+ String pw = "";
+ switch (config.getEncryption()) {
+ case ESAPICYPHER:
+ pw = produceSecureHash(plain, salt);
+ break;
+ case BCRYPT:
+ try {
+ return BCrypt.checkpw(plain, hash);
+ }catch (IllegalArgumentException e) {
+ return false;
+ }
+ case SIMPLE:
+ pw = hash(plain);
+ break;
+ }
+ return pw.equals(hash);
+ }
+
+ @Override
+ public boolean checkHash(String plain, String hash) {
+ switch (config.getEncryption()) {
+ case ESAPICYPHER:
+ return produceSecureHash(plain).equals(hash);
+ case BCRYPT:
+ try {
+ return BCrypt.checkpw(plain, hash);
+ }catch (IllegalArgumentException e) {
+ return false;
+ }
+ case SIMPLE:
+ return hash(plain).equals(hash);
+ }
+ return false;
+ }
+
+ @Override
+ public String getSalt(User user) {
+ Class u = user.getClass();
+ Field field;
+ try {
+ field = u.getSuperclass().getDeclaredField(PASSWORD_SALT_FIELD);
+ }catch (NoSuchFieldException e) {
+ try {
+ field = u.getDeclaredField(PASSWORD_SALT_FIELD);
+ }catch (NoSuchFieldException e1) {
+ // do nothing
+ e.printStackTrace();
+ return null;
+ }
+ }
+ try {
+ field.setAccessible(true);
+ String value = String.valueOf(field.get(user));
+ field.setAccessible(false);
+ return value;
+ }catch (IllegalAccessException e) {
+ // do nothing
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ @Override
+ public Map<String, Object> validateMap(Map<String, Object> map)
+ throws KustvaktException {
+ Map<String, Object> safeMap = new HashMap<>();
+ if (map != null) {
+ for (Map.Entry<String, Object> entry : map.entrySet()) {
+ Object value = null;
+ if (entry.getValue() instanceof String) {
+ value = validateString((String) entry.getValue());
+
+ }else if (entry.getValue() instanceof List) {
+ List list = (List) entry.getValue();
+ for (Object v : list) {
+ if (v instanceof String)
+ validateString((String) v);
+ }
+
+ if (((List) entry.getValue()).size() == 1)
+ value = list.get(0);
+ else
+ value = list;
+ }
+ safeMap.put(entry.getKey(), value);
+ }
+ }
+ return safeMap;
+ }
+
+
+ private String validateString(String descr, String input, String type,
+ int length, boolean nullable) throws KustvaktException {
+ if (jlog.isDebugEnabled())
+ jlog.debug("validating string entry '{}'", input);
+ String s;
+ try {
+ s = validator.getValidInput(descr, input, type, length, nullable);
+ }catch (ValidationException e) {
+ jlog.error(
+ "String value did not validate ('{}') with validation type {}",
+ new Object[] { input, type, e.getMessage() });
+ throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT,
+ "invalid string of type " + type, input);
+ }
+ return s;
+ }
+
+ @Override
+ public String validateString(String input) throws KustvaktException {
+ if (input.contains("@")) {
+ return validateEmail(input);
+ }else
+ return validateString("Safe String", input, "SafeString",
+ config.getValidationStringLength(), nullable);
+ }
+
+ @Override
+ public String validateEmail(String email) throws KustvaktException {
+ jlog.debug("validating email entry '{}'", email);
+ return validateString("Email", email, "Email",
+ config.getValidationEmaillength(), nullable);
+ }
+
+ @Override
+ public String validateIPAddress(String ipaddress) throws KustvaktException {
+ jlog.debug("validating IP address entry '{}'", ipaddress);
+ return validateString("IP Address", ipaddress, "IPAddress",
+ config.getValidationStringLength(), nullable);
+ }
+
+ @Override
+ public void validate(Object instance) throws KustvaktException {
+ if (instance == null)
+ return;
+ try {
+ validateStringField(instance.getClass().getDeclaredFields(),
+ instance);
+ validateStringField(
+ instance.getClass().getSuperclass().getDeclaredFields(),
+ instance);
+ }catch (IllegalAccessException e) {
+ jlog.error("object value did not validate", e.getMessage());
+ throw new KustvaktException(StatusCodes.PARAMETER_VALIDATION_ERROR,
+ "object could not be validated", instance.toString());
+ }
+ }
+
+ //fixme: fix validation algorithm
+ @Override
+ public String validatePassphrase(String pw) throws KustvaktException {
+ String safe_string = validateString(pw);
+ String pw_conf;
+ try {
+ pw_conf = validator
+ .getValidInput("User Password", safe_string, "Password", 20,
+ false);
+ }catch (ValidationException e) {
+ jlog.error("password value did not validate", e.getMessage());
+ throw new KustvaktException(StatusCodes.PARAMETER_VALIDATION_ERROR,
+ "password did not validate", "password");
+ }
+ return pw_conf;
+ }
+
+ //FIXME: currently all sets are skipped during validation (since users should not be allowed to edit those sets anyway,
+ //I think we will be safe here
+ private void validateStringField(Field[] fields, Object instance)
+ throws KustvaktException, IllegalAccessException {
+ for (Field field : fields) {
+ boolean priv = false;
+ if (field.getType().isAssignableFrom(String.class)) {
+ if (Modifier.isPrivate(field.getModifiers())) {
+ priv = true;
+ field.setAccessible(true);
+ }
+ if (field.getName().equals("password") | Modifier
+ .isFinal(field.getModifiers()))
+ continue;
+ String val = (String) field.get(instance);
+ if (val != null) {
+ String[] set = val.split(";");
+ if (set.length > 1)
+ continue;
+ }
+ String safe;
+ if (!field.getName().equals("email"))
+ safe = validateString("Safe String", val, "SafeString",
+ config.getValidationStringLength(), true);
+ else
+ safe = validateString("User Email", val, "Email",
+ config.getValidationEmaillength(), true);
+ field.set(instance, safe == null ? "" : safe);
+
+ if (priv) {
+ field.setAccessible(false);
+ }
+ }
+ }
+ }
+
+ private String bcryptHash(String text, String salt) {
+ if (salt == null || salt.isEmpty())
+ salt = BCrypt.gensalt(config.getLoadFactor());
+ return BCrypt.hashpw(text, salt);
+ }
+
+ @Override
+ public String toString() {
+ return this.getClass().getCanonicalName();
+ }
+
+ public static class SecureRGenerator {
+ private static final String SHA1_PRNG = "SHA1PRNG";
+ protected static final int DEFAULT_RANDOM_SIZE = 128;
+ protected static final int TOKEN_RANDOM_SIZE = 128;
+ protected static final int USERID_RANDOM_SIZE = 64;
+ protected static final int CORPUS_RANDOM_SIZE = 48;
+ private static final char[] HEX_DIGIT = { '0', '1', '2', '3', '4', '5',
+ '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'z', 'x', 'h',
+ 'q', 'w' };
+ private static final SecureRandom sRandom__;
+
+ static {
+ try {
+ sRandom__ = SecureRandom.getInstance("SHA1PRNG");
+ }catch (NoSuchAlgorithmException e) {
+ throw new Error(e);
+ }
+ }
+
+ public static byte[] getNextSecureRandom(int bits) {
+ if (bits % 8 != 0) {
+ throw new IllegalArgumentException(
+ "Size is not divisible by 8!");
+ }
+
+ byte[] bytes = new byte[bits / 8];
+
+ sRandom__.nextBytes(bytes);
+
+ return bytes;
+ }
+
+ public static String toHex(byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+
+ StringBuilder buffer = new StringBuilder(bytes.length * 2);
+ for (byte thisByte : bytes) {
+ buffer.append(byteToHex(thisByte));
+ }
+
+ return buffer.toString();
+ }
+
+ private static String byteToHex(byte b) {
+ char[] array = { HEX_DIGIT[(b >> 4 & 0xF)], HEX_DIGIT[(b & 0xF)] };
+ return new String(array);
+ }
+ }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/resource/rewrite/IdWriter.java b/src/main/java/de/ids_mannheim/korap/resource/rewrite/IdWriter.java
new file mode 100644
index 0000000..0c91ac0
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/resource/rewrite/IdWriter.java
@@ -0,0 +1,8 @@
+package de.ids_mannheim.korap.resource.rewrite;
+
+/**
+ * @author hanl
+ * @date 25/09/2015
+ */
+public class IdWriter {
+}
diff --git a/src/main/java/de/ids_mannheim/korap/security/ac/PolicyDao.java b/src/main/java/de/ids_mannheim/korap/security/ac/PolicyDao.java
new file mode 100644
index 0000000..d260b65
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/security/ac/PolicyDao.java
@@ -0,0 +1,808 @@
+package de.ids_mannheim.korap.security.ac;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.exceptions.StatusCodes;
+import de.ids_mannheim.korap.exceptions.dbException;
+import de.ids_mannheim.korap.ext.interfaces.PolicyHandlerIface;
+import de.ids_mannheim.korap.ext.resource.KorAPResource;
+import de.ids_mannheim.korap.ext.resource.ResourceFactory;
+import de.ids_mannheim.korap.ext.security.types.Parameter;
+import de.ids_mannheim.korap.ext.security.types.PolicyCondition;
+import de.ids_mannheim.korap.ext.security.types.SecurityPolicy;
+import de.ids_mannheim.korap.interfaces.PersistenceClient;
+import de.ids_mannheim.korap.user.Attributes;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.BooleanUtils;
+import de.ids_mannheim.korap.utils.KustvaktLogger;
+import de.ids_mannheim.korap.utils.StringUtils;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.jdbc.core.RowCallbackHandler;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import org.springframework.jdbc.support.KeyHolder;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * @author hanl
+ * @date 14/01/2014
+ */
+// todo: transactions and exception management
+public class PolicyDao implements PolicyHandlerIface {
+
+ private NamedParameterJdbcTemplate jdbcTemplate;
+
+ public PolicyDao(PersistenceClient client) {
+ this.jdbcTemplate = (NamedParameterJdbcTemplate) client.getSource();
+ }
+
+ /**
+ * @param policy
+ * @param user
+ * @return int to indicate the rows updated/inserted
+ * @throws KustvaktException
+ */
+ // fixme: better way of dealing with this?
+ // fixme: enable needs to be set specifically for mysql db
+ @Override
+ public int createPolicy(SecurityPolicy policy, User user)
+ throws KustvaktException {
+ String sql =
+ "INSERT INTO policy_store (target_id, creator, created, posix, enable, expire, iprange)"
+ + " SELECT id, :creator, :cr, :posix, :en, :exp, :ip FROM resource_store WHERE persistent_id=:target;";
+
+ if (policy.getTarget() == null)
+ throw new dbException(user.getId(), "policy_store",
+ StatusCodes.MISSING_POLICY_TARGET, policy.toString());
+
+ if (policy.getConditions().isEmpty())
+ throw new dbException(user.getId(), "policy_store",
+ StatusCodes.MISSING_POLICY_CONDITIONS);
+
+ if (policy.getPermissionByte() == 0)
+ throw new dbException(user.getId(), "policy_store",
+ StatusCodes.MISSING_POLICY_PERMISSION);
+
+ KeyHolder keyHolder = new GeneratedKeyHolder();
+ MapSqlParameterSource np = new MapSqlParameterSource();
+ np.addValue("target", policy.getTarget());
+ if (policy.getContext().getEnd() != 0L)
+ np.addValue("exp", new Timestamp(policy.getContext().getEnd()));
+ else
+ np.addValue("exp", null);
+ np.addValue("en", new Timestamp(policy.getContext().getStart()));
+ np.addValue("posix", policy.getPermissionByte());
+ np.addValue("cr", new Timestamp(TimeUtils.getNow().getMillis()));
+ np.addValue("creator", user.getId());
+ np.addValue("ip", policy.getContext().getIpmask());
+
+ try {
+ mapConditionsToUsers(policy, user);
+ this.jdbcTemplate.update(sql, np, keyHolder, new String[] { "id" });
+ policy.setID(keyHolder.getKey().intValue());
+ this.mapConstraints(policy);
+ return policy.getID();
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (INSERT) not possible for '{}' for user '{}'",
+ policy.toString(), user.getId());
+ throw new dbException(user.getId(), "policy_store",
+ StatusCodes.DB_INSERT_FAILED, policy.toString());
+ }
+ }
+
+ /**
+ * should also include a remove operation, so removed policy constraints
+ *
+ * @param policy
+ * @return
+ * @throws KustvaktException
+ */
+ // benchmark this!
+ @Override
+ public void mapConstraints(SecurityPolicy policy) throws KustvaktException {
+ final String cond = "INSERT INTO group_ref (group_id, policy_id) VALUES (:group, :policyID);";
+ final String remove = "DELETE FROM group_ref WHERE group_id=:group and policy_id=:policyID;";
+ try {
+ List<PolicyCondition> conditions = policy.getConditions();
+ int idx = 0;
+ if (!policy.getRemoved().isEmpty()) {
+ MapSqlParameterSource[] sources_removed = new MapSqlParameterSource[policy
+ .getRemoved().size()];
+ for (Integer toremove : policy.getRemoved()) {
+ MapSqlParameterSource source = new MapSqlParameterSource();
+ source.addValue("group",
+ conditions.get(toremove).getSpecifier());
+ source.addValue("policyID", policy.getID());
+ sources_removed[idx++] = source;
+ }
+ this.jdbcTemplate.batchUpdate(remove, sources_removed);
+ }
+
+ if (!policy.getAdded().isEmpty()) {
+ idx = 0;
+ MapSqlParameterSource[] sources = new MapSqlParameterSource[policy
+ .getAdded().size()];
+ for (Integer add : policy.getAdded()) {
+ MapSqlParameterSource source = new MapSqlParameterSource();
+ source.addValue("group",
+ conditions.get(add).getSpecifier());
+ source.addValue("policyID", policy.getID());
+ sources[idx++] = source;
+ }
+ this.jdbcTemplate.batchUpdate(cond, sources);
+ }
+ policy.clear();
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (MAPPING POLICY CONDITIONS) not possible for '{}' for user '{}'",
+ policy.toString(), policy.getCreator());
+ // throwing an error here is not recommended
+ // throw new dbException(policy.getCreator(), "policy_store",
+ // StatusCodes.DB_INSERT_FAILED, policy.toString());
+ }
+ }
+
+ // todo: check transactional behaviour! --> rollback
+ private void mapConditionsToUsers(SecurityPolicy policy, User user)
+ throws KustvaktException {
+ for (PolicyCondition cond : policy.getConditions()) {
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("name", cond.getSpecifier());
+ param.addValue("userid", user.getId());
+
+ try {
+ final Integer[] results = new Integer[2];
+ jdbcTemplate
+ .query("SELECT COUNT(*) as total, (select count(*) from group_users where user_id=:userid and "
+ + "group_id=:name) as users FROM group_store WHERE name=:name",
+ param, new RowCallbackHandler() {
+ @Override
+ public void processRow(ResultSet rs)
+ throws SQLException {
+ results[0] = rs.getInt("total");
+ results[1] = rs.getInt("users");
+ }
+ });
+
+ boolean admin = false;
+ if (results[0] == 0) {
+ admin = true;
+ this.createCondition(cond, user);
+ }
+ if (results[1] == 0)
+ this.addToCondition(Arrays.asList(user.getUsername()), cond,
+ admin);
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (SELECT) not possible for '{}' for user '{}'",
+ policy.getTarget(), user.getId());
+ throw new dbException(user.getId(), "policy_store",
+ StatusCodes.DB_GET_FAILED, policy.toString());
+ }
+ }
+ }
+
+ // fixme: does not compare permissions. parent can still disregard policy because of missing permisssions
+ @Override
+ public List<SecurityPolicy>[] getPolicies(Integer target, final User user,
+ Byte perm) {
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("target", target);
+ param.addValue("userid", user.getId());
+ param.addValue("perm", perm);
+ param.addValue("en", new Timestamp(TimeUtils.getNow().getMillis()));
+
+ String sql_new =
+ "select pv.*, pv.perm & :perm as allowed, rh.depth, (select max(depth) from resource_tree \n"
+ +
+ "where child_id=rh.child_id) as max_depth from policy_view as pv "
+ +
+ "inner join resource_tree as rh on rh.parent_id=pv.id "
+ +
+ "where rh.child_id=:target and pv.enable <= :en and (pv.expire > :en or pv.expire is NULL) and "
+ +
+ "(pv.group_id='self' or pv.group_id in (select g.group_id from group_users as g where g.user_id=:userid)) and "
+ +
+ "(select sum(distinct depth) from resource_tree where child_id=rh.child_id) = "
+ +
+ "(select sum(distinct res.depth) from p_view as pos inner join resource_tree as res on res.parent_id=pos.id where (pos.group_id in (select g.group_id from group_users as g "
+ +
+ "where g.user_id=:userid) or pos.group_id='self') and res.child_id=rh.child_id group by child_id);";
+
+ try {
+ return this.jdbcTemplate.query(sql_new, param,
+ new ResultSetExtractor<List<SecurityPolicy>[]>() {
+
+ @Override
+ public List<SecurityPolicy>[] extractData(ResultSet rs)
+ throws SQLException, DataAccessException {
+ return SecurityRowMappers.mapping(rs, user);
+ }
+ });
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Permission Denied for retrieval for '{}' for user '{}'",
+ target, user.getId());
+ return new List[2];
+ }
+ }
+
+ @Override
+ public List<SecurityPolicy>[] getPolicies(String target, final User user,
+ Byte perm) {
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("target", target);
+ param.addValue("userid", user.getId());
+ param.addValue("perm", perm);
+ param.addValue("en", new Timestamp(TimeUtils.getNow().getMillis()));
+
+ // fixme: missing constraint of user group membership!
+ String sql_new = "select pv.*, pv.perm & :perm as allowed, " +
+ "rh.depth, (select max(depth) from resource_tree " +
+ "where child_id=rh.child_id) as max_depth from p_view as pv " +
+ "inner join resource_tree as rh on rh.parent_id=pv.id " +
+ "where rh.child_id=(select id from resource_store where persistentID=:target) and "
+ +
+ "pv.enable <= :en and (pv.expire > :en or pv.expire is NULL) and "
+ +
+ "(pv.group_id='self' or pv.group_id in (select g.group_id from group_users as g where g.userid=:userid)) and "
+ +
+ "(select sum(distinct depth) from resource_tree where child_id=rh.child_id) = "
+ +
+ "(select sum(distinct res.depth) from p_view as pos inner join resource_tree as res on res.parent_id=pos.id where (pos.group_id in (select g.group_id from group_users "
+ +
+ "as g where g.userid=:userid) or pos.group_id='self') and res.child_id=rh.child_id group by child_id)";
+
+ try {
+ return this.jdbcTemplate.query(sql_new, param,
+ new ResultSetExtractor<List<SecurityPolicy>[]>() {
+
+ @Override
+ public List<SecurityPolicy>[] extractData(ResultSet rs)
+ throws SQLException, DataAccessException {
+ return SecurityRowMappers.mapping(rs, user);
+ }
+ });
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Permission Denied for retrieval for '{}' for user '{}'",
+ target, user.getId());
+ return new List[2];
+ }
+ }
+
+ @Override
+ public List<SecurityPolicy>[] findPolicies(String path, final User user,
+ Byte perm) {
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("path", StringUtils.buildSQLRegex(path));
+ param.addValue("userid", user.getId());
+ param.addValue("perm", perm);
+ param.addValue("en", new Timestamp(TimeUtils.getNow().getMillis()));
+
+ String sql_new = "select pv.*, pv.perm & :perm as allowed, " +
+ "rh.depth, (select max(depth) from resource_tree " +
+ "where child_id=rh.child_id) as max_depth from p_view as pv " +
+ "inner join resource_tree as rh on rh.parent_id=pv.id " +
+ "where rt.name_path regexp :path and " +
+ "pv.enable <= :en and (pv.expire > :en or pv.expire is NULL) and "
+ +
+ "(pv.group_id='self' or pv.group_id in (select g.group_id from group_users as g where g.userid=:userid)) and "
+ +
+ "(select sum(distinct depth) from resource_tree where child_id=rh.child_id) = "
+ +
+ "(select sum(distinct res.depth) from p_view as pos inner join resource_tree as res on res.parent_id=pos.id where (pos.group_id in (select g.group_id from group_users "
+ +
+ "as g where g.userid=:userid) or pos.group_id='self') and res.child_id=rh.child_id group by child_id)";
+
+ try {
+ return this.jdbcTemplate.query(sql_new, param,
+ new ResultSetExtractor<List<SecurityPolicy>[]>() {
+
+ @Override
+ public List<SecurityPolicy>[] extractData(ResultSet rs)
+ throws SQLException, DataAccessException {
+ return SecurityRowMappers.mapping(rs, user);
+ }
+ });
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Permission Denied for retrieval for '{}' for user '{}'",
+ path, user.getId());
+ return new List[2];
+ }
+ }
+
+ /**
+ * @param path if set searches in path where the child element equals name. Also applicable for root resources!
+ * @param user
+ * @param clazz
+ * @return
+ */
+ //todo: not working yet!
+ // todo: does not concern itsself with location matching, ever!
+ @Override
+ public List<KorAPResource.Container> getDescending(String path,
+ final User user, Byte b, final Class<? extends KorAPResource> clazz)
+ throws KustvaktException {
+ final MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("userid", user.getId());
+ param.addValue("type", ResourceFactory.getResourceMapping(clazz));
+ param.addValue("part", "%" + path);
+ param.addValue("perm", b);
+
+ String sql;
+ if (path != null && !path.isEmpty()) {
+ sql = "select pv.*, pv.perm & :perm as allowed, rh.depth, rh.name_path, (select max(depth) from resource_tree \n"
+ +
+ "where child_id=rh.child_id) as max_depth from p_view as pv "
+ +
+ "inner join resource_tree as rh on rh.child_id=pv.id " +
+ "where pv.type=:type and (rh.name_path like :part) and ((pv.creator=:userid and pv.group_id='self') or "
+ +
+ "(pv.group_id in (select g.group_id from group_users as g where g.userid=:userid) and "
+ +
+ "(select sum(distinct depth) from resource_tree where child_id=rh.child_id) = "
+ +
+ "(select sum(distinct depth) from p_view as pos inner join resource_tree as res on res.parent_id=pos.id "
+ +
+ "where pos.group_id in (select g.group_id from group_users as g where g.userid=:userid) "
+ +
+ "and res.child_id=rh.child_id group by child_id))) " +
+ "group by pv.pid, pv.id having count(distinct pv.group_id) = "
+ +
+ "((select count(co.group_id) from group_ref as co where co.policyid=pv.pid) or "
+ +
+ "(select 1 from p_view as cp2 where cp2.group_id='self' and cp2.id=pv.id)) "
+ +
+ "order by rh.depth asc, pv.id desc;";
+ }else {
+ sql = "select pv.*, pv.perm & :perm as allowed, rh.depth, rh.name_path, (select max(depth) from resource_tree \n"
+ +
+ "where child_id=rh.child_id) as max_depth from p_view as pv "
+ +
+ "inner join resource_tree as rh on rh.child_id=pv.id " +
+ "where pv.type=:type and ((pv.creator=:userid and pv.group_id='self') or "
+ +
+ "(pv.group_id in (select g.group_id from group_users as g where g.userid=:userid) and "
+ +
+ "(select sum(distinct depth) from resource_tree where child_id=rh.child_id) = "
+ +
+ "(select sum(distinct depth) from p_view as pos inner join resource_tree as res on res.parent_id=pos.id "
+ +
+ "where pos.group_id in (select g.group_id from group_users as g where g.userid=:userid) "
+ +
+ "and res.child_id=rh.child_id group by child_id))) " +
+ "group by pv.pid, pv.id having count(distinct pv.group_id) = "
+ +
+ "((select count(co.group_id) from group_ref as co where co.policyid=pv.pid) or "
+ +
+ "(select 1 from p_view as cp2 where cp2.group_id='self' and cp2.id=pv.id)) "
+ +
+ "order by rh.depth asc, pv.id desc;";
+ }
+ try {
+ return this.jdbcTemplate.query(sql, param,
+ new SecurityRowMappers.HierarchicalResultExtractor());
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Permission Denied for retrieval for path '{}' for user '{}'",
+ path, user.getId());
+ throw new dbException(user.getId(), "policy_store",
+ StatusCodes.DB_GET_FAILED, path, clazz.toString());
+ }
+ }
+
+ @Override
+ public List<KorAPResource.Container> getAscending(String name, User user,
+ Byte b, Class<? extends KorAPResource> clazz)
+ throws KustvaktException {
+ final MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("userid", user.getId());
+ param.addValue("type", ResourceFactory.getResourceMapping(clazz));
+ param.addValue("part", "%" + name);
+ param.addValue("perm", b);
+
+ String sql;
+ if (name != null && !name.isEmpty()) {
+ sql = "select pv.*, pv.perm & :perm as allowed, rh.depth, rh.name_path,\n"
+ +
+ "(select max(depth) from resource_tree \n" +
+ "where child_id=rh.child_id) as max_depth from p_view as pv\n"
+ +
+ "inner join resource_tree as rh on rh.child_id=pv.id\n" +
+ "where pv.id in (select rt.parent_id from resource_tree as rt inner join resource_store rs on rs.id=rt.child_id\n"
+ +
+ "where rs.type=:type and rt.name_path like :part) and ((pv.creator=:userid and pv.group_id='self') or\n"
+ +
+ "(pv.group_id in (select g.group_id from group_users as g where g.userid=:userid) and\n"
+ +
+ "(select sum(distinct depth) from resource_tree where child_id=rh.child_id) =\n"
+ +
+ "(select sum(distinct depth) from p_view as pos inner join resource_tree as res on res.parent_id=pos.id\n"
+ +
+ "where pos.group_id in (select g.group_id from group_users as g where g.userid=:userid)\n"
+ +
+ "and res.child_id=rh.child_id group by child_id)))\n" +
+ "group by pv.pid, pv.id having count(distinct pv.group_id) = \n"
+ +
+ "case when pv.creator=:userid then 1 else (select count(distinct co.group_id) "
+ +
+ "from group_ref as co where co.policyid=pv.pid) end order by rh.depth desc, pv.id desc;";
+ }else {
+ sql = "select pv.*, pv.perm & :perm as allowed, rh.depth, rh.name_path,\n"
+ +
+ "(select max(depth) from resource_tree \n" +
+ "where child_id=rh.child_id) as max_depth from p_view as pv\n"
+ +
+ "inner join resource_tree as rh on rh.child_id=pv.id\n" +
+ "where pv.id in (select rt.parent_id from resource_tree as rt inner join resource_store rs on rs.id=rt.child_id\n"
+ +
+ "where rs.type=:type) and ((pv.creator=:userid and pv.group_id='self') or\n"
+ +
+ "(pv.group_id in (select g.group_id from group_users as g where g.userid=:userid) and\n"
+ +
+ "(select sum(distinct depth) from resource_tree where child_id=rh.child_id) =\n"
+ +
+ "(select sum(distinct depth) from p_view as pos inner join resource_tree as res on res.parent_id=pos.target_id\n"
+ +
+ "where pos.group_id in (select g.group_id from group_users as g where g.userid=:userid)\n"
+ +
+ "and res.child_id=rh.child_id group by child_id)))\n" +
+ "group by pv.pid, pv.id having count(distinct pv.group_id) = \n"
+ +
+ "case when pv.creator=:userid then 1 else (select count(distinct co.group_id) "
+ +
+ "from group_ref as co where co.policyid=pv.pid) end order by rh.depth desc, pv.id desc;";
+ }
+ try {
+ return this.jdbcTemplate.query(sql, param,
+ new SecurityRowMappers.HierarchicalResultExtractor());
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Permission Denied for retrieval for path '{}' for user '{}'",
+ name, user.getId());
+ throw new dbException(user.getId(), "policy_store",
+ StatusCodes.DB_GET_FAILED, name, clazz.toString());
+ }
+ }
+
+ // todo: return all resources or only leave nodes? --> currently only leaves are returned
+ // todo: access to leave node also means that the path to the root for that permission is allowed,
+ // todo: thus all upper resource access is as well allowed
+
+ //todo: remove not used context?! --> who is allowed to do so?
+ @Override
+ public int deletePolicy(SecurityPolicy policy, User user)
+ throws KustvaktException {
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("id", policy.getID());
+
+ try {
+ this.jdbcTemplate
+ .update("DELETE FROM group_ref WHERE policyid=:id", param);
+ return this.jdbcTemplate
+ .update("DELETE FROM policy_store WHERE id=:id", param);
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (DELETE) not possible for '{}' for user '{}'",
+ policy.toString(), user.getId());
+ throw new dbException(user.getId(), "policy_store, group_ref",
+ StatusCodes.DB_DELETE_FAILED, policy.toString());
+ }
+ }
+
+ @Override
+ public void deleteResourcePolicies(String id, User user)
+ throws KustvaktException {
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("id", id);
+ String sql = "DELETE FROM policy_store WHERE target_id=:id;";
+ try {
+ this.jdbcTemplate.update(sql, param);
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (DELETE) not possible for '{}' for user '{}'",
+ id, user.getId());
+ throw new dbException(user.getId(), "policy_store",
+ StatusCodes.DB_DELETE_FAILED, id);
+ }
+ }
+
+ @Override
+ public int updatePolicy(SecurityPolicy policy, User user)
+ throws KustvaktException {
+ MapSqlParameterSource np = new MapSqlParameterSource();
+ np.addValue("posix", policy.getPermissionByte());
+ np.addValue("en", policy.getContext().getStart());
+ np.addValue("ex", policy.getContext().getEnd());
+ np.addValue("id", policy.getID());
+
+ try {
+ int result = this.jdbcTemplate
+ .update("UPDATE policy_store SET posix=:posix WHERE id=:id",
+ np);
+ this.mapConstraints(policy);
+ return result;
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (UPDATE) not possible for '{}' for user '{}'",
+ policy.toString(), user.getId());
+ throw new dbException(user.getId(), "policy_store",
+ StatusCodes.DB_UPDATE_FAILED, policy.toString());
+ }
+ }
+
+ @Override
+ public int checkPolicy(SecurityPolicy policy, User user)
+ throws KustvaktException {
+ if (policy.getID() == -1)
+ return 0;
+
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("id", policy.getID());
+ String sql1 = "SELECT COUNT(*) FROM policy_store AS p WHERE p.id=:id;";
+
+ try {
+ return this.jdbcTemplate.queryForObject(sql1, param, Integer.class);
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (SELECT) not possible for '{}' for user '{}'",
+ policy.getTarget(), user.getId());
+ throw new dbException(user.getId(), "policy_store",
+ StatusCodes.DB_GET_FAILED, policy.toString());
+ }
+ }
+
+ /**
+ * checks if the user is a member of the specified group. Additional ownership can be tested via boolean flag
+ *
+ * @param user
+ * @param group
+ * @param owner
+ * @return
+ * @throws KustvaktException
+ */
+ @Override
+ public int matchCondition(User user, String group, boolean owner)
+ throws KustvaktException {
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("userid", user.getId());
+ param.addValue("group", group);
+ param.addValue("isadmin", BooleanUtils.getBoolean(owner));
+ String sql;
+ if (owner) {
+ sql = "SELECT COUNT(*) FROM group_users AS gu INNER JOIN groupolicy_store AS gs "
+ +
+ "ON gs.name=gu.group_id WHERE gu.userID=:userid " +
+ "AND gs.name=:group AND gu.admin=:isadmin;";
+ }else {
+ sql = "SELECT COUNT(*) FROM group_users AS gu INNER JOIN groupolicy_store AS gs "
+ +
+ "ON gs.name=gu.group_id WHERE gu.userID=:userid " +
+ "AND gs.name=:group;";
+ }
+
+ try {
+ return this.jdbcTemplate.queryForObject(sql, param, Integer.class);
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (SELECT) not possible for '{}' for user '{}'",
+ group, user.getId());
+ throw new dbException(user.getId(), "policy_store",
+ StatusCodes.DB_GET_FAILED, group);
+ }
+ }
+
+ private Integer createCondition(PolicyCondition condition, User user)
+ throws KustvaktException {
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ KeyHolder key = new GeneratedKeyHolder();
+ param.addValue("name", condition.getSpecifier());
+ param.addValue("ex", condition.getFlags().get(Attributes.EXPORT));
+ param.addValue("qo", condition.getFlags().get(Attributes.QUERY_ONLY));
+ param.addValue("com", condition.getFlags().get(Attributes.COMMERCIAL));
+ param.addValue("sy", condition.getFlags().get(Attributes.SYM_USE));
+ param.addValue("ex", condition.getFlags().get(Attributes.LICENCE));
+ try {
+ this.jdbcTemplate
+ .update("INSERT INTO group_store (name, sym_use, export, commercial) "
+ + "VALUES (:name, :sy, :ex, :com);", param, key);
+ return key.getKey().intValue();
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (INSERT) not possible for '{}'",
+ condition.toString());
+ throw new dbException(user.getId(), "group_store",
+ StatusCodes.DB_INSERT_FAILED, condition.toString());
+ }
+ }
+
+ @Override
+ public int addToCondition(String username, PolicyCondition condition,
+ boolean admin) throws KustvaktException {
+ final String insert =
+ "INSERT INTO group_users (userID, group_id, admin) " +
+ "VALUES ((SELECT id FROM korap_users " +
+ "WHERE username=:username), :group, :status);";
+ try {
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("group", condition.getSpecifier());
+ param.addValue("username", username);
+ param.addValue("status", BooleanUtils.getBoolean(admin));
+ return this.jdbcTemplate.update(insert, param);
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (INSERT) not possible for '{}' for user '{}'",
+ condition.toString(), username);
+ throw new dbException(null, "group_store",
+ StatusCodes.DB_INSERT_FAILED, condition.toString());
+ }
+ }
+
+ /**
+ * @param usernames
+ * @param condition
+ * @param admin
+ * @return
+ * @throws KustvaktException userID and group_id have a unique constraint,
+ * thus: if any of the supplied users is already a member of the group, the entire chain will be broken!
+ */
+ //todo definitely needs rework
+ //todo: test the unique index constraints!
+ @Override
+ public int[] addToCondition(List<String> usernames,
+ PolicyCondition condition, boolean admin) throws KustvaktException {
+ MapSqlParameterSource[] sources = new MapSqlParameterSource[usernames
+ .size()];
+
+ // todo: use unique index for that! problematic though --> why?
+ // final String select = "select count(id) from group_users where userID=" +
+ // "(select id from korap_users where username=:username) " +
+ // "AND group_id=:group;";
+
+ //todo: use index to create uniqueness. how to batch?
+ final String insert =
+ "INSERT INTO group_users (user_id, group_id, admin) " +
+ "VALUES ((SELECT id FROM korap_users " +
+ "WHERE username=:username), :group, :status);";
+ try {
+ for (int idx = 0; idx < usernames.size(); idx++) {
+ if (usernames.get(idx) == null || usernames.get(idx).isEmpty())
+ throw new KustvaktException(StatusCodes.ILLEGAL_ARGUMENT);
+
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("group", condition.getSpecifier());
+ param.addValue("username", usernames.get(idx));
+ param.addValue("status", BooleanUtils.getBoolean(admin));
+ // if primary keys uniqueness is determined by both keys, then use
+ // that as checkup (may also be manageable via triggers)
+ // if (this.jdbcTemplate
+ // .queryForObject(select, param, Integer.class) == 0)
+ sources[idx] = param;
+ }
+
+ // todo: only insert if user is not already a member of this group
+ //fixme: problem - unique constraints throws exception. skip that user entry?!
+ return this.jdbcTemplate.batchUpdate(insert, sources);
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (INSERT) not possible for '{}' for user '{}'",
+ condition.toString(), usernames);
+ throw new KustvaktException(e, StatusCodes.CONNECTION_ERROR);
+ }
+ }
+
+ @Override
+ public void removeFromCondition(List<String> usernames,
+ PolicyCondition condition) throws KustvaktException {
+ MapSqlParameterSource[] sources = new MapSqlParameterSource[usernames
+ .size()];
+ int idx = 0;
+ for (String s : usernames) {
+ MapSqlParameterSource param = new MapSqlParameterSource();
+ param.addValue("group", condition.getSpecifier());
+ param.addValue("username", s);
+ sources[idx++] = param;
+ }
+
+ final String del =
+ "DELETE FROM group_users WHERE group_id=:group AND userID=(SELECT id FROM "
+ + "korap_users WHERE username=:username);";
+
+ try {
+ this.jdbcTemplate.batchUpdate(del, sources);
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (DELETE) not possible for '{}' for user '{}'",
+ condition.toString(), usernames);
+ throw new KustvaktException(e, StatusCodes.CONNECTION_ERROR);
+ }
+ }
+
+ @Override
+ public void createParamBinding(Parameter param) throws KustvaktException {
+ MapSqlParameterSource source = new MapSqlParameterSource();
+ source.addValue("key", param.getName());
+ source.addValue("policy", param.getPolicy().getID());
+ source.addValue("value", param.getValue());
+ source.addValue("flag", param.isEquality());
+
+ if (!parameterExists(param.getName()))
+ createParameter(param.getName(), "", param.getOwner());
+ final String insert =
+ "INSERT INTO param_map (paramID, policy_id, value, flag) VALUES ((SELECT id FROM param_store "
+ + "WHERE p_key=:key), (SELECT id FROM policy_store WHERE id=:policy), :value, :flag);";
+ try {
+ this.jdbcTemplate.update(insert, source);
+ }catch (DataAccessException e) {
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (INSERT) not possible for '{}",
+ param.toString());
+ throw new KustvaktException(e, StatusCodes.CONNECTION_ERROR);
+ }
+ }
+
+ @Override
+ public List<String> getUsersFromCondition(PolicyCondition condition)
+ throws KustvaktException {
+ MapSqlParameterSource source = new MapSqlParameterSource();
+ source.addValue("specifier", condition.getSpecifier());
+ final String sql1 =
+ "SELECT username FROM korap_users WHERE id IN (SELECT user_id FROM "
+ + "group_users WHERE group_id=:specifier);";
+ try {
+ return this.jdbcTemplate.queryForList(sql1, source, String.class);
+ }catch (DataAccessException e) {
+ e.printStackTrace();
+ KustvaktLogger.SECURITY_LOGGER
+ .error("Operation (SELECT) not possible for '{}'",
+ condition.toString());
+ throw new KustvaktException(StatusCodes.CONNECTION_ERROR);
+ }
+ }
+
+ private boolean parameterExists(String key) {
+ MapSqlParameterSource source = new MapSqlParameterSource();
+ source.addValue("key", key);
+ final String select = "SELECT COUNT(*) FROM param_store WHERE p_key=:key;";
+ return this.jdbcTemplate.queryForObject(select, source, Integer.class)
+ == 1;
+ }
+
+ private void createParameter(String parameter, String value, Integer owner)
+ throws KustvaktException {
+ MapSqlParameterSource source = new MapSqlParameterSource();
+ source.addValue("name", parameter);
+ source.addValue("value", value);
+ source.addValue("owner", owner);
+ final String sql = "INSERT INTO param_store (p_key, p_value) VALUES (:name, :value);";
+ try {
+ this.jdbcTemplate.update(sql, source);
+ }catch (DataAccessException e) {
+ throw new KustvaktException(e, StatusCodes.CONNECTION_ERROR);
+ }
+ }
+
+ @Override
+ public void removeParamBinding(SecurityPolicy policy)
+ throws KustvaktException {
+ MapSqlParameterSource source = new MapSqlParameterSource();
+ source.addValue("id", policy.getID());
+ final String sql = "DELETE FROM param_map WHERE policy_id=:id";
+ try {
+ this.jdbcTemplate.update(sql, source);
+ }catch (DataAccessException e) {
+ throw new KustvaktException(e, StatusCodes.CONNECTION_ERROR);
+ }
+ }
+
+}
diff --git a/src/main/java/de/ids_mannheim/korap/security/ac/SecurityRowMappers.java b/src/main/java/de/ids_mannheim/korap/security/ac/SecurityRowMappers.java
new file mode 100644
index 0000000..bf2de64
--- /dev/null
+++ b/src/main/java/de/ids_mannheim/korap/security/ac/SecurityRowMappers.java
@@ -0,0 +1,239 @@
+package de.ids_mannheim.korap.security.ac;
+
+import de.ids_mannheim.korap.ext.resource.KorAPResource;
+import de.ids_mannheim.korap.ext.resource.ResourceFactory;
+import de.ids_mannheim.korap.ext.security.types.PolicyCondition;
+import de.ids_mannheim.korap.ext.security.types.PolicyContext;
+import de.ids_mannheim.korap.ext.security.types.SecurityPolicy;
+import de.ids_mannheim.korap.user.User;
+import de.ids_mannheim.korap.utils.PrefixTreeMap;
+import lombok.Data;
+import org.springframework.dao.DataAccessException;
+import org.springframework.jdbc.core.ResultSetExtractor;
+import org.springframework.jdbc.core.RowMapper;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Timestamp;
+import java.util.*;
+
+/**
+ * @author hanl
+ * @date 03/03/2014
+ */
+public class SecurityRowMappers {
+
+ public static class PolicyRowMapper implements RowMapper<SecurityPolicy> {
+
+ @Override
+ public SecurityPolicy mapRow(ResultSet rs, int rowNum)
+ throws SQLException {
+ SecurityPolicy p = new SecurityPolicy();
+ p.setID(rs.getInt("pid")).setTarget(rs.getString("persistent_id"))
+ .setPOSIX(rs.getString("perm"));
+
+ PolicyContext context = new PolicyContext();
+ context.setIPMask(rs.getString("iprange"));
+ Timestamp date = rs.getTimestamp("enable");
+ Timestamp date2 = rs.getTimestamp("expire");
+ if (date != null)
+ context.setEnableTime(date.getTime());
+ if (date2 != null)
+ context.setExpirationTime(date2.getTime());
+ // context.addFlag("export", rs.getBoolean("export"));
+ // context.addFlag("sym_use", rs.getInt("sym_use"));
+ p.setContext(context);
+ return p;
+ }
+ }
+
+ @Data
+ public static class FlagContext extends PolicyContext {
+
+ private Map<String, Object> flags;
+
+ public FlagContext() {
+ this.flags = new HashMap<>();
+ }
+
+ public FlagContext addFlag(String key, Object value) {
+ this.flags.put(key, value);
+ return this;
+ }
+
+ public FlagContext removeFlag(String key) {
+ this.flags.remove(key);
+ return this;
+ }
+
+ public FlagContext clearFlags() {
+ this.flags.clear();
+ return this;
+ }
+ }
+
+ public static List<SecurityPolicy>[] mapping(ResultSet rs, User user)
+ throws SQLException {
+ List<SecurityPolicy>[] policyArray = null;
+ List<Integer>[] idx = null;
+ while (rs.next()) {
+ // user has no permission here --> thus skip
+ if (rs.getInt("allowed") == 0)
+ continue;
+
+ if (policyArray == null) {
+ int v = rs.getInt("max_depth") + 1;
+ policyArray = new List[v];
+ idx = new List[v];
+ }
+
+ int depth = rs.getInt("depth");
+
+ if (policyArray[depth] == null) {
+ policyArray[depth] = new ArrayList<>();
+ idx[depth] = new ArrayList<>();
+ }
+
+ Integer pid = rs.getInt("pid");
+ String grouping = rs.getString("group_ref");
+ Integer index = idx[depth].indexOf(pid);
+
+ SecurityPolicy policy;
+ if (index == -1) {
+ if (pid == -1 && grouping.equalsIgnoreCase("self")) {
+ policy = new SecurityPolicy.OwnerPolicy(
+ rs.getString("persistent_id"), rs.getInt("creator"));
+ policyArray[depth].add(0, policy);
+ idx[depth].add(0, pid);
+ }else {
+ policy = new SecurityRowMappers.PolicyRowMapper()
+ .mapRow(rs, 0);
+ policyArray[depth].add(policy);
+ idx[depth].add(pid);
+
+ //todo:
+ // if (policy.isActive(user)) {
+ // policyArray[depth].add(policy);
+ // idx[depth].add(pid);
+ // }
+ }
+ }else
+ policy = policyArray[depth].get(index);
+
+ PolicyCondition c = new PolicyCondition(rs.getString("group_ref"));
+ if (!policy.contains(c))
+ policy.addCondition(c);
+ }
+ return policyArray;
+ }
+
+ @Deprecated
+ public static List<SecurityPolicy>[] map(ResultSet rs) throws SQLException {
+ Map<Integer, SecurityPolicy>[] policyArray = null;
+ while (rs.next()) {
+ // user has no permission here!
+ if (rs.getInt("allowed") == 0)
+ continue;
+
+ if (policyArray == null)
+ policyArray = new Map[rs.getInt("max_depth") + 1];
+
+ int depth = rs.getInt("depth");
+ Map<Integer, SecurityPolicy> cursor = policyArray[depth];
+ if (cursor == null)
+ cursor = new HashMap<>();
+
+ Integer pid = rs.getInt("pid");
+ SecurityPolicy policy = cursor.get(pid);
+ if (policy == null) {
+ policy = new SecurityRowMappers.PolicyRowMapper().mapRow(rs, 0);
+ cursor.put(pid, policy);
+ }
+ PolicyCondition c = new PolicyCondition(rs.getString("group_ref"));
+
+ if (!policy.contains(c))
+ policy.addCondition(c);
+ }
+
+ List<SecurityPolicy>[] results;
+ if (policyArray == null) {
+ results = new List[1];
+ results[0] = new ArrayList<>();
+ }else {
+ results = new List[policyArray.length];
+ for (int idx = 0; idx < policyArray.length; idx++) {
+ if (policyArray[idx] != null)
+ results[idx] = new ArrayList<>(policyArray[idx].values());
+ else
+ results[idx] = new ArrayList<>();
+ }
+ }
+ return results;
+ }
+
+ public static class HierarchicalResultExtractor
+ implements ResultSetExtractor<List<KorAPResource.Container>> {
+
+ private boolean _withpid;
+
+ // public HierarchicalResultExtractor(boolean wpid) {
+ // this._withpid = wpid;
+ // }
+
+ // todo: in order for this to work, all parent flags need to be matched in sql!
+ public List<KorAPResource.Container> extractData(ResultSet rs)
+ throws SQLException, DataAccessException {
+ // contains the container with the highest available name_path to retrieve partial matches!
+ PrefixTreeMap<KorAPResource.Container[]> containerMap = new PrefixTreeMap<>();
+ Map<Integer, SecurityPolicy> trace = new HashMap<>();
+
+ while (rs.next()) {
+ KorAPResource.Container[] cursor;
+ Integer pid = rs.getInt("pid");
+
+ SecurityPolicy policy = trace.get(pid);
+ if (policy == null | pid == -1) {
+ // Integer id = rs.getInt("id");
+ String persistentId = rs.getString("persistent_id");
+ int depth = rs.getInt("depth");
+ String namePath = rs.getString("name_path");
+ policy = new SecurityRowMappers.PolicyRowMapper()
+ .mapRow(rs, 0);
+
+ //todo: put active status here!
+ trace.put(pid, policy);
+
+ //fixme: since leaves are mentioned first, maybe retrieve
+ SortedMap<String, KorAPResource.Container[]> submatch;
+ if ((submatch = containerMap.getPrefixSubMap(namePath))
+ == null) {
+
+ cursor = new KorAPResource.Container[depth + 1];
+ cursor[depth] = new KorAPResource.Container(
+ persistentId,
+ ResourceFactory.getResource(rs.getInt("type"))
+ .getClass());
+ containerMap.put(namePath, cursor);
+ }else {
+ KorAPResource.Container[] values = submatch
+ .get(submatch.firstKey());
+ values[depth] = new KorAPResource.Container(
+ persistentId,
+ ResourceFactory.getResource(rs.getInt("type"))
+ .getClass());
+ }
+ }
+ }
+
+ List<KorAPResource.Container> result = new ArrayList<>();
+ for (KorAPResource.Container[] values : containerMap.values()) {
+ for (KorAPResource.Container container : values)
+ if (container == null)
+ containerMap.remove(values);
+ result.add(values[values.length - 1]);
+ }
+ return result;
+ }
+ }
+
+}
diff --git a/src/main/resources/log4j.properties b/src/main/resources/log4j.properties
new file mode 100644
index 0000000..602c496
--- /dev/null
+++ b/src/main/resources/log4j.properties
@@ -0,0 +1,22 @@
+
+# Root logger option
+#log4j.threshold=ALL
+log4j.rootLogger=INFO, stdout, debugLog
+log4j.logger.log=ERROR, errorLog
+
+# Direct log messages to stdout
+log4j.appender.stdout=org.apache.log4j.ConsoleAppender
+log4j.appender.stdout.Target=System.out
+log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
+log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd, HH:mm:ss} %C{6} - %M%n %-5p: %m%n
+
+log4j.appender.errorLog=org.apache.log4j.RollingFileAppender
+log4j.appender.errorLog.layout=org.apache.log4j.PatternLayout
+log4j.appender.errorLog.layout.ConversionPattern=%d{MMM dd, yyyy HH:mm:ss} %C{6} - %M %n %-5p: %m%n
+log4j.appender.errorLog.File=./logs/errors.log
+
+
+log4j.appender.debugLog=org.apache.log4j.RollingFileAppender
+log4j.appender.debugLog.layout=org.apache.log4j.PatternLayout
+log4j.appender.debugLog.layout.ConversionPattern=%d{MMM dd, yyyy HH:mm:ss} %C{6} - %M %n %-5p: %m%n
+log4j.appender.debugLog.File=./logs/logging.log
\ No newline at end of file
diff --git a/src/test/java/ClassLoaderTest.java b/src/test/java/de/ids_mannheim/korap/config/ClassLoaderTest.java
similarity index 77%
rename from src/test/java/ClassLoaderTest.java
rename to src/test/java/de/ids_mannheim/korap/config/ClassLoaderTest.java
index 207ef81..3303b37 100644
--- a/src/test/java/ClassLoaderTest.java
+++ b/src/test/java/de/ids_mannheim/korap/config/ClassLoaderTest.java
@@ -1,7 +1,10 @@
+package de.ids_mannheim.korap.config;
+
import de.ids_mannheim.korap.config.BeanConfiguration;
import de.ids_mannheim.korap.config.DefaultHandler;
-import de.ids_mannheim.korap.interfaces.AuditingIface;
+import de.ids_mannheim.korap.interfaces.db.AuditingIface;
import de.ids_mannheim.korap.interfaces.defaults.DefaultAuditing;
+import org.junit.After;
import org.junit.Test;
/**
@@ -10,10 +13,15 @@
*/
public class ClassLoaderTest {
+ @After
+ public void close() {
+ BeanConfiguration.closeApplication();
+ }
+
@Test
public void testBeanConfigurationLoaderThrowsNoException() {
- BeanConfiguration.loadClasspathContext("classpath-config.xml");
- assert BeanConfiguration.getBeans() != null;
+ BeanConfiguration.loadClasspathContext("default-config.xml");
+ assert BeanConfiguration.hasContext();
}
@Test
diff --git a/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java b/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java
new file mode 100644
index 0000000..2c11019
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/config/ConfigTest.java
@@ -0,0 +1,65 @@
+package de.ids_mannheim.korap.config;
+
+import de.ids_mannheim.korap.exceptions.KustvaktException;
+import de.ids_mannheim.korap.utils.ServiceVersion;
+import de.ids_mannheim.korap.utils.TimeUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Test;
+
+/**
+ * @author hanl
+ * @date 02/09/2015
+ */
+public class ConfigTest {
+
+ @After
+ public void close() {
+ BeanConfiguration.closeApplication();
+ }
+
+ @Test
+ public void testServiceVersion() {
+ String v = ServiceVersion.getAPIVersion();
+ Assert.assertNotEquals("wrong version", "UNKNOWN", v);
+ }
+
+ @Test
+ public void testPropertiesOverride() {
+ BeanConfiguration.loadClasspathContext();
+
+ Assert.assertEquals("token layer does not match", "opennlp",
+ BeanConfiguration.getBeans().getConfiguration()
+ .getDefault_token());
+ Assert.assertEquals("token expiration does not match",
+ TimeUtils.convertTimeToSeconds("150D"),
+ BeanConfiguration.getBeans().getConfiguration()
+ .getLongTokenTTL());
+
+ BeanConfiguration.getBeans().getConfiguration().setPropertiesAsStream(
+ ConfigTest.class.getClassLoader()
+ .getResourceAsStream("kustvakt_test.conf"));
+
+ Assert.assertEquals("token layer does not match", "tt",
+ BeanConfiguration.getBeans().getConfiguration()
+ .getDefault_token());
+ Assert.assertEquals("token expiration does not match",
+ TimeUtils.convertTimeToSeconds("230D"),
+ BeanConfiguration.getBeans().getConfiguration()
+ .getLongTokenTTL());
+ }
+
+ @Test(expected = KustvaktException.class)
+ public void testBeanOverrideInjection() throws KustvaktException {
+ BeanConfiguration.loadClasspathContext("default-config.xml");
+
+ BeanConfiguration.getBeans().getConfiguration().setPropertiesAsStream(
+ ConfigTest.class.getClassLoader()
+ .getResourceAsStream("kustvakt_test.conf"));
+
+ String v = "testmail@ids-mannheim.de";
+ BeanConfiguration.getBeans().getEncryption().validateEmail(v);
+ }
+}
+
+
diff --git a/src/test/java/de/ids_mannheim/korap/config/TestHelper.java b/src/test/java/de/ids_mannheim/korap/config/TestHelper.java
new file mode 100644
index 0000000..cbfe2a0
--- /dev/null
+++ b/src/test/java/de/ids_mannheim/korap/config/TestHelper.java
@@ -0,0 +1,10 @@
+package de.ids_mannheim.korap.config;
+
+/**
+ *
+ *
+ * @author hanl
+ * @date 16/10/2015
+ */
+public class TestHelper {
+}
diff --git a/src/test/resources/kustvakt_test.conf b/src/test/resources/kustvakt_test.conf
new file mode 100644
index 0000000..887b854
--- /dev/null
+++ b/src/test/resources/kustvakt_test.conf
@@ -0,0 +1,37 @@
+## index dir
+lucene.indexDir = /Users/hanl/Projects/prep_corpus
+
+kustvakt.default.pos = tt
+kustvakt.default.lemma = tt
+kustvakt.default.token = opennlp
+kustvakt.default.dep = mate
+kustvakt.default.const = mate
+
+
+## options referring to the security module!
+
+## token expiration time in minutes!
+## decpricated, no function uses this anymore
+security.absoluteTimeoutDuration = 45M
+
+security.longTokenTTL=150D
+security.tokenTTL=72H
+security.shortTokenTTL=5S
+
+## specifies the user data field that is used to salt user passwords
+security.passcode.salt=accountCreation
+
+security.idleTimeoutDuration = 25M
+security.multipleLogIn = true
+security.loginAttemptNum = 3
+security.authAttemptTTL = 45M
+
+security.encryption.loadFactor = 8
+security.validation.stringLength = 150
+security.validation.emailLength = 50
+security.encryption.algo=BCRYPT
+security.sharedSecret=nHim5JB-YqkX7sS55jayGBnga8WmqgpkzieGe8UhojE
+security.adminToken=f61d02c04a0f18d60172f7b990955824
+
+## applicable: rewrite, foundry, filter, deny
+security.rewrite.strategies=filter, foundry, rewrite
\ No newline at end of file