diff --git a/account/pom.xml b/account/pom.xml
index aca8664..0d93b86 100644
--- a/account/pom.xml
+++ b/account/pom.xml
@@ -76,6 +76,10 @@
com.google.code.gson
gson
+
+ commons-io
+ commons-io
+
javax
javaee-api
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/account/boundary/AccountProducer.java b/account/src/main/java/de/muehlencord/shared/account/business/account/boundary/AccountProducer.java
index 97b4e54..95d6502 100644
--- a/account/src/main/java/de/muehlencord/shared/account/business/account/boundary/AccountProducer.java
+++ b/account/src/main/java/de/muehlencord/shared/account/business/account/boundary/AccountProducer.java
@@ -77,13 +77,12 @@ public class AccountProducer implements Serializable {
}
if ((subject.isAuthenticated() == false) && (subject.isRemembered() == false)) {
- accountName = "web";
+ return null;
} else {
accountName = subject.getPrincipal().toString();
}
account = accountController.getAccountEntity(accountName, true);
- // TODO introduce locale support to account and switch
- // to pre-defined locale if set
+ // TODO introduce locale support to account and switch to pre-defined locale if set
}
return account;
}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/account/boundary/ApiKeyService.java b/account/src/main/java/de/muehlencord/shared/account/business/account/boundary/ApiKeyService.java
index 791c253..c800569 100644
--- a/account/src/main/java/de/muehlencord/shared/account/business/account/boundary/ApiKeyService.java
+++ b/account/src/main/java/de/muehlencord/shared/account/business/account/boundary/ApiKeyService.java
@@ -1,179 +1,216 @@
-package de.muehlencord.shared.account.business.account.boundary;
-
-import de.muehlencord.shared.account.business.config.boundary.ConfigService;
-import de.muehlencord.shared.account.business.account.control.AccountControl;
-import de.muehlencord.shared.account.business.account.entity.Account;
-import de.muehlencord.shared.account.business.account.entity.AccountEntity;
-import de.muehlencord.shared.account.business.account.entity.ApiKeyEntity;
-import de.muehlencord.shared.account.business.account.entity.JWTObject;
-import de.muehlencord.shared.account.business.config.entity.ConfigException;
-import de.muehlencord.shared.account.util.AccountPU;
-import de.muehlencord.shared.jeeutil.jwt.JWTDecoder;
-import de.muehlencord.shared.jeeutil.jwt.JWTEncoder;
-import de.muehlencord.shared.jeeutil.jwt.JWTException;
-import java.io.Serializable;
-import java.time.ZoneOffset;
-import java.time.ZonedDateTime;
-import java.util.ArrayList;
-import java.util.Date;
-import java.util.Iterator;
-import java.util.List;
-import javax.annotation.PostConstruct;
-import javax.ejb.Lock;
-import javax.ejb.LockType;
-import javax.ejb.Stateless;
-import javax.ejb.TransactionAttribute;
-import javax.ejb.TransactionAttributeType;
-import javax.inject.Inject;
-import javax.persistence.EntityManager;
-import javax.persistence.Query;
-import javax.transaction.Transactional;
-import org.apache.commons.lang3.RandomStringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- *
- * @author Joern Muehlencord
- */
-@Stateless
-public class ApiKeyService implements Serializable {
-
- private static final long serialVersionUID = -6981864888118320228L;
- private static final Logger LOGGER = LoggerFactory.getLogger(ApiKeyService.class);
-
- @Inject
- @AccountPU
- EntityManager em;
-
- @Inject
- AccountControl accountControl;
-
- @Inject
- ConfigService configService;
-
- @Inject
- Account account;
-
- private String password;
- private String issuer;
-
- @PostConstruct
- public void init() {
- if (configService == null) {
- password = null;
- issuer = null;
- } else {
- try {
- password = configService.getConfigValue("rest.password");
- issuer = configService.getConfigValue("rest.issuer");
- } catch (ConfigException ex) {
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(ex.toString(), ex);
- } else {
- LOGGER.error(ex.toString());
- }
- password = null;
- issuer = null;
- }
-
- }
- }
-
- public List getUsersApiKeys(AccountEntity account) {
- Query query = em.createNamedQuery("ApiKeyEntity.findByAccount");
- query.setParameter("account", account);
- List keys = query.getResultList();
- if (keys == null) {
- return new ArrayList<>();
- } else {
- return keys;
- }
-
- }
-
- public List getUsersApiKeys() {
- return getUsersApiKeys(accountControl.getAccountEntity(account.getUsername(), false));
- }
-
- @Transactional
- @Lock(LockType.WRITE)
- public String createNewApiKey(ZonedDateTime now, short expirationInMinutes) throws ApiKeyException {
- if ((password == null || issuer == null)) {
- LOGGER.error("password or issuer not set in, please validate configuration");
- }
- Date nowDate = Date.from(now.toInstant());
- String apiKeyString = RandomStringUtils.randomAscii(50);
-
- ApiKeyEntity apiKey = new ApiKeyEntity();
- apiKey.setAccount(accountControl.getAccountEntity(account.getUsername(), false));
- apiKey.setApiKey(apiKeyString);
- apiKey.setIssuedOn(nowDate);
- apiKey.setExpiration(expirationInMinutes);
-
- try {
- String jwtString = JWTEncoder.encode(password, issuer, now, apiKey.getAccount().getUsername(), apiKey.getApiKey(), apiKey.getExpiration());
- em.persist(apiKey);
- return jwtString;
- } catch (JWTException ex) {
- throw new ApiKeyException("Cannot create apiKey. Reason: " + ex.toString(), ex);
- }
- }
-
- public boolean validateJWT(String encodedJWT) {
- JWTDecoder decoder = new JWTDecoder(password, issuer, encodedJWT);
- ApiKeyEntity validKey = getValidKey(decoder.getSubject(), decoder.getUniqueId(), encodedJWT);
- return validKey != null;
- }
-
- public ApiKeyEntity getValidKey(String userName, String apiKey, String authorizationHeader) {
- AccountEntity userAccount = accountControl.getAccountEntity(userName, false);
- List apiKeys = getUsersApiKeys(userAccount);
-
- Iterator it = apiKeys.iterator();
- ApiKeyEntity keyToLogout = null;
- while (keyToLogout == null && it.hasNext()) {
- ApiKeyEntity key = it.next();
- if (key.getApiKey().equals(apiKey)) {
- ZonedDateTime issuedOn = ZonedDateTime.ofInstant(key.getIssuedOn().toInstant(), ZoneOffset.UTC);
- String testString;
-
- try {
- testString = JWTEncoder.encode(password, issuer, issuedOn, key.getAccount().getUsername(), key.getApiKey(), key.getExpiration());
- if (authorizationHeader.equals(testString)) {
- return key;
- }
- } catch (JWTException ex) {
-
- }
- }
- }
- return null;
- }
-
- public String getJWTFromApiKey(ApiKeyEntity apiKey) throws ApiKeyException {
- ZonedDateTime issuedAt = ZonedDateTime.ofInstant(apiKey.getIssuedOn().toInstant(), ZoneOffset.UTC);
- try {
- return JWTEncoder.encode(password, issuer, issuedAt, apiKey.getAccount().getUsername(), apiKey.getApiKey(), apiKey.getExpiration());
- } catch (JWTException ex) {
- throw new ApiKeyException("Cannot retrieve JWT from key. Reason: " + ex.toString(), ex);
- }
- }
-
- public JWTObject getJWTObject(String authorizationHeader) {
- JWTDecoder decoder = new JWTDecoder(password, issuer, authorizationHeader);
- JWTObject jwtObject = new JWTObject();
- jwtObject.setUserName(decoder.getSubject());
- jwtObject.setUnqiueId(decoder.getUniqueId());
- jwtObject.setValid(true);
- return jwtObject;
- }
-
- @TransactionAttribute(TransactionAttributeType.REQUIRED)
- @Transactional
- @Lock(LockType.WRITE)
- public void delete(ApiKeyEntity apiKey) {
- em.remove(em.merge(apiKey));
- }
-
-}
+package de.muehlencord.shared.account.business.account.boundary;
+
+import de.muehlencord.shared.account.business.account.control.AccountControl;
+import de.muehlencord.shared.account.business.account.entity.Account;
+import de.muehlencord.shared.account.business.account.entity.AccountEntity;
+import de.muehlencord.shared.account.business.account.entity.ApiKeyEntity;
+import de.muehlencord.shared.account.business.account.entity.JWTObject;
+import de.muehlencord.shared.account.business.config.boundary.ConfigService;
+import de.muehlencord.shared.account.business.config.entity.ConfigException;
+import de.muehlencord.shared.account.util.AccountPU;
+import de.muehlencord.shared.jeeutil.jwt.JWTDecoder;
+import de.muehlencord.shared.jeeutil.jwt.JWTEncoder;
+import de.muehlencord.shared.jeeutil.jwt.JWTException;
+import java.io.Serializable;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.Iterator;
+import java.util.List;
+import javax.annotation.PostConstruct;
+import javax.ejb.Lock;
+import javax.ejb.LockType;
+import javax.ejb.Stateless;
+import javax.ejb.TransactionAttribute;
+import javax.ejb.TransactionAttributeType;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import javax.transaction.Transactional;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author Joern Muehlencord
+ */
+@Stateless
+public class ApiKeyService implements Serializable {
+
+ private static final long serialVersionUID = -6981864888118320228L;
+ private static final Logger LOGGER = LoggerFactory.getLogger(ApiKeyService.class);
+
+ @Inject
+ @AccountPU
+ EntityManager em;
+
+ @Inject
+ AccountControl accountControl;
+
+ @Inject
+ ConfigService configService;
+
+ @Inject
+ Account account;
+
+ private String password;
+ private String issuer;
+
+ @PostConstruct
+ public void init() {
+ if (configService == null) {
+ password = null;
+ issuer = null;
+ } else {
+ try {
+ password = configService.getConfigValue("rest.password");
+ issuer = configService.getConfigValue("rest.issuer");
+ } catch (ConfigException ex) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(ex.toString(), ex);
+ } else {
+ LOGGER.error(ex.toString());
+ }
+ password = null;
+ issuer = null;
+ }
+
+ }
+ }
+
+ public List getUsersApiKeys(AccountEntity account) {
+ Query query = em.createNamedQuery("ApiKeyEntity.findByAccount");
+ query.setParameter("account", account);
+ List keys = query.getResultList();
+ if (keys == null) {
+ return new ArrayList<>();
+ } else {
+ return keys;
+ }
+
+ }
+
+ public List getUsersApiKeys() {
+ return getUsersApiKeys(accountControl.getAccountEntity(account.getUsername(), false));
+ }
+
+ @Transactional
+ @Lock(LockType.WRITE)
+ public String createNewApiKey(ZonedDateTime now, short expirationInMinutes) throws ApiKeyException {
+ if ((password == null || issuer == null)) {
+ LOGGER.error("password or issuer not set in, please validate configuration");
+ }
+ Date nowDate = Date.from(now.toInstant());
+ String apiKeyString = RandomStringUtils.randomAscii(50);
+
+ ApiKeyEntity apiKey = new ApiKeyEntity();
+ apiKey.setAccount(accountControl.getAccountEntity(account.getUsername(), false));
+ apiKey.setApiKey(apiKeyString);
+ apiKey.setIssuedOn(nowDate);
+ apiKey.setExpiration(expirationInMinutes);
+
+ try {
+ String jwtString = JWTEncoder.encode(password, issuer, now, apiKey.getAccount().getUsername(), apiKey.getApiKey(), apiKey.getExpiration());
+ em.persist(apiKey);
+ return jwtString;
+ } catch (JWTException ex) {
+ throw new ApiKeyException("Cannot create apiKey. Reason: " + ex.toString(), ex);
+ }
+ }
+
+ public boolean validateJWT(String encodedJWT) {
+ JWTDecoder decoder = new JWTDecoder(password, issuer, encodedJWT);
+ ApiKeyEntity validKey = getValidKey(decoder.getSubject(), decoder.getUniqueId(), encodedJWT);
+ return validKey != null;
+ }
+
+ private ApiKeyEntity getValidKey(String userName, String apiKey, String authorizationHeader) {
+ AccountEntity userAccount = accountControl.getAccountEntity(userName, false);
+ List apiKeys = getUsersApiKeys(userAccount);
+
+ Iterator it = apiKeys.iterator();
+ ApiKeyEntity keyToLogout = null;
+ while (keyToLogout == null && it.hasNext()) {
+ ApiKeyEntity key = it.next();
+ if (key.getApiKey().equals(apiKey)) {
+ ZonedDateTime issuedOn = ZonedDateTime.ofInstant(key.getIssuedOn().toInstant(), ZoneOffset.UTC);
+ String testString;
+
+ try {
+ testString = JWTEncoder.encode(password, issuer, issuedOn, key.getAccount().getUsername(), key.getApiKey(), key.getExpiration());
+ if (authorizationHeader.equals(testString)) {
+ return key;
+ }
+ } catch (JWTException ex) {
+
+ }
+ }
+ }
+ return null;
+ }
+
+ public String getJWTFromApiKey(ApiKeyEntity apiKey) throws ApiKeyException {
+ ZonedDateTime issuedAt = ZonedDateTime.ofInstant(apiKey.getIssuedOn().toInstant(), ZoneOffset.UTC);
+ try {
+ return JWTEncoder.encode(password, issuer, issuedAt, apiKey.getAccount().getUsername(), apiKey.getApiKey(), apiKey.getExpiration());
+ } catch (JWTException ex) {
+ throw new ApiKeyException("Cannot retrieve JWT from key. Reason: " + ex.toString(), ex);
+ }
+ }
+
+ public JWTObject getJWTObject(String encodedJWT) {
+ JWTDecoder decoder = new JWTDecoder(password, issuer, encodedJWT);
+ JWTObject jwtObject = new JWTObject();
+ jwtObject.setUserName(decoder.getSubject());
+ jwtObject.setUnqiueId(decoder.getUniqueId());
+ jwtObject.setValid(true);
+ return jwtObject;
+ }
+
+ /**
+ *
+ * @param apiKey
+ * @deprecated use delete (jwtObject) instead
+ */
+ @TransactionAttribute(TransactionAttributeType.REQUIRED)
+ @Transactional
+ @Lock(LockType.WRITE)
+ @Deprecated
+ public void delete(ApiKeyEntity apiKey) {
+ em.remove(em.merge(apiKey));
+ }
+
+ @TransactionAttribute(TransactionAttributeType.REQUIRED)
+ @Transactional
+ @Lock(LockType.WRITE)
+ public void delete(String authorizationHeader) throws ApiKeyException {
+
+ JWTObject jwtObject = getJWTObject(authorizationHeader);
+ if (jwtObject.isValid()) {
+ String userName = jwtObject.getUserName();
+
+ ApiKeyEntity keyToLogout = getValidKey(userName, jwtObject.getUnqiueId(), authorizationHeader);
+
+ if (keyToLogout == null) {
+ // no valid key found - must not happen, JWTVeryfingFIlter should have catched this
+ // FIXME - add logging / handle this problem
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("No valid key found, probably user {} has already logged out", userName);
+ }
+ throw new ApiKeyException("No valid key found, probably user " + userName + " has already logged out");
+ } else {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Found matching apiKey to logout");
+ }
+ em.remove(em.merge(keyToLogout));
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Key deleted, user {} logged out from webservice", userName);
+ }
+ }
+ } else {
+ throw new ApiKeyException("Provided JWT is not valid");
+ }
+ }
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/account/control/AccountControl.java b/account/src/main/java/de/muehlencord/shared/account/business/account/control/AccountControl.java
index 2581371..7ae4d08 100644
--- a/account/src/main/java/de/muehlencord/shared/account/business/account/control/AccountControl.java
+++ b/account/src/main/java/de/muehlencord/shared/account/business/account/control/AccountControl.java
@@ -1,13 +1,13 @@
package de.muehlencord.shared.account.business.account.control;
-import de.muehlencord.shared.account.business.account.entity.AccountException;
-import de.muehlencord.shared.account.business.account.entity.AccountStatus;
-import de.muehlencord.shared.account.business.mail.entity.MailException;
-import de.muehlencord.shared.account.business.mail.boundary.MailService;
import de.muehlencord.shared.account.business.account.entity.AccountEntity;
+import de.muehlencord.shared.account.business.account.entity.AccountException;
import de.muehlencord.shared.account.business.account.entity.AccountLoginEntity;
-import de.muehlencord.shared.account.business.application.entity.ApplicationRoleEntity;
+import de.muehlencord.shared.account.business.account.entity.AccountStatus;
import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
+import de.muehlencord.shared.account.business.application.entity.ApplicationRoleEntity;
+import de.muehlencord.shared.account.business.mail.boundary.MailService;
+import de.muehlencord.shared.account.business.mail.entity.MailException;
import de.muehlencord.shared.account.util.AccountPU;
import de.muehlencord.shared.account.util.SecurityUtil;
import de.muehlencord.shared.util.DateUtil;
@@ -23,10 +23,10 @@ import javax.persistence.NoResultException;
import javax.persistence.Query;
import javax.transaction.Transactional;
import org.apache.commons.lang3.RandomStringUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
*
@@ -47,7 +47,7 @@ public class AccountControl implements Serializable {
@Inject
@AccountPU
EntityManager em;
-
+
/**
* returns a list of active accounts
*
@@ -156,7 +156,7 @@ public class AccountControl implements Serializable {
@Transactional
public void deleteAccount(AccountEntity account) throws AccountException {
- Date now = new Date(); // Todo now in UTC
+ Date now = DateUtil.getCurrentTimeInUTC();
Subject currentUser = SecurityUtils.getSubject();
String currentUserName = currentUser.getPrincipal().toString();
@@ -186,7 +186,7 @@ public class AccountControl implements Serializable {
String randomString = RandomStringUtils.random(40, true, true);
- Date validTo = new Date(); // TODO now in UTC
+ Date validTo = DateUtil.getCurrentTimeInUTC();
validTo = new Date(validTo.getTime() + 1000 * 600); // 10 minutes to react
// TODO rework password reset
@@ -217,7 +217,7 @@ public class AccountControl implements Serializable {
/*
if (account.getPasswordResetOngoing() && (account.getPasswordResetHash() != null) && (account.getPasswordResetValidTo() != null)) {
- Date now = new Date(); // TODO now in UTC
+ Date now = DateUtil.getCurrentTimeInUTC();
String storedHash = account.getPasswordResetHash().trim();
if (account.getPasswordResetValidTo().after(now)) {
if (storedHash.equals(resetPasswordToken)) {
@@ -248,7 +248,7 @@ public class AccountControl implements Serializable {
}
private void executePasswordReset(AccountEntity account, String newPassword) {
- Date now = new Date(); // TODO now in UTC
+ Date now = DateUtil.getCurrentTimeInUTC();
String hashedPassword = SecurityUtil.createPassword(newPassword);
// account.setAccountPassword(hashedPassword);
@@ -294,7 +294,7 @@ public class AccountControl implements Serializable {
public void addLoginError(AccountEntity account) {
// TODO reimplement
// try {
-// Date now = new Date(); // TODO now in UTC
+// Date now = DateUtil.getCurrentTimeInUTC();
// account.setLastFailedLogin(now);
// account.setFailureCount(account.getFailureCount() + 1);
//
@@ -367,5 +367,5 @@ public class AccountControl implements Serializable {
em.remove(login);
em.merge(account);
}
-
+
}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/application/control/ApplicationController.java b/account/src/main/java/de/muehlencord/shared/account/business/application/control/ApplicationController.java
index 512effc..d2b1856 100644
--- a/account/src/main/java/de/muehlencord/shared/account/business/application/control/ApplicationController.java
+++ b/account/src/main/java/de/muehlencord/shared/account/business/application/control/ApplicationController.java
@@ -1,116 +1,114 @@
-/*
- * Copyright 2018 Joern Muehlencord .
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package de.muehlencord.shared.account.business.application.control;
-
-import de.muehlencord.shared.account.business.application.boundary.ApplicationService;
-import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Properties;
-import java.util.UUID;
-import javax.annotation.PostConstruct;
-import javax.ejb.EJB;
-import javax.enterprise.context.ApplicationScoped;
-import javax.enterprise.inject.Produces;
-import javax.inject.Named;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- *
- * @author Joern Muehlencord
- */
-@Named("applicationController")
-@ApplicationScoped
-public class ApplicationController {
-
- private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationController.class);
-
- @EJB
- ApplicationService applicationService;
-
- private String version;
- private String buildDate;
- private UUID uuid;
- private ApplicationEntity application = null;
-
- @PostConstruct
- public void readBuildInfoProperties() {
- if (LOGGER.isTraceEnabled()) {
- LOGGER.trace("Trying to read buildInfo.properties");
- }
- InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("buildInfo.properties");
- if (in == null) {
- return;
- }
- Properties props = new Properties();
- try {
- props.load(in);
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("properties read from buildInfo.properties");
- }
-
- version = props.getProperty("build.version");
- buildDate = props.getProperty("build.timestamp");
- uuid = UUID.fromString(props.getProperty("application.uuid"));
-
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug("buildInfo.properties parsed successfully");
- }
-
- } catch (IOException ex) {
- LOGGER.error("Cannot find buildInfo.properties. ", ex);
- version = "??";
- buildDate = "??";
- uuid = null;
- LOGGER.error("Application id not readable, application will not be able to run");
- }
-
- if (uuid != null) {
- this.application = applicationService.findById(uuid);
- if (application == null) {
- LOGGER.error("Could not find application with id ");
- } else {
- LOGGER.info("Found application {} with id {}", application.getApplicationName(), uuid.toString());
- }
- }
- }
-
- /**
- * needs to return link to "Account UI" and not to current selected
- * application TODO: ensure only Account UI can call functions where
- * appliction can be handed in - all other applications need to call the
- * function which use the injected application
- */
- @Produces
- public ApplicationEntity getApplication() {
- return application;
-
- }
-
- public String getVersion() {
- return version;
- }
-
- public String getBuildDate() {
- return buildDate;
- }
-
- public UUID getApplicationId() {
- return uuid;
- }
-
-}
+/*
+ * Copyright 2018 Joern Muehlencord .
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package de.muehlencord.shared.account.business.application.control;
+
+import de.muehlencord.shared.account.business.application.boundary.ApplicationService;
+import de.muehlencord.shared.account.business.application.entity.ApplicationEntity;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Properties;
+import java.util.UUID;
+import javax.annotation.PostConstruct;
+import javax.ejb.EJB;
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.inject.Produces;
+import javax.inject.Named;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author Joern Muehlencord
+ */
+@Named("applicationController")
+@ApplicationScoped
+public class ApplicationController {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(ApplicationController.class);
+
+ @EJB
+ ApplicationService applicationService;
+
+ private String version;
+ private String buildDate;
+ private UUID uuid;
+ private ApplicationEntity application = null;
+
+ @PostConstruct
+ public void readBuildInfoProperties() {
+ if (LOGGER.isTraceEnabled()) {
+ LOGGER.trace("Trying to read buildInfo.properties");
+ }
+ InputStream in = Thread.currentThread().getContextClassLoader().getResourceAsStream("buildInfo.properties");
+ if (in == null) {
+ return;
+ }
+ Properties props = new Properties();
+ try {
+ props.load(in);
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("properties read from buildInfo.properties");
+ }
+
+ version = props.getProperty("build.version");
+ buildDate = props.getProperty("build.timestamp");
+ uuid = UUID.fromString(props.getProperty("application.uuid"));
+
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("buildInfo.properties parsed successfully");
+ }
+
+ } catch (IOException ex) {
+ LOGGER.error("Cannot find buildInfo.properties. ", ex);
+ version = "??";
+ buildDate = "??";
+ uuid = null;
+ LOGGER.error("Application id not readable, application will not be able to run");
+ }
+
+ if (uuid != null) {
+ this.application = applicationService.findById(uuid);
+ if (application == null) {
+ LOGGER.error("Could not find application with id ");
+ } else {
+ LOGGER.info("Found application {} with id {}", application.getApplicationName(), uuid.toString());
+ }
+ }
+ }
+
+ /**
+ * needs to return link to "Account UI" and not to current selected application
+ * TODO: ensure only Account UI can call functions where appliction can be handed in - all other applications need to call the function which use the injected application
+ */
+ @Produces
+ public ApplicationEntity getApplication() {
+ return application;
+
+ }
+
+ public String getVersion() {
+ return version;
+ }
+
+ public String getBuildDate() {
+ return buildDate;
+ }
+
+ public UUID getApplicationId() {
+ return uuid;
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/business/mail/boundary/MailTemplateService.java b/account/src/main/java/de/muehlencord/shared/account/business/mail/boundary/MailTemplateService.java
index 49b6002..891cb63 100644
--- a/account/src/main/java/de/muehlencord/shared/account/business/mail/boundary/MailTemplateService.java
+++ b/account/src/main/java/de/muehlencord/shared/account/business/mail/boundary/MailTemplateService.java
@@ -1,71 +1,71 @@
-package de.muehlencord.shared.account.business.mail.boundary;
-
-import de.muehlencord.shared.account.business.mail.entity.MailDatamodel;
-import de.muehlencord.shared.account.business.mail.entity.MailTemplateException;
-import de.muehlencord.shared.account.business.mail.entity.MailTemplateEntity;
-import de.muehlencord.shared.account.util.AccountPU;
-import freemarker.cache.StringTemplateLoader;
-import freemarker.template.Configuration;
-import freemarker.template.Template;
-import freemarker.template.TemplateExceptionHandler;
-import java.io.Serializable;
-import java.io.StringWriter;
-import java.io.Writer;
-import javax.ejb.Stateless;
-import javax.inject.Inject;
-import javax.persistence.EntityManager;
-import javax.persistence.Query;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- *
- * @author jomu
- */
-@Stateless
-public class MailTemplateService implements Serializable {
-
- private static final long serialVersionUID = -136113381443058697L;
-
- private static final Logger LOGGER = LoggerFactory.getLogger(MailTemplateService.class.getName());
-
- @Inject
- @AccountPU
- 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.error("Tempate with name " + templateName + " not found");
- return null;
- }
-
- Configuration configuration = new Configuration(Configuration.VERSION_2_3_23);
- configuration.setDefaultEncoding("UTF-8"); // FIXME make encoding configurable
- 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.error(hint + " " + ex.toString());
- if (LOGGER.isDebugEnabled()) {
- LOGGER.debug(hint, ex);
- }
- throw new MailTemplateException(hint, ex);
-
- }
-
- }
-}
+package de.muehlencord.shared.account.business.mail.boundary;
+
+import de.muehlencord.shared.account.business.mail.entity.MailDatamodel;
+import de.muehlencord.shared.account.business.mail.entity.MailTemplateEntity;
+import de.muehlencord.shared.account.business.mail.entity.MailTemplateException;
+import de.muehlencord.shared.account.util.AccountPU;
+import freemarker.cache.StringTemplateLoader;
+import freemarker.template.Configuration;
+import freemarker.template.Template;
+import freemarker.template.TemplateExceptionHandler;
+import java.io.Serializable;
+import java.io.StringWriter;
+import java.io.Writer;
+import javax.ejb.Stateless;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.persistence.Query;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author jomu
+ */
+@Stateless
+public class MailTemplateService implements Serializable {
+
+ private static final long serialVersionUID = -136113381443058697L;
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(MailTemplateService.class.getName());
+
+ @Inject
+ @AccountPU
+ 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.error("Tempate with name " + templateName + " not found");
+ return null;
+ }
+
+ Configuration configuration = new Configuration(Configuration.VERSION_2_3_23);
+ configuration.setDefaultEncoding("UTF-8"); // FIXME - make encoding configurable
+ 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.error(hint + " " + ex.toString());
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(hint, ex);
+ }
+ throw new MailTemplateException(hint, ex);
+
+ }
+
+ }
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/shiro/authc/JwtMatcher.java b/account/src/main/java/de/muehlencord/shared/account/shiro/authc/JwtMatcher.java
new file mode 100644
index 0000000..ffbb432
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/shiro/authc/JwtMatcher.java
@@ -0,0 +1,109 @@
+/*
+ * 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.shiro.authc;
+
+import de.muehlencord.shared.account.business.account.boundary.ApiKeyService;
+import de.muehlencord.shared.account.business.account.entity.JWTObject;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.credential.CredentialsMatcher;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+public class JwtMatcher implements CredentialsMatcher {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(JwtMatcher.class);
+ private final ApiKeyService apiKeyService = lookupApiKeyServiceBean();
+
+ @Override
+ public boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {
+ if (token == null) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("No token available - cannot match credentials");
+ }
+ return false;
+ }
+
+ if ((info == null) || (info.getCredentials() == null)) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("No authenticationInfo available - cannot match credentials");
+ }
+ return false;
+ }
+
+ Object submittedJwtObj = token.getCredentials();
+ Object storedCredentials = getStoredPassword(info);
+ if ((submittedJwtObj != null) && (submittedJwtObj.getClass().isAssignableFrom(String.class))) {
+ String submittedJwt = (String) submittedJwtObj;
+ if (apiKeyService.validateJWT(submittedJwt)) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("JWT is valid, checking if it comes from the correct user");
+ }
+
+ JWTObject jwtObject = apiKeyService.getJWTObject(submittedJwt);
+ String storedUsername = info.getPrincipals().getPrimaryPrincipal().toString();
+ if (jwtObject.getUserName().equals(storedUsername)) {
+ if (jwtObject.getUnqiueId().equals (storedCredentials)) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("JWT matches user and password is correct");
+ }
+ return true;
+ } else {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("JWT password does not match provided password");
+ }
+ return false;
+ }
+ } else {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("JWT belongs to user {}, but authinfo is from user {} - JWT does not match");
+ }
+ return false;
+ }
+ } else {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("JWT is invalid");
+ }
+ return false;
+ }
+ } else {
+ LOGGER.error("Unexpected authInfoFormat - please check your configuration");
+ return false;
+ }
+ }
+
+ protected Object getStoredPassword(AuthenticationInfo storedAccountInfo) {
+ Object stored = storedAccountInfo != null ? storedAccountInfo.getCredentials() : null;
+ //fix for https://issues.apache.org/jira/browse/SHIRO-363
+ if (stored instanceof char[]) {
+ stored = new String((char[]) stored);
+ }
+ return stored;
+ }
+
+ // TODO - can this be injected?
+ private ApiKeyService lookupApiKeyServiceBean() {
+ try {
+ Context c = new InitialContext();
+ return (ApiKeyService) c.lookup("java:module/ApiKeyService"); // NOI18N
+ } catch (NamingException ex) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(ex.toString(), ex);
+ } else {
+ LOGGER.error(ex.toString());
+ }
+ throw new RuntimeException(ex);
+ }
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/shiro/filter/JWTAuthenticationFilter.java b/account/src/main/java/de/muehlencord/shared/account/shiro/filter/JWTAuthenticationFilter.java
new file mode 100644
index 0000000..28a5de7
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/shiro/filter/JWTAuthenticationFilter.java
@@ -0,0 +1,119 @@
+package de.muehlencord.shared.account.shiro.filter;
+
+import de.muehlencord.shared.account.business.account.boundary.ApiKeyService;
+import de.muehlencord.shared.account.business.account.entity.JWTObject;
+import de.muehlencord.shared.account.shiro.token.JWTAuthenticationToken;
+import java.io.StringReader;
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.naming.Context;
+import javax.naming.InitialContext;
+import javax.naming.NamingException;
+import javax.servlet.ServletRequest;
+import javax.servlet.ServletResponse;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.io.IOUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
+import org.apache.shiro.web.util.WebUtils;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author Joern Muehlencord
+ */
+public final class JWTAuthenticationFilter extends AuthenticatingFilter {
+
+ private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(JWTAuthenticationFilter.class);
+ protected static final String AUTHORIZATION_HEADER = "Authorization"; // NOI18N
+
+ public static final String USERNAME = "username";
+ public static final String PASSWORD = "password";
+ private final ApiKeyService apiKeyService = lookupApiKeyServiceBean();
+
+ public JWTAuthenticationFilter() {
+ // FIXME - logging in via JWTAuthenticationFilter does not yet work. Need to set login to anonymous to execute login in rest service
+ setLoginUrl("/rest/account/login");
+ }
+
+ @Override
+ protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
+ boolean loggedIn = false;
+
+ if (isLoginRequest(request, response) || isLoggedAttempt(request, response)) {
+ loggedIn = executeLogin(request, response);
+ }
+
+ if (!loggedIn) {
+ HttpServletResponse httpResponse = WebUtils.toHttp(response);
+ httpResponse.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+ }
+
+ return loggedIn;
+ }
+
+ @Override
+ protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
+ if (isLoginRequest(request, response)) {
+ String json = IOUtils.toString(request.getInputStream(), "UTF-8"); // FIXME - check charset in request
+
+ if (json != null && !json.isEmpty()) {
+ try (JsonReader jr = Json.createReader(new StringReader(json))) {
+ JsonObject object = jr.readObject();
+ String username = object.getString(USERNAME);
+ String password = object.getString(PASSWORD);
+ return new UsernamePasswordToken(username, password);
+ }
+
+ }
+ }
+
+ if (isLoggedAttempt(request, response)) {
+ String jwtToken = getAuthzHeader(request);
+ if (jwtToken != null) {
+ return createToken(jwtToken);
+ }
+ }
+
+ return new UsernamePasswordToken();
+ }
+
+ protected boolean isLoggedAttempt(ServletRequest request, ServletResponse response) {
+ String authzHeader = getAuthzHeader(request);
+ return authzHeader != null;
+ }
+
+ protected String getAuthzHeader(ServletRequest request) {
+ HttpServletRequest httpRequest = WebUtils.toHttp(request);
+ return httpRequest.getHeader(AUTHORIZATION_HEADER);
+ }
+
+ public JWTAuthenticationToken createToken(String token) {
+ if (apiKeyService.validateJWT(token)) {
+ JWTObject jwtObject = apiKeyService.getJWTObject(token);
+ return new JWTAuthenticationToken(jwtObject.getUserName(), token);
+ } else {
+ throw new AuthenticationException("provided API key invalid");
+ }
+ }
+
+ // TODO - can this be injected?
+ private ApiKeyService lookupApiKeyServiceBean() {
+ try {
+ Context c = new InitialContext();
+ return (ApiKeyService) c.lookup("java:module/ApiKeyService"); // NOI18N
+ } catch (NamingException ex) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(ex.toString(), ex);
+ } else {
+ LOGGER.error(ex.toString());
+ }
+ throw new RuntimeException(ex);
+ }
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/shiro/realm/AccountRealm.java b/account/src/main/java/de/muehlencord/shared/account/shiro/realm/AccountRealm.java
new file mode 100644
index 0000000..40bdb85
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/shiro/realm/AccountRealm.java
@@ -0,0 +1,254 @@
+package de.muehlencord.shared.account.shiro.realm;
+
+import de.muehlencord.shared.account.shiro.authc.JwtMatcher;
+import de.muehlencord.shared.account.shiro.token.JWTAuthenticationToken;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Collection;
+import java.util.LinkedHashSet;
+import java.util.Set;
+import java.util.UUID;
+import org.apache.shiro.authc.AccountException;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.AuthenticationInfo;
+import org.apache.shiro.authc.AuthenticationToken;
+import org.apache.shiro.authc.IncorrectCredentialsException;
+import org.apache.shiro.authc.SimpleAuthenticationInfo;
+import org.apache.shiro.authc.UnknownAccountException;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.authc.credential.AllowAllCredentialsMatcher;
+import org.apache.shiro.authc.credential.CredentialsMatcher;
+import org.apache.shiro.realm.jdbc.JdbcRealm;
+import org.apache.shiro.util.JdbcUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+public class AccountRealm extends JdbcRealm {
+
+ private static final Logger LOGGER = LoggerFactory.getLogger(AccountRealm.class);
+
+ protected String applicationId = null;
+ protected String jwtAuthenticationQuery = "select ak.api_key from account a, api_key ak where ak.account = a.id and a.username = ? and a.status not in ('LOCKED','DELETED','DISABLED') ORDER BY ak.issued_on ASC";
+ protected CredentialsMatcher jwtMatcher = new JwtMatcher();
+
+ public AccountRealm() {
+ this.authenticationQuery = "select al.account_password from account a, account_login al where al.account = a.id and a.username = ? and status not in ('LOCKED','DELETED','DISABLED')";
+ this.userRolesQuery = "select r.role_name from application_role r, account_role ar, account a WHERE a.username = ? AND a.id = ar.account AND ar.account_role = r.id";
+ this.permissionsQuery = "select permission_name from application_role appr, role_permission rp, application_permission appp WHERE appr.role_name = ? AND appr.application = ? AND rp.application_role = appr.id AND rp.role_permission = appp.id";
+ this.permissionsLookupEnabled = true;
+ }
+
+ @Override
+ public boolean supports(AuthenticationToken token) {
+ super.supports(token);
+ return token != null && (token instanceof JWTAuthenticationToken || token instanceof UsernamePasswordToken);
+ }
+
+ public boolean isJwtAuthentication(AuthenticationToken token) {
+ if (token == null) {
+ throw new AuthenticationException("empty tokens are not supported by this realm");
+ }
+
+ if (token.getClass().isAssignableFrom(JWTAuthenticationToken.class)) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Handling JWTAuthenticationToken");
+ }
+ return true;
+ } else if (token.getClass().isAssignableFrom(UsernamePasswordToken.class)) {
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Handling UsernamePasswordToken");
+ }
+ return false;
+ } else {
+ throw new AuthenticationException("Handling of " + token.getClass() + " not supported by this realm");
+ }
+ }
+
+ /**
+ * overwritten getPermissions. Only change is to inject the applicationId
+ * into the the query
+ *
+ * @param conn the connection to use
+ * @param username the user to lookup
+ * @param roleNames the users roles
+ * @return a list of permissions
+ * @throws SQLException if the SQL query fails
+ */
+ @Override
+ protected Set getPermissions(Connection conn, String username, Collection roleNames) throws SQLException {
+ PreparedStatement ps = null;
+ Set permissions = new LinkedHashSet<>();
+ try {
+ ps = conn.prepareStatement(permissionsQuery);
+ for (String roleName : roleNames) {
+
+ ps.setString(1, roleName);
+ ps.setObject(2, UUID.fromString(applicationId)); // this is the changed line - rest is the same as in JDBCRealm
+
+ ResultSet rs = null;
+
+ try {
+ // Execute query
+ rs = ps.executeQuery();
+
+ // Loop over results and add each returned role to a set
+ while (rs.next()) {
+
+ String permissionString = rs.getString(1);
+
+ // Add the permission to the set of permissions
+ permissions.add(permissionString);
+ }
+ } finally {
+ JdbcUtils.closeResultSet(rs);
+ }
+
+ }
+ } finally {
+ JdbcUtils.closeStatement(ps);
+ }
+
+ return permissions;
+ }
+
+ @Override
+ protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+ if (isJwtAuthentication(token)) {
+ return doGetJwtAuthenticationInfo(token);
+ } else {
+ return super.doGetAuthenticationInfo(token);
+ }
+ }
+
+ protected AuthenticationInfo doGetJwtAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
+ JWTAuthenticationToken jwtToken = (JWTAuthenticationToken) token;
+ String username = jwtToken.getUserName();
+
+ // Null username is invalid
+ if (username == null) {
+ throw new AccountException("Null usernames are not allowed by this realm.");
+ }
+
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug("Checking JWT for user {}", username);
+ }
+
+ Connection conn = null;
+ SimpleAuthenticationInfo info = null;
+ try {
+ conn = dataSource.getConnection();
+
+ String apiKey = getApiKeyForJwtUser(conn, username);
+
+ if (apiKey == null) {
+ throw new UnknownAccountException("No api key found for user [" + username + "]");
+ }
+
+ info = new SimpleAuthenticationInfo(username, apiKey.toCharArray(), getName());
+
+ } catch (SQLException ex) {
+ final String message = "There was a SQL error while authenticating user [" + username + "]";
+ if (LOGGER.isDebugEnabled()) {
+ LOGGER.debug(message, ex);
+ } else {
+ LOGGER.error(ex.toString());
+ }
+ // Rethrow any SQL errors as an authentication exception
+ throw new AuthenticationException(message, ex);
+ } finally {
+ JdbcUtils.closeConnection(conn);
+ }
+
+ return info;
+ }
+
+ private String getApiKeyForJwtUser(Connection conn, String username) throws SQLException {
+
+ String result = null;
+
+ PreparedStatement ps = null;
+ ResultSet rs = null;
+ try {
+ ps = conn.prepareStatement(jwtAuthenticationQuery);
+ ps.setString(1, username);
+
+ // Execute query
+ rs = ps.executeQuery();
+ // loop through result, take last one (by default ordered by issue date ASC)
+ // we only expect one - application should delete all obsolete ones automatically
+ while (rs.next()) {
+ result = rs.getString(1);
+ }
+ } finally {
+ JdbcUtils.closeResultSet(rs);
+ JdbcUtils.closeStatement(ps);
+ }
+
+ return result;
+ }
+
+ /**
+ * Asserts that the submitted {@code AuthenticationToken}'s credentials
+ * match the stored account {@code AuthenticationInfo}'s credentials, and if
+ * not, throws an {@link AuthenticationException}.
+ *
+ * @param token the submitted authentication token
+ * @param info the AuthenticationInfo corresponding to the given
+ * {@code token}
+ * @throws AuthenticationException if the token's credentials do not match
+ * the stored account credentials.
+ */
+ @Override
+ protected void assertCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) throws AuthenticationException {
+ CredentialsMatcher cm;
+ if (isJwtAuthentication(token)) {
+ cm = getJwtMatcher();
+ } else {
+ cm = getCredentialsMatcher();
+ }
+
+ if (cm != null) {
+ if (!cm.doCredentialsMatch(token, info)) {
+ //not successful - throw an exception to indicate this:
+ String msg = "Submitted credentials for token [" + token + "] did not match the expected credentials.";
+ throw new IncorrectCredentialsException(msg);
+ }
+ } else {
+ throw new AuthenticationException("A CredentialsMatcher must be configured in order to verify "
+ + "credentials during authentication. If you do not wish for credentials to be examined, you "
+ + "can configure an " + AllowAllCredentialsMatcher.class.getName() + " instance.");
+ }
+ }
+
+ /* *** getter / setter *** */
+ public String getApplicationId() {
+ return this.applicationId;
+ }
+
+ public void setApplicationId(String applicationId) {
+ this.applicationId = applicationId;
+ }
+
+ public String getJwtAuthenticationQuery() {
+ return jwtAuthenticationQuery;
+ }
+
+ public void setJwtAuthenticationQuery(String jwtAuthenticationQuery) {
+ this.jwtAuthenticationQuery = jwtAuthenticationQuery;
+ }
+
+ public CredentialsMatcher getJwtMatcher() {
+ return jwtMatcher;
+ }
+
+ public void setJwtMatcher(CredentialsMatcher jwtMatcher) {
+ this.jwtMatcher = jwtMatcher;
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/util/UserNameActiveDirectoryRealm.java b/account/src/main/java/de/muehlencord/shared/account/shiro/realm/UserNameActiveDirectoryRealm.java
similarity index 99%
rename from account/src/main/java/de/muehlencord/shared/account/util/UserNameActiveDirectoryRealm.java
rename to account/src/main/java/de/muehlencord/shared/account/shiro/realm/UserNameActiveDirectoryRealm.java
index 3f8a4d1..f2f825e 100644
--- a/account/src/main/java/de/muehlencord/shared/account/util/UserNameActiveDirectoryRealm.java
+++ b/account/src/main/java/de/muehlencord/shared/account/shiro/realm/UserNameActiveDirectoryRealm.java
@@ -1,4 +1,4 @@
-package de.muehlencord.shared.account.util;
+package de.muehlencord.shared.account.shiro.realm;
import java.util.HashSet;
import java.util.Set;
diff --git a/account/src/main/java/de/muehlencord/shared/account/shiro/token/JWTAuthenticationToken.java b/account/src/main/java/de/muehlencord/shared/account/shiro/token/JWTAuthenticationToken.java
new file mode 100644
index 0000000..60faed7
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/shiro/token/JWTAuthenticationToken.java
@@ -0,0 +1,50 @@
+/*
+ * 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.shiro.token;
+
+import org.apache.shiro.authc.AuthenticationToken;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+public class JWTAuthenticationToken implements AuthenticationToken {
+
+ private String userName;
+ private String token;
+
+ public JWTAuthenticationToken(String userId, String token) {
+ this.userName = userId;
+ this.token = token;
+ }
+
+ @Override
+ public String getPrincipal() {
+ return getUserName();
+ }
+
+ @Override
+ public String getCredentials() {
+ return getToken();
+ }
+
+ public String getUserName() {
+ return userName;
+ }
+
+ public void setUserName(String userId) {
+ this.userName = userId;
+ }
+
+ public String getToken() {
+ return token;
+ }
+
+ public void setToken(String token) {
+ this.token = token;
+ }
+
+}
diff --git a/account/src/main/java/de/muehlencord/shared/account/util/AccountSecurityExceptionMapper.java b/account/src/main/java/de/muehlencord/shared/account/util/AccountSecurityExceptionMapper.java
new file mode 100644
index 0000000..37467b3
--- /dev/null
+++ b/account/src/main/java/de/muehlencord/shared/account/util/AccountSecurityExceptionMapper.java
@@ -0,0 +1,22 @@
+/*
+ * 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.util;
+
+import javax.ws.rs.core.Response;
+import javax.ws.rs.ext.ExceptionMapper;
+
+/**
+ *
+ * @author joern.muehlencord
+ */
+public class AccountSecurityExceptionMapper implements ExceptionMapper {
+
+ @Override
+ public Response toResponse(AccountSecurityException ex) {
+ return Response.status(Response.Status.UNAUTHORIZED).entity(ex.getMessage()).build();
+ }
+
+}
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
index 97a2f73..5c9ec56 100644
--- a/account/src/main/java/de/muehlencord/shared/account/util/SecurityUtil.java
+++ b/account/src/main/java/de/muehlencord/shared/account/util/SecurityUtil.java
@@ -1,12 +1,12 @@
package de.muehlencord.shared.account.util;
import org.apache.shiro.SecurityUtils;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.apache.shiro.authc.credential.DefaultPasswordService;
import org.apache.shiro.crypto.hash.DefaultHashService;
import org.apache.shiro.crypto.hash.Sha512Hash;
import org.apache.shiro.subject.Subject;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
/**
*
diff --git a/account/src/test/java/de/muehlencord/shared/account/util/UserNameActiveDirectoryRealmTest.java b/account/src/test/java/de/muehlencord/shared/account/shiro/realm/UserNameActiveDirectoryRealmTest.java
similarity index 94%
rename from account/src/test/java/de/muehlencord/shared/account/util/UserNameActiveDirectoryRealmTest.java
rename to account/src/test/java/de/muehlencord/shared/account/shiro/realm/UserNameActiveDirectoryRealmTest.java
index 8f84201..3a8064b 100644
--- a/account/src/test/java/de/muehlencord/shared/account/util/UserNameActiveDirectoryRealmTest.java
+++ b/account/src/test/java/de/muehlencord/shared/account/shiro/realm/UserNameActiveDirectoryRealmTest.java
@@ -1,53 +1,53 @@
-package de.muehlencord.shared.account.util;
-
-import org.apache.shiro.SecurityUtils;
-import org.apache.shiro.authc.AuthenticationException;
-import org.apache.shiro.authc.UsernamePasswordToken;
-import org.apache.shiro.config.IniSecurityManagerFactory;
-import org.apache.shiro.subject.Subject;
-import org.junit.Test;
-import org.apache.shiro.mgt.SecurityManager;
-import static org.junit.Assume.assumeNotNull;
-
-/**
- *
- * @author Joern Muehlencord
- */
-public class UserNameActiveDirectoryRealmTest {
-
- @Test
- public void testUsernameLogin() {
- String userName = "user.name";
- String password = "secret";
- testLogin(userName, password);
- }
-
- @Test
- public void testEmailaddressLogin() {
- String userName = "user.name@domain.com";
- String password = "secret";
- testLogin(userName, password);
- }
-
- @Test(expected=AuthenticationException.class)
- public void testWrongUserNamePassword() {
- String userName = "test123";
- String password = "secret";
- testLogin(userName, password);
- }
-
- private void testLogin(String userName, String password) throws AuthenticationException {
- assumeNotNull(UserNameActiveDirectoryRealmTest.class.getResource("/shiro.ini"));
-
- IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
- SecurityManager securityManager = factory.getInstance();
- SecurityUtils.setSecurityManager(securityManager);
-
- UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
- Subject currentUser = SecurityUtils.getSubject();
-
- currentUser.login(token);
- System.out.println("Logged in");
- }
-
-}
+package de.muehlencord.shared.account.shiro.realm;
+
+import org.apache.shiro.SecurityUtils;
+import org.apache.shiro.authc.AuthenticationException;
+import org.apache.shiro.authc.UsernamePasswordToken;
+import org.apache.shiro.config.IniSecurityManagerFactory;
+import org.apache.shiro.subject.Subject;
+import org.junit.Test;
+import org.apache.shiro.mgt.SecurityManager;
+import static org.junit.Assume.assumeNotNull;
+
+/**
+ *
+ * @author Joern Muehlencord
+ */
+public class UserNameActiveDirectoryRealmTest {
+
+ @Test
+ public void testUsernameLogin() {
+ String userName = "user.name";
+ String password = "secret";
+ testLogin(userName, password);
+ }
+
+ @Test
+ public void testEmailaddressLogin() {
+ String userName = "user.name@domain.com";
+ String password = "secret";
+ testLogin(userName, password);
+ }
+
+ @Test(expected=AuthenticationException.class)
+ public void testWrongUserNamePassword() {
+ String userName = "test123";
+ String password = "secret";
+ testLogin(userName, password);
+ }
+
+ private void testLogin(String userName, String password) throws AuthenticationException {
+ assumeNotNull(UserNameActiveDirectoryRealmTest.class.getResource("/shiro.ini"));
+
+ IniSecurityManagerFactory factory = new IniSecurityManagerFactory("classpath:shiro.ini");
+ SecurityManager securityManager = factory.getInstance();
+ SecurityUtils.setSecurityManager(securityManager);
+
+ UsernamePasswordToken token = new UsernamePasswordToken(userName, password);
+ Subject currentUser = SecurityUtils.getSubject();
+
+ currentUser.login(token);
+ System.out.println("Logged in");
+ }
+
+}