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