source: AnnotationManagerN4J/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java @ 22:b1fb0d117877

Last change on this file since 22:b1fb0d117877 was 22:b1fb0d117877, checked in by casties, 12 years ago

adding and listing groups via html works now.
no editing of group membership yet.
no authentication yet.

File size: 21.2 KB
Line 
1/**
2 * Base class for Annotator resource classes.
3 */
4package de.mpiwg.itgroup.annotations.restlet;
5
6import java.io.UnsupportedEncodingException;
7import java.security.InvalidKeyException;
8import java.security.SignatureException;
9import java.text.SimpleDateFormat;
10import java.util.ArrayList;
11import java.util.Calendar;
12import java.util.HashSet;
13import java.util.List;
14import java.util.Set;
15import java.util.regex.Matcher;
16import java.util.regex.Pattern;
17
18import net.oauth.jsontoken.Checker;
19import net.oauth.jsontoken.JsonToken;
20import net.oauth.jsontoken.JsonTokenParser;
21import net.oauth.jsontoken.SystemClock;
22import net.oauth.jsontoken.crypto.HmacSHA256Verifier;
23import net.oauth.jsontoken.crypto.Verifier;
24
25import org.apache.commons.codec.binary.Base64;
26import org.apache.log4j.Logger;
27import org.json.JSONArray;
28import org.json.JSONException;
29import org.json.JSONObject;
30import org.restlet.data.Form;
31import org.restlet.data.Status;
32import org.restlet.representation.Representation;
33import org.restlet.resource.Options;
34import org.restlet.resource.ServerResource;
35
36import de.mpiwg.itgroup.annotations.Actor;
37import de.mpiwg.itgroup.annotations.Annotation;
38import de.mpiwg.itgroup.annotations.Annotation.FragmentTypes;
39import de.mpiwg.itgroup.annotations.Group;
40import de.mpiwg.itgroup.annotations.NS;
41import de.mpiwg.itgroup.annotations.Person;
42import de.mpiwg.itgroup.annotations.neo4j.AnnotationStore;
43
44/**
45 * Base class for Annotator resource classes.
46 *
47 * @author dwinter, casties
48 *
49 */
50public abstract class AnnotatorResourceImpl extends ServerResource {
51
52    protected static Logger logger = Logger.getLogger(AnnotatorResourceImpl.class);
53
54    private AnnotationStore store;
55
56    protected String getAllowedMethodsForHeader() {
57        return "OPTIONS,GET,POST";
58    }
59
60    protected AnnotationStore getAnnotationStore() {
61        if (store == null) {
62            store = ((BaseRestlet) getApplication()).getAnnotationStore();
63        }
64        return store;
65    }
66
67    public String encodeJsonId(String id) {
68        if (id == null) return null;
69        try {
70            return Base64.encodeBase64URLSafeString(id.getBytes("UTF-8"));
71        } catch (UnsupportedEncodingException e) {
72            return null;
73        }
74    }
75
76    public String decodeJsonId(String id) {
77        if (id == null) return null;
78        try {
79            return new String(Base64.decodeBase64(id), "UTF-8");
80        } catch (UnsupportedEncodingException e) {
81            return null;
82        }
83    }
84
85    /**
86     * Handle options request to allow CORS for AJAX.
87     *
88     * @param entity
89     */
90    @Options
91    public void doOptions(Representation entity) {
92        logger.debug("AnnotatorResourceImpl doOptions!");
93        setCorsHeaders();
94    }
95
96    /**
97     * set headers to allow CORS for AJAX.
98     */
99    protected void setCorsHeaders() {
100        Form responseHeaders = (Form) getResponse().getAttributes().get("org.restlet.http.headers");
101        if (responseHeaders == null) {
102            responseHeaders = new Form();
103            getResponse().getAttributes().put("org.restlet.http.headers", responseHeaders);
104        }
105        responseHeaders.add("Access-Control-Allow-Methods", getAllowedMethodsForHeader());
106        // echo back Origin and Request-Headers
107        Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers");
108        String origin = requestHeaders.getFirstValue("Origin", true);
109        if (origin == null) {
110            responseHeaders.add("Access-Control-Allow-Origin", "*");
111        } else {
112            responseHeaders.add("Access-Control-Allow-Origin", origin);
113        }
114        String allowHeaders = requestHeaders.getFirstValue("Access-Control-Request-Headers", true);
115        if (allowHeaders != null) {
116            responseHeaders.add("Access-Control-Allow-Headers", allowHeaders);
117        }
118        responseHeaders.add("Access-Control-Allow-Credentials", "true");
119        responseHeaders.add("Access-Control-Max-Age", "60");
120    }
121
122    /**
123     * returns if authentication information from headers is valid.
124     *
125     * @param entity
126     * @return
127     */
128    public boolean isAuthenticated(Representation entity) {
129        return (checkAuthToken(entity) != null);
130    }
131
132    /**
133     * checks Annotator Auth plugin authentication information from headers.
134     * returns userId if successful.
135     *
136     * @param entity
137     * @return
138     */
139    public String checkAuthToken(Representation entity) {
140        Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers");
141        String authToken = requestHeaders.getFirstValue("x-annotator-auth-token", true);
142        if (authToken == null) return null;
143        // decode token first to get consumer key
144        JsonToken token = new JsonTokenParser(null, null).deserialize(authToken);
145        String userId = token.getParamAsPrimitive("userId").getAsString();
146        String consumerKey = token.getParamAsPrimitive("consumerKey").getAsString();
147        // get stored consumer secret for key
148        BaseRestlet restServer = (BaseRestlet) getApplication();
149        String consumerSecret = restServer.getConsumerSecret(consumerKey);
150        logger.debug("requested consumer key=" + consumerKey + " secret=" + consumerSecret);
151        if (consumerSecret == null) {
152            return null;
153        }
154        // logger.debug(String.format("token=%s tokenString=%s signatureAlgorithm=%s",token,token.getTokenString(),token.getSignatureAlgorithm()));
155        try {
156            List<Verifier> verifiers = new ArrayList<Verifier>();
157            // we only do HS256 yet
158            verifiers.add(new HmacSHA256Verifier(consumerSecret.getBytes("UTF-8")));
159            // verify token signature(should really be static...)
160            new JsonTokenParser(new SystemClock(), null, (Checker[]) null).verify(token, verifiers);
161        } catch (SignatureException e) {
162            // TODO Auto-generated catch block
163            e.printStackTrace();
164        } catch (InvalidKeyException e) {
165            // TODO Auto-generated catch block
166            e.printStackTrace();
167        } catch (UnsupportedEncodingException e) {
168            // TODO Auto-generated catch block
169            e.printStackTrace();
170        }
171        // must be ok then
172        logger.debug("auth OK! user=" + userId);
173        return userId;
174    }
175
176    /**
177     * creates Annotator-JSON from an Annotation object.
178     *
179     * @param annot
180     * @param forAnonymous TODO
181     * @return
182     */
183    public JSONObject createAnnotatorJson(Annotation annot, boolean forAnonymous) {
184        // return user as a JSON object (otherwise just as string)
185        boolean makeUserObject = true;
186        JSONObject jo = new JSONObject();
187        try {
188            jo.put("text", annot.getBodyText());
189            jo.put("uri", annot.getTargetBaseUri());
190
191            /*
192             * user
193             */
194            if (makeUserObject) {
195                // create user object
196                JSONObject userObject = new JSONObject();
197                Actor creator = annot.getCreator();
198                // save creator as uri
199                userObject.put("uri", creator.getUri());
200                // make short user id
201                String userId = creator.getIdString();
202                // set as id
203                userObject.put("id", userId);
204                // get full name
205                String userName = creator.getName();
206                if (userName == null) {
207                    BaseRestlet restServer = (BaseRestlet) getApplication();
208                    userName = restServer.getFullNameFromLdap(userId);
209                }
210                userObject.put("name", userName);
211                // save user object
212                jo.put("user", userObject);
213            } else {
214                // save user as string
215                jo.put("user", annot.getCreatorUri());
216            }
217
218            /*
219             * ranges
220             */
221            if (annot.getTargetFragment() != null) {
222                // we only look at the first xpointer
223                List<String> fragments = new ArrayList<String>();
224                fragments.add(annot.getTargetFragment());
225                FragmentTypes xt = annot.getFragmentType();
226                if (xt == FragmentTypes.XPOINTER) {
227                    jo.put("ranges", transformToRanges(fragments));
228                } else if (xt == FragmentTypes.AREA) {
229                    jo.put("areas", transformToAreas(fragments));
230                }
231            }
232           
233            /*
234             * permissions
235             */
236            JSONObject perms = new JSONObject();
237            jo.put("permissions", perms);
238            // admin
239            JSONArray adminPerms = new JSONArray();
240            perms.put("admin", adminPerms);
241            Actor adminPerm = annot.getAdminPermission();
242            if (adminPerm != null) {
243                adminPerms.put(adminPerm.getIdString());
244            } else if (forAnonymous) {
245                // set something because its not allowed for anonymous
246                adminPerms.put("not-you");
247            }
248            // delete
249            JSONArray deletePerms = new JSONArray();
250            perms.put("delete", deletePerms);
251            Actor deletePerm = annot.getDeletePermission();
252            if (deletePerm != null) {
253                deletePerms.put(deletePerm.getIdString());
254            } else if (forAnonymous) {
255                // set something because its not allowed for anonymous
256                deletePerms.put("not-you");
257            }
258            // update
259            JSONArray updatePerms = new JSONArray();
260            perms.put("update", updatePerms);
261            Actor updatePerm = annot.getUpdatePermission();
262            if (updatePerm != null) {
263                updatePerms.put(updatePerm.getIdString());
264            } else if (forAnonymous) {
265                // set something because its not allowed for anonymous
266                updatePerms.put("not-you");
267            }
268            // read
269            JSONArray readPerms = new JSONArray();
270            perms.put("read", readPerms);
271            Actor readPerm = annot.getReadPermission();
272            if (readPerm != null) {
273                readPerms.put(readPerm.getIdString());
274            }
275           
276            /*
277             * tags
278             */
279            Set<String> tagset = annot.getTags(); 
280            if (tagset != null) {
281                JSONArray tags = new JSONArray();
282                jo.put("tags", tags);
283                for (String tag : tagset) {
284                    tags.put(tag);
285                }
286            }
287           
288            /*
289             * id
290             */
291            // encode Annotation URL (=id) in base64
292            String annotUrl = annot.getUri();
293            String annotId = encodeJsonId(annotUrl);
294            jo.put("id", annotId);
295            return jo;
296        } catch (JSONException e) {
297            // TODO Auto-generated catch block
298            e.printStackTrace();
299        }
300        return null;
301    }
302
303    private JSONArray transformToRanges(List<String> xpointers) {
304
305        JSONArray ja = new JSONArray();
306
307        Pattern rg = Pattern
308                .compile("xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)/range-to\\(end-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)\\)");
309        Pattern rg1 = Pattern.compile("xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)");
310
311        try {
312            for (String xpointer : xpointers) {
313                // String decoded = URLDecoder.decode(xpointer, "utf-8");
314                String decoded = xpointer;
315                Matcher m = rg.matcher(decoded);
316
317                if (m.find()) {
318                    {
319                        JSONObject jo = new JSONObject();
320                        jo.put("start", m.group(1));
321                        jo.put("startOffset", m.group(2));
322                        jo.put("end", m.group(3));
323                        jo.put("endOffset", m.group(4));
324                        ja.put(jo);
325                    }
326                }
327                m = rg1.matcher(xpointer);
328                if (m.find()) {
329                    JSONObject jo = new JSONObject();
330                    jo.put("start", m.group(1));
331                    jo.put("startOffset", m.group(2));
332
333                    ja.put(jo);
334                }
335            }
336        } catch (JSONException e) {
337            // TODO Auto-generated catch block
338            e.printStackTrace();
339        }
340        return ja;
341    }
342
343    private JSONArray transformToAreas(List<String> xpointers) {
344
345        JSONArray ja = new JSONArray();
346
347        Pattern rg = Pattern.compile("xywh=(\\w*:)([\\d\\.]+),([\\d\\.]+),([\\d\\.]+),([\\d\\.]+)");
348
349        try {
350            for (String xpointer : xpointers) {
351                // String decoded = URLDecoder.decode(xpointer, "utf-8");
352                String decoded = xpointer;
353                Matcher m = rg.matcher(decoded);
354
355                if (m.find()) {
356                    {
357                        JSONObject jo = new JSONObject();
358                        @SuppressWarnings("unused")
359                        String unit = m.group(1);
360                        jo.put("x", m.group(2));
361                        jo.put("y", m.group(3));
362                        jo.put("width", m.group(4));
363                        jo.put("height", m.group(5));
364                        ja.put(jo);
365                    }
366                }
367            }
368        } catch (JSONException e) {
369            // TODO Auto-generated catch block
370            e.printStackTrace();
371        }
372        return ja;
373    }
374
375    protected String parseArea(JSONObject area) throws JSONException {
376        String x = area.getString("x");
377        String y = area.getString("y");
378        String width = "0";
379        String height = "0";
380        if (area.has("width")) {
381            width = area.getString("width");
382            height = area.getString("height");
383        }
384        String fragment = String.format("xywh=fraction:%s,%s,%s,%s", x, y, width, height);
385        return fragment;
386    }
387
388    protected String parseRange(JSONObject range) throws JSONException {
389        String start = range.getString("start");
390        String end = range.getString("end");
391        String startOffset = range.getString("startOffset");
392        String endOffset = range.getString("endOffset");
393
394        String fragment = String.format(
395                "xpointer(start-point(string-range(\"%s\",%s,1))/range-to(end-point(string-range(\"%s\",%s,1))))", start,
396                startOffset, end, endOffset);
397        return fragment;
398    }
399
400    /**
401     * Creates an Annotation object with data from JSON.
402     *
403     * uses the specification from the annotator project: {@link https
404     * ://github.com/okfn/annotator/wiki/Annotation-format}
405     *
406     * The username will be transformed to an URI if not given already as URI,
407     * if not it will set to the MPIWG namespace defined in
408     * de.mpiwg.itgroup.annotationManager.Constants.NS
409     *
410     * @param jo
411     * @return
412     * @throws JSONException
413     * @throws UnsupportedEncodingException
414     */
415    public Annotation createAnnotation(JSONObject jo, Representation entity) throws JSONException, UnsupportedEncodingException {
416        return updateAnnotation(new Annotation(), jo, entity);
417    }
418
419    /**
420     * Updates an Annotation object with data from JSON.
421     *
422     * uses the specification from the annotator project: {@link https
423     * ://github.com/okfn/annotator/wiki/Annotation-format}
424     *
425     * The username will be transformed to an URI if not given already as URI,
426     * if not it will set to the MPIWG namespace defined in
427     * de.mpiwg.itgroup.annotationManager.Constants.NS
428     *
429     * @param annot
430     * @param jo
431     * @return
432     * @throws JSONException
433     * @throws UnsupportedEncodingException
434     */
435    public Annotation updateAnnotation(Annotation annot, JSONObject jo, Representation entity) throws JSONException,
436            UnsupportedEncodingException {
437        /*
438         * target uri
439         */
440        if (jo.has("uri")) {
441            annot.setTargetBaseUri(jo.getString("uri"));
442        }
443        /*
444         * annotation text
445         */
446        if (jo.has("text")) {
447            annot.setBodyText(jo.getString("text"));
448        }
449        /*
450         * check authentication
451         */
452        String authUser = checkAuthToken(entity);
453        if (authUser == null) {
454            /*
455             * // try http auth User httpUser = getHttpAuthUser(entity); if
456             * (httpUser == null) {
457             */
458            setStatus(Status.CLIENT_ERROR_FORBIDDEN);
459            return null;
460            /*
461             * } authUser = httpUser.getIdentifier();
462             */
463        }
464        /*
465         * get or create creator object
466         */
467        Actor creator = annot.getCreator();
468        if (creator == null) {
469            creator = new Person();
470            annot.setCreator(creator);
471        }
472        // username not required, if no username given authuser will be used
473        String username = null;
474        String userUri = creator.getUri();
475        if (jo.has("user")) {
476            if (jo.get("user") instanceof String) {
477                // user is just a String
478                username = jo.getString("user");
479                creator.setId(username);
480                // TODO: what if username and authUser are different?
481            } else {
482                // user is an object
483                JSONObject user = jo.getJSONObject("user");
484                if (user.has("id")) {
485                    String id = user.getString("id");
486                    creator.setId(id);
487                    username = id;
488                }
489                if (user.has("uri")) {
490                    userUri = user.getString("uri");
491                }
492            }
493        }
494        if (username == null) {
495            username = authUser;
496        }
497        // try to get full name
498        if (creator.getName() == null && username != null) {
499            BaseRestlet restServer = (BaseRestlet) getApplication();
500            String fullName = restServer.getFullNameFromLdap(username);
501            creator.setName(fullName);
502        }
503        // userUri should be a URI, if not it will set to the MPIWG namespace
504        if (userUri == null) {
505            if (username.startsWith("http")) {
506                userUri = username;
507            } else {
508                userUri = NS.MPIWG_PERSONS_URL + username;
509            }
510        }
511        // TODO: should we overwrite the creator?
512        if (creator.getUri() == null) {
513            creator.setUri(userUri);
514        }
515        /*
516         * creation date
517         */
518        if (annot.getCreated() == null) {
519            // set creation date
520            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
521            String ct = format.format(Calendar.getInstance().getTime());
522            annot.setCreated(ct);
523        }
524
525        /*
526         * create xpointer from the first range/area
527         */
528        if (jo.has("ranges")) {
529            JSONObject ranges = jo.getJSONArray("ranges").getJSONObject(0);
530            annot.setFragmentType(FragmentTypes.XPOINTER);
531            String fragment = parseRange(ranges);
532            annot.setTargetFragment(fragment);
533        }
534        if (jo.has("areas")) {
535            JSONObject area = jo.getJSONArray("areas").getJSONObject(0);
536            annot.setFragmentType(FragmentTypes.AREA);
537            String fragment = parseArea(area);
538            annot.setTargetFragment(fragment);
539        }
540
541        /*
542         * permissions
543         */
544        if (jo.has("permissions")) {
545            JSONObject permissions = jo.getJSONObject("permissions");
546            if (permissions.has("admin")) {
547                JSONArray perms = permissions.getJSONArray("admin");
548                Actor actor = getActorFromPermissions(perms);
549                annot.setAdminPermission(actor);
550            }
551            if (permissions.has("delete")) {
552                JSONArray perms = permissions.getJSONArray("delete");
553                Actor actor = getActorFromPermissions(perms);
554                annot.setDeletePermission(actor);
555            }
556            if (permissions.has("update")) {
557                JSONArray perms = permissions.getJSONArray("update");
558                Actor actor = getActorFromPermissions(perms);
559                annot.setUpdatePermission(actor);
560            }
561            if (permissions.has("read")) {
562                JSONArray perms = permissions.getJSONArray("read");
563                Actor actor = getActorFromPermissions(perms);
564                annot.setReadPermission(actor);
565            }
566        }
567
568        /*
569         * tags
570         */
571        if (jo.has("tags")) {
572            HashSet<String> tagset = new HashSet<String>();
573            JSONArray tags = jo.getJSONArray("tags");
574            for (int i = 0; i < tags.length(); ++i) {
575                tagset.add(tags.getString(i));
576            }
577            annot.setTags(tagset);
578        }
579
580       
581        return annot;
582    }
583
584    @SuppressWarnings("unused") // i in for loop
585    protected Actor getActorFromPermissions(JSONArray perms) throws JSONException {
586        Actor actor = null;
587        for (int i = 0; i < perms.length(); ++i) {
588            String perm = perms.getString(i);
589            if (perm.toLowerCase().startsWith("group:")) {
590                String groupId = perm.substring(6);
591                actor = new Group(groupId);
592            } else {
593                actor = new Person(perm);
594            }
595            // we just take the first one
596            break;
597        }
598        return actor;
599    }
600
601}
Note: See TracBrowser for help on using the repository browser.