mirror of
https://github.com/sismics/docs.git
synced 2024-11-25 23:27:57 +01:00
#84: TOTP key generation and validation code checking on login
This commit is contained in:
parent
5f84da61c8
commit
fb0bb62eaf
@ -36,9 +36,9 @@ public class UserDao {
|
|||||||
*
|
*
|
||||||
* @param username User login
|
* @param username User login
|
||||||
* @param password User password
|
* @param password User password
|
||||||
* @return ID of the authenticated user or null
|
* @return The authenticated user or null
|
||||||
*/
|
*/
|
||||||
public String authenticate(String username, String password) {
|
public User authenticate(String username, String password) {
|
||||||
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
EntityManager em = ThreadLocalContext.get().getEntityManager();
|
||||||
Query q = em.createQuery("select u from User u where u.username = :username and u.deleteDate is null");
|
Query q = em.createQuery("select u from User u where u.username = :username and u.deleteDate is null");
|
||||||
q.setParameter("username", username);
|
q.setParameter("username", username);
|
||||||
@ -47,7 +47,7 @@ public class UserDao {
|
|||||||
if (!BCrypt.checkpw(password, user.getPassword())) {
|
if (!BCrypt.checkpw(password, user.getPassword())) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return user.getId();
|
return user;
|
||||||
} catch (NoResultException e) {
|
} catch (NoResultException e) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -104,6 +104,7 @@ public class UserDao {
|
|||||||
userFromDb.setEmail(user.getEmail());
|
userFromDb.setEmail(user.getEmail());
|
||||||
userFromDb.setStorageQuota(user.getStorageQuota());
|
userFromDb.setStorageQuota(user.getStorageQuota());
|
||||||
userFromDb.setStorageCurrent(user.getStorageCurrent());
|
userFromDb.setStorageCurrent(user.getStorageCurrent());
|
||||||
|
userFromDb.setTotpKey(user.getTotpKey());
|
||||||
|
|
||||||
// Create audit log
|
// Create audit log
|
||||||
AuditLogUtil.create(userFromDb, AuditLogType.UPDATE, userId);
|
AuditLogUtil.create(userFromDb, AuditLogType.UPDATE, userId);
|
||||||
|
@ -64,56 +64,63 @@ public class AuthenticationToken {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(String id) {
|
public AuthenticationToken setId(String id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUserId() {
|
public String getUserId() {
|
||||||
return userId;
|
return userId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUserId(String userId) {
|
public AuthenticationToken setUserId(String userId) {
|
||||||
this.userId = userId;
|
this.userId = userId;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isLongLasted() {
|
public boolean isLongLasted() {
|
||||||
return longLasted;
|
return longLasted;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLongLasted(boolean longLasted) {
|
public AuthenticationToken setLongLasted(boolean longLasted) {
|
||||||
this.longLasted = longLasted;
|
this.longLasted = longLasted;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getIp() {
|
public String getIp() {
|
||||||
return ip;
|
return ip;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setIp(String ip) {
|
public AuthenticationToken setIp(String ip) {
|
||||||
this.ip = ip;
|
this.ip = ip;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUserAgent() {
|
public String getUserAgent() {
|
||||||
return userAgent;
|
return userAgent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUserAgent(String userAgent) {
|
public AuthenticationToken setUserAgent(String userAgent) {
|
||||||
this.userAgent = userAgent;
|
this.userAgent = userAgent;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getCreationDate() {
|
public Date getCreationDate() {
|
||||||
return creationDate;
|
return creationDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreationDate(Date creationDate) {
|
public AuthenticationToken setCreationDate(Date creationDate) {
|
||||||
this.creationDate = creationDate;
|
this.creationDate = creationDate;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getLastConnectionDate() {
|
public Date getLastConnectionDate() {
|
||||||
return lastConnectionDate;
|
return lastConnectionDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setLastConnectionDate(Date lastConnectionDate) {
|
public AuthenticationToken setLastConnectionDate(Date lastConnectionDate) {
|
||||||
this.lastConnectionDate = lastConnectionDate;
|
this.lastConnectionDate = lastConnectionDate;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -48,6 +48,12 @@ public class User implements Loggable {
|
|||||||
@Column(name = "USE_PRIVATEKEY_C", nullable = false, length = 100)
|
@Column(name = "USE_PRIVATEKEY_C", nullable = false, length = 100)
|
||||||
private String privateKey;
|
private String privateKey;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TOTP secret key.
|
||||||
|
*/
|
||||||
|
@Column(name = "USE_TOTPKEY_C", length = 100)
|
||||||
|
private String totpKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Email address.
|
* Email address.
|
||||||
*/
|
*/
|
||||||
@ -82,48 +88,54 @@ public class User implements Loggable {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setId(String id) {
|
public User setId(String id) {
|
||||||
this.id = id;
|
this.id = id;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getRoleId() {
|
public String getRoleId() {
|
||||||
return roleId;
|
return roleId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setRoleId(String roleId) {
|
public User setRoleId(String roleId) {
|
||||||
this.roleId = roleId;
|
this.roleId = roleId;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getUsername() {
|
public String getUsername() {
|
||||||
return username;
|
return username;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUsername(String username) {
|
public User setUsername(String username) {
|
||||||
this.username = username;
|
this.username = username;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPassword() {
|
public String getPassword() {
|
||||||
return password;
|
return password;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPassword(String password) {
|
public User setPassword(String password) {
|
||||||
this.password = password;
|
this.password = password;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getEmail() {
|
public String getEmail() {
|
||||||
return email;
|
return email;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setEmail(String email) {
|
public User setEmail(String email) {
|
||||||
this.email = email;
|
this.email = email;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Date getCreateDate() {
|
public Date getCreateDate() {
|
||||||
return createDate;
|
return createDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCreateDate(Date createDate) {
|
public User setCreateDate(Date createDate) {
|
||||||
this.createDate = createDate;
|
this.createDate = createDate;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -131,32 +143,45 @@ public class User implements Loggable {
|
|||||||
return deleteDate;
|
return deleteDate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setDeleteDate(Date deleteDate) {
|
public User setDeleteDate(Date deleteDate) {
|
||||||
this.deleteDate = deleteDate;
|
this.deleteDate = deleteDate;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getPrivateKey() {
|
public String getPrivateKey() {
|
||||||
return privateKey;
|
return privateKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPrivateKey(String privateKey) {
|
public User setPrivateKey(String privateKey) {
|
||||||
this.privateKey = privateKey;
|
this.privateKey = privateKey;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getStorageQuota() {
|
public Long getStorageQuota() {
|
||||||
return storageQuota;
|
return storageQuota;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStorageQuota(Long storageQuota) {
|
public User setStorageQuota(Long storageQuota) {
|
||||||
this.storageQuota = storageQuota;
|
this.storageQuota = storageQuota;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Long getStorageCurrent() {
|
public Long getStorageCurrent() {
|
||||||
return storageCurrent;
|
return storageCurrent;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setStorageCurrent(Long storageCurrent) {
|
public User setStorageCurrent(Long storageCurrent) {
|
||||||
this.storageCurrent = storageCurrent;
|
this.storageCurrent = storageCurrent;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getTotpKey() {
|
||||||
|
return totpKey;
|
||||||
|
}
|
||||||
|
|
||||||
|
public User setTotpKey(String totpKey) {
|
||||||
|
this.totpKey = totpKey;
|
||||||
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1 +1 @@
|
|||||||
db.version=8
|
db.version=9
|
@ -0,0 +1,2 @@
|
|||||||
|
alter table T_USER add column USE_TOTPKEY_C varchar(100);
|
||||||
|
update T_CONFIG set CFG_VALUE_C = '9' where CFG_ID_C = 'DB_VERSION';
|
@ -95,12 +95,11 @@ public class ClientUtil {
|
|||||||
* @return Authentication token
|
* @return Authentication token
|
||||||
*/
|
*/
|
||||||
public String login(String username, String password, Boolean remember) {
|
public String login(String username, String password, Boolean remember) {
|
||||||
Form form = new Form();
|
|
||||||
form.param("username", username);
|
|
||||||
form.param("password", password);
|
|
||||||
form.param("remember", remember.toString());
|
|
||||||
Response response = resource.path("/user/login").request()
|
Response response = resource.path("/user/login").request()
|
||||||
.post(Entity.form(form));
|
.post(Entity.form(new Form()
|
||||||
|
.param("username", username)
|
||||||
|
.param("password", password)
|
||||||
|
.param("remember", remember.toString())));
|
||||||
|
|
||||||
return getAuthenticationCookie(response);
|
return getAuthenticationCookie(response);
|
||||||
}
|
}
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
api.current_version=${project.version}
|
api.current_version=${project.version}
|
||||||
api.min_version=1.0
|
api.min_version=1.0
|
||||||
db.version=8
|
db.version=9
|
@ -255,6 +255,7 @@ public class UserResource extends BaseResource {
|
|||||||
public Response login(
|
public Response login(
|
||||||
@FormParam("username") String username,
|
@FormParam("username") String username,
|
||||||
@FormParam("password") String password,
|
@FormParam("password") String password,
|
||||||
|
@FormParam("code") String validationCodeStr,
|
||||||
@FormParam("remember") boolean longLasted) {
|
@FormParam("remember") boolean longLasted) {
|
||||||
// Validate the input data
|
// Validate the input data
|
||||||
username = StringUtils.strip(username);
|
username = StringUtils.strip(username);
|
||||||
@ -262,11 +263,26 @@ public class UserResource extends BaseResource {
|
|||||||
|
|
||||||
// Get the user
|
// Get the user
|
||||||
UserDao userDao = new UserDao();
|
UserDao userDao = new UserDao();
|
||||||
String userId = userDao.authenticate(username, password);
|
User user = userDao.authenticate(username, password);
|
||||||
if (userId == null) {
|
if (user == null) {
|
||||||
throw new ForbiddenClientException();
|
throw new ForbiddenClientException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Two factor authentication
|
||||||
|
if (user.getTotpKey() != null) {
|
||||||
|
// If TOTP is enabled, ask a validation code
|
||||||
|
if (Strings.isNullOrEmpty(validationCodeStr)) {
|
||||||
|
throw new ClientException("ValidationCodeRequired", "An OTP validation code is required");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check the validation code
|
||||||
|
int validationCode = ValidationUtil.validateInteger(validationCodeStr, "code");
|
||||||
|
GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
|
||||||
|
if (!googleAuthenticator.authorize(user.getTotpKey(), validationCode)) {
|
||||||
|
throw new ForbiddenClientException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Get the remote IP
|
// Get the remote IP
|
||||||
String ip = request.getHeader("x-forwarded-for");
|
String ip = request.getHeader("x-forwarded-for");
|
||||||
if (Strings.isNullOrEmpty(ip)) {
|
if (Strings.isNullOrEmpty(ip)) {
|
||||||
@ -275,15 +291,15 @@ public class UserResource extends BaseResource {
|
|||||||
|
|
||||||
// Create a new session token
|
// Create a new session token
|
||||||
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
|
AuthenticationTokenDao authenticationTokenDao = new AuthenticationTokenDao();
|
||||||
AuthenticationToken authenticationToken = new AuthenticationToken();
|
AuthenticationToken authenticationToken = new AuthenticationToken()
|
||||||
authenticationToken.setUserId(userId);
|
.setUserId(user.getId())
|
||||||
authenticationToken.setLongLasted(longLasted);
|
.setLongLasted(longLasted)
|
||||||
authenticationToken.setIp(ip);
|
.setIp(ip)
|
||||||
authenticationToken.setUserAgent(StringUtils.abbreviate(request.getHeader("user-agent"), 1000));
|
.setUserAgent(StringUtils.abbreviate(request.getHeader("user-agent"), 1000));
|
||||||
String token = authenticationTokenDao.create(authenticationToken);
|
String token = authenticationTokenDao.create(authenticationToken);
|
||||||
|
|
||||||
// Cleanup old session tokens
|
// Cleanup old session tokens
|
||||||
authenticationTokenDao.deleteOldSessionToken(userId);
|
authenticationTokenDao.deleteOldSessionToken(user.getId());
|
||||||
|
|
||||||
JsonObjectBuilder response = Json.createObjectBuilder();
|
JsonObjectBuilder response = Json.createObjectBuilder();
|
||||||
int maxAge = longLasted ? TokenBasedSecurityFilter.TOKEN_LONG_LIFETIME : -1;
|
int maxAge = longLasted ? TokenBasedSecurityFilter.TOKEN_LONG_LIFETIME : -1;
|
||||||
@ -648,18 +664,18 @@ public class UserResource extends BaseResource {
|
|||||||
throw new ForbiddenClientException();
|
throw new ForbiddenClientException();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new TOTP key and scratch codes
|
// Create a new TOTP key
|
||||||
GoogleAuthenticator gAuth = new GoogleAuthenticator();
|
GoogleAuthenticator gAuth = new GoogleAuthenticator();
|
||||||
final GoogleAuthenticatorKey key = gAuth.createCredentials();
|
final GoogleAuthenticatorKey key = gAuth.createCredentials();
|
||||||
|
|
||||||
JsonArrayBuilder scratchCodes = Json.createArrayBuilder();
|
// Save it
|
||||||
for (int scratchCode : key.getScratchCodes()) {
|
UserDao userDao = new UserDao();
|
||||||
scratchCodes.add(scratchCode);
|
User user = userDao.getActiveByUsername(principal.getName());
|
||||||
}
|
user.setTotpKey(key.getKey());
|
||||||
|
user = userDao.update(user, principal.getId());
|
||||||
|
|
||||||
JsonObjectBuilder response = Json.createObjectBuilder()
|
JsonObjectBuilder response = Json.createObjectBuilder()
|
||||||
.add("secret", key.getKey())
|
.add("secret", key.getKey());
|
||||||
.add("scratch_codes", scratchCodes);
|
|
||||||
return Response.ok().entity(response.build()).build();
|
return Response.ok().entity(response.build()).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,3 @@
|
|||||||
api.current_version=${project.version}
|
api.current_version=${project.version}
|
||||||
api.min_version=1.0
|
api.min_version=1.0
|
||||||
db.version=8
|
db.version=9
|
@ -1,3 +1,3 @@
|
|||||||
api.current_version=${project.version}
|
api.current_version=${project.version}
|
||||||
api.min_version=1.0
|
api.min_version=1.0
|
||||||
db.version=8
|
db.version=9
|
@ -1,5 +1,6 @@
|
|||||||
package com.sismics.docs.rest;
|
package com.sismics.docs.rest;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
|
|
||||||
import javax.json.JsonArray;
|
import javax.json.JsonArray;
|
||||||
@ -13,6 +14,7 @@ import org.junit.Assert;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
import com.sismics.util.filter.TokenBasedSecurityFilter;
|
||||||
|
import com.sismics.util.totp.GoogleAuthenticator;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Exhaustive test of the user resource.
|
* Exhaustive test of the user resource.
|
||||||
@ -299,5 +301,27 @@ public class TestUserResource extends BaseJerseyTest {
|
|||||||
.post(Entity.form(new Form()), JsonObject.class);
|
.post(Entity.form(new Form()), JsonObject.class);
|
||||||
String secret = json.getString("secret");
|
String secret = json.getString("secret");
|
||||||
Assert.assertNotNull(secret);
|
Assert.assertNotNull(secret);
|
||||||
|
|
||||||
|
// Try to login with totp1 without a validation code
|
||||||
|
Response response = target().path("/user/login").request()
|
||||||
|
.post(Entity.form(new Form()
|
||||||
|
.param("username", "totp1")
|
||||||
|
.param("password", "12345678")
|
||||||
|
.param("remember", "false")));
|
||||||
|
Assert.assertEquals(Status.BAD_REQUEST.getStatusCode(), response.getStatus());
|
||||||
|
json = response.readEntity(JsonObject.class);
|
||||||
|
Assert.assertEquals("ValidationCodeRequired", json.getString("type"));
|
||||||
|
|
||||||
|
// Generate a OTP
|
||||||
|
GoogleAuthenticator googleAuthenticator = new GoogleAuthenticator();
|
||||||
|
int validationCode = googleAuthenticator.calculateCode(secret, new Date().getTime() / 30000);
|
||||||
|
|
||||||
|
// Login with totp1 with a validation code
|
||||||
|
json = target().path("/user/login").request()
|
||||||
|
.post(Entity.form(new Form()
|
||||||
|
.param("username", "totp1")
|
||||||
|
.param("password", "12345678")
|
||||||
|
.param("code", Integer.toString(validationCode))
|
||||||
|
.param("remember", "false")), JsonObject.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user