From 6713e54d674bd6aa858065fa27eda91987cf0c45 Mon Sep 17 00:00:00 2001
From: yvesn <yvesn>
Date: Wed, 2 Aug 2017 15:38:49 +0000
Subject: [PATCH] SSDM-5398: password reset - storing tokens in file; added
 timestamp to make sure the token in not older then 2 days

SVN: 38598
---
 .../PersistentKeyValueStore.java              |  75 ++++++++++++++++--
 .../lib/persistentkeyvaluestore.jar           | Bin 974 -> 3462 bytes
 .../password-reset-api/password-reset-api.py  |  15 +++-
 .../password-reset-api/plugin.properties      |   1 +
 4 files changed, 81 insertions(+), 10 deletions(-)

diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore-source/PersistentKeyValueStore.java b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore-source/PersistentKeyValueStore.java
index c1aec05d6bb..cbbd3a4b2f7 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore-source/PersistentKeyValueStore.java
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore-source/PersistentKeyValueStore.java
@@ -1,27 +1,88 @@
 package ch.ethz.sis;
 
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.ObjectInputStream;
+import java.io.ObjectOutputStream;
 import java.io.Serializable;
+import java.util.Properties;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
 
+import ch.systemsx.cisd.openbis.dss.generic.server.DataStoreServer;
 
 
 public class PersistentKeyValueStore {
+
+	private static final String KEY_STORE_FILE;
 	private static ConcurrentMap<String, Serializable> keyStore = new ConcurrentHashMap<>();
 	
-	public static void put(String key, Serializable value) {
+	static {
+		Properties properties = DataStoreServer.getConfigParameters().getProperties();
+		String storerootDir = properties.getProperty("storeroot-dir");
+		KEY_STORE_FILE = storerootDir + "/" +  "PersistentKeyValueStore.bin";
+		load();
+	}
+
+	//
+	// Public API
+	//
+	public synchronized static void put(String key, Serializable value) {
 		keyStore.put(key, value);
+		save();
+		print();
 	}
-	
-	public static Serializable get(String key) {
+
+	public synchronized static Serializable get(String key) {
+		print();
 		return keyStore.get(key);
 	}
-	
-	public static void remove(String key) {
+
+	public synchronized static void remove(String key) {
 		keyStore.remove(key);
+		save();
 	}
-	
-	public static boolean containsKey(String key) {
+
+	public synchronized static boolean containsKey(String key) {
+		print();
 		return keyStore.containsKey(key);
 	}
+
+	private synchronized static void print() {
+		for (String key : keyStore.keySet()) {
+			System.out.println(key + " - " + keyStore.get(key));
+		}
+	}
+	
+	//
+	// save / load
+	//
+	private static void save() {
+		try (FileOutputStream fos = new FileOutputStream(KEY_STORE_FILE))
+		{
+			ObjectOutputStream oos = new ObjectOutputStream(fos);
+			oos.writeObject(keyStore);
+			oos.close();
+		} catch (IOException e)
+		{
+			e.printStackTrace();
+		}
+	}
+
+	@SuppressWarnings("unchecked")
+	private static void load() {
+		try (FileInputStream fis = new FileInputStream(KEY_STORE_FILE)) {
+			if (new File(KEY_STORE_FILE).exists()) {
+		        ObjectInputStream ois = new ObjectInputStream(fis);
+		        keyStore = (ConcurrentMap<String, Serializable>) ois.readObject();
+		        ois.close();
+			}
+		} catch (IOException | ClassNotFoundException e)
+		{
+			e.printStackTrace();
+		}
+	}
+
 }
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore.jar b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/lib/persistentkeyvaluestore.jar
index d3f77f6a23766b0947ddd07d80311cdfe1069101..1f2a896f4205e7371109844e1328390ff6557c8f 100644
GIT binary patch
literal 3462
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
JMc8Poe*rY!-$wud

delta 742
zcmZpZKF7`%;LXe;!ob17!5~%n-)kaYEQ=_Ro?OdhS>HS5cD|6K$g%y6K?#w!7U^z_
z%HJF55UFpPrBJ)lQ0-_|)4?0tQ|BJI@-}btn@L;hC-cY2&B{uu`POeVZ)U7!qiHL9
z+P5=5>z-Q|i`D)A`A_(OnB4M-2@zX&v3=jI?&klc$NbLXqtE;HpYD%dCi`L8`zo7W
z&9<G)oAaf%y)5aezy6jv?<dd9{y!0_25U^O6h7d-y&>i}mrcLMv2zK(PuZ+8Oqspt
z@r*hJ)oNjnSG}KC`Q8`yTQ26-l5Mh6-6wj^jNIARw{gx}8NIEbW&ha;cXYN&EGxUi
zpJI1@L!sl!>1pe|pPIawan+E$H*wdxh)9E&OA`;79L{mJsL{8`DSltSc8!-h?{%ZM
z(>(oNM6#Ny7<zC0yY7YI8gsQB@t@||aJ_KSo;~?nbk@@X-s_hm(js>LkSo=B#CG^{
zVx7g&TNO)8<dm9CvTwehVc!-KUv?;<XS>%ezU9wO8J?Z`NOjY8UfI<<oELY0lzD8>
zD|EO<rq}J|N9XPSvs*2=U#Y!+sZoDA%W{|FeG$L>@NLqw_v?R_f3f1xLyu$j<-d8N
zF7w)S?-$tg?^FG=xT!&RmYM5#?)MNndA;YFiLL5{g|%#vU6XXXLNwIucgkP5KR@Z2
zG*^7cqtsv1i~?Tm>GHU7$06f_mVfZmV4WVXO<s%IIW-=2b?GK)3fZgY3$3VkT^M6o
zfB34(y}8CFrPG$5kvs~Fm>r$br&jyjT`QpJy*Z}4!(YQa?6mhI$!o$tTF;(W-p1v;
z?USIMxAH2N3F(3=;!f|>j>YO;h>|(<gl)#EU6bbg66Z0D>exB|z;Z!lZ)X-=B?FtF
z$mn{;0B?4VMDcF7WF`g%A65p20B=Sn5e9IYge1zzdOS`Fpj3&hN|ON@Br;6?$D=F4
R$_5f-0>ZgKTACTe0|4P2K@R``

diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/password-reset-api.py b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/password-reset-api.py
index 486988df762..25067b67836 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/password-reset-api.py
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/password-reset-api.py
@@ -54,7 +54,7 @@ def sendResetPasswordEmail(tr, userId, baseUrl):
     sendResetPasswordEmailInternal(tr, emailAddress, userId, token, baseUrl)
 
 def resetPassword(tr, userId, token):
-    if tokenIsValid(userId, token):
+    if tokenIsValid(tr, userId, token):
         email = getUserEmail(tr, userId)
         resetPasswordInternal(tr, email, userId)
         PersistentKeyValueStore.remove(userId + RESET_TOKEN_KEY_POSTFIX)
@@ -78,8 +78,17 @@ def getJsonForData(data):
     jsonValue = objectMapper.writeValueAsString(data);
     return jsonValue;
 
-def tokenIsValid(userId, token):
-    return PersistentKeyValueStore.get(userId + RESET_TOKEN_KEY_POSTFIX) == token
+def tokenIsValid(tr, userId, token):
+    tokenAndTimestamp = PersistentKeyValueStore.get(userId + RESET_TOKEN_KEY_POSTFIX)
+    if (tokenAndTimestamp != None and tokenAndTimestamp["token"] == token):
+        timestampNow = time.time()
+        deltaInSeconds = timestampNow - float(tokenAndTimestamp["timestamp"])
+        maxDelayInMinutes = float(getProperty(tr, "max-delay-in-minutes"))
+        if deltaInSeconds < maxDelayInMinutes * 60:
+            return True
+        else:
+            PersistentKeyValueStore.remove(userId + RESET_TOKEN_KEY_POSTFIX)
+    return False
 
 def sendResetPasswordEmailInternal(tr, email, userId, token, baseUrl):
     passwordResetLink = getPasswordResetLink(email, userId, token, baseUrl)
diff --git a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/plugin.properties b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/plugin.properties
index 18c916fc9a5..e8a94ee542c 100644
--- a/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/plugin.properties
+++ b/openbis_standard_technologies/dist/core-plugins/eln-lims/1/dss/reporting-plugins/password-reset-api/plugin.properties
@@ -1,6 +1,7 @@
 label = Password Reset API
 class = ch.systemsx.cisd.openbis.dss.generic.server.plugins.jython.JythonIngestionService
 script-path = password-reset-api.py
+max-delay-in-minutes = 2880
 password-reset-request-subject = ELN-LIMS password reset for %s
 password-reset-request-body = Hi,\n\nA request has been made to reset the password of user %s.\n\nClick on this link in order to get a new password:\n%s\n\nSincere regards,\nYour ELN-LIMS Team
 new-password-subject = ELN-LIMS new password for %s
-- 
GitLab