001: /*
002: * Copyright 2005-2007 the original author or authors.
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016:
017: package org.acegisecurity.ui.portlet;
018:
019: import java.io.IOException;
020: import java.util.Iterator;
021: import java.util.List;
022: import java.util.Map;
023:
024: import javax.portlet.ActionRequest;
025: import javax.portlet.ActionResponse;
026: import javax.portlet.PortletRequest;
027: import javax.portlet.PortletResponse;
028: import javax.portlet.RenderRequest;
029: import javax.portlet.RenderResponse;
030:
031: import org.acegisecurity.Authentication;
032: import org.acegisecurity.AuthenticationException;
033: import org.acegisecurity.AuthenticationManager;
034: import org.acegisecurity.context.SecurityContext;
035: import org.acegisecurity.context.SecurityContextHolder;
036: import org.acegisecurity.providers.portlet.PortletAuthenticationProvider;
037: import org.acegisecurity.providers.portlet.PortletAuthenticationToken;
038: import org.acegisecurity.providers.portlet.populator.ContainerPortletAuthoritiesPopulator;
039: import org.acegisecurity.ui.AbstractProcessingFilter;
040: import org.apache.commons.logging.Log;
041: import org.apache.commons.logging.LogFactory;
042: import org.springframework.beans.factory.InitializingBean;
043: import org.springframework.util.Assert;
044: import org.springframework.web.portlet.HandlerInterceptor;
045: import org.springframework.web.portlet.ModelAndView;
046:
047: /**
048: * <p>This interceptor is responsible for processing portlet authentication requests. This
049: * is the portlet equivalent of the <code>AuthenticationProcessingFilter</code> used for
050: * traditional servlet-based web applications. It is applied to both <code>ActionRequest</code>s
051: * and <code>RenderRequest</code>s alike. If authentication is successful, the resulting
052: * {@link Authentication} object will be placed into the <code>SecurityContext</code>, which
053: * is guaranteed to have already been created by an earlier interceptor. If authentication
054: * fails, the <code>AuthenticationException</code> will be placed into the
055: * <code>PortletSession</code> with the attribute defined by
056: * {@link AbstractProcessingFilter#ACEGI_SECURITY_LAST_EXCEPTION_KEY}.</p>
057: *
058: * <p>Some portals do not properly provide the identity of the current user via the
059: * <code>getRemoteUser()</code> or <code>getUserPrincipal()</code> methods of the
060: * <code>PortletRequest</code>. In these cases they sometimes make it available in the
061: * <code>USER_INFO</code> map provided as one of the attributes of the request. If this is
062: * the case in your portal, you can specify a list of <code>USER_INFO</code> attributes
063: * to check for the username via the <code>userNameAttributes</code> property of this bean.
064: * You can also completely override the {@link #getPrincipalFromRequest(PortletRequest)}
065: * and {@link #getCredentialsFromRequest(PortletRequest)} methods to suit the particular
066: * behavior of your portal.</p>
067: *
068: * <p>This interceptor will put the <code>PortletRequest</code> object into the
069: * <code>details<code> property of the <code>Authentication</code> object that is sent
070: * as a request to the <code>AuthenticationManager</code>. This is done so that the request
071: * is available to classes like {@link ContainerPortletAuthoritiesPopulator} that need
072: * access to information from the portlet container. The {@link PortletAuthenticationProvider}
073: * will replace this with the <code>USER_INFO</code> map in the resulting <code>Authentication</code>
074: * object.</p>
075: *
076: * @see org.acegisecurity.ui.AbstractProcessingFilter
077: * @see org.acegisecurity.ui.webapp.AuthenticationProcessingFilter
078: * @author John A. Lewis
079: * @since 2.0
080: * @version $Id$
081: */
082: public class PortletProcessingInterceptor implements
083: HandlerInterceptor, InitializingBean {
084:
085: //~ Static fields/initializers =====================================================================================
086:
087: private static final Log logger = LogFactory
088: .getLog(PortletProcessingInterceptor.class);
089:
090: //~ Instance fields ================================================================================================
091:
092: private AuthenticationManager authenticationManager;
093:
094: private List userNameAttributes;
095:
096: //~ Methods ========================================================================================================
097:
098: public void afterPropertiesSet() throws Exception {
099: Assert.notNull(authenticationManager,
100: "An AuthenticationManager must be set");
101: }
102:
103: public boolean preHandleAction(ActionRequest request,
104: ActionResponse response, Object handler) throws Exception {
105: return preHandle(request, response, handler);
106: }
107:
108: public boolean preHandleRender(RenderRequest request,
109: RenderResponse response, Object handler) throws Exception {
110: return preHandle(request, response, handler);
111: }
112:
113: public void postHandleRender(RenderRequest request,
114: RenderResponse response, Object handler,
115: ModelAndView modelAndView) throws Exception {
116: }
117:
118: public void afterActionCompletion(ActionRequest request,
119: ActionResponse response, Object handler, Exception ex)
120: throws Exception {
121: }
122:
123: public void afterRenderCompletion(RenderRequest request,
124: RenderResponse response, Object handler, Exception ex)
125: throws Exception {
126: }
127:
128: /**
129: * Common preHandle method for both the action and render phases of the interceptor.
130: */
131: private boolean preHandle(PortletRequest request,
132: PortletResponse response, Object handler) throws Exception {
133:
134: // get the SecurityContext
135: SecurityContext ctx = SecurityContextHolder.getContext();
136:
137: if (logger.isDebugEnabled())
138: logger.debug("Checking secure context token: "
139: + ctx.getAuthentication());
140:
141: // if there is no existing Authentication object, then lets create one
142: if (ctx.getAuthentication() == null) {
143:
144: try {
145:
146: // build the authentication request from the PortletRequest
147: PortletAuthenticationToken authRequest = new PortletAuthenticationToken(
148: getPrincipalFromRequest(request),
149: getCredentialsFromRequest(request), null);
150:
151: // put the PortletRequest into the authentication request as the "details"
152: authRequest.setDetails(request);
153:
154: if (logger.isDebugEnabled())
155: logger
156: .debug("Beginning authentication request for user '"
157: + authRequest.getName() + "'");
158:
159: onPreAuthentication(request, response);
160:
161: // ask the authentication manager to authenticate the request
162: // it will throw an AuthenticationException if it fails, otherwise it succeeded
163: Authentication authResult = authenticationManager
164: .authenticate(authRequest);
165:
166: // process a successful authentication
167: if (logger.isDebugEnabled())
168: logger.debug("Authentication success: "
169: + authResult);
170: ctx.setAuthentication(authResult);
171: onSuccessfulAuthentication(request, response,
172: authResult);
173:
174: } catch (AuthenticationException failed) {
175:
176: // process an unsuccessful authentication
177: if (logger.isDebugEnabled())
178: logger
179: .debug("Authentication failed - updating ContextHolder to contain null Authentication");
180: ctx.setAuthentication(null);
181: request
182: .getPortletSession()
183: .setAttribute(
184: AbstractProcessingFilter.ACEGI_SECURITY_LAST_EXCEPTION_KEY,
185: failed);
186: onUnsuccessfulAuthentication(request, response, failed);
187: }
188: }
189:
190: return true;
191: }
192:
193: /**
194: * This method attempts to extract a principal from the portlet request.
195: * According to the JSR-168 spec, the <code>PortletRequest<code> should return the name
196: * of the user in the <code>getRemoteUser()</code> method. It should also provide a
197: * <code>java.security.Principal</code> object from the <code>getUserPrincipal()</code>
198: * method. We will first try these to come up with a valid username.
199: * <p>Unfortunately, some portals do not properly return these values for authenticated
200: * users. So, if neither of those succeeds and if the <code>userNameAttributes</code>
201: * property has been populated, then we will search through the <code>USER_INFO<code>
202: * map from the request to see if we can find a valid username.
203: * <p>This method can be overridden by subclasses to provide special handling
204: * for portals with weak support for the JSR-168 spec.</p>
205: * @param request the portlet request object
206: * @return the determined principal object, or null if none found
207: */
208: protected Object getPrincipalFromRequest(PortletRequest request) {
209:
210: // first try getRemoteUser()
211: Object principal = request.getRemoteUser();
212: if (principal != null) {
213: return principal;
214: }
215:
216: // next try getUserPrincipal()
217: principal = request.getUserPrincipal();
218: if (principal != null) {
219: return principal;
220: }
221:
222: // last try entries in USER_INFO if any attributes were defined
223: if (this .userNameAttributes != null) {
224: Map userInfo = (Map) request
225: .getAttribute(PortletRequest.USER_INFO);
226: if (userInfo != null) {
227: Iterator i = this .userNameAttributes.iterator();
228: while (i.hasNext()) {
229: principal = (String) userInfo.get(i.next());
230: if (principal != null) {
231: return principal;
232: }
233: }
234: }
235: }
236:
237: // none found so return null
238: return null;
239: }
240:
241: /**
242: * This method attempts to extract a credentials from the portlet request.
243: * We are trusting the portal framework to authenticate the user, so all
244: * we are really doing is trying to put something intelligent in here to
245: * indicate the user is authenticated. According to the JSR-168 spec,
246: * PortletRequest.getAuthType() should return a non-null value if the
247: * user is authenticated and should be null if not authenticated. So we
248: * will use this as the credentials and the token will be trusted as
249: * authenticated if the credentials are not null.
250: * <p>This method can be overridden by subclasses to provide special handling
251: * for portals with weak support for the JSR-168 spec. If that is done,
252: * be sure the value is non-null for authenticated users and null for
253: * non-authenticated users.</p>
254: * @param request the portlet request object
255: * @return the determined credentials object, or null if none found
256: */
257: protected Object getCredentialsFromRequest(PortletRequest request) {
258: return request.getAuthType();
259: }
260:
261: /**
262: * Callback for custom processing prior to the authentication attempt.
263: * @param request the portlet request to be authenticated
264: * @param response the portlet response to be authenticated
265: * @throws AuthenticationException to indicate that authentication attempt is not valid and should be terminated
266: * @throws IOException
267: */
268: protected void onPreAuthentication(PortletRequest request,
269: PortletResponse response) throws AuthenticationException,
270: IOException {
271: }
272:
273: /**
274: * Callback for custom processing after a successful authentication attempt.
275: * @param request the portlet request that was authenticated
276: * @param response the portlet response that was authenticated
277: * @param authResult the resulting Authentication object
278: * @throws IOException
279: */
280: protected void onSuccessfulAuthentication(PortletRequest request,
281: PortletResponse response, Authentication authResult)
282: throws IOException {
283: }
284:
285: /**
286: * Callback for custom processing after an unsuccessful authentication attempt.
287: * @param request the portlet request that failed authentication
288: * @param response the portlet response that failed authentication
289: * @param failed the AuthenticationException that occurred
290: * @throws IOException
291: */
292: protected void onUnsuccessfulAuthentication(PortletRequest request,
293: PortletResponse response, AuthenticationException failed)
294: throws IOException {
295: }
296:
297: public AuthenticationManager getAuthenticationManager() {
298: return authenticationManager;
299: }
300:
301: public void setAuthenticationManager(
302: AuthenticationManager authenticationManager) {
303: this .authenticationManager = authenticationManager;
304: }
305:
306: public List getUserNameAttributes() {
307: return userNameAttributes;
308: }
309:
310: public void setUserNameAttributes(List userNameAttributes) {
311: this.userNameAttributes = userNameAttributes;
312: }
313:
314: }
|