Skip to content

Commit 961c0e9

Browse files
VishalDalwadivogti
andauthored
Authentication (#14)
Co-authored-by: Marco Vogt <[email protected]>
1 parent 907d247 commit 961c0e9

22 files changed

+1020
-36
lines changed

.github/workflows/ci.yml

+4-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ jobs:
1010
matrix:
1111
java: [ 11, 17 ]
1212
os: [ macos-latest, ubuntu-latest, windows-latest ]
13+
withAuth: [ testWithoutAuth, testWithAuth ]
1314
name: Java ${{ matrix.java }} @ ${{ matrix.os }}
1415
steps:
1516
- name: Checkout
@@ -20,4 +21,6 @@ jobs:
2021
distribution: 'adopt'
2122
java-version: ${{ matrix.java }}
2223
- name: Build with Gradle
23-
run: ./gradlew build
24+
run: ./gradlew assemble
25+
- name: Run tests
26+
run: ./gradlew ${{ matrix.withAuth }}

README.md

+53
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,59 @@ In order to start Polypheny-DB, we first need to trigger a build. This can be do
4848

4949
You can now open the Polypheny-UI by opening `localhost:8080` in your browser.
5050

51+
### Authentication
52+
53+
Polypheny Control now allows you to use authentication to allow only verified users to use it. Authentication is available both in the command line and in the browser.
54+
CLI authentication can be enabled/disabled using the `pcrtl.auth.cli` config option. It is disabled by default.
55+
Authentication for `localhost` can be enabled/disabled using the `pcrtl.auth.local` config option. It is also disabled by default.
56+
Authentication itself for be fully enabled/disabled using the `pcrtl.auth.enable` config option. It is enabled by default.
57+
58+
#### The passwd file
59+
60+
Polypheny Control stores user information in a file name **passwd**. This file is NOT protected. You can protect the file against modification by setting the immutable flag using `chattr` on Linux / MacOS and `attrib` on Windows.
61+
But, remember to reset the flag when modifying the file using polypheny control.
62+
63+
#### Admin User
64+
65+
Polypheny Control requires a **admin** user to create and manage other users. So, before you can create any other user you will need to create a **admin** user using the following command:
66+
67+
```
68+
java -jar polypheny-control.jar adduser
69+
```
70+
71+
This will prompt you to enter a name and password. Enter "admin" as the name and a strong password for the user.
72+
All of the user management actions need to be authorized using the admin password.
73+
74+
An example run of the user management features:
75+
76+
```
77+
# Creating the admin user (The password is mandatory but won't be visible when you type it out)
78+
> java -jar polypheny-control.jar adduser
79+
Name: admin
80+
Password:
81+
Confirm Password:
82+
83+
# Creating a user
84+
> java -jar polypheny-control.jar adduser
85+
Name: Loki
86+
Password:
87+
Confirm Password:
88+
Enter 'admin' password (Try 1/3):
89+
90+
# Modifying a user's password
91+
> java -jar polypheny-control.jar moduser
92+
Name: Loki
93+
Password:
94+
Confirm Password:
95+
Enter 'admin' password (Try 1/3):
96+
97+
# Remove a user
98+
> java -jar polypheny-control.jar remuser
99+
Name: Loki
100+
Enter 'admin' password (Try 1/3):
101+
102+
```
103+
51104

52105
## Roadmap
53106
See the [open issues](https://github.com/polypheny/Polypheny-DB/labels/A-control) for a list of proposed features (and known issues).

build.gradle

+22-2
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,8 @@ dependencies {
7474

7575
// Log4J
7676
implementation group: "org.slf4j", name: "slf4j-api", version: slf4j_version // MIT
77-
implementation group: "org.apache.logging.log4j", name: "log4j-slf4j-impl", version: log4j_version // Apache 2.0
77+
implementation group: "org.apache.logging.log4j", name: "log4j-slf4j2-impl", version: log4j_version // Apache 2.0
7878
implementation group: "org.apache.logging.log4j", name: "log4j-core", version: log4j_version // Apache 2.0
79-
implementation group: "org.apache.logging.log4j", name: "log4j-api", version: log4j_version // Apache 2.0
8079

8180
// Configuration management
8281
implementation group: "com.typesafe", name: "config", version: typesafe_config_version // Apache 2.0
@@ -150,6 +149,7 @@ jar {
150149
attributes "Implementation-Version": project.version
151150
attributes "Multi-Release": "true"
152151
attributes "Version": project.version
152+
attributes "Main-Class": mainClassName
153153
attributes "Add-Opens": "java.base/java.lang"
154154
}
155155
}
@@ -188,6 +188,26 @@ task extractWebjars(type: Copy) {
188188
}
189189
classes.dependsOn(extractWebjars)
190190

191+
task testWithAuth(type: Test) {
192+
systemProperty "config.auth.local", "true"
193+
description = "Runs test with local authentication enabled."
194+
group = "verification"
195+
}
196+
testWithAuth.dependsOn(testClasses)
197+
198+
task testWithoutAuth(type: Test) {
199+
systemProperty "config.auth.local", "false"
200+
description = "Runs test with local authentication disabled."
201+
group = "verification"
202+
}
203+
testWithoutAuth.dependsOn(testClasses)
204+
205+
test {
206+
systemProperty "config.auth.local", "false"
207+
}
208+
test.dependsOn(testWithAuth)
209+
210+
191211

192212
//////////////
193213
// IntelliJ //
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
/*
2+
* Copyright 2019-2023 The Polypheny Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.polypheny.control.client;
18+
19+
20+
public class ClientData {
21+
22+
private final ClientType clientType;
23+
private final String username;
24+
private final String password;
25+
26+
27+
public ClientData( ClientType clientType, String username, String password ) {
28+
this.clientType = clientType;
29+
this.username = username;
30+
this.password = password;
31+
}
32+
33+
34+
public ClientType getClientType() {
35+
return clientType;
36+
}
37+
38+
39+
public String getUsername() {
40+
return username;
41+
}
42+
43+
44+
public String getPassword() {
45+
return password;
46+
}
47+
48+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
/*
2+
* Copyright 2019-2023 The Polypheny Project
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.polypheny.control.client;
18+
19+
import kong.unirest.Cookie;
20+
import kong.unirest.GetRequest;
21+
import kong.unirest.HttpRequest;
22+
import kong.unirest.HttpRequestWithBody;
23+
import kong.unirest.HttpResponse;
24+
import kong.unirest.MultipartBody;
25+
import kong.unirest.Unirest;
26+
27+
28+
interface POSTComposer {
29+
30+
MultipartBody composeRequest( HttpRequestWithBody request );
31+
32+
}
33+
34+
35+
interface SessionTimeoutHandler {
36+
37+
boolean handleSessionTimeout();
38+
39+
}
40+
41+
42+
public class HttpConnector {
43+
44+
private Cookie jsessionid;
45+
private SessionTimeoutHandler sessionTimeoutHandler;
46+
47+
48+
public HttpConnector() {
49+
Unirest.config().connectTimeout( 0 );
50+
Unirest.config().socketTimeout( 0 );
51+
Unirest.config().concurrency( 200, 100 );
52+
}
53+
54+
55+
public void authenticate( String url, String username, String password ) {
56+
HttpResponse<String> response = Unirest.get( url )
57+
.basicAuth( username, password )
58+
.asString();
59+
jsessionid = response.getCookies().getNamed( "JSESSIONID" );
60+
}
61+
62+
63+
public HttpResponse<String> post( String url, POSTComposer composer ) {
64+
HttpRequestWithBody request = Unirest.post( url );
65+
MultipartBody multipartBody = composer.composeRequest( request );
66+
ensureJSESSIONIDAttached( multipartBody );
67+
HttpResponse<String> response = multipartBody.asString();
68+
69+
// Handle Session Timeout
70+
if ( response.getStatus() == 401 ) {
71+
boolean shouldResendRequest = sessionTimeoutHandler.handleSessionTimeout();
72+
if ( shouldResendRequest ) {
73+
return post( url, composer );
74+
}
75+
}
76+
77+
return response;
78+
}
79+
80+
81+
public HttpResponse<String> get( String url ) {
82+
GetRequest request = Unirest.get( url );
83+
ensureJSESSIONIDAttached( request );
84+
HttpResponse<String> response = request.asString();
85+
86+
// Handle Session Timeout
87+
if ( response.getStatus() == 401 ) {
88+
boolean shouldResendRequest = sessionTimeoutHandler.handleSessionTimeout();
89+
if ( shouldResendRequest ) {
90+
return get( url );
91+
}
92+
}
93+
94+
return response;
95+
}
96+
97+
98+
public void setSessionTimeoutHandler( SessionTimeoutHandler sessionTimeoutHandler ) {
99+
this.sessionTimeoutHandler = sessionTimeoutHandler;
100+
}
101+
102+
103+
private void ensureJSESSIONIDAttached( HttpRequest request ) {
104+
if ( jsessionid != null ) {
105+
request.cookie( jsessionid );
106+
}
107+
}
108+
109+
}

control-connector/src/main/java/org/polypheny/control/client/PolyphenyControlConnector.java

+25-13
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019-2021 The Polypheny Project
2+
* Copyright 2019-2023 The Polypheny Project
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,6 +16,7 @@
1616

1717
package org.polypheny.control.client;
1818

19+
1920
import com.google.common.reflect.TypeToken;
2021
import com.google.gson.Gson;
2122
import java.lang.reflect.Type;
@@ -39,21 +40,28 @@ public class PolyphenyControlConnector {
3940

4041
private final String controlUrl;
4142
private static int clientId = -1;
42-
private final ClientType clientType;
43+
private final ClientData clientData;
44+
private final HttpConnector httpConnector;
4345

4446
private final Gson gson = new Gson();
4547

4648
private final LogHandler logHandler;
4749

4850

49-
public PolyphenyControlConnector( String controlUrl, ClientType clientType, LogHandler logHandler ) throws URISyntaxException {
50-
this.clientType = clientType;
51+
public PolyphenyControlConnector( String controlUrl, ClientData clientData, LogHandler logHandler ) throws URISyntaxException {
52+
this.clientData = clientData;
5153
this.logHandler = logHandler;
5254

53-
Unirest.config().connectTimeout( 0 );
54-
Unirest.config().socketTimeout( 0 );
55-
Unirest.config().concurrency( 200, 100 );
5655
this.controlUrl = "http://" + controlUrl;
56+
57+
httpConnector = new HttpConnector();
58+
httpConnector.setSessionTimeoutHandler( () -> {
59+
httpConnector.authenticate( this.controlUrl + "/", clientData.getUsername(), clientData.getPassword() );
60+
// After authenticating, try again.
61+
return true;
62+
} );
63+
httpConnector.authenticate( this.controlUrl + "/", clientData.getUsername(), clientData.getPassword() );
64+
5765
WebSocket webSocket = new WebSocket( new URI( "ws://" + controlUrl + "/socket/" ) );
5866
webSocket.connect();
5967

@@ -74,7 +82,7 @@ public PolyphenyControlConnector( String controlUrl, ClientType clientType, LogH
7482
public void stopPolypheny() {
7583
setClientType(); // Set the client type (again) - does not hurt and makes sure its set
7684
try {
77-
Unirest.post( controlUrl + "/control/stop" ).field( "clientId", clientId ).asString();
85+
httpConnector.post( controlUrl + "/control/stop", request -> request.field( "clientId", clientId ) );
7886
} catch ( UnirestException e ) {
7987
log.error( "Error while stopping Polypheny-DB", e );
8088
}
@@ -84,7 +92,7 @@ public void stopPolypheny() {
8492
public void startPolypheny() {
8593
setClientType(); // Set the client type (again) - does not hurt and makes sure its set
8694
try {
87-
Unirest.post( controlUrl + "/control/start" ).field( "clientId", clientId ).asString();
95+
httpConnector.post( controlUrl + "/control/start", request -> request.field( "clientId", clientId ) );
8896
} catch ( UnirestException e ) {
8997
log.error( "Error while starting Polypheny-DB", e );
9098
}
@@ -99,7 +107,7 @@ public void updatePolypheny() {
99107
}
100108
// Trigger update
101109
try {
102-
Unirest.post( controlUrl + "/control/update" ).field( "clientId", clientId ).asString();
110+
httpConnector.post( controlUrl + "/control/update", request -> request.field( "clientId", clientId ) );
103111
} catch ( UnirestException e ) {
104112
log.error( "Error while updating Polypheny-DB", e );
105113
}
@@ -121,7 +129,9 @@ public void setConfig( Map<String, String> map ) {
121129
obj.put( entry.getKey(), entry.getValue() );
122130
}
123131
try {
124-
Unirest.post( controlUrl + "/config/set" ).field( "clientId", clientId ).field( "config", obj.toString() ).asString();
132+
httpConnector.post(
133+
controlUrl + "/config/set",
134+
request -> request.field( "clientId", clientId ).field( "config", obj.toString() ) );
125135
} catch ( UnirestException e ) {
126136
log.error( "Error while setting client type", e );
127137
}
@@ -130,7 +140,9 @@ public void setConfig( Map<String, String> map ) {
130140

131141
void setClientType() {
132142
try {
133-
Unirest.post( controlUrl + "/client/type" ).field( "clientId", clientId ).field( "clientType", clientType.name() ).asString();
143+
httpConnector.post(
144+
controlUrl + "/client/type",
145+
request -> request.field( "clientId", clientId ).field( "clientType", clientData.getClientType().name() ) );
134146
} catch ( UnirestException e ) {
135147
log.error( "Error while setting client type", e );
136148
}
@@ -155,7 +167,7 @@ String getStatus() {
155167
private String executeGet( String command ) {
156168
HttpResponse<String> httpResponse;
157169
try {
158-
httpResponse = Unirest.get( controlUrl + command ).asString();
170+
httpResponse = httpConnector.get( controlUrl + command );
159171
return httpResponse.getBody();
160172
} catch ( UnirestException e ) {
161173
log.error( "Exception while sending request", e );

0 commit comments

Comments
 (0)