You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@isis.apache.org by ah...@apache.org on 2022/01/31 08:44:51 UTC

[isis] branch master updated: ISIS-2951: recognize System User as not to be vetoed (tenanted authorization)

This is an automated email from the ASF dual-hosted git repository.

ahuber pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/isis.git


The following commit(s) were added to refs/heads/master by this push:
     new e5a6b77  ISIS-2951: recognize System User as not to be vetoed (tenanted authorization)
e5a6b77 is described below

commit e5a6b77b77b35c6b9e008d386165f523b11ddb11
Author: Andi Huber <ah...@apache.org>
AuthorDate: Mon Jan 31 09:44:40 2022 +0100

    ISIS-2951: recognize System User as not to be vetoed (tenanted
    authorization)
---
 .../isis/applib/services/user/UserMemento.java     |  10 +-
 .../isis/applib/services/user/UserService.java     | 127 +++++++++++----------
 .../facets/TenantedAuthorizationFacet.java         |   3 +-
 .../facets/TenantedAuthorizationFacetDefault.java  |  29 +++--
 4 files changed, 95 insertions(+), 74 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserMemento.java b/api/applib/src/main/java/org/apache/isis/applib/services/user/UserMemento.java
index 922903d..76b1dff 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserMemento.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/user/UserMemento.java
@@ -24,6 +24,7 @@ import java.io.Serializable;
 import java.net.URL;
 import java.util.List;
 import java.util.Locale;
+import java.util.Objects;
 import java.util.stream.Stream;
 
 import org.springframework.context.event.EventListener;
@@ -304,8 +305,13 @@ public class UserMemento implements Serializable {
         return streamRoleNames().anyMatch(myRoleName->myRoleName.equals(roleName));
     }
 
-
-
+    /**
+     * Whether this {@link UserMemento} represent the <i>system user</i>.
+     */
+    @Programmatic
+    public boolean isSystem() {
+        return Objects.equals(SYSTEM_USER, this);
+    }
 
     // -- UTILITY
 
diff --git a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserService.java b/api/applib/src/main/java/org/apache/isis/applib/services/user/UserService.java
index 831b179..0cfb909 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/services/user/UserService.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/services/user/UserService.java
@@ -21,13 +21,13 @@ package org.apache.isis.applib.services.user;
 import java.util.List;
 import java.util.Optional;
 
-import org.springframework.lang.Nullable;
 import javax.annotation.Priority;
 import javax.inject.Inject;
 import javax.inject.Named;
 import javax.inject.Provider;
 
 import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.lang.Nullable;
 import org.springframework.stereotype.Service;
 
 import org.apache.isis.applib.annotation.PriorityPrecedence;
@@ -45,16 +45,20 @@ import lombok.val;
  *
  * <p>
  * If {@link SudoService} has been used to temporarily override the user and/or
- * roles, then this service will report the overridden values instead.  This is within the context of a thread.
+ * roles, then this service will report the overridden values instead. This is
+ * within the context of a thread.
  * </p>
  *
  * <p>
- * In addition, if impersonation has been invoked through the {@link ImpersonateMenu}, then this service
- * will report the impersonated user, with the companion {@link ImpersonatedUserHolder} taking responsibilty for
- * remembering the impersonated user over multiple (http) requests, eg using an http session.  It's important to note
- * that under these circumstances the user reported by this service (the &quot;effective&quot; user) will <i>not</i> be
+ * In addition, if impersonation has been invoked through the
+ * {@link ImpersonateMenu}, then this service will report the impersonated user,
+ * with the companion {@link ImpersonatedUserHolder} taking responsibilty for
+ * remembering the impersonated user over multiple (http) requests, eg using an
+ * http session. It's important to note that under these circumstances the user
+ * reported by this service (the &quot;effective&quot; user) will <i>not</i> be
  * the same as the user held in the {@link InteractionContext}, as obtained by
