# HG changeset patch # User casties # Date 1332339837 -3600 # Node ID 9393c9c9b91677c5b6e93bbc3a973578853dc73d # Parent 2e5d526079de0f2b89e46bb76cad714b40362dc9 saves annotations now! diff -r 2e5d526079de -r 9393c9c9b916 .classpath --- a/.classpath Wed Mar 21 11:09:20 2012 +0100 +++ b/.classpath Wed Mar 21 15:23:57 2012 +0100 @@ -42,7 +42,11 @@ - - + + + + + + diff -r 2e5d526079de -r 9393c9c9b916 WebContent/WEB-INF/web.xml.template --- a/WebContent/WEB-INF/web.xml.template Wed Mar 21 11:09:20 2012 +0100 +++ b/WebContent/WEB-INF/web.xml.template Wed Mar 21 15:23:57 2012 +0100 @@ -4,38 +4,36 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> -RESTfulJavaWebServices-Restlet - - -org.restlet.application -de.mpiwg.itgroup.annotationManager.restlet.RestServer - - - -de.mpiwg.itgroup.annotationManager.jaas.configFilePath -file:///etc/jaasAuth.conf - - - -de.mpiwg.itgroup.annotationManager.virtuoso.tripleStoreUser -USERNAME - - - -de.mpiwg.itgroup.annotationManager.virtuoso.tripleStoreUserPassword -PASSWORD - - - -RestletServlet - -org.restlet.ext.servlet.ServerServlet - - - - - -RestletServlet -/annotator/* - + RESTfulJavaWebServices-Restlet + + + org.restlet.application + de.mpiwg.itgroup.annotationManager.restlet.RestServer + + + de.mpiwg.itgroup.annotationManager.jaas.configFilePath + file:///etc/jaasAuth.conf + + + de.mpiwg.itgroup.annotationManager.virtuoso.tripleStoreUser + USERNAME + + + de.mpiwg.itgroup.annotationManager.virtuoso.tripleStoreUserPassword + PASSWORD + + + de.mpiwg.itgroup.annotationManager.virtuoso.tripleStoreConnectionURL + jdbc:virtuoso://virtuoso.mpiwg-berlin.mpg.de:1111 + + + + RestletServlet + org.restlet.ext.servlet.ServerServlet + + + + RestletServlet + /annotator/* + \ No newline at end of file diff -r 2e5d526079de -r 9393c9c9b916 src/de/mpiwg/itgroup/annotationManager/RDFHandling/Convert.java --- a/src/de/mpiwg/itgroup/annotationManager/RDFHandling/Convert.java Wed Mar 21 11:09:20 2012 +0100 +++ b/src/de/mpiwg/itgroup/annotationManager/RDFHandling/Convert.java Wed Mar 21 15:23:57 2012 +0100 @@ -298,7 +298,10 @@ String user = context.getParameters().getFirstValue("de.mpiwg.itgroup.annotationManager.virtuoso.tripleStoreUser"); String pw = context.getParameters().getFirstValue("de.mpiwg.itgroup.annotationManager.virtuoso.tripleStoreUserPassword"); String connectionURL = context.getParameters().getFirstValue("de.mpiwg.itgroup.annotationManager.virtuoso.tripleStoreConnectionURL"); - + if (user == null || pw == null || connectionURL == null) { + logger.error(String.format("Missing triplestore parameters! (user=%s pw=%s url=%s)", user, pw, connectionURL)); + throw new TripleStoreStoreError(); + } //TripleStoreHandler th = new TripleStoreHandler("jdbc:virtuoso://virtuoso.mpiwg-berlin.mpg.de:1111", user, pw); TripleStoreHandler th = new TripleStoreHandler(connectionURL, user, pw); diff -r 2e5d526079de -r 9393c9c9b916 src/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorAnnotations.java --- a/src/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorAnnotations.java Wed Mar 21 11:09:20 2012 +0100 +++ b/src/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorAnnotations.java Wed Mar 21 15:23:57 2012 +0100 @@ -51,7 +51,7 @@ */ @Get("json") public Representation doGetJSON(Representation entity) { - + logger.debug("AnnotatorAnnotations doGetJSON!"); doOptions(entity); // TODO: what to do with authentication? boolean authenticated = isAuthenticated(entity); @@ -73,9 +73,9 @@ results = new JSONArray(); for (Convert.Annotation annot : annots) { - JSONObject jo = annot2AnnotatorJSON(annot); + JSONObject jo = createAnnotatorJson(annot); if (jo != null) { - results.put(annot2AnnotatorJSON(annot)); + results.put(createAnnotatorJson(annot)); } else { setStatus(Status.SERVER_ERROR_INTERNAL, "JSon Error"); return null; @@ -98,22 +98,27 @@ } /** - * POST with JSON content-type. json hash: username: name des users xpointer: xpointer auf den Ausschnitt (incl. der URL des + * POST with JSON content-type. + * + * json hash: username: name des users xpointer: xpointer auf den Ausschnitt (incl. der URL des * Dokumentes) text: text der annotation annoturl: url auf eine Annotation falls extern * * @return */ @Post("json") public Representation doPostJson(Representation entity) { - JsonRepresentation jrep; - Annotation annot; + logger.debug("AnnotatorAnnotations doPostJSON!"); + // set headers + setCorsHeaders(); + Annotation annot = null; try { - jrep = new JsonRepresentation(entity); + JsonRepresentation jrep = new JsonRepresentation(entity); JSONObject jo = jrep.getJsonObject(); if (jo == null) { setStatus(Status.SERVER_ERROR_INTERNAL); return null; } + // get Annotation object from posted JSON annot = createAnnotation(jo, entity); } catch (IOException e1) { setStatus(Status.SERVER_ERROR_INTERNAL); @@ -122,19 +127,21 @@ setStatus(Status.CLIENT_ERROR_BAD_REQUEST); return null; } - if (annot.xpointer == null || annot.creator == null) { + if (annot == null || annot.xpointer == null || annot.creator == null) { setStatus(Status.CLIENT_ERROR_BAD_REQUEST); return null; } - Annotation retVal; + Annotation storedAnnot; try { - retVal = new Convert("file:///annotations").storeAnnotation(annot); + // store Annotation + storedAnnot = new Convert("file:///annotations").storeAnnotation(annot); } catch (TripleStoreStoreError e) { e.printStackTrace(); setStatus(Status.SERVER_ERROR_INTERNAL, "TripleStore Error"); return null; } - JSONObject jo = annot2AnnotatorJSON(retVal); + // return stored annotation + JSONObject jo = createAnnotatorJson(storedAnnot); JsonRepresentation retRep = new JsonRepresentation(jo); return retRep; } @@ -147,6 +154,7 @@ */ @Post("html") public Representation doPostHtml(Representation entity) { + logger.debug("AnnotatorAnnotations doPostHtml!"); Convert.Annotation annot; annot = handleForm(entity); if (annot.xpointer == null || annot.creator == null) { @@ -177,16 +185,15 @@ return retRep; } - /** * - * @param entity should contain a form with the parameters "username", "password", "xpointer","text","uri","type" - * - * username,password is optional, if not given BasicAuthentification is used. + * @param entity + * should contain a form with the parameters "username", "password", "xpointer","text","uri","type" * - * + * username,password is optional, if not given BasicAuthentification is used. * - * If username given as a URI, the username will be transformed to an URI, username will be added to the MPIWG namespace defined in de.mpiwg.itgroup.annotationManager.Constants.NS + * If username given as a URI, the username will be transformed to an URI, username will be added to the MPIWG + * namespace defined in de.mpiwg.itgroup.annotationManager.Constants.NS * * @return */ @@ -213,7 +220,7 @@ User authUser = null; if (userFromForm == null) { - authUser = handleBasicAuthentification(entity); + authUser = getHttpAuthUser(entity); } // weder BasicAuth noch FormAuth @@ -228,17 +235,17 @@ username = authUser.getIdentifier(); } - //username should be a URI, if not it will set to the MPIWG namespace defined in de.mpiwg.itgroup.annotationManager.Constants.NS - String usernameOrig=username; + // username should be a URI, if not it will set to the MPIWG namespace defined in + // de.mpiwg.itgroup.annotationManager.Constants.NS + String usernameOrig = username; if (!username.startsWith("http")) - username=NS.MPIWG_PERSONS+username; - - if (mode.equals("complexAnnotation")){// Annotation mit text in externer ressource - + username = NS.MPIWG_PERSONS + username; + + if (mode.equals("complexAnnotation")) {// Annotation mit text in externer ressource + Context context = getContext(); String drupalPath = context.getParameters().getFirstValue("de.mpiwg.itgroup.annotationManager.drupalServer"); - - + AnnotationHandler ah = new AnnotationHandler(drupalPath); JSONObject newAnnot; try { @@ -249,7 +256,7 @@ return null; } try { - annot= new Convert.Annotation(xpointer, username, null, text, type, newAnnot.getString("node_uri")); + annot = new Convert.Annotation(xpointer, username, null, text, type, newAnnot.getString("node_uri")); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); @@ -257,8 +264,7 @@ return null; } } else - annot = new Convert.Annotation(xpointer, username, null, text, - type, url); + annot = new Convert.Annotation(xpointer, username, null, text, type, url); return annot; } diff -r 2e5d526079de -r 9393c9c9b916 src/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorResourceImpl.java --- a/src/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorResourceImpl.java Wed Mar 21 11:09:20 2012 +0100 +++ b/src/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorResourceImpl.java Wed Mar 21 15:23:57 2012 +0100 @@ -20,6 +20,7 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import org.restlet.data.ClientInfo; import org.restlet.data.Form; import org.restlet.data.Status; import org.restlet.representation.Representation; @@ -45,12 +46,45 @@ } /** + * returns a hex String of a SHA256 digest of text. + * + * @param text + * @return + */ + public String getSha256Digest(String text) { + String digest = null; + try { + MessageDigest md = MessageDigest.getInstance("SHA-256"); + md.update(text.getBytes("UTF-8")); + byte[] dg = md.digest(); + StringBuffer sb = new StringBuffer(); + for (byte b : dg) { + sb.append(String.format("%02x", b)); + } + digest = sb.toString(); + } catch (NoSuchAlgorithmException e) { + e.printStackTrace(); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + } + return digest; + } + + /** * Handle options request to allow CORS for AJAX. * * @param entity */ @Options public void doOptions(Representation entity) { + logger.debug("AnnotatorResourceImpl doOptions!"); + setCorsHeaders(); + } + + /** + * set headers to allow CORS for AJAX. + */ + protected void setCorsHeaders() { Form responseHeaders = (Form) getResponse().getAttributes().get("org.restlet.http.headers"); if (responseHeaders == null) { responseHeaders = new Form(); @@ -80,49 +114,42 @@ * @return */ public boolean isAuthenticated(Representation entity) { - // get authToken + return (checkAuthToken(entity) != null); + } + + /** + * checks Annotator Auth plugin authentication information from headers. returns userId if successful. + * + * @param entity + * @return + */ + public String checkAuthToken(Representation entity) { Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers"); String consumerKey = requestHeaders.getFirstValue("x-annotator-consumer-key", true); if (consumerKey == null) { - return false; + return null; } + // get stored consumer secret for key RestServer restServer = (RestServer) getApplication(); String consumerSecret = restServer.getConsumerSecret(consumerKey); logger.debug("requested consumer key=" + consumerKey + " secret=" + consumerSecret); if (consumerSecret == null) { - return false; + return null; } String userId = requestHeaders.getFirstValue("x-annotator-user-id", true); String issueTime = requestHeaders.getFirstValue("x-annotator-auth-token-issue-time", true); if (userId == null || issueTime == null) { - return false; + return null; } // compute hashed token based on the values we know // computed_token = hashlib.sha256(consumer.secret + user_id + issue_time).hexdigest() - String computedToken; - try { - MessageDigest md = MessageDigest.getInstance("SHA-256"); - String computedString = consumerSecret + userId + issueTime; - md.update(computedString.getBytes("UTF-8")); - byte[] dg = md.digest(); - StringBuffer sb = new StringBuffer(); - for (byte b : dg) { - sb.append(String.format("%02x", b)); - } - computedToken = sb.toString(); - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - return false; - } catch (UnsupportedEncodingException e) { - e.printStackTrace(); - return false; - } + String computedToken = getSha256Digest(consumerSecret + userId + issueTime); // compare to the token we got String authToken = requestHeaders.getFirstValue("x-annotator-auth-token", true); logger.debug(String.format("got: authToken=%s consumerSecret=%s userId=%s issueTime=%s", authToken, consumerSecret, userId, issueTime)); if (!computedToken.equals(authToken)) { - return false; + return null; } // check token lifetime // validity = iso8601.parse_date(issue_time) @@ -140,10 +167,10 @@ e.printStackTrace(); } if (tokenValidity == null || tokenValidity.isAfterNow() || tokenExpiry.isBeforeNow()) { - return false; + return null; } // must be ok then - return true; + return userId; } /** @@ -152,36 +179,45 @@ * @param annot * @return */ - public JSONObject annot2AnnotatorJSON(Convert.Annotation annot) { + public JSONObject createAnnotatorJson(Convert.Annotation annot) { + boolean makeUserObject = true; JSONObject jo = new JSONObject(); try { jo.put("text", annot.text); jo.put("uri", annot.url); - JSONObject userObject = new JSONObject(); - userObject.put("id", annot.creator); - - RestServer restServer = (RestServer) getApplication(); - - String userID = annot.creator; - if (userID.startsWith(NS.MPIWG_PERSONS)) { - userID = userID.replace(NS.MPIWG_PERSONS, ""); // entferne NAMESPACE + if (makeUserObject) { + // create user object + JSONObject userObject = new JSONObject(); + // save creator as uri + userObject.put("uri", annot.creator); + // make short user id + String userID = annot.creator; + if (userID.startsWith(NS.MPIWG_PERSONS)) { + userID = userID.replace(NS.MPIWG_PERSONS, ""); // entferne NAMESPACE + } + // save as id + userObject.put("id", userID); + // get full name + RestServer restServer = (RestServer) getApplication(); + String userName = restServer.getUserNameFromLdap(userID); + userObject.put("name", userName); + // save user object + jo.put("user", userObject); + } else { + // save user as string + jo.put("user", annot.creator); } - String userName = restServer.getUserNameFromLdap(userID); - userObject.put("name", userName); - jo.put("user", userObject); - - List xpointer = new ArrayList(); - + List xpointers = new ArrayList(); if (annot.xpointers == null || annot.xpointers.size() == 0) - xpointer.add(annot.xpointer); + xpointers.add(annot.xpointer); else { for (String xpointerString : annot.xpointers) { - xpointer.add(xpointerString); + xpointers.add(xpointerString); } } - jo.put("ranges", transformToRanges(xpointer)); + jo.put("ranges", transformToRanges(xpointers)); jo.put("id", annot.annotationUri); return jo; } catch (JSONException e) { @@ -235,56 +271,56 @@ } /** - * creates an Annotation object with data from JSON. - * uses the specification from the annotator project. + * creates an Annotation object with data from JSON. + * + * uses the specification from the annotator project: {@link https://github.com/okfn/annotator/wiki/Annotation-format} * - * @see{https://github.com/okfn/annotator/wiki/Annotation-format} + * 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 + * de.mpiwg.itgroup.annotationManager.Constants.NS * - * 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 - * de.mpiwg.itgroup.annotationManager.Constants.NS * @param jo * @return * @throws JSONException */ public Convert.Annotation createAnnotation(JSONObject jo, Representation entity) throws JSONException { - Convert.Annotation annot; String url = jo.getString("uri"); String text = jo.getString("text"); - - String username = null; - if (jo.has("user")) { - // not required, if no username given authuser - // will be used otherwise username and password - // has to be submitted - JSONObject user = jo.getJSONObject("user"); - if (user.has("id")) { - username = user.getString("id"); - if (!user.has("password")) { - User authUser = handleBasicAuthentification(entity); - if (authUser == null) { - setStatus(Status.CLIENT_ERROR_FORBIDDEN); - return null; - } - username = authUser.getIdentifier(); - } else { - String password = user.getString("password"); - if (!((RestServer) getApplication()).authenticate(username, password, getRequest())) { - setStatus(Status.CLIENT_ERROR_FORBIDDEN); - return null; - } - } - } - - } else { - User authUser = handleBasicAuthentification(entity); - if (authUser == null) { + + // check authentication + String authUser = checkAuthToken(entity); + if (authUser == null) { + // try http auth + User httpUser = getHttpAuthUser(entity); + if (httpUser == null) { setStatus(Status.CLIENT_ERROR_FORBIDDEN); return null; } - username = authUser.getIdentifier(); + authUser = httpUser.getIdentifier(); } - + // username not required, if no username given authuser will be used + String username = null; + String userUri = null; + if (jo.has("user")) { + if (jo.get("user") instanceof String) { + // user is just a String + username = jo.getString("user"); + // TODO: what if username and authUser are different? + } else { + // user is an object + JSONObject user = jo.getJSONObject("user"); + if (user.has("id")) { + username = user.getString("id"); + } + if (user.has("uri")) { + userUri = user.getString("uri"); + } + } + } + if (username == null) { + username = authUser; + } + + // create xpointer String xpointer; if (jo.has("ranges")) { JSONObject ranges = jo.getJSONArray("ranges").getJSONObject(0); @@ -292,7 +328,7 @@ String end = ranges.getString("end"); String startOffset = ranges.getString("startOffset"); String endOffset = ranges.getString("endOffset"); - + try { xpointer = url + "#" @@ -307,13 +343,36 @@ } else { xpointer = url; } - + // username should be a URI, if not it will set to the MPIWG namespace defined in // de.mpiwg.itgroup.annotationManager.Constants.NS - if (!username.startsWith("http")) - username = NS.MPIWG_PERSONS + username; - - return new Convert.Annotation(xpointer, username, null, text, null); + if (userUri == null) { + if (username.startsWith("http")) { + userUri = username; + } else { + userUri = NS.MPIWG_PERSONS + username; + } + } + return new Convert.Annotation(xpointer, userUri, null, text, null); + } + + /** + * returns the logged in User. + * + * @param entity + * @return + */ + protected User getHttpAuthUser(Representation entity) { + RestServer restServer = (RestServer) getApplication(); + if (!restServer.authenticate(getRequest(), getResponse())) { + // Not authenticated + return null; + } + + ClientInfo ci = getRequest().getClientInfo(); + logger.debug(ci); + return getRequest().getClientInfo().getUser(); + } } diff -r 2e5d526079de -r 9393c9c9b916 src/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorSearch.java --- a/src/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorSearch.java Wed Mar 21 11:09:20 2012 +0100 +++ b/src/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorSearch.java Wed Mar 21 15:23:57 2012 +0100 @@ -69,9 +69,9 @@ ja = new JSONArray(); for (Convert.Annotation annot : annots) { - JSONObject jo = annot2AnnotatorJSON(annot); + JSONObject jo = createAnnotatorJson(annot); if (jo != null) { - ja.put(annot2AnnotatorJSON(annot)); + ja.put(createAnnotatorJson(annot)); } else { setStatus(Status.SERVER_ERROR_INTERNAL, "JSON Error"); return null; diff -r 2e5d526079de -r 9393c9c9b916 src/de/mpiwg/itgroup/annotationManager/restlet/RestServer.java --- a/src/de/mpiwg/itgroup/annotationManager/restlet/RestServer.java Wed Mar 21 11:09:20 2012 +0100 +++ b/src/de/mpiwg/itgroup/annotationManager/restlet/RestServer.java Wed Mar 21 15:23:57 2012 +0100 @@ -186,8 +186,8 @@ Router router = new Router(getContext()); - router.attach("/annotations", AddAndReadAnnotations.class); - router.attach("/search", AnnotatorSearch.class); // annotator api askes + router.attach("/annotator/annotations", AnnotatorAnnotations.class); + router.attach("/annotator/search", AnnotatorSearch.class); // annotator api askes // for different uris // for search and // adding