diff --git a/gsrs-controlled-vocabulary-api/pom.xml b/gsrs-controlled-vocabulary-api/pom.xml
index e0047940..d4596e06 100644
--- a/gsrs-controlled-vocabulary-api/pom.xml
+++ b/gsrs-controlled-vocabulary-api/pom.xml
@@ -5,7 +5,7 @@
gsrs-spring-boot
gov.nih.ncats
- 3.1
+ 3.1.1-SNAPSHOT
4.0.0
diff --git a/gsrs-controlled-vocabulary/pom.xml b/gsrs-controlled-vocabulary/pom.xml
index b36f092d..cbc3fe36 100644
--- a/gsrs-controlled-vocabulary/pom.xml
+++ b/gsrs-controlled-vocabulary/pom.xml
@@ -5,7 +5,7 @@
gsrs-spring-boot
gov.nih.ncats
- 3.1
+ 3.1.1-SNAPSHOT
4.0.0
diff --git a/gsrs-core-entities/pom.xml b/gsrs-core-entities/pom.xml
index f6cb65e1..e0027a55 100644
--- a/gsrs-core-entities/pom.xml
+++ b/gsrs-core-entities/pom.xml
@@ -5,7 +5,7 @@
gsrs-spring-boot
gov.nih.ncats
- 3.1
+ 3.1.1-SNAPSHOT
4.0.0
diff --git a/gsrs-core-entities/src/main/java/gsrs/model/UserProfileAuthenticationResult.java b/gsrs-core-entities/src/main/java/gsrs/model/UserProfileAuthenticationResult.java
new file mode 100644
index 00000000..3169edbb
--- /dev/null
+++ b/gsrs-core-entities/src/main/java/gsrs/model/UserProfileAuthenticationResult.java
@@ -0,0 +1,28 @@
+package gsrs.model;
+
+import lombok.AllArgsConstructor;
+import lombok.NoArgsConstructor;
+
+@NoArgsConstructor
+@AllArgsConstructor
+public class UserProfileAuthenticationResult {
+ private boolean matchesRepository;
+
+ public boolean matchesRepository() {
+ return matchesRepository;
+ }
+
+ public void setMatchesRepository(boolean matchesRepository) {
+ this.matchesRepository = matchesRepository;
+ }
+
+ public boolean needsSave() {
+ return needsSave;
+ }
+
+ public void setNeedsSave(boolean needsSave) {
+ this.needsSave = needsSave;
+ }
+
+ private boolean needsSave;
+}
diff --git a/gsrs-core-entities/src/main/java/ix/core/models/UserProfile.java b/gsrs-core-entities/src/main/java/ix/core/models/UserProfile.java
index dd744772..8a8f1237 100644
--- a/gsrs-core-entities/src/main/java/ix/core/models/UserProfile.java
+++ b/gsrs-core-entities/src/main/java/ix/core/models/UserProfile.java
@@ -1,17 +1,20 @@
package ix.core.models;
-import ch.qos.logback.core.rolling.helper.TokenConverter;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.databind.ObjectMapper;
import gov.nih.ncats.common.util.CachedSupplier;
import gov.nih.ncats.common.util.TimeUtil;
+import gsrs.model.UserProfileAuthenticationResult;
import gsrs.security.TokenConfiguration;
import gsrs.springUtils.StaticContextAccessor;
+import gsrs.util.GsrsPasswordHasher;
+import gsrs.util.Hasher;
+import gsrs.util.LegacyTypeSalter;
+import gsrs.util.Salter;
import ix.utils.Util;
import lombok.extern.slf4j.Slf4j;
-import org.springframework.beans.factory.annotation.Autowired;
import javax.persistence.*;
import java.util.*;
@@ -22,7 +25,14 @@
@SequenceGenerator(name = "LONG_SEQ_ID", sequenceName = "ix_core_userprof_seq", allocationSize = 1)
@EntityListeners(UserProfileEntityProcessor.class)
public class UserProfile extends IxModel{
- private static ObjectMapper om = new ObjectMapper();
+ private final static String SALT_PREFIX = "G";
+
+ private static ObjectMapper om = new ObjectMapper();
+
+ //todo: look into autowiring the salter and hasher
+ private static Salter salter = new LegacyTypeSalter(new GsrsPasswordHasher(), SALT_PREFIX);
+
+ private static Hasher hasher = new GsrsPasswordHasher();
private static CachedSupplier GUEST_PROF= CachedSupplier.of(()->{
UserProfile up = new UserProfile(new Principal("GUEST"));
@@ -182,21 +192,38 @@ public boolean acceptToken(String token) {
return false;
}
- public boolean acceptPassword(String password) {
- if (this.hashp == null || this.salt == null)
- return false;
- return this.hashp.equals(Util.encrypt(password, this.salt));
+ public UserProfileAuthenticationResult acceptPassword(String password) {
+ UserProfileAuthenticationResult result = new UserProfileAuthenticationResult(false, false);
+ if (this.hashp == null || this.salt == null) {
+ return result;
+ }
+ boolean pwOk = this.hashp.equals(hasher.hash(password, this.salt));
+ log.trace("pwOk: {}", pwOk);
+ boolean legacyPwOk = false;
+ //when authentication using latest algorithms fails, see if the password works with the legacy methods
+ if( !pwOk) {
+ legacyPwOk= this.hashp.equals(Util.encrypt(password, this.salt));
+ }
+ if( legacyPwOk && !salter.mayBeOneOfMine(this.salt)) {
+ //we have a pw assigned using the older algorithm so we need to resalt and rehash
+ log.trace("going to request rehash of password");
+ setPassword(password);
+ result.setNeedsSave(true);
+ }
+ result.setMatchesRepository(pwOk || legacyPwOk);
+ return result;
}
public void setPassword(String password) {
if (password == null || password.length() <= 0) {
password = UUID.randomUUID().toString();
}
- this.salt = Util.generateSalt();
- this.hashp = Util.encrypt(password, this.salt);
+ this.salt = salter.generateSalt();
+ this.hashp = hasher.hash(password, salt); //Util.encrypt(password, this.salt);
setIsDirty("salt");
setIsDirty("hashp");
}
+
@Indexable(indexed = false)
@JsonIgnore
public String getEncodePassword(){
diff --git a/gsrs-core-entities/src/test/java/gsrs/LegacySalterTests.java b/gsrs-core-entities/src/test/java/gsrs/LegacySalterTests.java
new file mode 100644
index 00000000..7c608969
--- /dev/null
+++ b/gsrs-core-entities/src/test/java/gsrs/LegacySalterTests.java
@@ -0,0 +1,38 @@
+package gsrs;
+
+import gsrs.util.GsrsPasswordHasher;
+import gsrs.util.Hasher;
+import gsrs.util.LegacyTypeSalter;
+import gsrs.util.Salter;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+public class LegacySalterTests {
+
+ private final String SALT_PREFIX = "G";
+
+ private final Hasher hasher = new GsrsPasswordHasher();
+
+ private final Salter salter = new LegacyTypeSalter(hasher, SALT_PREFIX);
+
+ @Test
+ void testGenerateSalt() {
+ String salt1 = salter.generateSalt();
+ Assertions.assertNotNull(salt1);
+ System.out.printf("salt: %s\n", salt1);
+ }
+
+ @Test
+ void testGenerateOwnSalt() {
+ String salt1 = salter.generateSalt();
+ Assertions.assertTrue(salter.mayBeOneOfMine(salt1));
+ }
+
+ @Test
+ void testGenerateNotOwnSalt() {
+ String salt1 = salter.generateSalt();
+ String changedSalt = salt1.replace('G', 'N');
+ Assertions.assertFalse(salter.mayBeOneOfMine(changedSalt));
+ }
+
+}
diff --git a/gsrs-core-test/pom.xml b/gsrs-core-test/pom.xml
index fd0b93c7..39e367fc 100644
--- a/gsrs-core-test/pom.xml
+++ b/gsrs-core-test/pom.xml
@@ -5,7 +5,7 @@
gsrs-spring-boot
gov.nih.ncats
- 3.1
+ 3.1.1-SNAPSHOT
4.0.0
diff --git a/gsrs-core/pom.xml b/gsrs-core/pom.xml
index 1c8ef9ff..f9eea0ee 100644
--- a/gsrs-core/pom.xml
+++ b/gsrs-core/pom.xml
@@ -5,7 +5,7 @@
gsrs-spring-boot
gov.nih.ncats
- 3.1
+ 3.1.1-SNAPSHOT
4.0.0
diff --git a/gsrs-core/src/main/java/gsrs/util/GsrsPasswordHasher.java b/gsrs-core/src/main/java/gsrs/util/GsrsPasswordHasher.java
new file mode 100644
index 00000000..3955e2d1
--- /dev/null
+++ b/gsrs-core/src/main/java/gsrs/util/GsrsPasswordHasher.java
@@ -0,0 +1,62 @@
+package gsrs.util;
+
+import lombok.extern.slf4j.Slf4j;
+
+import javax.crypto.SecretKeyFactory;
+import javax.crypto.spec.PBEKeySpec;
+import java.io.UnsupportedEncodingException;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.spec.InvalidKeySpecException;
+
+@Slf4j
+public class GsrsPasswordHasher implements Hasher {
+
+ String preferredHashAlgorithm = "PBKDF2";
+ static int iterations = 1000;
+ static String characterSet ="utf8";
+
+ private final static String HASHING_ALGORITHM = "PBKDF2WithHmacSHA512";
+
+ @Override
+ public String getHashType() {
+ return this.preferredHashAlgorithm;
+ }
+
+ @Override
+ public String hash(String... values) {
+ if (values == null) {
+ return null;
+ }
+ try {
+ if(preferredHashAlgorithm.equals("PBKDF2")) {
+ return hash(values[0], values.length > 1 ? values[1] : null, iterations);
+ }
+ MessageDigest md = MessageDigest.getInstance(preferredHashAlgorithm);
+ for (String v : values) {
+ md.update(v.getBytes(characterSet));
+ }
+ return toHex(md.digest());
+ } catch (Exception ex) {
+ log.error("Can't generate hash!", ex);
+ throw new RuntimeException(ex);
+ }
+ }
+
+ public static String toHex(byte[] d) {
+ StringBuilder sb = new StringBuilder();
+ for (byte b : d) {
+ sb.append(String.format("%1$02x", b & 0xff));
+ }
+ return sb.toString();
+ }
+
+ public static String hash(String input, String salt, int iterations) throws NoSuchAlgorithmException, InvalidKeySpecException, UnsupportedEncodingException {
+ PBEKeySpec spec = new PBEKeySpec(input.toCharArray(), salt != null ? salt.getBytes(characterSet) :
+ input.getBytes(characterSet), iterations, 64 * 8);
+ SecretKeyFactory skf = SecretKeyFactory.getInstance(HASHING_ALGORITHM);
+
+ byte[] hash = skf.generateSecret(spec).getEncoded();
+ return toHex(hash);
+ }
+}
diff --git a/gsrs-core/src/main/java/gsrs/util/Hasher.java b/gsrs-core/src/main/java/gsrs/util/Hasher.java
new file mode 100644
index 00000000..1b7370ca
--- /dev/null
+++ b/gsrs-core/src/main/java/gsrs/util/Hasher.java
@@ -0,0 +1,8 @@
+package gsrs.util;
+
+public interface Hasher {
+
+ String getHashType();
+
+ String hash(String... values);
+}
diff --git a/gsrs-core/src/main/java/gsrs/util/LegacyTypeSalter.java b/gsrs-core/src/main/java/gsrs/util/LegacyTypeSalter.java
new file mode 100644
index 00000000..89de3fa0
--- /dev/null
+++ b/gsrs-core/src/main/java/gsrs/util/LegacyTypeSalter.java
@@ -0,0 +1,34 @@
+package gsrs.util;
+
+import gov.nih.ncats.common.util.TimeUtil;
+import lombok.Data;
+
+import static java.lang.String.valueOf;
+
+@Data
+public class LegacyTypeSalter implements Salter {
+
+ Hasher hasher;
+
+ String prefix = "";
+
+ public LegacyTypeSalter(Hasher newHasher, String newPrefix) {
+ hasher = newHasher;
+ prefix = newPrefix;
+ }
+ @Override
+ public void setHasher(Hasher hasher) {
+ this.hasher = hasher;
+ }
+
+ @Override
+ public String generateSalt() {
+ String text = "---" + TimeUtil.getCurrentDate().toString() + "---" + String.valueOf(Math.random()) + "---";
+ return prefix + hasher.hash(text);
+ }
+
+ @Override
+ public boolean mayBeOneOfMine(String testHash) {
+ return testHash != null && testHash.startsWith(prefix);
+ }
+}
diff --git a/gsrs-core/src/main/java/gsrs/util/Salter.java b/gsrs-core/src/main/java/gsrs/util/Salter.java
new file mode 100644
index 00000000..eb43a72e
--- /dev/null
+++ b/gsrs-core/src/main/java/gsrs/util/Salter.java
@@ -0,0 +1,10 @@
+package gsrs.util;
+
+public interface Salter {
+
+ void setHasher(Hasher hasher);
+
+ String generateSalt();
+
+ boolean mayBeOneOfMine(String testHash);
+}
diff --git a/gsrs-data-exchange/pom.xml b/gsrs-data-exchange/pom.xml
index 91aeee39..533484ed 100644
--- a/gsrs-data-exchange/pom.xml
+++ b/gsrs-data-exchange/pom.xml
@@ -5,7 +5,7 @@
gsrs-spring-boot
gov.nih.ncats
- 3.1
+ 3.1.1-SNAPSHOT
4.0.0
diff --git a/gsrs-discovery/pom.xml b/gsrs-discovery/pom.xml
index fb57e785..cc41afc7 100644
--- a/gsrs-discovery/pom.xml
+++ b/gsrs-discovery/pom.xml
@@ -10,7 +10,7 @@
gov.nih.ncats
gsrs-discovery
- 3.1
+ 3.1.1-SNAPSHOT
gsrs-discovery
Demo project for Spring Boot
diff --git a/gsrs-rest-api/pom.xml b/gsrs-rest-api/pom.xml
index 7016d341..55f79121 100644
--- a/gsrs-rest-api/pom.xml
+++ b/gsrs-rest-api/pom.xml
@@ -5,7 +5,7 @@
gsrs-spring-boot
gov.nih.ncats
- 3.1
+ 3.1.1-SNAPSHOT
4.0.0
diff --git a/gsrs-scheduled-tasks/pom.xml b/gsrs-scheduled-tasks/pom.xml
index 2d061a54..387a5b15 100644
--- a/gsrs-scheduled-tasks/pom.xml
+++ b/gsrs-scheduled-tasks/pom.xml
@@ -5,7 +5,7 @@
gsrs-spring-boot
gov.nih.ncats
- 3.1
+ 3.1.1-SNAPSHOT
4.0.0
diff --git a/gsrs-spring-akka/pom.xml b/gsrs-spring-akka/pom.xml
index 6c2ec8ba..c7a91e11 100644
--- a/gsrs-spring-akka/pom.xml
+++ b/gsrs-spring-akka/pom.xml
@@ -5,7 +5,7 @@
gsrs-spring-boot
gov.nih.ncats
- 3.1
+ 3.1.1-SNAPSHOT
4.0.0
diff --git a/gsrs-spring-boot-autoconfigure/pom.xml b/gsrs-spring-boot-autoconfigure/pom.xml
index 525ead72..c82f3dff 100644
--- a/gsrs-spring-boot-autoconfigure/pom.xml
+++ b/gsrs-spring-boot-autoconfigure/pom.xml
@@ -5,7 +5,7 @@
gsrs-spring-boot
gov.nih.ncats
- 3.1
+ 3.1.1-SNAPSHOT
4.0.0
diff --git a/gsrs-spring-boot-autoconfigure/src/main/java/gsrs/controller/AbstractGsrsEntityController.java b/gsrs-spring-boot-autoconfigure/src/main/java/gsrs/controller/AbstractGsrsEntityController.java
index 4ee2d7e5..105670c1 100644
--- a/gsrs-spring-boot-autoconfigure/src/main/java/gsrs/controller/AbstractGsrsEntityController.java
+++ b/gsrs-spring-boot-autoconfigure/src/main/java/gsrs/controller/AbstractGsrsEntityController.java
@@ -3,6 +3,8 @@
import java.io.IOException;
import java.io.UnsupportedEncodingException;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
import java.net.URLDecoder;
import java.security.Principal;
import java.util.ArrayList;
@@ -16,8 +18,12 @@
import java.util.regex.Pattern;
import java.util.stream.Collectors;
+import javax.persistence.EntityManager;
+import javax.persistence.Id;
+import javax.persistence.metamodel.Metamodel;
import javax.servlet.http.HttpServletRequest;
+import org.hibernate.metadata.ClassMetadata;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
@@ -76,8 +82,6 @@ public abstract class AbstractGsrsEntityController page(@RequestParam(value = "top", defaultValue = "
@RequestParam(value = "skip", defaultValue = "0") long skip,
@RequestParam(value = "order", required = false) String order,
@RequestParam Map queryParameters){
-
-
+
Page page = getEntityService().page(new OffsetBasedPageRequest(skip, top,parseSortFromOrderParam(order)));
String view=queryParameters.get("view");
@@ -430,10 +433,27 @@ public ResponseEntity