Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add xDS group service. #981

Merged
merged 8 commits into from
Aug 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions dependencies.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ dropwizard-metrics = "4.2.21"
eddsa = "0.3.0"
findbugs = "3.0.2"
futures-completable = "0.3.6"
grpc-java = "1.64.0"
guava = "33.2.1-jre"
guava-failureaccess = "1.0.1"
hamcrest-library = "2.2"
Expand Down Expand Up @@ -53,6 +54,8 @@ nexus-publish-plugin = "2.0.0"
node-gradle-plugin = "7.0.2"
osdetector = "1.7.3"
proguard = "7.4.2"
protobuf = "3.25.1"
protobuf-gradle-plugin = "0.8.19"
quartz = "2.3.2"
shadow-gradle-plugin = "7.1.2"
shiro = "1.3.2"
Expand All @@ -72,6 +75,7 @@ zookeeper = "3.7.2"
[boms]
armeria = { module = "com.linecorp.armeria:armeria-bom", version.ref = "armeria" }
jackson = { module = "com.fasterxml.jackson:jackson-bom", version.ref = "jackson" }
grpc-java = { module = "io.grpc:grpc-bom", version.ref = "grpc-java" }
junit5 = { module = "org.junit:junit-bom", version.ref = "junit5" }

[libraries.armeria]
Expand Down Expand Up @@ -328,6 +332,13 @@ version.ref = "mockito"
module = "com.guardsquare:proguard-gradle"
version.ref = "proguard"

[libraries.protobuf-protoc]
module = "com.google.protobuf:protoc"
version.ref = "protobuf"
[libraries.protobuf-gradle-plugin]
module = "com.google.protobuf:protobuf-gradle-plugin"
version.ref = "protobuf-gradle-plugin"

[libraries.quartz]
module = "org.quartz-scheduler:quartz"
version.ref = "quartz"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,9 @@
import com.linecorp.centraldogma.server.internal.api.TokenService;
import com.linecorp.centraldogma.server.internal.api.WatchService;
import com.linecorp.centraldogma.server.internal.api.auth.ApplicationTokenAuthorizer;
import com.linecorp.centraldogma.server.internal.api.auth.RequiresPermissionDecorator.RequiresReadPermissionDecoratorFactory;
import com.linecorp.centraldogma.server.internal.api.auth.RequiresPermissionDecorator.RequiresWritePermissionDecoratorFactory;
import com.linecorp.centraldogma.server.internal.api.auth.RequiresRoleDecorator.RequiresRoleDecoratorFactory;
import com.linecorp.centraldogma.server.internal.api.converter.HttpApiRequestConverter;
import com.linecorp.centraldogma.server.internal.mirror.DefaultMirroringServicePlugin;
import com.linecorp.centraldogma.server.internal.replication.ZooKeeperCommandExecutor;
Expand All @@ -154,7 +157,6 @@
import com.linecorp.centraldogma.server.management.ServerStatus;
import com.linecorp.centraldogma.server.management.ServerStatusManager;
import com.linecorp.centraldogma.server.metadata.MetadataService;
import com.linecorp.centraldogma.server.metadata.MetadataServiceInjector;
import com.linecorp.centraldogma.server.mirror.MirrorProvider;
import com.linecorp.centraldogma.server.plugin.AllReplicasPlugin;
import com.linecorp.centraldogma.server.plugin.Plugin;
Expand Down Expand Up @@ -616,8 +618,9 @@ private Server startServer(ProjectManager pm, CommandExecutor executor,
HttpHeaders.of(HttpHeaderNames.AUTHORIZATION,
"Bearer " + CsrfToken.ANONYMOUS))
.build());

