Add support for Basic Access Authentication aka BasicAuth

This commit is contained in:
Rohit Awate 2018-08-30 19:34:07 +05:30
parent 2a8046a97e
commit 0de2f257ca
No known key found for this signature in database
GPG key ID: 1051D7B79CF2EE25
16 changed files with 268 additions and 11 deletions

View file

@ -114,5 +114,11 @@
<artifactId>richtextfx</artifactId>
<version>0.9.0</version>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>RELEASE</version>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View file

@ -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();
}

View file

@ -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;
}
}

View file

@ -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<StringKeyValueFieldController> 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() {

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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<String, String> headers;
public void setTarget(String target) throws MalformedURLException {
@ -40,4 +43,12 @@ public abstract class EverestRequest implements Serializable {
public HashMap<String, String> getHeaders() {
return this.headers;
}
public AuthProvider getAuthProvider() {
return authProvider;
}
public void setAuthProvider(AuthProvider authProvider) {
this.authProvider = authProvider;
}
}

View file

@ -132,6 +132,10 @@ public class RequestManager extends Service<EverestResponse> {
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());
}
}
/**

View file

@ -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<FieldState> 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;

View file

@ -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");

View file

@ -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;

View file

@ -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;

View file

@ -23,14 +23,14 @@
<?import javafx.scene.image.Image?>
<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.*?>
<HBox fx:id="headerField" alignment="CENTER" spacing="10.0" stylesheets="@../../css/Adreana.css"
<HBox fx:id="headerField" alignment="CENTER" spacing="10.0" styleClass="KVField" stylesheets="@../../css/Adreana.css"
xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.rohitawate.everest.controllers.StringKeyValueFieldController">
<children>
<JFXCheckBox fx:id="checkBox" checkedColor="ORANGERED" unCheckedColor="#7a7a7a" HBox.hgrow="ALWAYS"/>
<TextField fx:id="keyField" prefWidth="400.0" promptText="KEY" HBox.hgrow="ALWAYS"/>
<TextField fx:id="keyField" prefWidth="400.0" promptText="KEY" styleClass="KVField" HBox.hgrow="ALWAYS"/>
<TextField fx:id="valueField" layoutX="10.0" layoutY="10.0" prefWidth="400.0" promptText="VALUE"
HBox.hgrow="ALWAYS"/>
styleClass="KVField" HBox.hgrow="ALWAYS"/>
<JFXButton fx:id="deleteButton" ripplerFill="WHITE" HBox.hgrow="ALWAYS">
<graphic>
<ImageView fitHeight="15.0" fitWidth="15.0" opacity="0.25" pickOnBounds="true" preserveRatio="true">

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import javafx.scene.control.Tab?>
<?import javafx.scene.control.TabPane?>
<TabPane fx:id="authTabPane" stylesheets="@../../../css/Adreana.css" tabClosingPolicy="UNAVAILABLE" tabMinHeight="25.0"
tabMinWidth="100.0" xmlns="http://javafx.com/javafx/8.0.141" xmlns:fx="http://javafx.com/fxml/1"
fx:controller="com.rohitawate.everest.controllers.auth.AuthTabController">
<tabs>
<Tab fx:id="basicTab" text="BASIC"/>
<Tab fx:id="digestTab" text="DIGEST"/>
</tabs>
</TabPane>

View file

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<?import com.jfoenix.controls.JFXCheckBox?>
<?import javafx.geometry.Insets?>
<?import javafx.scene.control.PasswordField?>
<?import javafx.scene.control.TextField?>
<?import javafx.scene.layout.VBox?>
<VBox alignment="CENTER" spacing="10.0" stylesheets="@../../../css/Adreana.css" xmlns="http://javafx.com/javafx/8.0.141"
xmlns:fx="http://javafx.com/fxml/1" fx:controller="com.rohitawate.everest.controllers.auth.BasicAuthController">
<children>
<TextField fx:id="usernameField" maxWidth="400.0" minWidth="200.0" prefWidth="400.0" promptText="USERNAME"
styleClass="KVField" VBox.vgrow="ALWAYS"/>
<PasswordField fx:id="passwordField" maxWidth="400.0" minWidth="200.0" prefWidth="400.0" promptText="PASSWORD"
styleClass="KVField" VBox.vgrow="ALWAYS"/>
<JFXCheckBox fx:id="checkBox" checkedColor="ORANGERED" text="ENABLE" textFill="#b9b9b9" unCheckedColor="#7a7a7a"
VBox.vgrow="ALWAYS"/>
</children>
<padding>
<Insets bottom="5.0" left="5.0" right="5.0" top="5.0"/>
</padding>
</VBox>

View file

@ -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());
}
}