11/*
2- * Copyright © 2015, 2017 IBM Corp. All rights reserved.
2+ * Copyright © 2015, 2019 IBM Corp. All rights reserved.
33 *
44 * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
55 * except in compliance with the License. You may obtain a copy of the License at
1414
1515package com .cloudant .http .internal .interceptors ;
1616
17- import com .cloudant .http .HttpConnection ;
1817import com .cloudant .http .HttpConnectionInterceptorContext ;
1918import com .cloudant .http .internal .Utils ;
2019
2726import java .util .logging .Level ;
2827
2928/**
30- * Adds cookie authentication support to http requests.
31- *
32- * It does this by adding the cookie header for CouchDB
33- * using request interceptor pipeline in {@link HttpConnection}.
34- *
35- * If a response has a response code of 401, it will fetch a cookie from
36- * the server using provided credentials and tell {@link HttpConnection} to reply
37- * the request by setting {@link HttpConnectionInterceptorContext#replayRequest} property to true.
38- *
39- * If the request to get the cookie for use in future request fails with a 401 status code
40- * (or any status that indicates client error) cookie authentication will not be attempted again.
29+ * Extends the CookieInterceptorBase to provide Apache CouchDB _session cookie support.
4130 */
4231public class CookieInterceptor extends CookieInterceptorBase {
4332
33+ private final byte [] auth ;
34+
4435 /**
4536 * Constructs a cookie interceptor. Credentials should be supplied not URL encoded, this class
4637 * will perform the necessary URL encoding.
@@ -50,92 +41,72 @@ public class CookieInterceptor extends CookieInterceptorBase {
5041 * @param baseURL The base URL to use when constructing an `_session` request.
5142 */
5243 public CookieInterceptor (String username , String password , String baseURL ) {
53- super ("application/x-www-form-urlencoded" , baseURL , "/_session" );
44+ // Use form encoding for the user/pass submission
45+ super (baseURL , "/_session" , "application/x-www-form-urlencoded" );
5446 try {
55- this .sessionRequestBody = String .format ("name=%s&password=%s" , URLEncoder .encode (username , "UTF-8" ), URLEncoder .encode (password , "UTF-8" ))
56- .getBytes ("UTF-8" ); ;
47+ this .auth = String .format ("name=%s&password=%s" , URLEncoder .encode (username , "UTF-8" )
48+ , URLEncoder .encode (password , "UTF-8" ))
49+ .getBytes ("UTF-8" );
50+ ;
5751 } catch (UnsupportedEncodingException e ) {
5852 //all JVMs should support UTF-8, so this should not happen
5953 throw new RuntimeException (e );
6054 }
6155 }
6256
63-
57+ /**
58+ * Returns form encoded credentials to pass to _session.
59+ *
60+ * @param context interceptor context
61+ * @return form encoded credentials body payload
62+ */
6463 @ Override
65- public HttpConnectionInterceptorContext interceptResponse (HttpConnectionInterceptorContext
66- context ) {
67-
68- // Check if this interceptor is valid before attempting any kind of renewal
69- if (shouldAttemptCookieRequest .get ()) {
70-
71- HttpURLConnection connection = context .connection .getConnection ();
72-
73- // If we got a 401 or 403 we might need to renew the cookie
74- try {
75- boolean renewCookie = false ;
76- int statusCode = connection .getResponseCode ();
77-
78- if (statusCode == HttpURLConnection .HTTP_FORBIDDEN || statusCode ==
79- HttpURLConnection .HTTP_UNAUTHORIZED ) {
80- // Get the string value of the error stream
81- InputStream errorStream = connection .getErrorStream ();
82- String errorString = null ;
83- if (errorStream != null ) {
84- errorString = Utils .collectAndCloseStream (connection
85- .getErrorStream ());
86- logger .log (Level .FINE , String .format (Locale .ENGLISH , "Intercepted " +
87- "response %d %s" , statusCode , errorString ));
88- }
89- switch (statusCode ) {
90- case HttpURLConnection .HTTP_FORBIDDEN : //403
91- // Check if it was an expiry case
92- // Check using a regex to avoid dependency on a JSON library.
93- // Note (?siu) flags used for . to also match line breaks and for
94- // unicode
95- // case insensitivity.
96- if (errorString != null && errorString .matches ("(?siu)" +
97- ".*\\ \" error\\ \" \\ s*:\\ s*\\ \" credentials_expired\\ \" .*" )) {
98- // Was expired - set boolean to renew cookie
99- renewCookie = true ;
100- } else {
101- // Wasn't a credentials expired, throw exception
102- HttpConnectionInterceptorException toThrow = new
103- HttpConnectionInterceptorException (errorString );
104- // Set the flag for deserialization
105- toThrow .deserialize = errorString != null ;
106- throw toThrow ;
107- }
108- break ;
109- case HttpURLConnection .HTTP_UNAUTHORIZED : //401
110- // We need to get a new cookie
111- renewCookie = true ;
112- break ;
113- default :
114- break ;
115- }
64+ protected byte [] getSessionRequestPayload (HttpConnectionInterceptorContext context ) {
65+ return auth ;
66+ }
11667
117- if (renewCookie ) {
118- logger .finest ("Cookie was invalid. Will attempt to get new cookie." );
119- boolean success = requestCookie (context );
120- if (success ) {
121- // New cookie obtained, replay the request
122- context .replayRequest = true ;
123- } else {
124- // Didn't successfully renew, maybe creds are invalid
125- context .replayRequest = false ; // Don't replay
126- shouldAttemptCookieRequest .set (false ); // Set the flag to stop trying
127- }
128- }
68+ /**
69+ * Adds an additional check for HTTP 403 status codes with "credentials expired" messages that
70+ * are returned by some Cloudant versions.
71+ *
72+ * @param connection the connection to interrogate
73+ * @param statusCode the HTTP response status code
74+ * @return
75+ */
76+ @ Override
77+ protected boolean shouldRenew (HttpURLConnection connection , int statusCode ) {
78+ try {
79+ if (statusCode == HttpURLConnection .HTTP_FORBIDDEN ) {
80+ // Get the string value of the error stream
81+ InputStream errorStream = connection .getErrorStream ();
82+ String errorString = null ;
83+ if (errorStream != null ) {
84+ errorString = Utils .collectAndCloseStream (connection
85+ .getErrorStream ());
86+ logger .log (Level .FINE , String .format (Locale .ENGLISH , "Intercepted " +
87+ "response %d %s" , statusCode , errorString ));
88+ }
89+ // Check if it was an expiry case
90+ // Check using a regex to avoid dependency on a JSON library.
91+ // Note (?siu) flags used for . to also match line breaks and for
92+ // unicode
93+ // case insensitivity.
94+ if (errorString != null && errorString .matches ("(?siu)" +
95+ ".*\\ \" error\\ \" \\ s*:\\ s*\\ \" credentials_expired\\ \" .*" )) {
96+ // Was expired - renew cookie
97+ return true ;
12998 } else {
130- // Store any cookies provided on the response
131- storeCookiesFromResponse (connection );
99+ // Wasn't a credentials expired, throw exception
100+ HttpConnectionInterceptorException toThrow = new
101+ HttpConnectionInterceptorException (errorString , null );
102+ // Set the flag for deserialization
103+ toThrow .deserialize = errorString != null ;
104+ throw toThrow ;
132105 }
133- } catch (IOException e ) {
134- logger .log (Level .SEVERE , "Error reading response code or body from request" , e );
135106 }
107+ } catch (IOException e ) {
108+ throw wrapIOException ("Failed to read HTTP reponse code or body from" , connection , e );
136109 }
137- return context ;
138-
110+ return false ;
139111 }
140-
141112}
0 commit comments