View Javadoc

1   package com.atlassian.seraph.util;
2   
3   import com.atlassian.seraph.config.SecurityConfig;
4   import com.atlassian.seraph.config.SecurityConfigFactory;
5   import com.atlassian.seraph.filter.SecurityFilter;
6   import com.atlassian.seraph.RequestParameterConstants;
7   
8   import javax.servlet.http.HttpServletRequest;
9   
10  import java.io.UnsupportedEncodingException;
11  import java.net.URLEncoder;
12  
13  /**
14   * Utilities for login link redirection.
15   */
16  public class RedirectUtils
17  {
18      private static final String HTTP_BASIC_AUTH_HEADER = "Authorization";
19  
20      /**
21       * Returns a login URL that would log the user in to access resource indicated by <code>request</code>.
22       * <p>
23       * For instance, if <code>request</code> is for protected path "/browse/JRA-123" and the user must login before accessing this resource, this
24       * method might return "/login.jsp?os_destination=%2Fbrowse%2FJRA-123". Presumably the login.jsp page will redirect back to 'os_destination' once
25       * logged in.
26       * <p>
27       * The returned path is derived from the <code>login.url</code> parameter in seraph-config.xml, which in the example above would be
28       * "/login.jsp?os_destination={originalurl}". The '${originalurl}' token is replaced at runtime with a relative or absolute path to the original
29       * resource requested by <code>request</code> ('/browse/JRA-123').
30       * <p>
31       * Both the returned URL and the ${originalurl} replacement URL may be absolute or root-relative, depending on whether the seraph-config.xml
32       * <code>login.url</code> parameter is. This allows for redirection to external <acronym title="Single Sign-on">SSO</acronym> apps, which are
33       * passed an absolute path to the originally requested resource.
34       * <p>
35       * No actual permission checks are performed to determine whether the user needs to log in to access the resource. The caller is assumed to have
36       * done this before calling this method.
37       *
38       * @param request The original request made by the user for a resource.
39       * @return A root-relative or absolute URL of a login link that would log the user in to access the resource.
40       */
41      public static String getLoginUrl(final HttpServletRequest request)
42      {
43          final SecurityConfig securityConfig = SecurityConfigFactory.getInstance();
44          final String loginURL = securityConfig.getLoginURL();
45          return getLoginURL(loginURL, request);
46      }
47  
48      /**
49       * Returns a login URL that would log the user in to access resource indicated by <code>request</code>. Identical to
50       * {@link #getLoginUrl(javax.servlet.http.HttpServletRequest)}, except uses the 'link.login.url' parameter in seraph-config.xml instead of
51       * 'login.url', which allows for different login pages depending on whether invoked from a link ("link.login.url") or from a servlet filter that
52       * intercepted a request ("login.url").
53       *
54       * @see #getLoginUrl(javax.servlet.http.HttpServletRequest) for parameters, etc
55       */
56      public static String getLinkLoginURL(final HttpServletRequest request)
57      {
58          final SecurityConfig securityConfig = SecurityConfigFactory.getInstance();
59          final String loginURL = securityConfig.getLinkLoginURL();
60          return getLoginURL(loginURL, request);
61      }
62  
63      private static String getLoginURL(String loginURL, final HttpServletRequest request)
64      {
65          final boolean externalLoginLink = isExternalLoginLink(loginURL);
66          loginURL = replaceOriginalURL(loginURL, request, externalLoginLink);
67          if (externalLoginLink)
68          {
69              return loginURL;
70          }
71          return request.getContextPath() + loginURL;
72      }
73  
74      private static boolean isExternalLoginLink(final String loginURL)
75      {
76          return (loginURL.indexOf("://") != -1);
77      }
78  
79      /**
80       * Replace ${originalurl} token in a string with a URL for a Request.
81       */
82      private static String replaceOriginalURL(final String loginURL, final HttpServletRequest request, final boolean external)
83      {
84          final int i = loginURL.indexOf("${originalurl}");
85          if (i != -1)
86          {
87              final String originalURL = getOriginalURL(request, external);
88              final String osDest = request.getParameter(RequestParameterConstants.OS_DESTINATION);
89              return loginURL.substring(0, i) + ((osDest != null) ? encodeUrl(osDest) : encodeUrl(originalURL)) + loginURL.substring(i + "${originalurl}".length());
90          }
91          return loginURL;
92      }
93  
94      private static String encodeUrl(final String url)
95      {
96          try
97          {
98              return URLEncoder.encode(url, "UTF-8");
99          }
100         catch (final UnsupportedEncodingException e)
101         {
102             throw new AssertionError(e);
103         }
104     }
105 
106     /**
107      * Recreate a URL from a Request.
108      */
109     private static String getOriginalURL(final HttpServletRequest request, final boolean external)
110     {
111         final String originalURL = (String) request.getAttribute(SecurityFilter.ORIGINAL_URL);
112         if (originalURL != null)
113         {
114             if (external)
115             {
116                 return getServerNameAndPath(request) + originalURL;
117             }
118             return originalURL;
119         }
120 
121         if (external)
122         {
123             return request.getRequestURL() + (request.getQueryString() == null ? "" : "?" + request.getQueryString());
124         }
125         return request.getServletPath() + (request.getPathInfo() == null ? "" : request.getPathInfo()) + (request.getQueryString() == null ? "" : "?" + request.getQueryString());
126     }
127 
128     /**
129      * Reconstruct the request URL from a HttpServletRequest.
130      */
131     public static String getServerNameAndPath(final HttpServletRequest request)
132     {
133         final StringBuffer buf = new StringBuffer();
134         buf.append(request.getScheme()).append("://").append(request.getServerName());
135         if (!(("http".equals(request.getScheme()) && (request.getServerPort() == 80)) || ("https".equals(request.getScheme()) && (request.getServerPort() == 443))))
136         {
137             buf.append(":").append(request.getServerPort());
138         }
139         buf.append(request.getContextPath());
140         return buf.toString();
141     }
142 
143     /**
144      * Check whether the request authentication strategy is using
145      * <a href="http://www.ietf.org/rfc/rfc2617.txt">HTTP Basic Authentication</a>
146      *
147      * @param request the current {@link HttpServletRequest}
148      * @param basicAuthParameterName the name of the request parameter that sets the type of authorisation to apply
149      * for the current <code>request</code>
150      */
151     public static boolean isBasicAuthentication(final HttpServletRequest request, final String basicAuthParameterName)
152     {
153         return hasHttpBasicAuthenticationRequestParameter(request, basicAuthParameterName) || hasHttpBasicAuthenticationRequestHeader(request);
154     }
155 
156     /**
157      * Check whether the * <a href="http://www.ietf.org/rfc/rfc2617.txt">HTTP Basic Authentication</a> header is set on
158      * the request header
159      *
160      * @param request the current {@link HttpServletRequest}
161      * @return <code>true</code> if the <code>Authorisation</code> header was set to <code>Basic</code>,
162      *         <code>false</code> otherwise.
163      */
164     static boolean hasHttpBasicAuthenticationRequestHeader(HttpServletRequest request)
165     {
166         return containsIgnoreCase(request.getHeader(HTTP_BASIC_AUTH_HEADER), HttpServletRequest.BASIC_AUTH);
167     }
168 
169     /**
170      * Check whether the * <a href="http://www.ietf.org/rfc/rfc2617.txt">HTTP Basic Authentication</a> is set on
171      * the request parameter. The value of the request parameter is case insensitive.
172      *
173      * @param request the current {@link HttpServletRequest}
174      * @param basicAuthParameterName the name of the request parameter specifying the authentication type
175      * @return <code>true</code> if the request contains the give request parameter sepcifying <code>basic</code> as the
176      * authentication type, <code>false</code> otherwise.
177      *
178      */
179     static boolean hasHttpBasicAuthenticationRequestParameter(HttpServletRequest request, String basicAuthParameterName)
180     {
181         // here we work on the query string to avoid request parsing in the filter
182         String queryString = request.getQueryString() == null ? "" : request.getQueryString();
183 
184         queryString = "&" + queryString + "&"; // for easy pattern matching
185         return queryString.indexOf("&" + basicAuthParameterName + "=" + HttpServletRequest.BASIC_AUTH.toLowerCase() + "&") != -1;
186     }
187 
188     /**
189      * Appends the path to the context, dealing with any missing slashes properly. Does NOT resolve the path using URL resolution rules. Does NOT
190      * attempt to normalise the resulting URL.
191      *
192      * @param context
193      *            a context path as returned by HttpServletRequest.getContextPath, e.g. "/confluence". If null, it will be treated as the default
194      *            context ("").
195      * @param path
196      *            a path to be appended to the context, e.g. "/homepage.action". If this is null, the context will be returned if it is non-null,
197      *            otherwise the empty string will be returned.
198      * @return a URL of the given path within the context, e.g. "/confluence/homepage.action".
199      */
200     public static String appendPathToContext(String context, final String path)
201     {
202         if (context == null)
203         {
204             context = "";
205         }
206         if (path == null)
207         {
208             return context;
209         }
210 
211         final StringBuffer result = new StringBuffer(context);
212         if (!context.endsWith("/"))
213         {
214             result.append("/");
215         }
216 
217         String pathToAppend = path;
218         if (pathToAppend.startsWith("/"))
219         {
220             pathToAppend = pathToAppend.substring(1);
221         }
222 
223         result.append(pathToAppend);
224         return result.toString();
225     }
226 
227     /*
228      * copied from commons-lang 2.4
229      */
230     static boolean containsIgnoreCase(String str, String searchStr)
231     {
232         if (str == null || searchStr == null)
233         {
234             return false;
235         }
236         return contains(str.toUpperCase(), searchStr.toUpperCase());
237     }
238 
239     /*
240      * copied from commons-lang 2.4
241      */
242     static boolean contains(String str, String searchStr)
243     {
244         if (str == null || searchStr == null)
245         {
246             return false;
247         }
248         return str.indexOf(searchStr) >= 0;
249     }
250 }