Mercurial > hg > AnnotationManager
comparison src/main/java/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorResourceImpl.java @ 23:a3e324009990
now Mavenified!
removed tiny-mce javascript from html frontend.
still needs TripleStoreManager project in Eclipse.
jsontoken 1.1 has to be built manually.
author | casties |
---|---|
date | Tue, 03 Apr 2012 13:05:05 +0200 |
parents | src/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorResourceImpl.java@0f4428febcc6 |
children | d9809412b67f |
comparison
equal
deleted
inserted
replaced
22:0f4428febcc6 | 23:a3e324009990 |
---|---|
1 /** | |
2 * Base class for Annotator resource classes. | |
3 */ | |
4 package de.mpiwg.itgroup.annotationManager.restlet; | |
5 | |
6 import java.io.UnsupportedEncodingException; | |
7 import java.net.URLDecoder; | |
8 import java.net.URLEncoder; | |
9 import java.security.InvalidKeyException; | |
10 import java.security.SignatureException; | |
11 import java.util.ArrayList; | |
12 import java.util.List; | |
13 import java.util.regex.Matcher; | |
14 import java.util.regex.Pattern; | |
15 | |
16 import javax.xml.bind.DatatypeConverter; | |
17 | |
18 import net.oauth.jsontoken.Checker; | |
19 import net.oauth.jsontoken.JsonToken; | |
20 import net.oauth.jsontoken.JsonTokenParser; | |
21 import net.oauth.jsontoken.SystemClock; | |
22 import net.oauth.jsontoken.crypto.HmacSHA256Verifier; | |
23 import net.oauth.jsontoken.crypto.Verifier; | |
24 | |
25 import org.apache.log4j.Logger; | |
26 import org.json.JSONArray; | |
27 import org.json.JSONException; | |
28 import org.json.JSONObject; | |
29 import org.restlet.data.ClientInfo; | |
30 import org.restlet.data.Form; | |
31 import org.restlet.data.Status; | |
32 import org.restlet.representation.Representation; | |
33 import org.restlet.resource.Options; | |
34 import org.restlet.resource.ServerResource; | |
35 import org.restlet.security.User; | |
36 | |
37 import de.mpiwg.itgroup.annotationManager.Constants.NS; | |
38 import de.mpiwg.itgroup.annotationManager.RDFHandling.Annotation; | |
39 | |
40 /** | |
41 * Base class for Annotator resource classes. | |
42 * | |
43 * @author dwinter, casties | |
44 * | |
45 */ | |
46 public abstract class AnnotatorResourceImpl extends ServerResource { | |
47 | |
48 protected Logger logger = Logger.getRootLogger(); | |
49 | |
50 protected String getAllowedMethodsForHeader() { | |
51 return "OPTIONS,GET,POST"; | |
52 } | |
53 | |
54 public String encodeJsonId(String id) { | |
55 try { | |
56 return DatatypeConverter.printBase64Binary(id.getBytes("UTF-8")); | |
57 } catch (UnsupportedEncodingException e) { | |
58 return null; | |
59 } | |
60 } | |
61 | |
62 public String decodeJsonId(String id) { | |
63 try { | |
64 return new String(DatatypeConverter.parseBase64Binary(id), "UTF-8"); | |
65 } catch (UnsupportedEncodingException e) { | |
66 return null; | |
67 } | |
68 } | |
69 | |
70 /** | |
71 * Handle options request to allow CORS for AJAX. | |
72 * | |
73 * @param entity | |
74 */ | |
75 @Options | |
76 public void doOptions(Representation entity) { | |
77 logger.debug("AnnotatorResourceImpl doOptions!"); | |
78 setCorsHeaders(); | |
79 } | |
80 | |
81 /** | |
82 * set headers to allow CORS for AJAX. | |
83 */ | |
84 protected void setCorsHeaders() { | |
85 Form responseHeaders = (Form) getResponse().getAttributes().get("org.restlet.http.headers"); | |
86 if (responseHeaders == null) { | |
87 responseHeaders = new Form(); | |
88 getResponse().getAttributes().put("org.restlet.http.headers", responseHeaders); | |
89 } | |
90 responseHeaders.add("Access-Control-Allow-Methods", getAllowedMethodsForHeader()); | |
91 // echo back Origin and Request-Headers | |
92 Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers"); | |
93 String origin = requestHeaders.getFirstValue("Origin", true); | |
94 if (origin == null) { | |
95 responseHeaders.add("Access-Control-Allow-Origin", "*"); | |
96 } else { | |
97 responseHeaders.add("Access-Control-Allow-Origin", origin); | |
98 } | |
99 String allowHeaders = requestHeaders.getFirstValue("Access-Control-Request-Headers", true); | |
100 if (allowHeaders != null) { | |
101 responseHeaders.add("Access-Control-Allow-Headers", allowHeaders); | |
102 } | |
103 responseHeaders.add("Access-Control-Allow-Credentials", "true"); | |
104 responseHeaders.add("Access-Control-Max-Age", "60"); | |
105 } | |
106 | |
107 /** | |
108 * returns if authentication information from headers is valid. | |
109 * | |
110 * @param entity | |
111 * @return | |
112 */ | |
113 public boolean isAuthenticated(Representation entity) { | |
114 return (checkAuthToken(entity) != null); | |
115 } | |
116 | |
117 /** | |
118 * checks Annotator Auth plugin authentication information from headers. returns userId if successful. | |
119 * | |
120 * @param entity | |
121 * @return | |
122 */ | |
123 public String checkAuthToken(Representation entity) { | |
124 Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers"); | |
125 String authToken = requestHeaders.getFirstValue("x-annotator-auth-token", true); | |
126 // decode token first to get consumer key | |
127 JsonToken token = new JsonTokenParser(null, null).deserialize(authToken); | |
128 String userId = token.getParamAsPrimitive("userId").getAsString(); | |
129 String consumerKey = token.getParamAsPrimitive("consumerKey").getAsString(); | |
130 // get stored consumer secret for key | |
131 RestServer restServer = (RestServer) getApplication(); | |
132 String consumerSecret = restServer.getConsumerSecret(consumerKey); | |
133 logger.debug("requested consumer key=" + consumerKey + " secret=" + consumerSecret); | |
134 if (consumerSecret == null) { | |
135 return null; | |
136 } | |
137 //logger.debug(String.format("token=%s tokenString=%s signatureAlgorithm=%s",token,token.getTokenString(),token.getSignatureAlgorithm())); | |
138 try { | |
139 List<Verifier> verifiers = new ArrayList<Verifier>(); | |
140 // we only do HS256 yet | |
141 verifiers.add(new HmacSHA256Verifier(consumerSecret.getBytes("UTF-8"))); | |
142 // verify token signature(should really be static...) | |
143 new JsonTokenParser(new SystemClock(), null, (Checker[]) null).verify(token, verifiers); | |
144 } catch (SignatureException e) { | |
145 // TODO Auto-generated catch block | |
146 e.printStackTrace(); | |
147 } catch (InvalidKeyException e) { | |
148 // TODO Auto-generated catch block | |
149 e.printStackTrace(); | |
150 } catch (UnsupportedEncodingException e) { | |
151 // TODO Auto-generated catch block | |
152 e.printStackTrace(); | |
153 } | |
154 // must be ok then | |
155 logger.debug("auth OK! user="+userId); | |
156 return userId; | |
157 } | |
158 | |
159 /** | |
160 * creates Annotator-JSON from an Annotation object. | |
161 * | |
162 * @param annot | |
163 * @return | |
164 */ | |
165 public JSONObject createAnnotatorJson(Annotation annot) { | |
166 boolean makeUserObject = true; | |
167 JSONObject jo = new JSONObject(); | |
168 try { | |
169 jo.put("text", annot.text); | |
170 jo.put("uri", annot.url); | |
171 | |
172 if (makeUserObject) { | |
173 // create user object | |
174 JSONObject userObject = new JSONObject(); | |
175 // save creator as uri | |
176 userObject.put("uri", annot.creator); | |
177 // make short user id | |
178 String userID = annot.creator; | |
179 if (userID.startsWith(NS.MPIWG_PERSONS)) { | |
180 userID = userID.replace(NS.MPIWG_PERSONS, ""); // entferne NAMESPACE | |
181 } | |
182 // save as id | |
183 userObject.put("id", userID); | |
184 // get full name | |
185 RestServer restServer = (RestServer) getApplication(); | |
186 String userName = restServer.getUserNameFromLdap(userID); | |
187 userObject.put("name", userName); | |
188 // save user object | |
189 jo.put("user", userObject); | |
190 } else { | |
191 // save user as string | |
192 jo.put("user", annot.creator); | |
193 } | |
194 | |
195 List<String> xpointers = new ArrayList<String>(); | |
196 if (annot.xpointers == null || annot.xpointers.size() == 0) | |
197 xpointers.add(annot.xpointer); | |
198 else { | |
199 for (String xpointerString : annot.xpointers) { | |
200 xpointers.add(xpointerString); | |
201 } | |
202 } | |
203 jo.put("ranges", transformToRanges(xpointers)); | |
204 // encode Annotation URL (=id) in base64 | |
205 String annotUrl = annot.getAnnotationUri(); | |
206 String annotId = encodeJsonId(annotUrl); | |
207 jo.put("id", annotId); | |
208 return jo; | |
209 } catch (JSONException e) { | |
210 // TODO Auto-generated catch block | |
211 e.printStackTrace(); | |
212 } | |
213 return null; | |
214 } | |
215 | |
216 private JSONArray transformToRanges(List<String> xpointers) { | |
217 | |
218 JSONArray ja = new JSONArray(); | |
219 | |
220 Pattern rg = Pattern | |
221 .compile("#xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)/range-to\\(end-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)\\)"); | |
222 Pattern rg1 = Pattern.compile("#xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)"); | |
223 | |
224 try { | |
225 for (String xpointer : xpointers) { | |
226 String decoded = URLDecoder.decode(xpointer, "utf-8"); | |
227 Matcher m = rg.matcher(decoded); | |
228 | |
229 if (m.find()) { | |
230 { | |
231 JSONObject jo = new JSONObject(); | |
232 jo.put("start", m.group(1)); | |
233 jo.put("startOffset", m.group(2)); | |
234 jo.put("end", m.group(3)); | |
235 jo.put("endOffset", m.group(4)); | |
236 ja.put(jo); | |
237 } | |
238 } | |
239 m = rg1.matcher(xpointer); | |
240 if (m.find()) { | |
241 JSONObject jo = new JSONObject(); | |
242 jo.put("start", m.group(1)); | |
243 jo.put("startOffset", m.group(2)); | |
244 | |
245 ja.put(jo); | |
246 } | |
247 } | |
248 } catch (JSONException e) { | |
249 // TODO Auto-generated catch block | |
250 e.printStackTrace(); | |
251 } catch (UnsupportedEncodingException e) { | |
252 // TODO Auto-generated catch block | |
253 e.printStackTrace(); | |
254 } | |
255 | |
256 return ja; | |
257 } | |
258 | |
259 /** | |
260 * creates an Annotation object with data from JSON. | |
261 * | |
262 * uses the specification from the annotator project: {@link https://github.com/okfn/annotator/wiki/Annotation-format} | |
263 * | |
264 * The username will be transformed to an URI if not given already as URI, if not it will set to the MPIWG namespace defined in | |
265 * de.mpiwg.itgroup.annotationManager.Constants.NS | |
266 * | |
267 * @param jo | |
268 * @return | |
269 * @throws JSONException | |
270 */ | |
271 public Annotation createAnnotation(JSONObject jo, Representation entity) throws JSONException { | |
272 return updateAnnotation(new Annotation(), jo, entity); | |
273 } | |
274 | |
275 public Annotation updateAnnotation(Annotation annot, JSONObject jo, Representation entity) throws JSONException { | |
276 // annotated uri | |
277 String url = annot.url; | |
278 if (jo.has("uri")) { | |
279 url = jo.getString("uri"); | |
280 } | |
281 // annotation text | |
282 String text = annot.text; | |
283 if (jo.has("text")) { | |
284 text = jo.getString("text"); | |
285 } | |
286 // check authentication | |
287 String authUser = checkAuthToken(entity); | |
288 if (authUser == null) { | |
289 // try http auth | |
290 User httpUser = getHttpAuthUser(entity); | |
291 if (httpUser == null) { | |
292 setStatus(Status.CLIENT_ERROR_FORBIDDEN); | |
293 return null; | |
294 } | |
295 authUser = httpUser.getIdentifier(); | |
296 } | |
297 // username not required, if no username given authuser will be used | |
298 String username = null; | |
299 String userUri = annot.creator; | |
300 if (jo.has("user")) { | |
301 if (jo.get("user") instanceof String) { | |
302 // user is just a String | |
303 username = jo.getString("user"); | |
304 // TODO: what if username and authUser are different? | |
305 } else { | |
306 // user is an object | |
307 JSONObject user = jo.getJSONObject("user"); | |
308 if (user.has("id")) { | |
309 username = user.getString("id"); | |
310 } | |
311 if (user.has("uri")) { | |
312 userUri = user.getString("uri"); | |
313 } | |
314 } | |
315 } | |
316 if (username == null) { | |
317 username = authUser; | |
318 } | |
319 // username should be a URI, if not it will set to the MPIWG namespace defined in | |
320 // de.mpiwg.itgroup.annotationManager.Constants.NS | |
321 if (userUri == null) { | |
322 if (username.startsWith("http")) { | |
323 userUri = username; | |
324 } else { | |
325 userUri = NS.MPIWG_PERSONS + username; | |
326 } | |
327 } | |
328 // TODO: should we overwrite the creator? | |
329 | |
330 // create xpointer | |
331 String xpointer = annot.xpointer; | |
332 if (jo.has("ranges")) { | |
333 JSONObject ranges = jo.getJSONArray("ranges").getJSONObject(0); | |
334 String start = ranges.getString("start"); | |
335 String end = ranges.getString("end"); | |
336 String startOffset = ranges.getString("startOffset"); | |
337 String endOffset = ranges.getString("endOffset"); | |
338 | |
339 try { | |
340 xpointer = url | |
341 + "#" | |
342 + URLEncoder.encode(String.format( | |
343 "xpointer(start-point(string-range(\"%s\",%s,1))/range-to(end-point(string-range(\"%s\",%s,1))))", | |
344 start, startOffset, end, endOffset), "utf-8"); | |
345 } catch (UnsupportedEncodingException e) { | |
346 e.printStackTrace(); | |
347 setStatus(Status.SERVER_ERROR_INTERNAL); | |
348 return null; | |
349 } | |
350 } | |
351 return new Annotation(xpointer, userUri, annot.time, text, annot.type); | |
352 } | |
353 | |
354 /** | |
355 * returns the logged in User. | |
356 * | |
357 * @param entity | |
358 * @return | |
359 */ | |
360 protected User getHttpAuthUser(Representation entity) { | |
361 RestServer restServer = (RestServer) getApplication(); | |
362 if (!restServer.authenticate(getRequest(), getResponse())) { | |
363 // Not authenticated | |
364 return null; | |
365 } | |
366 | |
367 ClientInfo ci = getRequest().getClientInfo(); | |
368 logger.debug(ci); | |
369 return getRequest().getClientInfo().getUser(); | |
370 | |
371 } | |
372 | |
373 } |