source: AnnotationManagerN4J/src/main/java/de/mpiwg/itgroup/annotations/restlet/AnnotatorResourceImpl.java @ 10:90911b2da322

Last change on this file since 10:90911b2da322 was 10:90911b2da322, checked in by casties, 12 years ago

more work on permissions...

File size: 19.8 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.List;
13import java.util.regex.Matcher;
14import java.util.regex.Pattern;
15
16import javax.servlet.ServletContext;
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.Person;
41import de.mpiwg.itgroup.annotations.neo4j.AnnotationStore;
42import de.mpiwg.itgroup.annotations.old.NS;
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            ServletContext sc = (ServletContext) getContext().getServerDispatcher().getContext().getAttributes()
63                    .get("org.restlet.ext.servlet.ServletContext");
64            logger.debug("Getting AnnotationStore from Context");
65            store = (AnnotationStore) sc.getAttribute(RestServer.ANNSTORE_KEY);
66        }
67        return store;
68    }
69
70    public String encodeJsonId(String id) {
71        try {
72            return Base64.encodeBase64URLSafeString(id.getBytes("UTF-8"));
73        } catch (UnsupportedEncodingException e) {
74            return null;
75        }
76    }
77
78    public String decodeJsonId(String id) {
79        try {
80            return new String(Base64.decodeBase64(id), "UTF-8");
81        } catch (UnsupportedEncodingException e) {
82            return null;
83        }
84    }
85
86    /**
87     * Handle options request to allow CORS for AJAX.
88     *
89     * @param entity
90     */
91    @Options
92    public void doOptions(Representation entity) {
93        logger.debug("AnnotatorResourceImpl doOptions!");
94        setCorsHeaders();
95    }
96
97    /**
98     * set headers to allow CORS for AJAX.
99     */
100    protected void setCorsHeaders() {
101        Form responseHeaders = (Form) getResponse().getAttributes().get("org.restlet.http.headers");
102        if (responseHeaders == null) {
103            responseHeaders = new Form();
104            getResponse().getAttributes().put("org.restlet.http.headers", responseHeaders);
105        }
106        responseHeaders.add("Access-Control-Allow-Methods", getAllowedMethodsForHeader());
107        // echo back Origin and Request-Headers
108        Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers");
109        String origin = requestHeaders.getFirstValue("Origin", true);
110        if (origin == null) {
111            responseHeaders.add("Access-Control-Allow-Origin", "*");
112        } else {
113            responseHeaders.add("Access-Control-Allow-Origin", origin);
114        }
115        String allowHeaders = requestHeaders.getFirstValue("Access-Control-Request-Headers", true);
116        if (allowHeaders != null) {
117            responseHeaders.add("Access-Control-Allow-Headers", allowHeaders);
118        }
119        responseHeaders.add("Access-Control-Allow-Credentials", "true");
120        responseHeaders.add("Access-Control-Max-Age", "60");
121    }
122
123    /**
124     * returns if authentication information from headers is valid.
125     *
126     * @param entity
127     * @return
128     */
129    public boolean isAuthenticated(Representation entity) {
130        return (checkAuthToken(entity) != null);
131    }
132
133    /**
134     * checks Annotator Auth plugin authentication information from headers.
135     * returns userId if successful.
136     *
137     * @param entity
138     * @return
139     */
140    public String checkAuthToken(Representation entity) {
141        Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers");
142        String authToken = requestHeaders.getFirstValue("x-annotator-auth-token", true);
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        RestServer restServer = (RestServer) 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     * @return
181     */
182    public JSONObject createAnnotatorJson(Annotation annot) {
183        // return user as a JSON object (otherwise just as string)
184        boolean makeUserObject = true;
185        JSONObject jo = new JSONObject();
186        try {
187            jo.put("text", annot.getBodyText());
188            jo.put("uri", annot.getTargetBaseUri());
189
190            if (makeUserObject) {
191                // create user object
192                JSONObject userObject = new JSONObject();
193                Actor creator = annot.getCreator();
194                // save creator as uri
195                userObject.put("uri", creator.getUri());
196                // make short user id
197                String userId = creator.getIdString();
198                // set as id
199                userObject.put("id", userId);
200                // get full name
201                String userName = creator.getName();
202                if (userName == null) {
203                    RestServer restServer = (RestServer) getApplication();
204                    userName = restServer.getFullNameFromLdap(userId);
205                }
206                userObject.put("name", userName);
207                // save user object
208                jo.put("user", userObject);
209            } else {
210                // save user as string
211                jo.put("user", annot.getCreatorUri());
212            }
213
214            if (annot.getTargetFragment() != null) {
215                // we only look at the first xpointer
216                List<String> fragments = new ArrayList<String>();
217                fragments.add(annot.getTargetFragment());
218                FragmentTypes xt = annot.getFragmentType();
219                if (xt == FragmentTypes.XPOINTER) {
220                    jo.put("ranges", transformToRanges(fragments));
221                } else if (xt == FragmentTypes.AREA) {
222                    jo.put("areas", transformToAreas(fragments));
223                }
224            }
225           
226            // permissions
227            JSONObject perms = new JSONObject();
228            jo.put("permissions", perms);
229            // admin
230            JSONArray adminPerms = new JSONArray();
231            perms.put("admin", adminPerms);
232            Actor adminPerm = annot.getAdminPermission();
233            if (adminPerm != null) {
234                adminPerms.put(adminPerm.getIdString());
235            }
236            // delete
237            JSONArray deletePerms = new JSONArray();
238            perms.put("delete", deletePerms);
239            Actor deletePerm = annot.getDeletePermission();
240            if (deletePerm != null) {
241                deletePerms.put(deletePerm.getIdString());
242            }
243            // update
244            JSONArray updatePerms = new JSONArray();
245            perms.put("update", updatePerms);
246            Actor updatePerm = annot.getUpdatePermission();
247            if (updatePerm != null) {
248                updatePerms.put(updatePerm.getIdString());
249            }
250            // read
251            JSONArray readPerms = new JSONArray();
252            perms.put("read", readPerms);
253            Actor readPerm = annot.getReadPermission();
254            if (readPerm != null) {
255                readPerms.put(readPerm.getIdString());
256            }
257           
258            // encode Annotation URL (=id) in base64
259            String annotUrl = annot.getUri();
260            String annotId = encodeJsonId(annotUrl);
261            jo.put("id", annotId);
262            return jo;
263        } catch (JSONException e) {
264            // TODO Auto-generated catch block
265            e.printStackTrace();
266        }
267        return null;
268    }
269
270    private JSONArray transformToRanges(List<String> xpointers) {
271
272        JSONArray ja = new JSONArray();
273
274        Pattern rg = Pattern
275                .compile("xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)/range-to\\(end-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)\\)");
276        Pattern rg1 = Pattern.compile("xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)");
277
278        try {
279            for (String xpointer : xpointers) {
280                // String decoded = URLDecoder.decode(xpointer, "utf-8");
281                String decoded = xpointer;
282                Matcher m = rg.matcher(decoded);
283
284                if (m.find()) {
285                    {
286                        JSONObject jo = new JSONObject();
287                        jo.put("start", m.group(1));
288                        jo.put("startOffset", m.group(2));
289                        jo.put("end", m.group(3));
290                        jo.put("endOffset", m.group(4));
291                        ja.put(jo);
292                    }
293                }
294                m = rg1.matcher(xpointer);
295                if (m.find()) {
296                    JSONObject jo = new JSONObject();
297                    jo.put("start", m.group(1));
298                    jo.put("startOffset", m.group(2));
299
300                    ja.put(jo);
301                }
302            }
303        } catch (JSONException e) {
304            // TODO Auto-generated catch block
305            e.printStackTrace();
306        }
307        return ja;
308    }
309
310    private JSONArray transformToAreas(List<String> xpointers) {
311
312        JSONArray ja = new JSONArray();
313
314        Pattern rg = Pattern.compile("xywh=(\\w*:)([\\d\\.]+),([\\d\\.]+),([\\d\\.]+),([\\d\\.]+)");
315
316        try {
317            for (String xpointer : xpointers) {
318                // String decoded = URLDecoder.decode(xpointer, "utf-8");
319                String decoded = xpointer;
320                Matcher m = rg.matcher(decoded);
321
322                if (m.find()) {
323                    {
324                        JSONObject jo = new JSONObject();
325                        @SuppressWarnings("unused")
326                        String unit = m.group(1);
327                        jo.put("x", m.group(2));
328                        jo.put("y", m.group(3));
329                        jo.put("width", m.group(4));
330                        jo.put("height", m.group(5));
331                        ja.put(jo);
332                    }
333                }
334            }
335        } catch (JSONException e) {
336            // TODO Auto-generated catch block
337            e.printStackTrace();
338        }
339        return ja;
340    }
341
342    protected String parseArea(JSONObject area) throws JSONException {
343        String x = area.getString("x");
344        String y = area.getString("y");
345        String width = "0";
346        String height = "0";
347        if (area.has("width")) {
348            width = area.getString("width");
349            height = area.getString("height");
350        }
351        String fragment = String.format("xywh=fraction:%s,%s,%s,%s", x, y, width, height);
352        return fragment;
353    }
354
355    protected String parseRange(JSONObject range) throws JSONException {
356        String start = range.getString("start");
357        String end = range.getString("end");
358        String startOffset = range.getString("startOffset");
359        String endOffset = range.getString("endOffset");
360
361        String fragment = String.format(
362                "xpointer(start-point(string-range(\"%s\",%s,1))/range-to(end-point(string-range(\"%s\",%s,1))))", start,
363                startOffset, end, endOffset);
364        return fragment;
365    }
366
367    /**
368     * Creates an Annotation object with data from JSON.
369     *
370     * uses the specification from the annotator project: {@link https
371     * ://github.com/okfn/annotator/wiki/Annotation-format}
372     *
373     * The username will be transformed to an URI if not given already as URI,
374     * if not it will set to the MPIWG namespace defined in
375     * de.mpiwg.itgroup.annotationManager.Constants.NS
376     *
377     * @param jo
378     * @return
379     * @throws JSONException
380     * @throws UnsupportedEncodingException
381     */
382    public Annotation createAnnotation(JSONObject jo, Representation entity) throws JSONException, UnsupportedEncodingException {
383        return updateAnnotation(new Annotation(), jo, entity);
384    }
385
386    /**
387     * Updates an Annotation object with data from JSON.
388     *
389     * uses the specification from the annotator project: {@link https
390     * ://github.com/okfn/annotator/wiki/Annotation-format}
391     *
392     * The username will be transformed to an URI if not given already as URI,
393     * if not it will set to the MPIWG namespace defined in
394     * de.mpiwg.itgroup.annotationManager.Constants.NS
395     *
396     * @param annot
397     * @param jo
398     * @return
399     * @throws JSONException
400     * @throws UnsupportedEncodingException
401     */
402    public Annotation updateAnnotation(Annotation annot, JSONObject jo, Representation entity) throws JSONException,
403            UnsupportedEncodingException {
404        // target uri
405        if (jo.has("uri")) {
406            annot.setTargetBaseUri(jo.getString("uri"));
407        }
408        // annotation text
409        if (jo.has("text")) {
410            annot.setBodyText(jo.getString("text"));
411        }
412        // check authentication
413        String authUser = checkAuthToken(entity);
414        if (authUser == null) {
415            /*
416             * // try http auth User httpUser = getHttpAuthUser(entity); if
417             * (httpUser == null) {
418             */
419            setStatus(Status.CLIENT_ERROR_FORBIDDEN);
420            return null;
421            /*
422             * } authUser = httpUser.getIdentifier();
423             */
424        }
425        // get or create creator object
426        Actor creator = annot.getCreator();
427        if (creator == null) {
428            creator = new Person();
429            annot.setCreator(creator);
430        }
431        // username not required, if no username given authuser will be used
432        String username = null;
433        String userUri = creator.getUri();
434        if (jo.has("user")) {
435            if (jo.get("user") instanceof String) {
436                // user is just a String
437                username = jo.getString("user");
438                creator.setId(username);
439                // TODO: what if username and authUser are different?
440            } else {
441                // user is an object
442                JSONObject user = jo.getJSONObject("user");
443                if (user.has("id")) {
444                    String id = user.getString("id");
445                    creator.setId(id);
446                    username = id;
447                }
448                if (user.has("uri")) {
449                    userUri = user.getString("uri");
450                }
451            }
452        }
453        if (username == null) {
454            username = authUser;
455        }
456        // try to get full name
457        if (creator.getName() == null && username != null) {
458            RestServer restServer = (RestServer) getApplication();
459            String fullName = restServer.getFullNameFromLdap(username);
460            creator.setName(fullName);
461        }
462        // userUri should be a URI, if not it will set to the MPIWG namespace
463        if (userUri == null) {
464            if (username.startsWith("http")) {
465                userUri = username;
466            } else {
467                userUri = NS.MPIWG_PERSONS_URL + username;
468            }
469        }
470        // TODO: should we overwrite the creator?
471        if (creator.getUri() == null) {
472            creator.setUri(userUri);
473        }
474
475        if (annot.getCreated() == null) {
476            // set creation date
477            SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
478            String ct = format.format(Calendar.getInstance().getTime());
479            annot.setCreated(ct);
480        }
481
482        // create xpointer from the first range/area
483        if (jo.has("ranges")) {
484            JSONObject ranges = jo.getJSONArray("ranges").getJSONObject(0);
485            annot.setFragmentType(FragmentTypes.XPOINTER);
486            String fragment = parseRange(ranges);
487            annot.setTargetFragment(fragment);
488        }
489        if (jo.has("areas")) {
490            JSONObject area = jo.getJSONArray("areas").getJSONObject(0);
491            annot.setFragmentType(FragmentTypes.AREA);
492            String fragment = parseArea(area);
493            annot.setTargetFragment(fragment);
494        }
495
496        // permissions
497        if (jo.has("permissions")) {
498            JSONObject permissions = jo.getJSONObject("permissions");
499            if (permissions.has("admin")) {
500                JSONArray perms = permissions.getJSONArray("admin");
501                Actor actor = getActorFromPermissions(perms);
502                annot.setAdminPermission(actor);
503            }
504            if (permissions.has("delete")) {
505                JSONArray perms = permissions.getJSONArray("delete");
506                Actor actor = getActorFromPermissions(perms);
507                annot.setDeletePermission(actor);
508            }
509            if (permissions.has("update")) {
510                JSONArray perms = permissions.getJSONArray("update");
511                Actor actor = getActorFromPermissions(perms);
512                annot.setUpdatePermission(actor);
513            }
514            if (permissions.has("read")) {
515                JSONArray perms = permissions.getJSONArray("read");
516                Actor actor = getActorFromPermissions(perms);
517                annot.setReadPermission(actor);
518            }
519        }
520
521        return annot;
522    }
523
524    @SuppressWarnings("unused")
525    protected Actor getActorFromPermissions(JSONArray perms) throws JSONException {
526        Actor actor = null;
527        for (int i = 0; i < perms.length(); ++i) {
528            String perm = perms.getString(i);
529            if (perm.toLowerCase().startsWith("group:")) {
530                String groupId = perm.substring(6);
531                actor = new Group(groupId);
532            } else {
533                actor = new Person(perm);
534            }
535            // we just take the first one
536            break;
537        }
538        return actor;
539    }
540
541}
Note: See TracBrowser for help on using the repository browser.