Mercurial > hg > AnnotationManager
view src/de/mpiwg/itgroup/annotationManager/restlet/AnnotatorResourceImpl.java @ 20:6629e8422760
half baked version for new JWT auth :-(
author | casties |
---|---|
date | Fri, 23 Mar 2012 21:41:53 +0100 |
parents | b0ef5c860464 |
children | 0cd1e7608d25 |
line wrap: on
line source
/** * Base class for Annotator resource classes. */ package de.mpiwg.itgroup.annotationManager.restlet; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.ArrayList; import java.util.List; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.xml.bind.DatatypeConverter; import net.oauth.jsontoken.JsonToken; import net.oauth.jsontoken.JsonTokenParser; import org.apache.log4j.Logger; 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; import org.restlet.resource.Options; import org.restlet.resource.ServerResource; import org.restlet.security.User; import com.google.gson.JsonPrimitive; import de.mpiwg.itgroup.annotationManager.Constants.NS; import de.mpiwg.itgroup.annotationManager.RDFHandling.Annotation; /** * Base class for Annotator resource classes. * * @author dwinter, casties * */ public abstract class AnnotatorResourceImpl extends ServerResource { protected Logger logger = Logger.getRootLogger(); protected String getAllowedMethodsForHeader() { return "OPTIONS,GET,POST"; } /** * 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(); digest = DatatypeConverter.printHexBinary(dg); } catch (NoSuchAlgorithmException e) { e.printStackTrace(); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } return digest; } public String encodeJsonId(String id) { try { return DatatypeConverter.printBase64Binary(id.getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { return null; } } public String decodeJsonId(String id) { try { return new String(DatatypeConverter.parseBase64Binary(id), "UTF-8"); } catch (UnsupportedEncodingException e) { return null; } } /** * 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(); getResponse().getAttributes().put("org.restlet.http.headers", responseHeaders); } responseHeaders.add("Access-Control-Allow-Methods", getAllowedMethodsForHeader()); // echo back Origin and Request-Headers Form requestHeaders = (Form) getRequest().getAttributes().get("org.restlet.http.headers"); String origin = requestHeaders.getFirstValue("Origin", true); if (origin == null) { responseHeaders.add("Access-Control-Allow-Origin", "*"); } else { responseHeaders.add("Access-Control-Allow-Origin", origin); } String allowHeaders = requestHeaders.getFirstValue("Access-Control-Request-Headers", true); if (allowHeaders != null) { responseHeaders.add("Access-Control-Allow-Headers", allowHeaders); } responseHeaders.add("Access-Control-Allow-Credentials", "true"); responseHeaders.add("Access-Control-Max-Age", "60"); } /** * returns if authentication information from headers is valid. * * @param entity * @return */ public boolean isAuthenticated(Representation entity) { 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 authToken = requestHeaders.getFirstValue("x-annotator-auth-token", true); String userId = null; String tokenString; JsonToken token = new JsonTokenParser(null, null).deserialize(authToken); String consumerKey = token.getParamAsPrimitive("consumerKey").getAsString(); // 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 null; } logger.debug("token="+token); /* try { logger.debug(String.format("authToken=%s", authToken)); String[] tokenParts = authToken.split("\\."); logger.debug(String.format("tokenParts=%s", tokenParts.toString())); String payloadEnc = tokenParts[1]; if (payloadEnc.length() % 4 > 0) { // add padding for parseBase64Binary payloadEnc += "===".substring(0, payloadEnc.length() % 4); } String payloadString = new String(DatatypeConverter.parseBase64Binary(payloadEnc), "UTF-8"); logger.debug(String.format("payloadString=%s", payloadString)); JSONObject to = new JSONObject(payloadString); logger.debug(String.format("jsonToken=%s", to)); String consumerKey = to.getString("consumerKey"); // 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 null; } String decrypted = WebToken.decrypt(authToken, consumerSecret); logger.debug("decrypted="+decrypted); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ArrayIndexOutOfBoundsException e) { e.printStackTrace(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } */ //WebToken.decrypt(encrypted, password) /* String consumerKey = requestHeaders.getFirstValue("x-annotator-consumer-key", true); if (consumerKey == null) { 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 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 null; } // compute hashed token based on the values we know // computed_token = hashlib.sha256(consumer.secret + user_id + issue_time).hexdigest() 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 computedToken=%s", authToken, consumerSecret, userId, issueTime, computedToken)); if (!computedToken.equalsIgnoreCase(authToken)) { logger.warn("authToken differ!"); return null; } // check token lifetime // validity = iso8601.parse_date(issue_time) // expiry = validity + datetime.timedelta(seconds=consumer.ttl) int tokenTtl = 86400; DateTime tokenValidity = null; DateTime tokenExpiry = null; try { DateTimeFormatter parser = ISODateTimeFormat.dateTime(); tokenValidity = parser.parseDateTime(issueTime); String tokenTtlString = requestHeaders.getFirstValue("x-annotator-auth-token-ttl", true); tokenTtl = Integer.parseInt(tokenTtlString); tokenExpiry = tokenValidity.plusSeconds(tokenTtl); } catch (NumberFormatException e) { e.printStackTrace(); } if (tokenValidity == null || tokenValidity.isAfterNow() || tokenExpiry == null || tokenExpiry.isBeforeNow()) { logger.warn(String.format("authToken invalid! tokenValidity=%s tokenExpiry=%s now=%s", tokenValidity, tokenExpiry, DateTime.now())); // we dont care about validity right now //return null; } */ // must be ok then logger.debug("auth OK! user="+userId); return userId; } /** * creates Annotator-JSON from an Annotation object. * * @param annot * @return */ public JSONObject createAnnotatorJson(Annotation annot) { boolean makeUserObject = true; JSONObject jo = new JSONObject(); try { jo.put("text", annot.text); jo.put("uri", annot.url); 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); } List<String> xpointers = new ArrayList<String>(); if (annot.xpointers == null || annot.xpointers.size() == 0) xpointers.add(annot.xpointer); else { for (String xpointerString : annot.xpointers) { xpointers.add(xpointerString); } } jo.put("ranges", transformToRanges(xpointers)); // encode Annotation URL (=id) in base64 String annotUrl = annot.getAnnotationUri(); String annotId = encodeJsonId(annotUrl); jo.put("id", annotId); return jo; } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } return null; } private JSONArray transformToRanges(List<String> xpointers) { JSONArray ja = new JSONArray(); Pattern rg = Pattern .compile("#xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)/range-to\\(end-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)\\)"); Pattern rg1 = Pattern.compile("#xpointer\\(start-point\\(string-range\\(\"([^\"]*)\",([^,]*),1\\)\\)\\)"); try { for (String xpointer : xpointers) { String decoded = URLDecoder.decode(xpointer, "utf-8"); Matcher m = rg.matcher(decoded); if (m.find()) { { JSONObject jo = new JSONObject(); jo.put("start", m.group(1)); jo.put("startOffset", m.group(2)); jo.put("end", m.group(3)); jo.put("endOffset", m.group(4)); ja.put(jo); } } m = rg1.matcher(xpointer); if (m.find()) { JSONObject jo = new JSONObject(); jo.put("start", m.group(1)); jo.put("startOffset", m.group(2)); ja.put(jo); } } } catch (JSONException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (UnsupportedEncodingException e) { // TODO Auto-generated catch block e.printStackTrace(); } return ja; } /** * creates an Annotation object with data from JSON. * * uses the specification from the annotator project: {@link 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 * * @param jo * @return * @throws JSONException */ public Annotation createAnnotation(JSONObject jo, Representation entity) throws JSONException { return updateAnnotation(new Annotation(), jo, entity); } public Annotation updateAnnotation(Annotation annot, JSONObject jo, Representation entity) throws JSONException { // annotated uri String url = annot.url; if (jo.has("uri")) { url = jo.getString("uri"); } // annotation text String text = annot.text; if (jo.has("text")) { text = jo.getString("text"); } // 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; } authUser = httpUser.getIdentifier(); } // username not required, if no username given authuser will be used String username = null; String userUri = annot.creator; 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; } // username should be a URI, if not it will set to the MPIWG namespace defined in // de.mpiwg.itgroup.annotationManager.Constants.NS if (userUri == null) { if (username.startsWith("http")) { userUri = username; } else { userUri = NS.MPIWG_PERSONS + username; } } // TODO: should we overwrite the creator? // create xpointer String xpointer = annot.xpointer; if (jo.has("ranges")) { JSONObject ranges = jo.getJSONArray("ranges").getJSONObject(0); String start = ranges.getString("start"); String end = ranges.getString("end"); String startOffset = ranges.getString("startOffset"); String endOffset = ranges.getString("endOffset"); try { xpointer = url + "#" + URLEncoder.encode(String.format( "xpointer(start-point(string-range(\"%s\",%s,1))/range-to(end-point(string-range(\"%s\",%s,1))))", start, startOffset, end, endOffset), "utf-8"); } catch (UnsupportedEncodingException e) { e.printStackTrace(); setStatus(Status.SERVER_ERROR_INTERNAL); return null; } } return new Annotation(xpointer, userUri, annot.time, text, annot.type); } /** * 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(); } }