- * {@link InteractionLayerTracker#currentInteractionContext() InteractionLayerTracker} (the &quot;real&quot; user).
+ * {@link InteractionLayerTracker#currentInteractionContext()
+ * InteractionLayerTracker} (the &quot;real&quot; user).
  * </p>
  *
  * @see org.apache.isis.applib.services.iactnlayer.InteractionLayerTracker
@@ -69,7 +73,7 @@ import lombok.val;
 @Named("isis.applib.UserService")
 @Priority(PriorityPrecedence.MIDPOINT)
 @Qualifier("Default")
-@RequiredArgsConstructor(onConstructor_ = {@Inject})
+@RequiredArgsConstructor(onConstructor_ = { @Inject })
 public class UserService {
 
     /**
@@ -80,61 +84,70 @@ public class UserService {
     private final Provider<InteractionLayerTracker> iInteractionLayerTrackerProvider;
     private final List<ImpersonatedUserHolder> impersonatedUserHolders;
 
-
     /**
-     * Returns the details about the current user, either the &quot;effective&quot; user (if being
-     * {@link #impersonateUser(String, List, String) impersonated}) otherwise the &quot;real&quot; user (as obtained from
-     * the {@link InteractionContext} of the current thread).
+     * Returns the details about the current user, either the &quot;effective&quot;
+     * user (if being {@link #impersonateUser(String, List, String) impersonated})
+     * otherwise the &quot;real&quot; user (as obtained from the
+     * {@link InteractionContext} of the current thread).
      */
     public Optional<UserMemento> currentUser() {
         val impersonatedUserIfAny = impersonatedUserIfAny();
         return impersonatedUserIfAny.isPresent()
                 ? impersonatedUserIfAny
-                : iInteractionLayerTrackerProvider.get().currentInteractionContext().map(InteractionContext::getUser);
+                : iInteractionLayerTrackerProvider.get()
+                    .currentInteractionContext()
+                    .map(InteractionContext::getUser);
+    }
+
+    /**
+     * Whether the current user is the <i>system user</i> (as obtained from the
+     * {@link InteractionContext} of the current thread).
+     */
+    public boolean isCurrentUserWithSystemPrivileges() {
+        return currentUser()
+                .map(UserMemento::isSystem)
+                .orElse(false);
     }
 
     /**
-     * Gets the details about the {@link #currentUser()} current user, if any (and returning <code>null</code> if
-     * there is none).
+     * Gets the details about the {@link #currentUser()} current user, if any (and
+     * returning <code>null</code> if there is none).
      */
     @Nullable
     public UserMemento getUser() {
         return currentUser().orElse(null);
     }
 
-
     /**
-     * Gets the details about the {@link #currentUser()} current user, throwing an exception if there is none.
+     * Gets the details about the {@link #currentUser()} current user, throwing an
+     * exception if there is none.
      *
-     * @throws IllegalStateException if no {@link InteractionContext} can be found with the current thread's context.
+     * @throws IllegalStateException if no {@link InteractionContext} can be found
+     *                               with the current thread's context.
      */
     public UserMemento currentUserElseFail() {
         return currentUser()
-                .orElseThrow(()->_Exceptions.illegalState("Current thread has no InteractionContext."));
+                .orElseThrow(() -> _Exceptions.illegalState("Current thread has no InteractionContext."));
     }
 
     /**
-     * Optionally gets the {@link #currentUser() current user}'s name, obtained from {@link UserMemento}.
+     * Optionally gets the {@link #currentUser() current user}'s name, obtained from
+     * {@link UserMemento}.
      */
     public Optional<String> currentUserName() {
-        return currentUser()
-                .map(UserMemento::getName);
+        return currentUser().map(UserMemento::getName);
     }
 
     /**
      * Returns either the current user's name or else {@link #NOBODY}.
      */
     public String currentUserNameElseNobody() {
-        return currentUserName()
-                .orElse(NOBODY);
+        return currentUserName().orElse(NOBODY);
     }
 
-
-
-
     /**
-     * Whether or not the user currently reported (in {@link #currentUser()}
-     * and similar) is actually an impersonated user.
+     * Whether or not the user currently reported (in {@link #currentUser()} and
+     * similar) is actually an impersonated user.
      *
      * @see #currentUser()
      * @see #supportsImpersonation()
@@ -145,27 +158,26 @@ public class UserService {
         return impersonatedUserIfAny().isPresent();
     }
 
-
-
     /**
      * Whether impersonation is available for this request.
      *
      * <p>
-     *     The typical implementation uses an HTTP session, which is not guaranteed to be available
-     *     for all viewers.  Specifically, the Wicket viewer <i>does</i> use HTTP sessions and
-     *     therefore supports impersonation, but the RestfulObjects viewer does <i>not</i>.
-     *     This means that the result of this call varies on a request-by-request basis.
+     * The typical implementation uses an HTTP session, which is not guaranteed to
+     * be available for all viewers. Specifically, the Wicket viewer <i>does</i> use
+     * HTTP sessions and therefore supports impersonation, but the RestfulObjects
+     * viewer does <i>not</i>. This means that the result of this call varies on a
+     * request-by-request basis.
      * </p>
      *
      * @see #impersonateUser(String, List, String)
      * @see #isImpersonating()
      * @see #stopImpersonating()
      *
-     * @return whether impersonation is supported in the context of this (http) request.
+     * @return whether impersonation is supported in the context of this (http)
+     *         request.
      */
     public boolean supportsImpersonation() {
-        return impersonatingHolder()
-                .isPresent();
+        return impersonatingHolder().isPresent();
     }
 
     private Optional<ImpersonatedUserHolder> impersonatingHolder() {
@@ -185,50 +197,45 @@ public class UserService {
      * Allows implementations to override the current user with another user.
      *
      * <p>
-     *     If this service (for this request) does not
-     *     {@link #supportsImpersonation() support impersonation}, then the
-     *     request is just ignored.
+     * If this service (for this request) does not {@link #supportsImpersonation()
+     * support impersonation}, then the request is just ignored.
      * </p>
      *
      * <p>
-     *     IMPORTANT: this is intended for non-production environments only, where it can
-     *     be invaluable (from a support perspective) to be able to quickly
-     *     use the application &quot;as if&quot; logged in as another user.
+     * IMPORTANT: this is intended for non-production environments only, where it
+     * can be invaluable (from a support perspective) to be able to quickly use the
+     * application &quot;as if&quot; logged in as another user.
      * </p>
      *
      * @see #supportsImpersonation()
      * @see #isImpersonating()
      * @see #stopImpersonating()
-     * @param userName - the name of the user to be impersonated
-     * @param roles - the collection of roles for the impersonated user to have.
+     * @param userName          - the name of the user to be impersonated
+     * @param roles             - the collection of roles for the impersonated user
+     *                          to have.
      * @param multiTenancyToken
      */
     public void impersonateUser(
             final String userName,
             final List<String> roles,
             final String multiTenancyToken) {
-        impersonatingHolder().ifPresent(x ->
-                x.setUserMemento(
-                    UserMemento.ofNameAndRoleNames(userName, roles)
-                        .withImpersonating(true)
-                        .withMultiTenancyToken(multiTenancyToken)
-                )
-        );
+        impersonatingHolder()
+        .ifPresent(x -> x.setUserMemento(UserMemento.ofNameAndRoleNames(userName, roles)
+                .withImpersonating(true).withMultiTenancyToken(multiTenancyToken)));
     }
 
     /**
-     * For implementations that support impersonation, this is to
-     * programmatically stop impersonating a user
+     * For implementations that support impersonation, this is to programmatically
+     * stop impersonating a user
      *
      * <p>
-     *     If this service (for this request) does not
-     *     {@link #supportsImpersonation() support impersonation}, then the
-     *     request is just ignored.
+     * If this service (for this request) does not {@link #supportsImpersonation()
+     * support impersonation}, then the request is just ignored.
      * </p>
      *
      * <p>
-     *     Intended to be called at some point after
-     *     {@link #impersonateUser(String, List, String)} would have been called.
+     * Intended to be called at some point after
+     * {@link #impersonateUser(String, List, String)} would have been called.
      * </p>
      *
      * @see #supportsImpersonation()
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacet.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacet.java
index 25e8ee4..83b2ca6 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacet.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacet.java
@@ -26,6 +26,7 @@ import org.apache.isis.core.metamodel.interactions.HidingInteractionAdvisor;
  * Optionally hide or disable an object, property, collection or action
  * depending on the tenancy.
  */
-public interface TenantedAuthorizationFacet extends Facet, HidingInteractionAdvisor, DisablingInteractionAdvisor {
+public interface TenantedAuthorizationFacet
+extends Facet, HidingInteractionAdvisor, DisablingInteractionAdvisor {
 
 }
diff --git a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java
index 767ed2b..a861915 100644
--- a/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java
+++ b/extensions/security/secman/integration/src/main/java/org/apache/isis/extensions/secman/integration/facets/TenantedAuthorizationFacetDefault.java
@@ -19,7 +19,6 @@
 package org.apache.isis.extensions.secman.integration.facets;
 
 import java.util.List;
-import java.util.concurrent.Callable;
 
 import javax.inject.Provider;
 
@@ -34,7 +33,9 @@ import org.apache.isis.extensions.secman.applib.tenancy.spi.ApplicationTenancyEv
 import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUser;
 import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUserRepository;
 
-public class TenantedAuthorizationFacetDefault extends FacetAbstract implements TenantedAuthorizationFacet {
+public class TenantedAuthorizationFacetDefault
+extends FacetAbstract
+implements TenantedAuthorizationFacet {
 
     private static final Class<? extends Facet> type() {
         return TenantedAuthorizationFacet.class;
@@ -61,7 +62,9 @@ public class TenantedAuthorizationFacetDefault extends FacetAbstract implements
     @Override
     public String hides(final VisibilityContext ic) {
 
-        if(evaluators == null || evaluators.isEmpty()) {
+        if(evaluators == null
+                || evaluators.isEmpty()
+                || userService.isCurrentUserWithSystemPrivileges()) {
             return null;
         }
 
@@ -86,7 +89,10 @@ public class TenantedAuthorizationFacetDefault extends FacetAbstract implements
 
     @Override
     public String disables(final UsabilityContext ic) {
-        if(evaluators == null || evaluators.isEmpty()) {
+
+        if(evaluators == null
+                || evaluators.isEmpty()
+                || userService.isCurrentUserWithSystemPrivileges()) {
             return null;
         }
 
@@ -110,15 +116,16 @@ public class TenantedAuthorizationFacetDefault extends FacetAbstract implements
 
 
     /**
-     * Per {@link #findApplicationUserNoCache(String)}, cached for the request using the {@link QueryResultsCache}.
+     * Per {@link #findApplicationUserNoCache(String)},
+     * cached for the request using the {@link QueryResultsCache}.
      */
     protected ApplicationUser findApplicationUser(final String userName) {
-        return queryResultsCacheProvider.get().execute(new Callable<ApplicationUser>() {
-            @Override
-            public ApplicationUser call() throws Exception {
-                return findApplicationUserNoCache(userName);
-            }
-        }, TenantedAuthorizationFacetDefault.class, "findApplicationUser", userName);
+        return queryResultsCacheProvider.get()
+            .execute(
+                    ()->findApplicationUserNoCache(userName),
+                    TenantedAuthorizationFacetDefault.class,
+                    "findApplicationUser",
+                    userName);
     }
 
     protected ApplicationUser findApplicationUserNoCache(final String userName) {