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     // --------------------------------------------------------------------------------------------------- inner classes
186 
187     /**
188      * Used to convert an error message String from the server at the client to a TransportErrorMessage.
189      * <p>
190      * String format is:
191      * 
192      * <pre>
193      * code SEPARATOR message SEPARATOR args
194      * </pre>
195      * 
196      * args are JSON array formatted.
197      * 
198      * @see TransportErrorMessage#toString()
199      */
200     static class Parser
201     {
202         static final String SEPARATOR = ";\t";
203 
204         TransportErrorMessage parse(final String inputItring) throws IllegalArgumentException
205         {
206             Null.not("inputString", inputItring);
207             final String[] args = inputItring.split(SEPARATOR);
208             if (args.length != 3)
209             {
210                 throw new IllegalArgumentException("Cannot split message into Code, Message, Parameters:" + inputItring);
211             }
212             final Code code = Code.get(args[0]);
213             final String[] params = StringUtil.split(args[2]);
214             return new TransportErrorMessage(code, args[1], params);
215         }
216 
217         /**
218          * Format a String representation of a {@link TransportErrorMessage} in such a way as the {@link #parse(String)}
219          * method can parse it.
220          * 
221          * @param msg
222          *            the message to turn into a String
223          * @return the String representation of the message
224          */
225         String toString(final TransportErrorMessage msg)
226         {
227             return new StringBuffer(msg.code.getCode()).append(Parser.SEPARATOR).append(msg.message).append(Parser.SEPARATOR).append(
228                 StringUtil.toString(msg.params)).toString();
229         }
230     }
231 
232     /**
233      * Typesafe enum that contains all known error codes.
234      * <p>
235      * Note: for backwards compatibility, do not ever remove a code once its been released. Deprecate if necessary, but
236      * not remove.
237      */
238     public static final class Code
239     {
240         private static final Map<String, Code> ALL = new HashMap<String, Code>();
241 
242         public static final Code UNKNOWN = new Code(Severity.ERROR, "UNKNOWN");
243 
244         public static final Code APP_UNKNOWN = new Code(Severity.ERROR, "APP_UNKNOWN");
245         public static final Code SYSTEM = new Code(Severity.ERROR, "SYSTEM");
246         public static final Code BAD_PROTOCOL_VERSION = new Code(Severity.ERROR, "BAD_PROTOCOL_VERSION");
247         public static final Code APP_ID_NOT_FOUND = new Code(Severity.ERROR, "APP_ID_NOT_FOUND");
248         public static final Code SECRET_KEY_NOT_FOUND = new Code(Severity.ERROR, "SECRET_KEY_NOT_FOUND");
249         public static final Code MAGIC_NUMBER_NOT_FOUND = new Code(Severity.ERROR, "MAGIC_NUMBER_NOT_FOUND");
250 
251         public static final Code BAD_REMOTE_IP = new Code(Severity.FAIL, "BAD_REMOTE_IP");
252         public static final Code BAD_XFORWARD_IP = new Code(Severity.FAIL, "BAD_XFORWARD_IP");
253         public static final Code BAD_URL = new Code(Severity.FAIL, "BAD_URL");
254         public static final Code OLD_CERT = new Code(Severity.FAIL, "OLD_CERT");
255         public static final Code MISSING_CERT = new Code(Severity.FAIL, "MISSING_CERT");
256         public static final Code BAD_MAGIC = new Code(Severity.FAIL, "BAD_MAGIC");
257         public static final Code USER_UNKNOWN = new Code(Severity.ERROR, "USER_UNKNOWN");
258         public static final Code PERMISSION_DENIED = new Code(Severity.ERROR, "PERMISSION_DENIED");
259 
260         static Code get(final String code)
261         {
262             final Code result = ALL.get(code);
263             return (result == null) ? Code.UNKNOWN : result;
264         }
265 
266         private final Severity severity;
267         private final String code;
268 
269         private Code(final Severity severity, final String code)
270         {
271             Null.not("severity", severity);
272             Null.not("code", code);
273             this.severity = severity;
274             this.code = code;
275             if (ALL.containsKey(code))
276             {
277                 throw new IllegalArgumentException(code + " is already mapped as a " + this.getClass().getName());
278             }
279             ALL.put(code, this);
280         }
281 
282         public Severity getSeverity()
283         {
284             return severity;
285         }
286 
287         public String getCode()
288         {
289             return code;
290         }
291 
292         public static final class Severity
293         {
294             static final Severity ERROR = new Severity("ERROR");
295             static final Severity FAIL = new Severity("FAIL");
296 
297             private final String name;
298 
299             private Severity(final String name)
300             {
301                 this.name = name;
302             }
303 
304             @Override
305             public String toString()
306             {
307                 return name;
308             }
309         }
310     }
311 }