diff --git a/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java b/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java index 0eef4481..1fccc926 100644 --- a/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java +++ b/src/main/java/io/seqera/tower/cli/commands/AbstractApiCmd.java @@ -18,6 +18,8 @@ import io.seqera.tower.ApiClient; import io.seqera.tower.ApiException; +import io.seqera.tower.ApiResponse; +import io.seqera.tower.Pair; import io.seqera.tower.api.ActionsApi; import io.seqera.tower.api.AvatarsApi; import io.seqera.tower.api.ComputeEnvsApi; @@ -78,6 +80,7 @@ import picocli.CommandLine; import javax.ws.rs.client.Entity; +import javax.ws.rs.core.GenericType; import javax.ws.rs.core.MediaType; import java.io.IOException; import java.net.URLConnection; @@ -318,6 +321,17 @@ public ClientConfig getDefaultClientConfig() { return config; } + @Override + public ApiResponse invokeAPI(String operation, String path, String method, List queryParams, Object body, Map headerParams, Map cookieParams, Map formParams, String accept, String contentType, String[] authNames, GenericType returnType, boolean isBodyNullable) throws ApiException { + // Some operations are missing their security requirement in the OpenAPI spec (e.g. the workflow + // star endpoints), so the generated SDK passes no auth names and skips authentication entirely, + // sending the request unauthenticated. Fall back to the only scheme this client uses. + if (authNames == null || authNames.length == 0) { + authNames = new String[]{"BearerAuth"}; + } + return super.invokeAPI(operation, path, method, queryParams, body, headerParams, cookieParams, formParams, accept, contentType, authNames, returnType, isBodyNullable); + } + @Override protected void applyDebugSetting(ClientConfig clientConfig) { if (app().verbose) { diff --git a/src/test/java/io/seqera/tower/cli/runs/RunsCmdTest.java b/src/test/java/io/seqera/tower/cli/runs/RunsCmdTest.java index 74019b24..906fe00e 100644 --- a/src/test/java/io/seqera/tower/cli/runs/RunsCmdTest.java +++ b/src/test/java/io/seqera/tower/cli/runs/RunsCmdTest.java @@ -198,6 +198,34 @@ void testUnstarNotStarred(MockServerClient mock) { assertEquals(1, out.exitCode); } + @Test + void testStarSendsAuthorization(MockServerClient mock) { + mock.when( + request().withMethod("POST").withPath("/workflow/5dAZoXrcmZXRO4/star"), exactly(1) + ).respond( + response().withStatusCode(200).withBody("{\"workflowId\":\"5dAZoXrcmZXRO4\"}").withContentType(MediaType.APPLICATION_JSON) + ); + + exec(mock, "runs", "star", "-i", "5dAZoXrcmZXRO4"); + + mock.verify(request().withMethod("POST").withPath("/workflow/5dAZoXrcmZXRO4/star") + .withHeader("Authorization", "Bearer " + token()), VerificationTimes.atLeast(1)); + } + + @Test + void testUnstarSendsAuthorization(MockServerClient mock) { + mock.when( + request().withMethod("DELETE").withPath("/workflow/5dAZoXrcmZXRO4/star"), exactly(1) + ).respond( + response().withStatusCode(204) + ); + + exec(mock, "runs", "unstar", "-i", "5dAZoXrcmZXRO4"); + + mock.verify(request().withMethod("DELETE").withPath("/workflow/5dAZoXrcmZXRO4/star") + .withHeader("Authorization", "Bearer " + token()), VerificationTimes.atLeast(1)); + } + @Test void testCancelForbidden(MockServerClient mock) { mock.when(