From 7e5d20e8acf4b626e66392d4899a3f07bcc48348 Mon Sep 17 00:00:00 2001 From: Dave the Dev Date: Tue, 14 Apr 2026 11:52:50 +0000 Subject: [PATCH] Hithomelabs/CFTunnels#114: Add JavaDoc and API documentation to TunnelController --- .../Controllers/TunnelController.java | 229 +++++++++++++++++- 1 file changed, 217 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java b/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java index 33c4d07..cb77525 100644 --- a/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java +++ b/src/main/java/com/hithomelabs/CFTunnels/Controllers/TunnelController.java @@ -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. + * + *

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.

+ * + *

Base URL: {@code /cloudflare}

+ * + *

Authentication: OIDC-based with role-based access

+ * + *

Available Roles:

+ * + * + *

Example Usage:

+ *
+ * # Get all tunnels (requires USER role)
+ * curl -H "Authorization: Bearer <token>" \
+ *   https://api.example.com/cloudflare/tunnels
+ * 
+ * # Add a mapping (requires ADMIN role)
+ * curl -X POST -H "Authorization: Bearer <token>" \
+ *   -H "Content-Type: application/json" \
+ *   -d '{"hostname":"api.example.com","service":"http://localhost:8080"}' \
+ *   https://api.example.com/cloudflare/tunnels/{tunnelId}/mappings
+ * 
+ * + * @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. + * + *

Returns the authenticated user's username and roles.

+ * + * @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 whoAmI(@AuthenticationPrincipal OidcUser oidcUser) { @@ -79,6 +127,19 @@ public class TunnelController implements ErrorController { ); } + /** + * Get all tunnels from Cloudflare API. + * + *

Fetches the complete list of tunnels from Cloudflare, + * including their status and configuration from the Cloudflare API.

+ * + * @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 Cloudflare API + */ @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. + * + *

Returns the tunnels that have been configured locally + * with environment associations.

+ * + * @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> getConfiguredTunnels(){ @@ -106,6 +180,17 @@ public class TunnelController implements ErrorController { } } + /** + * Get all mapping requests. + * + *

Returns all pending, approved, and rejected mapping requests.

+ * + * @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> getAllRequests() { @@ -120,6 +205,20 @@ public class TunnelController implements ErrorController { } } + /** + * Get tunnel configuration from Cloudflare. + * + *

Fetches the complete configuration for a specific tunnel, + * including all ingress rules.

+ * + * @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 Cloudflare API + */ @PreAuthorize("hasAnyRole('DEVELOPER')") @GetMapping("/tunnels/{tunnelId}/mappings") public ResponseEntity> 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. + * + *

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.

+ * + * @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> addTunnelconfiguration(@PathVariable String tunnelId, @RequestBody Ingress ingress) throws JsonProcessingException { ResponseEntity 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 response_ingress = config.getIngress(); response_ingress.add(response_ingress.size()-1, ingress); - // * * Hitting put endpoint + // Hitting put endpoint ResponseEntity response = cloudflareAPIService.putCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class, config); - // * * Displaying response + // Displaying response Map 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. + * + *

Removes an ingress rule by hostname from the tunnel configuration.

+ * + * @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> deleteTunnelConfiguration(@PathVariable String tunnelId, @RequestBody Ingress ingress) throws JsonProcessingException { ResponseEntity 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 response_ingress = config.getIngress(); Boolean result = Ingress.deleteByHostName(response_ingress, ingress.getHostname()); - // * * Hitting put endpoint + // Hitting put endpoint ResponseEntity response = cloudflareAPIService.putCloudflareTunnelConfigurations(tunnelId, restTemplateConfig.restTemplate(), TunnelResponse.class, config); - // * * Displaying response + // Displaying response Map jsonResponse = new HashMap<>(); if (result){ @@ -184,6 +318,24 @@ public class TunnelController implements ErrorController { return ResponseEntity.ok(jsonResponse); } + /** + * Create a mapping change request. + * + *

Creates a new request for changing tunnel ingress mappings. + * The request starts in PENDING status and must be approved + * before the changes are applied.

+ * + * @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 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. + * + *

Approves a pending mapping request. If approved, the + * mapping will be applied to the Cloudflare tunnel.

+ * + * @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 approveMappingRequest(@PathVariable UUID requestId, @AuthenticationPrincipal OidcUser oidcUser) { @@ -210,6 +377,21 @@ public class TunnelController implements ErrorController { } } + /** + * Reject a mapping request. + * + *

Rejects a pending mapping request. No changes + * will be made to the tunnel.

+ * + * @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 rejectMappingRequest(@PathVariable UUID requestId, @AuthenticationPrincipal OidcUser oidcUser) { @@ -227,13 +409,36 @@ public class TunnelController implements ErrorController { } } + /** + * Configure a tunnel for the current environment. + * + *

Creates a local configuration entry for a tunnel, + * associating it with the current environment (from spring.profiles.active).

+ * + *

Response Codes:

+ *
    + *
  • 200 - Created/updated with new tunnel
  • + *
  • 204 - No changes needed
  • + *
  • 404 - Tunnel not found in Cloudflare
  • + *
+ * + * @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 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 { @@ -249,4 +454,4 @@ public class TunnelController implements ErrorController { } } -} +} \ No newline at end of file