configureHttpApi(sb, projectApiManager, executor, watchService, mds, authProvider, sessionManager,
final Function<? super HttpService, AuthService> authService =
authService(mds, authProvider, sessionManager);
configureHttpApi(sb, projectApiManager, executor, watchService, mds, authProvider, authService,
meterRegistry);

configureMetrics(sb, meterRegistry);
Expand All @@ -640,7 +643,7 @@ private Server startServer(ProjectManager pm, CommandExecutor executor,
if (pluginsForAllReplicas != null) {
final PluginInitContext pluginInitContext =
new PluginInitContext(config(), pm, executor, meterRegistry, purgeWorker, sb,
projectInitializer);
authService, projectInitializer);
pluginsForAllReplicas.plugins()
.forEach(p -> {
if (!(p instanceof AllReplicasPlugin)) {
Expand Down Expand Up @@ -743,53 +746,47 @@ private void configureThriftService(ServerBuilder sb, ProjectApiManager projectA
sb.service("/cd/thrift/v1", thriftService);
}

private Function<? super HttpService, AuthService> authService(
MetadataService mds, @Nullable AuthProvider authProvider, @Nullable SessionManager sessionManager) {
if (authProvider == null) {
return AuthService.newDecorator(new CsrfTokenAuthorizer());
}
final AuthConfig authCfg = cfg.authConfig();
assert authCfg != null : "authCfg";
assert sessionManager != null : "sessionManager";
final Authorizer<HttpRequest> tokenAuthorizer =
new ApplicationTokenAuthorizer(mds::findTokenBySecret)
.orElse(new SessionTokenAuthorizer(sessionManager,
authCfg.administrators()));
return AuthService.builder()
.add(tokenAuthorizer)
.onFailure(new CentralDogmaAuthFailureHandler())
.newDecorator();
}

private void configureHttpApi(ServerBuilder sb,
ProjectApiManager projectApiManager, CommandExecutor executor,
WatchService watchService, MetadataService mds,
@Nullable AuthProvider authProvider,
@Nullable SessionManager sessionManager, MeterRegistry meterRegistry) {
Function<? super HttpService, ? extends HttpService> decorator;

if (authProvider != null) {
sb.service("/security_enabled", new AbstractHttpService() {
@Override
protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) {
return HttpResponse.of(HttpStatus.OK);
}
});

final AuthConfig authCfg = cfg.authConfig();
assert authCfg != null : "authCfg";
assert sessionManager != null : "sessionManager";
final Authorizer<HttpRequest> tokenAuthorizer =
new ApplicationTokenAuthorizer(mds::findTokenBySecret)
.orElse(new SessionTokenAuthorizer(sessionManager,
authCfg.administrators()));
decorator = MetadataServiceInjector
.newDecorator(mds)
.andThen(AuthService.builder()
.add(tokenAuthorizer)
.onFailure(new CentralDogmaAuthFailureHandler())
.newDecorator());
} else {
decorator = MetadataServiceInjector
.newDecorator(mds)
.andThen(AuthService.newDecorator(new CsrfTokenAuthorizer()));
}

Function<? super HttpService, AuthService> authService,
MeterRegistry meterRegistry) {
final DependencyInjector dependencyInjector = DependencyInjector.ofSingletons(
// Use the default ObjectMapper without any configuration.
// See JacksonRequestConverterFunctionTest
new JacksonRequestConverterFunction(new ObjectMapper()),
new HttpApiRequestConverter(projectApiManager)
new HttpApiRequestConverter(projectApiManager),
new RequiresReadPermissionDecoratorFactory(mds),
new RequiresWritePermissionDecoratorFactory(mds),
new RequiresRoleDecoratorFactory(mds)
);
sb.dependencyInjector(dependencyInjector, false)
// TODO(ikhoon): Consider exposing ReflectiveDependencyInjector as a public API via
// DependencyInjector.ofReflective()
.dependencyInjector(new ReflectiveDependencyInjector(), false);

// Enable content compression for API responses.
decorator = decorator.andThen(contentEncodingDecorator());
final Function<? super HttpService, ? extends HttpService> decorator =
authService.andThen(contentEncodingDecorator());
for (String path : ImmutableList.of(API_V0_PATH_PREFIX, API_V1_PATH_PREFIX)) {
final DecoratingServiceBindingBuilder decoratorBuilder =
sb.routeDecorator().pathPrefix(path);
Expand All @@ -803,7 +800,6 @@ protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) {
}

assert statusManager != null;
final ContextPathServicesBuilder apiV0ServiceBuilder = sb.contextPath(API_V0_PATH_PREFIX);
final ContextPathServicesBuilder apiV1ServiceBuilder = sb.contextPath(API_V1_PATH_PREFIX);
apiV1ServiceBuilder
.annotatedService(new AdministrativeService(executor, statusManager))
Expand Down Expand Up @@ -831,12 +827,14 @@ public String serviceName(ServiceRequestContext ctx) {
})
.build(new ContentServiceV1(executor, watchService, meterRegistry));

sb.annotatedService()
.decorator(decorator)
.decorator(DecodingService.newDecorator())
.build(new GitHttpService(projectApiManager));

if (authProvider != null) {
sb.service("/security_enabled", new AbstractHttpService() {
@Override
protected HttpResponse doGet(ServiceRequestContext ctx, HttpRequest req) {
return HttpResponse.of(HttpStatus.OK);
}
});

final AuthConfig authCfg = cfg.authConfig();
assert authCfg != null : "authCfg";
apiV1ServiceBuilder
Expand All @@ -858,10 +856,15 @@ public String serviceName(ServiceRequestContext ctx) {
authProvider.moreServices().forEach(sb::service);
}

sb.annotatedService()
.decorator(decorator)
.decorator(DecodingService.newDecorator())
.build(new GitHttpService(projectApiManager));

if (cfg.isWebAppEnabled()) {
apiV0ServiceBuilder
.annotatedService(new UserService(executor))
.annotatedService(new RepositoryService(projectApiManager, executor));
sb.contextPath(API_V0_PATH_PREFIX)
.annotatedService(new UserService(executor))
.annotatedService(new RepositoryService(projectApiManager, executor));

if (authProvider != null) {
// Will redirect to /web/auth/login by default.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,18 @@ public final class AuthUtil {
public static Author currentAuthor(ServiceRequestContext ctx) {
final User user = ctx.attr(CURRENT_USER);
assert user != null;
return user == User.DEFAULT ? Author.DEFAULT
: new Author(user.name(), user.email());
return getAuthor(user);
}

public static Author currentAuthor() {
return currentAuthor(RequestContext.current());
}

public static Author getAuthor(User user) {
return user == User.DEFAULT ? Author.DEFAULT
: new Author(user.name(), user.email());
}

public static User currentUser(ServiceRequestContext ctx) {
return ctx.attr(CURRENT_USER);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright 2024 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.internal.api;

import java.util.concurrent.CompletableFuture;

import com.linecorp.centraldogma.common.Author;
import com.linecorp.centraldogma.common.Revision;
import com.linecorp.centraldogma.server.command.Command;
import com.linecorp.centraldogma.server.command.CommandExecutor;
import com.linecorp.centraldogma.server.metadata.MetadataService;

public final class RepositoryServiceUtil {

public static CompletableFuture<Revision> createRepository(
CommandExecutor commandExecutor, MetadataService mds,
Author author, String projectName, String repoName) {
ikhoon marked this conversation as resolved.
Show resolved Hide resolved
return commandExecutor.execute(Command.createRepository(author, projectName, repoName))
.thenCompose(unused -> mds.addRepo(author, projectName, repoName));
}

public static CompletableFuture<Revision> removeRepository(
CommandExecutor commandExecutor, MetadataService mds, Author author,
String projectName, String repoName) {
return commandExecutor.execute(Command.removeRepository(author, projectName, repoName))
.thenCompose(unused -> mds.removeRepo(author, projectName, repoName));
}

private RepositoryServiceUtil() {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -119,13 +119,15 @@ public CompletableFuture<List<RepositoryDto>> listRepositories(ServiceRequestCon
public CompletableFuture<RepositoryDto> createRepository(ServiceRequestContext ctx, Project project,
CreateRepositoryRequest request,
Author author) {
if (Project.isReservedRepoName(request.name())) {
final String repoName = request.name();
if (Project.isReservedRepoName(repoName)) {
return HttpApiUtil.throwResponse(ctx, HttpStatus.FORBIDDEN,
"A reserved repository cannot be created.");
}
return execute(Command.createRepository(author, project.name(), request.name()))
.thenCompose(unused -> mds.addRepo(author, project.name(), request.name()))
.handle(returnOrThrow(() -> DtoConverter.convert(project.repos().get(request.name()))));
final CommandExecutor commandExecutor = executor();
final CompletableFuture<Revision> future =
RepositoryServiceUtil.createRepository(commandExecutor, mds, author, project.name(), repoName);
return future.handle(returnOrThrow(() -> DtoConverter.convert(project.repos().get(repoName))));
}

/**
Expand All @@ -143,9 +145,9 @@ public CompletableFuture<Void> removeRepository(ServiceRequestContext ctx,
return HttpApiUtil.throwResponse(ctx, HttpStatus.FORBIDDEN,
"A reserved repository cannot be removed.");
}
return execute(Command.removeRepository(author, repository.parent().name(), repository.name()))
.thenCompose(unused -> mds.removeRepo(author, repository.parent().name(), repository.name()))
.handle(HttpApiUtil::throwUnsafelyIfNonNull);
return RepositoryServiceUtil.removeRepository(executor(), mds, author,
repository.parent().name(), repoName)
.handle(HttpApiUtil::throwUnsafelyIfNonNull);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,6 @@
import com.linecorp.centraldogma.server.internal.api.GitHttpService;
import com.linecorp.centraldogma.server.internal.api.HttpApiUtil;
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.User;
import com.linecorp.centraldogma.server.storage.project.Project;
Expand All @@ -52,24 +51,26 @@
*/
public final class RequiresPermissionDecorator extends SimpleDecoratingHttpService {

private final MetadataService mds;

private final Permission requiredPermission;
@Nullable
private final String projectName;
@Nullable
private final String repoName;

RequiresPermissionDecorator(HttpService delegate, Permission requiredPermission,
RequiresPermissionDecorator(HttpService delegate, MetadataService mds, Permission requiredPermission,
@Nullable String projectName,
@Nullable String repoName) {
super(delegate);
this.mds = requireNonNull(mds, "mds");
this.requiredPermission = requireNonNull(requiredPermission, "requiredPermission");
this.projectName = projectName;
this.repoName = repoName;
}

@Override
public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exception {
final MetadataService mds = MetadataServiceInjector.getMetadataService(ctx);
ikhoon marked this conversation as resolved.
Show resolved Hide resolved
final User user = AuthUtil.currentUser(ctx);

String projectName = this.projectName;
Expand All @@ -89,7 +90,7 @@ public HttpResponse serve(ServiceRequestContext ctx, HttpRequest req) throws Exc
}
return unwrap().serve(ctx, req);
}
return serveUserRepo(ctx, req, mds, user, projectName, maybeRemoveGitSuffix(repoName));
return serveUserRepo(ctx, req, user, projectName, maybeRemoveGitSuffix(repoName));
}

private static HttpResponse throwForbiddenResponse(ServiceRequestContext ctx, String projectName,
Expand All @@ -111,8 +112,7 @@ private static String maybeRemoveGitSuffix(String repoName) {
}

private HttpResponse serveUserRepo(ServiceRequestContext ctx, HttpRequest req,
MetadataService mds, User user,
String projectName, String repoName) throws Exception {
User user, String projectName, String repoName) throws Exception {
final CompletionStage<Collection<Permission>> f;
try {
f = mds.findPermissions(projectName, repoName, user);
Expand Down Expand Up @@ -144,10 +144,17 @@ private HttpResponse serveUserRepo(ServiceRequestContext ctx, HttpRequest req,
*/
public static final class RequiresReadPermissionDecoratorFactory
implements DecoratorFactoryFunction<RequiresReadPermission> {

private final MetadataService mds;

public RequiresReadPermissionDecoratorFactory(MetadataService mds) {
this.mds = requireNonNull(mds, "mds");
}

@Override
public Function<? super HttpService, ? extends HttpService>
newDecorator(RequiresReadPermission parameter) {
return delegate -> new RequiresPermissionDecorator(delegate, Permission.READ,
return delegate -> new RequiresPermissionDecorator(delegate, mds, Permission.READ,
Strings.emptyToNull(parameter.project()),
Strings.emptyToNull(parameter.repository()));
}
Expand All @@ -159,10 +166,17 @@ public static final class RequiresReadPermissionDecoratorFactory
*/
public static final class RequiresWritePermissionDecoratorFactory
implements DecoratorFactoryFunction<RequiresWritePermission> {

private final MetadataService mds;

public RequiresWritePermissionDecoratorFactory(MetadataService mds) {
this.mds = requireNonNull(mds, "mds");
}

@Override
public Function<? super HttpService, ? extends HttpService>
newDecorator(RequiresWritePermission parameter) {
return delegate -> new RequiresPermissionDecorator(delegate, Permission.WRITE,
return delegate -> new RequiresPermissionDecorator(delegate, mds, Permission.WRITE,
Strings.emptyToNull(parameter.project()),
Strings.emptyToNull(parameter.repository()));
}
Expand Down
Loading
Loading