Search in sources :

Example 6 with CouchDbException

use of com.cloudant.client.org.lightcouch.CouchDbException in project java-cloudant by cloudant.

the class HttpIamTest method iamRenewalFailureOnIamToken.

/**
 * Test IAM token and cookie flow, where session expires and subsequent IAM token fails:
 * - GET a resource on the cloudant server
 * - Cookie jar empty, so get IAM token followed by session cookie
 * - GET now proceeds as normal, expected cookie value is sent in header
 * - second GET on cloudant server, re-using session cookie
 * - third GET on cloudant server, cookie expired, subsequent IAM token fails, no more requests
 * are made
 *
 * @throws Exception
 */
@Test
public void iamRenewalFailureOnIamToken() throws Exception {
    // Request sequence
    mockWebServer.enqueue(OK_IAM_COOKIE);
    mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(hello));
    mockWebServer.enqueue(new MockResponse().setResponseCode(200).setBody(hello));
    // cookie expired
    mockWebServer.enqueue(new MockResponse().setResponseCode(401).setBody("{\"error\":\"credentials_expired\"}"));
    mockIamServer.enqueue(new MockResponse().setResponseCode(200).setBody(IAM_TOKEN));
    // mock IAM going down randomly
    mockIamServer.enqueue(new MockResponse().setResponseCode(500));
    CloudantClient c = CloudantClientHelper.newMockWebServerClientBuilder(mockWebServer).iamApiKey(iamApiKey).build();
    String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString();
    assertEquals(hello, response, "The expected response should be received");
    String response2 = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString();
    assertEquals(hello, response2, "The expected response should be received");
    // unhelpful
    try {
        c.executeRequest(Http.GET(c.getBaseUri())).responseAsString();
        fail("Should get CouchDbException when trying to get response");
    } catch (CouchDbException cdbe) {
        ;
    }
    // cloudant mock server
    // assert that there were 4 calls
    RecordedRequest[] recordedRequests = takeN(mockWebServer, 4);
    assertEquals("/_iam_session", recordedRequests[0].getPath(), "The request should have been for /_iam_session");
    assertThat("The request body should contain the IAM token", recordedRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString(IAM_TOKEN));
    // first request
    assertEquals("/", recordedRequests[1].getPath(), "The request should have been for /");
    // The cookie may or may not have the session id quoted, so check both
    assertThat("The Cookie header should contain the expected session value", recordedRequests[1].getHeader("Cookie"), anyOf(containsString(iamSession(EXPECTED_OK_COOKIE)), containsString(iamSessionUnquoted(EXPECTED_OK_COOKIE))));
    // second request
    assertEquals("/", recordedRequests[2].getPath(), "The request should have been for /");
    // third request, will be rejected due to cookie expiry
    assertEquals("/", recordedRequests[3].getPath(), "The request should have been for /");
    // iam mock server
    // assert that there were 2 calls
    RecordedRequest[] recordedIamRequests = takeN(mockIamServer, 2);
    // first time, automatically fetch because cookie jar is empty
    assertEquals(iamTokenEndpoint, recordedIamRequests[0].getPath(), "The request should have been for " + "/identity/token");
    assertThat("The request body should contain the IAM API key", recordedIamRequests[0].getBody().readString(Charset.forName("UTF-8")), containsString("apikey=" + iamApiKey));
    // second time, refresh (but gets 500) because the cloudant session cookie has expired
    assertEquals(iamTokenEndpoint, recordedIamRequests[1].getPath(), "The request should have been for " + "/identity/token");
}
Also used : RecordedRequest(okhttp3.mockwebserver.RecordedRequest) MockResponse(okhttp3.mockwebserver.MockResponse) CouchDbException(com.cloudant.client.org.lightcouch.CouchDbException) CloudantClient(com.cloudant.client.api.CloudantClient) CoreMatchers.containsString(org.hamcrest.CoreMatchers.containsString) Test(org.junit.jupiter.api.Test)

Example 7 with CouchDbException

use of com.cloudant.client.org.lightcouch.CouchDbException in project java-cloudant by cloudant.

the class ResponseTest method testJsonErrorStreamFromLB.

