From 2b07420e92f99011d7ee459ee96ac5cc1d035038 Mon Sep 17 00:00:00 2001 From: minux Date: Fri, 8 Dec 2023 14:27:35 +0900 Subject: [PATCH] Fix to access for a member to the meta repository. (#901) Motivation: Despite having the necessary permissions after #898, members were unable to access the meta repository. Modifications: - Implemented a fix to ensure that members can access the meta repository as intended. Result: - Members with appropriate permissions can now successfully access the meta repository. --- .../centraldogma/it/NonRandomTokenTest.java | 4 +- .../it/ReplicationWriteQuotaTest.java | 4 +- .../it/StandaloneWriteQuotaTest.java | 4 +- .../api/auth/RequiresPermissionDecorator.java | 37 +----- .../metadata/MetadataApiServiceTest.java | 119 ++++++++++++++++++ 5 files changed, 131 insertions(+), 37 deletions(-) create mode 100644 server/src/test/java/com/linecorp/centraldogma/server/metadata/MetadataApiServiceTest.java diff --git a/it/server/src/test/java/com/linecorp/centraldogma/it/NonRandomTokenTest.java b/it/server/src/test/java/com/linecorp/centraldogma/it/NonRandomTokenTest.java index 3c9dd699a5..32316af3bd 100644 --- a/it/server/src/test/java/com/linecorp/centraldogma/it/NonRandomTokenTest.java +++ b/it/server/src/test/java/com/linecorp/centraldogma/it/NonRandomTokenTest.java @@ -27,7 +27,7 @@ import com.linecorp.armeria.common.HttpRequest; import com.linecorp.armeria.common.HttpStatus; import com.linecorp.armeria.common.MediaType; -import com.linecorp.armeria.common.auth.OAuth2Token; +import com.linecorp.armeria.common.auth.AuthToken; import com.linecorp.centraldogma.internal.Jackson; import com.linecorp.centraldogma.internal.api.v1.AccessToken; import com.linecorp.centraldogma.server.CentralDogmaBuilder; @@ -57,7 +57,7 @@ void createNonRandomToken() throws Exception { final String sessionId = Jackson.readValue(response.content().array(), AccessToken.class) .accessToken(); final WebClient adminClient = WebClient.builder(client.uri()) - .auth(OAuth2Token.of(sessionId)).build(); + .auth(AuthToken.ofOAuth2(sessionId)).build(); final HttpRequest request = HttpRequest.builder() .post("/api/v1/tokens") diff --git a/it/server/src/test/java/com/linecorp/centraldogma/it/ReplicationWriteQuotaTest.java b/it/server/src/test/java/com/linecorp/centraldogma/it/ReplicationWriteQuotaTest.java index 462896ebd5..022615b311 100644 --- a/it/server/src/test/java/com/linecorp/centraldogma/it/ReplicationWriteQuotaTest.java +++ b/it/server/src/test/java/com/linecorp/centraldogma/it/ReplicationWriteQuotaTest.java @@ -37,7 +37,7 @@ import com.linecorp.armeria.common.AggregatedHttpResponse; import com.linecorp.armeria.common.HttpStatus; import com.linecorp.armeria.common.SessionProtocol; -import com.linecorp.armeria.common.auth.OAuth2Token; +import com.linecorp.armeria.common.auth.AuthToken; import com.linecorp.centraldogma.client.CentralDogma; import com.linecorp.centraldogma.client.armeria.ArmeriaCentralDogmaBuilder; import com.linecorp.centraldogma.internal.Jackson; @@ -92,7 +92,7 @@ void setUp() throws IOException { getSessionId(port1, TestAuthMessageUtil.USERNAME, TestAuthMessageUtil.PASSWORD); webClient = WebClient.builder("http://127.0.0.1:" + port1) - .auth(OAuth2Token.of(adminSessionId)) + .auth(AuthToken.ofOAuth2(adminSessionId)) .build(); dogmaClient = new ArmeriaCentralDogmaBuilder() .accessToken(adminSessionId) diff --git a/it/server/src/test/java/com/linecorp/centraldogma/it/StandaloneWriteQuotaTest.java b/it/server/src/test/java/com/linecorp/centraldogma/it/StandaloneWriteQuotaTest.java index fa7130c999..ec8c8db8de 100644 --- a/it/server/src/test/java/com/linecorp/centraldogma/it/StandaloneWriteQuotaTest.java +++ b/it/server/src/test/java/com/linecorp/centraldogma/it/StandaloneWriteQuotaTest.java @@ -29,7 +29,7 @@ import com.linecorp.armeria.client.WebClient; import com.linecorp.armeria.common.AggregatedHttpResponse; import com.linecorp.armeria.common.HttpStatus; -import com.linecorp.armeria.common.auth.OAuth2Token; +import com.linecorp.armeria.common.auth.AuthToken; import com.linecorp.centraldogma.client.CentralDogma; import com.linecorp.centraldogma.client.armeria.ArmeriaCentralDogmaBuilder; import com.linecorp.centraldogma.internal.Jackson; @@ -61,7 +61,7 @@ void setUp() throws JsonProcessingException, UnknownHostException { final URI uri = dogma.httpClient().uri(); webClient = WebClient.builder(uri) - .auth(OAuth2Token.of(adminSessionId)) + .auth(AuthToken.ofOAuth2(adminSessionId)) .build(); dogmaClient = new ArmeriaCentralDogmaBuilder() .accessToken(adminSessionId) diff --git a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java index 46e1bbcc40..9dfbb6b828 100644 --- a/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java +++ b/server/src/main/java/com/linecorp/centraldogma/server/internal/api/auth/RequiresPermissionDecorator.java @@ -39,7 +39,6 @@ import com.linecorp.centraldogma.server.metadata.MetadataService; import com.linecorp.centraldogma.server.metadata.MetadataServiceInjector; import com.linecorp.centraldogma.server.metadata.Permission; -import com.linecorp.centraldogma.server.metadata.ProjectRole; import com.linecorp.centraldogma.server.metadata.User; import com.linecorp.centraldogma.server.storage.project.Project; @@ -65,37 +64,13 @@ public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exc final String repoName = ctx.pathParam("repoName"); checkArgument(!isNullOrEmpty(repoName), "no repository name is specified"); - if (Project.isReservedRepoName(repoName)) { - return serveInternalRepo(ctx, req, mds, user, projectName, repoName); - } else { - return serveUserRepo(ctx, req, mds, user, projectName, repoName); - } - } - - private HttpResponse serveInternalRepo(ServiceRequestContext ctx, HttpRequest req, - MetadataService mds, User user, - String projectName, String repoName) throws Exception { - if (user.isAdmin()) { - return unwrap().serve(ctx, req); - } if (Project.REPO_DOGMA.equals(repoName)) { - return throwForbiddenResponse(ctx, projectName, repoName, "administrator"); - } - assert Project.REPO_META.equals(repoName); - - return HttpResponse.from(mds.findRole(projectName, user).handle((role, cause) -> { - if (cause != null) { - return handleException(ctx, cause); + if (!user.isAdmin()) { + return throwForbiddenResponse(ctx, projectName, repoName, "administrator"); } - if (role != ProjectRole.OWNER) { - return throwForbiddenResponse(ctx, projectName, repoName, "owner"); - } - try { - return unwrap().serve(ctx, req); - } catch (Exception e) { - return Exceptions.throwUnsafely(e); - } - })); + return unwrap().serve(ctx, req); + } + return serveUserRepo(ctx, req, mds, user, projectName, repoName); } private static HttpResponse throwForbiddenResponse(ServiceRequestContext ctx, String projectName, @@ -115,7 +90,7 @@ private HttpResponse serveUserRepo(ServiceRequestContext ctx, HttpRequest req, return handleException(ctx, cause); } - return HttpResponse.from(f.handle((permission, cause) -> { + return HttpResponse.of(f.handle((permission, cause) -> { if (cause != null) { return handleException(ctx, cause); } diff --git a/server/src/test/java/com/linecorp/centraldogma/server/metadata/MetadataApiServiceTest.java b/server/src/test/java/com/linecorp/centraldogma/server/metadata/MetadataApiServiceTest.java new file mode 100644 index 0000000000..5c09fed86f --- /dev/null +++ b/server/src/test/java/com/linecorp/centraldogma/server/metadata/MetadataApiServiceTest.java @@ -0,0 +1,119 @@ +/* + * Copyright 2023 LINE Corporation + * + * LINE Corporation licenses this file to you under the Apache License, + * version 2.0 (the "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at: + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + */ +package com.linecorp.centraldogma.server.metadata; + +import static com.linecorp.centraldogma.internal.api.v1.HttpApiV1Constants.PROJECTS_PREFIX; +import static com.linecorp.centraldogma.testing.internal.auth.TestAuthMessageUtil.login; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import com.linecorp.armeria.client.WebClient; +import com.linecorp.armeria.common.AggregatedHttpResponse; +import com.linecorp.armeria.common.HttpHeaderNames; +import com.linecorp.armeria.common.HttpMethod; +import com.linecorp.armeria.common.HttpRequest; +import com.linecorp.armeria.common.HttpStatus; +import com.linecorp.armeria.common.MediaType; +import com.linecorp.armeria.common.RequestHeaders; +import com.linecorp.armeria.common.auth.AuthToken; +import com.linecorp.centraldogma.internal.Jackson; +import com.linecorp.centraldogma.internal.api.v1.AccessToken; +import com.linecorp.centraldogma.server.CentralDogmaBuilder; +import com.linecorp.centraldogma.testing.internal.auth.TestAuthMessageUtil; +import com.linecorp.centraldogma.testing.internal.auth.TestAuthProviderFactory; +import com.linecorp.centraldogma.testing.junit.CentralDogmaExtension; + +class MetadataApiServiceTest { + + @RegisterExtension + static CentralDogmaExtension dogma = new CentralDogmaExtension() { + + @Override + protected void configure(CentralDogmaBuilder builder) { + builder.administrators(TestAuthMessageUtil.USERNAME); + builder.authProviderFactory(new TestAuthProviderFactory()); + } + }; + + @Test + void grantPermissionToMemberForMetaRepository() throws Exception { + final String projectName = "foo_proj"; + final WebClient client = dogma.httpClient(); + final AggregatedHttpResponse response = login(client, + TestAuthMessageUtil.USERNAME, + TestAuthMessageUtil.PASSWORD); + + assertThat(response.status()).isEqualTo(HttpStatus.OK); + final String sessionId = Jackson.readValue(response.content().array(), AccessToken.class) + .accessToken(); + final WebClient adminClient = WebClient.builder(client.uri()) + .auth(AuthToken.ofOAuth2(sessionId)).build(); + final RequestHeaders headers = RequestHeaders.of(HttpMethod.POST, PROJECTS_PREFIX, + HttpHeaderNames.CONTENT_TYPE, MediaType.JSON); + final String body = "{\"name\": \"" + projectName + "\"}"; + // Create a project. + assertThat(adminClient.execute(headers, body).aggregate().join().status()).isSameAs(HttpStatus.CREATED); + + final String memberToken = "appToken-secret-member"; + // Create a token with a non-random secret. + HttpRequest request = HttpRequest.builder() + .post("/api/v1/tokens") + .content(MediaType.FORM_DATA, + "secret=" + memberToken + "&isAdmin=false&appId=foo") + .build(); + AggregatedHttpResponse res = adminClient.execute(request).aggregate().join(); + assertThat(res.status()).isEqualTo(HttpStatus.CREATED); + res = adminClient.get("/api/v1/tokens").aggregate().join(); + assertThat(res.contentUtf8()).contains("\"secret\":\"" + memberToken + '"'); + + // Add as a member to the project + request = HttpRequest.builder() + .post("/api/v1/metadata/" + projectName + "/tokens") + .content(MediaType.JSON, + '{' + + "\"id\":\"foo\"," + + "\"role\":\"MEMBER\"" + + '}') + .build(); + res = adminClient.execute(request).aggregate().join(); + assertThat(res.status()).isSameAs(HttpStatus.OK); + + final WebClient memberClient = WebClient.builder(client.uri()) + .auth(AuthToken.ofOAuth2(memberToken)).build(); + res = memberClient.get("/api/v1/projects/" + projectName + "/repos/meta/list").aggregate().join(); + // A member isn't allowed to access the meta repository yet. + assertThat(res.status()).isSameAs(HttpStatus.FORBIDDEN); + assertThat(res.contentUtf8()).contains("You must have READ permission for repository"); + + // Grant a READ permission to the member. + request = HttpRequest.builder() + .post("/api/v1/metadata/" + projectName + "/repos/meta/perm/role") + .content(MediaType.JSON, + "{\n" + + " \"owner\": [ \"READ\", \"WRITE\" ],\n" + + " \"member\": [ \"READ\" ],\n" + + " \"guest\": [ ]\n" + + '}') + .build(); + adminClient.execute(request).aggregate().join(); + + // Now the member can access the meta repository. + res = memberClient.get("/api/v1/projects/" + projectName + "/repos/meta/list").aggregate().join(); + assertThat(res.status()).isSameAs(HttpStatus.NO_CONTENT); + } +}