View Javadoc

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(Code code, String message, 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(Code code, String message)
52      {
53          this(code, message, new String[0]);
54      }
55  
56      TransportErrorMessage(Code code, String message, String param)
57      {
58          this(code, message, new String[] { param });
59      }
60  
61      TransportErrorMessage(Code code, String message, String one, String two)
62      {
63          this(code, message, new String[] { one, two });
64      }
65  
66      TransportErrorMessage(Code code, String message, String one, String two, 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 (String[]) params.clone();
81      }
82  
83      public String getFormattedMessage()
84      {
85          return MessageFormat.format(message, 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     public String toString()
103     {
104         return PARSER.toString(this);
105     }
106 
107     // ------------------------------------------------------------------------------------------------- specializations
108 
109     static class System extends TransportErrorMessage
110     {
111         System(Throwable cause, String appId)
112         {
113             super(Code.SYSTEM, "Exception: {0} occurred serving request for application: {1}", cause.toString(), appId);
114         }
115     }
116 
117     static class BadMagicNumber extends TransportErrorMessage
118     {
119         public BadMagicNumber(String keyName, String appId)
120         {
121             super(Code.BAD_MAGIC, "Unable to decrypt certificate {0} for application {1}", keyName, appId);
122         }
123     }
124 
125     public static class BadProtocolVersion extends TransportErrorMessage
126     {
127         public BadProtocolVersion(String versionString)
128         {
129             super(Code.BAD_PROTOCOL_VERSION, "Bad protocol version: {0}", versionString);
130         }
131     }
132 
133     /**
134      * AppId not found in request
135      */
136     public static class ApplicationIdNotFoundInRequest extends TransportErrorMessage
137     {
138         public ApplicationIdNotFoundInRequest()
139         {
140             super(Code.APP_ID_NOT_FOUND, "Application ID not found in request");
141         }
142     }
143 
144     public static class SecretKeyNotFoundInRequest extends TransportErrorMessage
145     {
146         public SecretKeyNotFoundInRequest()
147         {
148             super(Code.SECRET_KEY_NOT_FOUND, "Secret Key not found in request");
149         }
150     }
151 
152     public static class MagicNumberNotFoundInRequest extends TransportErrorMessage
153     {
154         public MagicNumberNotFoundInRequest()
155         {
156             super(Code.MAGIC_NUMBER_NOT_FOUND, "Magic Number not found in request");
157         }
158     }
159 
160     public static class ApplicationUnknown extends TransportErrorMessage
161     {
162         public ApplicationUnknown(String appId)
163         {
164             super(Code.APP_UNKNOWN, "Unknown Application: {0}", appId);
165         }
166     }
167 
168     public static class UserUnknown extends TransportErrorMessage
169     {
170         public UserUnknown(String userName)
171         {
172             super(Code.USER_UNKNOWN, "Unknown User: {0}", userName);
173         }
174     }
175 
176     public static class PermissionDenied extends TransportErrorMessage
177     {
178         public PermissionDenied()
179         {
180             super(Code.PERMISSION_DENIED, "Permission Denied");
181         }
182     }
183 
184     // --------------------------------------------------------------------------------------------------- inner classes
185 
186     /**
187      * Used to convert an error message String from the server at the client to a TransportErrorMessage.
188      * <p>
189      * String format is:
190      * 
191      * <pre>
192      * code SEPARATOR message SEPARATOR args
193      * </pre>
194      * 
195      * args are JSON array formatted.
196      * 
197      * @see TransportErrorMessage#toString()
198      */
199     static class Parser
200     {
201         static final String SEPARATOR = ";\t";
202 
203         TransportErrorMessage parse(String inputItring) throws IllegalArgumentException
204         {
205             Null.not("inputString", inputItring);
206             String[] args = inputItring.split(SEPARATOR);
207             if (args.length != 3)
208             {
209                 throw new IllegalArgumentException("Cannot split message into Code, Message, Parameters:" + inputItring);
210             }
211             Code code = Code.get(args[0]);
212             String[] params = StringUtil.split(args[2]);
213             return new TransportErrorMessage(code, args[1], params);
214         }
215 
216         /**
217          * Format a String representation of a {@link TransportErrorMessage} in such a way as the {@link #parse(String)}
218          * method can parse it.
219          * 
220          * @param msg
221          *            the message to turn into a String
222          * @return the String representation of the message
223          */
224         String toString(TransportErrorMessage msg)
225         {
226             return new StringBuffer(msg.code.getCode()).append(Parser.SEPARATOR).append(msg.message).append(Parser.SEPARATOR).append(StringUtil.toString(msg.params)).toString();
227         }
228     }
229 
230     /**
231      * Typesafe enum that contains all known error codes.
232      * <p>
233      * Note: for backwards compatibility, do not ever remove a code once its been released. Deprecate if necessary, but
234      * not remove.
235      */
236     public static final class Code
237     {
238         private static final Map /* <String Code> */ALL = new HashMap() /* <String Code> */;
239 
240         public static final Code UNKNOWN = new Code(Severity.ERROR, "UNKNOWN");
241 
242         public static final Code APP_UNKNOWN = new Code(Severity.ERROR, "APP_UNKNOWN");
243         public static final Code SYSTEM = new Code(Severity.ERROR, "SYSTEM");
244         public static final Code BAD_PROTOCOL_VERSION = new Code(Severity.ERROR, "BAD_PROTOCOL_VERSION");
245         public static final Code APP_ID_NOT_FOUND = new Code(Severity.ERROR, "APP_ID_NOT_FOUND");
246         public static final Code SECRET_KEY_NOT_FOUND = new Code(Severity.ERROR, "SECRET_KEY_NOT_FOUND");
247         public static final Code MAGIC_NUMBER_NOT_FOUND = new Code(Severity.ERROR, "MAGIC_NUMBER_NOT_FOUND");
248 
249         public static final Code BAD_REMOTE_IP = new Code(Severity.FAIL, "BAD_REMOTE_IP");
250         public static final Code BAD_XFORWARD_IP = new Code(Severity.FAIL, "BAD_XFORWARD_IP");
251         public static final Code BAD_URL = new Code(Severity.FAIL, "BAD_URL");
252         public static final Code OLD_CERT = new Code(Severity.FAIL, "OLD_CERT");
253         public static final Code MISSING_CERT = new Code(Severity.FAIL, "MISSING_CERT");
254         public static final Code BAD_MAGIC = new Code(Severity.FAIL, "BAD_MAGIC");
255         public static final Code USER_UNKNOWN = new Code(Severity.ERROR, "USER_UNKNOWN");
256         public static final Code PERMISSION_DENIED = new Code(Severity.ERROR, "PERMISSION_DENIED");
257 
258         static Code get(String code)
259         {
260             final Code result = (Code) ALL.get(code);
261             return (result == null) ? Code.UNKNOWN : result;
262         }
263 
264         private final Severity severity;
265         private final String code;
266 
267         private Code(Severity severity, final String code)
268         {
269             Null.not("severity", severity);
270             Null.not("code", code);
271             this.severity = severity;
272             this.code = code;
273             if (ALL.containsKey(code))
274             {
275                 throw new IllegalArgumentException(code + " is already mapped as a " + this.getClass().getName());
276             }
277             ALL.put(code, this);
278         }
279 
280         public Severity getSeverity()
281         {
282             return severity;
283         }
284 
285         public String getCode()
286         {
287             return code;
288         }
289 
290         public static final class Severity
291         {
292             static final Severity ERROR = new Severity("ERROR");
293             static final Severity FAIL = new Severity("FAIL");
294 
295             private final String name;
296 
297             private Severity(String name)
298             {
299                 this.name = name;
300             }
301 
302             public String toString()
303             {
304                 return name;
305             }
306         }
307     }
308 }