/**
 * Test that an error stream is correctly assigned to a CouchDbException error field if it comes
 * directly from the lb and not the service.
 * <P>
 * This is a Cloudant service test, where a HTTP proxy may send an error.
 * We can provoke it into doing so by sending an invalid HTTP request - in this case an
 * invalid header field.
 * Originally these errors were wrapped in HTML and so exercised a different path - they are now
 * JSON body responses like most other Cloudant requests so should be on the normal exception
 * handling path, but it is worth checking that we do work with these error types.
 * </P>
 */
@RequiresCloudant
@Test
public void testJsonErrorStreamFromLB() throws Exception {
    final AtomicBoolean badHeaderEnabled = new AtomicBoolean(false);
    CloudantClient c = CloudantClientHelper.getClientBuilder().interceptors(new HttpConnectionRequestInterceptor() {

        @Override
        public HttpConnectionInterceptorContext interceptRequest(HttpConnectionInterceptorContext context) {
            if (badHeaderEnabled.get()) {
                // Note space is also a bad char, but OkHttp prohibits them
                String badHeaderCharacters = "()<>@,;\\/[]?=";
                // set a header using characters that are not permitted
                context.connection.requestProperties.put(badHeaderCharacters, badHeaderCharacters);
            }
            return context;
        }
    }).build();
    try {
        // Make a good request, which will set up the session etc
        HttpConnection d = c.executeRequest(Http.GET(c.getBaseUri()));
        d.responseAsString();
        assertTrue(d.getConnection().getResponseCode() / 100 == 2, "The first request should " + "succeed");
        // Enable the bad headers and expect the exception on the next request
        badHeaderEnabled.set(true);
        String response = c.executeRequest(Http.GET(c.getBaseUri())).responseAsString();
        fail("A CouchDbException should be thrown, but had response " + response);
    } catch (CouchDbException e) {
        // we expect a CouchDbException
        assertEquals(400, e.getStatusCode(), "The exception should be for a bad request");
        assertNotNull(e.getError(), "The exception should have an error set");
        assertEquals("bad_request", e.getError(), "The exception error should be bad request");
    } finally {
        // Disable the bad header to allow a clean shutdown
        badHeaderEnabled.set(false);
        c.shutdown();
    }
}
Also used : HttpConnectionRequestInterceptor(com.cloudant.http.HttpConnectionRequestInterceptor) AtomicBoolean(java.util.concurrent.atomic.AtomicBoolean) CouchDbException(com.cloudant.client.org.lightcouch.CouchDbException) CloudantClient(com.cloudant.client.api.CloudantClient) HttpConnection(com.cloudant.http.HttpConnection) HttpConnectionInterceptorContext(com.cloudant.http.HttpConnectionInterceptorContext) Test(org.junit.jupiter.api.Test) RequiresCloudant(com.cloudant.test.main.RequiresCloudant)

Example 8 with CouchDbException

use of com.cloudant.client.org.lightcouch.CouchDbException in project java-cloudant by cloudant.

the class SslAuthenticationTest method localSslAuthenticationEnabled.

/**
 * Connect to the local simple https server with SSL authentication enabled explicitly.
 * This should throw an exception because the SSL authentication fails.
 */
@TestTemplate
public void localSslAuthenticationEnabled() throws Exception {
    CouchDbException thrownException = null;
    try {
        CloudantClient dbClient = CloudantClientHelper.newMockWebServerClientBuilder(server).build();
        // Queue a 200 OK response
        server.enqueue(new MockResponse());
        // Make an arbitrary connection to the DB.
        dbClient.getAllDbs();
    } catch (CouchDbException e) {
        thrownException = e;
    }
    validateClientAuthenticationException(thrownException);
}
Also used : CouchDbException(com.cloudant.client.org.lightcouch.CouchDbException) MockResponse(okhttp3.mockwebserver.MockResponse) CloudantClient(com.cloudant.client.api.CloudantClient) TestTemplate(org.junit.jupiter.api.TestTemplate)

Example 9 with CouchDbException

use of com.cloudant.client.org.lightcouch.CouchDbException in project java-cloudant by cloudant.

the class ClientBuilder method build.

/**
 * Build the {@link CloudantClient} instance based on the endpoint used to construct this
 * client builder and the options that have been set on it before calling this method.
 *
 * @return the {@link CloudantClient} instance for the specified end point and options
 */
