diff --git a/webapp/build.xml b/webapp/build.xml
index 41b3269c7..ee1a4280a 100644
--- a/webapp/build.xml
+++ b/webapp/build.xml
@@ -114,8 +114,8 @@ deploy - Deploy the application directly into the Tomcat webapps directory.
message="${deploy.properties.file} must contain a value for VitroConnection.DataSource.username" />
-
+
diff --git a/webapp/config/example.deploy.properties b/webapp/config/example.deploy.properties
index 9148818b3..339405801 100644
--- a/webapp/config/example.deploy.properties
+++ b/webapp/config/example.deploy.properties
@@ -83,11 +83,11 @@ VitroConnection.DataSource.validationQuery = SELECT 1
# vitro.local.solr.url =
#
-# The name of your first admin user for the Vitro application. The password
-# for this user is initially set to "defaultAdmin", but you will be asked to
+# The email address of the root user for the VIVO application. The password
+# for this user is initially set to "rootPassword", but you will be asked to
# change the password the first time you log in.
#
-initialAdminUser = defaultAdmin
+rootUser.emailAddress = root@myDomain.com
#
# How is a logged-in user associated with a particular Individual? One way is
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/identifier/common/CommonIdentifierBundleFactory.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/identifier/common/CommonIdentifierBundleFactory.java
index af51ba1d6..b2353e6ae 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/identifier/common/CommonIdentifierBundleFactory.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/identifier/common/CommonIdentifierBundleFactory.java
@@ -24,6 +24,7 @@ import edu.cornell.mannlib.vitro.webapp.beans.Individual;
import edu.cornell.mannlib.vitro.webapp.beans.SelfEditingConfiguration;
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao;
+import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao;
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
/**
@@ -49,6 +50,7 @@ public class CommonIdentifierBundleFactory implements IdentifierBundleFactory {
ArrayIdentifierBundle bundle = new ArrayIdentifierBundle();
bundle.addAll(createUserIdentifiers(req));
+ bundle.addAll(createRootUserIdentifiers(req));
bundle.addAll(createRoleLevelIdentifiers(req));
bundle.addAll(createBlacklistOrAssociatedIndividualIdentifiers(req));
@@ -68,6 +70,16 @@ public class CommonIdentifierBundleFactory implements IdentifierBundleFactory {
}
}
+ private Collection extends Identifier> createRootUserIdentifiers(
+ HttpServletRequest req) {
+ UserAccount user = LoginStatusBean.getCurrentUser(req);
+ if (isRootUser(user)) {
+ return Collections.singleton(new IsRootUser());
+ } else {
+ return Collections.emptySet();
+ }
+ }
+
/**
* Create an identifier that shows the role level of the current user, or
* PUBLIC if the user is not logged in.
@@ -131,6 +143,25 @@ public class CommonIdentifierBundleFactory implements IdentifierBundleFactory {
return individuals;
}
+ /**
+ * Is this user a root user?
+ */
+ private boolean isRootUser(UserAccount user) {
+ if (user == null) {
+ return false;
+ }
+
+ WebappDaoFactory wdf = (WebappDaoFactory) context
+ .getAttribute("webappDaoFactory");
+ if (wdf == null) {
+ log.error("Could not get a WebappDaoFactory from the ServletContext");
+ return false;
+ }
+
+ UserAccountsDao uaDao = wdf.getUserAccountsDao();
+ return uaDao.isRootUser(user);
+ }
+
@Override
public String toString() {
return this.getClass().getSimpleName() + " - " + hashCode();
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/identifier/common/IsRootUser.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/identifier/common/IsRootUser.java
new file mode 100644
index 000000000..a41c2ab04
--- /dev/null
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/identifier/common/IsRootUser.java
@@ -0,0 +1,21 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.auth.identifier.common;
+
+import edu.cornell.mannlib.vitro.webapp.auth.identifier.Identifier;
+import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
+
+/**
+ * The current user is a root user.
+ */
+public class IsRootUser extends AbstractCommonIdentifier implements Identifier {
+ public static boolean isRootUser(IdentifierBundle ids) {
+ return !getIdentifiersForClass(ids, IsRootUser.class).isEmpty();
+ }
+
+ @Override
+ public String toString() {
+ return "IsRootUser";
+ }
+
+}
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/RootUserPolicy.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/RootUserPolicy.java
new file mode 100644
index 000000000..362fcba1e
--- /dev/null
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/auth/policy/RootUserPolicy.java
@@ -0,0 +1,164 @@
+/* $This file is distributed under the terms of the license in /doc/license.txt$ */
+
+package edu.cornell.mannlib.vitro.webapp.auth.policy;
+
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+
+import org.apache.commons.logging.Log;
+import org.apache.commons.logging.LogFactory;
+
+import com.hp.hpl.jena.ontology.OntModel;
+import com.hp.hpl.jena.rdf.model.Resource;
+import com.hp.hpl.jena.shared.Lock;
+import com.hp.hpl.jena.vocabulary.RDF;
+
+import edu.cornell.mannlib.vitro.webapp.auth.identifier.IdentifierBundle;
+import edu.cornell.mannlib.vitro.webapp.auth.identifier.common.IsRootUser;
+import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.Authorization;
+import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyDecision;
+import edu.cornell.mannlib.vitro.webapp.auth.policy.ifaces.PolicyIface;
+import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.ifaces.RequestedAction;
+import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
+import edu.cornell.mannlib.vitro.webapp.beans.UserAccount.Status;
+import edu.cornell.mannlib.vitro.webapp.config.ConfigurationProperties;
+import edu.cornell.mannlib.vitro.webapp.controller.authenticate.Authenticator;
+import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao;
+import edu.cornell.mannlib.vitro.webapp.dao.VitroVocabulary;
+import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
+import edu.cornell.mannlib.vitro.webapp.dao.jena.ModelContext;
+import edu.cornell.mannlib.vitro.webapp.servlet.setup.AbortStartup;
+
+/**
+ * If the user has an IsRootUser identifier, they can do anything!
+ *
+ * On setup, check to see that there is a root user. If not, create one. If we
+ * can't create one, abort.
+ */
+public class RootUserPolicy implements PolicyIface {
+ private static final Log log = LogFactory.getLog(RootUserPolicy.class);
+
+ private static final String PROPERTY_ROOT_USER_EMAIL = "rootUser.emailAddress";
+ private static final String ROOT_USER_INITIAL_PASSWORD = "rootPassword";
+
+ /**
+ * This is the entire policy. If you are a root user, you are authorized.
+ */
+ @Override
+ public PolicyDecision isAuthorized(IdentifierBundle whoToAuth,
+ RequestedAction whatToAuth) {
+ if (IsRootUser.isRootUser(whoToAuth)) {
+ return new BasicPolicyDecision(Authorization.AUTHORIZED,
+ "RootUserPolicy: approved");
+ } else {
+ return new BasicPolicyDecision(Authorization.INCONCLUSIVE,
+ "not root user");
+ }
+ }
+
+ // ----------------------------------------------------------------------
+ // Setup class
+ // ----------------------------------------------------------------------
+
+ public static class Setup implements ServletContextListener {
+
+ @Override
+ public void contextInitialized(ServletContextEvent sce) {
+ ServletContext ctx = sce.getServletContext();
+
+ if (AbortStartup.isStartupAborted(ctx)) {
+ return;
+ }
+
+ try {
+ UserAccountsDao uaDao = getUserAccountsDao(ctx);
+ OntModel userAccountsModel = getUserAccountsModel(ctx);
+
+ if (!rootUserExists(uaDao)) {
+ createRootUser(ctx, uaDao, userAccountsModel);
+ }
+
+ ServletPolicyList.addPolicy(ctx, new RootUserPolicy());
+ } catch (Exception e) {
+ log.error("could not run " + this.getClass().getSimpleName()
+ + ": " + e);
+ AbortStartup.abortStartup(ctx);
+ throw new RuntimeException(e);
+ }
+ }
+
+ private UserAccountsDao getUserAccountsDao(ServletContext ctx) {
+ WebappDaoFactory wadf = (WebappDaoFactory) ctx
+ .getAttribute("webappDaoFactory");
+ if (wadf == null) {
+ throw new IllegalStateException(
+ "No webappDaoFactory on the servlet context");
+ }
+ return wadf.getUserAccountsDao();
+ }
+
+ private OntModel getUserAccountsModel(ServletContext ctx) {
+ return ModelContext.getBaseOntModelSelector(ctx)
+ .getUserAccountsModel();
+ }
+
+ private boolean rootUserExists(UserAccountsDao uaDao) {
+ for (UserAccount ua : uaDao.getAllUserAccounts()) {
+ if (uaDao.isRootUser(ua)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * TODO The first and last name should be left blank, so the user will
+ * be forced to edit them. However, that's not in place yet.
+ */
+ private void createRootUser(ServletContext ctx, UserAccountsDao uaDao,
+ OntModel userAccountsModel) {
+ String emailAddress = ConfigurationProperties.getBean(ctx)
+ .getProperty(PROPERTY_ROOT_USER_EMAIL);
+ if (emailAddress == null) {
+ throw new IllegalStateException(
+ "deploy.properties must contain a value for '"
+ + PROPERTY_ROOT_USER_EMAIL + "'");
+ }
+
+ if (null != uaDao.getUserAccountByEmail(emailAddress)) {
+ throw new IllegalStateException("Can't create root user - "
+ + "an account already exists with email address '"
+ + emailAddress + "'");
+ }
+
+ UserAccount ua = new UserAccount();
+ ua.setEmailAddress(emailAddress);
+ ua.setFirstName("root");
+ ua.setLastName("user");
+ ua.setMd5Password(Authenticator
+ .applyMd5Encoding(ROOT_USER_INITIAL_PASSWORD));
+ ua.setPasswordChangeRequired(true);
+ ua.setStatus(Status.ACTIVE);
+
+ uaDao.insertUserAccount(ua);
+
+ userAccountsModel.enterCriticalSection(Lock.WRITE);
+ try {
+ Resource r = userAccountsModel.getResource(ua.getUri());
+ Resource t = userAccountsModel
+ .getResource(VitroVocabulary.USERACCOUNT_ROOT_USER);
+ userAccountsModel.add(r, RDF.type, t);
+ } finally {
+ userAccountsModel.leaveCriticalSection();
+ }
+
+ log.info("Created root user as '" + emailAddress + "'");
+ }
+
+ @Override
+ public void contextDestroyed(ServletContextEvent sce) {
+ // Nothing to destroy
+ }
+ }
+}
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDao.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDao.java
index 927538a31..46115df77 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDao.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDao.java
@@ -40,6 +40,11 @@ public interface UserAccountsDao {
*/
UserAccount getUserAccountByExternalAuthId(String externalAuthId);
+ /**
+ * Is this UserAccount a root user?
+ */
+ boolean isRootUser(UserAccount userAccount);
+
/**
* Create a new UserAccount in the model.
*
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java
index f92bba41e..706b665cb 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/VitroVocabulary.java
@@ -174,6 +174,7 @@ public class VitroVocabulary {
// =============== Vitro UserAccount and PermissionSet vocabulary ===========
public static final String USERACCOUNT = VITRO_AUTH + "UserAccount";
+ public static final String USERACCOUNT_ROOT_USER = VITRO_AUTH + "RootUserAccount";
public static final String USERACCOUNT_EMAIL_ADDRESS = VITRO_AUTH + "emailAddress";
public static final String USERACCOUNT_FIRST_NAME = VITRO_AUTH + "firstName";
public static final String USERACCOUNT_LAST_NAME = VITRO_AUTH + "lastName";
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/UserAccountsDaoFiltering.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/UserAccountsDaoFiltering.java
index 2a08eb9f8..12b725c5b 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/UserAccountsDaoFiltering.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/UserAccountsDaoFiltering.java
@@ -47,6 +47,11 @@ public class UserAccountsDaoFiltering extends BaseFiltering implements
return innerDao.getUserAccountByExternalAuthId(externalAuthId);
}
+ @Override
+ public boolean isRootUser(UserAccount userAccount) {
+ return innerDao.isRootUser(userAccount);
+ }
+
@Override
public String insertUserAccount(UserAccount userAccount) {
return innerDao.insertUserAccount(userAccount);
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoCon.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoCon.java
index c455add30..c29a19e54 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoCon.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/JenaBaseDaoCon.java
@@ -155,6 +155,7 @@ public class JenaBaseDaoCon {
/* ***************** User Account Model constants ***************** */
protected OntClass USERACCOUNT = _constModel.createClass(VitroVocabulary.USERACCOUNT);
+ protected OntClass USERACCOUNT_ROOT_USER = _constModel.createClass(VitroVocabulary.USERACCOUNT_ROOT_USER);
protected DatatypeProperty USERACCOUNT_EMAIL_ADDRESS = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_EMAIL_ADDRESS);
protected DatatypeProperty USERACCOUNT_FIRST_NAME = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_FIRST_NAME);
protected DatatypeProperty USERACCOUNT_LAST_NAME = _constModel.createDatatypeProperty(VitroVocabulary.USERACCOUNT_LAST_NAME);
diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java
index 82613c2da..48b53a856 100644
--- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java
+++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java
@@ -157,6 +157,21 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
return getUserAccountByUri(userUri);
}
+ @Override
+ public boolean isRootUser(UserAccount userAccount) {
+ if (userAccount == null) {
+ return false;
+ }
+
+ getOntModel().enterCriticalSection(Lock.READ);
+ try {
+ OntResource r = getOntModel().getOntResource(userAccount.getUri());
+ return isResourceOfType(r, USERACCOUNT_ROOT_USER);
+ } finally {
+ getOntModel().leaveCriticalSection();
+ }
+ }
+
@Override
public String insertUserAccount(UserAccount userAccount) {
if (userAccount == null) {
@@ -361,6 +376,13 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao
* There should already be a lock on the model when this is called.
*/
private boolean isResourceOfType(OntResource r, OntClass type) {
+ if (r == null) {
+ return false;
+ }
+ if (type == null) {
+ return false;
+ }
+
StmtIterator stmts = getOntModel().listStatements(r, RDF.type, type);
if (stmts.hasNext()) {
stmts.close();
diff --git a/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDaoStub.java b/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDaoStub.java
index 42aa6a104..c0a96befe 100644
--- a/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDaoStub.java
+++ b/webapp/test/stubs/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDaoStub.java
@@ -48,6 +48,11 @@ public class UserAccountsDaoStub implements UserAccountsDao {
"UserAccountsDaoStub.getUserAccountByEmail() not implemented.");
}
+ @Override
+ public boolean isRootUser(UserAccount userAccount) {
+ throw new RuntimeException("UserAccountsDao.isRootUser() not implemented.");
+ }
+
@Override
public String insertUserAccount(UserAccount userAccount) {
throw new RuntimeException(