diff --git a/account/pom.xml b/account/pom.xml
new file mode 100644
index 0000000..84f43f2
--- /dev/null
+++ b/account/pom.xml
@@ -0,0 +1,94 @@
+
+
+ 4.0.0
+
+ de.muehlencord.shared
+ shared-account
+ 0.1-SNAPSHOT
+ ejb
+
+ shared-account
+
+
+ ${project.build.directory}/endorsed
+ UTF-8
+
+
+
+
+ org.apache.shiro
+ shiro-core
+ 1.2.4
+
+
+ commons-lang
+ commons-lang
+ 2.6
+
+
+ log4j
+ log4j
+ 1.2.17
+
+
+ org.freemarker
+ freemarker
+ 2.3.23
+
+
+
+ javax
+ javaee-api
+ 7.0
+ provided
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.1
+
+ 1.8
+ 1.8
+
+
+
+ org.apache.maven.plugins
+ maven-ejb-plugin
+ 2.5.1
+
+ 3.1
+
+
+
+ org.apache.maven.plugins
+ maven-dependency-plugin
+ 2.10
+
+
+ validate
+
+ copy
+
+
+ ${endorsed.dir}
+ true
+
+
+ javax
+ javaee-endorsed-api
+ 7.0
+ jar
+
+
+
+
+
+
+
+
+
+
diff --git a/account/sql/01_config.sql b/account/sql/01_config.sql
new file mode 100644
index 0000000..d0b6ce6
--- /dev/null
+++ b/account/sql/01_config.sql
@@ -0,0 +1,8 @@
+DROP TABLE config;
+
+CREATE TABLE config (
+ config_key varchar(100),
+ config_value varchar(200),
+ CONSTRAINT config_pk PRIMARY KEY (config_key)
+);
+INSERT INTO config (config_key, config_value) VALUES ('account.maxFailedLogins', '5');
\ No newline at end of file
diff --git a/account/sql/02_accounts.sql b/account/sql/02_accounts.sql
new file mode 100644
index 0000000..67f2931
--- /dev/null
+++ b/account/sql/02_accounts.sql
@@ -0,0 +1,83 @@
+/**
+ * Author: joern.muehlencord
+ * Created: 06.09.2015
+ */
+
+DROP TABLE account_role;
+DROP TABLE account_history;
+DROP TABLE account;
+DROP TABLE role_permission;
+DROP TABLE application_role;
+DROP TABLE application_permission;
+
+
+CREATE TABLE application_role (
+ role_name varchar(80) NOT NULL,
+ role_description varchar(200) NOT NULL,
+ CONSTRAINT pk_application_role_pk PRIMARY KEY (role_name)
+);
+
+CREATE TABLE account (
+ username varchar(32) NOT NULL,
+ emailaddress varchar(200) NOT NULL,
+ firstname varchar(100) NOT NULL,
+ lastname varchar(100) NOT NULL,
+ account_password char(200) NOT NULL,
+ last_login timestamp with time zone,
+ last_failed_login timestamp with time zone,
+ failure_count int NOT NULL DEFAULT 0,
+ status varchar(10) NOT NULL DEFAULT 'NEW', -- NEW, INIT, OK, BLOCKED,
+ password_reset_ongoing boolean NOT NULL DEFAULT false,
+ password_reset_valid_to timestamp with time zone,
+ password_reset_hash char(200),
+ created_on timestamp with time zone NOT NULL DEFAULT (now() at time zone 'utc'),
+ created_by varchar(32) NOT NULL,
+ last_updated_on timestamp with time zone NOT NULL DEFAULT (now() at time zone 'utc'),
+ last_updated_by varchar(32) NOT NULL,
+ CONSTRAINT pk_account PRIMARY KEY (username)
+);
+
+CREATE TABLE account_history (
+ id SERIAL NOT NULL,
+ username varchar(32) NOT NULL,
+ message varchar(200),
+ failure_count int NOT NULL DEFAULT 0,
+ status varchar(20) NOT NULL, -- constants needed, after action - new, init, active, blocked, inactive, marked for deletion
+ last_updated_on timestamp with time zone NOT NULL DEFAULT (now() at time zone 'utc'),
+ last_updated_by varchar(32) NOT NULL,
+ CONSTRAINT pk_account_history PRIMARY KEY (id),
+ CONSTRAINT fk_account_history_username_fk FOREIGN KEY (username) REFERENCES account (username)
+);
+
+CREATE TABLE account_role (
+ username varchar(32) NOT NULL,
+ role_name varchar(80) NOT NULL,
+ CONSTRAINT pk_account_role PRIMARY KEY (username, role_name),
+ CONSTRAINT fk_account_role_account FOREIGN KEY (username) REFERENCES account(username),
+ CONSTRAINT fk_account_role_role_name FOREIGN KEY (role_name) REFERENCES application_role(role_name)
+);
+
+
+CREATE TABLE application_permission (
+ permission_name varchar(80) NOT NULL,
+ permission_description varchar(200) NOT NULL,
+ CONSTRAINT application_permission_pk PRIMARY KEY (permission_name)
+);
+
+CREATE TABLE role_permission (
+ role_name varchar(80) NOT NULL,
+ permission_name varchar(80) NOT NULL,
+ CONSTRAINT pk_role_permission_role_permission_name PRIMARY KEY (role_name, permission_name),
+ CONSTRAINT fk_role_permission_role_name FOREIGN KEY (role_name) REFERENCES application_role(role_name),
+ CONSTRAINT fk_role_permission_permission_name FOREIGN KEY (permission_name) REFERENCES application_permission(permission_name)
+);
+
+INSERT INTO application_permission (permission_name, permission_description) values ('test:view', 'Display test view');
+
+INSERT INTO application_role (role_name, role_description) values ('Admin', 'Admin role');
+INSERT INTO application_role (role_name, role_description) values ('User', 'Standard user role');
+
+-- INSERT INTO role_permission (role_name, permission_name) values ('Admin','test:view');
+
+INSERT INTO account (username, emailaddress, firstname, lastname, account_password, created_by, last_updated_by) values('admin', 'joern@muehlencord.de', 'Joern', 'Muehlencord','$shiro1$SHA-256$500000$4bHPNH9k539UjdFLgm/HOA==$T/n8skgoGSOtNw/c9ScDlXCiGrx2cZF0Esrvf6WPq6g=', 'admin','admin'); --admin/secret
+INSERT INTO account_role (username, role_name) values ('admin', 'Admin');
\ No newline at end of file
diff --git a/account/sql/03_templates.sql b/account/sql/03_templates.sql
new file mode 100644
index 0000000..140e6c0
--- /dev/null
+++ b/account/sql/03_templates.sql
@@ -0,0 +1,21 @@
+DROP TABLE mail_template;
+
+CREATE TABLE mail_template (
+ template_name varchar(40) NOT NULL,
+ template_value text NOT NULL,
+ CONSTRAINT mail_template_pk PRIMARY KEY (template_name)
+);
+
+
+INSERT INTO mail_template (template_name, template_value) VALUES('password_reset_html',
+'<#ftl strip_whitespace = true>
+
+
+
+ Dear ${account.firstname},
+
+ you requested to reset your password at ${parameter.url}. Please open the following URL to proceed.
+ ${parameter.resetUrl}
+
+
+ ');
diff --git a/account/sql/create_tables.sql b/account/sql/create_tables.sql
new file mode 100644
index 0000000..0b75621
--- /dev/null
+++ b/account/sql/create_tables.sql
@@ -0,0 +1,2 @@
+\i 01_accounts.sql
+\i 02_templates.sql
\ No newline at end of file
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/ConfigService.java b/account/src/main/java/de/muehlencord/shared/account/business/ConfigService.java
new file mode 100644
index 0000000..bbe5f72
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/business/ConfigService.java
@@ -0,0 +1,45 @@
+package de.muehlencord.shared.account.business;
+
+import de.muehlencord.shared.account.entity.ConfigEntity;
+import javax.annotation.PostConstruct;
+import javax.ejb.Singleton;
+import javax.ejb.Startup;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+@Singleton
+@Startup
+public class ConfigService {
+
+ @PersistenceContext
+ EntityManager em;
+
+ private String storagePath = null;
+ private int maxFailedLogins = 5;
+
+ @PostConstruct
+ public void init() {
+ ConfigEntity configEntity = em.find(ConfigEntity.class, "storage.path");
+ if (configEntity != null) {
+ this.storagePath = configEntity.getConfigValue();
+ }
+ configEntity = em.find(ConfigEntity.class, "account.maxFailedLogins");
+ if (configEntity != null) {
+ this.maxFailedLogins = Integer.parseInt(configEntity.getConfigValue());
+ }
+ }
+
+ /* *** getter *** */
+ public String getStoragePath() {
+ return storagePath;
+ }
+
+ public int getMaxFailedLogins() {
+ return maxFailedLogins;
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/account/AccountControl.java b/account/src/main/java/de/muehlencord/shared/account/business/account/AccountControl.java
new file mode 100644
index 0000000..6640d6c
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/business/account/AccountControl.java
@@ -0,0 +1,278 @@
+package de.muehlencord.shared.account.business.account;
+
+import de.muehlencord.shared.account.business.ConfigService;
+import de.muehlencord.shared.account.business.mail.MailService;
+import de.muehlencord.shared.account.business.mail.MailTemplateException;
+import de.muehlencord.shared.account.entity.AccountEntity;
+import de.muehlencord.shared.account.entity.RoleEntity;
+import de.muehlencord.shared.account.util.SecurityUtil;
+import freemarker.template.TemplateException;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+import javax.ejb.EJB;
+import javax.ejb.Stateless;
+import javax.mail.MessagingException;
+import javax.persistence.EntityManager;
+import javax.persistence.NoResultException;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import javax.transaction.Transactional;
+import org.apache.commons.lang.RandomStringUtils;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.subject.Subject;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+@Stateless
+public class AccountControl {
+
+ private static final Logger LOGGER = Logger.getLogger(AccountControl.class.getName());
+
+ @EJB
+ private ConfigService configService;
+
+ @EJB
+ private MailService mailService;
+
+ @PersistenceContext
+ EntityManager em;
+
+ public List getAccounts() {
+ Query query = em.createQuery("SELECT a FROM AccountEntity a WHERE a.status <> :status", AccountEntity.class);
+ query.setParameter("status", "DELETED"); // TODO add status enum
+ return query.getResultList();
+ }
+
+ public AccountEntity getAccountEntity(String userName, boolean loadRoles) {
+ StringBuilder queryBuilder = new StringBuilder();
+ queryBuilder.append("SELECT a FROM AccountEntity a ");
+ if (loadRoles) {
+ queryBuilder.append("JOIN FETCH a.roleEntityList ");
+ }
+ queryBuilder.append("WHERE a.username = :username");
+ Query query = em.createQuery(queryBuilder.toString());
+ query.setParameter("username", userName);
+ try {
+ return (AccountEntity) query.getSingleResult();
+ } catch (NoResultException ex) {
+ return null;
+ }
+ }
+
+ @Transactional
+ // TODO add role names from application because only application can now how its roles are named
+ public AccountEntity saveAccount(AccountEntity account, boolean isAdmin) {
+ Date now = new Date(); // Todo now in UTC
+ Subject currentUser = SecurityUtils.getSubject();
+ String currentLoggedInUser = currentUser.getPrincipal().toString();
+
+ account.setLastUpdatedBy(currentLoggedInUser);
+ account.setLastUpdatedOn(now);
+
+ boolean newAccount = (account.getCreatedOn() == null);
+
+ // new account
+ if (newAccount) {
+ account.setCreatedOn(now);
+ account.setCreatedBy(currentLoggedInUser);
+
+ // set default random password, user has to get password via lost passwort option afterwards
+ String randomPassword = RandomStringUtils.random(20, true, true);
+ String hashedPassword = SecurityUtil.createPassword(randomPassword);
+ account.setAccountPassword(hashedPassword);
+ em.persist(account);
+ } else {
+ em.merge(account);
+
+ // reload account from db and join roles
+ account = getAccountEntity(account.getUsername(), true);
+ }
+
+ // load Admin or User role from database
+ String roleName = (isAdmin ? "Admin" : "User");
+ Query roleQuery = em.createNamedQuery("RoleEntity.findByRoleName");
+ roleQuery.setParameter("roleName", roleName);
+ RoleEntity role = (RoleEntity) roleQuery.getSingleResult();
+
+ if (role != null) {
+ // add new user add required role
+ // do not request based on newUser variable; this way existing users with missing role (for whatever reason)
+ // will be fixed automatically
+ if (account.getRoleEntityList() == null || account.getRoleEntityList().isEmpty()) {
+ account.setRoleEntityList(new ArrayList<>());
+ account.getRoleEntityList().add(role);
+ em.merge(account);
+ LOGGER.log(Level.INFO, "Added role " + roleName + " to user " + account.getUsername());
+
+ } else if (!account.getRoleEntityList().get(0).equals(role)) {
+ // change role from User to Admin and vice versa
+ // user already exists, has existing role
+ // check if existing role is different from current role and change it
+ // be carefull: this only works as long as a user has exactly one role!
+ // he is either User or Admin
+ // TODO add "UserRole" to every user, make this default Role configurable
+ // TODO add AdminRole in addtion if needed
+ account.getRoleEntityList().remove(0);
+ account.getRoleEntityList().add(role);
+ em.merge(account);
+ LOGGER.log(Level.INFO, "Switched role of user " + account.getUsername() + " to " + roleName);
+
+ }
+ }
+
+ return account;
+ }
+
+ public void deleteAccount(AccountEntity account) throws AccountException {
+ Date now = new Date(); // Todo now in UTC
+ Subject currentUser = SecurityUtils.getSubject();
+ String currentUserName = currentUser.getPrincipal().toString();
+
+ if (account.getUsername().equals(currentUserName)) {
+ throw new AccountException ("Cannot delete own account");
+ } else {
+ account.setStatus("DELETED"); // TODO add enum
+ account.setLastUpdatedBy(currentUserName);
+ account.setLastUpdatedOn(now);
+ em.merge(account);
+ }
+
+ }
+
+ public boolean initPasswordReset(String userName) {
+ try {
+ AccountEntity account = getAccountEntity(userName, false);
+ if (account == null) {
+ LOGGER.log(Level.WARN, "Account with name " + userName + " not found");
+ return false;
+ }
+
+ if (account.getStatus().equals("LOCKED")) { // TODO add enumType
+ LOGGER.log(Level.WARN, "Account " + userName + " is locked, cannot initialize password reset");
+ return false;
+ }
+
+ String randomString = RandomStringUtils.random(40, true, true);
+
+ Date validTo = new Date(); // TODO now in UTC
+ validTo = new Date(validTo.getTime() + 1000 * 600); // 10 minutes to react
+
+ account.setPasswordResetHash(randomString);
+ account.setPasswordResetOngoing(true);
+ account.setPasswordResetValidTo(validTo);
+
+ mailService.sendPasswortResetStartEmail(account, randomString);
+
+ em.merge(account);
+ return true;
+ } catch (MessagingException | MailTemplateException | URISyntaxException | IOException | TemplateException ex) {
+ LOGGER.log(Level.ERROR, "Error while sending password reset mail. " + ex.toString());
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.log(Level.DEBUG, "Error while sending password reset mail.", ex);
+ }
+ return false;
+ }
+ }
+
+ public boolean resetPassword(String userName, String newPassword, String resetPasswordToken) {
+ AccountEntity account = getAccountEntity(userName, false);
+
+ if (account == null) {
+ LOGGER.log(Level.WARN, "Error while resetting password, no account with username " + userName + " found");
+ // TODO add extra logging for intrusion protection system like fail2ban
+ return false;
+ }
+
+ if (account.getPasswordResetOngoing() && (account.getPasswordResetHash() != null) && (account.getPasswordResetValidTo() != null)) {
+ Date now = new Date(); // TODO now in UTC
+ String storedHash = account.getPasswordResetHash().trim();
+ if (account.getPasswordResetValidTo().after(now)) {
+ if (storedHash.equals(resetPasswordToken)) {
+ // everything ok, reset password
+ executePasswordReset(account, newPassword);
+ LOGGER.log(Level.INFO, "Updated password for user " + userName);
+ return true;
+ } else {
+ // token is not valid, refuse to change password
+ LOGGER.log(Level.WARN, "Trying to reset password for user " + userName + " but wrong token " + resetPasswordToken + " provided");
+ addLoginError(account);
+ return false;
+ }
+ } else {
+ // password reset token no longer valid
+ LOGGER.log(Level.WARN, "Trying to reset password for user " + userName + " but token is no longer valid");
+ addLoginError(account);
+ return false;
+ }
+ } else {
+ // user is not is password reset mode
+ LOGGER.log(Level.WARN, "Trying to reset password for user " + userName + " but password reset was not requested");
+ addLoginError(account);
+ return false;
+ }
+ }
+
+ private void executePasswordReset(AccountEntity account, String newPassword) {
+ Date now = new Date(); // TODO now in UTC
+
+ String hashedPassword = SecurityUtil.createPassword(newPassword);
+ account.setAccountPassword(hashedPassword);
+
+ account.setPasswordResetOngoing(false);
+ account.setPasswordResetHash(null);
+ account.setPasswordResetValidTo(null);
+
+ account.setLastUpdatedBy(account.getUsername());
+ account.setLastUpdatedOn(now);
+ em.merge(account);
+
+ }
+
+ public void updateLogin(AccountEntity account) {
+ Date now = new Date(); // TODO now in UTC
+ // a scucessful login ends a password reset procedure
+ if (account.getPasswordResetOngoing()) {
+ account.setPasswordResetOngoing(false);
+ account.setPasswordResetHash(null);
+ account.setPasswordResetValidTo(null);
+ account.setLastUpdatedOn(now);
+ account.setLastUpdatedBy(account.getUsername());
+ }
+
+ account.setLastLogin(now);
+ account.setFailureCount(0);
+ account.setStatus("OK"); // TODO add statusEnum
+
+ em.merge(account);
+ }
+
+ public void addLoginError(AccountEntity account) {
+ Date now = new Date(); // TODO now in UTC
+ account.setLastFailedLogin(now);
+ account.setFailureCount(account.getFailureCount() + 1);
+
+ int maxFailedLogins = configService.getMaxFailedLogins();
+ if ((account.getFailureCount() >= maxFailedLogins) && (!account.getStatus().equals("LOCKED"))) { // TOD add status enum
+ // max failed logins reached, disabling user
+ LOGGER.log(Level.INFO, "Locking account " + account.getUsername() + " due to " + account.getFailureCount() + " failed logins");
+ account.setStatus("LOCKED"); // TODO add enum
+ }
+
+ // on a failed login request, disable password reset
+ account.setPasswordResetOngoing(false);
+ account.setPasswordResetHash(null);
+ account.setPasswordResetValidTo(null);
+
+ account.setLastUpdatedBy("system");
+ account.setLastUpdatedOn(now);
+ em.merge(account);
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/account/AccountException.java b/account/src/main/java/de/muehlencord/shared/account/business/account/AccountException.java
new file mode 100644
index 0000000..04cc492
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/business/account/AccountException.java
@@ -0,0 +1,25 @@
+package de.muehlencord.shared.account.business.account;
+
+/**
+ *
+ * @author Raimund
+ */
+public class AccountException extends Exception {
+
+ /**
+ * Creates a new instance of AccountException without detail
+ * message.
+ */
+ public AccountException() {
+ }
+
+ /**
+ * Constructs an instance of AccountException with the
+ * specified detail message.
+ *
+ * @param msg the detail message.
+ */
+ public AccountException(String msg) {
+ super(msg);
+ }
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/mail/MailDatamodel.java b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailDatamodel.java
new file mode 100644
index 0000000..77042c3
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailDatamodel.java
@@ -0,0 +1,35 @@
+package de.muehlencord.shared.account.business.mail;
+
+import de.muehlencord.shared.account.entity.AccountEntity;
+import java.util.HashMap;
+import java.util.Map;
+
+/**
+ *
+ * @author jomu
+ */
+public class MailDatamodel {
+
+ private final AccountEntity account;
+ private final Map parameter;
+
+ public MailDatamodel(AccountEntity account) {
+ this.parameter = new HashMap<>();
+ this.account = account;
+ }
+
+ public void addParameter(String name, String value) {
+ this.parameter.put(name, value);
+ }
+
+
+ /* **** getter / setter **** */
+
+ public AccountEntity getAccount() {
+ return account;
+ }
+
+ public Map getParameter() {
+ return parameter;
+ }
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/mail/MailException.java b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailException.java
new file mode 100644
index 0000000..6471740
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailException.java
@@ -0,0 +1,36 @@
+package de.muehlencord.shared.account.business.mail;
+
+/**
+ *
+ * @author Raimund
+ */
+public class MailException extends Exception {
+
+ /**
+ * Creates a new instance of MailException without detail
+ * message.
+ */
+ public MailException() {
+ }
+
+ /**
+ * Constructs an instance of MailException with the specified
+ * detail message.
+ *
+ * @param msg the detail message.
+ */
+ public MailException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an instance of MailException with the specified
+ * detail message.
+ *
+ * @param msg the detail message.
+ * @param th the root cause
+ */
+ public MailException(String msg, Throwable th) {
+ super(msg, th);
+ }
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/mail/MailService.java b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailService.java
new file mode 100644
index 0000000..88283a1
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailService.java
@@ -0,0 +1,104 @@
+package de.muehlencord.shared.account.business.mail;
+
+import de.muehlencord.shared.account.entity.AccountEntity;
+import freemarker.template.TemplateException;
+import java.io.IOException;
+import java.net.URISyntaxException;
+import java.net.URL;
+import java.util.Date;
+import javax.annotation.Resource;
+import javax.ejb.EJB;
+import javax.ejb.Stateless;
+import javax.faces.application.FacesMessage;
+import javax.faces.context.ExternalContext;
+import javax.faces.context.FacesContext;
+import javax.mail.BodyPart;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Multipart;
+import javax.mail.Session;
+import javax.mail.Transport;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+@Stateless
+public class MailService {
+
+ @EJB
+ private MailTemplateService mailTemplateService;
+
+ @Resource(lookup = "java:jboss/mail/ssgMail")
+ private Session mailSession;
+
+ public void sendTestEmail(String recipient) throws MailException {
+ sendMail(recipient, "Test email", "This is a test email");
+ }
+
+ public void sendTestHtmlEmail(String recipient) {
+ Date now = new Date();
+ AccountEntity account = new AccountEntity("joern.muehlencord", "joern@muehlencord.de", "Jörn", "Mühlencord", "secret", 0, "NEW", now, "admin", now, "admin");
+ MailDatamodel dataModel = new MailDatamodel(account);
+ dataModel.addParameter("url", "http://url.de");
+ dataModel.addParameter("resetUrl", "http://reseturl.de");
+ sendHTMLMail(recipient, "Test HTML Email", dataModel, "password_reset");
+ }
+
+ public void sendMail(String recipient, String subject, String body) throws MailException {
+ try {
+ MimeMessage message = new MimeMessage(mailSession);
+ message.setSubject(subject);
+ message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipient, false));
+ message.setText(body);
+ Transport.send(message);
+ } catch (MessagingException ex) {
+ throw new MailException ("Error while sending email.", ex);
+ }
+ }
+
+ public void sendHTMLMail(String recipient, String subject, MailDatamodel dataModel, String templateName) {
+ try {
+ Message message = new MimeMessage(mailSession);
+ message.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipient, false));
+ message.setSubject(subject);
+
+ String body = mailTemplateService.getStringFromTemplate("password_reset_html", dataModel);
+
+ BodyPart bodyPart = new MimeBodyPart();
+ bodyPart.setContent(body, "text/html");
+
+ Multipart multipart = new MimeMultipart();
+ multipart.addBodyPart(bodyPart);
+ message.setContent(multipart, "text/html; charset=UTF-8");
+
+ Transport.send(message);
+ } catch (MessagingException | MailTemplateException ex) {
+ FacesMessage message = new FacesMessage(FacesMessage.SEVERITY_ERROR, "Error while sending email", "Exception: " + ex.toString());
+ FacesContext context = FacesContext.getCurrentInstance();
+ context.addMessage(null, message);
+ }
+
+ }
+
+ public void sendPasswortResetStartEmail(AccountEntity account, String token) throws MessagingException, URISyntaxException, IOException, TemplateException, MailTemplateException {
+ MailDatamodel model = new MailDatamodel(account);
+
+ // String absoluteWebPath = FacesContext.getCurrentInstance().getExternalContext().getApplicationContextPath();
+ ExternalContext externalContext = FacesContext.getCurrentInstance().getExternalContext();
+ String resetPage = "/login.xhtml?token=" + token;
+ URL baseUrl = new URL(externalContext.getRequestScheme(),
+ externalContext.getRequestServerName(),
+ externalContext.getRequestServerPort(),
+ externalContext.getRequestContextPath());
+
+ model.addParameter("url", baseUrl.toString());
+ model.addParameter("resetUrl", baseUrl.toString() + resetPage);
+
+ sendHTMLMail(account.getEmailaddress(), "Reset your password", model, "password_reset");
+ }
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/mail/MailServiceLocator.java b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailServiceLocator.java
new file mode 100644
index 0000000..b620ddf
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailServiceLocator.java
@@ -0,0 +1,138 @@
+package de.muehlencord.shared.account.business.mail;
+
+import java.net.URL;
+
+import javax.ejb.EJBHome;
+import javax.ejb.EJBLocalHome;
+import javax.jms.ConnectionFactory;
+import javax.jms.Destination;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.rmi.PortableRemoteObject;
+import javax.sql.DataSource;
+import javax.mail.Session;
+
+/**
+ *
+ * @author Raimund
+ */
+public class MailServiceLocator {
+
+ private InitialContext ic;
+
+ public MailServiceLocator() {
+ try {
+ ic = new InitialContext();
+ } catch (NamingException ne) {
+ throw new RuntimeException(ne);
+ }
+ }
+
+ private Object lookup(String jndiName) throws NamingException {
+ return ic.lookup(jndiName);
+ }
+
+ /**
+ * Will get the ejb Local home factory. Clients need to cast to the type of
+ * EJBHome they desire.
+ *
+ * @param jndiHomeName jndi home name matching the requested local home
+ * @return the Local EJB Home corresponding to the homeName
+ * @throws NamingException if the lookup fails
+ */
+ public EJBLocalHome getLocalHome(String jndiHomeName) throws NamingException {
+ return (EJBLocalHome) lookup(jndiHomeName);
+ }
+
+ /**
+ * Will get the ejb Remote home factory. Clients need to cast to the type of
+ * EJBHome they desire.
+ *
+ * @param jndiHomeName jndi home name matching the requested remote home
+ * @param className desired type of the object
+ * @return the EJB Home corresponding to the homeName
+ * @throws NamingException if the lookup fails
+ */
+ public EJBHome getRemoteHome(String jndiHomeName, Class className) throws NamingException {
+ Object objref = lookup(jndiHomeName);
+ return (EJBHome) PortableRemoteObject.narrow(objref, className);
+ }
+
+ /**
+ * This method helps in obtaining the JMS connection factory.
+ *
+ * @param connFactoryName name of the connection factory
+ * @return the factory for obtaining JMS connection
+ * @throws NamingException if the lookup fails
+ */
+ public ConnectionFactory getConnectionFactory(String connFactoryName) throws NamingException {
+ return (ConnectionFactory) lookup(connFactoryName);
+ }
+
+ /**
+ * This method obtains the topic itself for a caller.
+ *
+ * @param destName destination name
+ * @return the Topic Destination to send messages to
+ * @throws NamingException if the lookup fails
+ */
+ public Destination getDestination(String destName) throws NamingException {
+ return (Destination) lookup(destName);
+ }
+
+ /**
+ * This method obtains the datasource itself for a caller.
+ *
+ * @param dataSourceName data source name
+ * @return the DataSource corresponding to the name parameter
+ * @throws NamingException if the lookup fails
+ */
+ public DataSource getDataSource(String dataSourceName) throws NamingException {
+ return (DataSource) lookup(dataSourceName);
+ }
+
+ /**
+ * This method obtains the E-mail session itself for a caller.
+ *
+ * @param sessionName session name
+ * @return the Session corresponding to the name parameter
+ * @throws NamingException if the lookup fails
+ */
+ public Session getSession(String sessionName) throws NamingException {
+ return (Session) lookup(sessionName);
+ }
+
+ /**
+ * Gets the URL corresponding to the environment entry name.
+ *
+ * @param envName the environment name
+ * @return the URL value corresponding to the environment entry name
+ * @throws NamingException if the lookup fails
+ */
+ public URL getUrl(String envName) throws NamingException {
+ return (URL) lookup(envName);
+ }
+
+ /**
+ * Gets boolean value corresponding to the environment entry name.
+ *
+ * @param envName the environment name
+ * @return the boolean value corresponding to the environment entry
+ * @throws NamingException if the lookup fails
+ */
+ public boolean getBoolean(String envName) throws NamingException {
+ Boolean bool = (Boolean) lookup(envName);
+ return bool.booleanValue();
+ }
+
+ /**
+ * Gets string value corresponding to the environment entry name.
+ *
+ * @param envName the environment name
+ * @return the String value corresponding to the environment entry name
+ * @throws NamingException if the lookup fails
+ */
+ public String getString(String envName) throws NamingException {
+ return (String) lookup(envName);
+ }
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/mail/MailTemplateException.java b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailTemplateException.java
new file mode 100644
index 0000000..3075952
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailTemplateException.java
@@ -0,0 +1,37 @@
+package de.muehlencord.shared.account.business.mail;
+
+/**
+ *
+ * @author jomu
+ */
+public class MailTemplateException extends Exception {
+
+ /**
+ * Creates a new instance of MailTemplateException without
+ * detail message.
+ */
+ public MailTemplateException() {
+ }
+
+ /**
+ * Constructs an instance of MailTemplateException with the
+ * specified detail message.
+ *
+ * @param msg the detail message.
+ */
+ public MailTemplateException(String msg) {
+ super(msg);
+ }
+
+ /**
+ * Constructs an instance of MailTemplateException with the
+ * specified detail message.
+ *
+ * @param msg the detail message.
+ * @param th the root cause
+ */
+ public MailTemplateException(String msg, Throwable th) {
+ super(msg, th);
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/mail/MailTemplateService.java b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailTemplateService.java
new file mode 100644
index 0000000..e8a17f5
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/business/mail/MailTemplateService.java
@@ -0,0 +1,66 @@
+package de.muehlencord.shared.account.business.mail;
+
+import de.muehlencord.shared.account.entity.MailTemplateEntity;
+import freemarker.cache.StringTemplateLoader;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateExceptionHandler;
+import java.io.IOException;
+import java.io.StringWriter;
+import java.io.Writer;
+import javax.ejb.Stateless;
+import javax.persistence.EntityManager;
+import javax.persistence.PersistenceContext;
+import javax.persistence.Query;
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+
+/**
+ *
+ * @author jomu
+ */
+@Stateless
+public class MailTemplateService {
+
+ private static final Logger LOGGER = Logger.getLogger(MailTemplateService.class.getName());
+
+ @PersistenceContext
+ EntityManager em;
+
+ public String getStringFromTemplate(String templateName, MailDatamodel dataModel) throws MailTemplateException {
+ try {
+ Query query = em.createNamedQuery("MailTemplateEntity.findByTemplateName");
+ query.setParameter("templateName", templateName);
+ MailTemplateEntity templateEntity = (MailTemplateEntity) query.getSingleResult();
+ if (templateEntity == null) {
+ LOGGER.log(Level.ERROR, "Tempate with name " + templateName + " not found");
+ return null;
+ }
+
+ Configuration configuration = new Configuration(Configuration.VERSION_2_3_23);
+ configuration.setDefaultEncoding("UTF-8");
+ configuration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
+
+ StringTemplateLoader stringLoader = new StringTemplateLoader();
+ stringLoader.putTemplate(templateEntity.getTemplateName(), templateEntity.getTemplateValue());
+ configuration.setTemplateLoader(stringLoader);
+
+ Template template = configuration.getTemplate(templateEntity.getTemplateName());
+
+ Writer out = new StringWriter();
+ template.process(dataModel, out);
+ String templateString = out.toString();
+ return templateString;
+ } catch (Exception ex) {
+ String hint = "Error while processing template with name " + templateName + ".";
+ LOGGER.log(Level.ERROR, hint + " " + ex.toString());
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.log(Level.DEBUG, hint, ex);
+ }
+ throw new MailTemplateException(hint, ex);
+
+ }
+
+ }
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/entity/AccountEntity.java b/account/src/main/java/de/muehlencord/shared/account/entity/AccountEntity.java
new file mode 100644
index 0000000..0eaa37b
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/entity/AccountEntity.java
@@ -0,0 +1,322 @@
+package de.muehlencord.shared.account.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+import java.util.List;
+import javax.persistence.Basic;
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.OneToMany;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+@Entity
+@Table(name = "account")
+@XmlRootElement
+@NamedQueries({
+ @NamedQuery(name = "AccountEntity.findAll", query = "SELECT a FROM AccountEntity a"),
+ @NamedQuery(name = "AccountEntity.findByUsername", query = "SELECT a FROM AccountEntity a WHERE a.username = :username"),
+ @NamedQuery(name = "AccountEntity.findByEmailaddress", query = "SELECT a FROM AccountEntity a WHERE a.emailaddress = :emailaddress"),
+ @NamedQuery(name = "AccountEntity.findByFirstname", query = "SELECT a FROM AccountEntity a WHERE a.firstname = :firstname"),
+ @NamedQuery(name = "AccountEntity.findByLastname", query = "SELECT a FROM AccountEntity a WHERE a.lastname = :lastname"),
+ @NamedQuery(name = "AccountEntity.findByAccountPassword", query = "SELECT a FROM AccountEntity a WHERE a.accountPassword = :accountPassword"),
+ @NamedQuery(name = "AccountEntity.findByLastLogin", query = "SELECT a FROM AccountEntity a WHERE a.lastLogin = :lastLogin"),
+ @NamedQuery(name = "AccountEntity.findByLastFailedLogin", query = "SELECT a FROM AccountEntity a WHERE a.lastFailedLogin = :lastFailedLogin"),
+ @NamedQuery(name = "AccountEntity.findByFailureCount", query = "SELECT a FROM AccountEntity a WHERE a.failureCount = :failureCount"),
+ @NamedQuery(name = "AccountEntity.findByStatus", query = "SELECT a FROM AccountEntity a WHERE a.status = :status"),
+ @NamedQuery(name = "AccountEntity.findByCreatedOn", query = "SELECT a FROM AccountEntity a WHERE a.createdOn = :createdOn"),
+ @NamedQuery(name = "AccountEntity.findByCreatedBy", query = "SELECT a FROM AccountEntity a WHERE a.createdBy = :createdBy"),
+ @NamedQuery(name = "AccountEntity.findByLastUpdatedOn", query = "SELECT a FROM AccountEntity a WHERE a.lastUpdatedOn = :lastUpdatedOn"),
+ @NamedQuery(name = "AccountEntity.findByLastUpdatedBy", query = "SELECT a FROM AccountEntity a WHERE a.lastUpdatedBy = :lastUpdatedBy")})
+public class AccountEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ @Id
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 32)
+ @Column(name = "username")
+ private String username;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 200)
+ @Column(name = "emailaddress")
+ private String emailaddress;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 100)
+ @Column(name = "firstname")
+ private String firstname;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 100)
+ @Column(name = "lastname")
+ private String lastname;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 200)
+ @Column(name = "account_password", columnDefinition = "bpchar(200)")
+ private String accountPassword;
+ @Column(name = "last_login")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date lastLogin;
+ @Column(name = "last_failed_login")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date lastFailedLogin;
+ @Basic(optional = false)
+ @NotNull
+ @Column(name = "failure_count")
+ private int failureCount;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 10)
+ @Column(name = "status")
+ private String status;
+ @Basic(optional = false)
+ @NotNull
+ @Column(name = "password_reset_ongoing")
+ private boolean passwordResetOngoing;
+ @Column(name = "password_reset_valid_to")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date passwordResetValidTo;
+ @Size(max = 200)
+ @Column(name = "password_reset_hash", columnDefinition = "bpchar(200)")
+ private String passwordResetHash;
+ @Basic(optional = false)
+ @NotNull
+ @Column(name = "created_on")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date createdOn;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 32)
+ @Column(name = "created_by")
+ private String createdBy;
+ @Basic(optional = false)
+ @NotNull
+ @Column(name = "last_updated_on")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date lastUpdatedOn;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 32)
+ @Column(name = "last_updated_by")
+ private String lastUpdatedBy;
+ @JoinTable(name = "account_role", joinColumns = {
+ @JoinColumn(name = "username", referencedColumnName = "username")}, inverseJoinColumns = {
+ @JoinColumn(name = "role_name", referencedColumnName = "role_name")})
+ @ManyToMany
+ private List roleEntityList;
+ @OneToMany(cascade = CascadeType.ALL, mappedBy = "username")
+ private List accountHistoryEntityList;
+
+ public AccountEntity() {
+ // empty constructor needed for JPA handling, do not remove
+ }
+
+ public AccountEntity(String username) {
+ this.username = username;
+ }
+
+ public AccountEntity(String username, String emailaddress, String firstname, String lastname, String accountPassword, int failureCount, String status, Date createdOn, String createdBy, Date lastUpdatedOn, String lastUpdatedBy) {
+ this.username = username;
+ this.emailaddress = emailaddress;
+ this.firstname = firstname;
+ this.lastname = lastname;
+ this.accountPassword = accountPassword;
+ this.failureCount = failureCount;
+ this.status = status;
+ this.createdOn = createdOn;
+ this.createdBy = createdBy;
+ this.lastUpdatedOn = lastUpdatedOn;
+ this.lastUpdatedBy = lastUpdatedBy;
+ }
+
+ public String getUsername() {
+ return username;
+ }
+
+ public void setUsername(String username) {
+ this.username = username;
+ }
+
+ public String getEmailaddress() {
+ return emailaddress;
+ }
+
+ public void setEmailaddress(String emailaddress) {
+ this.emailaddress = emailaddress;
+ }
+
+ 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 getAccountPassword() {
+ return accountPassword;
+ }
+
+ public void setAccountPassword(String accountPassword) {
+ this.accountPassword = accountPassword;
+ }
+
+ public Date getLastLogin() {
+ return lastLogin;
+ }
+
+ public void setLastLogin(Date lastLogin) {
+ this.lastLogin = lastLogin;
+ }
+
+ public Date getLastFailedLogin() {
+ return lastFailedLogin;
+ }
+
+ public void setLastFailedLogin(Date lastFailedLogin) {
+ this.lastFailedLogin = lastFailedLogin;
+ }
+
+ public int getFailureCount() {
+ return failureCount;
+ }
+
+ public void setFailureCount(int failureCount) {
+ this.failureCount = failureCount;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public boolean getPasswordResetOngoing() {
+ return passwordResetOngoing;
+ }
+
+ public void setPasswordResetOngoing(boolean passwordResetOngoing) {
+ this.passwordResetOngoing = passwordResetOngoing;
+ }
+
+ public Date getPasswordResetValidTo() {
+ return passwordResetValidTo;
+ }
+
+ public void setPasswordResetValidTo(Date passwordResetValidTo) {
+ this.passwordResetValidTo = passwordResetValidTo;
+ }
+
+ public String getPasswordResetHash() {
+ return passwordResetHash;
+ }
+
+ public void setPasswordResetHash(String passwordResetHash) {
+ this.passwordResetHash = passwordResetHash;
+ }
+
+ public Date getCreatedOn() {
+ return createdOn;
+ }
+
+ public void setCreatedOn(Date createdOn) {
+ this.createdOn = createdOn;
+ }
+
+ public String getCreatedBy() {
+ return createdBy;
+ }
+
+ public void setCreatedBy(String createdBy) {
+ this.createdBy = createdBy;
+ }
+
+ public Date getLastUpdatedOn() {
+ return lastUpdatedOn;
+ }
+
+ public void setLastUpdatedOn(Date lastUpdatedOn) {
+ this.lastUpdatedOn = lastUpdatedOn;
+ }
+
+ public String getLastUpdatedBy() {
+ return lastUpdatedBy;
+ }
+
+ public void setLastUpdatedBy(String lastUpdatedBy) {
+ this.lastUpdatedBy = lastUpdatedBy;
+ }
+
+ @XmlTransient
+ public List getRoleEntityList() {
+ return roleEntityList;
+ }
+
+ public void setRoleEntityList(List roleEntityList) {
+ this.roleEntityList = roleEntityList;
+ }
+
+ @XmlTransient
+ public List getAccountHistoryEntityList() {
+ return accountHistoryEntityList;
+ }
+
+ public void setAccountHistoryEntityList(List accountHistoryEntityList) {
+ this.accountHistoryEntityList = accountHistoryEntityList;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (username != null ? username.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof AccountEntity)) {
+ return false;
+ }
+ AccountEntity other = (AccountEntity) object;
+ if ((this.username == null && other.username != null) || (this.username != null && !this.username.equals(other.username))) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "de.muehlencord.ssg.entity.AccountEntity[ username=" + username + " ]";
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/entity/AccountHistoryEntity.java b/account/src/main/java/de/muehlencord/shared/account/entity/AccountHistoryEntity.java
new file mode 100644
index 0000000..af07350
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/entity/AccountHistoryEntity.java
@@ -0,0 +1,167 @@
+package de.muehlencord.shared.account.entity;
+
+import java.io.Serializable;
+import java.util.Date;
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.GenerationType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.ManyToOne;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.persistence.Temporal;
+import javax.persistence.TemporalType;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+@Entity
+@Table(name = "account_history")
+@XmlRootElement
+@NamedQueries({
+ @NamedQuery(name = "AccountHistoryEntity.findAll", query = "SELECT a FROM AccountHistoryEntity a"),
+ @NamedQuery(name = "AccountHistoryEntity.findById", query = "SELECT a FROM AccountHistoryEntity a WHERE a.id = :id"),
+ @NamedQuery(name = "AccountHistoryEntity.findByMessage", query = "SELECT a FROM AccountHistoryEntity a WHERE a.message = :message"),
+ @NamedQuery(name = "AccountHistoryEntity.findByFailureCount", query = "SELECT a FROM AccountHistoryEntity a WHERE a.failureCount = :failureCount"),
+ @NamedQuery(name = "AccountHistoryEntity.findByStatus", query = "SELECT a FROM AccountHistoryEntity a WHERE a.status = :status"),
+ @NamedQuery(name = "AccountHistoryEntity.findByLastUpdatedOn", query = "SELECT a FROM AccountHistoryEntity a WHERE a.lastUpdatedOn = :lastUpdatedOn"),
+ @NamedQuery(name = "AccountHistoryEntity.findByLastUpdatedBy", query = "SELECT a FROM AccountHistoryEntity a WHERE a.lastUpdatedBy = :lastUpdatedBy")})
+public class AccountHistoryEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Basic(optional = false)
+ @Column(name = "id")
+ private Integer id;
+ @Size(max = 200)
+ @Column(name = "message")
+ private String message;
+ @Basic(optional = false)
+ @NotNull
+ @Column(name = "failure_count")
+ private int failureCount;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 20)
+ @Column(name = "status")
+ private String status;
+ @Basic(optional = false)
+ @NotNull
+ @Column(name = "last_updated_on")
+ @Temporal(TemporalType.TIMESTAMP)
+ private Date lastUpdatedOn;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 32)
+ @Column(name = "last_updated_by")
+ private String lastUpdatedBy;
+ @JoinColumn(name = "username", referencedColumnName = "username")
+ @ManyToOne(optional = false)
+ private AccountEntity username;
+
+ public AccountHistoryEntity() {
+ }
+
+ public AccountHistoryEntity(Integer id) {
+ this.id = id;
+ }
+
+ public AccountHistoryEntity(Integer id, int failureCount, String status, Date lastUpdatedOn, String lastUpdatedBy) {
+ this.id = id;
+ this.failureCount = failureCount;
+ this.status = status;
+ this.lastUpdatedOn = lastUpdatedOn;
+ this.lastUpdatedBy = lastUpdatedBy;
+ }
+
+ public Integer getId() {
+ return id;
+ }
+
+ public void setId(Integer id) {
+ this.id = id;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ public void setMessage(String message) {
+ this.message = message;
+ }
+
+ public int getFailureCount() {
+ return failureCount;
+ }
+
+ public void setFailureCount(int failureCount) {
+ this.failureCount = failureCount;
+ }
+
+ public String getStatus() {
+ return status;
+ }
+
+ public void setStatus(String status) {
+ this.status = status;
+ }
+
+ public Date getLastUpdatedOn() {
+ return lastUpdatedOn;
+ }
+
+ public void setLastUpdatedOn(Date lastUpdatedOn) {
+ this.lastUpdatedOn = lastUpdatedOn;
+ }
+
+ public String getLastUpdatedBy() {
+ return lastUpdatedBy;
+ }
+
+ public void setLastUpdatedBy(String lastUpdatedBy) {
+ this.lastUpdatedBy = lastUpdatedBy;
+ }
+
+ public AccountEntity getUsername() {
+ return username;
+ }
+
+ public void setUsername(AccountEntity username) {
+ this.username = username;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (id != null ? id.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof AccountHistoryEntity)) {
+ return false;
+ }
+ AccountHistoryEntity other = (AccountHistoryEntity) object;
+ if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "de.muehlencord.ssg.entity.AccountHistoryEntity[ id=" + id + " ]";
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/entity/ConfigEntity.java b/account/src/main/java/de/muehlencord/shared/account/entity/ConfigEntity.java
new file mode 100644
index 0000000..66c72b5
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/entity/ConfigEntity.java
@@ -0,0 +1,92 @@
+/*
+ * 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 de.muehlencord.shared.account.entity;
+
+import java.io.Serializable;
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ *
+ * @author jomu
+ */
+@Entity
+@Table(name = "config")
+@XmlRootElement
+@NamedQueries({
+ @NamedQuery(name = "ConfigEntity.findAll", query = "SELECT c FROM ConfigEntity c"),
+ @NamedQuery(name = "ConfigEntity.findByConfigKey", query = "SELECT c FROM ConfigEntity c WHERE c.configKey = :configKey"),
+ @NamedQuery(name = "ConfigEntity.findByConfigValue", query = "SELECT c FROM ConfigEntity c WHERE c.configValue = :configValue")})
+public class ConfigEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ @Id
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 100)
+ @Column(name = "config_key")
+ private String configKey;
+ @Size(max = 200)
+ @Column(name = "config_value")
+ private String configValue;
+
+ public ConfigEntity() {
+ }
+
+ public ConfigEntity(String configKey) {
+ this.configKey = configKey;
+ }
+
+ public String getConfigKey() {
+ return configKey;
+ }
+
+ public void setConfigKey(String configKey) {
+ this.configKey = configKey;
+ }
+
+ public String getConfigValue() {
+ return configValue;
+ }
+
+ public void setConfigValue(String configValue) {
+ this.configValue = configValue;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (configKey != null ? configKey.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof ConfigEntity)) {
+ return false;
+ }
+ ConfigEntity other = (ConfigEntity) object;
+ if ((this.configKey == null && other.configKey != null) || (this.configKey != null && !this.configKey.equals(other.configKey))) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "de.muehlencord.ssg.entity.ConfigEntity[ configKey=" + configKey + " ]";
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/entity/MailTemplateEntity.java b/account/src/main/java/de/muehlencord/shared/account/entity/MailTemplateEntity.java
new file mode 100644
index 0000000..cfa0106
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/entity/MailTemplateEntity.java
@@ -0,0 +1,94 @@
+package de.muehlencord.shared.account.entity;
+
+import java.io.Serializable;
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import javax.xml.bind.annotation.XmlRootElement;
+
+/**
+ *
+ * @author jomu
+ */
+@Entity
+@Table(name = "mail_template")
+@XmlRootElement
+@NamedQueries({
+ @NamedQuery(name = "MailTemplateEntity.findAll", query = "SELECT m FROM MailTemplateEntity m"),
+ @NamedQuery(name = "MailTemplateEntity.findByTemplateName", query = "SELECT m FROM MailTemplateEntity m WHERE m.templateName = :templateName"),
+ @NamedQuery(name = "MailTemplateEntity.findByTemplateValue", query = "SELECT m FROM MailTemplateEntity m WHERE m.templateValue = :templateValue")})
+public class MailTemplateEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ @Id
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 40)
+ @Column(name = "template_name")
+ private String templateName;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 2147483647)
+ @Column(name = "template_value")
+ private String templateValue;
+
+ public MailTemplateEntity() {
+ }
+
+ public MailTemplateEntity(String templateName) {
+ this.templateName = templateName;
+ }
+
+ public MailTemplateEntity(String templateName, String templateValue) {
+ this.templateName = templateName;
+ this.templateValue = templateValue;
+ }
+
+ public String getTemplateName() {
+ return templateName;
+ }
+
+ public void setTemplateName(String templateName) {
+ this.templateName = templateName;
+ }
+
+ public String getTemplateValue() {
+ return templateValue;
+ }
+
+ public void setTemplateValue(String templateValue) {
+ this.templateValue = templateValue;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (templateName != null ? templateName.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof MailTemplateEntity)) {
+ return false;
+ }
+ MailTemplateEntity other = (MailTemplateEntity) object;
+ if ((this.templateName == null && other.templateName != null) || (this.templateName != null && !this.templateName.equals(other.templateName))) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "de.muehlencord.ssg.entity.MailTemplateEntity[ templateName=" + templateName + " ]";
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/entity/PermissionEntity.java b/account/src/main/java/de/muehlencord/shared/account/entity/PermissionEntity.java
new file mode 100644
index 0000000..cce033d
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/entity/PermissionEntity.java
@@ -0,0 +1,113 @@
+package de.muehlencord.shared.account.entity;
+
+import java.io.Serializable;
+import java.util.List;
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.JoinTable;
+import javax.persistence.ManyToMany;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+@Entity
+@Table(name = "application_permission")
+@XmlRootElement
+@NamedQueries({
+ @NamedQuery(name = "PermissionEntity.findAll", query = "SELECT p FROM PermissionEntity p"),
+ @NamedQuery(name = "PermissionEntity.findByPermissionName", query = "SELECT p FROM PermissionEntity p WHERE p.permissionName = :permissionName"),
+ @NamedQuery(name = "PermissionEntity.findByPermissionDescription", query = "SELECT p FROM PermissionEntity p WHERE p.permissionDescription = :permissionDescription")})
+public class PermissionEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ @Id
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 80)
+ @Column(name = "permission_name")
+ private String permissionName;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 200)
+ @Column(name = "permission_description")
+ private String permissionDescription;
+ @JoinTable(name = "role_permission", joinColumns = {
+ @JoinColumn(name = "permission_name", referencedColumnName = "permission_name")}, inverseJoinColumns = {
+ @JoinColumn(name = "role_name", referencedColumnName = "role_name")})
+ @ManyToMany
+ private List roleEntityList;
+
+ public PermissionEntity() {
+ }
+
+ public PermissionEntity(String permissionName) {
+ this.permissionName = permissionName;
+ }
+
+ public PermissionEntity(String permissionName, String permissionDescription) {
+ this.permissionName = permissionName;
+ this.permissionDescription = permissionDescription;
+ }
+
+ public String getPermissionName() {
+ return permissionName;
+ }
+
+ public void setPermissionName(String permissionName) {
+ this.permissionName = permissionName;
+ }
+
+ public String getPermissionDescription() {
+ return permissionDescription;
+ }
+
+ public void setPermissionDescription(String permissionDescription) {
+ this.permissionDescription = permissionDescription;
+ }
+
+ @XmlTransient
+ public List getRoleEntityList() {
+ return roleEntityList;
+ }
+
+ public void setRoleEntityList(List roleEntityList) {
+ this.roleEntityList = roleEntityList;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (permissionName != null ? permissionName.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof PermissionEntity)) {
+ return false;
+ }
+ PermissionEntity other = (PermissionEntity) object;
+ if ((this.permissionName == null && other.permissionName != null) || (this.permissionName != null && !this.permissionName.equals(other.permissionName))) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "de.muehlencord.ssg.entity.PermissionEntity[ permissionName=" + permissionName + " ]";
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/entity/RoleEntity.java b/account/src/main/java/de/muehlencord/shared/account/entity/RoleEntity.java
new file mode 100644
index 0000000..cd03330
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/entity/RoleEntity.java
@@ -0,0 +1,119 @@
+package de.muehlencord.shared.account.entity;
+
+import java.io.Serializable;
+import java.util.List;
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.ManyToMany;
+import javax.persistence.NamedQueries;
+import javax.persistence.NamedQuery;
+import javax.persistence.Table;
+import javax.validation.constraints.NotNull;
+import javax.validation.constraints.Size;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlTransient;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+@Entity
+@Table(name = "application_role")
+@XmlRootElement
+@NamedQueries({
+ @NamedQuery(name = "RoleEntity.findAll", query = "SELECT r FROM RoleEntity r"),
+ @NamedQuery(name = "RoleEntity.findByRoleName", query = "SELECT r FROM RoleEntity r WHERE r.roleName = :roleName"),
+ @NamedQuery(name = "RoleEntity.findByRoleDescription", query = "SELECT r FROM RoleEntity r WHERE r.roleDescription = :roleDescription")})
+public class RoleEntity implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+ @Id
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 80)
+ @Column(name = "role_name")
+ private String roleName;
+ @Basic(optional = false)
+ @NotNull
+ @Size(min = 1, max = 200)
+ @Column(name = "role_description")
+ private String roleDescription;
+ @ManyToMany(mappedBy = "roleEntityList")
+ private List accountEntityList;
+ @ManyToMany(mappedBy = "roleEntityList")
+ private List permissionEntityList;
+
+ public RoleEntity() {
+ }
+
+ public RoleEntity(String roleName) {
+ this.roleName = roleName;
+ }
+
+ public RoleEntity(String roleName, String roleDescription) {
+ this.roleName = roleName;
+ this.roleDescription = roleDescription;
+ }
+
+ public String getRoleName() {
+ return roleName;
+ }
+
+ public void setRoleName(String roleName) {
+ this.roleName = roleName;
+ }
+
+ public String getRoleDescription() {
+ return roleDescription;
+ }
+
+ public void setRoleDescription(String roleDescription) {
+ this.roleDescription = roleDescription;
+ }
+
+ @XmlTransient
+ public List getAccountEntityList() {
+ return accountEntityList;
+ }
+
+ public void setAccountEntityList(List accountEntityList) {
+ this.accountEntityList = accountEntityList;
+ }
+
+ @XmlTransient
+ public List getPermissionEntityList() {
+ return permissionEntityList;
+ }
+
+ public void setPermissionEntityList(List permissionEntityList) {
+ this.permissionEntityList = permissionEntityList;
+ }
+
+ @Override
+ public int hashCode() {
+ int hash = 0;
+ hash += (roleName != null ? roleName.hashCode() : 0);
+ return hash;
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ // TODO: Warning - this method won't work in the case the id fields are not set
+ if (!(object instanceof RoleEntity)) {
+ return false;
+ }
+ RoleEntity other = (RoleEntity) object;
+ if ((this.roleName == null && other.roleName != null) || (this.roleName != null && !this.roleName.equals(other.roleName))) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public String toString() {
+ return "de.muehlencord.ssg.entity.RoleEntity[ roleName=" + roleName + " ]";
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/util/SecurityUtil.java b/account/src/main/java/de/muehlencord/shared/account/util/SecurityUtil.java
new file mode 100644
index 0000000..67067bf
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/util/SecurityUtil.java
@@ -0,0 +1,33 @@
+package de.muehlencord.shared.account.util;
+
+import org.apache.log4j.Level;
+import org.apache.log4j.Logger;
+import org.apache.shiro.authc.credential.DefaultPasswordService;
+import org.apache.shiro.crypto.hash.DefaultHashService;
+import org.apache.shiro.crypto.hash.Sha512Hash;
+
+/**
+ *
+ * @author jomu
+ */
+public class SecurityUtil {
+
+ private final static Logger LOGGER = Logger.getLogger (SecurityUtil.class.getName());
+
+ public static String createPassword(String clearTextPassword) {
+ // TODO read values from shiro.ini
+ DefaultHashService hashService = new DefaultHashService();
+ hashService.setHashIterations(500000); //
+ hashService.setHashAlgorithmName(Sha512Hash.ALGORITHM_NAME);
+ hashService.setGeneratePublicSalt(true);
+
+ DefaultPasswordService passwordService = new DefaultPasswordService();
+ passwordService.setHashService(hashService);
+
+ // try to encrypt password
+ String encryptedPassword = passwordService.encryptPassword(clearTextPassword);
+ LOGGER.log (Level.TRACE, encryptedPassword);
+ return encryptedPassword;
+ }
+
+}
diff --git a/account/src/main/resources/META-INF/MANIFEST.MF b/account/src/main/resources/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..59499bc
--- /dev/null
+++ b/account/src/main/resources/META-INF/MANIFEST.MF
@@ -0,0 +1,2 @@
+Manifest-Version: 1.0
+