diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/LoginUtils.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/LoginUtils.java new file mode 100644 index 0000000..967a311 --- /dev/null +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/core/utils/LoginUtils.java @@ -0,0 +1,116 @@ +package marketing.heyday.hartmann.fotodocumentation.core.utils; + +import java.nio.charset.Charset; +import java.security.Principal; + +import java.util.Optional; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.wildfly.security.auth.principal.NamePrincipal; +import org.wildfly.security.auth.server.RealmUnavailableException; +import org.wildfly.security.auth.server.SecurityDomain; +import org.wildfly.security.auth.server.SecurityIdentity; +import org.wildfly.security.evidence.PasswordGuessEvidence; + +import jakarta.servlet.http.HttpServletRequest; +import javaslang.Tuple; +import javaslang.Tuple2; + +/** + * + *

Copyright: Copyright (c) 2024

+ *

Company: heyday Marketing GmbH

+ * @author Patrick Verboom + * @version 1.0 + * + * created: 20 Jan 2026 + */ + +public class LoginUtils { + private static final Log LOG = LogFactory.getLog(LoginUtils.class); + private static final int BASIC_HEADER_SIZE = 6; + private static final int USERPASS_LENGTH = 1; + + public Optional authenticate(HttpServletRequest httpServletRequest) { + Tuple2 userPass = extractUsernamePassword(httpServletRequest); + if (userPass._1.isBlank() || userPass._2.isBlank()) { + return Optional.empty(); + } + return authenticate(userPass._1, userPass._2); + } + + public Optional getSecurityToken(HttpServletRequest httpServletRequest) { + Optional headerOpt = extractAuthHeader(httpServletRequest); + if (headerOpt.isPresent() && headerOpt.get().length > 2) { + return Optional.ofNullable(headerOpt.get()[2]); + } + return Optional.empty(); + } + + private Optional authenticate(String username, String password) { + try { + Principal principal = new NamePrincipal(username); + PasswordGuessEvidence evidence = new PasswordGuessEvidence(password.toCharArray()); + + SecurityDomain sd = SecurityDomain.getCurrent(); + return Optional.ofNullable(sd.authenticate(principal, evidence)); + } catch (RealmUnavailableException | SecurityException e) { + LOG.warn("Failed to authenticate user " + e.getMessage(), e); + return Optional.empty(); + } + } + + private Tuple2 extractUsernamePassword(HttpServletRequest httpServletRequest) { + Optional userPassOptional = extractAuthHeader(httpServletRequest); + + if (userPassOptional.isPresent() && userPassOptional.get().length >= USERPASS_LENGTH) { + String[] userpass = userPassOptional.get(); + String username = userpass[0]; + String password = userpass.length > USERPASS_LENGTH ? userpass[1] : ""; + + return Tuple.of(username, password); + } + return Tuple.of("", ""); + } + + private Optional extractAuthHeader(HttpServletRequest httpServletRequest) { + Optional retVal = Optional.empty(); + + String authorization = httpServletRequest.getHeader("Authorization"); + if (authorization != null && StringUtils.length(authorization) > BASIC_HEADER_SIZE) { + authorization = authorization.substring(BASIC_HEADER_SIZE); + String decoded = StringUtils.toEncodedString(Base64.decodeBase64(authorization), Charset.forName("utf-8")); + retVal = Optional.of(decoded.split(":")); + } + return retVal; + } + + /** + * Extract device information from User-Agent header + * + * @param request HTTP servlet request + * @return Device/browser information + */ + public String extractDeviceInfo(HttpServletRequest request) { + String userAgent = request.getHeader("User-Agent"); + return userAgent != null ? userAgent : "Unknown"; + } + + /** + * Extract client IP address, considering proxies + * + * @param request HTTP servlet request + * @return Client IP address + */ + public String extractIpAddress(HttpServletRequest request) { + String xForwardedFor = request.getHeader("X-Forwarded-For"); + if (xForwardedFor != null && !xForwardedFor.isEmpty()) { + // X-Forwarded-For can contain multiple IPs, take the first one + return xForwardedFor.split(",")[0].trim(); + } + return request.getRemoteAddr(); + } +} diff --git a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerPictureResource.java b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerPictureResource.java index 3a01182..4fd6627 100644 --- a/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerPictureResource.java +++ b/hartmann-foto-documentation-app/src/main/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerPictureResource.java @@ -1,18 +1,27 @@ package marketing.heyday.hartmann.fotodocumentation.rest; +import java.util.Optional; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.jboss.resteasy.annotations.GZIP; +import org.wildfly.security.auth.server.SecurityIdentity; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import jakarta.ejb.EJB; import jakarta.enterprise.context.RequestScoped; +import jakarta.inject.Inject; +import jakarta.servlet.http.HttpServletRequest; import jakarta.ws.rs.Consumes; import jakarta.ws.rs.POST; import jakarta.ws.rs.Path; +import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response.Status; import marketing.heyday.hartmann.fotodocumentation.core.service.CustomerPictureService; +import marketing.heyday.hartmann.fotodocumentation.core.utils.LoginUtils; import marketing.heyday.hartmann.fotodocumentation.rest.jackson.JsonSchemaValidate; import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerPictureValue; @@ -28,18 +37,28 @@ import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerPictureValue; @RequestScoped @Path("customer-picture") public class CustomerPictureResource { - + private static final Log LOG = LogFactory.getLog(CustomerPictureResource.class); + @EJB private CustomerPictureService customerPictureService; + @Inject + private LoginUtils loginUtils; + @GZIP @POST @Path("") - //@Authenticate(shouldBeInAllRoles = RightUtils.ADMIN_RIGHT) @Consumes(MediaType.APPLICATION_JSON) @Operation(summary = "Add Customer Image to database") @ApiResponse(responseCode = "200", description = "Add successfull") - public Response doAddCustomerPicture(@JsonSchemaValidate("schema/customer_picture_add.json") CustomerPictureValue customerPictureValue) { + public Response doAddCustomerPicture(@Context HttpServletRequest httpServletRequest, @JsonSchemaValidate("schema/customer_picture_add.json") CustomerPictureValue customerPictureValue) { + Optional identity = loginUtils.authenticate(httpServletRequest); + if (identity.isEmpty()) { + LOG.debug("identity empty login invalid"); + return Response.status(401).build(); + } + + boolean success = customerPictureService.addCustomerPicture(customerPictureValue); return success ? Response.ok().build() : Response.status(Status.BAD_REQUEST).build(); diff --git a/hartmann-foto-documentation-docker/src/main/docker/hartmann-foto-documentation-web-1.0.0-SNAPSHOT.war b/hartmann-foto-documentation-docker/src/main/docker/hartmann-foto-documentation-web-1.0.0-SNAPSHOT.war index 6b21898..ed3659b 100644 Binary files a/hartmann-foto-documentation-docker/src/main/docker/hartmann-foto-documentation-web-1.0.0-SNAPSHOT.war and b/hartmann-foto-documentation-docker/src/main/docker/hartmann-foto-documentation-web-1.0.0-SNAPSHOT.war differ diff --git a/hartmann-foto-documentation-docker/src/main/docker/standalone-fotodocumentation.xml b/hartmann-foto-documentation-docker/src/main/docker/standalone-fotodocumentation.xml index 80c65ef..6144f70 100644 --- a/hartmann-foto-documentation-docker/src/main/docker/standalone-fotodocumentation.xml +++ b/hartmann-foto-documentation-docker/src/main/docker/standalone-fotodocumentation.xml @@ -144,6 +144,7 @@ + jdbc:postgresql://hartmann_postgres:5432/fotodocumentation postgres @@ -168,6 +169,7 @@ + org.postgresql.xa.PGXADataSource @@ -300,7 +302,7 @@ - + diff --git a/hartmann-foto-documentation-docker/src/test/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerPictureTest.java b/hartmann-foto-documentation-docker/src/test/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerPictureResourceTest.java similarity index 74% rename from hartmann-foto-documentation-docker/src/test/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerPictureTest.java rename to hartmann-foto-documentation-docker/src/test/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerPictureResourceTest.java index f5b1ca2..74d8ec1 100644 --- a/hartmann-foto-documentation-docker/src/test/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerPictureTest.java +++ b/hartmann-foto-documentation-docker/src/test/java/marketing/heyday/hartmann/fotodocumentation/rest/CustomerPictureResourceTest.java @@ -26,8 +26,8 @@ import org.junit.jupiter.api.TestMethodOrder; * created: 14 Nov 2024 */ @TestMethodOrder(OrderAnnotation.class) -public class CustomerPictureTest extends AbstractRestTest { - private static final Log LOG = LogFactory.getLog(CustomerPictureTest.class); +public class CustomerPictureResourceTest extends AbstractRestTest { + private static final Log LOG = LogFactory.getLog(CustomerPictureResourceTest.class); private static final String PATH = "api/customer-picture"; private static final String BASE_UPLOAD = "src/test/resources/upload/"; @@ -50,4 +50,18 @@ public class CustomerPictureTest extends AbstractRestTest { int code = httpResponse.getStatusLine().getStatusCode(); assertEquals(200, code); } + + @Test + @Order(2) + public void doAddCustomerPictureNoAuth() throws IOException { + LOG.info("doAddCustomerPicture"); + + String path = deploymentURL + PATH; + Request request = Request.Post(path).addHeader("Accept", "application/json; charset=utf-8") + .bodyFile(new File(BASE_UPLOAD + "add.json"), ContentType.APPLICATION_JSON); + + HttpResponse httpResponse = executeRequest(request); + int code = httpResponse.getStatusLine().getStatusCode(); + assertEquals(401, code); + } }