diff --git a/sandbox/src/main/java/org/acegisecurity/intercept/web/SandboxSecurityEnforcementFilter.java b/sandbox/src/main/java/org/acegisecurity/intercept/web/SandboxSecurityEnforcementFilter.java new file mode 100644 index 0000000000..b9d44762ec --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/intercept/web/SandboxSecurityEnforcementFilter.java @@ -0,0 +1,308 @@ +/* Copyright 2004, 2005 Acegi Technology Pty Limited + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package net.sf.acegisecurity.intercept.web; + +import java.io.IOException; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import net.sf.acegisecurity.AccessDeniedException; +import net.sf.acegisecurity.AuthenticationException; +import net.sf.acegisecurity.AuthenticationTrustResolver; +import net.sf.acegisecurity.AuthenticationTrustResolverImpl; +import net.sf.acegisecurity.InsufficientAuthenticationException; +import net.sf.acegisecurity.context.SecurityContextHolder; +import net.sf.acegisecurity.ui.AbstractProcessingFilter; +import net.sf.acegisecurity.util.PortResolver; +import net.sf.acegisecurity.util.PortResolverImpl; +import net.sf.acegisecurity.wrapper.redirect.SavedHttpServletRequest; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.util.Assert; + +/** + * Wraps requests to the {@link FilterSecurityInterceptor}. + * + *
+ * This filter is necessary because it provides the bridge between incoming
+ * requests and the FilterSecurityInterceptor
instance.
+ *
+ * If an {@link AuthenticationException} is detected, the filter will launch
+ * the authenticationEntryPoint
. This allows common handling of
+ * authentication failures originating from any subclass of {@link
+ * net.sf.acegisecurity.intercept.AbstractSecurityInterceptor}.
+ *
+ * If an {@link AccessDeniedException} is detected, the filter will determine
+ * whether or not the user is an anonymous user. If they are an anonymous
+ * user, the authenticationEntryPoint
will be launched. If they
+ * are not an anonymous user, the filter will respond with a
+ * HttpServletResponse.SC_FORBIDDEN
(403 error). In addition,
+ * the AccessDeniedException
itself will be placed in the
+ * HttpSession
attribute keyed against {@link
+ * #ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY} (to allow access to the stack
+ * trace etc). Again, this allows common access denied handling irrespective
+ * of the originating security interceptor.
+ *
+ * To use this filter, it is necessary to specify the following properties: + *
+ * + *filterSecurityInterceptor
indicates the
+ * FilterSecurityInterceptor
to delegate HTTP security decisions
+ * to.
+ * authenticationEntryPoint
indicates the handler that should
+ * commence the authentication process if an
+ * AuthenticationException
is detected. Note that this may also
+ * switch the current protocol from http to https for an SSL login.
+ * portResolver
is used to determine the "real" port that a
+ * request was received on.
+ *
+ * Do not use this class directly. Instead configure
+ * web.xml
to use the {@link
+ * net.sf.acegisecurity.util.FilterToBeanProxy}.
+ *
true
, indicates that SecurityEnforcementFilter
is permitted
+ * to store the target URL and exception information in the HttpSession
(the
+ * default). In situations where you do not wish to unnecessarily create HttpSession
s
+ * - because the user agent will know the failed URL, such as with BASIC or Digest authentication
+ * - you may wish to set this property to false
. Remember to also set the
+ * {@link net.sf.acegisecurity.context.HttpSessionContextIntegrationFilter#allowSessionCreation}
+ * to false
if you set this property to false
.
+ *
+ * @return true
if the HttpSession
will be used to store information
+ * about the failed request, false
if the HttpSession
will not be
+ * used
+ */
+ public boolean isCreateSessionAllowed() {
+ return createSessionAllowed;
+ }
+
+ public void setCreateSessionAllowed(boolean createSessionAllowed) {
+ this.createSessionAllowed = createSessionAllowed;
+ }
+
+ public AuthenticationTrustResolver getAuthenticationTrustResolver() {
+ return authenticationTrustResolver;
+ }
+
+ public void setFilterSecurityInterceptor(
+ FilterSecurityInterceptor filterSecurityInterceptor) {
+ this.filterSecurityInterceptor = filterSecurityInterceptor;
+ }
+
+ public FilterSecurityInterceptor getFilterSecurityInterceptor() {
+ return filterSecurityInterceptor;
+ }
+
+ public void setPortResolver(PortResolver portResolver) {
+ this.portResolver = portResolver;
+ }
+
+ public PortResolver getPortResolver() {
+ return portResolver;
+ }
+
+ public void afterPropertiesSet() throws Exception {
+ Assert.notNull(authenticationEntryPoint,
+ "authenticationEntryPoint must be specified");
+ Assert.notNull(filterSecurityInterceptor,
+ "filterSecurityInterceptor must be specified");
+ Assert.notNull(portResolver, "portResolver must be specified");
+ Assert.notNull(authenticationTrustResolver,
+ "authenticationTrustResolver must be specified");
+ }
+
+ public void destroy() {
+ }
+
+ public void doFilter(ServletRequest request, ServletResponse response,
+ FilterChain chain) throws IOException, ServletException {
+ if (!(request instanceof HttpServletRequest)) {
+ throw new ServletException("HttpServletRequest required");
+ }
+
+ if (!(response instanceof HttpServletResponse)) {
+ throw new ServletException("HttpServletResponse required");
+ }
+
+ FilterInvocation fi = new FilterInvocation(request, response, chain);
+
+ try {
+ filterSecurityInterceptor.invoke(fi);
+
+ if (logger.isDebugEnabled()) {
+ logger.debug("Chain processed normally");
+ }
+ } catch (AuthenticationException authentication) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Authentication exception occurred; redirecting to authentication entry point",
+ authentication);
+ }
+
+ sendStartAuthentication(fi, authentication);
+ } catch (AccessDeniedException accessDenied) {
+ if (authenticationTrustResolver.isAnonymous(
+ SecurityContextHolder.getContext().getAuthentication())) {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Access is denied (user is anonymous); redirecting to authentication entry point",
+ accessDenied);
+ }
+
+ sendStartAuthentication(fi,
+ new InsufficientAuthenticationException(
+ "Full authentication is required to access this resource"));
+ } else {
+ if (logger.isDebugEnabled()) {
+ logger.debug("Access is denied (user is not anonymous); sending back forbidden response",
+ accessDenied);
+ }
+
+ sendAccessDeniedError(fi, accessDenied);
+ }
+ } catch (ServletException e) {
+ throw e;
+ } catch (IOException e) {
+ throw e;
+ } catch (Throwable otherException) {
+ throw new ServletException(otherException);
+ }
+ }
+
+ public void init(FilterConfig filterConfig) throws ServletException {
+ }
+
+ protected void sendAccessDeniedError(FilterInvocation fi,
+ AccessDeniedException accessDenied)
+ throws ServletException, IOException {
+ if (createSessionAllowed) {
+ ((HttpServletRequest) fi.getRequest()).getSession().setAttribute(ACEGI_SECURITY_ACCESS_DENIED_EXCEPTION_KEY,
+ accessDenied);
+ }
+
+ ((HttpServletResponse) fi.getResponse()).sendError(HttpServletResponse.SC_FORBIDDEN,
+ accessDenied.getMessage()); // 403
+ }
+
+ protected void sendStartAuthentication(FilterInvocation fi,
+ AuthenticationException reason) throws ServletException, IOException {
+
+ // Get the HttpServletRequest
+ HttpServletRequest request = (HttpServletRequest) fi.getRequest();
+
+ // Save this original request on the session in case
+ // SecurityContextHolderAwareRequestWrapper has to resume it after (re)authentication.
+ request.getSession().setAttribute(SAVED_REQUEST_SESSION_ATTRIBUTE, SavedHttpServletRequest.saveRequest(request));
+
+ int port = portResolver.getServerPort(request);
+ boolean includePort = true;
+
+ if ("http".equals(request.getScheme().toLowerCase()) && (port == 80)) {
+ includePort = false;
+ }
+
+ if ("https".equals(request.getScheme().toLowerCase()) && (port == 443)) {
+ includePort = false;
+ }
+
+ // Build the target URL from the request
+ String targetUrl = request.getScheme() + "://" +
+ request.getServerName() + ((includePort) ? (":" + port) : "") +
+ request.getContextPath() + fi.getRequestUrl();
+
+ if (logger.isDebugEnabled()) {
+ logger.debug(
+ "Authentication entry point being called; target URL added to Session: " +
+ targetUrl);
+ }
+
+ if (createSessionAllowed) {
+ ((HttpServletRequest) request).getSession().setAttribute(AbstractProcessingFilter.ACEGI_SECURITY_TARGET_URL_KEY,
+ targetUrl);
+ }
+
+ authenticationEntryPoint.commence(request,
+ (HttpServletResponse) fi.getResponse(), reason);
+ }
+
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/wrapper/SandboxSecurityContextHolderAwareRequestWrapper.java b/sandbox/src/main/java/org/acegisecurity/wrapper/SandboxSecurityContextHolderAwareRequestWrapper.java
new file mode 100644
index 0000000000..0e4982c0d1
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/wrapper/SandboxSecurityContextHolderAwareRequestWrapper.java
@@ -0,0 +1,475 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.wrapper;
+
+import java.security.Principal;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletRequestWrapper;
+import javax.servlet.http.HttpSession;
+
+import net.sf.acegisecurity.Authentication;
+import net.sf.acegisecurity.AuthenticationTrustResolver;
+import net.sf.acegisecurity.AuthenticationTrustResolverImpl;
+import net.sf.acegisecurity.GrantedAuthority;
+import net.sf.acegisecurity.UserDetails;
+import net.sf.acegisecurity.context.SecurityContextHolder;
+import net.sf.acegisecurity.intercept.web.SandboxSecurityEnforcementFilter;
+import net.sf.acegisecurity.wrapper.redirect.Enumerator;
+import net.sf.acegisecurity.wrapper.redirect.FastHttpDateFormat;
+import net.sf.acegisecurity.wrapper.redirect.SavedHttpServletRequest;
+
+/**
+ * An Acegi Security-aware HttpServletRequestWrapper
, which uses
+ * the SecurityContext
-defined Authentication
+ * object for
+ * {@link SecurityContextHolderAwareRequestWrapper#isUserInRole(java.lang.String)}
+ * and {@link javax.servlet.http.HttpServletRequestWrapper#getRemoteUser()}
+ * responses.
+ * + * Provides request parameters, headers, cookies from original requrest or saved request. + *
+ * + * @author Orlando Garcia Carmona + * @author Ben Alex + * @author Andrey Grebnev <andrey.grebnev@blandware.com> + * @version $Id$ + */ +public class SandboxSecurityContextHolderAwareRequestWrapper extends + HttpServletRequestWrapper { + + // ~ Static fields ======================================================== + + protected static final TimeZone GMT_ZONE = TimeZone.getTimeZone("GMT"); + + /** + * The default Locale if none are specified. + */ + protected static Locale defaultLocale = Locale.getDefault(); + + // ~ Instance fields + // ======================================================== + + /** + * Authentication trust resolver. + */ + private AuthenticationTrustResolver authenticationTrustResolver = new AuthenticationTrustResolverImpl(); + + /** + * The set of SimpleDateFormat formats to use in getDateHeader(). + * + * Notice that because SimpleDateFormat is not thread-safe, we can't declare + * formats[] as a static variable. + */ + protected SimpleDateFormat formats[] = new SimpleDateFormat[3]; + + /** + * Saved request (to be resumed after authentication) + */ + protected SavedHttpServletRequest savedRequest = null; + + // ~ Constructors + // =========================================================== + + /** + * The class' primary constructor. + * + * @param request HttpServletRequest + */ + public SandboxSecurityContextHolderAwareRequestWrapper(HttpServletRequest request) { + + // First do what the parent class needs to. + super(request); + + // Return if there isn't an existing HttpSession + HttpSession session = request.getSession(false); + if (session != null) { + + // We know there's an existing HttpSession, so see if it has a + // saved request (placed there by SecurityEnforcementFilter). + SavedHttpServletRequest saved = (SavedHttpServletRequest) session + .getAttribute(SandboxSecurityEnforcementFilter.SAVED_REQUEST_SESSION_ATTRIBUTE); + if (saved != null) { + + // We know there's a saved request, so see if it has a + // saved "root" request URI to forward to. + String requestURI = saved.getRequestURI(); + if (requestURI != null) { + + // We know there's a saved "root" request URI, so see if + // it's the + // same one specified by this request. + if (requestURI.equals(request.getRequestURI())) { + + // They're the same "root" request URIs, so get the + // saved request and remove it from the HttpSession + // since we only want to process it once. + savedRequest = saved; + session + .removeAttribute(SandboxSecurityEnforcementFilter.SAVED_REQUEST_SESSION_ATTRIBUTE); + + formats[0] = new SimpleDateFormat( + "EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); + formats[1] = new SimpleDateFormat( + "EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US); + formats[2] = new SimpleDateFormat( + "EEE MMMM d HH:mm:ss yyyy", Locale.US); + + formats[0].setTimeZone(GMT_ZONE); + formats[1].setTimeZone(GMT_ZONE); + formats[2].setTimeZone(GMT_ZONE); + } + + } + + } + + } + + return; + + } + + // ~ Methods + // ================================================================ + + /** + * Returns the principal's name, as obtained from the + *SecurityContextHolder
. Properly handles both
+ * String
-based and UserDetails
-based
+ * principals.
+ *
+ * @return the username or null
if unavailable
+ */
+ public String getRemoteUser() {
+ Authentication auth = getAuthentication();
+
+ if ((auth == null) || (auth.getPrincipal() == null)) {
+ return null;
+ }
+
+ if (auth.getPrincipal() instanceof UserDetails) {
+ return ((UserDetails) auth.getPrincipal()).getUsername();
+ }
+
+ return auth.getPrincipal().toString();
+ }
+
+ /**
+ * Simple searches for an exactly matching {@link
+ * GrantedAuthority#getAuthority()}.
+ *
+ *
+ * Will always return false
if the
+ * SecurityContextHolder
contains an
+ * Authentication
with
+ * null
principal
and/or
+ * GrantedAuthority[]
objects.
+ *
GrantedAuthority
String
representation to check for.
+ * @return true
if an exact (case sensitive) matching granted authority is located, false
otherwise.
+ */
+ public boolean isUserInRole(String role) {
+ return isGranted(role);
+ }
+
+ /**
+ * Returns the Authentication
(which is a subclass of
+ * Principal
), or null
if unavailable.
+ *
+ * + * Note: Override this method in order to workaround the problem in Sun Java + * System Application Server 8.1 PE + *
+ * + * @return theAuthentication
, or null
+ */
+ public Principal getUserPrincipal() {
+ Authentication auth = getAuthentication();
+
+ if ((auth == null) || (auth.getPrincipal() == null)) {
+ return null;
+ }
+
+ return auth;
+ }
+
+ /**
+ * Obtain the current active Authentication
+ *
+ * @return the authentication object or null
+ */
+ private Authentication getAuthentication() {
+ Authentication auth = SecurityContextHolder.getContext()
+ .getAuthentication();
+
+ if (!authenticationTrustResolver.isAnonymous(auth)) {
+ return auth;
+ }
+
+ return null;
+ }
+
+ /**
+ * Determines if principal has been granted a given role.
+ *
+ * @param role The role being tested.
+ * @return True if principal has been granted the given role.
+ */
+ private boolean isGranted(String role) {
+ Authentication auth = getAuthentication();
+
+ if ((auth == null) || (auth.getPrincipal() == null)
+ || (auth.getAuthorities() == null)) {
+ return false;
+ }
+
+ for (int i = 0; i < auth.getAuthorities().length; i++) {
+ if (role.equals(auth.getAuthorities()[i].getAuthority())) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ /**
+ * The default behavior of this method is to return getMethod() on the
+ * wrapped request object.
+ */
+ public String getMethod() {
+ if (savedRequest == null) {
+ return super.getMethod();
+ } else {
+ return savedRequest.getMethod();
+ }
+ }
+
+ /**
+ * The default behavior of this method is to return getHeader(String name)
+ * on the wrapped request object.
+ */
+ public String getHeader(String name) {
+ if (savedRequest == null) {
+ return super.getHeader(name);
+ } else {
+ String header = null;
+ Iterator iterator = savedRequest.getHeaderValues(name);
+ while (iterator.hasNext()) {
+ header = (String) iterator.next();
+ break;
+ }
+ return header;
+ }
+ }
+
+ /**
+ * The default behavior of this method is to return getIntHeader(String
+ * name) on the wrapped request object.
+ */
+ public int getIntHeader(String name) {
+ if (savedRequest == null) {
+ return super.getIntHeader(name);
+ } else {
+ String value = getHeader(name);
+ if (value == null) {
+ return (-1);
+ } else {
+ return (Integer.parseInt(value));
+ }
+ }
+ }
+
+ /**
+ * The default behavior of this method is to return getDateHeader(String
+ * name) on the wrapped request object.
+ */
+ public long getDateHeader(String name) {
+ if (savedRequest == null) {
+ return super.getDateHeader(name);
+ } else {
+ String value = getHeader(name);
+ if (value == null)
+ return (-1L);
+
+ // Attempt to convert the date header in a variety of formats
+ long result = FastHttpDateFormat.parseDate(value, formats);
+ if (result != (-1L)) {
+ return result;
+ }
+ throw new IllegalArgumentException(value);
+ }
+ }
+
+ /**
+ * The default behavior of this method is to return getHeaderNames() on the
+ * wrapped request object.
+ */
+ public Enumeration getHeaderNames() {
+ if (savedRequest == null) {
+ return super.getHeaderNames();
+ } else {
+ return new Enumerator(savedRequest.getHeaderNames());
+ }
+ }
+
+ /**
+ * The default behavior of this method is to return getHeaders(String name)
+ * on the wrapped request object.
+ */
+ public Enumeration getHeaders(String name) {
+ if (savedRequest == null) {
+ return super.getHeaders(name);
+ } else {
+ return new Enumerator(savedRequest.getHeaderValues(name));
+ }
+ }
+
+ /**
+ * The default behavior of this method is to return getCookies() on the
+ * wrapped request object.
+ */
+ public Cookie[] getCookies() {
+ if (savedRequest == null) {
+ return super.getCookies();
+ } else {
+ List cookies = savedRequest.getCookies();
+ return (Cookie[]) cookies.toArray(new Cookie[cookies.size()]);
+ }
+ }
+
+ /**
+ * The default behavior of this method is to return
+ * getParameterValues(String name) on the wrapped request object.
+ */
+ public String[] getParameterValues(String name) {
+ if (savedRequest == null) {
+ return super.getParameterValues(name);
+ } else {
+ return savedRequest.getParameterValues(name);
+ }
+ }
+
+ /**
+ * The default behavior of this method is to return getParameterNames() on
+ * the wrapped request object.
+ */
+ public Enumeration getParameterNames() {
+ if (savedRequest == null) {
+ return super.getParameterNames();
+ } else {
+ return new Enumerator(savedRequest.getParameterNames());
+ }
+ }
+
+ /**
+ * The default behavior of this method is to return getParameterMap() on the
+ * wrapped request object.
+ */
+ public Map getParameterMap() {
+ if (savedRequest == null) {
+ return super.getParameterMap();
+ } else {
+ return savedRequest.getParameterMap();
+ }
+ }
+
+ /**
+ * The default behavior of this method is to return getParameter(String
+ * name) on the wrapped request object.
+ */
+ public String getParameter(String name) {
+
+ /*
+ * if (savedRequest == null) { return super.getParameter(name); } else {
+ * String value = null; String[] values =
+ * savedRequest.getParameterValues(name); if (values == null) return
+ * null; for (int i = 0; i < values.length; i++) { value = values[i];
+ * break; } return value; }
+ */
+
+ // We do not get value from super.getParameter because
+ // of a bug in Jetty servlet-container.
+ String value = null;
+ String[] values = null;
+ if (savedRequest == null) {
+ values = super.getParameterValues(name);
+ } else {
+ values = savedRequest.getParameterValues(name);
+ }
+
+ if (values == null)
+ return null;
+ for (int i = 0; i < values.length; i++) {
+ value = values[i];
+ break;
+ }
+ return value;
+
+ }
+
+ /**
+ * The default behavior of this method is to return getLocales() on the
+ * wrapped request object.
+ */
+ public Enumeration getLocales() {
+ if (savedRequest == null) {
+ return super.getLocales();
+ } else {
+ Iterator iterator = savedRequest.getLocales();
+ if (iterator.hasNext()) {
+ return new Enumerator(iterator);
+ } else {
+ ArrayList results = new ArrayList();
+ results.add(defaultLocale);
+ return new Enumerator(results.iterator());
+ }
+ }
+ }
+
+ /**
+ * The default behavior of this method is to return getLocale() on the
+ * wrapped request object.
+ */
+ public Locale getLocale() {
+ if (savedRequest == null) {
+ return super.getLocale();
+ } else {
+ Locale locale = null;
+ Iterator iterator = savedRequest.getLocales();
+ while (iterator.hasNext()) {
+ locale = (Locale) iterator.next();
+ break;
+ }
+ if (locale == null) {
+ return defaultLocale;
+ } else {
+ return locale;
+ }
+ }
+ }
+
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/Enumerator.java b/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/Enumerator.java
new file mode 100644
index 0000000000..4828e7397d
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/Enumerator.java
@@ -0,0 +1,143 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.wrapper.redirect;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.NoSuchElementException;
+
+/**
+ * Adapter class that wraps an Enumeration
around a Java2
+ * collection classes object Iterator
so that existing APIs
+ * returning Enumerations can easily run on top of the new collections.
+ * Constructors are provided to easliy create such wrappers.
The source code is taken from Apache Tomcat
+ * + * + * + * @author Craig R. McClanahan + * @author Andrey Grebnev <andrey.grebnev@blandware.com> + * @version $Revision$ $Date$ + */ +public class Enumerator implements Enumeration { + + // ----------------------------------------------------------- Constructors + + /** + * TheIterator
over which the Enumeration
+ * represented by this class actually operates.
+ */
+ private Iterator iterator = null;
+
+ /**
+ * Return an Enumeration over the values of the specified Collection.
+ *
+ * @param collection Collection whose values should be enumerated
+ */
+ public Enumerator(Collection collection) {
+ this(collection.iterator());
+ }
+
+ /**
+ * Return an Enumeration over the values of the specified Collection.
+ *
+ * @param collection Collection whose values should be enumerated
+ * @param clone true to clone iterator
+ */
+ public Enumerator(Collection collection, boolean clone) {
+ this(collection.iterator(), clone);
+ }
+
+ /**
+ * Return an Enumeration over the values returned by the
+ * specified Iterator.
+ *
+ * @param iterator Iterator to be wrapped
+ */
+ public Enumerator(Iterator iterator) {
+ super();
+ this.iterator = iterator;
+ }
+
+ /**
+ * Return an Enumeration over the values returned by the
+ * specified Iterator.
+ *
+ * @param iterator Iterator to be wrapped
+ * @param clone true to clone iterator
+ */
+ public Enumerator(Iterator iterator, boolean clone) {
+
+ super();
+ if (!clone) {
+ this.iterator = iterator;
+ } else {
+ List list = new ArrayList();
+ while (iterator.hasNext()) {
+ list.add(iterator.next());
+ }
+ this.iterator = list.iterator();
+ }
+
+ }
+
+ /**
+ * Return an Enumeration over the values of the specified Map.
+ *
+ * @param map Map whose values should be enumerated
+ */
+ public Enumerator(Map map) {
+ this(map.values().iterator());
+ }
+
+ /**
+ * Return an Enumeration over the values of the specified Map.
+ *
+ * @param map Map whose values should be enumerated
+ * @param clone true to clone iterator
+ */
+ public Enumerator(Map map, boolean clone) {
+ this(map.values().iterator(), clone);
+ }
+
+ /**
+ * Tests if this enumeration contains more elements.
+ *
+ * @return true
if and only if this enumeration object
+ * contains at least one more element to provide, false
+ * otherwise
+ */
+ public boolean hasMoreElements() {
+ return (iterator.hasNext());
+ }
+
+ /**
+ * Returns the next element of this enumeration if this enumeration
+ * has at least one more element to provide.
+ *
+ * @return the next element of this enumeration
+ *
+ * @exception NoSuchElementException if no more elements exist
+ */
+ public Object nextElement() throws NoSuchElementException {
+ return (iterator.next());
+ }
+
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/FastHttpDateFormat.java b/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/FastHttpDateFormat.java
new file mode 100644
index 0000000000..4f8f8d4cf1
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/FastHttpDateFormat.java
@@ -0,0 +1,226 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.wrapper.redirect;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * Utility class to generate HTTP dates.
+ * + *This source code is taken from Tomcat Apache
+ * + * @author Remy Maucherat + * @author Andrey Grebnev <andrey.grebnev@blandware.com> + * @version $Revision$ $Date$ + */ +public class FastHttpDateFormat { + + /** + * Current formatted date. + */ + protected static String currentDate = null; + + /** + * Instant on which the currentDate object was generated. + */ + protected static long currentDateGenerated = 0L; + + /** + * HTTP date format. + */ + protected static final SimpleDateFormat format = + new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US); + + /** + * Formatter cache. + */ + protected static final HashMap formatCache = new HashMap(); + + /** + * The set of SimpleDateFormat formats to use ingetDateHeader()
.
+ */
+ protected static final SimpleDateFormat formats[] = {
+ new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", Locale.US),
+ new SimpleDateFormat("EEEEEE, dd-MMM-yy HH:mm:ss zzz", Locale.US),
+ new SimpleDateFormat("EEE MMMM d HH:mm:ss yyyy", Locale.US)
+ };
+
+ /**
+ * GMT timezone - all HTTP dates are on GMT
+ */
+ protected final static TimeZone gmtZone = TimeZone.getTimeZone("GMT");
+
+ /**
+ * Parser cache.
+ */
+ protected static final HashMap parseCache = new HashMap();
+
+ static {
+
+ format.setTimeZone(gmtZone);
+
+ formats[0].setTimeZone(gmtZone);
+ formats[1].setTimeZone(gmtZone);
+ formats[2].setTimeZone(gmtZone);
+
+ }
+
+ /**
+ * Formats a specified date to HTTP format. If local format is not
+ * null
, it's used instead.
+ *
+ * @param value Date value to format
+ * @param threadLocalformat The format to use (or null
-- then
+ * HTTP format will be used)
+ * @return Formatted date
+ */
+ public static final String formatDate(long value,
+ DateFormat threadLocalformat) {
+
+ String cachedDate = null;
+ Long longValue = new Long(value);
+ try {
+ cachedDate = (String) formatCache.get(longValue);
+ } catch (Exception e) {
+ }
+ if (cachedDate != null)
+ return cachedDate;
+
+ String newDate = null;
+ Date dateValue = new Date(value);
+ if (threadLocalformat != null) {
+ newDate = threadLocalformat.format(dateValue);
+ synchronized (formatCache) {
+ updateCache(formatCache, longValue, newDate);
+ }
+ } else {
+ synchronized (formatCache) {
+ newDate = format.format(dateValue);
+ updateCache(formatCache, longValue, newDate);
+ }
+ }
+ return newDate;
+
+ }
+
+ /**
+ * Gets the current date in HTTP format.
+ *
+ * @return Current date in HTTP format
+ */
+ public static final String getCurrentDate() {
+
+ long now = System.currentTimeMillis();
+ if ((now - currentDateGenerated) > 1000) {
+ synchronized (format) {
+ if ((now - currentDateGenerated) > 1000) {
+ currentDateGenerated = now;
+ currentDate = format.format(new Date(now));
+ }
+ }
+ }
+ return currentDate;
+
+ }
+
+ /**
+ * Parses date with given formatters.
+ *
+ * @param value The string to parse
+ * @param formats Array of formats to use
+ * @return Parsed date (or null
if no formatter mached)
+ */
+ private static final Long internalParseDate
+ (String value, DateFormat[] formats) {
+ Date date = null;
+ for (int i = 0; (date == null) && (i < formats.length); i++) {
+ try {
+ date = formats[i].parse(value);
+ } catch (ParseException e) {
+ ;
+ }
+ }
+ if (date == null) {
+ return null;
+ }
+ return new Long(date.getTime());
+ }
+
+ /**
+ * Tries to parse the given date as an HTTP date. If local format list is not
+ * null
, it's used instead.
+ *
+ * @param value The string to parse
+ * @param threadLocalformats Array of formats to use for parsing.
+ * If null
, HTTP formats are used.
+ * @return Parsed date (or -1 if error occured)
+ */
+ public static final long parseDate(String value,
+ DateFormat[] threadLocalformats) {
+
+ Long cachedDate = null;
+ try {
+ cachedDate = (Long) parseCache.get(value);
+ } catch (Exception e) {
+ }
+ if (cachedDate != null)
+ return cachedDate.longValue();
+
+ Long date = null;
+ if (threadLocalformats != null) {
+ date = internalParseDate(value, threadLocalformats);
+ synchronized (parseCache) {
+ updateCache(parseCache, value, date);
+ }
+ } else {
+ synchronized (parseCache) {
+ date = internalParseDate(value, formats);
+ updateCache(parseCache, value, date);
+ }
+ }
+ if (date == null) {
+ return (-1L);
+ } else {
+ return date.longValue();
+ }
+
+ }
+
+ /**
+ * Updates cache.
+ *
+ * @param cache Cache to be updated
+ * @param key Key to be updated
+ * @param value New value
+ */
+ private static final void updateCache(HashMap cache, Object key,
+ Object value) {
+ if (value == null) {
+ return;
+ }
+ if (cache.size() > 1000) {
+ cache.clear();
+ }
+ cache.put(key, value);
+ }
+
+}
diff --git a/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/SavedHttpServletRequest.java b/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/SavedHttpServletRequest.java
new file mode 100644
index 0000000000..f6bffbd315
--- /dev/null
+++ b/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/SavedHttpServletRequest.java
@@ -0,0 +1,352 @@
+/* Copyright 2004, 2005 Acegi Technology Pty Limited
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.sf.acegisecurity.wrapper.redirect;
+
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+
+import javax.servlet.http.Cookie;
+import javax.servlet.http.HttpServletRequest;
+
+import net.sf.acegisecurity.intercept.web.SecurityEnforcementFilter;
+import net.sf.acegisecurity.wrapper.SecurityContextHolderAwareRequestWrapper;
+
+/**
+ * + * Object that saves the critical information from a request so that + * HTTP (header and parameter)-based authentication can reproduce it + * once the user has been authenticated. + *
+ * IMPLEMENTATION NOTE - It is assumed that this object is accessed + * only from the context of a single thread, so no synchronization around + * internal collection classes is performed. + *
+ * Note that SavedHttpServletRequest doesn't save uploaded file binary data, although + * it does save request parameters so that a POST transaction can be faithfully + * duplicated. On one hand it is unfortunate to lose such type of data, but on + * the other hand we don't store it in session because the data can be very big + * and this solution can overload restricted resources. + *
+ *The original source code from Apache Tomcat
+ * + * @see SecurityEnforcementFilter + * @see SecurityContextHolderAwareRequestWrapper + * @author Craig R. McClanahan + * @author Andrey Grebnev <andrey.grebnev@blandware.com> + * @version $Revision$ $Date$ + */ +public class SavedHttpServletRequest { + + /** + * This method provides ability to create SavedHttpServletRequest from HttpServletRequest + * @param request request to be saved + * @return saved request resulting SavedHttpServletRequest + */ + public static SavedHttpServletRequest saveRequest(HttpServletRequest request) { + if (request.getRequestURI() == null) + return null; + + // Create and populate a SavedHttpServletRequest object for this request + SavedHttpServletRequest saved = new SavedHttpServletRequest(); + Cookie cookies[] = request.getCookies(); + if (cookies != null) { + for (int i = 0; i < cookies.length; i++) + saved.addCookie(cookies[i]); + } + Enumeration names = request.getHeaderNames(); + while (names.hasMoreElements()) { + String name = (String) names.nextElement(); + Enumeration values = request.getHeaders(name); + while (values.hasMoreElements()) { + String value = (String) values.nextElement(); + saved.addHeader(name, value); + } + } + Enumeration locales = request.getLocales(); + while (locales.hasMoreElements()) { + Locale locale = (Locale) locales.nextElement(); + saved.addLocale(locale); + } + Map parameters = request.getParameterMap(); + Iterator paramNames = parameters.keySet().iterator(); + while (paramNames.hasNext()) { + String paramName = (String) paramNames.next(); + String paramValues[] = (String[]) parameters.get(paramName); + saved.addParameter(paramName, paramValues); + } + saved.setMethod(request.getMethod()); + saved.setQueryString(request.getQueryString()); + saved.setRequestURI(request.getRequestURI()); +// saved.setPathInfo(request.getPathInfo()); + + return saved; + } + + /** + * The set of Cookies associated with this Request. + */ + private ArrayList cookies = new ArrayList(); + + /** + * The set of Headers associated with this Request. Each key is a header + * name, while the value is a ArrayList containing one or more actual + * values for this header. The values are returned as an Iterator when + * you ask for them. + */ + private HashMap headers = new HashMap(); + + /** + * The set of Locales associated with this Request. + */ + private ArrayList locales = new ArrayList(); + + /** + * The request method used on this Request. + */ + private String method = null; + + /** + * The set of request parameters associated with this Request. Each + * entry is keyed by the parameter name, pointing at a String array of + * the corresponding values. + */ + private HashMap parameters = new HashMap(); + + /** + * The request pathInfo associated with this Request. + */ + private String pathInfo = null; + + /** + * The query string associated with this Request. + */ + private String queryString = null; + + /** + * The request URI associated with this Request. + */ + private String requestURI = null; + + /** + * Adds cookie to list of cookies + * + * @param cookie cookie to add + */ + public void addCookie(Cookie cookie) { + cookies.add(cookie); + } + + /** + * Adds header + * + * @param name header name + * @param value header value + */ + public void addHeader(String name, String value) { + ArrayList values = (ArrayList) headers.get(name); + if (values == null) { + values = new ArrayList(); + headers.put(name, values); + } + values.add(value); + } + + /** + * Adds locale + * + * @param locale locale to add + */ + public void addLocale(Locale locale) { + locales.add(locale); + } + + /** + * Adds parameter + * + * @param name parameter name + * @param values parameter values + */ + public void addParameter(String name, String values[]) { + parameters.put(name, values); + } + + /** + * Returns list of cookies + * + * @return list of cookies + */ + public List getCookies() { + return cookies; + } + + /** + * Returns iterator over header names + * + * @return iterator over header names + */ + public Iterator getHeaderNames() { + return (headers.keySet().iterator()); + } + + /** + * Returns iterator over header values + * + * @param name header name + * @return iterator over header values + */ + public Iterator getHeaderValues(String name) { + ArrayList values = (ArrayList) headers.get(name); + if (values == null) + return ((new ArrayList()).iterator()); + else + return (values.iterator()); + } + + /** + * Returns iterator over locales + * + * @return iterator over locales + */ + public Iterator getLocales() { + return (locales.iterator()); + } + + /** + * Returns request method + * + * @return request method + */ + public String getMethod() { + return (this.method); + } + + /** + * Returns parameters + * + * @return parameters map + */ + public Map getParameterMap() { + return parameters; + } + + /** + * Returns iterator over parameter names + * + * @return iterator over parameter names + */ + public Iterator getParameterNames() { + return (parameters.keySet().iterator()); + } + + /** + * Returns parameter values + * + * @param name parameter name + * @return parameter values + */ + public String[] getParameterValues(String name) { + return ((String[]) parameters.get(name)); + } + + /** + * Returns path info + * + * @return path info + */ + public String getPathInfo() { + return pathInfo; + } + + /** + * Returns query string + * + * @return query string + */ + public String getQueryString() { + return (this.queryString); + } + + /** + * Returns request URI + * + * @return request URI + */ + public String getRequestURI() { + return (this.requestURI); + } + + /** + * Gets uri with path info and query string + * + * @return uri with path info and query string + */ + public String getRequestURL() { + if (getRequestURI() == null) + return null; + + StringBuffer sb = new StringBuffer(getRequestURI()); +// if (getPathInfo() != null) { +// sb.append(getPathInfo()); +// } + if (getQueryString() != null) { + sb.append('?'); + sb.append(getQueryString()); + } + return sb.toString(); + } + + /** + * Sets request method + * + * @param method request method to set + */ + public void setMethod(String method) { + this.method = method; + } + + /** + * Sets path info + * + * @param pathInfo path info to set + */ + public void setPathInfo(String pathInfo) { + this.pathInfo = pathInfo; + } + + /** + * Sets query string + * + * @param queryString query string to set + */ + public void setQueryString(String queryString) { + this.queryString = queryString; + } + + /** + * Sets request URI + * + * @param requestURI request URI to set + */ + public void setRequestURI(String requestURI) { + this.requestURI = requestURI; + } + +} diff --git a/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/package.html b/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/package.html new file mode 100644 index 0000000000..cc8aa87f29 --- /dev/null +++ b/sandbox/src/main/java/org/acegisecurity/wrapper/redirect/package.html @@ -0,0 +1,8 @@ + +
+Redirection helper code for +SecurityContextHolderAwareRequestWrapper
+and
+SecurityEnforcementFilter
.
+
+