diff --git a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/test-user-model.owl b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/test-user-model.owl index c6e9a3523..a84b13707 100644 --- a/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/test-user-model.owl +++ b/utilities/testrunner/src/edu/cornell/mannlib/vitro/utilities/testrunner/test-user-model.owl @@ -16,7 +16,7 @@ ACTIVE 1 0 - + @@ -28,7 +28,7 @@ ACTIVE 1 0 - + @@ -40,7 +40,7 @@ ACTIVE 1 0 - + @@ -52,7 +52,7 @@ ACTIVE 1 0 - + diff --git a/webapp/rdf/auth/everytime/permission_config.n3 b/webapp/rdf/auth/everytime/permission_config.n3 index c4a88018a..e0b1c1960 100644 --- a/webapp/rdf/auth/everytime/permission_config.n3 +++ b/webapp/rdf/auth/everytime/permission_config.n3 @@ -12,6 +12,7 @@ auth:ADMIN # ADMIN-only permissions auth:hasPermission simplePermission:AccessSpecialDataModels ; + auth:hasPermission simplePermission:EnableDeveloperPanel ; auth:hasPermission simplePermission:LoginDuringMaintenance ; auth:hasPermission simplePermission:ManageMenus ; auth:hasPermission simplePermission:ManageProxies ; @@ -23,8 +24,12 @@ auth:ADMIN auth:hasPermission simplePermission:UseAdvancedDataToolsPages ; auth:hasPermission simplePermission:UseMiscellaneousAdminPages ; auth:hasPermission simplePermission:UseSparqlQueryPage ; - auth:hasPermission simplePermission:PageViewableAdmin ; - auth:hasPermission simplePermission:EnableDeveloperPanel ; + auth:hasPermission simplePermission:PageViewableAdmin ; + + # Uncomment the following permission line to enable the SPARQL update API. + # Before enabling, be sure that the URL api/sparqlUpdate is secured by SSH, + # so passwords will not be sent in clear text. +# auth:hasPermission simplePermission:UseSparqlUpdateApi ; # permissions for CURATOR and above. auth:hasPermission simplePermission:EditOntology ; diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java index 33c609c5b..0484638bd 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/permissions/SimplePermission.java @@ -38,6 +38,8 @@ public class SimplePermission extends Permission { NAMESPACE + "EditOwnAccount"); public static final SimplePermission EDIT_SITE_INFORMATION = new SimplePermission( NAMESPACE + "EditSiteInformation"); + public static final SimplePermission ENABLE_DEVELOPER_PANEL = new SimplePermission( + NAMESPACE + "EnableDeveloperPanel"); public static final SimplePermission LOGIN_DURING_MAINTENANCE = new SimplePermission( NAMESPACE + "LoginDuringMaintenance"); public static final SimplePermission MANAGE_MENUS = new SimplePermission( @@ -76,9 +78,8 @@ public class SimplePermission extends Permission { NAMESPACE + "UseAdvancedDataToolsPages"); public static final SimplePermission USE_SPARQL_QUERY_PAGE = new SimplePermission( NAMESPACE + "UseSparqlQueryPage"); - public static final SimplePermission ENABLE_DEVELOPER_PANEL = new SimplePermission( - NAMESPACE + "EnableDeveloperPanel"); - + public static final SimplePermission USE_SPARQL_UPDATE_API = new SimplePermission( + NAMESPACE + "UseSparqlUpdateApi"); // ---------------------------------------------------------------------- // These instances are "catch all" permissions to cover poorly defined diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java index d8a606efb..b1250a855 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/PolicyHelper.java @@ -83,11 +83,11 @@ public class PolicyHelper { } try{ - Authenticator basicAuth = new BasicAuthenticator(req); - UserAccount user = basicAuth.getAccountForInternalAuth( email ); + Authenticator auth = Authenticator.getInstance(req); + UserAccount user = auth.getAccountForInternalAuth( email ); log.debug("userAccount is " + user==null?"null":user.getUri() ); - if( ! basicAuth.isCurrentPassword( user, password ) ){ + if( ! auth.isCurrentPassword( user, password ) ){ log.debug(String.format("UNAUTHORIZED, password not accepted for %s, account URI: %s", user.getEmailAddress(), user.getUri())); return false; diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/SparqlUpdateApiController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/SparqlUpdateApiController.java new file mode 100644 index 000000000..60a0e6af0 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/api/SparqlUpdateApiController.java @@ -0,0 +1,201 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.api; + +import static javax.servlet.http.HttpServletResponse.SC_BAD_REQUEST; +import static javax.servlet.http.HttpServletResponse.SC_FORBIDDEN; +import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR; +import static javax.servlet.http.HttpServletResponse.SC_OK; + +import java.io.IOException; +import java.io.PrintWriter; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.query.Dataset; +import com.hp.hpl.jena.update.GraphStore; +import com.hp.hpl.jena.update.GraphStoreFactory; +import com.hp.hpl.jena.update.UpdateAction; +import com.hp.hpl.jena.update.UpdateFactory; +import com.hp.hpl.jena.update.UpdateRequest; + +import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; +import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator; +import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset; +import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder; + +/** + * This extends HttpServlet instead of VitroHttpServlet because we want to have + * full control over the response: + * + * + * So these responses will be produced: + * + *
+ * 200 Success
+ * 400 Failed to parse SPARQL update
+ * 400 SPARQL update must specify a GRAPH URI.
+ * 403 username/password combination is not valid
+ * 403 Account is not authorized
+ * 405 Method not allowed
+ * 500 Unknown error
+ * 
+ */ +public class SparqlUpdateApiController extends HttpServlet { + private static final Log log = LogFactory + .getLog(SparqlUpdateApiController.class); + + private static final Actions REQUIRED_ACTIONS = SimplePermission.USE_SPARQL_UPDATE_API.ACTIONS; + + @Override + protected void doPost(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + try { + checkAuthorization(req); + UpdateRequest parsed = parseUpdateString(req); + executeUpdate(req, parsed); + do200response(resp); + } catch (AuthException e) { + do403response(resp, e); + } catch (ParseException e) { + do400response(resp, e); + } catch (Exception e) { + do500response(resp, e); + } + } + + private void checkAuthorization(HttpServletRequest req) + throws AuthException { + String email = req.getParameter("email"); + String password = req.getParameter("password"); + + Authenticator auth = Authenticator.getInstance(req); + UserAccount account = auth.getAccountForInternalAuth(email); + if (!auth.isCurrentPassword(account, password)) { + log.debug("Invalid: '" + email + "'/'" + password + "'"); + throw new AuthException("email/password combination is not valid"); + } + + if (!PolicyHelper.isAuthorizedForActions(req, email, password, + REQUIRED_ACTIONS)) { + log.debug("Not authorized: '" + email + "'"); + throw new AuthException("Account is not authorized"); + } + + log.debug("Authorized for '" + email + "'"); + } + + private UpdateRequest parseUpdateString(HttpServletRequest req) + throws ParseException { + String update = req.getParameter("update"); + if (StringUtils.isBlank(update)) { + log.debug("No update parameter."); + throw new ParseException("No 'update' parameter."); + } + + if (!StringUtils.containsIgnoreCase(update, "GRAPH")) { + if (log.isDebugEnabled()) { + log.debug("No GRAPH uri in '" + update + "'"); + } + throw new ParseException("SPARQL update must specify a GRAPH URI."); + } + + try { + return UpdateFactory.create(update); + } catch (Exception e) { + log.debug("Problem parsing", e); + throw new ParseException("Failed to parse SPARQL update", e); + } + } + + private void executeUpdate(HttpServletRequest req, UpdateRequest parsed) { + ServletContext ctx = req.getSession().getServletContext(); + VitroRequest vreq = new VitroRequest(req); + + IndexBuilder.getBuilder(ctx).pause(); + try { + Dataset ds = new RDFServiceDataset(vreq.getUnfilteredRDFService()); + GraphStore graphStore = GraphStoreFactory.create(ds); + UpdateAction.execute(parsed, graphStore); + } finally { + IndexBuilder.getBuilder(ctx).unpause(); + } + } + + private void do200response(HttpServletResponse resp) throws IOException { + doResponse(resp, SC_OK, "SPARQL update accepted."); + } + + private void do403response(HttpServletResponse resp, AuthException e) + throws IOException { + doResponse(resp, SC_FORBIDDEN, e.getMessage()); + } + + private void do400response(HttpServletResponse resp, ParseException e) + throws IOException { + if (e.getCause() == null) { + doResponse(resp, SC_BAD_REQUEST, e.getMessage()); + } else { + doResponse(resp, SC_BAD_REQUEST, e.getMessage(), e.getCause()); + } + } + + private void do500response(HttpServletResponse resp, Exception e) + throws IOException { + doResponse(resp, SC_INTERNAL_SERVER_ERROR, "Unknown error", e); + } + + private void doResponse(HttpServletResponse resp, int statusCode, + String message) throws IOException { + resp.setStatus(statusCode); + PrintWriter writer = resp.getWriter(); + writer.println("

" + statusCode + " " + message + "

"); + } + + private void doResponse(HttpServletResponse resp, int statusCode, + String message, Throwable e) throws IOException { + resp.setStatus(statusCode); + PrintWriter writer = resp.getWriter(); + writer.println("

" + statusCode + " " + message + "

"); + writer.println("
");
+		e.printStackTrace(writer);
+		writer.println("
"); + } + + // ---------------------------------------------------------------------- + // Helper classes + // ---------------------------------------------------------------------- + + private static class AuthException extends Exception { + private AuthException(String message) { + super(message); + } + } + + private static class ParseException extends Exception { + private ParseException(String message) { + super(message); + } + + private ParseException(String message, Throwable cause) { + super(message, cause); + } + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java index 785bb031b..fe08c7d95 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/PageController.java @@ -58,15 +58,16 @@ public class PageController extends FreemarkerHttpServlet{ if( pageActs == null && dgActs == null){ return Actions.AUTHORIZED; - }else if( pageActs == null && dgActs != null ){ + }else if( pageActs == null ){ return dgActs; + }else if( dgActs == null ){ + return pageActs; }else{ - return pageActs; + return pageActs.and(dgActs); } } catch (Exception e) { - // TODO Auto-generated catch block - log.debug(e); + log.warn(e); return Actions.UNAUTHORIZED; } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java index 23e300013..fd333560b 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/PageDaoJena.java @@ -537,13 +537,14 @@ public class PageDaoJena extends JenaBaseDao implements PageDao { List actions = new ArrayList(); Model dModel = getOntModelSelector().getDisplayModel(); + dModel.enterCriticalSection(false); try{ QueryExecution qe = QueryExecutionFactory.create( requiredActionsQuery, dModel, initialBindings); actions = executeQueryToList( qe ); qe.close(); }finally{ - dModel.enterCriticalSection(false); + dModel.leaveCriticalSection(); } return actions; } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/loader/FreemarkerTemplateLoader.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/loader/FreemarkerTemplateLoader.java index 2ae556862..0bef38b30 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/loader/FreemarkerTemplateLoader.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/freemarker/loader/FreemarkerTemplateLoader.java @@ -285,7 +285,7 @@ public class FreemarkerTemplateLoader implements TemplateLoader { } public boolean fileQualifies(Path path) { - return Files.isRegularFile(path) && Files.isReadable(path); + return Files.isReadable(path) && !Files.isDirectory(path); } public SortedSet getMatches() { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java index 6b917c43f..5ccea3bfd 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/servlet/setup/ThemeInfoSetup.java @@ -73,8 +73,8 @@ public class ThemeInfoSetup implements ServletContextListener { ApplicationBean.themeInfo = new ThemeInfo(themesBaseDir, defaultThemeName, themeNames); - ss.info(this, ", current theme: " + currentThemeName - + "default theme: " + defaultThemeName + ", available themes: " + ss.info(this, "current theme: " + currentThemeName + + ", default theme: " + defaultThemeName + ", available themes: " + themeNames); } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlUpdate.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlUpdate.java deleted file mode 100644 index 7bb33194b..000000000 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/utils/dataGetter/SparqlUpdate.java +++ /dev/null @@ -1,96 +0,0 @@ -/* $This file is distributed under the terms of the license in /doc/license.txt$ */ - -package edu.cornell.mannlib.vitro.webapp.utils.dataGetter; - -import java.util.HashMap; -import java.util.Map; - -import javax.servlet.ServletContext; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -import com.hp.hpl.jena.query.Dataset; -import com.hp.hpl.jena.rdf.model.Model; -import com.hp.hpl.jena.update.GraphStore; -import com.hp.hpl.jena.update.GraphStoreFactory; -import com.hp.hpl.jena.update.UpdateAction; - -import edu.cornell.mannlib.vitro.webapp.auth.permissions.SimplePermission; -import edu.cornell.mannlib.vitro.webapp.auth.policy.PolicyHelper; -import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; -import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequiresActions; -import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; -import edu.cornell.mannlib.vitro.webapp.dao.jena.RDFServiceDataset; -import edu.cornell.mannlib.vitro.webapp.search.indexing.IndexBuilder; - -/** - * Handle a SPARQL Update request. This uses Jena ARQ and the RDFServiceDataset to - * evaluate a SPARQL Update with the RDFService. - * - * The reason to make this a DataGettere was to allow configuration in RDF of this - * service. - */ -public class SparqlUpdate implements DataGetter, RequiresActions{ - - private static final Log log = LogFactory.getLog(SparqlUpdate.class); - - VitroRequest vreq; - ServletContext context; - - public SparqlUpdate( - VitroRequest vreq, Model displayModel, String dataGetterURI ) { - if( vreq == null ) - throw new IllegalArgumentException("VitroRequest may not be null."); - this.vreq = vreq; - this.context = vreq.getSession().getServletContext(); - } - - - /** - * Gets the update from the request and then executes it on - * the RDFService. - */ - @Override - public Map getData( Map valueMap ) { - HashMap data = new HashMap(); - - String update = vreq.getParameter("update"); - - if( update != null && !update.trim().isEmpty()){ - try{ - IndexBuilder.getBuilder(context).pause(); - Dataset ds = new RDFServiceDataset( vreq.getUnfilteredRDFService() ); - GraphStore graphStore = GraphStoreFactory.create(ds); - log.warn("The SPARQL update is '"+vreq.getParameter("update")+"'"); - UpdateAction.parseExecute( vreq.getParameter("update") , graphStore ); - }finally{ - IndexBuilder.getBuilder(context).unpause(); - } - - } - - data.put("bodyTemplate", "page-sparqlUpdateTest.ftl"); - return data; - } - - - /** - * Check if this request is authorized by the email/password. - * If not do normal authorization. - */ - @Override - public Actions requiredActions(VitroRequest vreq) { - String email = vreq.getParameter("email"); - String password = vreq.getParameter("password"); - - boolean isAuth = PolicyHelper.isAuthorizedForActions(vreq, - email, password, SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS); - - if( isAuth ) - return Actions.AUTHORIZED; - else - return SimplePermission.MANAGE_SEARCH_INDEX.ACTIONS; - } - -} diff --git a/webapp/web/WEB-INF/web.xml b/webapp/web/WEB-INF/web.xml index 3a5377bda..1d393a7b9 100644 --- a/webapp/web/WEB-INF/web.xml +++ b/webapp/web/WEB-INF/web.xml @@ -1025,6 +1025,16 @@ /admin/sparqlquery + + SparqlUpdateApi + edu.cornell.mannlib.vitro.webapp.controller.api.SparqlUpdateApiController + + + + SparqlUpdateApi + /api/sparqlUpdate + + primitiveRdfEdit edu.cornell.mannlib.vitro.webapp.controller.edit.PrimitiveRdfEdit diff --git a/webapp/web/templates/freemarker/body/menupage/page-sparqlUpdateTest.ftl b/webapp/web/templates/freemarker/body/menupage/page-sparqlUpdateTest.ftl deleted file mode 100644 index 17274f829..000000000 --- a/webapp/web/templates/freemarker/body/menupage/page-sparqlUpdateTest.ftl +++ /dev/null @@ -1,12 +0,0 @@ -<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> - -

SPARQL Update Test

- -

This is an expermental SPARQL update service.

- -
-

- - -

-
\ No newline at end of file