1   package com.atlassian.security.auth.trustedapps;
2   
3   import java.text.MessageFormat;
4   import java.util.HashMap;
5   import java.util.Map;
6   
7   /**
8    * TransportErrorMessages are reported when a client makes a TrustedApplication request.
9    * <p>
10   * The String format of these is important. They basically consist of three elements:
11   * <ol>
12   * <li>Error Code (one of the {@link Code} constants)
13   * <li>Message (a MessageFormat formatted message)
14   * <li>Parameters (encoded as a JSON array)
15   * </ol>
16   * These are separated by a semi-colon tab.
17   * <p>
18   * To parse a String into a {@link TransportErrorMessage} use a {@link Parser}. If the String is not in the correct
19   * format, exceptions will be thrown. The format to the {@link #toString()} method and the format the {@link Parser}
20   * accepts must be symmetric and constant.
21   * 
22   * @TODO replace inner classes with enum in JDK5
23   */
24  public class TransportErrorMessage
25  {
26      static final Parser PARSER = new Parser();
27  
28      // --------------------------------------------------------------------------------------------------------- members
29  
30      private final Code code;
31      private final String message;
32      private final String[] params;
33  
34      // ----------------------------------------------------------------------------------------------------------- ctors
35  
36      TransportErrorMessage(final Code code, final String message, final String[] params)
37      {
38          Null.not("code", code);
39          Null.not("params", params);
40          Null.not("message", message);
41          for (int i = 0; i < params.length; i++)
42          {
43              Null.not(String.valueOf(i), params[i]);
44          }
45  
46          this.code = code;
47          this.message = message;
48          this.params = params;
49      }
50  
51      TransportErrorMessage(final Code code, final String message)
52      {
53          this(code, message, new String[0]);
54      }
55  
56      TransportErrorMessage(final Code code, final String message, final String param)
57      {
58          this(code, message, new String[] { param });
59      }
60  
61      TransportErrorMessage(final Code code, final String message, final String one, final String two)
62      {
63          this(code, message, new String[] { one, two });
64      }
65  
66      TransportErrorMessage(final Code code, final String message, final String one, final String two, final String three)
67      {
68          this(code, message, new String[] { one, two, three });
69      }
70  
71      // ------------------------------------------------------------------------------------------------------- accessors
72  
73      public Code getCode()
74      {
75          return code;
76      }
77  
78      public String[] getParameters()
79      {
80          return params.clone();
81      }
82  
83      public String getFormattedMessage()
84      {
85          return MessageFormat.format(message, (Object[]) params);
86      }
87  
88      /**
89       * String representation of a {@link TransportErrorMessage}. Of the form:
90       * 
91       * <pre>
92       * code {@link Parser#SEPARATOR} message {@link Parser#SEPARATOR} params
93       * </pre>
94       * 
95       * where the params are encoded as a JSON array.
96       * <p>
97       * Note: The String representation of a {@link TransportErrorMessage} is sent across the wire as an error to the
98       * client. Therefore this format is static and must not change.
99       * 
100      * @see Parser#parse(String)
101      */
102     @Override
103     public String toString()
104     {
105         return PARSER.toString(this);
106     }
107 
108     // ------------------------------------------------------------------------------------------------- specializations
109 
110     static class System extends TransportErrorMessage
111     {
112         System(final Throwable cause, final String appId)
113         {
114             super(Code.SYSTEM, "Exception: {0} occurred serving request for application: {1}", cause.toString(), appId);
115         }
116     }
117 
118     static class BadMagicNumber extends TransportErrorMessage
119     {
120         public BadMagicNumber(final String keyName, final String appId)
121         {
122             super(Code.BAD_MAGIC, "Unable to decrypt certificate {0} for application {1}", keyName, appId);
123         }
124     }
125 
126     public static class BadProtocolVersion extends TransportErrorMessage
127     {
128         public BadProtocolVersion(final String versionString)
129         {
130             super(Code.BAD_PROTOCOL_VERSION, "Bad protocol version: {0}", versionString);
131         }
132     }
133 
134     /**
135      * AppId not found in request
136      */
137     public static class ApplicationIdNotFoundInRequest extends TransportErrorMessage
138     {
139         public ApplicationIdNotFoundInRequest()
140         {
141             super(Code.APP_ID_NOT_FOUND, "Application ID not found in request");
142         }
143     }
144 
145     public static class SecretKeyNotFoundInRequest extends TransportErrorMessage
146     {
147         public SecretKeyNotFoundInRequest()
148         {
149             super(Code.SECRET_KEY_NOT_FOUND, "Secret Key not found in request");
150         }
151     }
152 
153     public static class MagicNumberNotFoundInRequest extends TransportErrorMessage
154     {
155         public MagicNumberNotFoundInRequest()
156         {
157             super(Code.MAGIC_NUMBER_NOT_FOUND, "Magic Number not found in request");
158         }
159     }
160 
161     public static class ApplicationUnknown extends TransportErrorMessage
162     {
163         public ApplicationUnknown(final String appId)
164         {
165             super(Code.APP_UNKNOWN, "Unknown Application: {0}", appId);
166         }
167     }
168 
169     public static class UserUnknown extends TransportErrorMessage
170     {
171         public UserUnknown(final String userName)
172         {
173             super(Code.USER_UNKNOWN, "Unknown User: {0}", userName);
174         }
175     }
176 
177     public static class PermissionDenied extends TransportErrorMessage
178     {
179         public PermissionDenied()
180         {
181             super(Code.PERMISSION_DENIED, "Permission Denied");
182         }
183     }
184     
185     public static class BadSignature extends TransportErrorMessage
186     {
187         public BadSignature(String url)
188         {
189             super(Code.BAD_SIGNATURE, "Bad signature for URL: {0}", url);
190         }
191         
192         public BadSignature()
193         {
194             super(Code.BAD_SIGNATURE, "Missing signature in a v2 request");
195         }
196     }
197 
198     // --------------------------------------------------------------------------------------------------- inner classes
199 
200     /**
201      * Used to convert an error message String from the server at the client to a TransportErrorMessage.
202      * <p>
203      * String format is:
204      * 
205      * <pre>
206      * code SEPARATOR message SEPARATOR args
207      * </pre>
208      * 
209      * args are JSON array formatted.
210      * 
211      * @see TransportErrorMessage#toString()
212      */
213     static class Parser
214     {
215         static final String SEPARATOR = ";\t";
216 
217         TransportErrorMessage parse(final String inputItring) throws IllegalArgumentException
218         {
219             Null.not("inputString", inputItring);
220             final String[] args = inputItring.split(SEPARATOR);
221             if (args.length != 3)
222             {
223                 throw new IllegalArgumentException("Cannot split message into Code, Message, Parameters:" + inputItring);
224             }
225             final Code code = Code.get(args[0]);
226             final String[] params = StringUtil.split(args[2]);
227             return new TransportErrorMessage(code, args[1], params);
228         }
229 
230         /**
231          * Format a String representation of a {@link TransportErrorMessage} in such a way as the {@link #parse(String)}
232          * method can parse it.
233          * 
234          * @param msg
235          *            the message to turn into a String
236          * @return the String representation of the message
237          */
238         String toString(final TransportErrorMessage msg)
239         {
240             return new StringBuffer(msg.code.getCode()).append(Parser.SEPARATOR).append(msg.message).append(Parser.SEPARATOR).append(
241                 StringUtil.toString(msg.params)).toString();
242         }
243     }
244 
245     /**
246      * Typesafe enum that contains all known error codes.
247      * <p>
248      * Note: for backwards compatibility, do not ever remove a code once its been released. Deprecate if necessary, but
249      * not remove.
250      */
251     public static final class Code
252     {
253         private static final Map<String, Code> ALL = new HashMap<String, Code>();
254 
255         public static final Code UNKNOWN = new Code(Severity.ERROR, "UNKNOWN");
256 
257         public static final Code APP_UNKNOWN = new Code(Severity.ERROR, "APP_UNKNOWN");
258         public static final Code SYSTEM = new Code(Severity.ERROR, "SYSTEM");
259         public static final Code BAD_PROTOCOL_VERSION = new Code(Severity.ERROR, "BAD_PROTOCOL_VERSION");
260         public static final Code APP_ID_NOT_FOUND = new Code(Severity.ERROR, "APP_ID_NOT_FOUND");
261         public static final Code SECRET_KEY_NOT_FOUND = new Code(Severity.ERROR, "SECRET_KEY_NOT_FOUND");
262         public static final Code MAGIC_NUMBER_NOT_FOUND = new Code(Severity.ERROR, "MAGIC_NUMBER_NOT_FOUND");
263 
264         public static final Code BAD_REMOTE_IP = new Code(Severity.FAIL, "BAD_REMOTE_IP");
265         public static final Code BAD_XFORWARD_IP = new Code(Severity.FAIL, "BAD_XFORWARD_IP");
266         public static final Code BAD_URL = new Code(Severity.FAIL, "BAD_URL");
267         public static final Code OLD_CERT = new Code(Severity.FAIL, "OLD_CERT");
268         public static final Code MISSING_CERT = new Code(Severity.FAIL, "MISSING_CERT");
269         public static final Code BAD_MAGIC = new Code(Severity.FAIL, "BAD_MAGIC");
270         public static final Code USER_UNKNOWN = new Code(Severity.ERROR, "USER_UNKNOWN");
271         public static final Code PERMISSION_DENIED = new Code(Severity.ERROR, "PERMISSION_DENIED");
272         public static final Code BAD_SIGNATURE = new Code(Severity.FAIL, "BAD_SIGNATURE");
273 
274         static Code get(final String code)
275         {
276             final Code result = ALL.get(code);
277             return (result == null) ? Code.UNKNOWN : result;
278         }
279 
280         private final Severity severity;
281         private final String code;
282 
283         private Code(final Severity severity, final String code)
284         {
285             Null.not("severity", severity);
286             Null.not("code", code);
287             this.severity = severity;
288             this.code = code;
289             if (ALL.containsKey(code))
290             {
291                 throw new IllegalArgumentException(code + " is already mapped as a " + this.getClass().getName());
292             }
293             ALL.put(code, this);
294         }
295 
296         public Severity getSeverity()
297         {
298             return severity;
299         }
300 
301         public String getCode()
302         {
303             return code;
304         }
305 
306         public static final class Severity
307         {
308             static final Severity ERROR = new Severity("ERROR");
309             static final Severity FAIL = new Severity("FAIL");
310 
311             private final String name;
312 
313             private Severity(final String name)
314             {
315                 this.name = name;
316             }
317 
318             @Override
319             public String toString()
320             {
321                 return name;
322             }
323         }
324     }
325 }