Skip to content

Commit

Permalink
GH-320 Support execution of commands through the endpoint (Resolve #320)
Browse files Browse the repository at this point in the history
  • Loading branch information
dzikoysk committed Dec 19, 2020
1 parent 347771f commit 91474f6
Show file tree
Hide file tree
Showing 19 changed files with 290 additions and 85 deletions.
6 changes: 6 additions & 0 deletions reposilite-backend/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,12 @@
</dependency>

<!-- Tests -->
<dependency>
<groupId>com.google.http-client</groupId>
<artifactId>google-http-client-jackson2</artifactId>
<version>1.38.0</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,15 @@
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.panda_lang.reposilite.auth.AuthController;
import org.panda_lang.reposilite.auth.AuthEndpoint;
import org.panda_lang.reposilite.auth.PostAuthHandler;
import org.panda_lang.reposilite.config.Configuration;
import org.panda_lang.reposilite.console.CliController;
import org.panda_lang.reposilite.console.RemoteExecutionEndpoint;
import org.panda_lang.reposilite.error.FailureService;
import org.panda_lang.reposilite.frontend.FrontendController;
import org.panda_lang.reposilite.repository.DeployController;
import org.panda_lang.reposilite.repository.LookupApiController;
import org.panda_lang.reposilite.repository.DeployEndpoint;
import org.panda_lang.reposilite.repository.LookupApiEndpoint;
import org.panda_lang.reposilite.repository.LookupController;
import org.panda_lang.reposilite.utils.FilesUtils;
import org.panda_lang.utilities.commons.function.Option;
Expand All @@ -47,7 +48,7 @@ public final class ReposiliteHttpServer {

void start(Configuration configuration, Runnable onStart) {
FailureService failureService = reposilite.getFailureService();
DeployController deployController = new DeployController(reposilite.getContextFactory(), reposilite.getDeployService());
DeployEndpoint deployEndpoint = new DeployEndpoint(reposilite.getContextFactory(), reposilite.getDeployService());

LookupController lookupController = new LookupController(
configuration.proxied.size() > 0,
Expand All @@ -57,7 +58,7 @@ void start(Configuration configuration, Runnable onStart) {
reposilite.getProxyService(),
reposilite.getFailureService());

LookupApiController lookupApiController = new LookupApiController(
LookupApiEndpoint lookupApiEndpoint = new LookupApiEndpoint(
configuration.rewritePathsEnabled,
reposilite.getContextFactory(),
reposilite.getRepositoryAuthenticator(),
Expand All @@ -73,14 +74,15 @@ void start(Configuration configuration, Runnable onStart) {
this.javalin = create(configuration)
.before(ctx -> reposilite.getStatsService().record(ctx.req.getRequestURI()))
.get("/js/app.js", new FrontendController(reposilite))
.get("/api/auth", new AuthController(reposilite.getAuthService()))
.get("/api/auth", new AuthEndpoint(reposilite.getAuthService()))
.post("/api/execute", new RemoteExecutionEndpoint(reposilite.getAuthenticator(), reposilite.getContextFactory(), reposilite.getConsole()))
.ws("/api/cli", cliController)
.get("/api", lookupApiController)
.get("/api/*", lookupApiController)
.get("/api", lookupApiEndpoint)
.get("/api/*", lookupApiEndpoint)
.get("/*", lookupController)
.head("/*", lookupController)
.put("/*", deployController)
.post("/*", deployController)
.put("/*", deployEndpoint)
.post("/*", deployEndpoint)
.after("/*", new PostAuthHandler())
.exception(Exception.class, (exception, ctx) -> failureService.throwException(ctx.req.getRequestURI(), exception));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,26 +21,26 @@

final class AuthDto implements Serializable {

private final boolean manager;
private final String path;
private final String permissions;
private final List<String> repositories;

AuthDto(boolean manager, String path, List<String> repositories) {
this.manager = manager;
AuthDto(String path, String permissions, List<String> repositories) {
this.path = path;
this.permissions = permissions;
this.repositories = repositories;
}

public List<String> getRepositories() {
return repositories;
}

public String getPath() {
return path;
public String getPermissions() {
return permissions;
}

public boolean isManager() {
return manager;
public String getPath() {
return path;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@
import org.panda_lang.reposilite.RepositoryController;
import org.panda_lang.reposilite.error.ResponseUtils;

public final class AuthController implements RepositoryController {
public final class AuthEndpoint implements RepositoryController {

private final AuthService authService;

public AuthController(AuthService authService) {
public AuthEndpoint(AuthService authService) {
this.authService = authService;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ public AuthService(Authenticator authenticator) {
Result<AuthDto, ErrorDto> authByHeader(Map<String, String> headers) {
return authenticator
.authByHeader(headers)
.map(session -> new AuthDto(session.isManager(), session.getToken().getPath(), session.getRepositoryNames()))
.map(session -> new AuthDto(session.getToken().getPath(), session.getToken().getPermissions(), session.getRepositoryNames()))
.mapError(error -> new ErrorDto(HttpStatus.SC_UNAUTHORIZED, error));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

package org.panda_lang.reposilite.auth;

import org.panda_lang.reposilite.Reposilite;
import org.panda_lang.reposilite.console.ReposiliteCommand;
import picocli.CommandLine.Command;

Expand All @@ -33,10 +32,10 @@ public TokensCommand(TokenService tokenService) {

@Override
public boolean execute(List<String> response) {
Reposilite.getLogger().info("Tokens (" + tokenService.count() + ")");
response.add("Tokens (" + tokenService.count() + ")");

for (Token token : tokenService.getTokens()) {
Reposilite.getLogger().info(token.getPath() + " as " + token.getAlias() + " with '" + token.getPermissions() + "' permissions");
response.add(token.getPath() + " as " + token.getAlias() + " with '" + token.getPermissions() + "' permissions");
}

return true;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,51 +99,6 @@ public Result<List<String>, List<String>> execute(String command) {
response.add(missingParameterException.getCommandLine().getUsageMessage());
return Result.error(response);
}
/*
switch (command.toLowerCase()) {
case "help":
case "?":
return new HelpCommand().execute(reposilite);
case "version":
return new VersionCommand().execute(reposilite);
case "status":
return new StatusCommand().execute(reposilite);
case "purge":
return new PurgeCommand().execute(reposilite);
case "tokens":
return new TokenListCommand().execute(reposilite);
case "gc":
Reposilite.getLogger().info("[Utility Command] Called gc");
System.gc();
return true;
case "stop":
reposilite.schedule(reposilite::forceShutdown);
return true;
default:
break;
}
String[] elements = command.split(" ");
command = elements[0];
switch (command.toLowerCase()) {
case "stats":
if (elements.length == 1) {
return new StatsCommand(-1).execute(reposilite);
}
return Option.attempt(NumberFormatException.class, () -> new StatsCommand(Long.parseLong(elements[1])))
.orElseGet(new StatsCommand(elements[1]))
.execute(reposilite);
case "keygen":
return new KeygenCommand(elements[1], elements[2], ArrayUtils.get(elements, 3).orElseGet("w")).execute(reposilite);
case "revoke":
return new RevokeCommand(elements[1]).execute(reposilite);
default:
Reposilite.getLogger().warn("Unknown command " + command);
return false;
}
*/
}

public void stop() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2020 Dzikoysk
*
* Licensed 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
*
* http://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 org.panda_lang.reposilite.console;

import com.google.api.client.util.Key;

import java.util.List;

public final class RemoteExecutionDto {

@Key
private boolean succeeded;
@Key
private List<String> response;

public RemoteExecutionDto(boolean succeeded, List<String> response) {
this.succeeded = succeeded;
this.response = response;
}

public RemoteExecutionDto() {
// Jackson
}

public boolean isSucceeded() {
return succeeded;
}

public List<String> getResponse() {
return response;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright (c) 2020 Dzikoysk
*
* Licensed 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
*
* http://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 org.panda_lang.reposilite.console;

import io.javalin.http.Context;
import org.apache.http.HttpStatus;
import org.panda_lang.reposilite.Reposilite;
import org.panda_lang.reposilite.ReposiliteContext;
import org.panda_lang.reposilite.ReposiliteContextFactory;
import org.panda_lang.reposilite.RepositoryController;
import org.panda_lang.reposilite.auth.Authenticator;
import org.panda_lang.reposilite.auth.Session;
import org.panda_lang.reposilite.error.ResponseUtils;
import org.panda_lang.reposilite.utils.Result;
import org.panda_lang.utilities.commons.StringUtils;

import java.util.List;

public final class RemoteExecutionEndpoint implements RepositoryController {

private static final int MAX_COMMAND_LENGTH = 1024;

private final Authenticator authenticator;
private final ReposiliteContextFactory contextFactory;
private final Console console;

public RemoteExecutionEndpoint(Authenticator authenticator, ReposiliteContextFactory contextFactory, Console console) {
this.authenticator = authenticator;
this.contextFactory = contextFactory;
this.console = console;
}

@Override
public Context handleContext(Context ctx) {
ReposiliteContext context = contextFactory.create(ctx);
Reposilite.getLogger().info("REMOTE EXECUTION " + context.uri() + " from " + context.address());

Result<Session, String> authResult = authenticator.authByHeader(context.headers());

if (authResult.containsError()) {
return ResponseUtils.errorResponse(ctx, HttpStatus.SC_UNAUTHORIZED, authResult.getError());
}

Session session = authResult.getValue();

if (!session.isManager()) {
return ResponseUtils.errorResponse(ctx, HttpStatus.SC_UNAUTHORIZED, "Authenticated user is not a manger");
}

String command = ctx.body();

if (StringUtils.isEmpty(command)) {
return ResponseUtils.errorResponse(ctx, HttpStatus.SC_BAD_REQUEST, "Missing command");
}

if (command.length() > MAX_COMMAND_LENGTH) {
return ResponseUtils.errorResponse(ctx, HttpStatus.SC_BAD_REQUEST, "The given command exceeds allowed length (" + command.length() + " > " + MAX_COMMAND_LENGTH + ")");
}

Reposilite.getLogger().info(session.getAlias() + " (" + context.address() + ") requested command: " + command);
Result<List<String>, List<String>> result = console.execute(command);

return ctx.json(new RemoteExecutionDto(result.isDefined(), result.isDefined() ? result.getValue() : result.getError()));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -23,15 +23,15 @@ public final class ResponseUtils {

private ResponseUtils() { }

public static <T> Result<T, ErrorDto> error(int status, String messge) {
return Result.error(new ErrorDto(status, messge));
}

public static Context response(Context ctx, Result<?, ErrorDto> response) {
response.peek(ctx::json).onError(error -> errorResponse(ctx, error));
return ctx;
}

public static <T> Result<T, ErrorDto> error(int status, String messge) {
return Result.error(new ErrorDto(status, messge));
}

public static Context errorResponse(Context context, int status, String message) {
return errorResponse(context, new ErrorDto(status, message));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@
import org.panda_lang.reposilite.ReposiliteContextFactory;
import org.panda_lang.reposilite.error.ResponseUtils;

public final class DeployController implements Handler {
public final class DeployEndpoint implements Handler {

private final ReposiliteContextFactory contextFactory;
private final DeployService deployService;

public DeployController(ReposiliteContextFactory contextFactory, DeployService deployService) {
public DeployEndpoint(ReposiliteContextFactory contextFactory, DeployService deployService) {
this.contextFactory = contextFactory;
this.deployService = deployService;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@
import java.io.File;
import java.util.Optional;

public final class LookupApiController implements RepositoryController {
public final class LookupApiEndpoint implements RepositoryController {

private final boolean rewritePathsEnabled;
private final ReposiliteContextFactory contextFactory;
private final RepositoryAuthenticator repositoryAuthenticator;
private final RepositoryService repositoryService;
private final LookupService lookupService;

public LookupApiController(
public LookupApiEndpoint(
boolean rewritePathsEnabled,
ReposiliteContextFactory contextFactory,
RepositoryAuthenticator repositoryAuthenticator,
Expand Down
2 changes: 1 addition & 1 deletion reposilite-backend/src/main/resources/static/js/app.js

Large diffs are not rendered by default.

Loading

0 comments on commit 91474f6

Please sign in to comment.