chore: reorganize into polyglot monorepo (workshop)

- Move bigmind/ -> mcp/bigmind/
- Move webscraper/ -> mcp/webscraper/
- Move mss-failsafe/ -> java/mss-failsafe/
- Move Wellmann-Shop/ -> java/wellmann-shop/ (normalize to kebab-case)
- Add .roo/ IDE config files to tracking
- Add plans/REPO_STRATEGY.md (monorepo strategy document)
- Expand .gitignore: Java/Maven, Node/TS, coverage, uv.lock
- Rewrite README.md as navigation index
- Update .roo/mcp.json webscraper path to mcp/webscraper/
This commit is contained in:
Patrick Plate
2026-04-04 08:51:15 +02:00
parent 4167e15ed9
commit 155d56e8e8
1598 changed files with 19429 additions and 23 deletions
@@ -0,0 +1,36 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package business.user;
import javax.ejb.EJB;
import javax.ejb.Startup;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Named;
/**
*
* @author patri
*/
@Named(value = "DemoManager")
@ApplicationScoped
@Startup
public class DemoManager {
@EJB
PersonManager personManager;
/**
* Creates a new instance of NewJSFManagedBean
*/
public DemoManager() {
runDemos();
}
private void runDemos(){
personManager.demo();
}
}
@@ -0,0 +1,174 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package business.user;
import java.io.Serializable;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.security.spec.InvalidKeySpecException;
import java.util.Arrays;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.ejb.Stateless;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.NoResultException;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.transaction.Transactional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import model.person.Password;
import model.person.Salt;
import model.person.Person;
/**
*
* @author Patrick
*/
@Named(value = "passwordManager")
@Stateless
public class PasswordManager implements Serializable {
private static final long serialVersionUID = -4563304131856981259L;
final static Logger LOGGER = LogManager.getLogger(PasswordManager.class);
@PersistenceContext(name = "pu_person")
private EntityManager em;
//private Password password;
private Person user;
private final int keyLength = 256;
public byte[] hashPassword(final char[] password, final byte[] salt, final int iterations) {
try {
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
PBEKeySpec spec = new PBEKeySpec(password, salt, iterations, this.keyLength);
SecretKey key = skf.generateSecret(spec);
//this.password = new Password(this.costumer, key.getEncoded());
LOGGER.debug("Hash created!");
return key.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
LOGGER.error("Failure creating hash for with:" + e);
return null;
//throw new RuntimeException( e );
}
}
public byte[] hashPasswordUser(final char[] password, Person user) {
if (user == null) {
LOGGER.error("Tried to create hash for Nullcostumer!");
return null;
}
try {
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
PBEKeySpec spec = new PBEKeySpec(password, user.getSalt().getSalt(), user.getSalt().getInterations(), keyLength);
SecretKey key = skf.generateSecret(spec);
LOGGER.debug("Hash created!");
return key.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
LOGGER.error("Failure creating hash for" + user.getEmail() + "with: " + e);
return null;
}
}
public boolean passwordCheckEmail(final String email, final byte[] password) {
TypedQuery<Person> query = em.createNamedQuery(Person.FIND_BY_EMAIL, Person.class);
query.setParameter("email", email);
try {
Person user;
user = query.getSingleResult();
boolean equals = Arrays.equals(password, user.getPassword().getPassword());
for (int i = 0; i < password.length; i++) {
password[i] = 0;
}
return equals;
} catch (NoResultException e) {
return false;
}
}
@Transactional
public boolean passwordCheckCustomer(final Person user, final String password) {
LOGGER.debug("Test " + user.getEmail() + " mit " + password);
byte[] pw = hashPasswordUser(password.toCharArray(), user);
boolean equals = Arrays.equals(pw, user.getPassword().getPassword());
for (int i = 0; i < pw.length; i++) {
pw[i] = 0;
}
return equals;
}
public boolean changePassword(Person user, String newPassword, String oldPassword) {
if (user == null) {
LOGGER.error("Nullcostumer tried to change Password");
return false;
}
if(passwordCheckCustomer(user, oldPassword)){
user.getPassword().setPassowrd(hashPasswordUser(newPassword.toCharArray(), user));
LOGGER.info("Password changed for " + user.getEmail());
} else {
return false;
}
try {
em.persist(user);
LOGGER.info("Password changed for " + user.getEmail());
return true;
} catch (Exception e) {
LOGGER.error("Couldn't save new password to " + user.getEmail() + " with: " + e.toString());
return false;
}
}
public byte[] generateRandomPassword() {
SecureRandom random = new SecureRandom();
byte bytes[] = new byte[20];
random.nextBytes(bytes);
return bytes;
}
public Password gerateNewRandomPasswordClass(Person user){
Salt salt = user.getSalt();
String pass = Arrays.toString(generateRandomPassword());
return new Password(user, hashPassword(pass.toCharArray(), salt.getSalt(), salt.getInterations()));
}
public byte[] hashToken(String token){
try {
SecretKeyFactory skf = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
byte[] test = "SuperTestSalz".getBytes();
PBEKeySpec spec = new PBEKeySpec(token.toCharArray(), test, 200, this.keyLength);
SecretKey key = skf.generateSecret(spec);
LOGGER.debug("TokenHash created!");
return key.getEncoded();
} catch (NoSuchAlgorithmException | InvalidKeySpecException e) {
LOGGER.error("Failure creating tokenhash for " + token + " with: " + e);
return null;
}
}
public Person getUser() {
return user;
}
public void setUser(Person user) {
this.user = user;
}
}
@@ -0,0 +1,229 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package business.user;
import java.io.Serializable;
import java.time.Instant;
import java.util.Optional;
import javax.ejb.EJB;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.inject.Named;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;
import javax.transaction.Transactional;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import controller.person.PersonController;
import exception.InvalidEmailException;
import exception.InvalidPasswordException;
import exception.PersonInaktiveException;
import java.util.HashSet;
import java.util.Set;
import model.person.Salt;
import model.person.enums.TokenType;
import model.person.Person;
import model.person.enums.UserGroup;
/**
*
* @author Patrick
*/
@Named
@Stateless
public class PersonManager implements Serializable {
private static final long serialVersionUID = -6581582446436303658L;
final static Logger LOGGER = LogManager.getLogger(PersonManager.class);
@EJB
private PasswordManager passwordManager;
@Inject
private PersonController userController;
@PersistenceContext(name = "pu_person", type = PersistenceContextType.EXTENDED)
private EntityManager em;
@Transactional
public void demo() {
Salt salt = new Salt();
Salt salt2 = new Salt();
Salt salt3 = new Salt();
Set<UserGroup> groupUser = new HashSet<>();
groupUser.add(UserGroup.USER);
Set<UserGroup> adminUser = new HashSet<>();
adminUser.add(UserGroup.ADMIN);
Person test = new Person(
"user@test.de",
(passwordManager.hashPassword("test".toCharArray(), salt.getSalt(), salt.getInterations())),
salt,
groupUser
);
test.setFirstname("User");
test.setLastname("Nachname");
test.setMobile("0124584589");
test.setFax("3445565675");
test.setTelefon("042154585");
test.setActive(true);
em.persist(test);
Person testAdmin = new Person(
"admin@test.de",
(passwordManager.hashPassword("admin".toCharArray(), salt2.getSalt(), salt2.getInterations())),
salt2,
adminUser
);
testAdmin.setFirstname("Admin");
testAdmin.setLastname("Administratori");
testAdmin.setMobile("0124584589");
testAdmin.setFax("3445565675");
testAdmin.setTelefon("042154585");
testAdmin.setActive(true);
em.persist(testAdmin);
Person testInactive = new Person(
"inaktive@test.de",
(passwordManager.hashPassword("test".toCharArray(), salt3.getSalt(), salt3.getInterations())),
salt3,
groupUser
);
testInactive.setFirstname("Admin");
testInactive.setLastname("Administratori");
testInactive.setMobile("0124584589");
testInactive.setFax("3445565675");
testInactive.setTelefon("042154585");
testInactive.setActive(false);
em.persist(testInactive);
}
@Transactional
public Person load(Person user) {
try {
Person loaded = this.em.find(Person.class, user.getId());
LOGGER.info(loaded);
return loaded;
} catch (Exception e) {
LOGGER.error("Error", e);
return null;
}
}
@Transactional
public Person getActiveUser() {
try {
Person user = em.find(Person.class, userController.getActiveUser().getId());
return user;
} catch (Exception e) {
LOGGER.error("couldn't load user: " + e);
return null;
}
}
@Transactional
public Person getPlainActiveUser() {
try {
Person user = em.find(Person.class, userController.getActiveUser().getId());
return user;
} catch (Exception e) {
LOGGER.error("couldn't load user: " + e);
return null;
}
}
public Optional<Person> getByEmail(String email) {
try {
Person person = this.em.createNamedQuery(Person.FIND_BY_EMAIL, Person.class)
.setParameter("email", email).getSingleResult();
if (person != null) {
person.getUserGroups().size();
}
return Optional.of(person);
} catch (Exception e) {
return Optional.empty();
}
}
public Optional<Person> getByLoginToken(String loginToken, TokenType tokenType) {
Optional<Person> optional;
try {
optional = Optional.of(this.em.createNamedQuery(Person.FIND_BY_TOKEN, Person.class)
.setParameter("tokenHash", this.passwordManager.hashToken(loginToken))
.setParameter("tokenType", tokenType)
.setParameter("timestamp", Instant.now())
.getSingleResult()
);
return optional;
} catch (Exception e) {
return Optional.empty();
}
}
public Person getByEmailAndPassword(String email, String password) {
Person managedUser = getByEmail(email).orElseThrow(InvalidEmailException::new);
LOGGER.debug("Loaded " + managedUser.getEmail());
if (!passwordManager.passwordCheckCustomer(managedUser, password)) {
throw new InvalidPasswordException();
}
if (!managedUser.isActive()) {
throw new PersonInaktiveException();
}
userController.setActiveUser(managedUser);
return managedUser;
}
@Transactional
public boolean save(Person user) {
if (user == null) {
LOGGER.error("Tried to save null or Nullcustomer");
return false;
}
try {
if (user.getId() != null && user.getId() > 0) {
em.merge(user);
} else {
em.persist(user);
}
LOGGER.info("Saved " + user.getEmail());
return true;
} catch (Exception e) {
LOGGER.error("Tried to save " + user.getEmail() + " with error: " + e);
return false;
}
}
public void refresh(Person user) {
if (user == null) {
LOGGER.error("Tried to save null or Nullcustomer");
return;
}
try {
em.refresh(user);
} catch (Exception e) {
LOGGER.error("Tried to refresh " + user.getEmail() + " with error: " + e);
}
}
public String resetPassword() {
//TODO
return null;
}
}
@@ -0,0 +1,91 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package business.user;
import java.time.Instant;
import static java.time.temporal.ChronoUnit.DAYS;
import static java.util.UUID.randomUUID;
import javax.ejb.Stateless;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import exception.InvalidEmailException;
import model.person.Token;
import model.person.enums.TokenType;
import model.person.Person;
import java.util.Arrays;
import static java.time.Instant.now;
/**
*
* @author Patrick
*/
@Stateless
public class TokenManager {
@PersistenceContext(name = "pu_person")
private EntityManager em;
@Inject
PasswordManager passwordManager;
@Inject
PersonManager customerManager;
public String generate(final String email, final String ipAddress, final String description,
final TokenType tokenType) {
String rawToken = randomUUID().toString();
Instant expiration = now().plus(14, DAYS);
save(rawToken, email, ipAddress, description, tokenType, expiration);
return rawToken;
}
public String generateFileToken(final String email, final String description) {
String rawToken = randomUUID().toString();
Instant expiration = now().plus(3, DAYS);
save(rawToken, email, null, description, TokenType.FILE, expiration);
return rawToken;
}
public void save(final String rawToken, final String email, final String ipAddress,
final String description, final TokenType tokenType, final Instant expiration) {
Person user = this.customerManager.getByEmail(email)
.orElseThrow(InvalidEmailException::new);
Token token = new Token();
token.setTokenHash(Arrays.toString(this.passwordManager.hashToken(rawToken)));
token.setExpiration(expiration);
token.setDescription(description);
token.setTokenType(tokenType);
token.setIpAddress(ipAddress);
user.addToken(token);
this.em.persist(user);
}
public void remove(String token) {
this.em.createNamedQuery(Token.REMOVE_TOKEN)
.setParameter("tokenHash", token).executeUpdate();
}
public void removeExpired() {
this.em.createNamedQuery(Token.REMOVE_EXPIRED_TOKEN)
.setParameter("timestamp", Instant.now())
.executeUpdate();
}
}
@@ -0,0 +1,53 @@
package controller;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
import java.io.Serializable;
import javax.faces.application.FacesMessage;
import javax.faces.context.FacesContext;
/**
*
* @author Patrick
*/
public abstract class AbstractController implements Serializable{
private static final long serialVersionUID = -5908716187853409719L;
protected void sendInfoMessage(String title, String message){
FacesMessage facesMessage = new FacesMessage(
FacesMessage.SEVERITY_INFO, title, message);
addMessage(facesMessage);
}
protected void sendWarnMessage(String title, String message){
FacesMessage facesMessage = new FacesMessage(
FacesMessage.SEVERITY_WARN, title, message);
addMessage(facesMessage);
}
protected void sendErrorMessage(String title, String message){
FacesMessage facesMessage = new FacesMessage(
FacesMessage.SEVERITY_ERROR, title, message);
addMessage(facesMessage);
}
protected void sendFatalMessage(String title, String message){
FacesMessage facesMessage = new FacesMessage(
FacesMessage.SEVERITY_FATAL, title, message);
addMessage(facesMessage);
}
private void addMessage(FacesMessage message) {
FacesContext.getCurrentInstance().addMessage(null, message);
}
protected void errorMessage() {
String title = "Fehler!";
String info = "Es ist ein Fehler aufgetreten, bitte versuchen Sie es erneut!";
sendErrorMessage(title, info);
}
}
@@ -0,0 +1,173 @@
package controller.person;
import javax.enterprise.context.SessionScoped;
import javax.faces.context.FacesContext;
import javax.inject.Inject;
import javax.inject.Named;
import javax.security.enterprise.AuthenticationStatus;
import javax.security.enterprise.SecurityContext;
import javax.security.enterprise.credential.Password;
import javax.security.enterprise.credential.UsernamePasswordCredential;
import javax.servlet.http.HttpSession;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import controller.AbstractController;
import exception.InvalidEmailException;
import exception.InvalidPasswordException;
import exception.PersonInaktiveException;
import httpauthenticationmechanism.ManagedPerson;
import model.person.Person;
import static javax.security.enterprise.AuthenticationStatus.SEND_FAILURE;
import static javax.security.enterprise.AuthenticationStatus.SUCCESS;
import java.io.Serializable;
import java.util.Set;
import static javax.security.enterprise.authentication.mechanism.http.AuthenticationParameters.withParams;
import model.person.enums.UserGroup;
import org.omnifaces.cdi.Param;
import static org.omnifaces.util.Faces.getRequest;
import static org.omnifaces.util.Faces.getResponse;
import static org.omnifaces.util.Faces.redirect;
/**
*
* @author Patrick
*/
@Named
@SessionScoped
public class PersonController extends AbstractController implements Serializable {
private static final long serialVersionUID = -2257766986862616262L;
final static Logger LOGGER = LogManager.getLogger(PersonController.class);
private String username;
private String password;
private boolean rememberMe = false;
@Inject
SecurityContext securityContext;
@Inject
ManagedPerson managedPerson;
@Param(name = "continue") // Defined in @LoginToContinue of SecurityFormAuthenticationMechanism
private boolean loginToContinue;
private Person activePerson;
public PersonController() {
}
public String submit() {
try {
// credential that want to be validate was UsernamePasswordCredential
UsernamePasswordCredential credential = new UsernamePasswordCredential(username, new Password(password));
// this will call our security configuration to authorize the user
AuthenticationStatus status = securityContext.authenticate(
getRequest(),
getResponse(),
withParams()
.credential(credential)
.newAuthentication(!loginToContinue)
.rememberMe(rememberMe)
);
// When logged in choose the right page by class. When more then one group
// fits then the higher order is used
if (status.equals(SUCCESS)) {
managedPerson.addLogin(username);
FacesContext facesContext = FacesContext.getCurrentInstance();
HttpSession session = (HttpSession) facesContext.getExternalContext().getSession(true);
session.setAttribute("user", this);
session.setAttribute("realUsername", username);
if (securityContext.isCallerInRole(UserGroup.ADMIN.toString())) {
LOGGER.info("Login succesfull " + username + " with role: ADMIN");
return "admin/welcome.xhtml?faces-redirect=true";
}
if (securityContext.isCallerInRole(UserGroup.USER.toString())) {
LOGGER.info("Login succesfull " + username + " with role: USER");
return "user/welcome.xhtml?faces-redirect=true";
}
if (securityContext.isCallerInRole(UserGroup.CUSTOMER.toString())) {
LOGGER.info("Login succesfull " + username + " with role: USER");
return "customer/welcome.xhtml?faces-redirect=true";
}
redirect("index.xhtml");
} else if (status.equals(SEND_FAILURE)) {
sendErrorMessage("Fehler!", "Ein Fehler ist aufgetreten. Bitte versuchen Sie es erneut!.");
return "";
}
} catch (InvalidPasswordException | InvalidEmailException e) {
LOGGER.info("Wrong Email or Password: " + username);
sendErrorMessage("Fehler!", "Falsche Email oder Passwort!");
return "";
} catch (PersonInaktiveException p){
sendErrorMessage("Fehler!", " Ihr Konto ist inatkiv. Bitte wenden Sie sich an den Administator.");
return "";
} catch (Exception e) {
LOGGER.error("Login error with " + e);
sendErrorMessage("Fehler!", "Ein Fehler ist aufgetreten, bitte versuchen Sie es erneut!");
return "";
}
return "";
}
public String logout() {
LOGGER.info("User is logging out: " + username);
try {
FacesContext.getCurrentInstance().getExternalContext().invalidateSession();
} catch (Exception e) {
LOGGER.error("couldn't logout " + username + "with:" + e);
}
return "index.xhtml?faces-redirect=true";
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public boolean isRememberMe() {
return rememberMe;
}
public void setRememberMe(boolean rememberMe) {
this.rememberMe = rememberMe;
}
public Person getActiveUser() {
return activePerson;
}
public void setActiveUser(Person activeUser) {
this.activePerson = activeUser;
}
public Set<String> getLogins() {
return managedPerson.getLogins();
}
}
@@ -0,0 +1,17 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package exception;
import javax.ejb.ApplicationException;
/**
*
* @author Patrick
*/
@ApplicationException(rollback = true)
public abstract class AbstractBusinessException extends RuntimeException {
}
@@ -0,0 +1,14 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package exception;
/**
*
* @author Patrick
*/
public class InvalidCredentialException extends AbstractBusinessException{
}
@@ -0,0 +1,13 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package exception;
/**
*
* @author Patrick
*/
public class InvalidEmailException extends AbstractBusinessException {
}
@@ -0,0 +1,14 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package exception;
/**
*
* @author Patrick
*/
public class InvalidPasswordException extends AbstractBusinessException {
}
@@ -0,0 +1,14 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package exception;
/**
*
* @author patri
*/
public class PersonInaktiveException extends AbstractBusinessException{
}
@@ -0,0 +1,78 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package httpauthenticationmechanism;
import javax.enterprise.context.ApplicationScoped;
import javax.security.enterprise.credential.CallerOnlyCredential;
import javax.security.enterprise.credential.Credential;
import javax.security.enterprise.credential.UsernamePasswordCredential;
import javax.security.enterprise.identitystore.CredentialValidationResult;
import static javax.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
import static javax.security.enterprise.identitystore.CredentialValidationResult.NOT_VALIDATED_RESULT;
import javax.security.enterprise.identitystore.IdentityStore;
import business.user.PersonManager;
import exception.InvalidCredentialException;
import exception.PersonInaktiveException;
import java.util.Set;
import java.util.stream.Collectors;
import javax.ejb.EJB;
import model.person.Person;
/**
*
* @author Patrick
*/
@ApplicationScoped
public class AppIdentityStore implements IdentityStore {
@EJB
PersonManager userManager;
@Override
public int priority() {
return 90;
}
@Override
public CredentialValidationResult validate(Credential credential) {
try {
// check if the credential was UsernamePasswordCredential
if (credential instanceof UsernamePasswordCredential) {
String username = ((UsernamePasswordCredential) credential).getCaller();
String password = ((UsernamePasswordCredential) credential).getPasswordAsString();
return validate(this.userManager.getByEmailAndPassword(username, password));
}
// check if the credential was CallerOnlyCredential
if (credential instanceof CallerOnlyCredential) {
String username = ((CallerOnlyCredential) credential).getCaller();
return validate(
this.userManager.getByEmail(username)
.orElseThrow(InvalidCredentialException::new)
);
}
} catch (InvalidCredentialException e) {
return INVALID_RESULT;
}
return NOT_VALIDATED_RESULT;
}
private CredentialValidationResult validate(Person person) {
Set<String> groups;
groups = person.getUserGroups().stream()
.map(gr -> gr.toString())
.collect(Collectors.toSet());
return new CredentialValidationResult(person.getEmail(), groups);
}
}
@@ -0,0 +1,69 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package httpauthenticationmechanism;
import static javax.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
import java.util.Optional;
import java.util.Set;
import javax.ejb.EJB;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.security.enterprise.CallerPrincipal;
import javax.security.enterprise.credential.RememberMeCredential;
import javax.security.enterprise.identitystore.CredentialValidationResult;
import javax.security.enterprise.identitystore.RememberMeIdentityStore;
import javax.servlet.http.HttpServletRequest;
import business.user.TokenManager;
import business.user.PersonManager;
import model.person.Person;
import static model.person.enums.TokenType.REMEMBER_ME;
/**
*
* @author Patrick
*/
@ApplicationScoped
public class AppRememberMeIdentityStore implements RememberMeIdentityStore {
@Inject
HttpServletRequest request;
@EJB
PersonManager userManager;
@EJB
TokenManager tokenManager;
@Override
public CredentialValidationResult validate(RememberMeCredential rmc) {
Optional<Person> user = this.userManager.getByLoginToken(rmc.getToken(), REMEMBER_ME);
if (user.isPresent()) {
return new CredentialValidationResult(user.get().getEmail());
} else {
return INVALID_RESULT;
}
}
@Override
public String generateLoginToken(CallerPrincipal cp, Set<String> set) {
return this.tokenManager.generate(cp.getName(), getRemoteAddr(request), getDescription(), REMEMBER_ME);
}
@Override
public void removeLoginToken(String string) {
this.tokenManager.remove(string);
}
private String getRemoteAddr(HttpServletRequest request){
return request.getRemoteAddr();
}
private String getDescription() {
return "Remember me session: " + this.request.getHeader("User-Agent");
}
}
@@ -0,0 +1,85 @@
package httpauthenticationmechanism;
import business.user.PersonManager;
import javax.annotation.PostConstruct;
import javax.ejb.EJB;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.inject.Named;
import javax.security.enterprise.AuthenticationStatus;
import javax.security.enterprise.authentication.mechanism.http.AutoApplySession;
import javax.security.enterprise.authentication.mechanism.http.CustomFormAuthenticationMechanismDefinition;
import javax.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism;
import javax.security.enterprise.authentication.mechanism.http.HttpMessageContext;
import javax.security.enterprise.authentication.mechanism.http.LoginToContinue;
import javax.security.enterprise.authentication.mechanism.http.RememberMe;
import javax.security.enterprise.credential.Credential;
import javax.security.enterprise.identitystore.IdentityStore;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
/**
*
* @author Patrick
*/
@AutoApplySession // For "Is user already logged-in?"
@RememberMe(
cookieMaxAgeSeconds = 60 * 60 * 24 * 14, // 14 days
cookieSecureOnly = false, // Remove this when login is served over HTTPS.
isRememberMeExpression = "#{self.isRememberMe(httpMessageContext)}"
)
@LoginToContinue(
loginPage = "/index.xhtml",
errorPage = "/error.xhtml",
useForwardToLogin = true
)
@ApplicationScoped
public class ApplicationConfig implements HttpAuthenticationMechanism{
final static Logger LOGGER = LogManager.getLogger(ApplicationConfig.class);
public ApplicationConfig() {
}
@Inject
private IdentityStore identityStore;
@Inject
private ManagedPerson managedPerson;
@EJB
private PersonManager personManager;
@PostConstruct
private void init(){
managedPerson.getLogins();
personManager.demo();
System.out.println("PostConstruct DEMO");
}
@Override
public AuthenticationStatus validateRequest(HttpServletRequest req, HttpServletResponse res, HttpMessageContext context) {
Credential credential = context.getAuthParameters().getCredential();
if (credential != null) {
return context.notifyContainerAboutLogin(this.identityStore.validate(credential));
} else {
return context.doNothing();
}
}
// this was called on @RememberMe annotations
public Boolean isRememberMe(HttpMessageContext httpMessageContext) {
return httpMessageContext.getAuthParameters().isRememberMe();
}
@Override
public void cleanSubject(HttpServletRequest request, HttpServletResponse response, HttpMessageContext httpMessageContext) {
HttpAuthenticationMechanism.super.cleanSubject(request, response, httpMessageContext);
}
}
@@ -0,0 +1,40 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package httpauthenticationmechanism;
import javax.servlet.annotation.WebListener;
import javax.servlet.http.HttpSessionEvent;
import javax.servlet.http.HttpSessionListener;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import controller.person.PersonController;
/**
*
* @author Patrick
*/
@WebListener
public class LogoutListener implements HttpSessionListener{
final static Logger LOGGER = LogManager.getLogger(LogoutListener.class);
@Override
public void sessionCreated(HttpSessionEvent event) {
// NOOP.
}
@Override
public void sessionDestroyed(HttpSessionEvent event) {
LOGGER.info("Session destroyed");
PersonController userManager = (PersonController) event.getSession().getAttribute("user");
String username = (String) event.getSession().getAttribute("realUsername");
if (userManager != null && username != null) {
LOGGER.info("not nulls");
userManager.getLogins().remove(username);
}
}
}
@@ -0,0 +1,45 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package httpauthenticationmechanism;
import java.util.HashSet;
import java.util.Set;
import javax.inject.Named;
import javax.enterprise.context.ApplicationScoped;
/**
*
* @author Patrick
*/
@Named(value = "managedPerson")
@ApplicationScoped
public class ManagedPerson {
private Set<String> logins;
/**
* Creates a new instance of ManagedCustomer
*/
public ManagedPerson() {
}
public Set<String> getLogins(){
if (this.logins == null) {
this.logins = new HashSet<>();
}
return this.logins;
}
public void addLogin(String user){
getLogins().add(user);
}
public void removeLogin(String user){
getLogins().remove(user);
}
}
@@ -0,0 +1,89 @@
package model;
import java.io.Serializable;
import java.time.LocalDateTime;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
/**
*
* @author Patrick Plate
*/
@MappedSuperclass
public class AbstractEntity implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private LocalDateTime changedDate;
private LocalDateTime creationDate;
private boolean outdated;
public AbstractEntity() {
this.creationDate = LocalDateTime.now();
this.changedDate = this.creationDate;
this.outdated = false;
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
@Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
@Override
public boolean equals(Object object) {
if (object == null) {
return false;
}
if (!(object.getClass() == this.getClass())) {
return false;
}
AbstractEntity other = (AbstractEntity) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
public boolean isOutdated() {
return outdated;
}
public void setOutdated(boolean outdated) {
this.outdated = outdated;
}
public LocalDateTime getChangedDate() {
return changedDate;
}
public void setChangedDate(LocalDateTime changedDate) {
this.changedDate = changedDate;
}
public void setCreationDate (LocalDateTime creationDate) {
this.creationDate = creationDate;
}
public LocalDateTime getCreationDate() {
return creationDate;
}
}
@@ -0,0 +1,86 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package model.person;
import java.util.Arrays;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.OneToOne;
import model.AbstractEntity;
/**
*
* @author Patrick
*/
@Entity
@NamedQueries({
@NamedQuery(name = "Password.findByPassword",
query = "SELECT p FROM Password p WHERE p.password = :password"),
@NamedQuery(name = "Password.findByCostumerID",
query = "SELECT p FROM Password p WHERE p.person = :person")
})
public class Password extends AbstractEntity {
private static final long serialVersionUID = -1924150926160449302L;
@OneToOne
private Person person;
@Column(name="password")
private byte[] password;
public Password() {
}
public Password (Person person, byte[] password) {
this.person = person;
this.password = password;
}
public Person getPerson() {
return person;
}
public byte[] getPassword() {
return password;
}
public void setPassowrd(byte[] password){
this.password = password;
}
@Override
public int hashCode() {
int hash = 3;
hash = 79 * hash + Objects.hashCode(this.person);
hash = 79 * hash + Arrays.hashCode(this.password);
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Password other = (Password) obj;
if (!Objects.equals(this.person, other.person)) {
return false;
}
if (!Arrays.equals(this.password, other.password)) {
return false;
}
return true;
}
}
@@ -0,0 +1,215 @@
package model.person;
import model.person.enums.Call;
import java.io.Serializable;
import java.util.AbstractSet;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.*;
import model.AbstractEntity;
import model.person.enums.UserGroup;
/**
* Entity implementation class for Entity: User
*
*/
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@NamedQueries({
@NamedQuery(name = Person.FIND_BY_EMAIL,
query = "SELECT p FROM Person p WHERE p.email = :email"),
@NamedQuery(name = Person.FIND_BY_ID,
query = "SELECT p FROM Person p WHERE p.id = :id"),
@NamedQuery(name = Person.FIND_BY_TOKEN,
query = "SELECT p FROM Person p inner join p.tokens t where t.tokenHash = :tokenHash and t.tokenType = :tokenType and t.expiration > :timestamp")
})
public class Person extends AbstractEntity implements Serializable {
public static final String FIND_BY_EMAIL = "Person.findByEmail";
public static final String FIND_BY_ID = "Person.findByID";
public static final String FIND_BY_TOKEN = "Person.findByToken";
private static final long serialVersionUID = 1L;
@Column(name = "Email", unique = true, nullable = false)
private String email;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "Password_ID", nullable = false, unique = true)
private Password password;
@OneToOne(cascade = CascadeType.ALL)
@JoinColumn(name = "Salt_ID", nullable = false, unique = true)
private Salt salt;
@Enumerated(EnumType.STRING)
@Column(name = "Anrede")
private Call call;
@OneToMany(mappedBy = "person", orphanRemoval = true, cascade = CascadeType.ALL)
private List<Token> tokens;
@ElementCollection(targetClass = UserGroup.class)
@Enumerated(EnumType.STRING)
@JoinTable(name = "UserGroups", joinColumns = @JoinColumn(name = "UserID"))
@Column(name = "UserGroups", nullable = false)
private Set<UserGroup> userGroups;
@Column(nullable = true)
private String telefon;
@Column(nullable = true)
private String mobile;
@Column(nullable = true)
private String fax;
@Column(nullable = false)
private String firstname;
@Column(nullable = true)
private String lastname;
@Column(nullable = true)
private String title;
@Column(nullable = true)
private boolean active;
public Person() {
super();
active = false;
}
public Person(String email, byte[] password, Salt salt, Set<UserGroup> usergroups) {
this.email = email;
this.tokens = new ArrayList<>();
this.salt = salt;
this.password = new Password(this, password);
this.userGroups = usergroups;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Password getPassword() {
return password;
}
public void setPassword(Password password) {
this.password = password;
}
public Salt getSalt() {
return salt;
}
public void setSalt(Salt salt) {
this.salt = salt;
}
public Call getCall() {
return call;
}
public void setCall(Call call) {
this.call = call;
}
public String getTelefon() {
return telefon;
}
public void setTelefon(String telefon) {
this.telefon = telefon;
}
public String getMobile() {
return mobile;
}
public void setMobile(String mobile) {
this.mobile = mobile;
}
public String getFax() {
return fax;
}
public void setFax(String fax) {
this.fax = fax;
}
public String getFirstname() {
return firstname;
}
public void setFirstname(String firstname) {
this.firstname = firstname;
}
public String getLastname() {
return lastname;
}
public void setLastname(String lastname) {
this.lastname = lastname;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public List<Token> getTokens() {
return tokens;
}
public void setTokens(List<Token> tokens) {
this.tokens = tokens;
}
public void addToken(Token token) {
if (this.tokens == null) {
tokens = new ArrayList<>();
}
tokens.add(token);
}
public Set<UserGroup> getUserGroups() {
return userGroups;
}
public void setUserGroups(Set<UserGroup> userGroups) {
this.userGroups = userGroups;
}
public void addGroup(UserGroup userGroup){
if (this.userGroups == null) {
this.userGroups = new HashSet<>();
}
this.userGroups.add(userGroup);
}
public boolean isActive() {
return active;
}
public void setActive(boolean active) {
this.active = active;
}
}
@@ -0,0 +1,79 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package model.person;
//import java.nio.charset.Charset;
import java.security.SecureRandom;
import java.util.Arrays;
import javax.persistence.Entity;
import model.AbstractEntity;
/**
*
* @author Patrick
*/
@Entity
public class Salt extends AbstractEntity {
//private final Charset UTF8_CHARSET = Charset.forName("UTF-8");
private static final long serialVersionUID = -1068077226987746862L;
private byte[] salt;
private int interations;
public Salt() {
interations = 3072;
generateSalt();
}
private void generateSalt() {
SecureRandom random = new SecureRandom();
byte bytes[] = new byte[64];
random.nextBytes(bytes);
salt = bytes;
}
public java.lang.String bytetoString(byte[] input) {
return Arrays.toString(input);
}
public byte[] getSalt() {
return salt;
}
public int getInterations() {
return interations;
}
@Override
public int hashCode() {
int hash = 7;
hash = 79 * hash + Arrays.hashCode(this.salt);
hash = 79 * hash + this.interations;
return hash;
}
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
final Salt other = (Salt) obj;
if (this.interations != other.interations) {
return false;
}
if (!Arrays.equals(this.salt, other.salt)) {
return false;
}
return true;
}
}
@@ -0,0 +1,153 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package model.person;
import model.person.enums.TokenType;
import static java.time.temporal.ChronoUnit.MONTHS;
import java.time.Instant;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.PrePersist;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import model.AbstractEntity;
/**
*
* @author Patrick
*/
@Entity
@Table(name = "token", uniqueConstraints = {
@UniqueConstraint(columnNames = {"token_hash"})
})
@NamedQueries({
@NamedQuery(name = Token.REMOVE_TOKEN, query = "DELETE FROM Token t where t.tokenHash = :tokenHash"),
@NamedQuery(name = Token.REMOVE_EXPIRED_TOKEN, query = "DELETE FROM Token t where t.expiration < :timestamp")
})
public class Token extends AbstractEntity {
private static final long serialVersionUID = -6632692800064453512L;
public static final String REMOVE_TOKEN = "Token.removeToken";
public static final String REMOVE_EXPIRED_TOKEN = "Token.removeExpiredToken";
@Column(name = "token_hash")
private String tokenHash;
@Column(name = "token_type")
@Enumerated(EnumType.STRING)
private TokenType tokenType;
@Column(name = "ip_address", length = 45)
private String ipAddress;
private String description;
private Instant created;
private Instant expiration;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "account_id")
private Person person;
public Token() {
}
@PrePersist
public void generateInformation() {
this.created = Instant.now();
if (this.expiration == null) {
this.expiration = this.created.plus(1, MONTHS);
}
}
public String getTokenHash() {
return tokenHash;
}
public void setTokenHash(String tokenHash) {
this.tokenHash = tokenHash;
}
public TokenType getTokenType() {
return tokenType;
}
public void setTokenType(TokenType tokenType) {
this.tokenType = tokenType;
}
public String getIpAddress() {
return ipAddress;
}
public void setIpAddress(String ipAddress) {
this.ipAddress = ipAddress;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Instant getCreated() {
return created;
}
public void setCreated(Instant created) {
this.created = created;
}
public Instant getExpiration() {
return expiration;
}
public void setExpiration(Instant expiration) {
this.expiration = expiration;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
/* don't depend on natural identifier for equality checks, see: https://vladmihalcea.com/2017/03/29/the-best-way-to-map-a-onetomany-association-with-jpa-and-hibernate/#more-7143 */
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
Token token = (Token) obj;
return Objects.equals(getId(), token.getId());
}
@Override
public int hashCode() {
return Objects.hash(getId());
}
@Override
public String toString() {
return "Token{ id " + getId() + '}';
}
}
@@ -0,0 +1,56 @@
package model.person.enums;
import java.util.Locale;
public enum Call {
HERR(0), FRAU(1), DIVERS(2), UNBESTIMMT(3);
private final int type;
private Locale locale = null;
private Call(int type) {
this.type = type;
}
public Locale getLocale() {
return locale;
}
public void setLocale(Locale locale) {
this.locale = locale;
}
public int getType() {
return type;
}
@Override
public String toString() {
if (locale == null || Locale.GERMAN.equals(locale)) {
switch (this) {
case HERR:
return "Herr";
case FRAU:
return "Frau";
case DIVERS:
return "Divers";
case UNBESTIMMT:
return "Unbestimmt";
}
}
if (locale.equals(Locale.ENGLISH)) {
switch (this) {
case HERR:
return "Mr";
case FRAU:
return "Mrs";
case DIVERS:
return "divers";
case UNBESTIMMT:
return "unknown";
}
}
return super.toString();
}
}
@@ -0,0 +1,17 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package model.person.enums;
/**
*
* @author Patrick
*/
public enum TokenType {
REMEMBER_ME,
API,
RESET_PASSWORD,
FILE
}
@@ -0,0 +1,16 @@
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
package model.person.enums;
/**
*
* @author patri
*/
public enum UserGroup {
USER,
ADMIN,
CUSTOMER;
}
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2" xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd">
<!-- Define Persistence Unit -->
<persistence-unit name="pu_person">
<jta-data-source>java:/mss-failsave</jta-data-source>
<class>model.person.Token</class>
<class>model.person.Salt</class>
<class>model.person.Person</class>
<class>model.person.Password</class>
<properties>
<property name="javax.persistence.schema-generation.database.action" value="drop-and-create"/>
</properties>
</persistence-unit>
</persistence>