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 }