From 0de2f257cad168801631060ea26587faed3b6e65 Mon Sep 17 00:00:00 2001 From: Rohit Awate Date: Thu, 30 Aug 2018 19:34:07 +0530 Subject: [PATCH] Add support for Basic Access Authentication aka BasicAuth --- pom.xml | 6 ++ .../rohitawate/everest/auth/AuthProvider.java | 13 +++++ .../everest/auth/BasicAuthProvider.java | 28 ++++++++++ .../controllers/DashboardController.java | 27 +++++++-- .../controllers/auth/AuthTabController.java | 56 +++++++++++++++++++ .../controllers/auth/BasicAuthController.java | 30 ++++++++++ .../models/requests/EverestRequest.java | 11 ++++ .../requestmanager/RequestManager.java | 4 ++ .../everest/state/ComposerState.java | 7 +++ .../everest/sync/SQLiteManager.java | 36 +++++++++++- .../rohitawate/everest/sync/SyncManager.java | 1 + src/main/resources/css/Adreana.css | 6 +- .../fxml/homewindow/StringKeyValueField.fxml | 6 +- .../fxml/homewindow/auth/AuthTab.fxml | 12 ++++ .../fxml/homewindow/auth/BasicAuth.fxml | 22 ++++++++ .../everest/auth/BasicAuthProviderTest.java | 14 +++++ 16 files changed, 268 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/rohitawate/everest/auth/AuthProvider.java create mode 100644 src/main/java/com/rohitawate/everest/auth/BasicAuthProvider.java create mode 100644 src/main/java/com/rohitawate/everest/controllers/auth/AuthTabController.java create mode 100644 src/main/java/com/rohitawate/everest/controllers/auth/BasicAuthController.java create mode 100644 src/main/resources/fxml/homewindow/auth/AuthTab.fxml create mode 100644 src/main/resources/fxml/homewindow/auth/BasicAuth.fxml create mode 100644 test/com/rohitawate/everest/auth/BasicAuthProviderTest.java diff --git a/pom.xml b/pom.xml index adf7c3c..a1dc455 100644 --- a/pom.xml +++ b/pom.xml @@ -114,5 +114,11 @@ richtextfx 0.9.0 + + org.junit.jupiter + junit-jupiter-api + RELEASE + test + \ No newline at end of file diff --git a/src/main/java/com/rohitawate/everest/auth/AuthProvider.java b/src/main/java/com/rohitawate/everest/auth/AuthProvider.java new file mode 100644 index 0000000..8dcae22 --- /dev/null +++ b/src/main/java/com/rohitawate/everest/auth/AuthProvider.java @@ -0,0 +1,13 @@ +package com.rohitawate.everest.auth; + +public interface AuthProvider { + /** + * Returns true or false indicating whether or not the user has enabled authentication. + */ + boolean isEnabled(); + + /** + * Returns the 'Authorization' header to be attached to an API call. + */ + String getAuthHeader(); +} diff --git a/src/main/java/com/rohitawate/everest/auth/BasicAuthProvider.java b/src/main/java/com/rohitawate/everest/auth/BasicAuthProvider.java new file mode 100644 index 0000000..fc664b5 --- /dev/null +++ b/src/main/java/com/rohitawate/everest/auth/BasicAuthProvider.java @@ -0,0 +1,28 @@ +package com.rohitawate.everest.auth; + +import java.util.Base64; +import java.util.Base64.Encoder; + +public class BasicAuthProvider implements AuthProvider { + private static Encoder encoder = Base64.getEncoder(); + + private String username; + private String password; + private boolean enabled; + + public BasicAuthProvider(String username, String password, boolean enabled) { + this.username = username; + this.password = password; + this.enabled = enabled; + } + + @Override + public String getAuthHeader() { + return "Basic " + encoder.encodeToString((username + ":" + password).getBytes()); + } + + @Override + public boolean isEnabled() { + return enabled; + } +} diff --git a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java index 05e7c4a..13fe508 100644 --- a/src/main/java/com/rohitawate/everest/controllers/DashboardController.java +++ b/src/main/java/com/rohitawate/everest/controllers/DashboardController.java @@ -18,6 +18,7 @@ package com.rohitawate.everest.controllers; import com.jfoenix.controls.JFXButton; import com.jfoenix.controls.JFXProgressBar; import com.jfoenix.controls.JFXSnackbar; +import com.rohitawate.everest.controllers.auth.AuthTabController; import com.rohitawate.everest.controllers.codearea.EverestCodeArea; import com.rohitawate.everest.controllers.codearea.highlighters.HighlighterFactory; import com.rohitawate.everest.controllers.visualizers.TreeVisualizer; @@ -94,6 +95,7 @@ public class DashboardController implements Initializable { private JFXSnackbar snackbar; private List paramsControllers; private RequestManager requestManager; + private AuthTabController authTabController; private HeaderTabController headerTabController; private BodyTabController bodyTabController; private IntegerProperty paramsCountProperty; @@ -124,19 +126,26 @@ public class DashboardController implements Initializable { @Override public void initialize(URL url, ResourceBundle rb) { try { + // Loading the headers tab + FXMLLoader authTabLoader = new FXMLLoader(getClass().getResource("/fxml/homewindow/auth/AuthTab.fxml")); + Parent authTabFXML = authTabLoader.load(); + ThemeManager.setTheme(authTabFXML); + authTabController = authTabLoader.getController(); + authTab.setContent(authTabFXML); + // Loading the headers tab FXMLLoader headerTabLoader = new FXMLLoader(getClass().getResource("/fxml/homewindow/HeaderTab.fxml")); - Parent headerTabContent = headerTabLoader.load(); - ThemeManager.setTheme(headerTabContent); + Parent headerTabFXML = headerTabLoader.load(); + ThemeManager.setTheme(headerTabFXML); headerTabController = headerTabLoader.getController(); - headersTab.setContent(headerTabContent); + headersTab.setContent(headerTabFXML); // Loading the body tab FXMLLoader bodyTabLoader = new FXMLLoader(getClass().getResource("/fxml/homewindow/BodyTab.fxml")); - Parent bodyTabContent = bodyTabLoader.load(); - ThemeManager.setTheme(bodyTabContent); + Parent bodyTabFXML = bodyTabLoader.load(); + ThemeManager.setTheme(bodyTabFXML); bodyTabController = bodyTabLoader.getController(); - bodyTab.setContent(bodyTabContent); + bodyTab.setContent(bodyTabFXML); } catch (IOException e) { LoggingService.logSevere("Could not load headers/body tabs.", e, LocalDateTime.now()); } @@ -241,6 +250,7 @@ public class DashboardController implements Initializable { getRequest = new GETRequest(); getRequest.setTarget(address); + getRequest.setAuthProvider(authTabController.getAuthProvider()); getRequest.setHeaders(headerTabController.getHeaders()); requestManager = RequestManagersPool.manager(); @@ -254,6 +264,7 @@ public class DashboardController implements Initializable { dataRequest.setRequestType(httpMethodBox.getValue()); dataRequest.setTarget(address); + dataRequest.setAuthProvider(authTabController.getAuthProvider()); dataRequest.setHeaders(headerTabController.getHeaders()); if (bodyTabController.rawTab.isSelected()) { @@ -279,6 +290,7 @@ public class DashboardController implements Initializable { deleteRequest = new DELETERequest(); deleteRequest.setTarget(address); + deleteRequest.setAuthProvider(authTabController.getAuthProvider()); deleteRequest.setHeaders(headerTabController.getHeaders()); requestManager = RequestManagersPool.manager(); @@ -663,6 +675,7 @@ public class DashboardController implements Initializable { composerState.httpMethod = httpMethodBox.getValue(); composerState.headers = headerTabController.getFieldStates(); composerState.params = getParamFieldStates(); + authTabController.getState(composerState); dashboardState.composer = composerState; dashboardState.visibleResponseLayer = visibleLayer; @@ -814,6 +827,8 @@ public class DashboardController implements Initializable { if (!(state.composer.httpMethod.equals(HTTPConstants.GET) || state.composer.httpMethod.equals(HTTPConstants.DELETE))) bodyTabController.setState(state.composer); + + authTabController.setState(state.composer); } void reset() { diff --git a/src/main/java/com/rohitawate/everest/controllers/auth/AuthTabController.java b/src/main/java/com/rohitawate/everest/controllers/auth/AuthTabController.java new file mode 100644 index 0000000..2166c3f --- /dev/null +++ b/src/main/java/com/rohitawate/everest/controllers/auth/AuthTabController.java @@ -0,0 +1,56 @@ +package com.rohitawate.everest.controllers.auth; + +import com.rohitawate.everest.auth.AuthProvider; +import com.rohitawate.everest.auth.BasicAuthProvider; +import com.rohitawate.everest.state.ComposerState; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.fxml.Initializable; +import javafx.scene.Parent; +import javafx.scene.control.Tab; +import javafx.scene.control.TabPane; + +import java.io.IOException; +import java.net.URL; +import java.util.ResourceBundle; + +public class AuthTabController implements Initializable { + @FXML + private TabPane authTabPane; + @FXML + private Tab basicTab, digestTab; + + private BasicAuthController basicController; + + @Override + public void initialize(URL location, ResourceBundle resources) { + try { + FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/homewindow/auth/BasicAuth.fxml")); + Parent basicFXML = loader.load(); + basicTab.setContent(basicFXML); + basicController = loader.getController(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public AuthProvider getAuthProvider() { + switch (authTabPane.getSelectionModel().getSelectedIndex()) { + case 0: + return new BasicAuthProvider( + basicController.getUsername(), basicController.getPassword(), basicController.isSelected()); + default: + return null; + } + } + + public void getState(ComposerState state) { + state.basicUsername = basicController.getUsername(); + state.basicPassword = basicController.getPassword(); + state.basicAuthEnabled = basicController.isSelected(); + } + + public void setState(ComposerState state) { + basicController.setState(state.basicUsername, state.basicPassword, state.basicAuthEnabled); + } +} diff --git a/src/main/java/com/rohitawate/everest/controllers/auth/BasicAuthController.java b/src/main/java/com/rohitawate/everest/controllers/auth/BasicAuthController.java new file mode 100644 index 0000000..ca84914 --- /dev/null +++ b/src/main/java/com/rohitawate/everest/controllers/auth/BasicAuthController.java @@ -0,0 +1,30 @@ +package com.rohitawate.everest.controllers.auth; + +import com.jfoenix.controls.JFXCheckBox; +import javafx.fxml.FXML; +import javafx.scene.control.TextField; + +public class BasicAuthController { + @FXML + private TextField usernameField, passwordField; + @FXML + private JFXCheckBox checkBox; + + boolean isSelected() { + return checkBox.isSelected(); + } + + String getUsername() { + return usernameField.getText(); + } + + String getPassword() { + return passwordField.getText(); + } + + void setState(String username, String password, boolean enabled) { + usernameField.setText(username); + passwordField.setText(password); + checkBox.setSelected(enabled); + } +} diff --git a/src/main/java/com/rohitawate/everest/models/requests/EverestRequest.java b/src/main/java/com/rohitawate/everest/models/requests/EverestRequest.java index 166397e..9ba4d04 100644 --- a/src/main/java/com/rohitawate/everest/models/requests/EverestRequest.java +++ b/src/main/java/com/rohitawate/everest/models/requests/EverestRequest.java @@ -16,6 +16,8 @@ package com.rohitawate.everest.models.requests; +import com.rohitawate.everest.auth.AuthProvider; + import java.io.Serializable; import java.net.MalformedURLException; import java.net.URL; @@ -23,6 +25,7 @@ import java.util.HashMap; public abstract class EverestRequest implements Serializable { private URL target; + private AuthProvider authProvider; private HashMap headers; public void setTarget(String target) throws MalformedURLException { @@ -40,4 +43,12 @@ public abstract class EverestRequest implements Serializable { public HashMap getHeaders() { return this.headers; } + + public AuthProvider getAuthProvider() { + return authProvider; + } + + public void setAuthProvider(AuthProvider authProvider) { + this.authProvider = authProvider; + } } diff --git a/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java b/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java index 0ba1d12..e857c12 100644 --- a/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java +++ b/src/main/java/com/rohitawate/everest/requestmanager/RequestManager.java @@ -132,6 +132,10 @@ public class RequestManager extends Service { private void appendHeaders() { request.getHeaders().forEach((key, value) -> requestBuilder.header(key, value)); requestBuilder.header("User-Agent", "Everest"); + + if (request.getAuthProvider() != null && request.getAuthProvider().isEnabled()) { + requestBuilder.header("Authorization", request.getAuthProvider().getAuthHeader()); + } } /** diff --git a/src/main/java/com/rohitawate/everest/state/ComposerState.java b/src/main/java/com/rohitawate/everest/state/ComposerState.java index bb02b74..871c7ed 100644 --- a/src/main/java/com/rohitawate/everest/state/ComposerState.java +++ b/src/main/java/com/rohitawate/everest/state/ComposerState.java @@ -35,6 +35,10 @@ public class ComposerState { public String rawBody; public String rawBodyBoxValue; + public String basicUsername; + public String basicPassword; + public boolean basicAuthEnabled; + // Tuples of URL-encoded requests public List urlStringTuples; @@ -59,6 +63,9 @@ public class ComposerState { if (!httpMethod.equals(state.httpMethod)) return false; if (!params.equals(state.params)) return false; if (!headers.equals(state.headers)) return false; + if (!basicUsername.equals(state.basicUsername)) return false; + if (!basicPassword.equals(state.basicPassword)) return false; + if (basicAuthEnabled != state.basicAuthEnabled) return false; if (state.httpMethod.equals(HTTPConstants.GET) || state.httpMethod.equals(HTTPConstants.DELETE)) return true; diff --git a/src/main/java/com/rohitawate/everest/sync/SQLiteManager.java b/src/main/java/com/rohitawate/everest/sync/SQLiteManager.java index 0aa820d..af764cc 100644 --- a/src/main/java/com/rohitawate/everest/sync/SQLiteManager.java +++ b/src/main/java/com/rohitawate/everest/sync/SQLiteManager.java @@ -40,7 +40,8 @@ class SQLiteManager implements DataManager { "CREATE TABLE IF NOT EXISTS RequestContentMap(RequestID INTEGER, ContentType TEXT NOT NULL, FOREIGN KEY(RequestID) REFERENCES Requests(ID))", "CREATE TABLE IF NOT EXISTS Bodies(RequestID INTEGER, Type TEXT NOT NULL CHECK(Type IN ('application/json', 'application/xml', 'text/html', 'text/plain')), Body TEXT NOT NULL, FOREIGN KEY(RequestID) REFERENCES Requests(ID))", "CREATE TABLE IF NOT EXISTS FilePaths(RequestID INTEGER, Path TEXT NOT NULL, FOREIGN KEY(RequestID) REFERENCES Requests(ID))", - "CREATE TABLE IF NOT EXISTS Tuples(RequestID INTEGER, Type TEXT NOT NULL CHECK(Type IN ('Header', 'Param', 'URLString', 'FormString', 'File')), Key TEXT NOT NULL, Value TEXT NOT NULL, Checked INTEGER CHECK (Checked IN (0, 1)), FOREIGN KEY(RequestID) REFERENCES Requests(ID))" + "CREATE TABLE IF NOT EXISTS Tuples(RequestID INTEGER, Type TEXT NOT NULL CHECK(Type IN ('Header', 'Param', 'URLString', 'FormString', 'File')), Key TEXT NOT NULL, Value TEXT NOT NULL, Checked INTEGER CHECK (Checked IN (0, 1)), FOREIGN KEY(RequestID) REFERENCES Requests(ID))", + "CREATE TABLE IF NOT EXISTS BasicAuthCredentials(RequestID INTEGER, Username TEXT NOT NULL, Password TEXT NOT NULL, Enabled INTEGER CHECK (Enabled IN (1, 0)), FOREIGN KEY(RequestID) REFERENCES Requests(ID))" }; private static final String saveRequest = "INSERT INTO Requests(Type, Target, Date) VALUES(?, ?, ?)"; @@ -48,10 +49,12 @@ class SQLiteManager implements DataManager { private static final String saveBody = "INSERT INTO Bodies(RequestID, Body, Type) VALUES(?, ?, ?)"; private static final String saveFilePath = "INSERT INTO FilePaths(RequestID, Path) VALUES(?, ?)"; private static final String saveTuple = "INSERT INTO Tuples(RequestID, Type, Key, Value, Checked) VALUES(?, ?, ?, ?, ?)"; + private static final String saveBasicAuthCredentials = "INSERT INTO BasicAuthCredentials(RequestID, Username, Password, Enabled) VALUES(?, ?, ?, ?)"; private static final String selectRecentRequests = "SELECT * FROM Requests WHERE Requests.Date > ?"; private static final String selectRequestContentType = "SELECT ContentType FROM RequestContentMap WHERE RequestID == ?"; private static final String selectRequestBody = "SELECT Body, Type FROM Bodies WHERE RequestID == ?"; private static final String selectFilePath = "SELECT Path FROM FilePaths WHERE RequestID == ?"; + private static final String selectBasicAuthCredentials = "SELECT * FROM BasicAuthCredentials WHERE RequestID == ?"; private static final String selectTuplesByType = "SELECT * FROM Tuples WHERE RequestID == ? AND Type == ?"; private static final String selectMostRecentRequest = "SELECT * FROM Requests ORDER BY ID DESC LIMIT 1"; } @@ -109,6 +112,8 @@ class SQLiteManager implements DataManager { saveTuple(newState.headers, "Header", requestID); saveTuple(newState.params, "Param", requestID); + saveBasicAuthCredentials(requestID, newState.basicUsername, newState.basicPassword, newState.basicAuthEnabled); + if (!(newState.httpMethod.equals(HTTPConstants.GET) || newState.httpMethod.equals(HTTPConstants.DELETE))) { // Maps the request to its ContentType for faster retrieval statement = conn.prepareStatement(Queries.saveRequestContentPair); @@ -133,6 +138,19 @@ class SQLiteManager implements DataManager { } } + private void saveBasicAuthCredentials(int requestID, String username, String password, boolean enabled) throws SQLException { + if (username == null || password == null) + return; + + statement = conn.prepareStatement(Queries.saveBasicAuthCredentials); + statement.setInt(1, requestID); + statement.setString(2, username); + statement.setString(3, password); + statement.setInt(4, enabled ? 1 : 0); + + statement.executeUpdate(); + } + /** * Returns a list of all the recent requests. */ @@ -156,6 +174,7 @@ class SQLiteManager implements DataManager { state.headers = getTuples(requestID, "Header"); state.params = getTuples(requestID, "Param"); state.httpMethod = resultSet.getString("Type"); + getBasicAuthCredentials(state, requestID); if (!(state.httpMethod.equals(HTTPConstants.GET) || state.httpMethod.equals(HTTPConstants.DELETE))) { // Retrieves request body ContentType for querying corresponding table @@ -181,6 +200,19 @@ class SQLiteManager implements DataManager { return history; } + private void getBasicAuthCredentials(ComposerState state, int requestID) throws SQLException { + statement = conn.prepareStatement(Queries.selectBasicAuthCredentials); + statement.setInt(1, requestID); + + ResultSet RS = statement.executeQuery(); + + if (RS.next()) { + state.basicUsername = RS.getString("Username"); + state.basicPassword = RS.getString("Password"); + state.basicAuthEnabled = RS.getInt("Enabled") == 1; + } + } + private String getRequestContentType(int requestID) throws SQLException { String contentType = null; @@ -239,6 +271,8 @@ class SQLiteManager implements DataManager { lastRequest.httpMethod = RS.getString("Type"); } + getBasicAuthCredentials(lastRequest, requestID); + lastRequest.headers = getTuples(requestID, "Header"); lastRequest.params = getTuples(requestID, "Param"); lastRequest.urlStringTuples = getTuples(requestID, "URLString"); diff --git a/src/main/java/com/rohitawate/everest/sync/SyncManager.java b/src/main/java/com/rohitawate/everest/sync/SyncManager.java index 4ef7871..7819d69 100644 --- a/src/main/java/com/rohitawate/everest/sync/SyncManager.java +++ b/src/main/java/com/rohitawate/everest/sync/SyncManager.java @@ -42,6 +42,7 @@ public class SyncManager { * Asynchronously saves the new state by invoking all the registered DataManagers. */ public void saveState(ComposerState newState) { + // Compares new state with the last added state from the primary fetch source if (newState.equals(managers.get(Settings.fetchSource).getLastAdded())) return; diff --git a/src/main/resources/css/Adreana.css b/src/main/resources/css/Adreana.css index a52c745..91f82ae 100644 --- a/src/main/resources/css/Adreana.css +++ b/src/main/resources/css/Adreana.css @@ -174,6 +174,8 @@ } /* Body tab pane */ +#authTabPane:top .tab-header-area .tab-header-background, +#authTabPane:top .tab-header-area .headers-region .tab:top, #bodyTabPane:top .tab-header-area .tab-header-background, #bodyTabPane:top .tab-header-area .headers-region .tab:top { -fx-background-color: #545454; @@ -184,10 +186,12 @@ -fx-background-color: #3d3d3d; } +#authTabPane:top .tab-header-area .headers-region .tab:hover:top, #bodyTabPane:top .tab-header-area .headers-region .tab:hover:top { -fx-background-color: #4e848f; } +#authTabPane:top .tab-header-area .headers-region .tab:selected:top, #bodyTabPane:top .tab-header-area .headers-region .tab:selected:top { -fx-background-color: #3d3d3d; } @@ -210,7 +214,7 @@ -fx-padding: 0px; } -#keyField, #valueField { +.KVField { -fx-prompt-text-fill: #919191; -fx-background-color: #303030; -fx-text-fill: white; diff --git a/src/main/resources/fxml/homewindow/StringKeyValueField.fxml b/src/main/resources/fxml/homewindow/StringKeyValueField.fxml index f91e7ae..dcc3a5d 100644 --- a/src/main/resources/fxml/homewindow/StringKeyValueField.fxml +++ b/src/main/resources/fxml/homewindow/StringKeyValueField.fxml @@ -23,14 +23,14 @@ - - + + styleClass="KVField" HBox.hgrow="ALWAYS"/> diff --git a/src/main/resources/fxml/homewindow/auth/AuthTab.fxml b/src/main/resources/fxml/homewindow/auth/AuthTab.fxml new file mode 100644 index 0000000..9b6c82d --- /dev/null +++ b/src/main/resources/fxml/homewindow/auth/AuthTab.fxml @@ -0,0 +1,12 @@ + + + + + + + + + + diff --git a/src/main/resources/fxml/homewindow/auth/BasicAuth.fxml b/src/main/resources/fxml/homewindow/auth/BasicAuth.fxml new file mode 100644 index 0000000..db5866f --- /dev/null +++ b/src/main/resources/fxml/homewindow/auth/BasicAuth.fxml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + diff --git a/test/com/rohitawate/everest/auth/BasicAuthProviderTest.java b/test/com/rohitawate/everest/auth/BasicAuthProviderTest.java new file mode 100644 index 0000000..d122cde --- /dev/null +++ b/test/com/rohitawate/everest/auth/BasicAuthProviderTest.java @@ -0,0 +1,14 @@ +package com.rohitawate.everest.auth; + +import org.junit.jupiter.api.Test; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +class BasicAuthProviderTest { + + @Test + void getAuthHeader() { + BasicAuthProvider provider = new BasicAuthProvider("username", "password", true); + assertEquals("dXNlcm5hbWU6cGFzc3dvcmQ=", provider.getAuthHeader()); + } +} \ No newline at end of file