You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by da...@apache.org on 2014/09/12 08:25:44 UTC

git commit: ISIS-883, ISIS-885, ISIS-846: prevent user circumventing security by hacking a URL.

Repository: isis
Updated Branches:
  refs/heads/master df5702911 -> 48694d8e6


ISIS-883, ISIS-885, ISIS-846: prevent user circumventing security by hacking a URL.

for (bookmarked actions), check business rules on execution, throw new ObjectMember.AuthorizationException if fails visibility or usability checks
for entities, if paste in URL, check user has permissions to at least one property or collection, throw AuthorizationException otherwise
for entities, if cannot load object, throw AuthorizationException (avoid disclosing whether the object exists or not)
for error page, if receive AuthorizationException then suppress the stack trace to avoid leaking information to possible attacker

in addition:
- for example todoapp, simplified


Project: http://git-wip-us.apache.org/repos/asf/isis/repo
Commit: http://git-wip-us.apache.org/repos/asf/isis/commit/48694d8e
Tree: http://git-wip-us.apache.org/repos/asf/isis/tree/48694d8e
Diff: http://git-wip-us.apache.org/repos/asf/isis/diff/48694d8e

Branch: refs/heads/master
Commit: 48694d8e6ada55179aa0d5ce547c3bda126b603e
Parents: df57029
Author: Dan Haywood <da...@haywood-associates.co.uk>
Authored: Fri Sep 12 07:25:28 2014 +0100
Committer: Dan Haywood <da...@haywood-associates.co.uk>
Committed: Fri Sep 12 07:25:28 2014 +0100

----------------------------------------------------------------------
 .../viewer/wicket/model/models/ActionModel.java | 23 ++++---
 .../wicket/model/models/ModelAbstract.java      |  7 ++-
 .../ajaxtable/BulkActionsLinkFactory.java       |  7 ++-
 .../components/widgets/cssmenu/CssMenuItem.java | 14 ++---
 .../viewer/wicket/ui/errors/ExceptionModel.java | 57 ++++++++++++-----
 .../ui/errors/ExceptionStackTracePanel.java     | 23 ++++---
 .../wicket/ui/pages/entity/EntityPage.java      | 26 ++++++--
 .../viewer/wicket/ui/pages/error/ErrorPage.java |  4 +-
 .../container/DomainObjectContainerDefault.java | 10 ++-
 .../metamodel/spec/feature/ObjectAction.java    | 17 ++++-
 .../metamodel/spec/feature/ObjectMember.java    | 35 ++++++++---
 .../specloader/specimpl/ObjectActionImpl.java   | 27 +++++++-
 .../specimpl/ObjectMemberAbstract.java          | 14 ++++-
 .../dom/src/main/java/dom/todo/ToDoItems.java   |  2 +
 .../webapp/src/main/resources/webapp/realm1.ini |  9 +++
 .../webapp/src/main/resources/webapp/realm2.ini | 66 --------------------
 .../webapp/src/main/webapp/WEB-INF/shiro.ini    |  6 +-
 17 files changed, 211 insertions(+), 136 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ActionModel.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ActionModel.java b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ActionModel.java
index 831df0e..b086bbd 100644
--- a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ActionModel.java
+++ b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ActionModel.java
@@ -28,12 +28,10 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
-
 import com.google.common.base.Throwables;
 import com.google.common.collect.Iterables;
 import com.google.common.collect.Lists;
 import com.google.common.collect.Maps;
-
 import org.apache.wicket.request.IRequestHandler;
 import org.apache.wicket.request.handler.resource.ResourceStreamRequestHandler;
 import org.apache.wicket.request.http.handler.RedirectRequestHandler;
@@ -43,14 +41,15 @@ import org.apache.wicket.util.resource.AbstractResourceStream;
 import org.apache.wicket.util.resource.IResourceStream;
 import org.apache.wicket.util.resource.ResourceStreamNotFoundException;
 import org.apache.wicket.util.resource.StringResourceStream;
-
-import org.apache.isis.applib.RecoverableException;
 import org.apache.isis.applib.Identifier;
+import org.apache.isis.applib.RecoverableException;
 import org.apache.isis.applib.annotation.ActionSemantics;
 import org.apache.isis.applib.annotation.BookmarkPolicy;
+import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.value.Blob;
 import org.apache.isis.applib.value.Clob;
 import org.apache.isis.applib.value.NamedWithMimeType;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
 import org.apache.isis.core.metamodel.adapter.oid.OidMarshaller;
@@ -447,16 +446,22 @@ public class ActionModel extends BookmarkableModel<ObjectAdapter> {
         
         return results;
     }
