[Notice] Announcing the End of Demo Server [Read me]
doortts doortts 2013-04-13
new feature: Reset password by email
how to test
---------
1. add suitable settings to application.conf
    - your email settings
    - change smtp.mock to false
    - add your hostname & port settings

        # application.hostname="www.projecthive.com"
        # application.port="80"

2. go to login page and select forget password link
     or use direct access url: /lostPassword

3. follow instructions of the page

4. check your email and follow the instruction, again.

Conflicts:
	conf/messages.en
	conf/messages.ko
@5dc3385d60f0173cc0e14ad7de1dd5d820207544
 
app/controllers/PasswordResetApp.java (added)
+++ app/controllers/PasswordResetApp.java
@@ -0,0 +1,104 @@
+package controllers;
+
+import info.schleichardt.play2.mailplugin.Mailer;
+import models.PasswordReset;
+import models.User;
+import org.apache.commons.mail.EmailException;
+import org.apache.commons.mail.SimpleEmail;
+import play.Configuration;
+import play.Logger;
+import play.data.DynamicForm;
+import play.mvc.*;
+import play.i18n.Messages;
+import utils.Constants;
+import views.html.login;
+import views.html.site.lostPassword;
+import views.html.user.resetPassword;
+
+import static play.data.Form.form;
+
+public class PasswordResetApp extends Controller {
+
+    public static final String LOCAL_HOST = "127.0.0.1";
+    public static final String DEV_MODE = "9000";
+
+    public static Result lostPassword(){
+        return ok(lostPassword.render("admin.resetPasswordEmail.title", null, null, false));
+    }
+
+    public static Result requestResetPasswordEmail(){
+        DynamicForm requestData = form().bindFromRequest();
+        String loginId = requestData.get("loginId");
+        String emailAddress = requestData.get("emailAddress");
+
+        Logger.debug("request reset password email by [" + loginId + ":" + emailAddress + "]");
+
+        User targetUser = User.findByLoginId(loginId);
+
+        boolean isMailSent = false;
+        String errorMessage = null;
+        if(!targetUser.isAnonymous() && targetUser.email.equals(emailAddress)) {
+           String hashString = PasswordReset.generateResetHash(targetUser.loginId);
+           PasswordReset.addHashToResetTable(targetUser.loginId, hashString);
+           isMailSent = sendPasswordResetMail(targetUser, hashString);
+        } else {
+            Logger.debug("wrong user: " + loginId);
+            errorMessage = Messages.get("admin.resetPasswordEmail.invalidRequest");
+        }
+        return ok(lostPassword.render("admin.resetPasswordEmail.title", emailAddress, errorMessage, isMailSent));
+    }
+
+    //ToDo SiteApp.sendMail()과 통합할 것
+    //ToDo site email setting check 하는 부분이 빠져 있음
+    private static boolean sendPasswordResetMail(User user, String hashString) {
+        Configuration config = play.Play.application().configuration();
+        String sender = config.getString("smtp.user") + "@" + config.getString("smtp.domain");
+        String resetPasswordUrl = getResetPasswordUrl(hashString);
+
+        try {
+            SimpleEmail email = new SimpleEmail();
+            email.setFrom(sender)
+                 .setSubject(Messages.get("admin.resetPasswordEmail.title"))
+                 .addTo(user.email)
+                 .setMsg(Messages.get("admin.resetPasswordEmail.mailcontents") + "\n\n" + resetPasswordUrl)
+                 .setCharset("utf-8");
+
+            Mailer.send(email);
+            return true;
+        } catch (EmailException e) {
+            e.printStackTrace();
+            return false;
+        }
+    }
+
+    private static String getResetPasswordUrl(String hashString) {
+        Configuration config = play.Play.application().configuration();
+        String hostname = config.getString("application.hostname");
+        String port = config.getString("application.port");
+
+        if(hostname == null) hostname = LOCAL_HOST;
+        if(port == null) port = DEV_MODE;
+
+        return "http://" + hostname + ":" + port + "/resetPassword?s=" + hashString;
+    }
+
+    public static Result resetPasswordForm(String s){
+        String hashString = s;
+        return ok(resetPassword.render("title.resetPassword", form(User.class), hashString));
+    }
+
+    public static Result resetPassword(){
+        DynamicForm requestData = form().bindFromRequest();
+        String hashString = requestData.get("hashString");
+        String newPassword = requestData.get("password");
+
+        if(PasswordReset.isValidResetHash(hashString)){
+            PasswordReset.resetPassword(hashString, newPassword);
+            Logger.debug("Password was reset");
+        } else {
+            Logger.debug("Not a valid request!");
+        }
+        flash(Constants.WARNING, "user.loginWithNewPassword");
+        return ok(login.render("title.login", form(User.class)));
+    }
+}
app/controllers/UserApp.java
--- app/controllers/UserApp.java
+++ app/controllers/UserApp.java
@@ -206,6 +206,12 @@
 		return user;
 	}
 