public CloudantClient build() {
    logger.config("Building client using URL: " + url);
    // Build properties and couchdb client
    CouchDbProperties props = new CouchDbProperties(url);
    props.addRequestInterceptors(USER_AGENT_INTERCEPTOR);
    if (this.iamApiKey != null) {
        // Create IAM cookie interceptor and set in HttpConnection interceptors
        IamCookieInterceptor cookieInterceptor = new IamCookieInterceptor(this.iamApiKey, this.url.toString());
        props.addRequestInterceptors(cookieInterceptor);
        props.addResponseInterceptors(cookieInterceptor);
        logger.config("Added IAM cookie interceptor");
    } else // Create cookie interceptor
    if (this.username != null && this.password != null) {
        // make interceptor if both username and password are not null
        // Create cookie interceptor and set in HttpConnection interceptors
        CookieInterceptor cookieInterceptor = new CookieInterceptor(username, password, this.url.toString());
        props.addRequestInterceptors(cookieInterceptor);
        props.addResponseInterceptors(cookieInterceptor);
        logger.config("Added cookie interceptor");
    } else {
        // If username or password is null, throw an exception
        if (username != null || password != null) {
            // Username and password both have to contain values
            throw new CouchDbException("Either a username and password must be provided, or " + "both values must be null. Please check the credentials and try again.");
        }
    }
    // If setter methods for read and connection timeout are not called, default values
    // are used.
    logger.config(String.format("Connect timeout: %s %s", connectTimeout, connectTimeoutUnit));
    logger.config(String.format("Read timeout: %s %s", readTimeout, readTimeoutUnit));
    // Log a warning if the DNS cache time is too long
    try {
        boolean shouldLogValueWarning = false;
        boolean isUsingDefaultTTLValue = true;
        String ttlString = Security.getProperty("networkaddress.cache.ttl");
        // Was able to access the property
        if (ttlString != null) {
            try {
                int ttl = Integer.parseInt(ttlString);
                isUsingDefaultTTLValue = false;
                logger.finest("networkaddress.cache.ttl was " + ttl);
                if (ttl > 30 || ttl < 0) {
                    shouldLogValueWarning = true;
                }
            } catch (NumberFormatException nfe) {
                // Suppress the exception, this will result in the default being used
                logger.finest("networkaddress.cache.ttl was not an int.");
            }
        }
        if (isUsingDefaultTTLValue && System.getSecurityManager() != null) {
            // If we're using a default value and there is a SecurityManager we need to warn
            shouldLogValueWarning = true;
        }
        if (shouldLogValueWarning) {
            logger.warning("DNS cache lifetime may be too long. DNS cache lifetimes in excess" + " of 30 seconds may impede client operation during cluster failover.");
        }
    } catch (SecurityException e) {
        // Couldn't access the property; log a warning
        logger.warning("Permission denied to check Java DNS cache TTL. If the cache " + "lifetime is too long cluster failover will be impeded.");
    }
    props.addRequestInterceptors(new TimeoutCustomizationInterceptor(connectTimeout, connectTimeoutUnit, readTimeout, readTimeoutUnit));
    // Set connect options
    props.setMaxConnections(maxConnections);
    props.setProxyURL(proxyURL);
    if (proxyUser != null) {
        // if there was proxy auth information set up proxy auth
        if ("http".equals(url.getProtocol())) {
            // If we are using http, create an interceptor to add the Proxy-Authorization header
            props.addRequestInterceptors(new ProxyAuthInterceptor(proxyUser, proxyPassword));
            logger.config("Added proxy auth interceptor");
        } else {
            // Set up an authenticator
            props.setProxyAuthentication(new PasswordAuthentication(proxyUser, proxyPassword.toCharArray()));
        }
    }
    if (isSSLAuthenticationDisabled) {
        props.addRequestInterceptors(SSLCustomizerInterceptor.SSL_AUTH_DISABLED_INTERCEPTOR);
        logger.config("SSL authentication is disabled");
    }
    if (authenticatedModeSSLSocketFactory != null) {
        props.addRequestInterceptors(new SSLCustomizerInterceptor(authenticatedModeSSLSocketFactory));
        logger.config("Added custom SSL socket factory");
    }
    // Set http connection interceptors
    if (requestInterceptors != null) {
        for (HttpConnectionRequestInterceptor requestInterceptor : requestInterceptors) {
            props.addRequestInterceptors(requestInterceptor);
            logger.config("Added request interceptor: " + requestInterceptor.getClass().getName());
        }
    }
    if (responseInterceptors != null) {
        for (HttpConnectionResponseInterceptor responseInterceptor : responseInterceptors) {
            props.addResponseInterceptors(responseInterceptor);
            logger.config("Added response interceptor: " + responseInterceptor.getClass().getName());
        }
    }
    // if no gsonBuilder has been provided, create a new one
    if (gsonBuilder == null) {
        gsonBuilder = new GsonBuilder();
        logger.config("Using default GSON builder");
    } else {
        logger.config("Using custom GSON builder");
    }
    // always register additional TypeAdapaters for derserializing some Cloudant specific
    // types before constructing the CloudantClient
    gsonBuilder.registerTypeAdapter(DeserializationTypes.SHARDS, new ShardDeserializer()).registerTypeAdapter(DeserializationTypes.INDICES, new IndexDeserializer()).registerTypeAdapter(DeserializationTypes.PERMISSIONS_MAP, new SecurityDeserializer()).registerTypeAdapter(Key.ComplexKey.class, new Key.ComplexKeyDeserializer());
    return new CloudantClient(props, gsonBuilder);
}
Also used : HttpConnectionRequestInterceptor(com.cloudant.http.HttpConnectionRequestInterceptor) CouchDbException(com.cloudant.client.org.lightcouch.CouchDbException) ShardDeserializer(com.cloudant.client.internal.util.ShardDeserializer) IndexDeserializer(com.cloudant.client.internal.util.IndexDeserializer) IamCookieInterceptor(com.cloudant.http.internal.interceptors.IamCookieInterceptor) GsonBuilder(com.google.gson.GsonBuilder) CouchDbProperties(com.cloudant.client.org.lightcouch.CouchDbProperties) SSLCustomizerInterceptor(com.cloudant.http.internal.interceptors.SSLCustomizerInterceptor) ProxyAuthInterceptor(com.cloudant.http.internal.interceptors.ProxyAuthInterceptor) IamCookieInterceptor(com.cloudant.http.internal.interceptors.IamCookieInterceptor) CookieInterceptor(com.cloudant.http.internal.interceptors.CookieInterceptor) SecurityDeserializer(com.cloudant.client.internal.util.SecurityDeserializer) TimeoutCustomizationInterceptor(com.cloudant.http.internal.interceptors.TimeoutCustomizationInterceptor) HttpConnectionResponseInterceptor(com.cloudant.http.HttpConnectionResponseInterceptor) Key(com.cloudant.client.api.views.Key) PasswordAuthentication(java.net.PasswordAuthentication)

