-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #18 from OpenLMIS/OIS-57
OIS-57: Introduce password reset lockout
- Loading branch information
Showing
14 changed files
with
544 additions
and
0 deletions.
There are no files selected for viewing
84 changes: 84 additions & 0 deletions
84
...est/java/org/openlmis/auth/repository/PasswordResetRegistryRepositoryIntegrationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
/* | ||
* This program is part of the OpenLMIS logistics management information system platform software. | ||
* Copyright © 2017 VillageReach | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms | ||
* of the GNU Affero General Public License as published by the Free Software Foundation, either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | ||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
* See the GNU Affero General Public License for more details. You should have received a copy of | ||
* the GNU Affero General Public License along with this program. If not, see | ||
* http://www.gnu.org/licenses. For additional information contact info@OpenLMIS.org. | ||
*/ | ||
|
||
package org.openlmis.auth.repository; | ||
|
||
import static org.junit.Assert.assertEquals; | ||
import static org.junit.Assert.assertNotNull; | ||
|
||
import java.util.UUID; | ||
import javax.persistence.EntityManager; | ||
import javax.persistence.PersistenceException; | ||
import org.junit.Test; | ||
import org.openlmis.auth.domain.PasswordResetRegistry; | ||
import org.openlmis.auth.domain.User; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.data.repository.CrudRepository; | ||
|
||
public class PasswordResetRegistryRepositoryIntegrationTest | ||
extends BaseCrudRepositoryIntegrationTest<PasswordResetRegistry> { | ||
|
||
@Autowired | ||
private PasswordResetRegistryRepository passwordResetRegistryRepository; | ||
|
||
@Autowired | ||
private UserRepository userRepository; | ||
|
||
@Autowired | ||
private EntityManager entityManager; | ||
|
||
@Test | ||
public void shouldFindRegistryByUser() throws Exception { | ||
User user = userRepository.save(generateUser()); | ||
PasswordResetRegistry registry = passwordResetRegistryRepository.save(generateInstance(user)); | ||
|
||
PasswordResetRegistry result = passwordResetRegistryRepository.findByUser(user).get(); | ||
|
||
assertNotNull(result); | ||
assertEquals(registry.getId(), result.getId()); | ||
} | ||
|
||
@Test(expected = PersistenceException.class) | ||
public void shouldThrowExceptionOnCreatingRegistryWithSameUser() throws Exception { | ||
User user = userRepository.save(generateUser()); | ||
|
||
passwordResetRegistryRepository.save(generateInstance(user)); | ||
passwordResetRegistryRepository.save(generateInstance(user)); | ||
|
||
entityManager.flush(); | ||
} | ||
|
||
@Override | ||
CrudRepository<PasswordResetRegistry, UUID> getRepository() { | ||
return passwordResetRegistryRepository; | ||
} | ||
|
||
@Override | ||
PasswordResetRegistry generateInstance() throws Exception { | ||
return new PasswordResetRegistry(userRepository.save(generateUser())); | ||
} | ||
|
||
PasswordResetRegistry generateInstance(User user) throws Exception { | ||
return new PasswordResetRegistry(user); | ||
} | ||
|
||
private User generateUser() { | ||
User user = new User(); | ||
user.setUsername("user" + getNextInstanceNumber()); | ||
user.setEnabled(true); | ||
return user; | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
75 changes: 75 additions & 0 deletions
75
src/main/java/org/openlmis/auth/domain/PasswordResetRegistry.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,75 @@ | ||
/* | ||
* This program is part of the OpenLMIS logistics management information system platform software. | ||
* Copyright © 2017 VillageReach | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms | ||
* of the GNU Affero General Public License as published by the Free Software Foundation, either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | ||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
* See the GNU Affero General Public License for more details. You should have received a copy of | ||
* the GNU Affero General Public License along with this program. If not, see | ||
* http://www.gnu.org/licenses. For additional information contact info@OpenLMIS.org. | ||
*/ | ||
|
||
package org.openlmis.auth.domain; | ||
|
||
import java.time.ZonedDateTime; | ||
import javax.persistence.Column; | ||
import javax.persistence.Entity; | ||
import javax.persistence.JoinColumn; | ||
import javax.persistence.OneToOne; | ||
import javax.persistence.Table; | ||
import lombok.AllArgsConstructor; | ||
import lombok.EqualsAndHashCode; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import lombok.Setter; | ||
|
||
@Entity | ||
@Table(name = "password_reset_registries") | ||
@NoArgsConstructor | ||
@AllArgsConstructor | ||
@Getter | ||
@Setter | ||
@EqualsAndHashCode(callSuper = true) | ||
public class PasswordResetRegistry extends BaseEntity { | ||
|
||
@OneToOne | ||
@JoinColumn(name = "userId", nullable = false, unique = true) | ||
private User user; | ||
|
||
@Column(nullable = false, columnDefinition = "timestamp with time zone") | ||
private ZonedDateTime lastAttemptDate = ZonedDateTime.now(); | ||
|
||
@Column(nullable = false, columnDefinition = "timestamp with time zone") | ||
private ZonedDateTime lastCounterResetDate = ZonedDateTime.now(); | ||
|
||
@Column(name = "attemptcounter") | ||
private Integer attemptCounter = 0; | ||
|
||
@Column(name = "blocked") | ||
private Boolean blocked; | ||
|
||
public PasswordResetRegistry(User user) { | ||
this.user = user; | ||
} | ||
|
||
/** | ||
* Resets the attempt counter and updates the last counter reset date and last attempt date. | ||
*/ | ||
public void resetCounter() { | ||
this.setAttemptCounter(0); | ||
this.setLastCounterResetDate(ZonedDateTime.now()); | ||
} | ||
|
||
/** | ||
* Increments the attempt counter and updates the last attempt date. | ||
*/ | ||
public void incrementCounter() { | ||
this.setAttemptCounter(this.getAttemptCounter() + 1); | ||
this.setLastAttemptDate(ZonedDateTime.now()); | ||
} | ||
|
||
} |
24 changes: 24 additions & 0 deletions
24
src/main/java/org/openlmis/auth/exception/TooManyRequestsMessageException.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
/* | ||
* This program is part of the OpenLMIS logistics management information system platform software. | ||
* Copyright © 2017 VillageReach | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms | ||
* of the GNU Affero General Public License as published by the Free Software Foundation, either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | ||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
* See the GNU Affero General Public License for more details. You should have received a copy of | ||
* the GNU Affero General Public License along with this program. If not, see | ||
* http://www.gnu.org/licenses. For additional information contact info@OpenLMIS.org. | ||
*/ | ||
|
||
package org.openlmis.auth.exception; | ||
|
||
public class TooManyRequestsMessageException extends BaseMessageException { | ||
|
||
public TooManyRequestsMessageException(String messageKey) { | ||
super(messageKey); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
29 changes: 29 additions & 0 deletions
29
src/main/java/org/openlmis/auth/repository/PasswordResetRegistryRepository.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
/* | ||
* This program is part of the OpenLMIS logistics management information system platform software. | ||
* Copyright © 2017 VillageReach | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms | ||
* of the GNU Affero General Public License as published by the Free Software Foundation, either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | ||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
* See the GNU Affero General Public License for more details. You should have received a copy of | ||
* the GNU Affero General Public License along with this program. If not, see | ||
* http://www.gnu.org/licenses. For additional information contact info@OpenLMIS.org. | ||
*/ | ||
|
||
package org.openlmis.auth.repository; | ||
|
||
import java.util.Optional; | ||
import java.util.UUID; | ||
import org.openlmis.auth.domain.PasswordResetRegistry; | ||
import org.openlmis.auth.domain.User; | ||
import org.springframework.data.repository.CrudRepository; | ||
|
||
public interface PasswordResetRegistryRepository | ||
extends CrudRepository<PasswordResetRegistry, UUID> { | ||
|
||
Optional<PasswordResetRegistry> findByUser(User user); | ||
|
||
} |
92 changes: 92 additions & 0 deletions
92
src/main/java/org/openlmis/auth/service/PasswordResetRegistryService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
/* | ||
* This program is part of the OpenLMIS logistics management information system platform software. | ||
* Copyright © 2017 VillageReach | ||
* | ||
* This program is free software: you can redistribute it and/or modify it under the terms | ||
* of the GNU Affero General Public License as published by the Free Software Foundation, either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; | ||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. | ||
* See the GNU Affero General Public License for more details. You should have received a copy of | ||
* the GNU Affero General Public License along with this program. If not, see | ||
* http://www.gnu.org/licenses. For additional information contact info@OpenLMIS.org. | ||
*/ | ||
|
||
package org.openlmis.auth.service; | ||
|
||
import static org.openlmis.auth.i18n.MessageKeys.ERROR_TOO_MANY_REQUESTS; | ||
|
||
import java.time.Duration; | ||
import java.time.ZonedDateTime; | ||
import java.util.Optional; | ||
import org.openlmis.auth.domain.PasswordResetRegistry; | ||
import org.openlmis.auth.domain.User; | ||
import org.openlmis.auth.exception.TooManyRequestsMessageException; | ||
import org.openlmis.auth.repository.PasswordResetRegistryRepository; | ||
import org.springframework.beans.factory.annotation.Autowired; | ||
import org.springframework.beans.factory.annotation.Value; | ||
import org.springframework.stereotype.Service; | ||
|
||
@Service | ||
public class PasswordResetRegistryService { | ||
|
||
@Value("${password.reset.maxAttempts}") | ||
private int maxAttempt; | ||
|
||
@Value("${password.reset.maxTimeForAttempts}") | ||
private long maxTimeForAttempts; | ||
|
||
@Value("${password.reset.lockoutTime}") | ||
private long lockoutTime; | ||
|
||
@Autowired | ||
private PasswordResetRegistryRepository passwordResetRegistryRepository; | ||
|
||
/** | ||
* Checks whether the user has exceeded the limit of attempts to send a password reset request. | ||
* | ||
* @param user @param user the User attempting a password reset | ||
*/ | ||
public void checkPasswordResetLimit(User user) { | ||
Optional<PasswordResetRegistry> registryOpt = passwordResetRegistryRepository.findByUser(user); | ||
ZonedDateTime now = ZonedDateTime.now(); | ||
|
||
PasswordResetRegistry registry; | ||
if (registryOpt.isPresent()) { | ||
registry = registryOpt.get(); | ||
|
||
if (Boolean.TRUE.equals(registry.getBlocked())) { | ||
long secondsSinceLastAttempt = | ||
Duration.between(registry.getLastAttemptDate(), now).getSeconds(); | ||
if (secondsSinceLastAttempt < lockoutTime) { | ||
throw new TooManyRequestsMessageException(ERROR_TOO_MANY_REQUESTS); | ||
} else { | ||
registry.resetCounter(); | ||
registry.setBlocked(false); | ||
} | ||
} | ||
|
||
long secondsSinceFirstAttempt = | ||
Duration.between(registry.getLastCounterResetDate(), now).getSeconds(); | ||
if (secondsSinceFirstAttempt > maxTimeForAttempts) { | ||
registry.resetCounter(); | ||
} | ||
|
||
registry.incrementCounter(); | ||
|
||
if (registry.getAttemptCounter() >= maxAttempt) { | ||
registry.setBlocked(true); | ||
} | ||
} else { | ||
PasswordResetRegistry newRegistry = new PasswordResetRegistry(user); | ||
newRegistry.setAttemptCounter(1); | ||
newRegistry.setLastAttemptDate(now); | ||
newRegistry.setLastCounterResetDate(now); | ||
|
||
registry = newRegistry; | ||
} | ||
passwordResetRegistryRepository.save(registry); | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.