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 516448 2007-03-09 16:25:47Z ate $
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     private PortletContext context;
124     private 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                     sso.updateCredentialsForSite(getSubject(), ssoPrincipal, site, ssoCredential);
164                 }
165                 else
166                 {
167                     sso.addCredentialsForSite(getSubject(), ssoPrincipal, site, ssoCredential);
168                 }
169             }
170             catch (SSOException e)
171             {
172                 throw new PortletException(e);
173             }
174         }
175     }
176     
177     public void doView(RenderRequest request, RenderResponse response)
178     throws PortletException, IOException
179     {
180         String site = request.getPreferences().getValue("SRC", null);
181 
182         if (site == null)
183         {
184             // no SRC configured in prefs - switch to SSO Configure View
185             request.setAttribute(PARAM_VIEW_PAGE, this.getPortletConfig().getInitParameter(PARAM_EDIT_PAGE));
186             setupPreferencesEdit(request, response);
187         }
188         else try
189         {
190             Subject subject = getSubject();                 
191             SSOContext context = sso.getCredentials(subject, site);
192             request.setAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME, context.getRemotePrincipalName());
193             request.setAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD, context.getRemoteCredential());
194         }
195         catch (SSOException e)
196         {
197             if (e.getMessage().equals(SSOException.NO_CREDENTIALS_FOR_SITE))
198             {
199                 // no credentials configured in SSO store
200                 // switch to SSO Configure View
201                 request.setAttribute(PARAM_VIEW_PAGE, this.getPortletConfig().getInitParameter(PARAM_EDIT_PAGE));
202                 setupPreferencesEdit(request, response);    
203             }
204             else
205             {
206                 throw new PortletException(e);
207             }
208         }        
209         
210         super.doView(request, response);
211     }
212     
213 
214     public void doEdit(RenderRequest request, RenderResponse response)
215     throws PortletException, IOException
216     {
217         try
218         {
219             Subject subject = getSubject();                 
220             String site = request.getPreferences().getValue("SRC", "");
221             SSOContext context = sso.getCredentials(subject, site);
222             getContext(request).put(SSO_EDIT_FIELD_PRINCIPAL, context.getRemotePrincipalName());
223             getContext(request).put(SSO_EDIT_FIELD_CREDENTIAL, context.getRemoteCredential());
224         }
225         catch (SSOException e)
226         {
227             if (e.getMessage().equals(SSOException.NO_CREDENTIALS_FOR_SITE))
228             {
229                 // no credentials configured in SSO store
230                 // switch to SSO Configure View
231                 getContext(request).put(SSO_EDIT_FIELD_PRINCIPAL, "");
232                 getContext(request).put(SSO_EDIT_FIELD_CREDENTIAL, "");
233             }
234             else
235             {
236                 throw new PortletException(e);
237             }
238         }        
239         
240         super.doEdit(request, response);
241     }
242 
243     private Subject getSubject()
244     {
245         AccessControlContext context = AccessController.getContext();
246         return JSSubject.getSubject(context);         
247     }
248     
249     protected byte[] doPreemptiveAuthentication(HttpClient client,HttpMethod method, RenderRequest request, RenderResponse response)
250     {
251     	byte[] result = super.doPreemptiveAuthentication(client, method, request, response);
252         if ( result != null)
253         {
254             // already handled
255             return result ;
256         }
257         
258         // System.out.println("SSOWebContentPortlet.doPreemptiveAuthentication...");
259         
260         PortletPreferences prefs = request.getPreferences();
261         String type = getSingleSignOnAuthType(prefs);
262 
263         if (type.equalsIgnoreCase(SSO_TYPE_BASIC_PREEMPTIVE))
264         {
265             // Preemptive, basic authentication
266             String userName = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
267             if (userName == null) userName = "";
268             String password = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
269             if (password == null) password = "";
270             
271             // System.out.println("...performing preemptive basic authentication with userName: "+userName+", and password: "+password);
272             method.setDoAuthentication(true);
273             method.getHostAuthState().setPreemptive();
274             client.getState().setCredentials(AuthScope.ANY, new UsernamePasswordCredentials(userName, password));
275             
276             // handled!
277             return result ;
278             
279         }
280         else if (type.startsWith(SSO_TYPE_FORM))
281         {
282             try
283             {
284                 Boolean formAuth = (Boolean)PortletMessaging.receive(request, FORM_AUTH_STATE);
285                 if (formAuth != null)
286                 {
287                     // already been here, done that
288                     return (formAuth.booleanValue() ? result : null);
289                 }
290                 else
291                 {
292                     // stop recursion, but assume failure, ...for now
293                     PortletMessaging.publish(request, FORM_AUTH_STATE, Boolean.FALSE);
294                 }
295 
296                 String formAction = prefs.getValue(SSO_TYPE_FORM_ACTION_URL, "");
297                 if (formAction == null || formAction.length() == 0)
298                 {
299                     log.warn("sso.type specified as 'form', but no: "+SSO_TYPE_FORM_ACTION_URL+", action was specified - unable to preemptively authenticate by form.");
300                     return null ;
301                 }
302                 String userNameField = prefs.getValue(SSO_TYPE_FORM_USERNAME_FIELD, "");
303                 if (userNameField == null || userNameField.length() == 0)
304                 {
305                     log.warn("sso.type specified as 'form', but no: "+SSO_TYPE_FORM_USERNAME_FIELD+", username field was specified - unable to preemptively authenticate by form.");
306                     return null ;
307                 }
308                 String passwordField = prefs.getValue(SSO_TYPE_FORM_PASSWORD_FIELD, "password");
309                 if (passwordField == null || passwordField.length() == 0)
310                 {
311                     log.warn("sso.type specified as 'form', but no: "+SSO_TYPE_FORM_PASSWORD_FIELD+", password field was specified - unable to preemptively authenticate by form.");
312                     return null ;
313                 }
314                 
315                 String userName = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
316                 if (userName == null) userName = "";
317                 String password = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
318                 if (password == null) password = "";
319 
320                 // get submit method
321                 int i = type.indexOf('.');
322                 boolean isPost = i > 0 ? type.substring(i+1).equalsIgnoreCase("post") : true ;    // default to post, since it is a form 
323             
324                 // get parameter map
325                 HashMap formParams = new HashMap();
326                 formParams.put(userNameField,new String[]{ userName });
327                 formParams.put(passwordField,new String[]{ password });
328                 String formArgs = prefs.getValue(SSO_TYPE_FORM_ACTION_ARGS, "");
329                 if (formArgs != null && formArgs.length() > 0)
330                 {
331                     StringTokenizer iter = new StringTokenizer(formArgs, ";");
332                     while (iter.hasMoreTokens())
333                     {
334                         String pair = iter.nextToken();
335                         i = pair.indexOf('=') ;
336                         if (i > 0)
337                             formParams.put(pair.substring(0,i), new String[]{pair.substring(i+1)});
338                     }
339                 }
340 
341                 // resuse client - in case new cookies get set - but create a new method (for the formAction)
342                 method = getHttpMethod(client, getURLSource(formAction, formParams, request, response), formParams, isPost, request);
343                 // System.out.println("...posting credentials");
344                 result = doHttpWebContent(client, method, 0, request, response) ;
345                 // System.out.println("Result of attempted authorization: "+success);
346                 PortletMessaging.publish(request, FORM_AUTH_STATE, Boolean.valueOf(result != null));
347                 return result ;
348             }
349             catch (Exception ex)
350             {
351                 // bad
352                 log.error("Form-based authentication failed", ex);
353             }
354         }
355         else if (type.equalsIgnoreCase(SSO_TYPE_URL) || type.equalsIgnoreCase(SSO_TYPE_URL_BASE64))
356         {
357             // set user name and password parameters in the HttpMethod
358             String userNameParam = prefs.getValue(SSO_TYPE_URL_USERNAME_PARAM, "");
359             if (userNameParam == null || userNameParam.length() == 0)
360             {
361                 log.warn("sso.type specified as 'url', but no: "+SSO_TYPE_URL_USERNAME_PARAM+", username parameter was specified - unable to preemptively authenticate by URL.");
362                 return null ;
363             }
364             String passwordParam = prefs.getValue(SSO_TYPE_URL_PASSWORD_PARAM, "");
365             if (passwordParam == null || passwordParam.length() == 0)
366             {
367                 log.warn("sso.type specified as 'url', but no: "+SSO_TYPE_URL_PASSWORD_PARAM+", password parameter was specified - unable to preemptively authenticate by URL.");
368                 return null ;
369             }
370             String userName = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
371             if (userName == null) userName = "";
372             String password = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
373             if (password == null) password = "";
374             if (type.equalsIgnoreCase(SSO_TYPE_URL_BASE64))
375             {
376                 Base64 encoder = new Base64() ;
377                 userName = new String(encoder.encode(userName.getBytes()));
378                 password = new String(encoder.encode(password.getBytes()));
379             }
380             
381             // GET and POST accept args differently
382             if ( method instanceof PostMethod )
383             {
384                 // add POST data
385                 PostMethod postMethod = (PostMethod)method ;
386                 postMethod.addParameter(userNameParam, userName);
387                 postMethod.addParameter(passwordParam, password);
388             }
389             else
390             {
391                 // augment GET query string
392                 NameValuePair[] authPairs = new NameValuePair[]{ new NameValuePair(userNameParam, userName), new NameValuePair(passwordParam, password) } ; 
393                 String existingQuery = method.getQueryString() ;
394                 method.setQueryString(authPairs);
395                 if (existingQuery != null && existingQuery.length() > 0)
396                 {
397                     // augment existing query with new auth query
398                     existingQuery = existingQuery + '&' + method.getQueryString();
399                     method.setQueryString(existingQuery);
400                 }
401             }
402             
403             return result ;
404         }
405         // else System.out.println("...sso.type: "+type+", no pre-emptive authentication");
406         
407         // not handled
408         return null ;
409     }
410 
411     protected boolean doRequestedAuthentication(HttpClient client,HttpMethod method, RenderRequest request, RenderResponse response)
412     {
413         if ( super.doRequestedAuthentication(client, method, request, response))
414         {
415             // already handled
416             return true ;
417         }
418         
419         // System.out.println("SSOWebContentPortlet.doRequestedAuthentication...");
420         
421         if (method.getHostAuthState().getAuthScheme().getSchemeName().equals(BASIC_AUTH_SCHEME_NAME))
422         {
423             // Basic authentication being requested
424             String userName = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
425             if (userName == null) userName = "";
426             String password = (String)request.getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
427             if (password == null) password = "";
428             
429             // System.out.println("...providing basic authentication with userName: "+userName+", and password: "+password);
430             method.setDoAuthentication(true);
431             AuthState state = method.getHostAuthState();
432             AuthScope scope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT, state.getRealm(), state.getAuthScheme().getSchemeName()) ;
433             client.getState().setCredentials(scope, new UsernamePasswordCredentials(userName, password));
434             
435             // handled!
436             return true ;
437         }
438         else
439         {
440             log.warn("SSOWebContentPortlent.doAuthenticate() - unexpected authentication scheme: "+method.getHostAuthState().getAuthScheme().getSchemeName());
441         }
442 
443         // only know how to handle Basic authentication, in this context
444         return false;
445     }
446     
447     protected String getSingleSignOnAuthType(PortletPreferences prefs)
448     {
449         String type = prefs.getValue(SSO_TYPE,SSO_TYPE_DEFAULT);
450         
451         if (type != null && type.equalsIgnoreCase(SSO_TYPE_HTTP))
452         {
453             log.warn("sso.type: "+SSO_TYPE_HTTP+", has been deprecated - use: "+SSO_TYPE_BASIC+", or: "+SSO_TYPE_BASIC_PREEMPTIVE);
454             type = SSO_TYPE_BASIC ;
455         }
456         
457         return type ;
458     }
459 }