-    
+
+    // REVIEW: should provide this rendering context, rather than hardcoding.
+    // the net effect currently is that class members annotated with
+    // @Hidden(where=Where.ANYWHERE) or @Disabled(where=Where.ANYWHERE) will indeed
+    // be hidden/disabled, but will be visible/enabled (perhaps incorrectly)
+    // for any other value for Where
+    public static final Where WHERE_FOR_ACTION_INVOCATION = Where.ANYWHERE;
+
     private ObjectAdapter executeAction() {
+
         final ObjectAdapter targetAdapter = getTargetAdapter();
         final ObjectAdapter[] arguments = getArgumentsAsArray();
         final ObjectAction action = getActionMemento().getAction();
 
-        // let any exceptions propogate, will be caught by UI layer 
-        // (ActionPanel at time of writing)
-        final ObjectAdapter results = action.execute(targetAdapter, arguments);
-        return results;
+        final AuthenticationSession session = getAuthenticationSession();
+        return action.executeWithRuleChecking(targetAdapter, arguments, session, WHERE_FOR_ACTION_INVOCATION);
     }
 
     public String getReasonInvalidIfAny() {

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ModelAbstract.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ModelAbstract.java b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ModelAbstract.java
index 2381092..24361af 100644
--- a/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ModelAbstract.java
+++ b/component/viewer/wicket/model/src/main/java/org/apache/isis/viewer/wicket/model/models/ModelAbstract.java
@@ -20,9 +20,8 @@
 package org.apache.isis.viewer.wicket.model.models;
 
 import java.util.List;
-
 import org.apache.wicket.model.LoadableDetachableModel;
-
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
 import org.apache.isis.core.runtime.system.context.IsisContext;
@@ -47,6 +46,10 @@ public abstract class ModelAbstract<T> extends LoadableDetachableModel<T> {
     // Dependencies
     // //////////////////////////////////////////////////////////////
 
+    protected AuthenticationSession getAuthenticationSession() {
+        return IsisContext.getAuthenticationSession();
+    }
+
     protected Persistor getPersistenceSession() {
         return IsisContext.getPersistenceSession();
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkActionsLinkFactory.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkActionsLinkFactory.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkActionsLinkFactory.java
index a1994af..a14300e 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkActionsLinkFactory.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/collectioncontents/ajaxtable/BulkActionsLinkFactory.java
@@ -110,8 +110,6 @@ final class BulkActionsLinkFactory implements ActionLinkFactory {
                     if (commandContext != null) {
                         command = commandContext.getCommand();
                         command.setExecutor(Executor.USER);
-                    } else {
-                        command = null;
                     }
 
 
@@ -126,8 +124,11 @@ final class BulkActionsLinkFactory implements ActionLinkFactory {
                         if (bulkInteractionContext != null) {
                             bulkInteractionContext.setIndex(i++);
                         }
-                        lastReturnedAdapter = objectAction.execute(adapter, new ObjectAdapter[]{});
+
+                        lastReturnedAdapter = objectAction.executeWithRuleChecking(adapter, new ObjectAdapter[]{}, getAuthenticationSession(), ActionModel.WHERE_FOR_ACTION_INVOCATION);
                     }
+
+
                     
                     model.clearToggleMementosList();
                     toggleboxColumn.clearToggles();

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
index f6a6e0f..247c5ce 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/components/widgets/cssmenu/CssMenuItem.java
@@ -32,7 +32,6 @@ import org.apache.wicket.markup.html.form.SubmitLink;
 import org.apache.wicket.markup.html.link.AbstractLink;
 import org.apache.wicket.model.Model;
 import org.apache.isis.applib.Identifier;
-import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.value.Blob;
 import org.apache.isis.applib.value.Clob;
 import org.apache.isis.core.commons.authentication.AuthenticationSession;
@@ -47,6 +46,7 @@ import org.apache.isis.core.metamodel.spec.feature.ObjectAction;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.viewer.wicket.model.links.LinkAndLabel;
 import org.apache.isis.viewer.wicket.model.mementos.ObjectAdapterMemento;
+import org.apache.isis.viewer.wicket.model.models.ActionModel;
 import org.apache.isis.viewer.wicket.ui.pages.PageAbstract;
 import org.apache.isis.viewer.wicket.ui.util.Components;
 import org.apache.isis.viewer.wicket.ui.util.CssClassAppender;
@@ -55,6 +55,7 @@ import static org.hamcrest.CoreMatchers.is;
 
 public class CssMenuItem implements Serializable {
 
+
     private static final long serialVersionUID = 1L;
 
     public static final String ID_MENU_LINK = "menuLink";
@@ -246,13 +247,6 @@ public class CssMenuItem implements Serializable {
     // To add submenu items
     // //////////////////////////////////////////////////////////////
 
-    // REVIEW: should provide this rendering context, rather than hardcoding.
-    // the net effect currently is that class members annotated with 
-    // @Hidden(where=Where.ANYWHERE) or @Disabled(where=Where.ANYWHERE) will indeed
-    // be hidden/disabled, but will be visible/enabled (perhaps incorrectly) 
-    // for any other value for Where
-    private final Where where = Where.ANYWHERE;
-
     /**
      * Creates a {@link Builder} for a submenu item invoking an action on the provided
      * {@link ObjectAdapterMemento target adapter}.
@@ -265,7 +259,7 @@ public class CssMenuItem implements Serializable {
         // check visibility
         final AuthenticationSession session = getAuthenticationSession();
         final ObjectAdapter adapter = targetAdapterMemento.getObjectAdapter(ConcurrencyChecking.CHECK);
-        final Consent visibility = objectAction.isVisible(session, adapter, where);
+        final Consent visibility = objectAction.isVisible(session, adapter, ActionModel.WHERE_FOR_ACTION_INVOCATION);
         if (visibility.isVetoed()) {
             return null;
         }
@@ -281,7 +275,7 @@ public class CssMenuItem implements Serializable {
         final AbstractLink link = linkAndLabel.getLink();
         final String actionLabel = linkAndLabel.getLabel();
 
-        final Consent usability = objectAction.isUsable(session, adapter, where);
+        final Consent usability = objectAction.isUsable(session, adapter, ActionModel.WHERE_FOR_ACTION_INVOCATION);
         final String reasonDisabledIfAny = usability.getReason();
         
         final DescribedAsFacet describedAsFacet = objectAction.getFacet(DescribedAsFacet.class);

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java
index 2b52c60..91ca4c0 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionModel.java
@@ -23,6 +23,7 @@ import java.util.List;
 import com.google.common.base.Throwables;
 import com.google.common.collect.Lists;
 
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.viewer.wicket.model.models.ModelAbstract;
 
 public class ExceptionModel extends ModelAbstract<List<StackTraceDetail>> {
@@ -33,32 +34,53 @@ public class ExceptionModel extends ModelAbstract<List<StackTraceDetail>> {
     
     private List<StackTraceDetail> stackTraceDetailList;
     private boolean recognized;
+    private boolean authorizationCause;
 
     private final String mainMessage;
-    
 
     public static ExceptionModel create(String recognizedMessageIfAny, Exception ex) {
-        return recognizedMessageIfAny != null
-                ? new ExceptionModel(recognizedMessageIfAny, true, ex)
-                : new ExceptionModel(MAIN_MESSAGE_IF_NOT_RECOGNIZED, false, ex);
+        return new ExceptionModel(recognizedMessageIfAny, ex);
     }
 
-    private ExceptionModel(String mainMessage, boolean recognized, Exception ex) {
-        this.mainMessage = mainMessage;
-        this.recognized = recognized;
-        setObject(ex);
+    /**
+     * Three cases: authorization exception, else recognized, else or not recognized.
+     * @param recognizedMessageIfAny
+     * @param ex
+     */
+    private ExceptionModel(String recognizedMessageIfAny, Exception ex) {
+
+        final ObjectMember.AuthorizationException authorizationException = causalChainOf(ex, ObjectMember.AuthorizationException.class);
+        if(authorizationException != null) {
+            this.authorizationCause = true;
+            this.mainMessage = authorizationException.getMessage();
+        } else {
+            this.authorizationCause = false;
+            if(recognizedMessageIfAny != null) {
+                this.recognized = true;
+                this.mainMessage = recognizedMessageIfAny;
+            } else {
+                this.recognized =false;
+                this.mainMessage = MAIN_MESSAGE_IF_NOT_RECOGNIZED;
+            }
+        }
+        stackTraceDetailList = asStackTrace(ex);
     }
 
+
     @Override
     protected List<StackTraceDetail> load() {
         return stackTraceDetailList;
     }
 
-    /**
-     * Not API
-     */
-    public void setObject(Exception ex) {
-        stackTraceDetailList = asStackTrace(ex);
+    private static <T extends Exception> T causalChainOf(Exception ex, Class<T> exType) {
+
+        final List<Throwable> causalChain = Throwables.getCausalChain(ex);
+        for (Throwable cause : causalChain) {
+            if(exType.isAssignableFrom(cause.getClass())) {
+                return (T)cause;
+            }
+        }
+        return null;
     }
 
     @Override
@@ -76,7 +98,14 @@ public class ExceptionModel extends ModelAbstract<List<StackTraceDetail>> {
     public String getMainMessage() {
         return mainMessage;
     }
-    
+
+    /**
+     * Whether this was an authorization exception (so UI can suppress information, eg stack trace).
+     */
+    public boolean isAuthorizationException() {
+        return authorizationCause;
+    }
+
     public List<StackTraceDetail> getStackTrace() {
         return stackTraceDetailList;
     }

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
index 4cebcb3..ff6216f 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/errors/ExceptionStackTracePanel.java
@@ -27,6 +27,7 @@ import org.apache.wicket.markup.html.basic.Label;
 import org.apache.wicket.markup.html.panel.Panel;
 import org.apache.wicket.request.resource.JavaScriptResourceReference;
 import org.apache.isis.viewer.wicket.ui.panels.PanelUtil;
+import org.apache.isis.viewer.wicket.ui.util.Components;
 
 public class ExceptionStackTracePanel extends Panel {
 
@@ -53,15 +54,21 @@ public class ExceptionStackTracePanel extends Panel {
 
         add(label);
 
+        final boolean authorizationException = exceptionModel.isAuthorizationException();
+        if(authorizationException) {
+            Components.permanentlyHide(this, ID_EXCEPTION_DETAIL);
+        } else {
+
         MarkupContainer container = new WebMarkupContainer(ID_EXCEPTION_DETAIL) {
-            private static final long serialVersionUID = 1L;
-            @Override
-            public void renderHead(IHeaderResponse response) {
-                response.render(JavaScriptReferenceHeaderItem.forReference(DIV_TOGGLE_JS));
-            }
-        };
-        container.add(new StackTraceListView(ID_STACK_TRACE_ELEMENT, ExceptionStackTracePanel.ID_LINE, exceptionModel.getStackTrace()));
-        add(container);
+                private static final long serialVersionUID = 1L;
+                @Override
+                public void renderHead(IHeaderResponse response) {
+                    response.render(JavaScriptReferenceHeaderItem.forReference(DIV_TOGGLE_JS));
+                }
+            };
+            container.add(new StackTraceListView(ID_STACK_TRACE_ELEMENT, ExceptionStackTracePanel.ID_LINE, exceptionModel.getStackTrace()));
+            add(container);
+        }
     }
 
     public void renderHead(final IHeaderResponse response) {

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java
index 0ec7fa6..e7ca28c 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/entity/EntityPage.java
@@ -19,13 +19,20 @@
 
 package org.apache.isis.viewer.wicket.ui.pages.entity;
 
+import java.util.List;
 import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
 import org.apache.wicket.event.Broadcast;
 import org.apache.wicket.markup.html.link.BookmarkablePageLink;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
+import org.apache.isis.applib.annotation.Where;
+import org.apache.isis.core.commons.authentication.AuthenticationSession;
 import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager.ConcurrencyChecking;
 import org.apache.isis.core.metamodel.adapter.version.ConcurrencyException;
+import org.apache.isis.core.metamodel.spec.ObjectSpecification;
+import org.apache.isis.core.metamodel.spec.feature.Contributed;
+import org.apache.isis.core.metamodel.spec.feature.ObjectAssociation;
+import org.apache.isis.core.metamodel.spec.feature.ObjectMember;
 import org.apache.isis.core.runtime.system.DeploymentType;
 import org.apache.isis.core.runtime.system.context.IsisContext;
 import org.apache.isis.viewer.wicket.model.hints.IsisUiHintEvent;
@@ -85,18 +92,28 @@ public class EntityPage extends PageAbstract {
 
         this.model = entityModel;
 
-
-        ObjectAdapter objectAdapter;
+        final ObjectAdapter objectAdapter;
         try {
             // check object still exists
             objectAdapter = entityModel.getObject();
         } catch(RuntimeException ex) {
             removeAnyBookmark(model);
             removeAnyBreadcrumb(model);
-            throw ex;
+
+            // we throw an authorization exception here to avoid leaking out information as to whether the object exists or not.
+            throw new ObjectMember.AuthorizationException(ex);
         }
 
-        
+        // check that at least one property of the entity can be viewed.
+        final AuthenticationSession session = getAuthenticationSession();
+        final ObjectSpecification specification = objectAdapter.getSpecification();
+        final List<ObjectAssociation> visibleAssociation = specification.getAssociations(Contributed.INCLUDED, ObjectAssociation.Filters.dynamicallyVisible(session, objectAdapter, Where.NOWHERE));
+
+        if(visibleAssociation.isEmpty()) {
+            throw new ObjectMember.AuthorizationException();
+        }
+
+
         // this is a work-around for JRebel integration...
         // ... even though the IsisJRebelPlugin calls invalidateCache, it seems that there is 
         // some caching elsewhere in the Wicket viewer meaning that stale metadata is referenced.
@@ -122,6 +139,7 @@ public class EntityPage extends PageAbstract {
         send(this, Broadcast.BREADTH, new IsisUiHintEvent(entityModel, null));
     }
 
+
     private void addBreadcrumb(EntityModel entityModel) {
         final BreadcrumbModelProvider session = (BreadcrumbModelProvider) getSession();
         final BreadcrumbModel breadcrumbModel = session.getBreadcrumbModel();

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.java
----------------------------------------------------------------------
diff --git a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.java b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.java
index e55a56f..6d61334 100644
--- a/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.java
+++ b/component/viewer/wicket/ui/src/main/java/org/apache/isis/viewer/wicket/ui/pages/error/ErrorPage.java
@@ -21,7 +21,6 @@ package org.apache.isis.viewer.wicket.ui.pages.error;
 
 import org.apache.wicket.authroles.authorization.strategies.role.annotations.AuthorizeInstantiation;
 import org.apache.wicket.request.mapper.parameter.PageParameters;
-
 import org.apache.isis.viewer.wicket.ui.errors.ExceptionModel;
 import org.apache.isis.viewer.wicket.ui.errors.ExceptionStackTracePanel;
 import org.apache.isis.viewer.wicket.ui.pages.PageAbstract;
@@ -39,10 +38,11 @@ public class ErrorPage extends PageAbstract {
 
     public ErrorPage(ExceptionModel exceptionModel) {
         super(new PageParameters(), ApplicationActions.EXCLUDE, null);
-        
+
         addBookmarkedPages();
 
         themeDiv.add(new ExceptionStackTracePanel(ID_EXCEPTION_STACK_TRACE, exceptionModel));
+
     }
 
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/container/DomainObjectContainerDefault.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/container/DomainObjectContainerDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/container/DomainObjectContainerDefault.java
index f427fb6..e6c4f7c 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/container/DomainObjectContainerDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/container/DomainObjectContainerDefault.java
@@ -514,9 +514,17 @@ public class  DomainObjectContainerDefault implements DomainObjectContainer, Que
         }
     }
 
-    private final ExceptionRecognizer recognizer = 
+    // make not recognizable, instead show the error page...
+//    static class ExceptionRecognizerForObjectMemberAuthorizationException extends ExceptionRecognizerForType {
+//        public ExceptionRecognizerForObjectMemberAuthorizationException() {
+//            super(ObjectMember.AuthorizationException.class);
+//        }
+//    }
+
+    private final ExceptionRecognizer recognizer =
             new ExceptionRecognizerComposite(
                     new ExceptionRecognizerForConcurrencyException(),
+//                    new ExceptionRecognizerForObjectMemberAuthorizationException(),
                     new ExceptionRecognizerForRecoverableException()
                 );
     

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
index 130da6e..ae99a25 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectAction.java
@@ -38,19 +38,20 @@ import org.apache.isis.core.metamodel.consent.Consent;
 import org.apache.isis.core.metamodel.consent.InteractionInvocationMethod;
 import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FacetFilters;
+import org.apache.isis.core.metamodel.facets.actions.bulk.BulkFacet;
 import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
-import org.apache.isis.core.metamodel.facets.members.order.MemberOrderFacet;
 import org.apache.isis.core.metamodel.facets.all.named.NamedFacet;
+import org.apache.isis.core.metamodel.facets.members.order.MemberOrderFacet;
 import org.apache.isis.core.metamodel.facets.object.wizard.WizardFacet;
 import org.apache.isis.core.metamodel.interactions.AccessContext;
 import org.apache.isis.core.metamodel.interactions.ActionInvocationContext;
 import org.apache.isis.core.metamodel.interactions.ValidatingInteractionAdvisor;
 import org.apache.isis.core.metamodel.spec.ActionType;
 import org.apache.isis.core.metamodel.spec.ObjectSpecification;
-import org.apache.isis.core.metamodel.facets.actions.bulk.BulkFacet;
 
 public interface ObjectAction extends ObjectMember {
 
+
     // //////////////////////////////////////////////////////
     // semantics, realTarget, getOnType
     // //////////////////////////////////////////////////////
@@ -91,11 +92,21 @@ public interface ObjectAction extends ObjectMember {
     boolean hasReturn();
 
     // //////////////////////////////////////////////////////////////////
-    // execute
+    // execute, executeWithRuleChecking
     // //////////////////////////////////////////////////////////////////
 
     /**
      * Invokes the action's method on the target object given the specified set
+     * of parameters, checking the visibility, usability and validity first.
+     */
+    ObjectAdapter executeWithRuleChecking(
+            final ObjectAdapter target,
+            final ObjectAdapter[] parameters,
+            final AuthenticationSession authenticationSession,
+            final Where where) throws AuthorizationException;
+
+    /**
+     * Invokes the action's method on the target object given the specified set
      * of parameters.
      */
     ObjectAdapter execute(ObjectAdapter target, ObjectAdapter[] parameters);

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java
index 02ea9f6..bec9623 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/spec/feature/ObjectMember.java
@@ -69,8 +69,7 @@ public interface ObjectMember extends ObjectFeature {
      * 
      * <p>
      * Typically it is easier to just call
-     * {@link #isVisible(AuthenticationSession, ObjectAdapter, Where)} or
-     * {@link #isVisibleResult(AuthenticationSession, ObjectAdapter)}; this is
+     * {@link #isVisible(AuthenticationSession, ObjectAdapter, Where)}; this is
      * provided as API for symmetry with interactions (such as
      * {@link AccessContext} accesses) have no corresponding vetoing methods.
      */
@@ -82,7 +81,6 @@ public interface ObjectMember extends ObjectFeature {
      *            may be <tt>null</tt> if just checking for authorization.
      * @param where 
      *            the member is being rendered in the UI
-     * @see #isVisibleResult(AuthenticationSession, ObjectAdapter)
      */
     Consent isVisible(AuthenticationSession session, ObjectAdapter target, Where where);
 
@@ -92,13 +90,11 @@ public interface ObjectMember extends ObjectFeature {
 
     /**
      * Create an {@link InteractionContext} to represent an attempt to
-     * {@link InteractionContextType#MEMBER_USABLE use this member} (that is, to
-     * check if it is usable or not).
+     * use this member (that is, to check if it is usable or not).
      * 
      * <p>
      * Typically it is easier to just call
-     * {@link #isUsable(AuthenticationSession, ObjectAdapter, Where)} or
-     * {@link #isUsableResult(AuthenticationSession, ObjectAdapter)}; this is
+     * {@link #isUsable(AuthenticationSession, ObjectAdapter, Where)}; this is
      * provided as API for symmetry with interactions (such as
      * {@link AccessContext} accesses) have no corresponding vetoing methods.
      */
@@ -158,4 +154,29 @@ public interface ObjectMember extends ObjectFeature {
 
     String debugData();
 
+    /**
+     * Thrown if the user is not authorized to access an action or any property/collection of an entity.
+     *
+     * <p>
+     *     For the former case, is thrown by
+     *     {@link ObjectAction#executeWithRuleChecking(org.apache.isis.core.metamodel.adapter.ObjectAdapter, org.apache.isis.core.metamodel.adapter.ObjectAdapter[], org.apache.isis.core.commons.authentication.AuthenticationSession, org.apache.isis.applib.annotation.Where)}
+     *     when the action being executed is not visible or not usable for the specified session.  One reason this
+     *     might occur if there was an attempt to construct a URL (eg a bookmarked action) and invoke in an unauthenticated session.
+     * </p>
+     *
+     * <p>
+     *     For the latter case, is thrown by <tt>EntityPage</tt>
+     *
+     * </p>
+     */
+    class AuthorizationException extends RuntimeException {
+
+        public AuthorizationException() {
+            this(null);
+        }
+        public AuthorizationException(final RuntimeException ex) {
+            super("Not authorized or no such object", ex);
+        }
+
+    }
 }

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionImpl.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionImpl.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionImpl.java
index 0d30bfc..7918932 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionImpl.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectActionImpl.java
@@ -27,6 +27,7 @@ import com.google.common.collect.Lists;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import org.apache.isis.applib.RecoverableException;
 import org.apache.isis.applib.annotation.ActionSemantics;
 import org.apache.isis.applib.annotation.Where;
 import org.apache.isis.applib.filter.Filter;
@@ -330,10 +331,34 @@ public class ObjectActionImpl extends ObjectMemberAbstract implements ObjectActi
     }
 
     // //////////////////////////////////////////////////////////////////
-    // execute
+    // executeWithRuleChecking, execute
     // //////////////////////////////////////////////////////////////////
 
     @Override
+    public ObjectAdapter executeWithRuleChecking(final ObjectAdapter target, final ObjectAdapter[] arguments, final AuthenticationSession authenticationSession, final Where where) {
+
+        // see it?
+        final Consent visibility = isVisible(authenticationSession, target, where);
+        if (visibility.isVetoed()) {
+            throw new AuthorizationException();
+        }
+
+        // use it?
+        final Consent usability = isUsable(authenticationSession, target, where);
+        if(usability.isVetoed()) {
+            throw new AuthorizationException();
+        }
+
+        // do it?
+        final Consent validity = isProposedArgumentSetValid(target, arguments);
+        if(validity.isVetoed()) {
+            throw new RecoverableException(validity.getReason());
+        }
+
+        return execute(target, arguments);
+    }
+
+    @Override
     public ObjectAdapter execute(final ObjectAdapter target, final ObjectAdapter[] arguments) {
         if(LOG.isDebugEnabled()) {
             LOG.debug("execute action " + target + "." + getId());

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java
----------------------------------------------------------------------
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java
index 8573425..6293827 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/specloader/specimpl/ObjectMemberAbstract.java
@@ -32,6 +32,7 @@ import org.apache.isis.core.metamodel.adapter.ObjectAdapter;
 import org.apache.isis.core.metamodel.adapter.QuerySubmitter;
 import org.apache.isis.core.metamodel.adapter.ServicesProvider;
 import org.apache.isis.core.metamodel.adapter.mgr.AdapterManager;
+import org.apache.isis.core.metamodel.consent.Allow;
 import org.apache.isis.core.metamodel.consent.Consent;
 import org.apache.isis.core.metamodel.consent.InteractionInvocationMethod;
 import org.apache.isis.core.metamodel.consent.InteractionResult;
@@ -40,6 +41,7 @@ import org.apache.isis.core.metamodel.facetapi.Facet;
 import org.apache.isis.core.metamodel.facetapi.FeatureType;
 import org.apache.isis.core.metamodel.facetapi.MultiTypedFacet;
 import org.apache.isis.core.metamodel.facets.FacetedMethod;
+import org.apache.isis.core.metamodel.facets.actions.homepage.HomePageFacet;
 import org.apache.isis.core.metamodel.facets.all.describedas.DescribedAsFacet;
 import org.apache.isis.core.metamodel.facets.all.help.HelpFacet;
 import org.apache.isis.core.metamodel.facets.all.hide.HiddenFacet;
@@ -224,10 +226,14 @@ public abstract class ObjectMemberAbstract implements ObjectMember {
      */
     @Override
     public Consent isVisible(final AuthenticationSession session, final ObjectAdapter target, Where where) {
-        return isVisibleResult(deploymentCategory, session, target, where).createConsent();
+        final boolean isHomePage = containsDoOpFacet(HomePageFacet.class);
+        if(isHomePage) {
+            return Allow.DEFAULT;
+        }
+        return isVisibleResult(session, target, where).createConsent();
     }
 
-    private InteractionResult isVisibleResult(DeploymentCategory deploymentCategory, final AuthenticationSession session, final ObjectAdapter target, Where where) {
+    private InteractionResult isVisibleResult(final AuthenticationSession session, final ObjectAdapter target, Where where) {
         final VisibilityContext<?> ic = createVisibleInteractionContext(session, InteractionInvocationMethod.BY_USER, target, where);
         return InteractionUtils.isVisibleResult(this, ic);
     }
@@ -246,6 +252,10 @@ public abstract class ObjectMemberAbstract implements ObjectMember {
      */
     @Override
     public Consent isUsable(final AuthenticationSession session, final ObjectAdapter target, Where where) {
+        final boolean isHomePage = containsDoOpFacet(HomePageFacet.class);
+        if(isHomePage) {
+            return Allow.DEFAULT;
+        }
         return isUsableResult(session, target, where).createConsent();
     }
 

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/example/application/todoapp/dom/src/main/java/dom/todo/ToDoItems.java
----------------------------------------------------------------------
diff --git a/example/application/todoapp/dom/src/main/java/dom/todo/ToDoItems.java b/example/application/todoapp/dom/src/main/java/dom/todo/ToDoItems.java
index 2cb6c9f..753cb59 100644
--- a/example/application/todoapp/dom/src/main/java/dom/todo/ToDoItems.java
+++ b/example/application/todoapp/dom/src/main/java/dom/todo/ToDoItems.java
@@ -124,6 +124,8 @@ public class ToDoItems {
     //endregion
 
     //region > newToDo (action)
+    @Bookmarkable
+    @ActionSemantics(Of.SAFE)
     @MemberOrder(sequence = "5")
     public ToDoItem newToDo(
             final @RegEx(validation = "\\w[@&:\\-\\,\\.\\+ \\w]*") @Named("Description") String description, 

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/example/application/todoapp/webapp/src/main/resources/webapp/realm1.ini
----------------------------------------------------------------------
diff --git a/example/application/todoapp/webapp/src/main/resources/webapp/realm1.ini b/example/application/todoapp/webapp/src/main/resources/webapp/realm1.ini
index 311694b..ea15aae 100644
--- a/example/application/todoapp/webapp/src/main/resources/webapp/realm1.ini
+++ b/example/application/todoapp/webapp/src/main/resources/webapp/realm1.ini
@@ -35,6 +35,8 @@
 sven = pass, admin_role
 dick = pass, user_role, analysis_role, self-install_role
 bob  = pass, user_role, self-install_role
+joe  = pass, user_role, self-install_role
+guest = guest, readonly_role
 
 
 
@@ -69,6 +71,13 @@ bob  = pass, user_role, self-install_role
 
 
 # configuring iniRealm to use Shiro's built-in WildcardPermissions
+readonly_role = *:ToDoItems:notYetComplete:*,\
+                *:ToDoItems:complete:*,\
+                *:ToDoItems:allToDos:*,\
+                *:ToDoAppDashboard:*:*
+
+#                *:ToDoItem:*:r,\
+
 user_role = *:ToDoItems:*:*,\
             *:ToDoItem:*:*,\
             *:ToDoAppDashboard:*:*

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/example/application/todoapp/webapp/src/main/resources/webapp/realm2.ini
----------------------------------------------------------------------
diff --git a/example/application/todoapp/webapp/src/main/resources/webapp/realm2.ini b/example/application/todoapp/webapp/src/main/resources/webapp/realm2.ini
deleted file mode 100644
index fd0ee8f..0000000
--- a/example/application/todoapp/webapp/src/main/resources/webapp/realm2.ini
+++ /dev/null
@@ -1,66 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#     http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-
-
-# -----------------------------------------------------------------------------
-# Users and their assigned roles
-#
-# Each line conforms to the format defined in the
-# org.apache.shiro.realm.text.TextConfigurationRealm#setUserDefinitions JavaDoc
-# -----------------------------------------------------------------------------
-
-[users]
-# user = password, role1, role2, role3, ...
-
-#
-# realm2 configures joe and guest only.  Additional logins are configured in realm1
-#
-
-joe  = pass, user_role, self-install_role
-guest = guest, user_role
-
-
-
-# -----------------------------------------------------------------------------
-# Roles with assigned permissions
-# 
-# Each line conforms to the format defined in the
-# org.apache.shiro.realm.text.TextConfigurationRealm#setRoleDefinitions JavaDoc
-# -----------------------------------------------------------------------------
-
-[roles]
-# role = perm1, perm2, perm3, ...
-# perm in format: packageName:className:memberName:r,w
-
-#
-# the role/perm mappings for realm1 and realm2 are "coincidentally" the same.
-# But if using the IsisLdapRealm, it could be pointed at an ini file such as this in 
-# order to share role/perm mappings.
-#
-
-# configuring iniRealm to use Shiro's built-in WildcardPermissions
-user_role = *:ToDoItems:*:*,\
-            *:ToDoItem:*:*,\
-            *:ToDoAppDashboard:*:*
-analysis_role = *:ToDoItemAnalysis:*:*,\
-            *:ToDoItemsByCategoryViewModel:*:*,\
-            *:ToDoItemsByDateRangeViewModel:*:*
-self-install_role = *:ToDoItemsFixturesService:installFixtures:*
-admin_role = *
-

http://git-wip-us.apache.org/repos/asf/isis/blob/48694d8e/example/application/todoapp/webapp/src/main/webapp/WEB-INF/shiro.ini
----------------------------------------------------------------------
diff --git a/example/application/todoapp/webapp/src/main/webapp/WEB-INF/shiro.ini b/example/application/todoapp/webapp/src/main/webapp/WEB-INF/shiro.ini
index 8c5f7d3..07ea04f 100644
--- a/example/application/todoapp/webapp/src/main/webapp/WEB-INF/shiro.ini
+++ b/example/application/todoapp/webapp/src/main/webapp/WEB-INF/shiro.ini
@@ -28,8 +28,6 @@
 realm1 = org.apache.shiro.realm.text.IniRealm
 realm1.resourcePath=classpath:webapp/realm1.ini
 
-realm2 = org.apache.shiro.realm.text.IniRealm
-realm2.resourcePath=classpath:webapp/realm2.ini
 
 
 
@@ -69,8 +67,8 @@ ldapRealm.resourcePath=classpath:webapp/realm1.ini
 # configure security manager to use realm(s)
 #######
 
-# authenticate/authorize first with realm1, then realm2
-securityManager.realms = $realm1,$realm2
+# authenticate/authorize using realm1
+securityManager.realms = $realm1
 
 
 # or to use ldap with realm1 as a backup...