From 2b42bc06240e3164f964d14a5ba5bc6df45b9bb3 Mon Sep 17 00:00:00 2001 From: Pim Otte Date: Mon, 4 Aug 2014 12:08:05 +0200 Subject: [PATCH] Authentication with CLI to daemon --- .../org/syncany/cli/CommandLineClient.java | 39 +++++++++++++------ .../org/syncany/config/to/DaemonConfigTO.java | 11 ++++++ .../operations/daemon/DaemonOperation.java | 14 +++++++ .../operations/daemon/WatchRunner.java | 17 +++----- .../operations/daemon/WatchServer.java | 8 ++-- .../syncany/operations/daemon/WebServer.java | 17 ++++++-- 6 files changed, 77 insertions(+), 29 deletions(-) diff --git a/syncany-cli/src/main/java/org/syncany/cli/CommandLineClient.java b/syncany-cli/src/main/java/org/syncany/cli/CommandLineClient.java index 3368a3ea0..142e8a0ea 100644 --- a/syncany-cli/src/main/java/org/syncany/cli/CommandLineClient.java +++ b/syncany-cli/src/main/java/org/syncany/cli/CommandLineClient.java @@ -19,9 +19,7 @@ import static java.util.Arrays.asList; -import java.io.BufferedReader; import java.io.File; -import java.io.FileReader; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -44,25 +42,29 @@ import joptsimple.OptionSet; import joptsimple.OptionSpec; -import org.apache.commons.io.FilenameUtils; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; +import org.apache.http.auth.AuthScope; +import org.apache.http.auth.UsernamePasswordCredentials; +import org.apache.http.client.CredentialsProvider; import org.apache.http.client.methods.HttpPost; import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; +import org.simpleframework.xml.core.Persister; import org.syncany.Client; import org.syncany.config.Config; import org.syncany.config.ConfigException; import org.syncany.config.ConfigHelper; import org.syncany.config.LogFormatter; import org.syncany.config.Logging; +import org.syncany.config.to.PortTO; import org.syncany.operations.daemon.messages.CliRequest; import org.syncany.operations.daemon.messages.CliResponse; import org.syncany.operations.daemon.messages.MessageFactory; import org.syncany.operations.daemon.messages.Response; import org.syncany.util.EnvironmentUtil; -import org.syncany.util.FileUtil; /** * The command line client implements a typical CLI. It represents the first entry @@ -76,7 +78,7 @@ public class CommandLineClient extends Client { private static final String SERVER_PROTOCOL = "http://"; private static final String SERVER_HOSTNAME = "localhost"; - private static int SERVER_PORT = 8080; + private static PortTO portTO; private static final String SERVER_REST_API = "/api/rs"; private static final String LOG_FILE_PATTERN = "syncany.log"; @@ -315,12 +317,11 @@ private int runCommand(Command command, String commandName, String[] commandArgs boolean sendToRest = localDirHandledInDaemonScope && needsToRunInInitializedScope; if (sendToRest) { - try (BufferedReader portFileReader = new BufferedReader(new FileReader(portFile))) { - SERVER_PORT = Integer.parseInt(portFileReader.readLine()); + try { + portTO = new Persister().read(PortTO.class, portFile); } catch (Exception e) { - logger.log(Level.SEVERE, "Cannot read REST server port from: " + portFile + ", because: " + e.getMessage()); - SERVER_PORT = 8080; + logger.log(Level.SEVERE, "ERROR: Could not read portFile to connect to daemon.", e); } return sendToRest(command, commandName, commandArgs); } @@ -345,9 +346,25 @@ private int runLocally(Command command, String[] commandArgs) { } private int sendToRest(Command command, String commandName, String[] commandArgs) { - CloseableHttpClient client = HttpClients.createDefault(); + if (portTO == null) { + logger.log(Level.SEVERE, "No port information available"); + return showErrorAndExit("Cannot connect to daemon."); + } + + + // Create authentication details + CredentialsProvider credsProvider = new BasicCredentialsProvider(); + credsProvider.setCredentials( + new AuthScope(SERVER_HOSTNAME, portTO.getPort()), + new UsernamePasswordCredentials(portTO.getUser().getUsername(), portTO.getUser().getPassword())); + + // Create client with authentication details + CloseableHttpClient client = HttpClients.custom() + .setDefaultCredentialsProvider(credsProvider) + .build(); + - String SERVER_URI = SERVER_PROTOCOL + SERVER_HOSTNAME + ":" + SERVER_PORT + SERVER_REST_API; + String SERVER_URI = SERVER_PROTOCOL + SERVER_HOSTNAME + ":" + portTO.getPort() + SERVER_REST_API; HttpPost post = new HttpPost(SERVER_URI); try { diff --git a/syncany-daemon/src/main/java/org/syncany/config/to/DaemonConfigTO.java b/syncany-daemon/src/main/java/org/syncany/config/to/DaemonConfigTO.java index a6fd3ba97..efe855aa3 100644 --- a/syncany-daemon/src/main/java/org/syncany/config/to/DaemonConfigTO.java +++ b/syncany-daemon/src/main/java/org/syncany/config/to/DaemonConfigTO.java @@ -39,6 +39,9 @@ public class DaemonConfigTO { @ElementList(name = "users", entry = "user", required = false) private ArrayList users = new ArrayList(); + // This is not in xml on purpose. It is generated dynamically by the daemon. + private PortTO portTO; + public static DaemonConfigTO load(File file) throws ConfigException { try { return new Persister().read(DaemonConfigTO.class, file); @@ -80,4 +83,12 @@ public WebServerTO getWebServer() { public void setWebServer(WebServerTO webServer) { this.webServer = webServer; } + + public PortTO getPortTO() { + return portTO; + } + + public void setPortTO(PortTO portTO) { + this.portTO = portTO; + } } diff --git a/syncany-daemon/src/main/java/org/syncany/operations/daemon/DaemonOperation.java b/syncany-daemon/src/main/java/org/syncany/operations/daemon/DaemonOperation.java index 4dbaf6d8f..2cd9636fd 100644 --- a/syncany-daemon/src/main/java/org/syncany/operations/daemon/DaemonOperation.java +++ b/syncany-daemon/src/main/java/org/syncany/operations/daemon/DaemonOperation.java @@ -28,6 +28,7 @@ import org.syncany.config.UserConfig; import org.syncany.config.to.DaemonConfigTO; import org.syncany.config.to.FolderTO; +import org.syncany.config.to.PortTO; import org.syncany.config.to.UserTO; import org.syncany.crypto.CipherUtil; import org.syncany.operations.Operation; @@ -77,6 +78,7 @@ public class DaemonOperation extends Operation { private ControlServer controlServer; private LocalEventBus eventBus; private DaemonConfigTO daemonConfig; + private PortTO portTO; public DaemonOperation(Config config) { super(config); @@ -160,6 +162,18 @@ private void loadOrCreateConfig() { else { daemonConfig = createAndWriteDefaultConfig(daemonConfigFile); } + + if (portTO == null) { + // Add user and password for access from the CLI + String accessToken = CipherUtil.createRandomAlphanumericString(20); + UserTO cliUser = new UserTO(); + cliUser.setUsername("CLI"); + cliUser.setPassword(accessToken); + portTO = new PortTO(); + portTO.setPort(daemonConfig.getWebServer().getPort()); + portTO.setUser(cliUser); + daemonConfig.setPortTO(portTO); + } } catch (Exception e) { logger.log(Level.WARNING, "Cannot (re-)load config. Exception thrown.", e); diff --git a/syncany-daemon/src/main/java/org/syncany/operations/daemon/WatchRunner.java b/syncany-daemon/src/main/java/org/syncany/operations/daemon/WatchRunner.java index dc6707fa2..15e5e6436 100644 --- a/syncany-daemon/src/main/java/org/syncany/operations/daemon/WatchRunner.java +++ b/syncany-daemon/src/main/java/org/syncany/operations/daemon/WatchRunner.java @@ -30,11 +30,13 @@ import java.util.logging.Level; import java.util.logging.Logger; +import org.simpleframework.xml.core.Persister; import org.syncany.Client; import org.syncany.cli.Command; import org.syncany.cli.CommandFactory; import org.syncany.config.Config; import org.syncany.config.ConfigException; +import org.syncany.config.to.PortTO; import org.syncany.database.ChunkEntry.ChunkChecksum; import org.syncany.database.DatabaseVersionHeader; import org.syncany.database.FileContent; @@ -86,7 +88,7 @@ public class WatchRunner implements WatchOperationListener { private Config config; private File portFile; - private int port; + private PortTO portTO; private Thread watchThread; private WatchOperation watchOperation; private WatchOperationResult watchOperationResult; @@ -94,10 +96,10 @@ public class WatchRunner implements WatchOperationListener { private SqlDatabase localDatabase; - public WatchRunner(Config config, WatchOperationOptions watchOperationOptions, int port) throws ConfigException { + public WatchRunner(Config config, WatchOperationOptions watchOperationOptions, PortTO portTO) throws ConfigException { this.config = config; this.portFile = new File(config.getAppDir(), Config.FILE_PORT); - this.port = port; + this.portTO = portTO; this.watchOperation = new WatchOperation(config, watchOperationOptions, this); this.localDatabase = new SqlDatabase(config); @@ -116,14 +118,7 @@ public void run() { // Write port to portFile portFile.createNewFile(); - try (FileWriter portFileWriter = new FileWriter(portFile)) { - String portStr = Integer.toString(port); - - logger.log(Level.INFO, "Writing Port file (for Port " + portStr + ") to " + portFile + " ..."); - - portFileWriter.write(portStr); - portFileWriter.close(); - } + new Persister().write(portTO, portFile); portFile.deleteOnExit(); // Start operation diff --git a/syncany-daemon/src/main/java/org/syncany/operations/daemon/WatchServer.java b/syncany-daemon/src/main/java/org/syncany/operations/daemon/WatchServer.java index 9a4470c2e..56e438f84 100644 --- a/syncany-daemon/src/main/java/org/syncany/operations/daemon/WatchServer.java +++ b/syncany-daemon/src/main/java/org/syncany/operations/daemon/WatchServer.java @@ -21,7 +21,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; -import java.util.Set; import java.util.TreeMap; import java.util.logging.Level; import java.util.logging.Logger; @@ -31,6 +30,7 @@ import org.syncany.config.ConfigHelper; import org.syncany.config.to.DaemonConfigTO; import org.syncany.config.to.FolderTO; +import org.syncany.config.to.PortTO; import org.syncany.operations.daemon.messages.BadRequestResponse; import org.syncany.operations.daemon.messages.ListWatchesRequest; import org.syncany.operations.daemon.messages.ListWatchesResponse; @@ -55,7 +55,7 @@ public class WatchServer { private Map watchOperations; private LocalEventBus eventBus; - private int port; + private PortTO portTO; public WatchServer() { this.watchOperations = new TreeMap(); @@ -72,7 +72,7 @@ public void reload(DaemonConfigTO daemonConfigTO) { logger.log(Level.INFO, "Starting/reloading watch server ... "); // Update port number - port = daemonConfigTO.getWebServer().getPort(); + portTO = daemonConfigTO.getPortTO(); // Restart threads try { @@ -114,7 +114,7 @@ private void startWatchOperations(Map newWatchedFolderTOs) throw if (watchConfig != null) { logger.log(Level.INFO, "- Starting watch operation at " + localDir + " ..."); - WatchRunner watchOperationThread = new WatchRunner(watchConfig, watchOperationOptions, port); + WatchRunner watchOperationThread = new WatchRunner(watchConfig, watchOperationOptions, portTO); watchOperationThread.start(); watchOperations.put(localDir, watchOperationThread); diff --git a/syncany-daemon/src/main/java/org/syncany/operations/daemon/WebServer.java b/syncany-daemon/src/main/java/org/syncany/operations/daemon/WebServer.java index 06dc3c7f5..b91dbb9f7 100644 --- a/syncany-daemon/src/main/java/org/syncany/operations/daemon/WebServer.java +++ b/syncany-daemon/src/main/java/org/syncany/operations/daemon/WebServer.java @@ -44,6 +44,7 @@ import org.syncany.config.to.DaemonConfigTO; import org.syncany.config.to.UserTO; +import org.syncany.crypto.CipherUtil; import org.syncany.operations.daemon.auth.MapIdentityManager; import org.syncany.operations.daemon.handlers.InternalRestHandler; import org.syncany.operations.daemon.handlers.InternalWebInterfaceHandler; @@ -83,7 +84,7 @@ public WebServer(DaemonConfigTO daemonConfig) { initCaches(); initEventBus(); - initServer(daemonConfig.getWebServer().getHost(), daemonConfig.getWebServer().getPort(), daemonConfig.getUsers()); + initServer(daemonConfig); } public void start() throws ServiceAlreadyStartedException { @@ -116,12 +117,22 @@ private void initEventBus() { eventBus.register(this); } - private void initServer(String host, int port, List users) { + private void initServer(DaemonConfigTO daemonConfigTO) { + String host = daemonConfigTO.getWebServer().getHost(); + int port = daemonConfigTO.getWebServer().getPort(); + List users = daemonConfigTO.getUsers(); + if (users == null) { users = new ArrayList(); - logger.log(Level.WARNING, "Webserver is starting without any users. No access possible."); } + if (daemonConfigTO.getPortTO() != null) { + // Add CLI credentials + users.add(daemonConfigTO.getPortTO().getUser()); + } + + + IdentityManager identityManager = new MapIdentityManager(users); HttpHandler pathHttpHandler = path()