+    public static void resetPassword(User user, String newPassword) {
+		user.password = new Sha256Hash(newPassword,
+				ByteSource.Util.bytes(user.passwordSalt), 1024).toBase64();
+        user.save();
+	}
+
 	public static void addUserInfoToSession(User user) {
 		session(SESSION_USERID, String.valueOf(user.id));
 		session(SESSION_LOGINID, user.loginId);
app/models/NullUser.java
--- app/models/NullUser.java
+++ app/models/NullUser.java
@@ -19,10 +19,7 @@
 
 import javax.persistence.*;
 import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 
 public class NullUser extends User {
     private static final long serialVersionUID = -1L;
@@ -37,7 +34,7 @@
     public String avatarUrl = UserApp.DEFAULT_AVATAR_URL;
 
     public List<Project> myProjects(){
-        return null;
+        return new ArrayList<Project>();
     }
 
     public boolean isAnonymous() {
 
app/models/PasswordReset.java (added)
+++ app/models/PasswordReset.java
@@ -0,0 +1,99 @@
+package models;
+
+import org.apache.shiro.crypto.SecureRandomNumberGenerator;
+import org.apache.shiro.crypto.hash.Sha1Hash;
+import org.joda.time.DateTime;
+import play.Logger;
+
+import java.util.HashMap;
+import java.util.Map;
+
+public class PasswordReset {
+    private static Map<String, String> resetHashMap = new HashMap<String, String>();
+    private static Map<String, DateTime> resetHashTimetable = new HashMap<String, DateTime>();
+    public static final int HASH_EXPIRE_TIME_SEC = 3600;
+
+    public static String generateResetHash(String userId) {
+        return new Sha1Hash(userId, new SecureRandomNumberGenerator().nextBytes(), 1).toHex();
+    }
+
+    public static void addHashToResetTable(String userId, String hashString) {
+        Logger.debug(">> add to HashTable " + userId + ":" + hashString);
+        PasswordReset.resetHashMap.put(userId, hashString);
+        resetHashTimetable.put(hashString, new DateTime());
+    }
+
+    public static String getResetHash(String userId) {
+        return PasswordReset.resetHashMap.get(userId);
+    }
+
+    public static boolean invalidateResetHash(String userId) {
+        String targetIdToReset = PasswordReset.resetHashMap.get(userId);
+        if (targetIdToReset == null){
+            return false;
+        }
+
+        PasswordReset.resetHashMap.remove(userId);
+        return true;
+    }
+
+    public static boolean isHashExist(String userId) {
+        return PasswordReset.resetHashMap.get(userId) != null;
+    }
+
+    public static void resetHashTable() {
+        resetHashMap.clear();
+    }
+
+    public static boolean isValidResetHash(String hashString) {
+        Logger.debug("Reset hash entry size:" + resetHashMap.size());
+        for(Map.Entry<String, String> entry: resetHashMap.entrySet()){
+            Logger.debug(">> " + entry.getKey());
+        }
+        if( !resetHashMap.containsValue(hashString) ) {
+            Logger.debug("HashString doesn't exists in resetHashMap: " + hashString);
+            return false;
+        }
+
+        if(isExpiredHashString(hashString)) {
+            Logger.debug("HashString was expired: " + hashString);
+            return false;
+        }
+
+        return true;
+    }
+
+    private static boolean isExpiredHashString(String hashString) {
+        return resetHashTimetable.get(hashString).getMillis() < new DateTime().minusSeconds(PasswordReset.HASH_EXPIRE_TIME_SEC).getMillis();
+    }
+
+    public static void removeResetHash(String hashString) {
+        String key = getKeyByValue(resetHashMap, hashString);
+        resetHashMap.remove(key);
+        resetHashTimetable.remove(hashString);
+    }
+
+    static Map<String, DateTime> getResetHashTimetable(){
+        return resetHashTimetable;
+    }
+
+    private static <T, E> T getKeyByValue(Map<T, E> map, E value) {
+        for (Map.Entry<T, E> entry : map.entrySet()) {
+            if (value.equals(entry.getValue())) {
+                return entry.getKey();
+            }
+        }
+        return null;
+    }
+
+    public static boolean resetPassword(String hashString, String newPassword) {
+        if( !isValidResetHash(hashString) ) {
+            return false;
+        }
+
+        String loginId = getKeyByValue(resetHashMap, hashString);
+        User.resetPassword(loginId, newPassword);
+        removeResetHash(hashString);
+        return true;
+    }
+}
app/models/User.java
--- app/models/User.java
+++ app/models/User.java
@@ -21,6 +21,8 @@
 import models.support.FinderTemplate;
 import models.support.OrderParams;
 import models.support.SearchParams;
+import org.apache.shiro.crypto.hash.Sha256Hash;
+import org.apache.shiro.util.ByteSource;
 import play.data.format.Formats;
 import play.data.validation.Constraints.*;
 import play.db.ebean.Model;
@@ -173,6 +175,14 @@
         return this == UserApp.anonymous;
     }
 
+    public static void resetPassword(String loginId, String newPassword) {
+        User user = findByLoginId(loginId);
+        user.password = new Sha256Hash(newPassword,
+                ByteSource.Util.bytes(user.passwordSalt), 1024).toBase64();
+        user.save();
+    }
+
+
     public Resource asResource() {
         return new Resource() {
             @Override
 
app/models/exceptions/InvalidResetHash.java (added)
+++ app/models/exceptions/InvalidResetHash.java
@@ -0,0 +1,16 @@
+package models.exceptions;
+
+/**
+ * User: doortts
+ * Date: 4/5/13
+ * Time: 10:37 AM
+ */
+public class InvalidResetHash extends RuntimeException {
+    public static final String NOT_EXISTS = "Not Existed ResetHash";
+    private static final long serialVersionUID = -1L;
+    public static final String EXIRED = "Reset Password hash is expired";
+
+    public InvalidResetHash(String message) {
+        super(message);
+    }
+}
 
app/views/site/lostPassword.scala.html (added)
+++ app/views/site/lostPassword.scala.html
@@ -0,0 +1,46 @@
+@(message: String, sender: String, errorMessage: String, isSent: Boolean)
+
+@home(message, utils.MenuType.NONE) {
+
+<form method="POST" action="@routes.PasswordResetApp.requestResetPasswordEmail()" id="mailForm" class="span7 form-horizontal">
+  <div class="page-header control-group controls">
+    <h3>@Messages(message)</h3>
+  </div>
+
+@if(isSent) {
+    <div class="control-group controls">
+        @Messages("mail.sended")
+    </div>
+}
+@if(errorMessage) {
+    <div class="control-group controls">
+        @Messages("mail.fail")<br/>
+        @Html(Messages(errorMessage))
+    </div>
+}
+  <div class="control-group">
+    <label name="from" class="control-label">@Messages("user.loginId")</label>
+    <div class="controls">
+        <div class="input-prepend">
+            <span class="add-on"><i class="icon-user"></i></span>
+            <input type="text" name="loginId" required="" placeholder="@Messages("user.yourLoginId")">
+        </div>
+    </div>
+  </div>
+  <div class="control-group">
+    <label name="to" class="control-label">@Messages("user.email")</label>
+    <div class="controls">
+        <div class="input-prepend">
+            <span class="add-on"><i class="icon-envelope"></i></span>
+            <input type="text" name="emailAddress" required="" placeholder="@Messages("user.yourEmail")">
+        </div>
+    </div>
+  </div>
+  <div class="controls">
+    <button type="submit" class="btn-transparent n-btn orange med">
+      <strong>@Messages("button.confirm")</strong>
+    </button>
+  </div>
+</form>
+
+}
 
app/views/user/resetPassword.scala.html (added)
+++ app/views/user/resetPassword.scala.html
@@ -0,0 +1,42 @@
+@(message: String, newUserForm: Form[User], hashString: String)
+@import utils.TemplateHelper._
+
+@home(message, utils.MenuType.NONE) {
+	<div class="page full">
+        <div class="center-wrap tag-line-wrap signup">
+            <h1 class="title"><span class="high-light">Password Reset </span></h1>
+        </div>
+        <div class="signup-form-wrap frm-wrap">
+            <form action="@routes.PasswordResetApp.resetPassword()" method="POST" name="passwordReset">
+            	<dl>
+					<dt>
+						<label for="password">@Messages("user.password")</label>
+					</dt>
+					<dd>
+						<input type="password" class="text password" id="password" placeholder="" autocomplete="off" name="password">
+					</dd>
+					
+					<dt>
+						<label for="retypedPassword">@Messages("validation.retypePassword")</label>
+					</dt>
+					<dd>
+						<input type="password" class="text password" id="retypedPassword" placeholder="" autocomplete="off" name="retypedPassword">
+					</dd>
+            	</dl>
+
+                <input type="hidden" name="hashString" value="@hashString">
+                <div class="btns-row">
+                    <button type="submit" class="n-btn orange sign-up">@Messages("button.confirm")</button>
+                </div>
+            </form>
+        </div>
+    </div>
+    
+    <script type="text/javascript" src="@getJSLink("lib/validate")"></script>
+    <script type="text/javascript">
+            $(document).ready(function(){
+                $hive.loadModule("resetPassword");
+            });
+    </script>
+}
+
conf/application.conf
--- conf/application.conf
+++ conf/application.conf
@@ -76,8 +76,14 @@
 
 # Components used to construct the URL to this application.
 application.scheme="http"
-# application.hostname="www.nforge.com"
-# application.port="8080"
+
+# if you want to use password reset email feature, you should set below with yours
+# application.hostname="www.projecthive.com"
+# application.port="80"
+#
+# below setting is used for default
+# application.hostname="127.0.0.1"
+# application.port="9000"
 
 # Mailer
 # ~~~~~~
@@ -86,7 +92,8 @@
 smtp.host = smtp.gmail.com
 smtp.port = 465
 smtp.ssl = true
-smtp.user = yourGmailId
+#smtp.user = yourGmailId
+smtp.user = hiveproject.mail
 # Be careful!!!
 smtp.password = yourGmailPassword
 smtp.domain = gmail.com
conf/messages.en
--- conf/messages.en
+++ conf/messages.en
@@ -25,6 +25,7 @@
 title.projectList = Project List
 title.help = HELP
 title.search = SEARCH
+title.resetPassword = Reset Password
 
 hive.name = HIVE
 hive.title = Collaborative Software Development Platform
@@ -270,6 +271,7 @@
 
 #User
 user.loginId = Login ID
+user.yourLoginId = Your login id
 user.wrongloginId.alert = Valid ID required
 user.loginId.duplicate = Already existing ID
 user.login.alert = Please, login.
@@ -279,12 +281,14 @@
 user.wrongPassword.alert = Wrong password!
 user.name = Name
 user.email = E-mail
+user.yourEmail = Your email address
 user.wrongEmail.alert = Wrong email address.
 user.login.failed = Your login ID or password is not valid.
 user.logout.success = Logout successfully
 user.isAlreadySignupUser = HAVE AN ACCOUNT ALREADY?
 user.signupId = ID (lower case)
 user.signupBtn = Sign Up
+user.loginWithNewPassword = Please, login with new password!
 
 #Time
 time.just = just now
@@ -346,7 +350,7 @@
 mail.body = Body
 mail.send = Send
 mail.fail = Failed to send a mail.
-mail.sended = Mail is sended.
+mail.sended = Mail is sent.
 
 author = Author
 assignee = Assignee
@@ -370,3 +374,8 @@
 #userinfo
 userinfo.myProjects = My Projects
 userinfo.starredProjects = Starred
+
+#Admin
+admin.resetPasswordEmail.title = [HIVE] Password reset mail request
+admin.resetPasswordEmail.mailcontents = copy the below url and paste it to browser url bar
+admin.resetPasswordEmail.invalidRequest = Invalid Password Reset Request
conf/messages.ko
--- conf/messages.ko
+++ conf/messages.ko
@@ -25,6 +25,7 @@
 title.projectList = 프로젝트 목록
 title.help = 도움말
 title.search = 검색
+title.resetPassword = 패스워드 재설정
 
 hive.name = HIVE
 hive.title = 협업개발 플랫폼
@@ -269,6 +270,7 @@
 
 #User
 user.loginId = 아이디
+user.yourLoginId = yourLoginId
 user.wrongloginId.alert = 올바른 아이디를 입력하세요.
 user.loginId.duplicate = 이미 존재하는 아이디입니다.
 user.login.alert = 로그인이 필요합니다.
@@ -278,12 +280,14 @@
 user.wrongPassword.alert = 잘못된 비밀번호입니다!
 user.name = 이름
 user.email = 이메일
+user.yourEmail = yourEmail@address.com
 user.wrongEmail.alert = 이메일이 잘못되었습니다.
 user.login.failed = 아이디 또는 비밀번호가 잘못되었습니다.
 user.logout.success = 로그아웃했습니다
 user.isAlreadySignupUser = 이미 가입하셨나요?
 user.signupId = 아이디
 user.signupBtn = 참여하기
+user.loginWithNewPassword = 새로 설정한 비밀번호로 로그인 하세요
 
 #Time
 time.just = 방금 전
@@ -369,4 +373,9 @@
 
 #userinfo
 userinfo.myProjects = 내 프로젝트
-userinfo.starredProjects = 관심 프로젝트
+userinfo.starredProjects = 관심 프로젝트
+
+#Admin
+admin.resetPasswordEmail.title = [HIVE] 패스워드 재 설정
+admin.resetPasswordEmail.mailcontents = 아래 URL을 브라우저 주소창에 붙여 넣으세요
+admin.resetPasswordEmail.invalidRequest = 잘못된 패스워드 재 설정 요청입니다.
conf/routes
--- conf/routes
+++ conf/routes
@@ -39,6 +39,10 @@
 GET     /sites/projectList                              controllers.SiteApp.projectList(filter:String ?= "")
 GET     /sites/project/delete/:projectId                controllers.SiteApp.deleteProject(projectId:Long)
 GET     /sites/softwareMap                              controllers.SiteApp.softwareMap()
+GET     /lostPassword                                   controllers.PasswordResetApp.lostPassword
+POST    /lostPassword                                   controllers.PasswordResetApp.requestResetPasswordEmail()
+GET     /resetPassword                                  controllers.PasswordResetApp.resetPasswordForm(s:String)
+POST    /resetPassword                                  controllers.PasswordResetApp.resetPassword()
 
 # Attachments
 GET     /files                                          controllers.AttachmentApp.getFileList()
 
public/javascripts/service/hive.resetPassword.js (added)
+++ public/javascripts/service/hive.resetPassword.js
@@ -0,0 +1,133 @@
+/**
+ * @(#)hive.user.SignUp.js 2013.04.02
+ *
+ * Copyright NHN Corporation.
+ * Released under the MIT license
+ * 
+ * http://hive.dev.naver.com/license
+ */
+
+(function(ns){
+	
+	var oNS = $hive.createNamespace(ns);
+	oNS.container[oNS.name] = function(){
+		
+		var htVar = {};
+		var htElement = {};
+		
+		/**
+		 * initialize
+		 */
+		function _init(){
+			_initElement();
+			_initFormValidator();
+			_attachEvent();
+		}
+		
+		/**
+		 * initialize elements
+		 */
+		function _initElement(){
+			htElement.welInputPassword  = $('#password');
+			htElement.welInputPassword2 = $('#retypedPassword');
+
+			htElement.welForm = $("form[name=passwordReset]");
+		}
+		
+		/**
+		 * attach event
+		 */
+		function _attachEvent(){
+			htElement.welInputPassword.focusout(_onBlurInputPassword);
+            htElement.welInputPassword2.focusout(_onBlurInputPassword);
+		}
+		
+
+		/**
+		 * 비밀번호 확인 입력란 벗어날 때 이벤트 핸들러
+		 * 마지막 입력란이므로 전체 폼 유효성 검사
+		 */
+		function _onBlurInputPassword(){
+			htVar.oValidator._validateForm();
+		}
+
+		/**
+		 * initialize FormValidator
+		 * @require validate.js
+		 */
+		function _initFormValidator(){
+			var aRules = [
+	  			{"name": 'password',		"rules": 'required|min_length[4]'},
+	  			{"name": 'retypedPassword', "rules": 'required|matches[password]'}
+	  		];
+
+			htVar.oValidator = new FormValidator('passwordReset', aRules, _onFormValidate);
+
+            // set error message
+            htVar.oValidator.setMessage('required',		 Messages("validation.required"));
+            htVar.oValidator.setMessage('min_length',	 Messages("validation.tooShortPassword"));
+            htVar.oValidator.setMessage('matches',		 Messages("validation.passwordMismatch"));
+		}
+		
+
+		/**
+		 * on validate form
+		 * @param {Array} aErrors
+		 */
+		function _onFormValidate(aErrors){
+            _clearTooltips();
+			// to avoid bootstrap bug
+			if (aErrors.length <= 0) {
+				return _clearTooltips();
+			}
+
+			var welTarget;
+			aErrors.forEach(function(htError){
+				welTarget = htElement.welForm.find("input[name=" + htError.name + "]");
+				if(welTarget){
+					showErrorMessage(welTarget, htError.message);
+				}
+			});
+		}
+
+		/**
+		 * 폼 영역에 있는 jquery.tooltip 모두 제거하는 함수
+		 */
+		function _clearTooltips(){
+			try {
+				htElement.welForm.find("input").each(function(i, v){
+					$(v).tooltip("destroy");
+				});
+			} catch(e){}
+		}
+		
+		/**
+		 * Bootstrap toolTip function has some limitation.
+		 * In this case, toolTip doesn't provide easy way to change title and contents.
+		 * So, unfortunately I had to change data value in directly.
+		 * @param {Wrapped Element} welInput
+		 * @param {String} sMessage
+		 */
+		function showErrorMessage(welInput, sMessage){
+	        welInput.tooltip({"trigger": "manual", "placement": "left"});
+
+	        var oToolTip = welInput.data('tooltip');
+	        oToolTip.options.placement = 'left';
+	        oToolTip.options.trigger   = 'manual';
+	        oToolTip.options.title     = sMessage;
+	        oToolTip.options.content   = sMessage;
+
+	        welInput.tooltip('show');
+		}
+			
+		function hideErrorMessage(welInput){
+            welInput.tooltip("hide");
+            
+            try{
+                welInput.tooltip("destroy");
+            } catch(e){} // to avoid bootstrap bug			
+		}
+		
+		_init();
+	};
+})("hive.resetPassword");(No newline at end of file)
 
test/controllers/PasswordResetAppTest.java (added)
+++ test/controllers/PasswordResetAppTest.java
@@ -0,0 +1,43 @@
+package controllers;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+import play.mvc.Result;
+import play.test.Helpers;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static play.test.Helpers.*;
+
+public class PasswordResetAppTest {
+    @BeforeClass
+    public static void beforeClass() {
+        callAction(
+                routes.ref.Application.init()
+        );
+    }
+
+    @Test
+    public void testRequestResetPassword_validLoginIdAndEmailAddress() {
+        running(fakeApplication(Helpers.inMemoryDatabase()), new Runnable() {
+            public void run() {
+                //Given
+                Map<String,String> data = new HashMap<String,String>();
+                data.put("loginId", "doortts");
+                data.put("emailAddress", "doortts@gmail.com");
+
+                //When
+                Result result = callAction(
+                        routes.ref.PasswordResetApp.requestResetPasswordEmail(),
+                        fakeRequest().withFormUrlEncodedBody(data)
+                );
+
+                //Then
+                assertThat(status(result)).isEqualTo(OK);
+                assertThat(contentAsString(result)).contains("메일을 발송하였습니다.");
+            }
+        });
+    }
+}
 
test/models/PasswordResetTest.java (added)
+++ test/models/PasswordResetTest.java
@@ -0,0 +1,176 @@
+package models;
+
+import static org.fest.assertions.Assertions.assertThat;
+import static org.fest.assertions.Fail.fail;
+
+import controllers.UserApp;
+import org.joda.time.DateTime;
+import org.junit.*;
+
+import java.util.Map;
+
+/**
+ * User: doortts
+ * Date: 4/4/13
+ * Time: 5:11 PM
+ */
+public class PasswordResetTest extends ModelTest<PasswordReset> {
+
+    @Before
+    public void setUp() throws Exception {
+        PasswordReset.resetHashTable();
+    }
+
+    @Test
+    public void testGenerateResetHash() throws Exception {
+        //Given
+        String userId = "doortts";
+
+        //When
+        String hashString = PasswordReset.generateResetHash(userId);
+
+        //Then
+        assertThat(hashString).describedAs("SHA1 character size").hasSize(40);
+    }
+
+    @Test
+    public void testAddHashToResetTable() throws Exception {
+        //Given
+        String userId = "doortts";
+        String hashString = PasswordReset.generateResetHash(userId);
+
+        //When
+        PasswordReset.addHashToResetTable(userId, hashString);
+
+        //Then
+        assertThat(PasswordReset.getResetHash(userId)).isEqualTo(hashString);
+    }
+
+    @Test
+    public void testInvalidateResetHash() throws Exception {
+        //Given
+        String userId = "doortts";
+        String hashString = PasswordReset.generateResetHash(userId);
+        PasswordReset.addHashToResetTable(userId, hashString);
+
+        //When
+        boolean result = PasswordReset.invalidateResetHash(userId);
+
+        //Then
+        assertThat(result).isTrue();
+        assertThat(PasswordReset.getResetHash(userId)).isNull();
+    }
+    
+    @Test
+    public void testIsHashExists() {
+        //Given
+        String userId = "doortts";
+        String hashString = PasswordReset.generateResetHash(userId);
+        PasswordReset.addHashToResetTable(userId, hashString);
+
+        //When
+        boolean result = PasswordReset.isHashExist(userId);
+
+        //Then
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void testIsHashExists_not() {
+        //Given
+        String userId = "doortts";
+
+        //When
+        boolean result = PasswordReset.isHashExist(userId);
+
+        //Then
+        assertThat(result).isFalse();
+    }
+
+    @Test
+    public void testIsValidResetHash_notExists() {
+        //Given
+        String userId = "doortts";
+
+        // When // Then
+        assertThat(PasswordReset.isValidResetHash("sfalkjsd")).isFalse();
+    }
+
+    @Test
+    public void testIsValidResetHash_expiredHash() {
+        //Given
+        String userId = "doortts";
+        String hashString = PasswordReset.generateResetHash(userId);
+        PasswordReset.addHashToResetTable(userId, hashString);
+        Map<String, DateTime> timetable = PasswordReset.getResetHashTimetable();
+        DateTime current = new DateTime();
+
+        //When //forced assume that time has passed
+        timetable.remove(hashString);
+        timetable.put(hashString, current.minusSeconds(PasswordReset.HASH_EXPIRE_TIME_SEC+1));
+
+        //Then
+        assertThat(PasswordReset.isValidResetHash(hashString)).isFalse();
+    }
+
+    @Test
+    public void testIsValidResetHash_evertythingOK() {
+        //Given
+        String userId = "doortts";
+        String hashString = PasswordReset.generateResetHash(userId);
+        PasswordReset.addHashToResetTable(userId, hashString);
+
+        //When
+        boolean result = PasswordReset.isValidResetHash(hashString);
+
+        //Then
+        assertThat(result).isTrue();
+    }
+
+    @Test
+    public void testRemoveResetHash() {
+        //Given
+        String userId = "doortts";
+        String hashString = PasswordReset.generateResetHash(userId);
+        PasswordReset.addHashToResetTable(userId, hashString);
+
+        //When
+        PasswordReset.removeResetHash(hashString);
+
+        //Then
+        assertThat(PasswordReset.isValidResetHash(hashString)).isFalse();
+    }
+
+    @Test
+    public void testResetPassword() {
+        //Given
+        String userId = "doortts";
+        String newPassword = "whffudy";
+        String hashString = PasswordReset.generateResetHash(userId);
+        PasswordReset.addHashToResetTable(userId, hashString);
+
+        //When
+        boolean result = PasswordReset.resetPassword(hashString, newPassword);
+
+        //Then
+        assertThat(result).isTrue();
+        assertThat(UserApp.authenticateWithPlainPassword(userId, newPassword)).isNotNull();
+    }
+
+    @Test
+    public void testResetPassword_wrongHash() {
+        //Given
+        String userId = "doortts";
+        String newPassword = "whffudy";
+        String hashString = PasswordReset.generateResetHash(userId);
+        PasswordReset.addHashToResetTable(userId, hashString);
+
+        //When
+        String wrongHash = "sfdlkjafslfjsda";
+        boolean result = PasswordReset.resetPassword(wrongHash, newPassword);
+
+        //Then
+        assertThat(result).isFalse();
+        assertThat(UserApp.authenticateWithPlainPassword(userId, newPassword)).isNull();
+    }
+}
Add a comment
List