RbacGrantController.java

package net.hostsharing.hsadminng.rbac.grant;

import io.micrometer.core.annotation.Timed;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import net.hostsharing.hsadminng.rbac.context.Context;
import net.hostsharing.hsadminng.mapper.StrictMapper;
import net.hostsharing.hsadminng.rbac.generated.api.v1.api.RbacGrantsApi;
import net.hostsharing.hsadminng.rbac.generated.api.v1.model.RbacGrantResource;
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 org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.util.List;
import java.util.UUID;

@RestController
@PreAuthorize("isAuthenticated()")
@SecurityRequirement(name = "bearerAuth")
public class RbacGrantController implements RbacGrantsApi {

    @Autowired
    private Context context;

    @Autowired
    private StrictMapper mapper;

    @Autowired
    private RbacGrantRepository rbacGrantRepository;

    @PersistenceContext
    private EntityManager em;

    @Override
    @Transactional(readOnly = true)
    @Timed("app.rbac.grants.api.getListOfGrantsByUuid")
    public ResponseEntity<RbacGrantResource> getListOfGrantsByUuid(
            final String assumedRoles,
            final UUID grantedRoleUuid,
            final UUID granteeSubjectUuid) {

        context.assumeRoles(assumedRoles);

        final var id = new RbacGrantId(granteeSubjectUuid, grantedRoleUuid);
        final var result = rbacGrantRepository.findById(id);
        if (result == null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(mapper.map(result, RbacGrantResource.class));
    }

    @Override
    @Transactional(readOnly = true)
    @Timed("app.rbac.grants.api.getListOfSubjectGrants")
    public ResponseEntity<List<RbacGrantResource>> getListOfSubjectGrants(
            final String assumedRoles) {
        context.assumeRoles(assumedRoles);

        return ResponseEntity.ok(mapper.mapList(rbacGrantRepository.findAll(), RbacGrantResource.class));
    }

    @Override
    @Transactional
    @Timed("app.rbac.grants.api.postNewRoleGrantToSubject")
    public ResponseEntity<RbacGrantResource> postNewRoleGrantToSubject(
            final String assumedRoles,
            final RbacGrantResource body) {

        context.assumeRoles(assumedRoles);

        final var granted = rbacGrantRepository.save(mapper.map(body, RbacGrantEntity.class));
        em.flush();
        em.refresh(granted);

        final var uri =
                MvcUriComponentsBuilder.fromController(getClass())
                        .path("/api/rbac.yaml/grants/{roleUuid}")
                        .buildAndExpand(body.getGrantedRoleUuid())
                        .toUri();
        return ResponseEntity.created(uri).body(mapper.map(granted, RbacGrantResource.class));
    }

    @Override
    @Transactional
    @Timed("app.rbac.grants.api.deleteRoleGrantFromSubject")
    public ResponseEntity<Void> deleteRoleGrantFromSubject(
            final String assumedRoles,
            final UUID grantedRoleUuid,
            final UUID granteeSubjectUuid) {

        context.assumeRoles(assumedRoles);

        rbacGrantRepository.deleteByRbacGrantId(new RbacGrantId(granteeSubjectUuid, grantedRoleUuid));

        return ResponseEntity.noContent().build();
    }

// TODO.feat: implement an endpoint to create a Mermaid flowchart with all grants of a given user
//    @GetMapping(
//            path = "/api/rbac/subjects/{subjectUuid}/grants",
//            produces = {"text/vnd.mermaid"})
//    @Transactional(readOnly = true)
//    public ResponseEntity<String> allGrantsOfUserAsMermaid(
//            @RequestHeader(name = "current-subject") String currentSubject,
//            @RequestHeader(name = "Hostsharing-Assumed-Roles", required = false) String assumedRoles) {
//        final var graph = RbacGrantsDiagramService.allGrantsToUser(currentSubject);
//        return ResponseEntity.ok(graph);
//    }

}