Fix build issues - Spring Boot plugin, imports, and ENV default #116

Merged
hitanshu merged 18 commits from Dave/CFTunnels:ISSUE-114 into test 2026-04-18 16:16:05 +00:00
Showing only changes of commit 7e5d20e8ac - Show all commits

View File

@ -22,7 +22,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.web.servlet.error.ErrorController;
import org.springframework.dao.DataAccessException;
import org.springframework.http.*;
import org.springframework.http.*;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
@ -36,6 +35,41 @@ import java.util.Map;
import java.util.NoSuchElementException;
import java.util.UUID;
/**
* REST Controller for managing Cloudflare Tunnels.
*
* <p>This controller provides the public API for managing Cloudflare Tunnels
* and their ingress mappings. All endpoints require authentication via OIDC
* and are protected by role-based access control.</p>
*
* <p><b>Base URL:</b> {@code /cloudflare}</p>
*
* <p><b>Authentication:</b> OIDC-based with role-based access</p>
*
* <p><b>Available Roles:</b></p>
* <ul>
* <li>USER - View tunnels and requests</li>
* <li>DEVELOPER - Create/modify/delete mappings</li>
* <li>APPROVER - Approve/reject requests</li>
* <li>ADMIN - Full tunnel configuration access</li>
* </ul>
*
* <p><b>Example Usage:</b></p>
* <pre>
* # Get all tunnels (requires USER role)
* curl -H "Authorization: Bearer &lt;token&gt;" \
* https://api.example.com/cloudflare/tunnels
*
* # Add a mapping (requires ADMIN role)
* curl -X POST -H "Authorization: Bearer &lt;token&gt;" \
* -H "Content-Type: application/json" \
* -d '{"hostname":"api.example.com","service":"http://localhost:8080"}' \
* https://api.example.com/cloudflare/tunnels/{tunnelId}/mappings
* </pre>
*
* @see CloudflareAPIService
* @see MappingRequestService
*/
@RestController
@RequestMapping("/cloudflare")
public class TunnelController implements ErrorController {
@ -63,9 +97,23 @@ public class TunnelController implements ErrorController {
@Autowired
private UserRepository userRepository;
/**
* Current environment (loaded from spring.profiles.active).
*/
@Value("${spring.profiles.active}")
private String environment;
/**
* Get current user information.
*
* <p>Returns the authenticated user's username and roles.</p>
*
* @param oidcUser The authenticated OIDC user
* @return Map containing username and roles
*
* @security Requires USER role
* @response 200 OK
*/
@PreAuthorize("hasAnyRole('USER')")
@GetMapping("/whoami")
public Map<String,Object> whoAmI(@AuthenticationPrincipal OidcUser oidcUser) {
@ -79,6 +127,19 @@ public class TunnelController implements ErrorController {
);
}
/**
* Get all tunnels from Cloudflare API.
*
* <p>Fetches the complete list of tunnels from Cloudflare,
* including their status and configuration from the Cloudflare API.</p>
*
* @return Map containing list of all tunnels
*
* @security Requires USER role
* @response 200 OK with tunnel list
* @response 500 Internal Server Error if API call fails
* @see <a href="https://api.cloudflare.com/#cfd_tunnel-get-tunnels">Cloudflare API</a>
*/
@PreAuthorize("hasAnyRole('USER')")
@GetMapping("/tunnels")
@Operation( security = { @SecurityRequirement(name = "oidcAuth") } )
@ -92,6 +153,19 @@ public class TunnelController implements ErrorController {
return ResponseEntity.ok(jsonResponse);
}
/**
* Get locally configured tunnels.
*
* <p>Returns the tunnels that have been configured locally
* with environment associations.</p>
*
* @return Map containing list of configured tunnels
*
* @security Requires USER role
* @response 200 OK with tunnel list
* @response 500 Internal Server Error if database access fails
* @see CloudflareAPIService#getAllConfiguredTunnels()
*/
@PreAuthorize("hasAnyRole('USER')")
@GetMapping("/configured/tunnels")
public ResponseEntity<Map<String,Object>> getConfiguredTunnels(){
@ -106,6 +180,17 @@ public class TunnelController implements ErrorController {
}
}
/**
* Get all mapping requests.
*
* <p>Returns all pending, approved, and rejected mapping requests.</p>
*
* @return Map containing list of all requests
*
* @security Requires USER role
* @response 200 OK with request list
* @response 500 Internal Server Error if database access fails
*/
@PreAuthorize("hasAnyRole('USER')")
@GetMapping("/requests")
public ResponseEntity<Map<String,Object>> getAllRequests() {
@ -120,6 +205,20 @@ public class TunnelController implements ErrorController {
}
}
/**
* Get tunnel configuration from Cloudflare.
*
* <p>Fetches the complete configuration for a specific tunnel,
* including all ingress rules.</p>
*
* @param tunnelId The Cloudflare tunnel ID (UUID)
* @return Map containing tunnel configuration
*
* @security Requires DEVELOPER role
* @response 200 OK with configuration
* @response 500 Internal Server Error
* @see <a href="https://api.cloudflare.com/#cfd_tunnel-get-tunnel-config">Cloudflare API</a>
*/
@PreAuthorize("hasAnyRole('DEVELOPER')")
@GetMapping("/tunnels/{tunnelId}/mappings")
public ResponseEntity<Map<String,Object>> getTunnelConfigurations(@PathVariable String tunnelId) {
@ -132,22 +231,44 @@ public class TunnelController implements ErrorController {
return ResponseEntity.ok(jsonResponse);
}
// 50df9101-f625-4618-b7c5-100338a57124
/**
* Add an ingress mapping to a tunnel.
*
* <p>Adds a new ingress rule to the tunnel configuration.
* The new rule is inserted at the second-to-last position,
* before any catch-all rule.</p>
*
* @param tunnelId The Cloudflare tunnel ID (UUID)
* @param ingress The ingress rule to add
* @return Map containing the updated configuration
*
* @security Requires ADMIN role
* @response 200 OK with updated configuration
* @response 400 Bad Request if ingress is invalid
* @response 500 Internal Server Error
*
* @example
* {
* "hostname": "api.example.com",
* "service": "http://localhost:8080",
* "originRequest": {"noTLSVerify": true}
* }
*/
@PreAuthorize("hasAnyRole('ADMIN')")
@PostMapping("/tunnels/{tunnelId}/mappings")
public ResponseEntity<Map<String, Object>> addTunnelconfiguration(@PathVariable String tunnelId, @RequestBody Ingress ingress) throws JsonProcessingException {
ResponseEntity<TunnelResponse> responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class);
// * * Inserting new ingress value at second-to last position in list
// Inserting new ingress value at second-to last position in list
Config config = responseEntity.getBody().getResult().getConfig();
List<Ingress> response_ingress = config.getIngress();
response_ingress.add(response_ingress.size()-1, ingress);
// * * Hitting put endpoint
// Hitting put endpoint
ResponseEntity<TunnelResponse> response = cloudflareAPIService.putCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class, config);
// * * Displaying response
// Displaying response
Map<String, Object> jsonResponse = new HashMap<>();
jsonResponse.put("status", response.getStatusCode().toString());
jsonResponse.put("data", response.getBody());
@ -155,21 +276,34 @@ public class TunnelController implements ErrorController {
return ResponseEntity.ok(jsonResponse);
}
/**
* Delete an ingress mapping from a tunnel.
*
* <p>Removes an ingress rule by hostname from the tunnel configuration.</p>
*
* @param tunnelId The Cloudflare tunnel ID (UUID)
* @param ingress Ingress containing hostname to delete (only hostname field is used)
* @return Map containing the result
*
* @security Requires DEVELOPER role
* @response 200 OK with updated configuration
* @response 409 Conflict if hostname not found
*/
@PreAuthorize("hasAnyRole('DEVELOPER')")
@DeleteMapping("/tunnels/{tunnelId}/mappings")
public ResponseEntity<Map<String, Object>> deleteTunnelConfiguration(@PathVariable String tunnelId, @RequestBody Ingress ingress) throws JsonProcessingException {
ResponseEntity<TunnelResponse> responseEntity = cloudflareAPIService.getCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class);
// * * Deleting the selected ingress value
// Deleting the selected ingress value
Config config = responseEntity.getBody().getResult().getConfig();
List<Ingress> response_ingress = config.getIngress();
Boolean result = Ingress.deleteByHostName(response_ingress, ingress.getHostname());
// * * Hitting put endpoint
// Hitting put endpoint
ResponseEntity<TunnelResponse> response = cloudflareAPIService.putCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class, config);
// * * Displaying response
// Displaying response
Map<String, Object> jsonResponse = new HashMap<>();
if (result){
@ -184,6 +318,24 @@ public class TunnelController implements ErrorController {
return ResponseEntity.ok(jsonResponse);
}
/**
* Create a mapping change request.
*
* <p>Creates a new request for changing tunnel ingress mappings.
* The request starts in PENDING status and must be approved
* before the changes are applied.</p>
*
* @param tunnelId The Cloudflare tunnel ID (UUID)
* @param oidcUser The authenticated user
* @param ingess The ingress configuration to request
* @return The created request with PENDING status
*
* @security Requires DEVELOPER role
* @response 201 Created with request
* @response 400 Bad Request if invalid
*
* @see MappingRequestService#createMappingRequest(String, Ingress, OidcUser)
*/
@PreAuthorize("hasAnyRole('DEVELOPER')")
@PostMapping("/tunnels/configure/{tunnelId}/requests")
public ResponseEntity<Request> createTunnelMappingRequest(@PathVariable String tunnelId, @AuthenticationPrincipal OidcUser oidcUser, @RequestBody Ingress ingess){
@ -193,6 +345,21 @@ public class TunnelController implements ErrorController {
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
/**
* Approve a mapping request.
*
* <p>Approves a pending mapping request. If approved, the
* mapping will be applied to the Cloudflare tunnel.</p>
*
* @param requestId The ID of the request to approve
* @param oidcUser The approver (must have APPROVER role)
* @return The updated request with APPROVED status
*
* @security Requires APPROVER role
* @response 200 OK with approved request
* @response 404 Not Found if request doesn't exist
* @response 409 Conflict if request already processed
*/
@PreAuthorize("hasAnyRole('APPROVER')")
@PutMapping("/requests/{requestId}/approve")
public ResponseEntity<Request> approveMappingRequest(@PathVariable UUID requestId, @AuthenticationPrincipal OidcUser oidcUser) {
@ -210,6 +377,21 @@ public class TunnelController implements ErrorController {
}
}
/**
* Reject a mapping request.
*
* <p>Rejects a pending mapping request. No changes
* will be made to the tunnel.</p>
*
* @param requestId The ID of the request to reject
* @param oidcUser The rejecter (must have APPROVER role)
* @return The updated request with REJECTED status
*
* @security Requires APPROVER role
* @response 200 OK with rejected request
* @response 404 Not Found
* @response 409 Conflict
*/
@PreAuthorize("hasAnyRole('APPROVER')")
@PutMapping("/requests/{requestId}/reject")
public ResponseEntity<Request> rejectMappingRequest(@PathVariable UUID requestId, @AuthenticationPrincipal OidcUser oidcUser) {
@ -227,13 +409,36 @@ public class TunnelController implements ErrorController {
}
}
/**
* Configure a tunnel for the current environment.
*
* <p>Creates a local configuration entry for a tunnel,
* associating it with the current environment (from spring.profiles.active).</p>
*
* <p><b>Response Codes:</b></p>
* <ul>
* <li>200 - Created/updated with new tunnel</li>
* <li>204 - No changes needed</li>
* <li>404 - Tunnel not found in Cloudflare</li>
* </ul>
*
* @param tunnelId The Cloudflare tunnel ID (UUID)
* @param user The authenticated user
* @return The tunnel configuration
*
* @security Requires ADMIN role
* @response 200 OK with tunnel
* @response 204 No Content
* @response 404 Not Found
* @response 500 Internal Server Error
*/
@PreAuthorize("hasAnyRole('ADMIN')")
@PutMapping("/tunnels/configure/{tunnelId}")
public ResponseEntity<Tunnel> configureTunnelForEnvironment(@PathVariable String tunnelId, @AuthenticationPrincipal OidcUser user) {
/*
* * Returns 200 if an object is created or updated with a new representation of the object
* * Returns 204 if the object state did not need any changing.
* * Returns 404 if the tunnelId is not valid
* Returns 200 if an object is created or updated with a new representation of the object
* Returns 204 if the object state did not need any changing.
* Returns 404 if the tunnelId is not valid
*/
try {