View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.portlet;
18  
19  import java.io.IOException;
20  import java.security.AccessControlContext;
21  import java.security.AccessController;
22  import java.util.HashMap;
23  import java.util.StringTokenizer;
24  
25  import javax.portlet.ActionRequest;
26  import javax.portlet.ActionResponse;
27  import javax.portlet.PortletConfig;
28  import javax.portlet.PortletContext;
29  import javax.portlet.PortletException;
30  import javax.portlet.PortletMode;
31  import javax.portlet.PortletPreferences;
32  import javax.portlet.RenderRequest;
33  import javax.portlet.RenderResponse;
34  import javax.security.auth.Subject;
35  
36  import org.apache.commons.codec.binary.Base64;
37  import org.apache.commons.httpclient.HttpClient;
38  import org.apache.commons.httpclient.HttpMethod;
39  import org.apache.commons.httpclient.NameValuePair;
40  import org.apache.commons.httpclient.UsernamePasswordCredentials;
41  import org.apache.commons.httpclient.auth.AuthScope;
42  import org.apache.commons.httpclient.auth.AuthState;
43  import org.apache.commons.httpclient.auth.BasicScheme;
44  import org.apache.commons.httpclient.methods.PostMethod;
45  import org.apache.commons.logging.Log;
46  import org.apache.commons.logging.LogFactory;
47  import org.apache.jetspeed.rewriter.WebContentRewriter;
48  import org.apache.jetspeed.security.JSSubject;
49  import org.apache.jetspeed.sso.SSOContext;
50  import org.apache.jetspeed.sso.SSOException;
51  import org.apache.jetspeed.sso.SSOProvider;
52  import org.apache.portals.messaging.PortletMessaging;
53  
54  
55  /***
56   * SSOWebContentPortlet
57   * 
58   * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
59   * @version $Id: SSOWebContentPortlet.java 605431 2007-12-19 05:11:40Z taylor $
60   */
61  public class SSOWebContentPortlet extends WebContentPortlet
62  {
63      // Constants
64      
65      // sso.type
66      public static final String SSO_TYPE = "sso.type";
67      
68      public static final String SSO_TYPE_HTTP = "http";                          // BOZO - depricate in favor of 'basic'
69      public static final String SSO_TYPE_BASIC = "basic";          
70      public static final String SSO_TYPE_BASIC_PREEMPTIVE = "basic.preemptive";
71      
72      public static final String SSO_TYPE_FORM = "form";
73      public static final String SSO_TYPE_FORM_GET = "form.get";
74      public static final String SSO_TYPE_FORM_POST = "form.post";
75      
76      public static final String SSO_TYPE_URL = "url";
77      public static final String SSO_TYPE_URL_BASE64 = "url.base64";
78      
79      public static final String SSO_TYPE_CERTIFICATE = "certificate";
80      
81      public static final String SSO_TYPE_DEFAULT = SSO_TYPE_BASIC;  // handled well even if nothing but credentials are set (see: doRequestedAuthentication)
82      
83      // ...standardized auth types
84      
85      public static final String BASIC_AUTH_SCHEME_NAME = (new BasicScheme()).getSchemeName();
86  
87      // supporting parameters - for various sso types
88      
89      // ...names of query args for sso.type=url|url.base64
90      
91      public static final String SSO_TYPE_URL_USERNAME_PARAM = "sso.url.Principal";
92      public static final String SSO_TYPE_URL_PASSWORD_PARAM = "sso.url.Credential";
93      
94      // ...names of fields for sso.type=form|form.get|form.post
95      
96      public static final String SSO_TYPE_FORM_ACTION_URL = "sso.form.Action";
97      public static final String SSO_TYPE_FORM_ACTION_ARGS = "sso.form.Args";
98      public static final String SSO_TYPE_FORM_USERNAME_FIELD = "sso.form.Principal";
99      public static final String SSO_TYPE_FORM_PASSWORD_FIELD = "sso.form.Credential";
100     
101     // ...tags for passing creditials along on the current request object
102     
103     public static final String SSO_REQUEST_ATTRIBUTE_USERNAME = "sso.ra.username";
104     public static final String SSO_REQUEST_ATTRIBUTE_PASSWORD = "sso.ra.password";
105     
106     // ...field names for EDIT mode
107     
108     public static final String SSO_EDIT_FIELD_PRINCIPAL = "ssoPrincipal";
109     public static final String SSO_EDIT_FIELD_CREDENTIAL = "ssoCredential";
110     
111     // SSOWebContent session variables 
112 
113     public static final String FORM_AUTH_STATE = "ssowebcontent.form.authstate" ;
114     
115     
116     // Class Data
117     
118     protected final static Log log = LogFactory.getLog(SSOWebContentPortlet.class);
119     
120     
121     // Data Members
122     
123     protected PortletContext context;
124     protected SSOProvider sso;
125     
126     
127     // Methods
128 
129     public void init(PortletConfig config) throws PortletException
130     {
131         super.init(config);
132         context = getPortletContext();
133         sso = (SSOProvider)context.getAttribute("cps:SSO");
134         if (null == sso)
135         {
136            throw new PortletException("Failed to find SSO Provider on portlet initialization");
137         }        
138     }
139     
140     public void processAction(ActionRequest actionRequest, ActionResponse actionResponse)
141     throws PortletException, IOException
142     {
143         // grab parameters - they will be cleared in processing of edit response
144         String webContentParameter = actionRequest.getParameter(WebContentRewriter.ACTION_PARAMETER_URL);
145         String ssoPrincipal = actionRequest.getParameter(SSO_EDIT_FIELD_PRINCIPAL);
146         String ssoCredential = actionRequest.getParameter(SSO_EDIT_FIELD_CREDENTIAL);        
147 
148         // save the prefs
149         super.processAction(actionRequest, actionResponse);
150   
151         // process credentials
152         if (webContentParameter == null || actionRequest.getPortletMode() == PortletMode.EDIT)            
153         {
154             // processPreferencesAction(request, actionResponse);
155             // get the POST params -- requires HTML post params named above 
156             String site = actionRequest.getPreferences().getValue("SRC", "");
157             
158             try
159             {
160                 Subject subject = getSubject();
161                 if (sso.hasSSOCredentials(subject, site))
162                 {
163                     SSOContext context = sso.getCredentials(subject, site);
164                     if (!context.getRemotePrincipalName().equals(ssoPrincipal))
165                     {
166                         sso.removeCredentialsForSite(subject, site);
167                         sso.addCredentialsForSite(subject, ssoPrincipal, site, ssoCredential);
168                     }
169                     else
170                     {
171                         sso.updateCredentialsForSite(subject, ssoPrincipal, site, ssoCredential);
172                     }
173                 }
174                 else
175                 {
176                     sso.addCredentialsForSite(subject, ssoPrincipal, site, ssoCredential);
177                 }
178             }
179             catch (SSOException e)
180             {
181                 throw new PortletException(e);
182             }
183         }
184     }
185     
186     public void doView(RenderRequest request, RenderResponse response)
187     throws PortletException, IOException
188     {
189         String site = request.getPreferences().getValue("SRC", null);
190 
191         if (site == null)
192         {
193             // no SRC configured in prefs - switch to SSO Configure View
194             request.setAttribute(PARAM_VIEW_PAGE, this.getPortletConfig().getInitParameter(PARAM_EDIT_PAGE));
195             setupPreferencesEdit(request, response);
196         }
197         else try
198         {
199             Subject subject = getSubject();                 
200             SSOContext context = sso.getCredentials(subject, site);
201             request.setAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME, context.getRemotePrincipalName());
202             request.setAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD, context.getRemoteCredential());
203         }
204         catch (SSOException e)
205         {
206             if (e.getMessage().equals(SSOException.NO_CREDENTIALS_FOR_SITE))
207             {
208                 // no credentials configured in SSO store
209                 // switch to SSO Configure View
210                 request.setAttribute(PARAM_VIEW_PAGE, this.getPortletConfig().getInitParameter(PARAM_EDIT_PAGE));
211                 setupPreferencesEdit(request, response);    
212             }
213             else
214             {
215                 throw new PortletException(e);
216             }
217         }        
218         
219         super.doView(request, response);
220     }
221     
222 
223     public void doEdit(RenderRequest request, RenderResponse response)
224     throws PortletException, IOException
225     {
226         try
227         {
228             Subject subject = getSubject();                 
229             String site = request.getPreferences().getValue("SRC", "");
230             SSOContext context = sso.getCredentials(subject, site);
231             getContext(request).put(SSO_EDIT_FIELD_PRINCIPAL, context.getRemotePrincipalName());
232             getContext(request).put(SSO_EDIT_FIELD_CREDENTIAL, context.getRemoteCredential());
233         }
234         catch (SSOException e)
235         {
236             if (e.getMessage().equals(SSOException.NO_CREDENTIALS_FOR_SITE))
237             {
238                 // no credentials configured in SSO store
239                 // switch to SSO Configure View
240                 getContext(request).put(SSO_EDIT_FIELD_PRINCIPAL, "");
241                 getContext(request).put(SSO_EDIT_FIELD_CREDENTIAL, "");
242             }
243             else
244             {
245                 throw new PortletException(e);
246             }
247         }        
248         
249         super.doEdit(request, response);
250     }
251 
252     private Subject getSubject()
253     {
254         AccessControlContext context = AccessController.getContext();
255         return JSSubject.getSubject(context);         
256     }
257     
258     protected byte[] doPreemptiveAuthentication(HttpClient client,HttpMethod method, RenderRequest request, RenderResponse response)
259     {
260     	byte[] result = super.doPreemptiveAuthentication(client, method, request, response);
261         if ( result != null)
262         {
263             // already handled
264             return result ;
265         }
266         
267         // System.out.println("SSOWebContentPortlet.doPreemptiveAuthentication...");
268         
269         PortletPreferences prefs = request.getPreferences();
270         String type = getSingleSignOnAuthType(prefs);
271 
272         if (type.equalsIgnoreCase(SSO_TYPE_BASIC_PREEMPTIVE))
273         {
274             // Preemptive, basic authentication
275             String userName = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
276             if (userName == null) userName = "";
277             String password = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
278             if (password == null) password = "";
279             
280             // System.out.println("...performing preemptive basic authentication with userName: "+userName+", and password: "+password);
281             method.setDoAuthentication(true);
282             method.getHostAuthState().setPreemptive();
283             client.getState().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));
284             
285             // handled!
286             return result ;
287             
288         }
289         else if (type.startsWith(SSO_TYPE_FORM))
290         {
291             try
292             {
293                 Boolean formAuth = (Boolean)PortletMessaging.receive(request, FORM_AUTH_STATE);
294                 if (formAuth != null)
295                 {
296                     // already been here, done that
297                     return (formAuth.booleanValue() ? result : null);
298                 }
299                 else
300                 {
301                     // stop recursion, but assume failure, ...for now
302                     PortletMessaging.publish(request, FORM_AUTH_STATE, Boolean.FALSE);
303                 }
304 
305                 String formAction = prefs.getValue(SSO_TYPE_FORM_ACTION_URL, "");
306                 if (formAction == null || formAction.length() == 0)
307                 {
308                     log.warn("sso.type specified as 'form', but no: "+SSO_TYPE_FORM_ACTION_URL+", action was specified - unable to preemptively authenticate by form.");
309                     return null ;
310                 }
311                 String userNameField = prefs.getValue(SSO_TYPE_FORM_USERNAME_FIELD, "");
312                 if (userNameField == null || userNameField.length() == 0)
313                 {
314                     log.warn("sso.type specified as 'form', but no: "+SSO_TYPE_FORM_USERNAME_FIELD+", username field was specified - unable to preemptively authenticate by form.");
315                     return null ;
316                 }
317                 String passwordField = prefs.getValue(SSO_TYPE_FORM_PASSWORD_FIELD, "password");
318                 if (passwordField == null || passwordField.length() == 0)
319                 {
320                     log.warn("sso.type specified as 'form', but no: "+SSO_TYPE_FORM_PASSWORD_FIELD+", password field was specified - unable to preemptively authenticate by form.");
321                     return null ;
322                 }
323                 
324                 String userName = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
325                 if (userName == null) userName = "";
326                 String password = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
327                 if (password == null) password = "";
328 
329                 // get submit method
330                 int i = type.indexOf('.');
331                 boolean isPost = i > 0 ? type.substring(i+1).equalsIgnoreCase("post") : true ;    // default to post, since it is a form 
332             
333                 // get parameter map
334                 HashMap formParams = new HashMap();
335                 formParams.put(userNameField,new String[]{ userName });
336                 formParams.put(passwordField,new String[]{ password });
337                 String formArgs = prefs.getValue(SSO_TYPE_FORM_ACTION_ARGS, "");
338                 if (formArgs != null && formArgs.length() > 0)
339                 {
340                     StringTokenizer iter = new StringTokenizer(formArgs, ";");
341                     while (iter.hasMoreTokens())
342                     {
343                         String pair = iter.nextToken();
344                         i = pair.indexOf('=') ;
345                         if (i > 0)
346                             formParams.put(pair.substring(0,i), new String[]{pair.substring(i+1)});
347                     }
348                 }
349 
350                 // resuse client - in case new cookies get set - but create a new method (for the formAction)
351                 String formMethod = (isPost) ? FORM_POST_METHOD : FORM_GET_METHOD;                
352                 method = getHttpMethod(client, getURLSource(formAction, formParams, request, response), formParams, formMethod, request);
353                 // System.out.println("...posting credentials");
354                 result = doHttpWebContent(client, method, 0, request, response) ;
355                 // System.out.println("Result of attempted authorization: "+success);
356                 PortletMessaging.publish(request, FORM_AUTH_STATE, Boolean.valueOf(result != null));
357                 return result ;
358             }
359             catch (Exception ex)
360             {
361                 // bad
362                 log.error("Form-based authentication failed", ex);
363             }
364         }
365         else if (type.equalsIgnoreCase(SSO_TYPE_URL) || type.equalsIgnoreCase(SSO_TYPE_URL_BASE64))
366         {
367             // set user name and password parameters in the HttpMethod
368             String userNameParam = prefs.getValue(SSO_TYPE_URL_USERNAME_PARAM, "");
369             if (userNameParam == null || userNameParam.length() == 0)
370             {
371                 log.warn("sso.type specified as 'url', but no: "+SSO_TYPE_URL_USERNAME_PARAM+", username parameter was specified - unable to preemptively authenticate by URL.");
372                 return null ;
373             }
374             String passwordParam = prefs.getValue(SSO_TYPE_URL_PASSWORD_PARAM, "");
375             if (passwordParam == null || passwordParam.length() == 0)
376             {
377                 log.warn("sso.type specified as 'url', but no: "+SSO_TYPE_URL_PASSWORD_PARAM+", password parameter was specified - unable to preemptively authenticate by URL.");
378                 return null ;
379             }
380             String userName = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
381             if (userName == null) userName = "";
382             String password = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
383             if (password == null) password = "";
384             if (type.equalsIgnoreCase(SSO_TYPE_URL_BASE64))
385             {
386                 Base64 encoder = new Base64() ;
387                 userName = new String(encoder.encode(userName.getBytes()));
388                 password = new String(encoder.encode(password.getBytes()));
389             }
390             
391             // GET and POST accept args differently
392             if ( method instanceof PostMethod )
393             {
394                 // add POST data
395                 PostMethod postMethod = (PostMethod)method ;
396                 postMethod.addParameter(userNameParam, userName);
397                 postMethod.addParameter(passwordParam, password);
398             }
399             else
400             {
401                 // augment GET query string
402                 NameValuePair[] authPairs = new NameValuePair[]{ new NameValuePair(userNameParam, userName), new NameValuePair(passwordParam, password) } ; 
403                 String existingQuery = method.getQueryString() ;
404                 method.setQueryString(authPairs);
405                 if (existingQuery != null && existingQuery.length() > 0)
406                 {
407                     // augment existing query with new auth query
408                     existingQuery = existingQuery + '&' + method.getQueryString();
409                     method.setQueryString(existingQuery);
410                 }
411             }
412             
413             return result ;
414         }
415         // else System.out.println("...sso.type: "+type+", no pre-emptive authentication");
416         
417         // not handled
418         return null ;
419     }
420 
421     protected boolean doRequestedAuthentication(HttpClient client,HttpMethod method, RenderRequest request, RenderResponse response)
422     {
423         if ( super.doRequestedAuthentication(client, method, request, response))
424         {
425             // already handled
426             return true ;
427         }
428         
429         // System.out.println("SSOWebContentPortlet.doRequestedAuthentication...");
430         
431         if (method.getHostAuthState().getAuthScheme().getSchemeName().equals(BASIC_AUTH_SCHEME_NAME))
432         {
433             // Basic authentication being requested
434             String userName = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
435             if (userName == null) userName = "";
436             String password = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
437             if (password == null) password = "";
438             
439             // System.out.println("...providing basic authentication with userName: "+userName+", and password: "+password);
440             method.setDoAuthentication(true);
441             AuthState state = method.getHostAuthState();
442             AuthScope scope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, state.getRealm(), state.getAuthScheme().getSchemeName()) ;
443             client.getState().setCredentials(scope, new UsernamePasswordCredentials(userName, password));
444             
445             // handled!
446             return true ;
447         }
448         else
449         {
450             log.warn("SSOWebContentPortlent.doAuthenticate() - unexpected authentication scheme: "+method.getHostAuthState().getAuthScheme().getSchemeName());
451         }
452 
453         // only know how to handle Basic authentication, in this context
454         return false;
455     }
456     
457     protected String getSingleSignOnAuthType(PortletPreferences prefs)
458     {
459         String type = prefs.getValue(SSO_TYPE,SSO_TYPE_DEFAULT);
460         
461         if (type != null && type.equalsIgnoreCase(SSO_TYPE_HTTP))
462         {
463             log.warn("sso.type: "+SSO_TYPE_HTTP+", has been deprecated - use: "+SSO_TYPE_BASIC+", or: "+SSO_TYPE_BASIC_PREEMPTIVE);
464             type = SSO_TYPE_BASIC ;
465         }
466         
467         return type ;
468     }
469 }