diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml index 3579b6b52..06244724e 100644 --- a/.github/workflows/maven.yml +++ b/.github/workflows/maven.yml @@ -20,10 +20,10 @@ jobs: matrix: docker-img: - docker.io/arangodb/arangodb:3.7.15 - - docker.io/arangodb/arangodb:3.8.2 + - docker.io/arangodb/arangodb:3.8.4 - docker.io/arangodb/arangodb-preview:3.9.0-alpha.1 - docker.io/arangodb/enterprise:3.7.15 - - docker.io/arangodb/enterprise:3.8.2 + - docker.io/arangodb/enterprise:3.8.4 - docker.io/arangodb/enterprise-preview:3.9.0-alpha.1 topology: - single @@ -38,7 +38,7 @@ jobs: steps: - uses: actions/checkout@v2 - - name: Set up JDK 1.8 + - name: Set up JDK uses: actions/setup-java@v2 with: java-version: ${{matrix.java-version}} @@ -74,3 +74,69 @@ jobs: with: name: logs.tgz path: ./logs.tgz + + test-jwt: + timeout-minutes: 20 + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + docker-img: + - docker.io/arangodb/enterprise:3.8.4 + topology: + - single + - cluster + - activefailover + db-ext-names: + - false + java-version: + - 17 + user-language: + - en + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK + uses: actions/setup-java@v2 + with: + java-version: ${{matrix.java-version}} + distribution: 'adopt' + - name: Start Database + run: ./docker/start_db.sh + env: + ARANGO_LICENSE_KEY: ${{ secrets.ARANGO_LICENSE_KEY }} + STARTER_MODE: ${{matrix.topology}} + DOCKER_IMAGE: ${{matrix.docker-img}} + DATABASE_EXTENDED_NAMES: ${{matrix.db-ext-names}} + - name: Cache local Maven repository + uses: actions/cache@v2 + with: + path: ~/.m2/repository + key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }} + restore-keys: ${{ runner.os }}-maven- + - name: Set JWT + run: | + ENDPOINT=$(./docker/find_active_endpoint.sh) + echo "Active endpoint: $ENDPOINT" + JWT=$(curl "http://$ENDPOINT/_db/_system/_open/auth" -X POST -d '{"username":"root","password":"test"}' | jq ".jwt" | xargs) + echo "Setting JWT: $JWT" + sed -i "/arangodb.password/c\arangodb.jwt=$JWT" src/test/resources/arangodb.properties + - name: Info + run: mvn -version + - name: Test + run: mvn --no-transfer-progress test -DargLine="-Duser.language=${{matrix.user-language}}" + - name: Collect docker logs on failure + if: ${{ cancelled() || failure() }} + uses: jwalton/gh-docker-logs@v1 + with: + dest: './logs' + - name: Tar logs + if: ${{ cancelled() || failure() }} + run: tar cvzf ./logs.tgz ./logs + - name: Upload logs to GitHub + if: ${{ cancelled() || failure() }} + uses: actions/upload-artifact@master + with: + name: logs.tgz + path: ./logs.tgz diff --git a/ChangeLog.md b/ChangeLog.md index c8b30170d..48ecb27aa 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -6,6 +6,11 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) a ## [Unreleased] +- fixed swallowing connection exceptions (#420) +- fixed `stopwords` analyzer (#414) +- set max retries for active failover redirects (#412) +- fixed deserializing `null` value as String (#411) + ## [6.14.0] - 2021-10-01 - fixed issues with non-English locales (#407) diff --git a/docker/find_active_endpoint.sh b/docker/find_active_endpoint.sh new file mode 100755 index 000000000..505f4022e --- /dev/null +++ b/docker/find_active_endpoint.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +COORDINATORS=("172.17.0.1:8529" "172.17.0.1:8539" "172.17.0.1:8549") + +for a in ${COORDINATORS[*]} ; do + if curl -u root:test --silent --fail "http://$a"; then + echo "$a" + exit 0 + fi +done + +echo "Could not find any active endpoint!" +exit 1 diff --git a/src/main/java/com/arangodb/ArangoDB.java b/src/main/java/com/arangodb/ArangoDB.java index 25a0738f8..8cfa37e31 100644 --- a/src/main/java/com/arangodb/ArangoDB.java +++ b/src/main/java/com/arangodb/ArangoDB.java @@ -179,6 +179,17 @@ public Builder password(final String password) { return this; } + /** + * Sets the JWT for the user authentication. + * + * @param jwt token to use (default: {@code null}) + * @return {@link ArangoDB.Builder} + */ + public Builder jwt(final String jwt) { + setJwt(jwt); + return this; + } + /** * If set to {@code true} SSL will be used when connecting to an ArangoDB server. * @@ -217,7 +228,7 @@ public Builder hostnameVerifier(final HostnameVerifier hostnameVerifier) { * * @param httpRequestRetryHandler HttpRequestRetryHandler to be used * @return {@link ArangoDB.Builder} - * + *

*

