use context.define(...) instead of setCurrent...
This commit is contained in:
@ -1,20 +1,33 @@
|
||||
package net.hostsharing.hsadminng.context;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static java.util.function.Predicate.not;
|
||||
import static org.springframework.transaction.annotation.Propagation.MANDATORY;
|
||||
|
||||
@Service
|
||||
public class Context {
|
||||
|
||||
private static final Set<String> HEADERS_TO_IGNORE = Set.of(
|
||||
"accept-encoding",
|
||||
"connection",
|
||||
"content-length",
|
||||
"host",
|
||||
"user-agent");
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
@ -22,81 +35,111 @@ public class Context {
|
||||
private HttpServletRequest request;
|
||||
|
||||
@Transactional(propagation = MANDATORY)
|
||||
public void register(final String currentUser, final String assumedRoles) {
|
||||
if (request != null) {
|
||||
setCurrentTask(request.getMethod() + " " + request.getRequestURI());
|
||||
} else {
|
||||
|
||||
final Optional<StackWalker.StackFrame> caller =
|
||||
StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
|
||||
.walk(frames ->
|
||||
frames.skip(1)
|
||||
.filter(c -> c.getDeclaringClass()
|
||||
.getPackageName()
|
||||
.startsWith("net.hostsharing.hsadminng"))
|
||||
.filter(c -> !c.getDeclaringClass().getName().contains("BySpringCGLIB$$"))
|
||||
.findFirst());
|
||||
final var callerName = caller.map(
|
||||
c -> c.getDeclaringClass().getSimpleName() + "." + c.getMethodName())
|
||||
.orElse("unknown");
|
||||
setCurrentTask(callerName);
|
||||
}
|
||||
setCurrentUser(currentUser);
|
||||
if (!StringUtils.isBlank(assumedRoles)) {
|
||||
assumeRoles(assumedRoles);
|
||||
}
|
||||
public void define(final String currentUser) {
|
||||
define(currentUser, null);
|
||||
}
|
||||
|
||||
@Transactional(propagation = MANDATORY)
|
||||
public void setCurrentTask(final String task) {
|
||||
final var sql = String.format(
|
||||
"set local hsadminng.currentTask = '%s';",
|
||||
shortenToMaxLength(task, 95)
|
||||
);
|
||||
em.createNativeQuery(sql).executeUpdate();
|
||||
public void define(final String currentUser, final String assumedRoles) {
|
||||
define(toTask(request), toCurl(request), currentUser, assumedRoles);
|
||||
}
|
||||
|
||||
@Transactional(propagation = MANDATORY)
|
||||
public void define(
|
||||
final String currentTask,
|
||||
final String currentRequest,
|
||||
final String currentUser,
|
||||
final String assumedRoles) {
|
||||
final var query = em.createNativeQuery(
|
||||
"call defineContext(:currentTask, :currentRequest, :currentUser, :assumedRoles);");
|
||||
query.setParameter("currentTask", shortenToMaxLength(currentTask, 96));
|
||||
query.setParameter("currentRequest", shortenToMaxLength(currentRequest, 512)); // TODO.SPEC: length?
|
||||
query.setParameter("currentUser", currentUser);
|
||||
query.setParameter("assumedRoles", assumedRoles != null ? assumedRoles : "");
|
||||
query.executeUpdate();
|
||||
}
|
||||
|
||||
public String getCurrentTask() {
|
||||
return (String) em.createNativeQuery("select current_setting('hsadminng.currentTask');").getSingleResult();
|
||||
}
|
||||
|
||||
@Transactional(propagation = MANDATORY)
|
||||
public void setCurrentUser(final String userName) {
|
||||
em.createNativeQuery(
|
||||
String.format(
|
||||
"set local hsadminng.currentUser = '%s';",
|
||||
userName
|
||||
)
|
||||
).executeUpdate();
|
||||
assumeNoSpecialRole();
|
||||
}
|
||||
|
||||
public String getCurrentUser() {
|
||||
return String.valueOf(em.createNativeQuery("select currentUser()").getSingleResult());
|
||||
}
|
||||
|
||||
@Transactional(propagation = MANDATORY)
|
||||
public void assumeRoles(final String roles) {
|
||||
em.createNativeQuery(
|
||||
String.format(
|
||||
"set local hsadminng.assumedRoles = '%s';",
|
||||
roles
|
||||
)
|
||||
).executeUpdate();
|
||||
}
|
||||
|
||||
@Transactional(propagation = MANDATORY)
|
||||
public void assumeNoSpecialRole() {
|
||||
em.createNativeQuery(
|
||||
"set local hsadminng.assumedRoles = '';"
|
||||
).executeUpdate();
|
||||
}
|
||||
|
||||
public String[] getAssumedRoles() {
|
||||
return (String[]) em.createNativeQuery("select assumedRoles()").getSingleResult();
|
||||
}
|
||||
|
||||
private static String shortenToMaxLength(final String task, final int maxLength) {
|
||||
return task.substring(0, Math.min(task.length(), maxLength));
|
||||
private static String getCallerMethodNameFromStack() {
|
||||
final Optional<StackWalker.StackFrame> caller =
|
||||
StackWalker.getInstance(StackWalker.Option.RETAIN_CLASS_REFERENCE)
|
||||
.walk(frames -> frames
|
||||
.skip(2)
|
||||
.filter(c -> c.getDeclaringClass() != Context.class)
|
||||
.filter(c -> c.getDeclaringClass()
|
||||
.getPackageName()
|
||||
.startsWith("net.hostsharing.hsadminng"))
|
||||
.filter(c -> !c.getDeclaringClass().getName().contains("BySpringCGLIB$$"))
|
||||
.findFirst());
|
||||
return caller.map(
|
||||
c -> c.getDeclaringClass().getSimpleName() + "." + c.getMethodName())
|
||||
.orElse("unknown");
|
||||
}
|
||||
|
||||
private String toTask(final HttpServletRequest request) {
|
||||
if (isRequestScopeAvailable()) {
|
||||
return request.getMethod() + " " + request.getRequestURI();
|
||||
} else {
|
||||
return getCallerMethodNameFromStack();
|
||||
}
|
||||
}
|
||||
|
||||
@SneakyThrows
|
||||
private String toCurl(final HttpServletRequest request) {
|
||||
if (!isRequestScopeAvailable()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
var curlCommand = "curl -0 -v";
|
||||
|
||||
// append method
|
||||
curlCommand += " -X " + request.getMethod();
|
||||
|
||||
// append request url
|
||||
curlCommand += " " + request.getRequestURI();
|
||||
|
||||
// append headers
|
||||
final var headers = Collections.list(request.getHeaderNames()).stream()
|
||||
.filter(not(HEADERS_TO_IGNORE::contains))
|
||||
.collect(Collectors.toSet());
|
||||
for (String headerName : headers) {
|
||||
final var headerValue = request.getHeader(headerName);
|
||||
curlCommand += " \\" + System.lineSeparator() + String.format("-H '%s:%s'", headerName, headerValue);
|
||||
}
|
||||
|
||||
// body
|
||||
final String body = request.getReader().lines().collect(Collectors.joining(System.lineSeparator()));
|
||||
if (!StringUtils.isEmpty(body)) {
|
||||
curlCommand += " \\" + System.lineSeparator() + "--data-binary @- ";
|
||||
curlCommand +=
|
||||
"<< EOF" + System.lineSeparator() + System.lineSeparator() + body + System.lineSeparator() + "EOF";
|
||||
}
|
||||
|
||||
return curlCommand;
|
||||
}
|
||||
|
||||
private boolean isRequestScopeAvailable() {
|
||||
return RequestContextHolder.getRequestAttributes() != null;
|
||||
}
|
||||
|
||||
private static String shortenToMaxLength(final String raw, final int maxLength) {
|
||||
if (raw == null) {
|
||||
return "";
|
||||
}
|
||||
if (raw.length() <= maxLength) {
|
||||
return raw;
|
||||
}
|
||||
return raw.substring(0, maxLength - 3) + "...";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,45 @@
|
||||
package net.hostsharing.hsadminng.context;
|
||||
|
||||
import javax.servlet.ReadListener;
|
||||
import javax.servlet.ServletInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
public class HttpServletRequestBodyCache extends ServletInputStream {
|
||||
|
||||
private InputStream inputStream;
|
||||
|
||||
public HttpServletRequestBodyCache(byte[] body) {
|
||||
this.inputStream = new ByteArrayInputStream(body);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int read() throws IOException {
|
||||
return inputStream.read();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int available() throws IOException {
|
||||
return inputStream.available();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isFinished() {
|
||||
try {
|
||||
return available() == 0;
|
||||
} catch (IOException e) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isReady() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setReadListener(final ReadListener listener) {
|
||||
throw new RuntimeException("Not implemented");
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package net.hostsharing.hsadminng.context;
|
||||
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.io.IOException;
|
||||
|
||||
@Component
|
||||
public class HttpServletRequestBodyCachingFilter extends OncePerRequestFilter {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(
|
||||
final HttpServletRequest request,
|
||||
final HttpServletResponse response,
|
||||
final FilterChain filterChain)
|
||||
throws ServletException, IOException {
|
||||
filterChain.doFilter(new HttpServletRequestWithCachedBody(request), response);
|
||||
}
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
package net.hostsharing.hsadminng.context;
|
||||
|
||||
import org.springframework.util.StreamUtils;
|
||||
|
||||
import javax.servlet.ServletInputStream;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletRequestWrapper;
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
|
||||
public class HttpServletRequestWithCachedBody extends HttpServletRequestWrapper {
|
||||
|
||||
private byte[] cachedBody;
|
||||
|
||||
public HttpServletRequestWithCachedBody(HttpServletRequest request) throws IOException {
|
||||
super(request);
|
||||
final var requestInputStream = request.getInputStream();
|
||||
this.cachedBody = StreamUtils.copyToByteArray(requestInputStream);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
return new HttpServletRequestBodyCache(this.cachedBody);
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedReader getReader() throws IOException {
|
||||
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(this.cachedBody);
|
||||
return new BufferedReader(new InputStreamReader(byteArrayInputStream));
|
||||
}
|
||||
}
|
@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.hs.hscustomer;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.generated.api.v1.api.CustomersApi;
|
||||
import net.hostsharing.hsadminng.generated.api.v1.model.CustomerResource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -33,7 +32,7 @@ public class CustomerController implements CustomersApi {
|
||||
String assumedRoles,
|
||||
String prefix
|
||||
) {
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var result = customerRepository.findCustomerByOptionalPrefixLike(prefix);
|
||||
|
||||
@ -47,7 +46,7 @@ public class CustomerController implements CustomersApi {
|
||||
final String assumedRoles,
|
||||
final CustomerResource customer) {
|
||||
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
if (customer.getUuid() == null) {
|
||||
customer.setUuid(UUID.randomUUID());
|
||||
|
@ -5,7 +5,6 @@ import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.generated.api.v1.api.PackagesApi;
|
||||
import net.hostsharing.hsadminng.generated.api.v1.model.PackageResource;
|
||||
import net.hostsharing.hsadminng.generated.api.v1.model.PackageUpdateResource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -33,7 +32,7 @@ public class PackageController implements PackagesApi {
|
||||
String assumedRoles,
|
||||
String name
|
||||
) {
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var result = packageRepository.findAllByOptionalNameLike(name);
|
||||
return ResponseEntity.ok(mapList(result, PackageResource.class));
|
||||
@ -47,7 +46,7 @@ public class PackageController implements PackagesApi {
|
||||
final UUID packageUuid,
|
||||
final PackageUpdateResource body) {
|
||||
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var current = packageRepository.findByUuid(packageUuid);
|
||||
OptionalFromJson.of(body.getDescription()).ifPresent(current::setDescription);
|
||||
|
@ -3,7 +3,6 @@ package net.hostsharing.hsadminng.rbac.rbacgrant;
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.generated.api.v1.api.RbacgrantsApi;
|
||||
import net.hostsharing.hsadminng.generated.api.v1.model.RbacGrantResource;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
@ -38,7 +37,7 @@ public class RbacGrantController implements RbacgrantsApi {
|
||||
final UUID grantedRoleUuid,
|
||||
final UUID granteeUserUuid) {
|
||||
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var id = new RbacGrantId(granteeUserUuid, grantedRoleUuid);
|
||||
final var result = rbacGrantRepository.findById(id);
|
||||
@ -54,7 +53,7 @@ public class RbacGrantController implements RbacgrantsApi {
|
||||
final String currentUser,
|
||||
final String assumedRoles) {
|
||||
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
return ResponseEntity.ok(mapList(rbacGrantRepository.findAll(), RbacGrantResource.class));
|
||||
}
|
||||
@ -66,7 +65,7 @@ public class RbacGrantController implements RbacgrantsApi {
|
||||
final String assumedRoles,
|
||||
final RbacGrantResource body) {
|
||||
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var granted = rbacGrantRepository.save(map(body, RbacGrantEntity.class));
|
||||
em.flush();
|
||||
@ -88,7 +87,7 @@ public class RbacGrantController implements RbacgrantsApi {
|
||||
final UUID grantedRoleUuid,
|
||||
final UUID granteeUserUuid) {
|
||||
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
rbacGrantRepository.deleteByRbacGrantId(new RbacGrantId(granteeUserUuid, grantedRoleUuid));
|
||||
|
||||
|
@ -28,7 +28,7 @@ public class RbacRoleController implements RbacrolesApi {
|
||||
final String currentUser,
|
||||
final String assumedRoles) {
|
||||
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
return ResponseEntity.ok(mapList(rbacRoleRepository.findAll(), RbacRoleResource.class));
|
||||
}
|
||||
|
@ -2,15 +2,12 @@ package net.hostsharing.hsadminng.rbac.rbacuser;
|
||||
|
||||
import net.hostsharing.hsadminng.context.Context;
|
||||
import net.hostsharing.hsadminng.generated.api.v1.api.RbacusersApi;
|
||||
import net.hostsharing.hsadminng.generated.api.v1.model.RbacGrantResource;
|
||||
import net.hostsharing.hsadminng.generated.api.v1.model.RbacUserPermissionResource;
|
||||
import net.hostsharing.hsadminng.generated.api.v1.model.RbacUserResource;
|
||||
import net.hostsharing.hsadminng.rbac.rbacgrant.RbacGrantId;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;
|
||||
|
||||
import java.util.List;
|
||||
@ -33,7 +30,7 @@ public class RbacUserController implements RbacusersApi {
|
||||
public ResponseEntity<RbacUserResource> createUser(
|
||||
final RbacUserResource body
|
||||
) {
|
||||
context.register(body.getName(), null);
|
||||
context.define(body.getName());
|
||||
|
||||
if (body.getUuid() == null) {
|
||||
body.setUuid(UUID.randomUUID());
|
||||
@ -55,7 +52,7 @@ public class RbacUserController implements RbacusersApi {
|
||||
final String assumedRoles,
|
||||
final UUID userUuid) {
|
||||
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
final var result = rbacUserRepository.findByUuid(userUuid);
|
||||
if (result == null) {
|
||||
@ -71,7 +68,7 @@ public class RbacUserController implements RbacusersApi {
|
||||
final String assumedRoles,
|
||||
final String userName
|
||||
) {
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
return ResponseEntity.ok(mapList(rbacUserRepository.findByOptionalNameLike(userName), RbacUserResource.class));
|
||||
}
|
||||
@ -83,8 +80,10 @@ public class RbacUserController implements RbacusersApi {
|
||||
final String assumedRoles,
|
||||
final UUID userUuid
|
||||
) {
|
||||
context.register(currentUser, assumedRoles);
|
||||
context.define(currentUser, assumedRoles);
|
||||
|
||||
return ResponseEntity.ok(mapList(rbacUserRepository.findPermissionsOfUserByUuid(userUuid), RbacUserPermissionResource.class));
|
||||
return ResponseEntity.ok(mapList(
|
||||
rbacUserRepository.findPermissionsOfUserByUuid(userUuid),
|
||||
RbacUserPermissionResource.class));
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,38 @@
|
||||
--liquibase formatted sql
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset context-DEFINE:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
/*
|
||||
Defines the transaction context.
|
||||
*/
|
||||
|
||||
create or replace procedure defineContext(
|
||||
currentTask varchar,
|
||||
currentRequest varchar,
|
||||
currentUser varchar,
|
||||
assumedRoles varchar
|
||||
)
|
||||
language plpgsql as $$
|
||||
begin
|
||||
raise notice 'currentRequest: %', defineContext.currentRequest;
|
||||
execute format('set local hsadminng.currentTask to %L', currentTask);
|
||||
execute format('set local hsadminng.currentUser to %L', currentUser);
|
||||
if length(assumedRoles) > 0 then
|
||||
execute format('set local hsadminng.assumedRoles to %L', assumedRoles);
|
||||
else
|
||||
execute format('set local hsadminng.assumedRoles to %L', '');
|
||||
end if;
|
||||
end; $$;
|
||||
--//
|
||||
|
||||
|
||||
-- ============================================================================
|
||||
--changeset context-CURRENT-TASK:1 endDelimiter:--//
|
||||
-- ----------------------------------------------------------------------------
|
||||
/*
|
||||
Returns the current tas as set by `hsadminng.currentTask`.
|
||||
Returns the current task as set by `hsadminng.currentTask`.
|
||||
Raises exception if not set.
|
||||
*/
|
||||
create or replace function currentTask()
|
||||
@ -117,7 +145,7 @@ create or replace function findIdNameByObjectUuid(objectTable varchar, objectUui
|
||||
returns null on null input
|
||||
language plpgsql as $$
|
||||
declare
|
||||
sql varchar;
|
||||
sql varchar;
|
||||
idName varchar;
|
||||
begin
|
||||
objectTable := pureIdentifier(objectTable);
|
||||
|
Reference in New Issue
Block a user