001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: package org.apache.jetspeed.portlet;
018:
019: import java.io.IOException;
020: import java.security.AccessControlContext;
021: import java.security.AccessController;
022: import java.util.HashMap;
023: import java.util.StringTokenizer;
024:
025: import javax.portlet.ActionRequest;
026: import javax.portlet.ActionResponse;
027: import javax.portlet.PortletConfig;
028: import javax.portlet.PortletContext;
029: import javax.portlet.PortletException;
030: import javax.portlet.PortletMode;
031: import javax.portlet.PortletPreferences;
032: import javax.portlet.RenderRequest;
033: import javax.portlet.RenderResponse;
034: import javax.security.auth.Subject;
035:
036: import org.apache.commons.codec.binary.Base64;
037: import org.apache.commons.httpclient.HttpClient;
038: import org.apache.commons.httpclient.HttpMethod;
039: import org.apache.commons.httpclient.NameValuePair;
040: import org.apache.commons.httpclient.UsernamePasswordCredentials;
041: import org.apache.commons.httpclient.auth.AuthScope;
042: import org.apache.commons.httpclient.auth.AuthState;
043: import org.apache.commons.httpclient.auth.BasicScheme;
044: import org.apache.commons.httpclient.methods.PostMethod;
045: import org.apache.commons.logging.Log;
046: import org.apache.commons.logging.LogFactory;
047: import org.apache.jetspeed.rewriter.WebContentRewriter;
048: import org.apache.jetspeed.security.JSSubject;
049: import org.apache.jetspeed.sso.SSOContext;
050: import org.apache.jetspeed.sso.SSOException;
051: import org.apache.jetspeed.sso.SSOProvider;
052: import org.apache.portals.messaging.PortletMessaging;
053:
054: /**
055: * SSOWebContentPortlet
056: *
057: * @author <a href="mailto:taylor@apache.org">David Sean Taylor</a>
058: * @version $Id: SSOWebContentPortlet.java 605431 2007-12-19 05:11:40Z taylor $
059: */
060: public class SSOWebContentPortlet extends WebContentPortlet {
061: // Constants
062:
063: // sso.type
064: public static final String SSO_TYPE = "sso.type";
065:
066: public static final String SSO_TYPE_HTTP = "http"; // BOZO - depricate in favor of 'basic'
067: public static final String SSO_TYPE_BASIC = "basic";
068: public static final String SSO_TYPE_BASIC_PREEMPTIVE = "basic.preemptive";
069:
070: public static final String SSO_TYPE_FORM = "form";
071: public static final String SSO_TYPE_FORM_GET = "form.get";
072: public static final String SSO_TYPE_FORM_POST = "form.post";
073:
074: public static final String SSO_TYPE_URL = "url";
075: public static final String SSO_TYPE_URL_BASE64 = "url.base64";
076:
077: public static final String SSO_TYPE_CERTIFICATE = "certificate";
078:
079: public static final String SSO_TYPE_DEFAULT = SSO_TYPE_BASIC; // handled well even if nothing but credentials are set (see: doRequestedAuthentication)
080:
081: // ...standardized auth types
082:
083: public static final String BASIC_AUTH_SCHEME_NAME = (new BasicScheme())
084: .getSchemeName();
085:
086: // supporting parameters - for various sso types
087:
088: // ...names of query args for sso.type=url|url.base64
089:
090: public static final String SSO_TYPE_URL_USERNAME_PARAM = "sso.url.Principal";
091: public static final String SSO_TYPE_URL_PASSWORD_PARAM = "sso.url.Credential";
092:
093: // ...names of fields for sso.type=form|form.get|form.post
094:
095: public static final String SSO_TYPE_FORM_ACTION_URL = "sso.form.Action";
096: public static final String SSO_TYPE_FORM_ACTION_ARGS = "sso.form.Args";
097: public static final String SSO_TYPE_FORM_USERNAME_FIELD = "sso.form.Principal";
098: public static final String SSO_TYPE_FORM_PASSWORD_FIELD = "sso.form.Credential";
099:
100: // ...tags for passing creditials along on the current request object
101:
102: public static final String SSO_REQUEST_ATTRIBUTE_USERNAME = "sso.ra.username";
103: public static final String SSO_REQUEST_ATTRIBUTE_PASSWORD = "sso.ra.password";
104:
105: // ...field names for EDIT mode
106:
107: public static final String SSO_EDIT_FIELD_PRINCIPAL = "ssoPrincipal";
108: public static final String SSO_EDIT_FIELD_CREDENTIAL = "ssoCredential";
109:
110: // SSOWebContent session variables
111:
112: public static final String FORM_AUTH_STATE = "ssowebcontent.form.authstate";
113:
114: // Class Data
115:
116: protected final static Log log = LogFactory
117: .getLog(SSOWebContentPortlet.class);
118:
119: // Data Members
120:
121: protected PortletContext context;
122: protected SSOProvider sso;
123:
124: // Methods
125:
126: public void init(PortletConfig config) throws PortletException {
127: super .init(config);
128: context = getPortletContext();
129: sso = (SSOProvider) context.getAttribute("cps:SSO");
130: if (null == sso) {
131: throw new PortletException(
132: "Failed to find SSO Provider on portlet initialization");
133: }
134: }
135:
136: public void processAction(ActionRequest actionRequest,
137: ActionResponse actionResponse) throws PortletException,
138: IOException {
139: // grab parameters - they will be cleared in processing of edit response
140: String webContentParameter = actionRequest
141: .getParameter(WebContentRewriter.ACTION_PARAMETER_URL);
142: String ssoPrincipal = actionRequest
143: .getParameter(SSO_EDIT_FIELD_PRINCIPAL);
144: String ssoCredential = actionRequest
145: .getParameter(SSO_EDIT_FIELD_CREDENTIAL);
146:
147: // save the prefs
148: super .processAction(actionRequest, actionResponse);
149:
150: // process credentials
151: if (webContentParameter == null
152: || actionRequest.getPortletMode() == PortletMode.EDIT) {
153: // processPreferencesAction(request, actionResponse);
154: // get the POST params -- requires HTML post params named above
155: String site = actionRequest.getPreferences().getValue(
156: "SRC", "");
157:
158: try {
159: Subject subject = getSubject();
160: if (sso.hasSSOCredentials(subject, site)) {
161: SSOContext context = sso.getCredentials(subject,
162: site);
163: if (!context.getRemotePrincipalName().equals(
164: ssoPrincipal)) {
165: sso.removeCredentialsForSite(subject, site);
166: sso.addCredentialsForSite(subject,
167: ssoPrincipal, site, ssoCredential);
168: } else {
169: sso.updateCredentialsForSite(subject,
170: ssoPrincipal, site, ssoCredential);
171: }
172: } else {
173: sso.addCredentialsForSite(subject, ssoPrincipal,
174: site, ssoCredential);
175: }
176: } catch (SSOException e) {
177: throw new PortletException(e);
178: }
179: }
180: }
181:
182: public void doView(RenderRequest request, RenderResponse response)
183: throws PortletException, IOException {
184: String site = request.getPreferences().getValue("SRC", null);
185:
186: if (site == null) {
187: // no SRC configured in prefs - switch to SSO Configure View
188: request.setAttribute(PARAM_VIEW_PAGE, this
189: .getPortletConfig().getInitParameter(
190: PARAM_EDIT_PAGE));
191: setupPreferencesEdit(request, response);
192: } else
193: try {
194: Subject subject = getSubject();
195: SSOContext context = sso.getCredentials(subject, site);
196: request.setAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME,
197: context.getRemotePrincipalName());
198: request.setAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD,
199: context.getRemoteCredential());
200: } catch (SSOException e) {
201: if (e.getMessage().equals(
202: SSOException.NO_CREDENTIALS_FOR_SITE)) {
203: // no credentials configured in SSO store
204: // switch to SSO Configure View
205: request.setAttribute(PARAM_VIEW_PAGE, this
206: .getPortletConfig().getInitParameter(
207: PARAM_EDIT_PAGE));
208: setupPreferencesEdit(request, response);
209: } else {
210: throw new PortletException(e);
211: }
212: }
213:
214: super .doView(request, response);
215: }
216:
217: public void doEdit(RenderRequest request, RenderResponse response)
218: throws PortletException, IOException {
219: try {
220: Subject subject = getSubject();
221: String site = request.getPreferences().getValue("SRC", "");
222: SSOContext context = sso.getCredentials(subject, site);
223: getContext(request).put(SSO_EDIT_FIELD_PRINCIPAL,
224: context.getRemotePrincipalName());
225: getContext(request).put(SSO_EDIT_FIELD_CREDENTIAL,
226: context.getRemoteCredential());
227: } catch (SSOException e) {
228: if (e.getMessage().equals(
229: SSOException.NO_CREDENTIALS_FOR_SITE)) {
230: // no credentials configured in SSO store
231: // switch to SSO Configure View
232: getContext(request).put(SSO_EDIT_FIELD_PRINCIPAL, "");
233: getContext(request).put(SSO_EDIT_FIELD_CREDENTIAL, "");
234: } else {
235: throw new PortletException(e);
236: }
237: }
238:
239: super .doEdit(request, response);
240: }
241:
242: private Subject getSubject() {
243: AccessControlContext context = AccessController.getContext();
244: return JSSubject.getSubject(context);
245: }
246:
247: protected byte[] doPreemptiveAuthentication(HttpClient client,
248: HttpMethod method, RenderRequest request,
249: RenderResponse response) {
250: byte[] result = super .doPreemptiveAuthentication(client,
251: method, request, response);
252: if (result != null) {
253: // already handled
254: return result;
255: }
256:
257: // System.out.println("SSOWebContentPortlet.doPreemptiveAuthentication...");
258:
259: PortletPreferences prefs = request.getPreferences();
260: String type = getSingleSignOnAuthType(prefs);
261:
262: if (type.equalsIgnoreCase(SSO_TYPE_BASIC_PREEMPTIVE)) {
263: // Preemptive, basic authentication
264: String userName = (String) request
265: .getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
266: if (userName == null)
267: userName = "";
268: String password = (String) request
269: .getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
270: if (password == null)
271: password = "";
272:
273: // System.out.println("...performing preemptive basic authentication with userName: "+userName+", and password: "+password);
274: method.setDoAuthentication(true);
275: method.getHostAuthState().setPreemptive();
276: client.getState()
277: .setCredentials(
278: AuthScope.ANY,
279: new UsernamePasswordCredentials(userName,
280: password));
281:
282: // handled!
283: return result;
284:
285: } else if (type.startsWith(SSO_TYPE_FORM)) {
286: try {
287: Boolean formAuth = (Boolean) PortletMessaging.receive(
288: request, FORM_AUTH_STATE);
289: if (formAuth != null) {
290: // already been here, done that
291: return (formAuth.booleanValue() ? result : null);
292: } else {
293: // stop recursion, but assume failure, ...for now
294: PortletMessaging.publish(request, FORM_AUTH_STATE,
295: Boolean.FALSE);
296: }
297:
298: String formAction = prefs.getValue(
299: SSO_TYPE_FORM_ACTION_URL, "");
300: if (formAction == null || formAction.length() == 0) {
301: log
302: .warn("sso.type specified as 'form', but no: "
303: + SSO_TYPE_FORM_ACTION_URL
304: + ", action was specified - unable to preemptively authenticate by form.");
305: return null;
306: }
307: String userNameField = prefs.getValue(
308: SSO_TYPE_FORM_USERNAME_FIELD, "");
309: if (userNameField == null
310: || userNameField.length() == 0) {
311: log
312: .warn("sso.type specified as 'form', but no: "
313: + SSO_TYPE_FORM_USERNAME_FIELD
314: + ", username field was specified - unable to preemptively authenticate by form.");
315: return null;
316: }
317: String passwordField = prefs.getValue(
318: SSO_TYPE_FORM_PASSWORD_FIELD, "password");
319: if (passwordField == null
320: || passwordField.length() == 0) {
321: log
322: .warn("sso.type specified as 'form', but no: "
323: + SSO_TYPE_FORM_PASSWORD_FIELD
324: + ", password field was specified - unable to preemptively authenticate by form.");
325: return null;
326: }
327:
328: String userName = (String) request
329: .getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
330: if (userName == null)
331: userName = "";
332: String password = (String) request
333: .getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
334: if (password == null)
335: password = "";
336:
337: // get submit method
338: int i = type.indexOf('.');
339: boolean isPost = i > 0 ? type.substring(i + 1)
340: .equalsIgnoreCase("post") : true; // default to post, since it is a form
341:
342: // get parameter map
343: HashMap formParams = new HashMap();
344: formParams
345: .put(userNameField, new String[] { userName });
346: formParams
347: .put(passwordField, new String[] { password });
348: String formArgs = prefs.getValue(
349: SSO_TYPE_FORM_ACTION_ARGS, "");
350: if (formArgs != null && formArgs.length() > 0) {
351: StringTokenizer iter = new StringTokenizer(
352: formArgs, ";");
353: while (iter.hasMoreTokens()) {
354: String pair = iter.nextToken();
355: i = pair.indexOf('=');
356: if (i > 0)
357: formParams.put(pair.substring(0, i),
358: new String[] { pair
359: .substring(i + 1) });
360: }
361: }
362:
363: // resuse client - in case new cookies get set - but create a new method (for the formAction)
364: String formMethod = (isPost) ? FORM_POST_METHOD
365: : FORM_GET_METHOD;
366: method = getHttpMethod(client, getURLSource(formAction,
367: formParams, request, response), formParams,
368: formMethod, request);
369: // System.out.println("...posting credentials");
370: result = doHttpWebContent(client, method, 0, request,
371: response);
372: // System.out.println("Result of attempted authorization: "+success);
373: PortletMessaging.publish(request, FORM_AUTH_STATE,
374: Boolean.valueOf(result != null));
375: return result;
376: } catch (Exception ex) {
377: // bad
378: log.error("Form-based authentication failed", ex);
379: }
380: } else if (type.equalsIgnoreCase(SSO_TYPE_URL)
381: || type.equalsIgnoreCase(SSO_TYPE_URL_BASE64)) {
382: // set user name and password parameters in the HttpMethod
383: String userNameParam = prefs.getValue(
384: SSO_TYPE_URL_USERNAME_PARAM, "");
385: if (userNameParam == null || userNameParam.length() == 0) {
386: log
387: .warn("sso.type specified as 'url', but no: "
388: + SSO_TYPE_URL_USERNAME_PARAM
389: + ", username parameter was specified - unable to preemptively authenticate by URL.");
390: return null;
391: }
392: String passwordParam = prefs.getValue(
393: SSO_TYPE_URL_PASSWORD_PARAM, "");
394: if (passwordParam == null || passwordParam.length() == 0) {
395: log
396: .warn("sso.type specified as 'url', but no: "
397: + SSO_TYPE_URL_PASSWORD_PARAM
398: + ", password parameter was specified - unable to preemptively authenticate by URL.");
399: return null;
400: }
401: String userName = (String) request
402: .getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
403: if (userName == null)
404: userName = "";
405: String password = (String) request
406: .getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
407: if (password == null)
408: password = "";
409: if (type.equalsIgnoreCase(SSO_TYPE_URL_BASE64)) {
410: Base64 encoder = new Base64();
411: userName = new String(encoder.encode(userName
412: .getBytes()));
413: password = new String(encoder.encode(password
414: .getBytes()));
415: }
416:
417: // GET and POST accept args differently
418: if (method instanceof PostMethod) {
419: // add POST data
420: PostMethod postMethod = (PostMethod) method;
421: postMethod.addParameter(userNameParam, userName);
422: postMethod.addParameter(passwordParam, password);
423: } else {
424: // augment GET query string
425: NameValuePair[] authPairs = new NameValuePair[] {
426: new NameValuePair(userNameParam, userName),
427: new NameValuePair(passwordParam, password) };
428: String existingQuery = method.getQueryString();
429: method.setQueryString(authPairs);
430: if (existingQuery != null && existingQuery.length() > 0) {
431: // augment existing query with new auth query
432: existingQuery = existingQuery + '&'
433: + method.getQueryString();
434: method.setQueryString(existingQuery);
435: }
436: }
437:
438: return result;
439: }
440: // else System.out.println("...sso.type: "+type+", no pre-emptive authentication");
441:
442: // not handled
443: return null;
444: }
445:
446: protected boolean doRequestedAuthentication(HttpClient client,
447: HttpMethod method, RenderRequest request,
448: RenderResponse response) {
449: if (super .doRequestedAuthentication(client, method, request,
450: response)) {
451: // already handled
452: return true;
453: }
454:
455: // System.out.println("SSOWebContentPortlet.doRequestedAuthentication...");
456:
457: if (method.getHostAuthState().getAuthScheme().getSchemeName()
458: .equals(BASIC_AUTH_SCHEME_NAME)) {
459: // Basic authentication being requested
460: String userName = (String) request
461: .getAttribute(SSO_REQUEST_ATTRIBUTE_USERNAME);
462: if (userName == null)
463: userName = "";
464: String password = (String) request
465: .getAttribute(SSO_REQUEST_ATTRIBUTE_PASSWORD);
466: if (password == null)
467: password = "";
468:
469: // System.out.println("...providing basic authentication with userName: "+userName+", and password: "+password);
470: method.setDoAuthentication(true);
471: AuthState state = method.getHostAuthState();
472: AuthScope scope = new AuthScope(AuthScope.ANY_HOST,
473: AuthScope.ANY_PORT, state.getRealm(), state
474: .getAuthScheme().getSchemeName());
475: client.getState()
476: .setCredentials(
477: scope,
478: new UsernamePasswordCredentials(userName,
479: password));
480:
481: // handled!
482: return true;
483: } else {
484: log
485: .warn("SSOWebContentPortlent.doAuthenticate() - unexpected authentication scheme: "
486: + method.getHostAuthState().getAuthScheme()
487: .getSchemeName());
488: }
489:
490: // only know how to handle Basic authentication, in this context
491: return false;
492: }
493:
494: protected String getSingleSignOnAuthType(PortletPreferences prefs) {
495: String type = prefs.getValue(SSO_TYPE, SSO_TYPE_DEFAULT);
496:
497: if (type != null && type.equalsIgnoreCase(SSO_TYPE_HTTP)) {
498: log.warn("sso.type: " + SSO_TYPE_HTTP
499: + ", has been deprecated - use: " + SSO_TYPE_BASIC
500: + ", or: " + SSO_TYPE_BASIC_PREEMPTIVE);
501: type = SSO_TYPE_BASIC;
502: }
503:
504: return type;
505: }
506: }
|