1   package com.atlassian.seraph.util;
2   
3   import org.apache.commons.codec.binary.Base64;
4   import org.apache.log4j.Category;
5   
6   import javax.servlet.ServletContext;
7   import javax.servlet.ServletRequest;
8   import javax.servlet.http.HttpServletRequest;
9   
10  import com.atlassian.seraph.config.SecurityConfig;
11  import com.atlassian.seraph.auth.Authenticator;
12  
13  public class SecurityUtils
14  {
15      private static final Category log = Category.getInstance(SecurityUtils.class);
16  
17      /** The Basic authorization encoding method. Includes trailing space, for convenient startsWith() checks, substring() and concatenation. */
18      private static final String BASIC_AUTHZ_TYPE_PREFIX = "Basic ";
19  
20      private static final String ALREADY_FILTERED = "loginfilter.already.filtered";
21  
22      public static Authenticator getAuthenticator(ServletContext servletContext)
23      {
24          SecurityConfig securityConfig = (SecurityConfig) servletContext.getAttribute(SecurityConfig.STORAGE_KEY);
25  
26          if (securityConfig.getAuthenticator() == null)
27          {
28              log.error("ack! Authenticator is null!!!");
29          }
30  
31          return securityConfig.getAuthenticator();
32      }
33  
34      public static boolean isBasicAuthorizationHeader(String header)
35      {
36          return (header != null) && header.startsWith(BASIC_AUTHZ_TYPE_PREFIX);
37      }
38  
39      /**
40       * Extracts the username and password from the given header string (including the 'Basic ' prefix).
41       * @param basicAuthorizationHeader the header to decode.
42       * @return the credentials, or a username and password of "" if there were no credentials to decode.
43       */
44      public static UserPassCredentials decodeBasicAuthorizationCredentials(String basicAuthorizationHeader)
45      {
46          final String base64Token = basicAuthorizationHeader.substring(BASIC_AUTHZ_TYPE_PREFIX.length());
47          // note: header fields are not UTF-8, but ISO Latin 1, even when base 64 encoded
48          final String token = SecurityUtils.decodeBase64(base64Token, CharsetUtils.ISO_LATIN_1_CHARSET);
49  
50          String userName = "";
51          String password = "";
52  
53          final int delim = token.indexOf(":");
54  
55          if (delim != -1)
56          {
57              userName = token.substring(0, delim);
58              password = token.substring(delim + 1);
59          }
60          return new UserPassCredentials(userName, password);
61      }
62  
63      /**
64       * Reverses the operation of decodeBasicAuthorizationCredentials. Mainly for unit tests, or ServletFilters faking
65       * basic authorization.
66       * @param username the username to encode.
67       * @param password the password to encode.
68       * @return the encoded credentials.
69       */
70      public static String encodeBasicAuthorizationCredentials(String username, String password)
71      {
72          return BASIC_AUTHZ_TYPE_PREFIX + encodeBase64(username + ":" + password, CharsetUtils.ISO_LATIN_1_CHARSET);
73  
74      }
75  
76      /**
77       * Disables seraph filtering
78       * @param request
79       * @since 2.5
80       */
81      public static void disableSeraphFiltering(ServletRequest request)
82      {
83          request.setAttribute(ALREADY_FILTERED, true);
84      }
85  
86      /**
87       * Checks if Seraph filtering is disabled
88       * @param request
89       * @return disabled
90       * @since 2.5
91       */
92      public static boolean isSeraphFilteringDisabled(ServletRequest request)
93      {
94          return request.getAttribute(ALREADY_FILTERED) != null;
95      }
96  
97      /**
98       * Decodes the given Base64 encoded string using the given charset.
99       * @param encodedString the string to decode.
100      * @param charset the charset to decode it with.
101      * @return the decoded string.
102      */
103     private static String decodeBase64(String encodedString, String charset)
104     {
105         byte[] encodedBytes = CharsetUtils.bytesFromString(encodedString, CharsetUtils.US_ASCII_CHARSET);
106         byte[] decodedBytes = Base64.decodeBase64(encodedBytes);
107         return CharsetUtils.stringFromBytes(decodedBytes, charset);
108     }
109 
110     private static String encodeBase64(String decodedString, String charset)
111     {
112         byte[] decodedBytes = CharsetUtils.bytesFromString(decodedString, charset);
113         byte[] encodedBytes = Base64.encodeBase64(decodedBytes);
114         return CharsetUtils.stringFromBytes(encodedBytes, CharsetUtils.US_ASCII_CHARSET);
115     }
116 
117     /**
118      * User credentials including a username and a password.
119      * TODO Eliminate duplication between this class and {@link com.atlassian.seraph.filter.PasswordBasedLoginFilter.UserPasswordPair}
120      */
121     public static class UserPassCredentials
122     {
123         private final String username;
124         private final String password;
125 
126         public UserPassCredentials(String username, String password)
127         {
128             this.username = username;
129             this.password = password;
130         }
131 
132         public String getUsername()
133         {
134             return username;
135         }
136 
137         public String getPassword()
138         {
139             return password;
140         }
141     }
142 }