RbacContextController.java
package net.hostsharing.hsadminng.rbac.context;
import io.micrometer.core.annotation.Timed;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import net.hostsharing.hsadminng.config.ApiKeyAuthenticationFilter;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacContextApi;
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacContextApiKeyResource;
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacContextResource;
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacRoleResource;
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectGroupResource;
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacSubjectResource;
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.SubjectTypeResource;
import net.hostsharing.hsadminng.rbac.role.RbacRoleEntity;
import net.hostsharing.hsadminng.rbac.role.RbacRoleRepository;
import net.hostsharing.hsadminng.rbac.subject.RealSubjectEntity;
import net.hostsharing.hsadminng.rbac.subject.RealSubjectRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
@RestController
@PreAuthorize("isAuthenticated()")
@SecurityRequirement(name = "bearerAuth")
public class RbacContextController implements RbacContextApi {
@Autowired
private Context context;
@Autowired
private StrictMapper mapper;
@Autowired
private RealSubjectRepository realSubjectRepo;
@Autowired
private RbacRoleRepository roleRepo;
@Override
@Transactional(readOnly = true)
@Timed("app.rbac.current.api.getContext")
public ResponseEntity<RbacContextResource> getContext(final String roleNamesToAssume) {
// fetch subject data before assuming any roles; otherwise we might have no SELECT permission anymore
context.define();
final var currentSubjectUuid = context.fetchCurrentSubjectUuid();
final var currentSubject = realSubjectRepo.findCurrentSubject();
if (currentSubject == null) {
return ResponseEntity.notFound().build();
}
final boolean isGlobalAdmin = context.isGlobalAdmin();
// claimed groups come straight from the JWT and are independent of any assumed role
final var claimedGroups = context.fetchClaimedSubjectGroupNames();
// now we can assume the roles
context.assumeRoles(roleNamesToAssume);
final var assumedRoles = roleRepo.fetchAssumedRoles();
final var effectiveGroups = realSubjectRepo.findEffectiveSubjectGroups();
// finally, return the result
final var result = rbacContextResponse(
currentSubjectUuid, currentSubject, claimedGroups, effectiveGroups, assumedRoles, isGlobalAdmin);
return ResponseEntity.ok(result);
}
private RbacContextResource rbacContextResponse(
final UUID currentSubjectUuid,
final RealSubjectEntity currentSubject,
final List<String> claimedGroups,
final List<RealSubjectEntity> effectiveGroups,
final List<RbacRoleEntity> assumedRoles,
final boolean isGlobalAdmin) {
final var result = new RbacContextResource();
final var currentSubjectResource = new RbacSubjectResource();
currentSubjectResource.setUuid(currentSubjectUuid);
currentSubjectResource.setName(currentSubject.getName());
currentSubjectResource.setType(SubjectTypeResource.valueOf(currentSubject.getType().name()));
result.setSubject(currentSubjectResource);
result.setClaimedGroups(claimedGroups);
result.setEffectiveGroups(mapper.mapList(effectiveGroups, RbacSubjectGroupResource.class));
result.setGlobalAdmin(isGlobalAdmin);
final var assumedRolesResource = mapper.mapList(assumedRoles, RbacRoleResource.class);
result.setAssumedRoles(assumedRolesResource);
result.setApiKey(currentApiKeyProperties());
return result;
}
/** The properties of the API-key used to authenticate the current request, from the synthesized JWT;
* null if the request was authenticated via an OIDC JWT instead. */
private static RbacContextApiKeyResource currentApiKeyProperties() {
return ApiKeyAuthenticationFilter.currentApiKeyJwt().map(jwt -> {
final var apiKeyResource = new RbacContextApiKeyResource();
final var scopeClaim = jwt.getClaimAsString(ApiKeyAuthenticationFilter.SCOPE_CLAIM);
apiKeyResource.setScopes(
scopeClaim == null || scopeClaim.isBlank() ? List.of() : Arrays.asList(scopeClaim.split(" ")));
final var expiresAt = jwt.getClaimAsInstant(ApiKeyAuthenticationFilter.API_KEY_EXPIRES_AT_CLAIM);
apiKeyResource.setExpiresAt(expiresAt == null ? null : expiresAt.atOffset(ZoneOffset.UTC));
return apiKeyResource;
}).orElse(null);
}
}