* NOTE: * Some ArangoDB HTTP endpoints do not honor RFC-2616 HTTP 1.1 specification in respect to @@ -647,12 +658,18 @@ public synchronized ArangoDB build() { final Collection hostList = createHostList(max, connectionFactory); final HostResolver hostResolver = createHostResolver(hostList, max, connectionFactory); final HostHandler hostHandler = createHostHandler(hostResolver); + hostHandler.setJwt(jwt); return new ArangoDBImpl( new VstCommunicationSync.Builder(hostHandler).timeout(timeout).user(user).password(password) - .useSsl(useSsl).sslContext(sslContext).chunksize(chunksize).maxConnections(maxConnections) - .connectionTtl(connectionTtl), - new HttpCommunication.Builder(hostHandler), util, protocol, hostResolver, new ArangoContext()); + .jwt(jwt).useSsl(useSsl).sslContext(sslContext).chunksize(chunksize) + .maxConnections(maxConnections).connectionTtl(connectionTtl), + new HttpCommunication.Builder(hostHandler), + util, + protocol, + hostResolver, + hostHandler, + new ArangoContext()); } } @@ -664,6 +681,14 @@ public synchronized ArangoDB build() { */ void shutdown() throws ArangoDBException; + /** + * Updates the JWT used for requests authorization. It does not change already existing VST connections, since VST + * connections are authenticated during the initialization phase. + * + * @param jwt token to use + */ + void updateJwt(String jwt); + /** * Returns a {@code ArangoDatabase} instance for the {@code _system} database. * @@ -897,10 +922,8 @@ public synchronized ArangoDB build() { /** * Returns fatal, error, warning or info log messages from the server's global log. * - * @param options - * Additional options, can be null + * @param options Additional options, can be null * @return the log messages - * * @throws ArangoDBException * @see API * Documentation @@ -912,10 +935,8 @@ public synchronized ArangoDB build() { /** * Returns the server logs * - * @param options - * Additional options, can be null + * @param options Additional options, can be null * @return the log messages - * * @see API * Documentation * @since ArangoDB 3.8 diff --git a/src/main/java/com/arangodb/async/ArangoDBAsync.java b/src/main/java/com/arangodb/async/ArangoDBAsync.java index 8c88f04e3..762427810 100644 --- a/src/main/java/com/arangodb/async/ArangoDBAsync.java +++ b/src/main/java/com/arangodb/async/ArangoDBAsync.java @@ -26,14 +26,7 @@ import com.arangodb.async.internal.ArangoDBAsyncImpl; import com.arangodb.async.internal.velocystream.VstCommunicationAsync; import com.arangodb.async.internal.velocystream.VstConnectionFactoryAsync; -import com.arangodb.entity.ArangoDBVersion; -import com.arangodb.entity.LoadBalancingStrategy; -import com.arangodb.entity.LogEntity; -import com.arangodb.entity.LogEntriesEntity; -import com.arangodb.entity.LogLevelEntity; -import com.arangodb.entity.Permissions; -import com.arangodb.entity.ServerRole; -import com.arangodb.entity.UserEntity; +import com.arangodb.entity.*; import com.arangodb.internal.ArangoContext; import com.arangodb.internal.ArangoDefaults; import com.arangodb.internal.InternalArangoDBBuilder; @@ -53,18 +46,7 @@ import com.arangodb.util.ArangoDeserializer; import com.arangodb.util.ArangoSerialization; import com.arangodb.util.ArangoSerializer; -import com.arangodb.velocypack.VPack; -import com.arangodb.velocypack.VPackAnnotationFieldFilter; -import com.arangodb.velocypack.VPackAnnotationFieldNaming; -import com.arangodb.velocypack.VPackDeserializer; -import com.arangodb.velocypack.VPackInstanceCreator; -import com.arangodb.velocypack.VPackJsonDeserializer; -import com.arangodb.velocypack.VPackJsonSerializer; -import com.arangodb.velocypack.VPackModule; -import com.arangodb.velocypack.VPackParser; -import com.arangodb.velocypack.VPackParserModule; -import com.arangodb.velocypack.VPackSerializer; -import com.arangodb.velocypack.ValueType; +import com.arangodb.velocypack.*; import com.arangodb.velocystream.Request; import com.arangodb.velocystream.Response; @@ -92,6 +74,14 @@ public interface ArangoDBAsync extends ArangoSerializationAccessor { void shutdown() throws ArangoDBException; + /** + * Updates the JWT used for requests authorization. It does not change already existing VST connections, since VST + * connections are authenticated during the initialization phase. + * + * @param jwt token to use + */ + void updateJwt(String jwt); + /** * Returns a handler of the system database * @@ -281,10 +271,8 @@ public interface ArangoDBAsync extends ArangoSerializationAccessor { /** * Returns fatal, error, warning or info log messages from the server's global log. * - * @param options - * Additional options, can be null + * @param options Additional options, can be null * @return the log messages - * * @see API * Documentation * @deprecated use {@link #getLogEntries(LogOptions)} instead @@ -295,10 +283,8 @@ public interface ArangoDBAsync extends ArangoSerializationAccessor { /** * Returns the server logs * - * @param options - * Additional options, can be null + * @param options Additional options, can be null * @return the log messages - * * @see API * Documentation * @since ArangoDB 3.8 @@ -383,6 +369,17 @@ public Builder password(final String password) { return this; } + /** + * Sets the JWT for the user authentication. + * + * @param jwt token to use (default: {@code null}) + * @return {@link ArangoDBAsync.Builder} + */ + public Builder jwt(final String jwt) { + setJwt(jwt); + return this; + } + /** * If set to true SSL will be used when connecting to an ArangoDB server. * @@ -812,18 +809,20 @@ public synchronized ArangoDBAsync build() { syncBuilder(syncHostHandler), asyncHostResolver, syncHostResolver, + asyncHostHandler, + syncHostHandler, new ArangoContext()); } private VstCommunicationAsync.Builder asyncBuilder(final HostHandler hostHandler) { return new VstCommunicationAsync.Builder(hostHandler).timeout(timeout).user(user).password(password) - .useSsl(useSsl).sslContext(sslContext).chunksize(chunksize).maxConnections(maxConnections) + .jwt(jwt).useSsl(useSsl).sslContext(sslContext).chunksize(chunksize).maxConnections(maxConnections) .connectionTtl(connectionTtl); } private VstCommunicationSync.Builder syncBuilder(final HostHandler hostHandler) { return new VstCommunicationSync.Builder(hostHandler).timeout(timeout).user(user).password(password) - .useSsl(useSsl).sslContext(sslContext).chunksize(chunksize).maxConnections(maxConnections) + .jwt(jwt).useSsl(useSsl).sslContext(sslContext).chunksize(chunksize).maxConnections(maxConnections) .connectionTtl(connectionTtl); } diff --git a/src/main/java/com/arangodb/async/internal/ArangoDBAsyncImpl.java b/src/main/java/com/arangodb/async/internal/ArangoDBAsyncImpl.java index 426546c5e..93c58df91 100644 --- a/src/main/java/com/arangodb/async/internal/ArangoDBAsyncImpl.java +++ b/src/main/java/com/arangodb/async/internal/ArangoDBAsyncImpl.java @@ -27,6 +27,7 @@ import com.arangodb.entity.*; import com.arangodb.internal.*; import com.arangodb.internal.net.CommunicationProtocol; +import com.arangodb.internal.net.HostHandler; import com.arangodb.internal.net.HostResolver; import com.arangodb.internal.util.ArangoSerializationFactory; import com.arangodb.internal.util.ArangoSerializationFactory.Serializer; @@ -55,6 +56,8 @@ public class ArangoDBAsyncImpl extends InternalArangoDB imp private static final Logger LOGGER = LoggerFactory.getLogger(ArangoDBAsyncImpl.class); private final CommunicationProtocol cp; + private final HostHandler asyncHostHandler; + private final HostHandler syncHostHandler; public ArangoDBAsyncImpl( final VstCommunicationAsync.Builder asyncCommBuilder, @@ -62,6 +65,8 @@ public ArangoDBAsyncImpl( final VstCommunicationSync.Builder syncCommBuilder, final HostResolver asyncHostResolver, final HostResolver syncHostResolver, + final HostHandler asyncHostHandler, + final HostHandler syncHostHandler, final ArangoContext context ) { @@ -70,6 +75,8 @@ public ArangoDBAsyncImpl( final VstCommunication cacheCom = syncCommBuilder.build(util.get(Serializer.INTERNAL)); cp = new VstProtocol(cacheCom); + this.asyncHostHandler = asyncHostHandler; + this.syncHostHandler = syncHostHandler; ArangoExecutorSync arangoExecutorSync = new ArangoExecutorSync(cp, util, new DocumentCache()); asyncHostResolver.init(arangoExecutorSync, util.get(Serializer.INTERNAL)); @@ -95,6 +102,14 @@ public void shutdown() throws ArangoDBException { } } + @Override + public void updateJwt(String jwt) { + asyncHostHandler.setJwt(jwt); + syncHostHandler.setJwt(jwt); + cp.setJwt(jwt); + executor.setJwt(jwt); + } + @Override public ArangoDatabaseAsync db() { return db(ArangoRequestParam.SYSTEM); diff --git a/src/main/java/com/arangodb/async/internal/ArangoExecutorAsync.java b/src/main/java/com/arangodb/async/internal/ArangoExecutorAsync.java index 8340ebcc8..860c185de 100644 --- a/src/main/java/com/arangodb/async/internal/ArangoExecutorAsync.java +++ b/src/main/java/com/arangodb/async/internal/ArangoExecutorAsync.java @@ -80,4 +80,9 @@ public void disconnect() { outgoingExecutor.shutdown(); } } + + public void setJwt(String jwt) { + communication.setJwt(jwt); + } + } diff --git a/src/main/java/com/arangodb/async/internal/velocystream/VstCommunicationAsync.java b/src/main/java/com/arangodb/async/internal/velocystream/VstCommunicationAsync.java index 3a1b3f1b7..f07eeba2a 100644 --- a/src/main/java/com/arangodb/async/internal/velocystream/VstCommunicationAsync.java +++ b/src/main/java/com/arangodb/async/internal/velocystream/VstCommunicationAsync.java @@ -28,6 +28,7 @@ import com.arangodb.internal.util.HostUtils; import com.arangodb.internal.velocystream.VstCommunication; import com.arangodb.internal.velocystream.internal.AuthenticationRequest; +import com.arangodb.internal.velocystream.internal.JwtAuthenticationRequest; import com.arangodb.internal.velocystream.internal.Message; import com.arangodb.util.ArangoSerialization; import com.arangodb.velocypack.exception.VPackException; @@ -49,9 +50,9 @@ public class VstCommunicationAsync extends VstCommunication send(final Message message, final VstConnecti @Override protected void authenticate(final VstConnectionAsync connection) { + Request authRequest; + if (jwt != null) { + authRequest = new JwtAuthenticationRequest(jwt, ENCRYPTION_JWT); + } else { + authRequest = new AuthenticationRequest(user, password != null ? password : "", ENCRYPTION_PLAIN); + } + Response response; try { - response = execute(new AuthenticationRequest(user, password != null ? password : "", ENCRYPTION_PLAIN), - connection).get(); + response = execute(authRequest, connection).get(); } catch (final InterruptedException | ExecutionException e) { - throw new ArangoDBException(e); + Throwable cause = e.getCause(); + if (cause instanceof ArangoDBException) { + throw (ArangoDBException) cause; + } else { + throw new ArangoDBException(e.getCause()); + } } checkError(response); } @@ -142,6 +154,7 @@ public static class Builder { private Long connectionTtl; private String user; private String password; + private String jwt; private Boolean useSsl; private SSLContext sslContext; private Integer chunksize; @@ -167,6 +180,11 @@ public Builder password(final String password) { return this; } + public Builder jwt(final String jwt) { + this.jwt = jwt; + return this; + } + public Builder useSsl(final Boolean useSsl) { this.useSsl = useSsl; return this; @@ -193,7 +211,7 @@ public Builder connectionTtl(final Long connectionTtl) { } public VstCommunicationAsync build(final ArangoSerialization util) { - return new VstCommunicationAsync(hostHandler, timeout, user, password, useSsl, sslContext, util, chunksize, + return new VstCommunicationAsync(hostHandler, timeout, user, password, jwt, useSsl, sslContext, util, chunksize, maxConnections, connectionTtl); } } diff --git a/src/main/java/com/arangodb/internal/ArangoDBImpl.java b/src/main/java/com/arangodb/internal/ArangoDBImpl.java index 60e337b6b..3ced2d2b7 100644 --- a/src/main/java/com/arangodb/internal/ArangoDBImpl.java +++ b/src/main/java/com/arangodb/internal/ArangoDBImpl.java @@ -29,6 +29,7 @@ import com.arangodb.internal.http.HttpProtocol; import com.arangodb.internal.net.CommunicationProtocol; import com.arangodb.internal.net.HostHandle; +import com.arangodb.internal.net.HostHandler; import com.arangodb.internal.net.HostResolver; import com.arangodb.internal.util.ArangoSerializationFactory; import com.arangodb.internal.util.ArangoSerializationFactory.Serializer; @@ -59,10 +60,11 @@ public class ArangoDBImpl extends InternalArangoDB implement private ArangoCursorInitializer cursorInitializer; private final CommunicationProtocol cp; + private final HostHandler hostHandler; public ArangoDBImpl(final VstCommunicationSync.Builder vstBuilder, final HttpCommunication.Builder httpBuilder, final ArangoSerializationFactory util, final Protocol protocol, final HostResolver hostResolver, - final ArangoContext context) { + final HostHandler hostHandler, final ArangoContext context) { super(new ArangoExecutorSync( createProtocol(vstBuilder, httpBuilder, util.get(Serializer.INTERNAL), protocol), @@ -76,6 +78,7 @@ public ArangoDBImpl(final VstCommunicationSync.Builder vstBuilder, final HttpCom new HttpCommunication.Builder(httpBuilder), util.get(Serializer.INTERNAL), protocol); + this.hostHandler = hostHandler; hostResolver.init(this.executor(), util()); @@ -123,6 +126,13 @@ public void shutdown() throws ArangoDBException { } } + @Override + public void updateJwt(String jwt) { + hostHandler.setJwt(jwt); + cp.setJwt(jwt); + executor.setJwt(jwt); + } + @Override public ArangoDatabase db() { return db(ArangoRequestParam.SYSTEM); diff --git a/src/main/java/com/arangodb/internal/ArangoExecutorSync.java b/src/main/java/com/arangodb/internal/ArangoExecutorSync.java index 20143fc8c..31c18d797 100644 --- a/src/main/java/com/arangodb/internal/ArangoExecutorSync.java +++ b/src/main/java/com/arangodb/internal/ArangoExecutorSync.java @@ -90,4 +90,9 @@ public void disconnect() { throw new ArangoDBException(e); } } + + public void setJwt(String jwt) { + protocol.setJwt(jwt); + } + } diff --git a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java index 3b238a751..3d8bb4907 100644 --- a/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java +++ b/src/main/java/com/arangodb/internal/InternalArangoDBBuilder.java @@ -66,6 +66,7 @@ public abstract class InternalArangoDBBuilder { private static final String PROPERTY_KEY_TIMEOUT = "arangodb.timeout"; private static final String PROPERTY_KEY_USER = "arangodb.user"; private static final String PROPERTY_KEY_PASSWORD = "arangodb.password"; + private static final String PROPERTY_KEY_JWT = "arangodb.jwt"; private static final String PROPERTY_KEY_USE_SSL = "arangodb.usessl"; private static final String PROPERTY_KEY_COOKIE_SPEC = "arangodb.httpCookieSpec"; private static final String PROPERTY_KEY_V_STREAM_CHUNK_CONTENT_SIZE = "arangodb.chunksize"; @@ -82,6 +83,7 @@ public abstract class InternalArangoDBBuilder { protected Integer timeout; protected String user; protected String password; + protected String jwt; protected Boolean useSsl; protected String httpCookieSpec; protected HttpRequestRetryHandler httpRequestRetryHandler; @@ -140,6 +142,7 @@ protected void loadProperties(final Properties properties) { timeout = loadTimeout(properties, timeout); user = loadUser(properties, user); password = loadPassword(properties, password); + jwt = loadJwt(properties, jwt); useSsl = loadUseSsl(properties, useSsl); httpCookieSpec = loadhttpCookieSpec(properties, httpCookieSpec); chunksize = loadChunkSize(properties, chunksize); @@ -167,6 +170,10 @@ protected void setPassword(final String password) { this.password = password; } + protected void setJwt(final String jwt) { + this.jwt = jwt; + } + protected void setUseSsl(final Boolean useSsl) { this.useSsl = useSsl; } @@ -305,6 +312,10 @@ private static String loadPassword(final Properties properties, final String cur return getProperty(properties, PROPERTY_KEY_PASSWORD, currentValue, null); } + private static String loadJwt(final Properties properties, final String currentValue) { + return getProperty(properties, PROPERTY_KEY_JWT, currentValue, null); + } + private static Boolean loadUseSsl(final Properties properties, final Boolean currentValue) { return Boolean.parseBoolean( getProperty(properties, PROPERTY_KEY_USE_SSL, currentValue, ArangoDefaults.DEFAULT_USE_SSL)); diff --git a/src/main/java/com/arangodb/internal/http/CURLLogger.java b/src/main/java/com/arangodb/internal/http/CURLLogger.java index 17acbd163..941769fe4 100644 --- a/src/main/java/com/arangodb/internal/http/CURLLogger.java +++ b/src/main/java/com/arangodb/internal/http/CURLLogger.java @@ -42,7 +42,8 @@ private CURLLogger() { public static void log( final String url, final Request request, - final Credentials credencials, + final Credentials credentials, + final String jwt, final ArangoSerialization util) { final RequestType requestType = request.getRequestType(); final boolean includeBody = (requestType == RequestType.POST || requestType == RequestType.PUT @@ -59,9 +60,12 @@ public static void log( buffer.append(" -H '").append(header.getKey()).append(":").append(header.getValue()).append("'"); } } - if (credencials != null) { - buffer.append(" -u ").append(credencials.getUserPrincipal().getName()).append(":") - .append(credencials.getPassword()); + if (credentials != null) { + buffer.append(" -u ").append(credentials.getUserPrincipal().getName()).append(":") + .append(credentials.getPassword()); + } + if (jwt != null) { + buffer.append(" -H ").append("'Authorization: Bearer ").append(jwt).append("'"); } if (includeBody) { buffer.append(" -d @-"); diff --git a/src/main/java/com/arangodb/internal/http/HttpConnection.java b/src/main/java/com/arangodb/internal/http/HttpConnection.java index 5a221164c..50cbc504d 100644 --- a/src/main/java/com/arangodb/internal/http/HttpConnection.java +++ b/src/main/java/com/arangodb/internal/http/HttpConnection.java @@ -69,6 +69,8 @@ import java.util.Map.Entry; import java.util.concurrent.TimeUnit; +import static org.apache.http.HttpHeaders.AUTHORIZATION; + /** * @author Mark Vollmary */ @@ -163,6 +165,7 @@ public HttpConnection build() { private final CloseableHttpClient client; private final String user; private final String password; + private volatile String jwt = null; private final ArangoSerialization util; private final Boolean useSsl; private final Protocol contentType; @@ -315,9 +318,19 @@ public Response execute(final Request request) throws ArangoDBException, IOExcep httpRequest.setHeader("Accept", "application/x-velocypack"); } addHeader(request, httpRequest); - final Credentials credentials = addCredentials(httpRequest); + Credentials credentials = null; + if (jwt != null) { + httpRequest.addHeader(AUTHORIZATION, "Bearer " + jwt); + } else if (user != null) { + credentials = new UsernamePasswordCredentials(user, password != null ? password : ""); + try { + httpRequest.addHeader(new BasicScheme().authenticate(credentials, httpRequest, null)); + } catch (final AuthenticationException e) { + throw new ArangoDBException(e); + } + } if (LOGGER.isDebugEnabled()) { - CURLLogger.log(url, request, credentials, util); + CURLLogger.log(url, request, credentials, jwt, util); } Response response; response = buildResponse(client.execute(httpRequest)); @@ -331,19 +344,6 @@ private static void addHeader(final Request request, final HttpRequestBase httpR } } - public Credentials addCredentials(final HttpRequestBase httpRequest) { - Credentials credentials = null; - if (user != null) { - credentials = new UsernamePasswordCredentials(user, password != null ? password : ""); - try { - httpRequest.addHeader(new BasicScheme().authenticate(credentials, httpRequest, null)); - } catch (final AuthenticationException e) { - throw new ArangoDBException(e); - } - } - return credentials; - } - public Response buildResponse(final CloseableHttpResponse httpResponse) throws UnsupportedOperationException, IOException { final Response response = new Response(); @@ -375,4 +375,9 @@ protected void checkError(final Response response) throws ArangoDBException { ResponseUtils.checkError(util, response); } + @Override + public void setJwt(String jwt) { + this.jwt = jwt; + } + } diff --git a/src/main/java/com/arangodb/internal/http/HttpProtocol.java b/src/main/java/com/arangodb/internal/http/HttpProtocol.java index 7997b245e..c3df748e9 100644 --- a/src/main/java/com/arangodb/internal/http/HttpProtocol.java +++ b/src/main/java/com/arangodb/internal/http/HttpProtocol.java @@ -45,6 +45,11 @@ public Response execute(final Request request, final HostHandle hostHandle) thro return httpCommunitaction.execute(request, hostHandle); } + @Override + public void setJwt(String jwt) { + // no-op: jwt is updated in the host handlers + } + @Override public void close() throws IOException { httpCommunitaction.close(); diff --git a/src/main/java/com/arangodb/internal/net/CommunicationProtocol.java b/src/main/java/com/arangodb/internal/net/CommunicationProtocol.java index fbec7116b..31e9a38ea 100644 --- a/src/main/java/com/arangodb/internal/net/CommunicationProtocol.java +++ b/src/main/java/com/arangodb/internal/net/CommunicationProtocol.java @@ -33,4 +33,6 @@ public interface CommunicationProtocol extends Closeable { Response execute(final Request request, HostHandle hostHandle) throws ArangoDBException; + void setJwt(String jwt); + } diff --git a/src/main/java/com/arangodb/internal/net/Connection.java b/src/main/java/com/arangodb/internal/net/Connection.java index a71a253ae..c2701361e 100644 --- a/src/main/java/com/arangodb/internal/net/Connection.java +++ b/src/main/java/com/arangodb/internal/net/Connection.java @@ -26,5 +26,5 @@ * @author Mark Vollmary */ public interface Connection extends Closeable { - + void setJwt(String jwt); } diff --git a/src/main/java/com/arangodb/internal/net/ConnectionPool.java b/src/main/java/com/arangodb/internal/net/ConnectionPool.java index 38341490e..d1ad313d0 100644 --- a/src/main/java/com/arangodb/internal/net/ConnectionPool.java +++ b/src/main/java/com/arangodb/internal/net/ConnectionPool.java @@ -31,4 +31,6 @@ public interface ConnectionPool extends Closeable { Connection connection(); + void setJwt(String jwt); + } diff --git a/src/main/java/com/arangodb/internal/net/ConnectionPoolImpl.java b/src/main/java/com/arangodb/internal/net/ConnectionPoolImpl.java index 43ab86d86..cf275e6db 100644 --- a/src/main/java/com/arangodb/internal/net/ConnectionPoolImpl.java +++ b/src/main/java/com/arangodb/internal/net/ConnectionPoolImpl.java @@ -41,6 +41,7 @@ public class ConnectionPoolImpl implements ConnectionPool { private final List connections; private int current; private final ConnectionFactory factory; + private volatile String jwt = null; public ConnectionPoolImpl(final HostDescription host, final Integer maxConnections, final ConnectionFactory factory) { @@ -54,7 +55,9 @@ public ConnectionPoolImpl(final HostDescription host, final Integer maxConnectio @Override public Connection createConnection(final HostDescription host) { - return factory.create(host); + Connection c = factory.create(host); + c.setJwt(jwt); + return c; } @Override @@ -78,6 +81,14 @@ public synchronized Connection connection() { return connection; } + @Override + public void setJwt(String jwt) { + this.jwt = jwt; + for (Connection connection : connections) { + connection.setJwt(jwt); + } + } + @Override public synchronized void close() throws IOException { for (final Connection connection : connections) { diff --git a/src/main/java/com/arangodb/internal/net/DirtyReadHostHandler.java b/src/main/java/com/arangodb/internal/net/DirtyReadHostHandler.java index 93039224d..d8e3f0033 100644 --- a/src/main/java/com/arangodb/internal/net/DirtyReadHostHandler.java +++ b/src/main/java/com/arangodb/internal/net/DirtyReadHostHandler.java @@ -81,4 +81,10 @@ public void closeCurrentOnError() { determineHostHandler().closeCurrentOnError(); } + @Override + public void setJwt(String jwt) { + master.setJwt(jwt); + follower.setJwt(jwt); + } + } diff --git a/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java b/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java index d88ac21ec..a1427d426 100644 --- a/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java +++ b/src/main/java/com/arangodb/internal/net/FallbackHostHandler.java @@ -104,4 +104,9 @@ public void closeCurrentOnError() { current.closeOnError(); } + @Override + public void setJwt(String jwt) { + hosts.setJwt(jwt); + } + } diff --git a/src/main/java/com/arangodb/internal/net/Host.java b/src/main/java/com/arangodb/internal/net/Host.java index c3e7514ba..57f4d6e80 100644 --- a/src/main/java/com/arangodb/internal/net/Host.java +++ b/src/main/java/com/arangodb/internal/net/Host.java @@ -38,4 +38,7 @@ public interface Host { void setMarkforDeletion(boolean markforDeletion); boolean isMarkforDeletion(); + + void setJwt(String jwt); + } diff --git a/src/main/java/com/arangodb/internal/net/HostHandler.java b/src/main/java/com/arangodb/internal/net/HostHandler.java index b432ca81e..42c1e7570 100644 --- a/src/main/java/com/arangodb/internal/net/HostHandler.java +++ b/src/main/java/com/arangodb/internal/net/HostHandler.java @@ -40,4 +40,7 @@ public interface HostHandler { void close() throws IOException; void closeCurrentOnError(); + + void setJwt(String jwt); + } diff --git a/src/main/java/com/arangodb/internal/net/HostImpl.java b/src/main/java/com/arangodb/internal/net/HostImpl.java index a9839c911..d50b84ae1 100644 --- a/src/main/java/com/arangodb/internal/net/HostImpl.java +++ b/src/main/java/com/arangodb/internal/net/HostImpl.java @@ -73,6 +73,11 @@ public boolean isMarkforDeletion() { return markforDeletion; } + @Override + public void setJwt(String jwt) { + connectionPool.setJwt(jwt); + } + public void setMarkforDeletion(boolean markforDeletion) { this.markforDeletion = markforDeletion; } diff --git a/src/main/java/com/arangodb/internal/net/HostSet.java b/src/main/java/com/arangodb/internal/net/HostSet.java index f945a43cb..ddcad12bb 100644 --- a/src/main/java/com/arangodb/internal/net/HostSet.java +++ b/src/main/java/com/arangodb/internal/net/HostSet.java @@ -13,6 +13,7 @@ public class HostSet { private static final Logger LOGGER = LoggerFactory.getLogger(HostSet.class); private final ArrayList hosts = new ArrayList<>(); + private volatile String jwt = null; public HostSet() { super(); @@ -32,21 +33,18 @@ public List getHostsList() { } public void addHost(Host newHost) { - if (hosts.contains(newHost)) { LOGGER.debug("Host" + newHost + " already in Set"); - for (Host host : hosts) { if (host.equals(newHost)) { host.setMarkforDeletion(false); } } - } else { + newHost.setJwt(jwt); hosts.add(newHost); LOGGER.debug("Added Host " + newHost + " - now " + hosts.size() + " Hosts in List"); } - } public void close() { @@ -77,7 +75,7 @@ public void clearAllMarkedForDeletion() { LOGGER.debug("Clear all Hosts in Set with markForDeletion"); Iterator iterable = hosts.iterator(); - while (iterable.hasNext()){ + while (iterable.hasNext()) { Host host = iterable.next(); if (host.isMarkforDeletion()) { try { @@ -99,4 +97,12 @@ public void clear() { close(); hosts.clear(); } + + public void setJwt(String jwt) { + this.jwt = jwt; + for (Host h : hosts) { + h.setJwt(jwt); + } + } + } diff --git a/src/main/java/com/arangodb/internal/net/RandomHostHandler.java b/src/main/java/com/arangodb/internal/net/RandomHostHandler.java index a3ee4cac7..3ab1ce7da 100644 --- a/src/main/java/com/arangodb/internal/net/RandomHostHandler.java +++ b/src/main/java/com/arangodb/internal/net/RandomHostHandler.java @@ -85,4 +85,10 @@ public void closeCurrentOnError() { current.closeOnError(); } + @Override + public void setJwt(String jwt) { + fallback.setJwt(jwt); + hosts.setJwt(jwt); + } + } diff --git a/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java b/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java index e4e653ff9..99f3c24eb 100644 --- a/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java +++ b/src/main/java/com/arangodb/internal/net/RoundRobinHostHandler.java @@ -109,4 +109,9 @@ public void closeCurrentOnError() { currentHost.closeOnError(); } + @Override + public void setJwt(String jwt) { + hosts.setJwt(jwt); + } + } diff --git a/src/main/java/com/arangodb/internal/velocypack/VPackDriverModule.java b/src/main/java/com/arangodb/internal/velocypack/VPackDriverModule.java index c0038cba3..723a696b2 100644 --- a/src/main/java/com/arangodb/internal/velocypack/VPackDriverModule.java +++ b/src/main/java/com/arangodb/internal/velocypack/VPackDriverModule.java @@ -27,6 +27,7 @@ import com.arangodb.entity.arangosearch.ConsolidationType; import com.arangodb.entity.arangosearch.analyzer.SearchAnalyzer; import com.arangodb.internal.velocystream.internal.AuthenticationRequest; +import com.arangodb.internal.velocystream.internal.JwtAuthenticationRequest; import com.arangodb.model.CollectionSchema; import com.arangodb.model.TraversalOptions; import com.arangodb.model.arangosearch.ArangoSearchPropertiesOptions; @@ -55,6 +56,7 @@ public > void setup(final C context) { }); context.registerSerializer(Request.class, VPackSerializers.REQUEST); context.registerSerializer(AuthenticationRequest.class, VPackSerializers.AUTH_REQUEST); + context.registerSerializer(JwtAuthenticationRequest.class, VPackSerializers.JWT_AUTH_REQUEST); context.registerSerializer(CollectionType.class, VPackSerializers.COLLECTION_TYPE); context.registerSerializer(BaseDocument.class, VPackSerializers.BASE_DOCUMENT); context.registerSerializer(BaseEdgeDocument.class, VPackSerializers.BASE_EDGE_DOCUMENT); diff --git a/src/main/java/com/arangodb/internal/velocypack/VPackSerializers.java b/src/main/java/com/arangodb/internal/velocypack/VPackSerializers.java index 9c7884e2f..ef3346db9 100644 --- a/src/main/java/com/arangodb/internal/velocypack/VPackSerializers.java +++ b/src/main/java/com/arangodb/internal/velocypack/VPackSerializers.java @@ -38,6 +38,7 @@ import com.arangodb.entity.arangosearch.StoreValuesType; import com.arangodb.entity.arangosearch.StoredValue; import com.arangodb.internal.velocystream.internal.AuthenticationRequest; +import com.arangodb.internal.velocystream.internal.JwtAuthenticationRequest; import com.arangodb.model.CollectionSchema; import com.arangodb.model.TraversalOptions; import com.arangodb.model.TraversalOptions.Order; @@ -90,6 +91,15 @@ public class VPackSerializers { builder.close(); }; + public static final VPackSerializer JWT_AUTH_REQUEST = (builder, attribute, value, context) -> { + builder.add(attribute, ValueType.ARRAY); + builder.add(value.getVersion()); + builder.add(value.getType()); + builder.add(value.getEncryption()); + builder.add(value.getToken()); + builder.close(); + }; + public static final VPackSerializer COLLECTION_TYPE = (builder, attribute, value, context) -> builder.add(attribute, value.getType()); public static final VPackSerializer BASE_DOCUMENT = (builder, attribute, value, context) -> { diff --git a/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java b/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java index 92187e9ba..0cb2faea1 100644 --- a/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java +++ b/src/main/java/com/arangodb/internal/velocystream/VstCommunication.java @@ -52,6 +52,7 @@ public abstract class VstCommunication implements Closeable { protected static final String ENCRYPTION_PLAIN = "plain"; + protected static final String ENCRYPTION_JWT = "jwt"; private static final Logger LOGGER = LoggerFactory.getLogger(VstCommunication.class); protected static final AtomicLong mId = new AtomicLong(0L); @@ -59,15 +60,17 @@ public abstract class VstCommunication implements Cl protected final String user; protected final String password; + protected volatile String jwt; protected final Integer chunksize; protected final HostHandler hostHandler; - protected VstCommunication(final Integer timeout, final String user, final String password, final Boolean useSsl, - final SSLContext sslContext, final ArangoSerialization util, final Integer chunksize, - final HostHandler hostHandler) { + protected VstCommunication(final Integer timeout, final String user, final String password, final String jwt, + final Boolean useSsl, final SSLContext sslContext, final ArangoSerialization util, + final Integer chunksize, final HostHandler hostHandler) { this.user = user; this.password = password; + this.jwt = jwt; this.util = util; this.hostHandler = hostHandler; this.chunksize = chunksize != null ? chunksize : ArangoDefaults.CHUNK_DEFAULT_CONTENT_SIZE; @@ -89,7 +92,7 @@ protected synchronized C connect(final HostHandle hostHandle, final AccessType a try { connection.open(); hostHandler.success(); - if (user != null) { + if (jwt != null || user != null) { tryAuthenticate(connection); } hostHandler.confirm(); @@ -191,4 +194,9 @@ protected Collection buildChunks(final Message message) { return chunks; } + public void setJwt(String jwt) { + this.jwt = jwt; + } + + } diff --git a/src/main/java/com/arangodb/internal/velocystream/VstCommunicationSync.java b/src/main/java/com/arangodb/internal/velocystream/VstCommunicationSync.java index f433194bf..59a03a551 100644 --- a/src/main/java/com/arangodb/internal/velocystream/VstCommunicationSync.java +++ b/src/main/java/com/arangodb/internal/velocystream/VstCommunicationSync.java @@ -27,6 +27,7 @@ import com.arangodb.internal.net.HostHandler; import com.arangodb.internal.util.HostUtils; import com.arangodb.internal.velocystream.internal.AuthenticationRequest; +import com.arangodb.internal.velocystream.internal.JwtAuthenticationRequest; import com.arangodb.internal.velocystream.internal.Message; import com.arangodb.internal.velocystream.internal.VstConnectionSync; import com.arangodb.util.ArangoSerialization; @@ -52,6 +53,7 @@ public static class Builder { private Long connectionTtl; private String user; private String password; + private String jwt; private Boolean useSsl; private SSLContext sslContext; private Integer chunksize; @@ -64,8 +66,9 @@ public Builder(final HostHandler hostHandler) { public Builder(final Builder builder) { this(builder.hostHandler); - timeout(builder.timeout).user(builder.user).password(builder.password).useSsl(builder.useSsl) - .sslContext(builder.sslContext).chunksize(builder.chunksize).maxConnections(builder.maxConnections); + timeout(builder.timeout).user(builder.user).password(builder.password).jwt(builder.jwt) + .useSsl(builder.useSsl).sslContext(builder.sslContext).chunksize(builder.chunksize) + .maxConnections(builder.maxConnections); } public Builder timeout(final Integer timeout) { @@ -83,6 +86,11 @@ public Builder password(final String password) { return this; } + public Builder jwt(final String jwt) { + this.jwt = jwt; + return this; + } + public Builder useSsl(final Boolean useSsl) { this.useSsl = useSsl; return this; @@ -109,16 +117,17 @@ public Builder connectionTtl(final Long connectionTtl) { } public VstCommunication build(final ArangoSerialization util) { - return new VstCommunicationSync(hostHandler, timeout, user, password, useSsl, sslContext, util, chunksize, + return new VstCommunicationSync(hostHandler, timeout, user, password, jwt, useSsl, sslContext, util, chunksize, maxConnections, connectionTtl); } } protected VstCommunicationSync(final HostHandler hostHandler, final Integer timeout, final String user, - final String password, final Boolean useSsl, final SSLContext sslContext, final ArangoSerialization util, + final String password, final String jwt, final Boolean useSsl, + final SSLContext sslContext, final ArangoSerialization util, final Integer chunksize, final Integer maxConnections, final Long ttl) { - super(timeout, user, password, useSsl, sslContext, util, chunksize, hostHandler); + super(timeout, user, password, jwt, useSsl, sslContext, util, chunksize, hostHandler); } @Override @@ -158,8 +167,13 @@ private Message send(final Message message, final VstConnectionSync connection) @Override protected void authenticate(final VstConnectionSync connection) { - final Response response = execute( - new AuthenticationRequest(user, password != null ? password : "", ENCRYPTION_PLAIN), connection); + Request authRequest; + if (jwt != null) { + authRequest = new JwtAuthenticationRequest(jwt, ENCRYPTION_JWT); + } else { + authRequest = new AuthenticationRequest(user, password != null ? password : "", ENCRYPTION_PLAIN); + } + final Response response = execute(authRequest, connection); checkError(response); } diff --git a/src/main/java/com/arangodb/internal/velocystream/VstProtocol.java b/src/main/java/com/arangodb/internal/velocystream/VstProtocol.java index 3a5d67352..25de5edba 100644 --- a/src/main/java/com/arangodb/internal/velocystream/VstProtocol.java +++ b/src/main/java/com/arangodb/internal/velocystream/VstProtocol.java @@ -46,6 +46,11 @@ public Response execute(final Request request, final HostHandle hostHandle) thro return communication.execute(request, hostHandle); } + @Override + public void setJwt(String jwt) { + communication.setJwt(jwt); + } + @Override public void close() throws IOException { communication.close(); diff --git a/src/main/java/com/arangodb/internal/velocystream/internal/JwtAuthenticationRequest.java b/src/main/java/com/arangodb/internal/velocystream/internal/JwtAuthenticationRequest.java new file mode 100644 index 000000000..53ab1a58c --- /dev/null +++ b/src/main/java/com/arangodb/internal/velocystream/internal/JwtAuthenticationRequest.java @@ -0,0 +1,25 @@ +package com.arangodb.internal.velocystream.internal; + +import com.arangodb.velocystream.Request; + +public class JwtAuthenticationRequest extends Request { + + private final String token; + private final String encryption; // "jwt" + + public JwtAuthenticationRequest(final String token, final String encryption) { + super(null, null, null); + this.token = token; + this.encryption = encryption; + setType(1000); + } + + public String getToken() { + return token; + } + + public String getEncryption() { + return encryption; + } + +} \ No newline at end of file diff --git a/src/main/java/com/arangodb/internal/velocystream/internal/VstConnection.java b/src/main/java/com/arangodb/internal/velocystream/internal/VstConnection.java index e1a656642..266820773 100644 --- a/src/main/java/com/arangodb/internal/velocystream/internal/VstConnection.java +++ b/src/main/java/com/arangodb/internal/velocystream/internal/VstConnection.java @@ -356,4 +356,8 @@ public String getConnectionName() { return this.connectionName; } + @Override + public void setJwt(String jwt) { + // no-op: VST connections send jwt token only at initialization time + } } diff --git a/src/test/java/com/arangodb/ArangoDBTest.java b/src/test/java/com/arangodb/ArangoDBTest.java index 5b3db075d..985862226 100644 --- a/src/test/java/com/arangodb/ArangoDBTest.java +++ b/src/test/java/com/arangodb/ArangoDBTest.java @@ -388,7 +388,7 @@ public void updateUserDefaultCollectionAccess() { @Test public void authenticationFailPassword() { - final ArangoDB arangoDB = new ArangoDB.Builder().password("no").build(); + final ArangoDB arangoDB = new ArangoDB.Builder().password("no").jwt(null).build(); try { arangoDB.getVersion(); fail(); @@ -399,7 +399,7 @@ public void authenticationFailPassword() { @Test public void authenticationFailUser() { - final ArangoDB arangoDB = new ArangoDB.Builder().user("no").build(); + final ArangoDB arangoDB = new ArangoDB.Builder().user("no").jwt(null).build(); try { arangoDB.getVersion(); fail(); diff --git a/src/test/java/com/arangodb/JwtAuthTest.java b/src/test/java/com/arangodb/JwtAuthTest.java new file mode 100644 index 000000000..9c2532fa7 --- /dev/null +++ b/src/test/java/com/arangodb/JwtAuthTest.java @@ -0,0 +1,122 @@ +package com.arangodb; + +import com.arangodb.util.ArangoSerialization; +import com.arangodb.velocystream.Request; +import com.arangodb.velocystream.RequestType; +import com.arangodb.velocystream.Response; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author Michele Rastelli + */ +@RunWith(Parameterized.class) +public class JwtAuthTest { + + private static String jwt; + private final Protocol protocol; + private ArangoDB arangoDB; + + @BeforeClass + public static void init() { + ArangoDB arangoDB = new ArangoDB.Builder().build(); + jwt = getJwt(arangoDB); + arangoDB.shutdown(); + } + + @After + public void after() { + if (arangoDB != null) + arangoDB.shutdown(); + } + + @Parameterized.Parameters + public static List builders() { + return Arrays.asList( + Protocol.VST, + Protocol.HTTP_JSON, + Protocol.HTTP_VPACK + ); + } + + public JwtAuthTest(Protocol protocol) { + this.protocol = protocol; + } + + @Test + public void notAuthenticated() { + arangoDB = getBuilder().build(); + try { + arangoDB.getVersion(); + fail(); + } catch (ArangoDBException e) { + assertThat(e.getResponseCode(), is(401)); + } + arangoDB.shutdown(); + } + + @Test + public void authenticated() { + arangoDB = getBuilder() + .jwt(jwt) + .build(); + arangoDB.getVersion(); + arangoDB.shutdown(); + } + + @Test + public void updateJwt() { + arangoDB = getBuilder() + .jwt(jwt) + .build(); + arangoDB.getVersion(); + if (protocol == Protocol.VST) { + arangoDB.shutdown(); + } + arangoDB.updateJwt("bla"); + try { + arangoDB.getVersion(); + fail(); + } catch (ArangoDBException e) { + assertThat(e.getResponseCode(), is(401)); + } + + arangoDB.updateJwt(jwt); + arangoDB.getVersion(); + arangoDB.shutdown(); + } + + private ArangoDB.Builder getBuilder() { + return new ArangoDB.Builder() + .useProtocol(protocol) + .jwt(null) // unset credentials from properties file + .user(null) // unset credentials from properties file + .password(null); // unset credentials from properties file + } + + private static String getJwt(ArangoDB arangoDB) { + ArangoSerialization serde = arangoDB.util(); + Map reqBody = new HashMap<>(); + reqBody.put("username", "root"); + reqBody.put("password", "test"); + + Request req = new Request("_system", RequestType.POST, "/_open/auth"); + req.setBody(serde.serialize(reqBody)); + + Response resp = arangoDB.execute(req); + Map respBody = serde.deserialize(resp.getBody(), Map.class); + return respBody.get("jwt"); + } +} diff --git a/src/test/java/com/arangodb/async/ArangoDBTest.java b/src/test/java/com/arangodb/async/ArangoDBTest.java index f6e33b7bc..c21715354 100644 --- a/src/test/java/com/arangodb/async/ArangoDBTest.java +++ b/src/test/java/com/arangodb/async/ArangoDBTest.java @@ -426,7 +426,7 @@ public void updateUserDefaultCollectionAccess() throws InterruptedException, Exe @Test public void authenticationFailPassword() throws InterruptedException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().password("no").build(); + final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().password("no").jwt(null).build(); try { arangoDB.getVersion().get(); fail(); @@ -437,7 +437,7 @@ public void authenticationFailPassword() throws InterruptedException { @Test public void authenticationFailUser() throws InterruptedException { - final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().user("no").build(); + final ArangoDBAsync arangoDB = new ArangoDBAsync.Builder().user("no").jwt(null).build(); try { arangoDB.getVersion().get(); fail(); diff --git a/src/test/java/com/arangodb/async/JwtAuthTest.java b/src/test/java/com/arangodb/async/JwtAuthTest.java new file mode 100644 index 000000000..25f6d1a06 --- /dev/null +++ b/src/test/java/com/arangodb/async/JwtAuthTest.java @@ -0,0 +1,102 @@ +package com.arangodb.async; + +import com.arangodb.ArangoDB; +import com.arangodb.ArangoDBException; +import com.arangodb.util.ArangoSerialization; +import com.arangodb.velocystream.Request; +import com.arangodb.velocystream.RequestType; +import com.arangodb.velocystream.Response; +import org.junit.After; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ExecutionException; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.Assert.fail; + +public class JwtAuthTest { + + private static String jwt; + private ArangoDBAsync arangoDB; + + @BeforeClass + public static void init() { + ArangoDB arangoDB = new ArangoDB.Builder().build(); + jwt = getJwt(arangoDB); + arangoDB.shutdown(); + } + + @After + public void after() { + if (arangoDB != null) + arangoDB.shutdown(); + } + + @Test + public void notAuthenticated() throws InterruptedException { + arangoDB = getBuilder().build(); + try { + arangoDB.getVersion().get(); + fail(); + } catch (ExecutionException e) { + assertThat(e.getCause(), is(instanceOf(ArangoDBException.class))); + assertThat(((ArangoDBException) e.getCause()).getResponseCode(), is(401)); + } + arangoDB.shutdown(); + } + + @Test + public void authenticated() throws ExecutionException, InterruptedException { + arangoDB = getBuilder() + .jwt(jwt) + .build(); + arangoDB.getVersion().get(); + arangoDB.shutdown(); + } + + @Test + public void updateJwt() throws ExecutionException, InterruptedException { + arangoDB = getBuilder() + .jwt(jwt) + .build(); + arangoDB.getVersion().get(); + arangoDB.updateJwt("bla"); + try { + arangoDB.getVersion().get(); + fail(); + } catch (ExecutionException e) { + assertThat(e.getCause(), is(instanceOf(ArangoDBException.class))); + assertThat(((ArangoDBException) e.getCause()).getResponseCode(), is(401)); + } + + arangoDB.updateJwt(jwt); + arangoDB.getVersion().get(); + arangoDB.shutdown(); + } + + private ArangoDBAsync.Builder getBuilder() { + return new ArangoDBAsync.Builder() + .jwt(null) // unset credentials from properties file + .user(null) // unset credentials from properties file + .password(null); // unset credentials from properties file + } + + private static String getJwt(ArangoDB arangoDB) { + ArangoSerialization serde = arangoDB.util(); + Map reqBody = new HashMap<>(); + reqBody.put("username", "root"); + reqBody.put("password", "test"); + + Request req = new Request("_system", RequestType.POST, "/_open/auth"); + req.setBody(serde.serialize(reqBody)); + + Response resp = arangoDB.execute(req); + Map respBody = serde.deserialize(resp.getBody(), Map.class); + return respBody.get("jwt"); + } +} diff --git a/src/test/java/com/arangodb/internal/HostHandlerTest.java b/src/test/java/com/arangodb/internal/HostHandlerTest.java index 7a4d22db0..b25585e50 100644 --- a/src/test/java/com/arangodb/internal/HostHandlerTest.java +++ b/src/test/java/com/arangodb/internal/HostHandlerTest.java @@ -26,6 +26,7 @@ import com.arangodb.util.ArangoSerialization; import org.junit.Test; +import java.io.IOException; import java.util.List; import static org.hamcrest.MatcherAssert.assertThat; @@ -38,9 +39,31 @@ */ public class HostHandlerTest { - private static final Host HOST_0 = new HostImpl(null, new HostDescription("127.0.0.1", 8529)); - private static final Host HOST_1 = new HostImpl(null, new HostDescription("127.0.0.2", 8529)); - private static final Host HOST_2 = new HostImpl(null, new HostDescription("127.0.0.3", 8529)); + private static final ConnectionPool mockCP = new ConnectionPool() { + @Override + public Connection createConnection(HostDescription host) { + return null; + } + + @Override + public Connection connection() { + return null; + } + + @Override + public void setJwt(String jwt) { + + } + + @Override + public void close() throws IOException { + + } + }; + + private static final Host HOST_0 = new HostImpl(mockCP, new HostDescription("127.0.0.1", 8529)); + private static final Host HOST_1 = new HostImpl(mockCP, new HostDescription("127.0.0.2", 8529)); + private static final Host HOST_2 = new HostImpl(mockCP, new HostDescription("127.0.0.3", 8529)); private static final HostResolver SINGLE_HOST = new HostResolver() {