diff --git a/app/adapters/web/pom.xml b/app/adapters/web/pom.xml index eb73c15..d33c739 100644 --- a/app/adapters/web/pom.xml +++ b/app/adapters/web/pom.xml @@ -31,13 +31,17 @@ </dependency> <dependency> - <groupId>jakarta.faces</groupId> - <artifactId>jakarta.faces-api</artifactId> + <groupId>jakarta.enterprise</groupId> + <artifactId>jakarta.enterprise.cdi-api</artifactId> <scope>provided</scope> </dependency> <dependency> - <groupId>jakarta.enterprise</groupId> - <artifactId>jakarta.enterprise.cdi-api</artifactId> + <groupId>jakarta.el</groupId> + <artifactId>jakarta.el-api</artifactId> + </dependency> + <dependency> + <groupId>jakarta.faces</groupId> + <artifactId>jakarta.faces-api</artifactId> <scope>provided</scope> </dependency> <dependency> @@ -45,6 +49,17 @@ <artifactId>jakarta.inject-api</artifactId> <scope>provided</scope> </dependency> + <dependency> + <groupId>jakarta.security.enterprise</groupId> + <artifactId>jakarta.security.enterprise-api</artifactId> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>jakarta.servlet</groupId> + <artifactId>jakarta.servlet-api</artifactId> + <scope>provided</scope> + </dependency> + <dependency> <groupId>org.primefaces</groupId> <artifactId>primefaces</artifactId> diff --git a/app/adapters/web/src/main/java/it/mulders/traqqr/web/security/OidcConfig.java b/app/adapters/web/src/main/java/it/mulders/traqqr/web/security/OidcConfig.java new file mode 100644 index 0000000..417abfb --- /dev/null +++ b/app/adapters/web/src/main/java/it/mulders/traqqr/web/security/OidcConfig.java @@ -0,0 +1,25 @@ +package it.mulders.traqqr.web.security; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Named; + +@ApplicationScoped +@Named("oidcConfig") +public class OidcConfig { + private final String clientId; + private final String clientSecret; + + public OidcConfig() { + var environment = System.getenv(); + this.clientId = environment.get("OPENID_CLIENT_ID"); + this.clientSecret = environment.get("OPENID_CLIENT_SECRET"); + } + + public String getClientId() { + return clientId; + } + + public String getClientSecret() { + return clientSecret; + } +} diff --git a/app/adapters/web/src/main/java/it/mulders/traqqr/web/security/SecurityConfiguration.java b/app/adapters/web/src/main/java/it/mulders/traqqr/web/security/SecurityConfiguration.java new file mode 100644 index 0000000..7dd650c --- /dev/null +++ b/app/adapters/web/src/main/java/it/mulders/traqqr/web/security/SecurityConfiguration.java @@ -0,0 +1,23 @@ +package it.mulders.traqqr.web.security; + +import jakarta.security.enterprise.authentication.mechanism.http.OpenIdAuthenticationMechanismDefinition; +import jakarta.security.enterprise.authentication.mechanism.http.openid.ClaimsDefinition; +import jakarta.security.enterprise.authentication.mechanism.http.openid.LogoutDefinition; + +@OpenIdAuthenticationMechanismDefinition( + claimsDefinition = @ClaimsDefinition( + callerNameClaim = "sub" + ), + clientId = "${oidcConfig.clientId}", + clientSecret = "${oidcConfig.clientSecret}", + logout = @LogoutDefinition( + redirectURI = "${baseURL}" + ), + providerURI = "https://accounts.google.com/.well-known/openid-configuration", + redirectURI = "${baseURL}/auth/callback/google", + redirectToOriginalResource = true, + useNonce = true, + useSession = true +) +public class SecurityConfiguration { +} diff --git a/app/adapters/web/src/main/java/it/mulders/traqqr/web/security/TraqqrIdentityStore.java b/app/adapters/web/src/main/java/it/mulders/traqqr/web/security/TraqqrIdentityStore.java new file mode 100644 index 0000000..ca386bc --- /dev/null +++ b/app/adapters/web/src/main/java/it/mulders/traqqr/web/security/TraqqrIdentityStore.java @@ -0,0 +1,26 @@ +package it.mulders.traqqr.web.security; + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.security.enterprise.identitystore.CredentialValidationResult; +import jakarta.security.enterprise.identitystore.IdentityStore; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.EnumSet; +import java.util.Set; + +@ApplicationScoped +public class TraqqrIdentityStore implements IdentityStore { + private static final Logger log = LoggerFactory.getLogger(TraqqrIdentityStore.class); + + @Override + public Set<String> getCallerGroups(final CredentialValidationResult validationResult) { + log.info("Assigning groups; caller_unique_id={}", validationResult.getCallerUniqueId()); + return Set.of("user"); + } + + @Override + public Set<ValidationType> validationTypes() { + return EnumSet.of(ValidationType.PROVIDE_GROUPS); + } +} diff --git a/app/adapters/web/src/main/java/it/mulders/traqqr/web/user/UserInfoBean.java b/app/adapters/web/src/main/java/it/mulders/traqqr/web/user/UserInfoBean.java new file mode 100644 index 0000000..4cd67c0 --- /dev/null +++ b/app/adapters/web/src/main/java/it/mulders/traqqr/web/user/UserInfoBean.java @@ -0,0 +1,32 @@ +package it.mulders.traqqr.web.user; + +import jakarta.enterprise.context.SessionScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import jakarta.security.enterprise.SecurityContext; +import jakarta.security.enterprise.identitystore.openid.OpenIdContext; + +import java.io.Serializable; + +@Named +@SessionScoped +public class UserInfoBean implements Serializable { + private final String displayName; + private final String profilePictureUrl; + + @Inject + public UserInfoBean(final SecurityContext securityContext, + final OpenIdContext openIdContext) { + var claims = openIdContext.getClaims(); + this.displayName = claims.getName().orElse("unknown user"); + this.profilePictureUrl = claims.getPicture().orElse(null); + } + + public String getUsername() {// name, picture, email + return displayName; + } + + public String getProfilePictureUrl() { + return profilePictureUrl; + } +} diff --git a/app/adapters/web/src/main/webapp/META-INF/beans.xml b/app/adapters/web/src/main/webapp/META-INF/beans.xml new file mode 100644 index 0000000..e7ca12d --- /dev/null +++ b/app/adapters/web/src/main/webapp/META-INF/beans.xml @@ -0,0 +1,5 @@ +<beans xmlns="https://jakarta.ee/xml/ns/jakartaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" + xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/beans_4_0.xsd" + version="4.0" + bean-discovery-mode="all"> +</beans> \ No newline at end of file diff --git a/app/adapters/web/src/main/webapp/WEB-INF/layout-closed.xhtml b/app/adapters/web/src/main/webapp/WEB-INF/layout-closed.xhtml new file mode 100644 index 0000000..d5fa0e4 --- /dev/null +++ b/app/adapters/web/src/main/webapp/WEB-INF/layout-closed.xhtml @@ -0,0 +1,24 @@ +<!DOCTYPE html> +<html xmlns="http://www.w3.org/1999/xhtml" + xmlns:h="http://xmlns.jcp.org/jsf/html" + xmlns:ui="http://xmlns.jcp.org/jsf/facelets" + xmlns:p="http://primefaces.org/ui"> +<h:head> + <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> + <title><ui:insert name="title">Default Title</ui:insert> | Traqqr</title> +</h:head> +<body> +<ui:debug/> + +<div class="content"> + <ui:insert name="content"/> +</div> + +<hr /> +<!-- alleen als ingelogd --> +<p:outputPanel> + Traqqr ${systemInfo.applicationVersion} (revision <code>${systemInfo.gitVersion}</code>) is made with ❤️ + ☕ + <a href="https://dev.java/" target="_blank">Java ${systemInfo.javaVersion}</a> + <a href="https://jakarta.ee/" target="_blank">Jakarta EE 10</a>. + Proudly running on ${systemInfo.javaRuntime}. +</p:outputPanel> +</body> +</html> \ No newline at end of file diff --git a/app/adapters/web/src/main/webapp/WEB-INF/layout.xhtml b/app/adapters/web/src/main/webapp/WEB-INF/layout-open.xhtml similarity index 82% rename from app/adapters/web/src/main/webapp/WEB-INF/layout.xhtml rename to app/adapters/web/src/main/webapp/WEB-INF/layout-open.xhtml index 6100599..a491252 100644 --- a/app/adapters/web/src/main/webapp/WEB-INF/layout.xhtml +++ b/app/adapters/web/src/main/webapp/WEB-INF/layout-open.xhtml @@ -1,7 +1,8 @@ <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:h="http://xmlns.jcp.org/jsf/html" - xmlns:ui="http://xmlns.jcp.org/jsf/facelets"> + xmlns:ui="http://xmlns.jcp.org/jsf/facelets" + xmlns:p="http://primefaces.org/ui"> <h:head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> <title><ui:insert name="title">Default Title</ui:insert> | Traqqr</title> diff --git a/app/adapters/web/src/main/webapp/WEB-INF/web.xml b/app/adapters/web/src/main/webapp/WEB-INF/web.xml index d6e45fc..e58578b 100644 --- a/app/adapters/web/src/main/webapp/WEB-INF/web.xml +++ b/app/adapters/web/src/main/webapp/WEB-INF/web.xml @@ -24,6 +24,23 @@ <url-pattern>*.xhtml</url-pattern> </servlet-mapping> + <security-role> + <role-name>user</role-name> + </security-role> + + <security-constraint> + <web-resource-collection> + <web-resource-name>Traqqr</web-resource-name> + <url-pattern>/secure/*</url-pattern> + </web-resource-collection> + <auth-constraint> + <role-name>user</role-name> + </auth-constraint> + <user-data-constraint> + <transport-guarantee>CONFIDENTIAL</transport-guarantee> + </user-data-constraint> + </security-constraint> + <!-- Primefaces configuration --> <context-param> <param-name>primefaces.CLIENT_SIDE_VALIDATION</param-name> diff --git a/app/adapters/web/src/main/webapp/index.xhtml b/app/adapters/web/src/main/webapp/index.xhtml index e8c906a..04f6ec0 100644 --- a/app/adapters/web/src/main/webapp/index.xhtml +++ b/app/adapters/web/src/main/webapp/index.xhtml @@ -3,7 +3,7 @@ xmlns:f="http://xmlns.jcp.org/jsf/core" xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:p="http://primefaces.org/ui" - template="/WEB-INF/layout.xhtml"> + template="/WEB-INF/layout-open.xhtml"> <ui:define name="title">Start</ui:define> <ui:define name="content"> diff --git a/app/adapters/web/src/main/webapp/privacy.xhtml b/app/adapters/web/src/main/webapp/privacy.xhtml index c889c9a..d4873fc 100644 --- a/app/adapters/web/src/main/webapp/privacy.xhtml +++ b/app/adapters/web/src/main/webapp/privacy.xhtml @@ -2,7 +2,7 @@ xmlns="http://www.w3.org/1999/xhtml" xmlns:ui="http://xmlns.jcp.org/jsf/facelets" xmlns:p="http://primefaces.org/ui" - template="/WEB-INF/layout.xhtml"> + template="/WEB-INF/layout-open.xhtml"> <ui:define name="title">Privacy</ui:define> <ui:define name="content"> diff --git a/app/adapters/web/src/main/webapp/secure/index.xhtml b/app/adapters/web/src/main/webapp/secure/index.xhtml new file mode 100644 index 0000000..710bfe0 --- /dev/null +++ b/app/adapters/web/src/main/webapp/secure/index.xhtml @@ -0,0 +1,16 @@ +<ui:composition + xmlns="http://www.w3.org/1999/xhtml" + xmlns:f="http://xmlns.jcp.org/jsf/core" + xmlns:ui="http://xmlns.jcp.org/jsf/facelets" + xmlns:p="http://primefaces.org/ui" + template="/WEB-INF/layout-closed.xhtml"> + <ui:define name="title">Start</ui:define> + + <ui:define name="content"> + <p:card> + <f:facet name="title">Secure Dashboard</f:facet> + + <p>Hi ${userInfoBean.username}!</p> + </p:card> + </ui:define> +</ui:composition> diff --git a/runtime/src/main/liberty/config/google-client-keystore.p12 b/runtime/src/main/liberty/config/google-client-keystore.p12 new file mode 100644 index 0000000..1a47a4e Binary files /dev/null and b/runtime/src/main/liberty/config/google-client-keystore.p12 differ diff --git a/runtime/src/main/liberty/config/server.xml b/runtime/src/main/liberty/config/server.xml index d45b153..65adb33 100644 --- a/runtime/src/main/liberty/config/server.xml +++ b/runtime/src/main/liberty/config/server.xml @@ -1,6 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <server description="traqqr-dev-server"> <featureManager> + <feature>appSecurity-5.0</feature> <feature>beanValidation-3.0</feature> <feature>cdi-4.0</feature> <feature>expressionLanguage-5.0</feature> @@ -12,7 +13,7 @@ <application location="traqqr.ear" name="traqqr" contextRoot="/" /> - <!-- Enable detailed logging for JPA. + <!-- Enable detailed logging for Jakarta Persistence API. <logging traceSpecification="eclipselink=all:eclipselink.sql=all" /> --> <!-- Enable detailed logging for Jakarta Security. -->