added security to picture upload resource

This commit is contained in:
verboomp
2026-01-20 15:28:15 +01:00
parent 8ccd98755b
commit 39580438c2
5 changed files with 157 additions and 6 deletions

View File

@@ -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;
/**
*
* <p>Copyright: Copyright (c) 2024</p>
* <p>Company: heyday Marketing GmbH</p>
* @author <a href="mailto:p.verboom@heyday.marketing">Patrick Verboom</a>
* @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<SecurityIdentity> authenticate(HttpServletRequest httpServletRequest) {
Tuple2<String, String> userPass = extractUsernamePassword(httpServletRequest);
if (userPass._1.isBlank() || userPass._2.isBlank()) {
return Optional.empty();
}
return authenticate(userPass._1, userPass._2);
}
public Optional<String> getSecurityToken(HttpServletRequest httpServletRequest) {
Optional<String[]> headerOpt = extractAuthHeader(httpServletRequest);
if (headerOpt.isPresent() && headerOpt.get().length > 2) {
return Optional.ofNullable(headerOpt.get()[2]);
}
return Optional.empty();
}
private Optional<SecurityIdentity> 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<String, String> extractUsernamePassword(HttpServletRequest httpServletRequest) {
Optional<String[]> 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<String[]> extractAuthHeader(HttpServletRequest httpServletRequest) {
Optional<String[]> 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();
}
}

View File

@@ -1,18 +1,27 @@
package marketing.heyday.hartmann.fotodocumentation.rest; 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.jboss.resteasy.annotations.GZIP;
import org.wildfly.security.auth.server.SecurityIdentity;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponse;
import jakarta.ejb.EJB; import jakarta.ejb.EJB;
import jakarta.enterprise.context.RequestScoped; import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.Consumes; import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.POST; import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path; import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType; import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response; import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.Status; import jakarta.ws.rs.core.Response.Status;
import marketing.heyday.hartmann.fotodocumentation.core.service.CustomerPictureService; 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.jackson.JsonSchemaValidate;
import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerPictureValue; import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerPictureValue;
@@ -28,18 +37,28 @@ import marketing.heyday.hartmann.fotodocumentation.rest.vo.CustomerPictureValue;
@RequestScoped @RequestScoped
@Path("customer-picture") @Path("customer-picture")
public class CustomerPictureResource { public class CustomerPictureResource {
private static final Log LOG = LogFactory.getLog(CustomerPictureResource.class);
@EJB @EJB
private CustomerPictureService customerPictureService; private CustomerPictureService customerPictureService;
@Inject
private LoginUtils loginUtils;
@GZIP @GZIP
@POST @POST
@Path("") @Path("")
//@Authenticate(shouldBeInAllRoles = RightUtils.ADMIN_RIGHT)
@Consumes(MediaType.APPLICATION_JSON) @Consumes(MediaType.APPLICATION_JSON)
@Operation(summary = "Add Customer Image to database") @Operation(summary = "Add Customer Image to database")
@ApiResponse(responseCode = "200", description = "Add successfull") @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<SecurityIdentity> identity = loginUtils.authenticate(httpServletRequest);
if (identity.isEmpty()) {
LOG.debug("identity empty login invalid");
return Response.status(401).build();
}
boolean success = customerPictureService.addCustomerPicture(customerPictureValue); boolean success = customerPictureService.addCustomerPicture(customerPictureValue);
return success ? Response.ok().build() : Response.status(Status.BAD_REQUEST).build(); return success ? Response.ok().build() : Response.status(Status.BAD_REQUEST).build();

View File

@@ -144,6 +144,7 @@
<subsystem xmlns="urn:jboss:domain:core-management:1.0"/> <subsystem xmlns="urn:jboss:domain:core-management:1.0"/>
<subsystem xmlns="urn:jboss:domain:datasources:7.1"> <subsystem xmlns="urn:jboss:domain:datasources:7.1">
<datasources> <datasources>
<!-- Patrick -->
<datasource jndi-name="java:/jdbc/fotoDocumentationDS" pool-name="fotoDocumentationDS" enabled="true" use-java-context="true" use-ccm="false"> <datasource jndi-name="java:/jdbc/fotoDocumentationDS" pool-name="fotoDocumentationDS" enabled="true" use-java-context="true" use-ccm="false">
<connection-url>jdbc:postgresql://hartmann_postgres:5432/fotodocumentation</connection-url> <connection-url>jdbc:postgresql://hartmann_postgres:5432/fotodocumentation</connection-url>
<driver>postgres</driver> <driver>postgres</driver>
@@ -168,6 +169,7 @@
<security user-name="sa" password="sa"/> <security user-name="sa" password="sa"/>
</datasource> </datasource>
<drivers> <drivers>
<!-- Patrick -->
<driver name="postgres" module="org.postgresql.jdbc"> <driver name="postgres" module="org.postgresql.jdbc">
<xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class> <xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class>
</driver> </driver>
@@ -300,7 +302,7 @@
<!-- patrick --> <!-- patrick -->
<jdbc-realm name="fotoDocumentationRealm" > <jdbc-realm name="fotoDocumentationRealm" >
<principal-query data-source="fotoDocumentationDS" sql="select password, salt, ri.code as Role from x_user u left join role_to_right rtr on rtr.role_id_fk = u.role_id_fk left join x_right ri on rtr.right_id_fk = ri.right_id where username = ?;"> <principal-query data-source="fotoDocumentationDS" sql="select password, salt, ri.code as Role from x_user u left join user_to_right rtr on rtr.user_id_fk = u.user_id left join x_right ri on rtr.right_id_fk = ri.right_id where username = ?;">
<salted-simple-digest-mapper algorithm="password-salt-digest-sha-256" password-index="1" salt-index="2" /> <salted-simple-digest-mapper algorithm="password-salt-digest-sha-256" password-index="1" salt-index="2" />
<attribute-mapping> <attribute-mapping>
<attribute to="groups" index="3"/> <attribute to="groups" index="3"/>

View File

@@ -26,8 +26,8 @@ import org.junit.jupiter.api.TestMethodOrder;
* created: 14 Nov 2024 * created: 14 Nov 2024
*/ */
@TestMethodOrder(OrderAnnotation.class) @TestMethodOrder(OrderAnnotation.class)
public class CustomerPictureTest extends AbstractRestTest { public class CustomerPictureResourceTest extends AbstractRestTest {
private static final Log LOG = LogFactory.getLog(CustomerPictureTest.class); private static final Log LOG = LogFactory.getLog(CustomerPictureResourceTest.class);
private static final String PATH = "api/customer-picture"; private static final String PATH = "api/customer-picture";
private static final String BASE_UPLOAD = "src/test/resources/upload/"; private static final String BASE_UPLOAD = "src/test/resources/upload/";
@@ -50,4 +50,18 @@ public class CustomerPictureTest extends AbstractRestTest {
int code = httpResponse.getStatusLine().getStatusCode(); int code = httpResponse.getStatusLine().getStatusCode();
assertEquals(200, code); 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);
}
} }