/*
* This file is part of gwap, an open platform for games with a purpose
*
* Copyright (C) 2013
* Project play4science
* Lehr- und Forschungseinheit für Programmier- und Modellierungssprachen
* Ludwig-Maximilians-Universität München
*
* 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/>.
*/
package gwap.authentication;
import gwap.model.Person;
import gwap.model.Role;
import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Random;
import javax.faces.model.SelectItem;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.Query;
import org.jboss.seam.ScopeType;
import org.jboss.seam.annotations.AutoCreate;
import org.jboss.seam.annotations.In;
import org.jboss.seam.annotations.Logger;
import org.jboss.seam.annotations.Name;
import org.jboss.seam.annotations.Observer;
import org.jboss.seam.annotations.Out;
import org.jboss.seam.annotations.Scope;
import org.jboss.seam.core.Events;
import org.jboss.seam.faces.FacesMessages;
import org.jboss.seam.faces.Renderer;
import org.jboss.seam.international.LocaleSelector;
import org.jboss.seam.international.StatusMessage.Severity;
import org.jboss.seam.log.Log;
import org.jboss.seam.security.Credentials;
import org.jboss.seam.security.Identity;
/**
* The authenticator is responsible for user logins. It also handles the roles
* and the mapping of persons to BPM actors.
*
* @author Christoph Wieser
*/
@AutoCreate
@Name("authenticator")
@Scope(ScopeType.SESSION)
public class Authenticator implements Serializable {
private static final long serialVersionUID = 1L;
@Logger
private Log log;
@In
private EntityManager entityManager;
@In
private Credentials credentials;
@In
private Identity identity;
@In
private LocaleSelector localeSelector;
@In
private FacesMessages facesMessages;
@In(create = true)
private Renderer renderer;
// @In private Actor actor; // jBPM
@In(required = false)
@Out(required = false)
private Person person;
private Person candidate;
public boolean authenticate() {
if (credentials.getUsername() == null || credentials.getUsername().length() == 0) {
facesMessages.addToControl("username", Severity.ERROR, "#{messages['person.username']} #{messages['validator.notNull']}");
return false;
}
if (credentials.getPassword() == null || credentials.getPassword().length() == 0) {
facesMessages.addToControl("password", Severity.ERROR, "#{messages['person.password']} #{messages['validator.notNull']}");
return false;
}
try {
// Get candidatePerson from the database by form data
Query query = entityManager
.createNamedQuery("person.authentication");
query.setParameter("username", credentials.getUsername());
Person candidatePerson = (Person) query.getSingleResult();
// Case insensitive => set login username
credentials.setUsername(candidatePerson.getUsername());
// Verify password with database password
String verifiedPassword = MD5Crypt.crypt(credentials.getPassword(),
candidatePerson.getPassword());
boolean passwordMatch = verifiedPassword.equals(candidatePerson
.getPassword());
Calendar calendar = GregorianCalendar.getInstance();
Date now = new Date();
// Do not allow users to log in twice (here: 60 seconds barrier)
// TODO: Implement it in a better way :)
boolean duplicateLogin = false;
if (candidatePerson.getLastLogin() != null) {
calendar.setTime(candidatePerson.getLastLogin());
calendar.add(Calendar.SECOND, 60); // duration of timeout
Date timeout = calendar.getTime();
duplicateLogin = !timeout.before(now);
if (duplicateLogin && candidatePerson.getLastLogout() != null)
duplicateLogin = !candidatePerson.getLastLogout().before(now);
}
if ((passwordMatch) && !duplicateLogin) {
// Connect anonymous person with registered person
// (e.g. for saving anonymously achieved game results)
boolean anonymousUserCreated = (person != null) && (person.getId() != null);
if (anonymousUserCreated) {
person = entityManager.find(Person.class, person.getId());
if (person != null && person.getId() != candidatePerson.getId()) {
person.setPersonConnected(candidatePerson);
facesMessages.addFromResourceBundle("register.pointsSaved");
}
}
// Login candidatePerson
person = candidatePerson;
log.info("Login: #0", person.getUsername());
updatePersonOnLogin(person);
// jBPM stuff
// actor.setId(Long.toString(person.getId()));
// actor.getGroupActorIds().add("player");
// log.info("Login: #0 (id: #1)", person.getUsername(),
// actor.getId());
return true;
} else {
facesMessages.addToControl("profileForm", Severity.ERROR, "#{messages['org.jboss.seam.loginFailed']}");
return false;
}
}
catch (NoResultException ex) {
return false;
}
}
private void updatePersonOnLogin(Person person) {
// Set meta data
try {
for (SelectItem locale : localeSelector.getSupportedLocales())
if (locale.getValue().equals(person.getLanguage()))
localeSelector.setLanguage(person.getLanguage());
} catch (NullPointerException e) {
// Not supported for restful services
}
if (person.getRoles() != null) {
for (Role role : person.getRoles()) {
identity.addRole(role.getRole());
}
}
}
public boolean tryLogin() {
try {
if (identity.isLoggedIn())
return true;
else if (identity.tryLogin()) {
person = (Person) entityManager.createNamedQuery("person.byUsername")
.setParameter("username", identity.getPrincipal().getName())
.getSingleResult();
credentials.setUsername(person.getUsername());
updatePersonOnLogin(person);
// does not get triggered automatically
Events.instance().raiseEvent("org.jboss.seam.security.loginSuccessful");
log.info("Person logged in with remember-me method #0", person);
return true;
}
} catch (Exception e) {
// do nothing
log.info("Could not login with remember me method", e);
}
return false;
}
@Observer("org.jboss.seam.security.loginSuccessful")
public void loginSuccessful() {
person.setLastLogout(null);
person.setLastLogin(new Date());
entityManager.merge(person);
}
@Observer("org.jboss.seam.security.loggedOut")
public void logout() {
person.setLastLogout(new Date());
entityManager.merge(person);
}
public void resetPassword() {
try {
log.info("Reset password for user with email " + person.getEmail());
Query query = entityManager.createNamedQuery("person.byEmail");
query.setParameter("email", person.getEmail());
candidate = (Person) query.getSingleResult();
// Create random password
candidate.setPasswordResetToken(generateRandomPassword());
candidate.setPasswordResetDate(new Date());
renderer.render("/email/resetPassword.xhtml");
facesMessages
.add("#{messages['general.emailSentSuccessfully']}");
entityManager.merge(candidate);
} catch (NoResultException e) {
facesMessages.add("#{messages['general.emailNotFound']}");
} catch (Exception e) {
facesMessages.add(
"#{messages['general.emailSendingFailed']}",
e.getMessage());
log.info("Email sending failed: " + e.getMessage());
}
}
public Person getCandidate() {
return candidate;
}
/**
* This method is used to generate a random password
*
* @param int - length of the password
* @return String - random password
*/
public static String generateRandomPassword(int length) {
String passarray = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
int range = passarray.length();
String passwd = "";
Random generator = new Random();
for (int i = 0; i < length; i++) {
int rnd = generator.nextInt(range);
String ch = passarray.substring(rnd, rnd + 1);
passwd += ch;
}
return passwd;
}
/**
* This method is used to generate a random password with a default length of 30
*/
public static String generateRandomPassword() {
return Authenticator.generateRandomPassword(30);
}
public Person getPerson() {
return person;
}
}