View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   * http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.chemistry.opencmis.client.bindings.spi;
20  
21  import java.io.UnsupportedEncodingException;
22  import java.util.ArrayList;
23  import java.util.Collections;
24  import java.util.HashMap;
25  import java.util.List;
26  import java.util.Map;
27  import java.util.concurrent.locks.ReentrantReadWriteLock;
28  
29  import org.apache.chemistry.opencmis.client.bindings.spi.cookies.CmisCookieManager;
30  import org.apache.chemistry.opencmis.commons.SessionParameter;
31  import org.apache.chemistry.opencmis.commons.exceptions.CmisRuntimeException;
32  import org.apache.chemistry.opencmis.commons.impl.Base64;
33  import org.apache.chemistry.opencmis.commons.impl.DateTimeHelper;
34  import org.apache.chemistry.opencmis.commons.impl.IOUtils;
35  import org.apache.chemistry.opencmis.commons.impl.XMLUtils;
36  import org.w3c.dom.Document;
37  import org.w3c.dom.Element;
38  
39  /**
40   * Standard authentication provider class.
41   * 
42   * Adds a basic authentication HTTP header and a WS-Security UsernameToken SOAP
43   * header.
44   */
45  public class StandardAuthenticationProvider extends AbstractAuthenticationProvider {
46  
47      private static final long serialVersionUID = 1L;
48  
49      protected static final String WSSE_NAMESPACE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd";
50      protected static final String WSU_NAMESPACE = "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd";
51  
52      private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
53  
54      private CmisCookieManager cookieManager;
55      private Map<String, List<String>> fixedHeaders = new HashMap<String, List<String>>();
56      private String csrfHeader;
57      private String csrfValue = "fetch";
58  
59      @Override
60      public void setSession(BindingSession session) {
61          super.setSession(session);
62  
63          if (getHandleCookies() && cookieManager == null) {
64              cookieManager = new CmisCookieManager(session.getSessionId());
65          }
66  
67          boolean sendBasicAuth = getSendBasicAuth();
68  
69          // basic authentication
70          if (sendBasicAuth) {
71              // get user and password
72              String user = getUser();
73              String password = getPassword();
74  
75              // if no user is set, don't set basic auth header
76              if (user != null) {
77                  fixedHeaders.put("Authorization", createBasicAuthHeaderValue(user, password));
78              }
79          }
80  
81          boolean sendBearerToken = getSendBearerToken();
82  
83          // send bearer token
84          if (sendBearerToken) {
85              String token = getBearerToken();
86  
87              // if no token is set, don't set bearer header
88              if (token != null) {
89                  fixedHeaders.put("Authorization", Collections.singletonList("Bearer " + token));
90              }
91          }
92  
93          // proxy authentication
94          if (getProxyUser() != null) {
95              // get proxy user and password
96              String proxyUser = getProxyUser();
97              String proxyPassword = getProxyPassword();
98  
99              fixedHeaders.put("Proxy-Authorization", createBasicAuthHeaderValue(proxyUser, proxyPassword));
100         }
101 
102         // CSRF header
103         csrfHeader = getCsrfHeader();
104 
105         // other headers
106         addSessionParameterHeadersToFixedHeaders();
107     }
108 
109     @Override
110     public Map<String, List<String>> getHTTPHeaders(String url) {
111         Map<String, List<String>> result = new HashMap<String, List<String>>(fixedHeaders);
112 
113         lock.readLock().lock();
114         try {
115             // cookies
116             if (cookieManager != null) {
117                 Map<String, List<String>> cookies = cookieManager.get(url, result);
118                 if (!cookies.isEmpty()) {
119                     result.putAll(cookies);
120                 }
121             }
122 
123             // CSRF header
124             if (csrfHeader != null && csrfValue != null) {
125                 result.put(csrfHeader, Collections.singletonList(csrfValue));
126             }
127         } finally {
128             lock.readLock().unlock();
129         }
130 
131         return result.isEmpty() ? null : result;
132     }
133 
134     @Override
135     public void putResponseHeaders(String url, int statusCode, Map<String, List<String>> headers) {
136         lock.writeLock().lock();
137         try {
138             // cookies
139             if (cookieManager != null) {
140                 if (headers != null && headers.size() > 0) {
141                     cookieManager.put(url, headers);
142                 }
143             }
144 
145             // CSRF header
146             if (csrfHeader != null) {
147                 for (Map.Entry<String, List<String>> header : headers.entrySet()) {
148                     if (csrfHeader.equalsIgnoreCase(header.getKey())) {
149                         List<String> values = header.getValue();
150                         if (values != null && values.size() > 0) {
151                             csrfValue = values.get(0);
152                         }
153                         break;
154                     }
155                 }
156             }
157         } finally {
158             lock.writeLock().unlock();
159         }
160     }
161 
162     @Override
163     public Element getSOAPHeaders(Object portObject) {
164         // only send SOAP header if configured
165         if (!getSendUsernameToken()) {
166             return null;
167         }
168 
169         // get user and password
170         String user = getUser();
171         String password = getPassword();
172 
173         // if no user is set, don't create SOAP header
174         if (user == null) {
175             return null;
176         }
177 
178         if (password == null) {
179             password = "";
180         }
181 
182         // set time
183         long created = System.currentTimeMillis();
184         long expires = created + 24 * 60 * 60 * 1000; // 24 hours
185 
186         // create the SOAP header
187         try {
188             Document document = XMLUtils.newDomDocument();
189 
190             Element wsseSecurityElement = document.createElementNS(WSSE_NAMESPACE, "Security");
191 
192             Element wsuTimestampElement = document.createElementNS(WSU_NAMESPACE, "Timestamp");
193             wsseSecurityElement.appendChild(wsuTimestampElement);
194 
195             Element tsCreatedElement = document.createElementNS(WSU_NAMESPACE, "Created");
196             tsCreatedElement.appendChild(document.createTextNode(DateTimeHelper.formatXmlDateTime(created)));
197             wsuTimestampElement.appendChild(tsCreatedElement);
198 
199             Element tsExpiresElement = document.createElementNS(WSU_NAMESPACE, "Expires");
200             tsExpiresElement.appendChild(document.createTextNode(DateTimeHelper.formatXmlDateTime(expires)));
201             wsuTimestampElement.appendChild(tsExpiresElement);
202 
203             Element usernameTokenElement = document.createElementNS(WSSE_NAMESPACE, "UsernameToken");
204             wsseSecurityElement.appendChild(usernameTokenElement);
205 
206             Element usernameElement = document.createElementNS(WSSE_NAMESPACE, "Username");
207             usernameElement.appendChild(document.createTextNode(user));
208             usernameTokenElement.appendChild(usernameElement);
209 
210             Element passwordElement = document.createElementNS(WSSE_NAMESPACE, "Password");
211             passwordElement.appendChild(document.createTextNode(password));
212             passwordElement.setAttribute("Type",
213                     "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText");
214             usernameTokenElement.appendChild(passwordElement);
215 
216             Element createdElement = document.createElementNS(WSU_NAMESPACE, "Created");
217             createdElement.appendChild(document.createTextNode(DateTimeHelper.formatXmlDateTime(created)));
218             usernameTokenElement.appendChild(createdElement);
219 
220             return wsseSecurityElement;
221         } catch (Exception e) {
222             // shouldn't happen...
223             throw new CmisRuntimeException("Could not build SOAP header: " + e.getMessage(), e);
224         }
225     }
226 
227     /**
228      * Returns the HTTP headers that are sent with all requests. The returned
229      * map is mutable but not synchronized!
230      */
231     protected Map<String, List<String>> getFixedHeaders() {
232         return fixedHeaders;
233     }
234 
235     /**
236      * Adds the {@link SessionParameter#HEADER} to the fixed headers. This
237      * method should only be called from the {@link #setSession(BindingSession)}
238      * method to avoid threading issues.
239      */
240     protected void addSessionParameterHeadersToFixedHeaders() {
241         int x = 0;
242         Object headerParam;
243         while ((headerParam = getSession().get(SessionParameter.HEADER + "." + x)) != null) {
244             String header = headerParam.toString();
245             int colon = header.indexOf(':');
246             if (colon > -1) {
247                 String key = header.substring(0, colon).trim();
248                 if (key.length() > 0) {
249                     String value = header.substring(colon + 1).trim();
250                     List<String> values = fixedHeaders.get(key);
251                     if (values == null) {
252                         fixedHeaders.put(key, Collections.singletonList(value));
253                     } else {
254                         List<String> newValues = new ArrayList<String>(values);
255                         newValues.add(value);
256                         fixedHeaders.put(key, newValues);
257                     }
258                 }
259             }
260             x++;
261         }
262     }
263 
264     /**
265      * Creates a basic authentication header value from a username and a
266      * password.
267      */
268     protected List<String> createBasicAuthHeaderValue(String username, String password) {
269         if (password == null) {
270             password = "";
271         }
272 
273         Object charset = getSession().get(SessionParameter.AUTH_HTTP_BASIC_CHARSET);
274         if (charset instanceof String) {
275             charset = ((String) charset).trim();
276         } else {
277             charset = IOUtils.UTF8;
278         }
279 
280         byte[] usernamePassword;
281         try {
282             usernamePassword = (username + ":" + password).getBytes((String) charset);
283         } catch (UnsupportedEncodingException e) {
284             throw new CmisRuntimeException("Unsupported encoding '" + charset + "'!", e);
285         }
286 
287         return Collections.singletonList("Basic " + Base64.encodeBytes(usernamePassword));
288     }
289 
290     /**
291      * Returns if a HTTP Basic Authentication header should be sent. (All
292      * bindings.)
293      */
294     protected boolean getSendBasicAuth() {
295         return getSession().get(SessionParameter.AUTH_HTTP_BASIC, false);
296     }
297 
298     /**
299      * Returns if an OAuth Bearer token header should be sent. (All bindings.)
300      */
301     protected boolean getSendBearerToken() {
302         return getSession().get(SessionParameter.AUTH_OAUTH_BEARER, false);
303     }
304 
305     /**
306      * Returns if a UsernameToken should be sent. (Web Services binding only.)
307      */
308     protected boolean getSendUsernameToken() {
309         return getSession().get(SessionParameter.AUTH_SOAP_USERNAMETOKEN, false);
310     }
311 
312     /**
313      * Returns if the authentication provider should handle cookies.
314      */
315     protected boolean getHandleCookies() {
316         Object value = getSession().get(SessionParameter.COOKIES);
317 
318         if (value instanceof Boolean) {
319             return ((Boolean) value).booleanValue();
320         } else if (value instanceof String) {
321             return Boolean.parseBoolean((String) value);
322         } else if (value == null) {
323             return getCsrfHeader() != null;
324         }
325 
326         return false;
327     }
328 }