4 min read
package org.example.kickoff.config;
import static java.text.MessageFormat.format;
import static java.util.ResourceBundle.getBundle;
import static org.example.kickoff.model.Group.ADMIN;
import static org.omnifaces.util.Faces.getLocale;
import static org.omnifaces.utils.Lang.isEmpty;
import java.util.ResourceBundle;
import javax.annotation.PostConstruct;
import javax.inject.Inject;
import org.example.kickoff.business.service.UserService;
import org.example.kickoff.model.User;
import org.omnifaces.cdi.Startup;
import org.omnifaces.util.Messages;
@Startup
public class StartupBean {
@Inject
private UserService userService;
@PostConstruct
public void init() {
setupTestUsers();
configureMessageResolver();
}
private void setupTestUsers() {
if (!userService.findByEmail("admin@kickoff.example.org").isPresent()) {
User user = new User();
user.setFirstName("Test");
user.setLastName("Admin");
user.setEmail("admin@kickoff.example.org");
userService.register(user, "passw0rd", ADMIN);
}
if (!userService.findByEmail("user@kickoff.example.org").isPresent()) {
User user = new User();
user.setFirstName("Test");
user.setLastName("User");
user.setEmail("user@kickoff.example.org");
userService.register(user, "passw0rd");
}
}
private void configureMessageResolver() {
Messages.setResolver(new Messages.Resolver() {
private static final String BASE_NAME = "org.example.kickoff.i18n.ApplicationBundle";
@Override
public String getMessage(String message, Object... params) {
ResourceBundle bundle = getBundle(BASE_NAME, getLocale());
if (bundle.containsKey(message)) {
message = bundle.getString(message);
}
return isEmpty(params) ? message : format(message, params);
}
});
}
}
package org.example.kickoff.config;
import java.time.Instant;
import java.util.Date;
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
/**
* Converts the JDK 8 / JSR 310 <code>Instant</code> type to <code>Date</code> for
* usage with JPA.
*
* <p>
* This converter is still necessary for Java EE 8 / JPA 2.2, since Instant is one of the
* "forgotten" types.
*
* See https://github.com/eclipse-ee4j/jpa-api/issues/163
*
* @author Arjan Tijms
*/
@Converter(autoApply = true)
public class InstantConverter implements AttributeConverter<Instant, Date> {
@Override
public Date convertToDatabaseColumn(Instant instant) {
if (instant == null) {
return null;
}
return Date.from(instant);
}
@Override
public Instant convertToEntityAttribute(Date date) {
if (date == null) {
return null;
}
return date.toInstant();
}
}
package org.example.kickoff.config;
import static java.util.logging.Logger.getLogger;
import static org.omnifaces.utils.properties.PropertiesUtils.loadPropertiesFromClasspath;
import static org.omnifaces.utils.properties.PropertiesUtils.loadXMLPropertiesStagedFromClassPath;
import java.util.Map;
import java.util.logging.Logger;
import org.omnifaces.persistence.datasource.PropertiesFileLoader;
/**
* Adaptor for the switchable datasource as defined in web.xml to be able to read properties from the
* right file.
*/
public class DataSourceStagedPropertiesFileLoader implements PropertiesFileLoader {
private static final Logger logger = getLogger(DataSourceStagedPropertiesFileLoader.class.getName());
@Override
public Map<String, String> loadFromFile(String fileName) {
// Make sure we use the same names as the application settings are using
Map<String, String> omniSettings = loadPropertiesFromClasspath("META-INF/omni-settings");
Map<String, String> dataSourceProperties = loadXMLPropertiesStagedFromClassPath(
fileName,
omniSettings.getOrDefault("stageSystemPropertyName", "omni.stage"),
omniSettings.get("defaultStage"));
logger.info(
"\n\nAbout to install DataSource. \n" +
"Classname: " + dataSourceProperties.get("className") + "\n" +
"URL: " + dataSourceProperties.getOrDefault("url", dataSourceProperties.get("URL") + "\n" +
"See META-INF/conf/" + fileName + " for details. \n" +
"\n\n")
);
return dataSourceProperties;
}
}
/src/main/java/org/example/kickoff/config/auth/
package org.example.kickoff.config.auth;
import javax.security.enterprise.CallerPrincipal;
import org.example.kickoff.model.User;
/**
* @see KickoffIdentityStore
* @see ActiveUser
*/
public class KickoffCallerPrincipal extends CallerPrincipal {
private final User user;
public KickoffCallerPrincipal(User user) {
super(user.getEmail());
this.user = user;
}
public User getUser() {
return user;
}
}
package org.example.kickoff.config.auth;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.security.enterprise.AuthenticationStatus;
import javax.security.enterprise.authentication.mechanism.http.AutoApplySession;
import javax.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism;
import javax.security.enterprise.authentication.mechanism.http.HttpMessageContext;
import javax.security.enterprise.authentication.mechanism.http.LoginToContinue;
import javax.security.enterprise.authentication.mechanism.http.RememberMe;
import javax.security.enterprise.credential.Credential;
import javax.security.enterprise.identitystore.IdentityStoreHandler;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* Authentication mechanism that authenticates according to the Servlet spec defined FORM authentication mechanism.
* See Servlet spec for further details.
*
* @author Arjan Tijms
*/
@AutoApplySession // For "Is user already logged-in?"
@RememberMe(
cookieSecureOnly = false, // Remove this when login is served over HTTPS.
cookieMaxAgeSeconds = 60 * 60 * 24 * 14) // 14 days.
@LoginToContinue(
loginPage = "/login?continue=true",
errorPage = "",
useForwardToLogin = false)
@ApplicationScoped
public class KickoffFormAuthenticationMechanism implements HttpAuthenticationMechanism {
@Inject
private IdentityStoreHandler identityStoreHandler;
@Override
public AuthenticationStatus validateRequest(HttpServletRequest request, HttpServletResponse response, HttpMessageContext context) {
Credential credential = context.getAuthParameters().getCredential();
if (credential != null) {
return context.notifyContainerAboutLogin(identityStoreHandler.validate(credential));
}
else {
return context.doNothing();
}
}
}
package org.example.kickoff.config.auth;
import static javax.security.enterprise.identitystore.CredentialValidationResult.INVALID_RESULT;
import static javax.security.enterprise.identitystore.CredentialValidationResult.NOT_VALIDATED_RESULT;
import static org.example.kickoff.model.Group.USER;
import java.util.function.Supplier;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.security.enterprise.credential.CallerOnlyCredential;
import javax.security.enterprise.credential.Credential;
import javax.security.enterprise.credential.UsernamePasswordCredential;
import javax.security.enterprise.identitystore.CredentialValidationResult;
import javax.security.enterprise.identitystore.IdentityStore;
import org.example.kickoff.business.exception.EmailNotVerifiedException;
import org.example.kickoff.business.exception.InvalidGroupException;
import org.example.kickoff.business.exception.CredentialsException;
import org.example.kickoff.business.service.UserService;
import org.example.kickoff.model.User;
@ApplicationScoped
public class KickoffIdentityStore implements IdentityStore {
@Inject
private UserService userService;
@Override
public CredentialValidationResult validate(Credential credential) {
Supplier<User> userSupplier = null;
if (credential instanceof UsernamePasswordCredential) {
String email = ((UsernamePasswordCredential) credential).getCaller();
String password = ((UsernamePasswordCredential) credential).getPasswordAsString();
userSupplier = () -> userService.getByEmailAndPassword(email, password);
}
else if (credential instanceof CallerOnlyCredential) {
String email = ((CallerOnlyCredential) credential).getCaller();
userSupplier = () -> userService.getByEmail(email);
}
return validate(userSupplier);
}
static CredentialValidationResult validate(Supplier<User> userSupplier) {
if (userSupplier == null) {
return NOT_VALIDATED_RESULT;
}
try {
User user = userSupplier.get();
if (!user.getGroups().contains(USER)) {
throw new InvalidGroupException();
}
if (!user.isEmailVerified()) {
throw new EmailNotVerifiedException();
}
return new CredentialValidationResult(new KickoffCallerPrincipal(user), user.getRolesAsStrings());
}
catch (CredentialsException e) {
return INVALID_RESULT;
}
}
}
package org.example.kickoff.config.auth;
import static org.example.kickoff.model.LoginToken.TokenType.REMEMBER_ME;
import static org.omnifaces.util.Servlets.getRemoteAddr;
import java.util.Set;
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.security.enterprise.CallerPrincipal;
import javax.security.enterprise.credential.RememberMeCredential;
import javax.security.enterprise.identitystore.CredentialValidationResult;
import javax.security.enterprise.identitystore.RememberMeIdentityStore;
import javax.servlet.http.HttpServletRequest;
import org.example.kickoff.business.service.LoginTokenService;
import org.example.kickoff.business.service.UserService;
@ApplicationScoped
public class KickoffRememberMeIdentityStore implements RememberMeIdentityStore {
@Inject
private HttpServletRequest request;
@Inject
private UserService userService;
@Inject
private LoginTokenService loginTokenService;
@Override
public CredentialValidationResult validate(RememberMeCredential credential) {
return KickoffIdentityStore.validate(() -> userService.getByLoginToken(credential.getToken(), REMEMBER_ME));
}
@Override
public String generateLoginToken(CallerPrincipal callerPrincipal, Set<String> groups) {
String ipAddress = getRemoteAddr(request);
String description = "Remember me session for " + ipAddress + " on " + request.getHeader("User-Agent");
return loginTokenService.generate(callerPrincipal.getName(), ipAddress, description, REMEMBER_ME);
}
@Override
public void removeLoginToken(String loginToken) {
loginTokenService.remove(loginToken);
}
}