1   package com.atlassian.security.auth.trustedapps.filter;
2   
3   import com.atlassian.security.auth.trustedapps.CurrentApplication;
4   import com.atlassian.security.auth.trustedapps.Null;
5   import com.atlassian.security.auth.trustedapps.TrustedApplicationUtils;
6   import com.atlassian.security.auth.trustedapps.TrustedApplicationsManager;
7   import com.atlassian.security.auth.trustedapps.UserResolver;
8   import org.bouncycastle.util.encoders.Base64;
9   
10  import javax.servlet.Filter;
11  import javax.servlet.FilterChain;
12  import javax.servlet.FilterConfig;
13  import javax.servlet.ServletException;
14  import javax.servlet.ServletRequest;
15  import javax.servlet.ServletResponse;
16  import javax.servlet.http.HttpServletRequest;
17  import javax.servlet.http.HttpServletResponse;
18  import java.io.IOException;
19  import java.io.OutputStreamWriter;
20  import java.io.UnsupportedEncodingException;
21  import java.io.Writer;
22  import java.security.PublicKey;
23  
24  /**
25   * This filter serves two purposes:
26   * <ol>
27   * <li>Authenticates requests from trusted applications if the right certificate is
28   * present in the request.</li>
29   * <li>Returns the UID and public key of this application upon request so other servers can
30   * establish trusted relationship with this application as a client.</li>
31   * </ol>
32   * <p/>
33   * For the first purpose, the filter will intercept any calls to a page '/admin/appTrustCertificate'. Directory
34   * structure of the request will be ignored. The returned page will contain 2 lines:
35   * <ul>
36   * <li> ID </li>
37   * <li> public key BASE64 encoded </li>
38   * </ul>
39   * <p/>
40   * For the second purpose the following header parameters must be present and valid:
41   * {@link CurrentApplication#HEADER_TRUSTED_APP_CERT} {@link CurrentApplication#HEADER_TRUSTED_APP_ID}
42   * <p/>
43   * If the authentication should fail a message will be set in the response header:
44   * {@link CurrentApplication#HEADER_TRUSTED_APP_ERROR}
45   */
46  public class TrustedApplicationsFilter implements Filter
47  {
48      static final class Status
49      {
50          static final String ERROR = "ERROR";
51          static final String OK = "OK";
52      }
53  
54      private final CertificateServer certificateServer;
55      private final Authenticator authenticator;
56  
57      private FilterConfig filterConfig = null;
58  
59      private final AuthenticationController authenticationController;
60      private final AuthenticationListener authenticationListener;
61  
62      public TrustedApplicationsFilter(TrustedApplicationsManager appManager, UserResolver resolver, AuthenticationController authenticationController, AuthenticationListener authenticationListener)
63      {
64          this(new CertificateServerImpl(appManager), new TrustedApplicationFilterAuthenticator(appManager, resolver, authenticationController), authenticationController, authenticationListener);
65      }
66  
67      protected TrustedApplicationsFilter(CertificateServer certificateServer, Authenticator authenticator, AuthenticationController authenticationController, AuthenticationListener authenticationListener)
68      {
69          Null.not("certificateServer", certificateServer);
70          Null.not("authenticator", authenticator);
71          Null.not("authenticationController", authenticationController);
72          Null.not("authenticationListener", authenticationListener);
73  
74          this.certificateServer = certificateServer;
75          this.authenticator = authenticator;
76          this.authenticationController = authenticationController;
77          this.authenticationListener = authenticationListener;
78      }
79  
80      public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException
81      {
82          // if this is a certificate request serve the certificate back and return
83          final HttpServletRequest request = (HttpServletRequest) req;
84          final HttpServletResponse response = (HttpServletResponse) res;
85  
86          if (getPathInfo(request).endsWith(TrustedApplicationUtils.Constant.CERTIFICATE_URL_PATH))
87          {
88              response.setContentType("text/plain");
89              final OutputStreamWriter outputStreamWriter = new OutputStreamWriter(response.getOutputStream());
90              certificateServer.writeCertificate(outputStreamWriter);
91              outputStreamWriter.flush();
92              return;
93          }
94  
95          final boolean isTrustedAppCall = authenticate(request, response);
96          try
97          {
98              chain.doFilter(request, res);
99          }
100         finally
101         {
102             if (isTrustedAppCall && request.getSession(false) != null)
103             {
104                 request.getSession().invalidate();
105             }
106         }
107     }
108 
109     boolean authenticate(HttpServletRequest request, HttpServletResponse response)
110     {
111         if (authenticationController.shouldAttemptAuthentication(request))
112         {
113             final Authenticator.Result result = authenticator.authenticate(request, response);
114 
115             if (result.getStatus() == Authenticator.Result.Status.SUCCESS)
116             {
117                 authenticationListener.authenticationSuccess(result, request, response);
118                 response.setHeader(TrustedApplicationUtils.Header.Response.STATUS, Status.OK);
119                 return true;
120             }
121 
122             if (result.getStatus() == Authenticator.Result.Status.FAILED)
123             {
124                 authenticationListener.authenticationFailure(result, request, response);
125             }
126             else
127             {
128                 authenticationListener.authenticationError(result, request, response);
129             }
130         }
131         else
132         {
133             authenticationListener.authenticationNotAttempted(request, response);
134         }
135         return false;
136     }
137 
138     protected String getPathInfo(HttpServletRequest request)
139     {
140         String context = request.getContextPath();
141         String uri = request.getRequestURI();
142         if (context != null && context.length() > 0)
143         {
144             return uri.substring(context.length());
145         }
146         else
147         {
148             return uri;
149         }
150     }
151 
152     public void init(FilterConfig config)
153     {
154         this.filterConfig = config;
155     }
156 
157     public void destroy()
158     {
159         filterConfig = null;
160     }
161 
162     /**
163      * @deprecated Not needed in latest version of Servlet 2.3 API
164      */
165     public FilterConfig getFilterConfig()
166     {
167         return filterConfig;
168     }
169 
170     /**
171      * @deprecated Not needed in latest version of Servlet 2.3 API - replaced by init().
172      */
173     public void setFilterConfig(FilterConfig filterConfig)
174     {
175         if (filterConfig != null) // it seems that Orion 1.5.2 calls this with a null config.
176         {
177             init(filterConfig);
178         }
179     }
180 
181     /**
182      * serve the CurrentApplication's certificate
183      */
184     public interface CertificateServer
185     {
186         void writeCertificate(Writer writer) throws IOException;
187     }
188 
189     public static class CertificateServerImpl implements CertificateServer
190     {
191         final TrustedApplicationsManager appManager;
192 
193         public CertificateServerImpl(TrustedApplicationsManager appManager)
194         {
195             this.appManager = appManager;
196         }
197 
198         public void writeCertificate(Writer writer) throws IOException
199         {
200             CurrentApplication currentApplication = appManager.getCurrentApplication();
201             PublicKey publicKey = currentApplication.getPublicKey();
202 
203             try
204             {
205                 writer.write(currentApplication.getID());
206                 writer.write("\n");
207 
208                 byte[] key = publicKey.getEncoded();
209                 writer.write(new String(Base64.encode(key), TrustedApplicationUtils.Constant.CHARSET_NAME));
210                 writer.write("\n");
211                 writer.write(TrustedApplicationUtils.Constant.VERSION.toString());
212                 writer.write("\n");
213                 writer.write(TrustedApplicationUtils.Constant.MAGIC);
214                 writer.flush();
215             }
216             catch (UnsupportedEncodingException ex)
217             {
218                 throw new AssertionError(ex);
219             }
220             catch (IOException e)
221             {
222                 throw new RuntimeException(e);
223             }
224         }
225     }
226 }