Aggregations

CouchDbException (com.cloudant.client.org.lightcouch.CouchDbException)9 CloudantClient (com.cloudant.client.api.CloudantClient)7 MockResponse (okhttp3.mockwebserver.MockResponse)6 Test (org.junit.jupiter.api.Test)4 CoreMatchers.containsString (org.hamcrest.CoreMatchers.containsString)3 HttpConnectionRequestInterceptor (com.cloudant.http.HttpConnectionRequestInterceptor)2 RecordedRequest (okhttp3.mockwebserver.RecordedRequest)2 TestTemplate (org.junit.jupiter.api.TestTemplate)2 Database (com.cloudant.client.api.Database)1 IndexField (com.cloudant.client.api.model.IndexField)1 Permissions (com.cloudant.client.api.model.Permissions)1 JsonIndex (com.cloudant.client.api.query.JsonIndex)1 Key (com.cloudant.client.api.views.Key)1 IndexDeserializer (com.cloudant.client.internal.util.IndexDeserializer)1 SecurityDeserializer (com.cloudant.client.internal.util.SecurityDeserializer)1 ShardDeserializer (com.cloudant.client.internal.util.ShardDeserializer)1 CouchDbProperties (com.cloudant.client.org.lightcouch.CouchDbProperties)1 HttpConnection (com.cloudant.http.HttpConnection)1 HttpConnectionInterceptorContext (com.cloudant.http.HttpConnectionInterceptorContext)1 HttpConnectionResponseInterceptor (com.cloudant.http.HttpConnectionResponseInterceptor)1