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 2018/12/17 09:13:22 UTC

[isis] branch v2 updated: ISIS-2040: module 'security': API changes: use streams instead of lists

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

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


The following commit(s) were added to refs/heads/v2 by this push:
     new ff40048  ISIS-2040: module 'security': API changes: use streams instead of lists
ff40048 is described below

commit ff40048cd63527aa640b82c61cdf4d20410f4a95
Author: Andi Huber <ah...@apache.org>
AuthorDate: Mon Dec 17 10:13:15 2018 +0100

    ISIS-2040: module 'security': API changes: use streams instead of lists
    
    improvements
    
    - no need to wrap list of roles with unmodifiable
    - for roles use HashSets instead of Lists
    
    Task-Url: https://issues.apache.org/jira/browse/ISIS-2040
---
 .../isis/applib/services/sudo/SudoService.java     |  6 +--
 .../isis/commons/internal/base/_NullSafe.java      | 24 +++++++--
 .../services/user/UserServiceDefault.java          | 61 ++++++++++++----------
 .../standard/AuthorizationManagerStandard.java     | 21 ++++----
 .../shiro/ShiroAuthenticatorOrAuthorizor.java      | 31 ++++++-----
 .../AuthenticationSessionProviderDefault.java      |  4 +-
 .../runtime/services/sudo/SudoServiceDefault.java  | 11 ++--
 .../SimpleSessionEncodabilityTestAbstract.java     | 21 ++++++--
 .../authentication/AuthenticationRequest.java      | 39 ++++++++++++--
 .../AuthenticationRequestAbstract.java             | 23 ++++----
 .../authentication/AuthenticationSession.java      | 15 +++++-
 .../AuthenticationSessionAbstract.java             | 50 ++++++++----------
 .../AuthenticationRequestLogonFixture.java         |  6 +--
 .../authentication/health/HealthAuthSession.java   |  4 +-
 .../AuthenticationRequestSingleUser.java           | 14 +----
 .../standard/AuthenticatorAbstract.java            |  2 +-
 .../authentication/standard/SimpleSession.java     | 25 ++++++---
 .../server/resources/UserReprRenderer.java         |  7 +--
 .../wicket/AuthenticatedWebSessionForIsis.java     | 23 ++++----
 19 files changed, 229 insertions(+), 158 deletions(-)

diff --git a/core/applib/src/main/java/org/apache/isis/applib/services/sudo/SudoService.java b/core/applib/src/main/java/org/apache/isis/applib/services/sudo/SudoService.java
index 9c6118b..f7a62df 100644
--- a/core/applib/src/main/java/org/apache/isis/applib/services/sudo/SudoService.java
+++ b/core/applib/src/main/java/org/apache/isis/applib/services/sudo/SudoService.java
@@ -61,13 +61,13 @@ public interface SudoService {
      * Executes the supplied block, with the {@link DomainObjectContainer} returning the specified user with the specified roles.
      */
     @Programmatic
-    void sudo(String username, List<String> roles, final Runnable runnable);
+    void sudo(String username, Iterable<String> roles, final Runnable runnable);
 
     /**
      * Executes the supplied block, with the {@link DomainObjectContainer} returning the specified user with the specified roles.
      */
     @Programmatic
-    <T> T sudo(String username, List<String> roles, final Callable<T> callable);
+    <T> T sudo(String username, Iterable<String> roles, final Callable<T> callable);
 
     /**
      * Allows the {@link SudoService} to notify other services/components that the effective user has been changed.
@@ -83,7 +83,7 @@ public interface SudoService {
          * </p>
          */
         @Programmatic
-        void runAs(String username, List<String> roles);
+        void runAs(String username, Iterable<String> roles);
 
         /**
          *
diff --git a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java
index 5b63a99..d53eba5 100644
--- a/core/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java
+++ b/core/commons/src/main/java/org/apache/isis/commons/internal/base/_NullSafe.java
@@ -29,6 +29,8 @@ import java.util.Spliterators;
 import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
+import javax.annotation.Nullable;
+
 /**
  * <h1>- internal use only -</h1>
  * <p>
@@ -56,7 +58,7 @@ public final class _NullSafe {
      * @param array
      * @return non-null stream object
      */
-    public static <T> Stream<T> stream(final T[] array) {
+    public static <T> Stream<T> stream(@Nullable final T[] array) {
         return array!=null ? Stream.of(array) : Stream.empty();
     }
 
@@ -67,7 +69,7 @@ public final class _NullSafe {
      * @param nullable
      * @return non-null stream object
      */
-    public static <T> Stream<T> streamNullable(final T nullable) {
+    public static <T> Stream<T> streamNullable(@Nullable final T nullable) {
         return nullable != null ? Stream.of(nullable) : Stream.empty();
     }
 
@@ -77,7 +79,7 @@ public final class _NullSafe {
      * @param coll
      * @return non-null stream object
      */
-    public static <T> Stream<T> stream(final Collection<T> coll){
+    public static <T> Stream<T> stream(@Nullable final Collection<T> coll){
         return coll!=null ? coll.stream() : Stream.empty();
     }
 
@@ -87,7 +89,7 @@ public final class _NullSafe {
      * @param iterable
      * @return non-null stream object
      */
-    public static <T> Stream<T> stream(final Iterable<T> iterable){
+    public static <T> Stream<T> stream(@Nullable final Iterable<T> iterable){
         return iterable!=null ? stream(iterable.iterator()) : Stream.empty();
     }
 
@@ -97,7 +99,7 @@ public final class _NullSafe {
      * @param iterator
      * @return non-null stream object
      */
-    public static <T> Stream<T> stream(final Iterator<T> iterator){
+    public static <T> Stream<T> stream(@Nullable final Iterator<T> iterator){
         return iterator!=null
                 ? StreamSupport.stream(
                         Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED), 
@@ -106,6 +108,16 @@ public final class _NullSafe {
     }
     
     /**
+     * If {@code stream} is {@code null} returns the empty stream,
+     * otherwise returns the stream argument.
+     * @param stream
+     * @return non-null stream object
+     */
+    public static <T> Stream<T> stream(@Nullable final Stream<T> stream) {
+        return stream!=null ? stream : Stream.empty();
+    }
+    
+    /**
      * If {@code enumeration} is {@code null} returns the empty stream,
      * otherwise returns a stream of the enumeration's elements.
      * @param enumeration
@@ -232,4 +244,6 @@ public final class _NullSafe {
 
 
 
+
+
 }
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/user/UserServiceDefault.java b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/user/UserServiceDefault.java
index ed34ddd..8d477b4 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/user/UserServiceDefault.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/metamodel/services/user/UserServiceDefault.java
@@ -19,9 +19,13 @@
 
 package org.apache.isis.core.metamodel.services.user;
 
-import java.util.ArrayList;
 import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
 import java.util.Stack;
+import java.util.TreeSet;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
 
 import javax.inject.Inject;
 
@@ -35,6 +39,8 @@ import org.apache.isis.applib.services.user.UserService;
 import org.apache.isis.core.security.authentication.AuthenticationSession;
 import org.apache.isis.core.security.authentication.AuthenticationSessionProvider;
 
+import static org.apache.isis.commons.internal.base._NullSafe.stream;
+
 @DomainService(
         nature = NatureOfService.DOMAIN,
         menuOrder = "" + Integer.MAX_VALUE
@@ -51,12 +57,12 @@ public class UserServiceDefault implements UserService {
 
             final String username = userAndRoleOverrides.user;
 
-            final List<String> roles;
+            final Stream<String> roles;
             if (userAndRoleOverrides.roles != null) {
-                roles = userAndRoleOverrides.roles;
+                roles = userAndRoleOverrides.streamRoles();
             } else {
                 // preserve the roles if were not overridden
-                roles = previousRoles();
+                roles = streamPreviousRoles();
             }
 
             final List<RoleMemento> roleMementos = asRoleMementos(roles);
@@ -69,25 +75,23 @@ public class UserServiceDefault implements UserService {
         }
     }
 
-    private List<String> previousRoles() {
-        final List<String> roles;
-
+    private Stream<String> streamPreviousRoles() {
         final AuthenticationSession session =
                 authenticationSessionProvider.getAuthenticationSession();
-        roles = session.getRoles();
-        return roles;
+        
+        return session.streamRoles();
     }
 
     public static class UserAndRoleOverrides {
         final String user;
-        final List<String> roles;
+        final Set<String> roles;
 
 
         UserAndRoleOverrides(final String user) {
             this(user, null);
         }
 
-        UserAndRoleOverrides(final String user, final List<String> roles) {
+        UserAndRoleOverrides(final String user, final Set<String> roles) {
             this.user = user;
             this.roles = roles;
         }
@@ -96,8 +100,8 @@ public class UserServiceDefault implements UserService {
             return user;
         }
 
-        public List<String> getRoles() {
-            return roles;
+        public Stream<String> streamRoles() {
+            return stream(roles);
         }
     }
 
@@ -109,9 +113,14 @@ public class UserServiceDefault implements UserService {
     };
 
 
-    private void overrideUserAndRoles(final String user, final List<String> rolesIfAny) {
-        final List<String> roles = rolesIfAny != null ? rolesIfAny : inheritRoles();
-        this.overrides.get().push(new UserAndRoleOverrides(user, roles));
+    private void overrideUserAndRoles(final String user, final Iterable<String> rolesIfAny) {
+        
+        final Stream<String> roles = rolesIfAny != null 
+                ? stream(rolesIfAny)
+                        : inheritRoles();
+        
+        final SortedSet<String> rolesSorted = roles.collect(Collectors.toCollection(TreeSet::new));
+        this.overrides.get().push(new UserAndRoleOverrides(user, rolesSorted));
     }
 
     private void resetOverrides() {
@@ -129,20 +138,18 @@ public class UserServiceDefault implements UserService {
                         : null;
     }
 
-    private List<String> inheritRoles() {
+    private Stream<String> inheritRoles() {
         final UserAndRoleOverrides currentOverridesIfAny = currentOverridesIfAny();
         return currentOverridesIfAny != null
-                ? currentOverridesIfAny.getRoles()
-                        : authenticationSessionProvider.getAuthenticationSession().getRoles();
+                ? currentOverridesIfAny.streamRoles()
+                        : authenticationSessionProvider.getAuthenticationSession().streamRoles();
     }
 
-    private static List<RoleMemento> asRoleMementos(final List<String> roles) {
-        final List<RoleMemento> mementos = new ArrayList<RoleMemento>();
-        if (roles != null) {
-            for (final String role : roles) {
-                mementos.add(new RoleMemento(role));
-            }
-        }
+    private static List<RoleMemento> asRoleMementos(final Stream<String> roles) {
+        final List<RoleMemento> mementos = stream(roles)
+                .map(RoleMemento::new)
+                .collect(Collectors.toList());
+        
         return mementos;
     }
 
@@ -154,7 +161,7 @@ public class UserServiceDefault implements UserService {
     public static class SudoServiceSpi implements SudoService.Spi {
 
         @Override
-        public void runAs(final String username, final List<String> roles) {
+        public void runAs(final String username, final Iterable<String> roles) {
             userServiceDefault.overrideUserAndRoles(username, roles);
         }
 
diff --git a/core/metamodel/src/main/java/org/apache/isis/core/runtime/authorization/standard/AuthorizationManagerStandard.java b/core/metamodel/src/main/java/org/apache/isis/core/runtime/authorization/standard/AuthorizationManagerStandard.java
index b02f198..0c33591 100644
--- a/core/metamodel/src/main/java/org/apache/isis/core/runtime/authorization/standard/AuthorizationManagerStandard.java
+++ b/core/metamodel/src/main/java/org/apache/isis/core/runtime/authorization/standard/AuthorizationManagerStandard.java
@@ -19,8 +19,6 @@
 
 package org.apache.isis.core.runtime.authorization.standard;
 
-import java.util.List;
-
 import org.apache.isis.applib.Identifier;
 import org.apache.isis.applib.services.sudo.SudoService;
 import org.apache.isis.core.metamodel.progmodel.ProgrammingModel;
@@ -72,11 +70,12 @@ public class AuthorizationManagerStandard extends AuthorizationManagerAbstract {
         if (authorizor.isUsableInAnyRole(identifier)) {
             return true;
         }
-        for (final String roleName : session.getRoles()) {
-            if (authorizor.isUsableInRole(roleName, identifier)) {
-                return true;
-            }
+        
+        if(session.streamRoles()
+                .anyMatch(roleName->authorizor.isUsableInRole(roleName, identifier)) ) {
+            return true;
         }
+        
         return false;
     }
 
@@ -97,17 +96,15 @@ public class AuthorizationManagerStandard extends AuthorizationManagerAbstract {
         if (authorizor.isVisibleInAnyRole(identifier)) {
             return true;
         }
-        for (final String roleName : session.getRoles()) {
-            if (authorizor.isVisibleInRole(roleName, identifier)) {
-                return true;
-            }
+        if(session.streamRoles()
+                .anyMatch(roleName->authorizor.isVisibleInRole(roleName, identifier)) ) {
+            return true;
         }
         return false;
     }
 
     private static boolean containsSudoSuperuserRole(final AuthenticationSession session) {
-        final List<String> roles = session.getRoles();
-        return roles != null && roles.contains(SudoService.ACCESS_ALL_ROLE);
+        return session.hasRole(SudoService.ACCESS_ALL_ROLE);
     }
 
     private boolean isPerspectiveMember(final Identifier identifier) {
diff --git a/core/plugins/security-shiro/src/main/java/org/apache/isis/security/shiro/ShiroAuthenticatorOrAuthorizor.java b/core/plugins/security-shiro/src/main/java/org/apache/isis/security/shiro/ShiroAuthenticatorOrAuthorizor.java
index d2a3e46..d62391e 100644
--- a/core/plugins/security-shiro/src/main/java/org/apache/isis/security/shiro/ShiroAuthenticatorOrAuthorizor.java
+++ b/core/plugins/security-shiro/src/main/java/org/apache/isis/security/shiro/ShiroAuthenticatorOrAuthorizor.java
@@ -20,8 +20,8 @@ package org.apache.isis.security.shiro;
 
 import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 import java.util.Set;
+import java.util.stream.Stream;
 
 import org.apache.shiro.SecurityUtils;
 import org.apache.shiro.UnavailableSecurityManagerException;
@@ -44,7 +44,7 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
 import org.apache.isis.applib.Identifier;
-import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.core.security.authentication.AuthenticationRequest;
 import org.apache.isis.core.security.authentication.AuthenticationRequestPassword;
 import org.apache.isis.core.security.authentication.AuthenticationSession;
@@ -162,12 +162,19 @@ public class ShiroAuthenticatorOrAuthorizor implements Authenticator, Authorizor
         }
     }
 
-    AuthenticationSession authenticationSessionFor(AuthenticationRequest request, String code, AuthenticationToken token, Subject currentSubject) {
-        List<String> roles = getRoles(currentSubject, token);
-        // copy over any roles passed in
-        // (this is used by the Wicket viewer, for example).
-        roles.addAll(request.getRoles());
-
+    AuthenticationSession authenticationSessionFor(
+            AuthenticationRequest request, 
+            String code, 
+            AuthenticationToken token, 
+            Subject currentSubject) {
+        
+        final Stream<String> roles = Stream.concat(
+                streamRoles(currentSubject, token),
+                
+                // copy over any roles passed in
+                // (this is used by the Wicket viewer, for example).
+                request.streamRoles());
+        
         return new SimpleSession(request.getName(), roles, code);
     }
 
@@ -176,12 +183,12 @@ public class ShiroAuthenticatorOrAuthorizor implements Authenticator, Authorizor
      * in the future that might obtain the list of roles for a principal from
      * somewhere other than Shiro's {@link RealmSecurityManager}.
      */
-    protected List<String> getRoles(final Subject subject, final AuthenticationToken token) {
-        final List<String> roles = _Lists.newArrayList();
+    protected Stream<String> streamRoles(final Subject subject, final AuthenticationToken token) {
+        final Set<String> roles = _Sets.newHashSet();
 
         RealmSecurityManager securityManager = getSecurityManager();
         if(securityManager == null) {
-            return roles;
+            return Stream.empty();
         }
 
         final Set<String> realmNames = realmNamesOf(subject);
@@ -200,7 +207,7 @@ public class ShiroAuthenticatorOrAuthorizor implements Authenticator, Authorizor
                 }
             }
         }
-        return roles;
+        return roles.stream();
     }
 
     private static Set<String> realmNamesOf(final Subject subject) {
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/authsess/AuthenticationSessionProviderDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/authsess/AuthenticationSessionProviderDefault.java
index 0321983..555f580 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/authsess/AuthenticationSessionProviderDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/authsess/AuthenticationSessionProviderDefault.java
@@ -16,7 +16,7 @@
  */
 package org.apache.isis.core.runtime.services.authsess;
 
-import java.util.List;
+import java.util.stream.Stream;
 
 import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.NatureOfService;
@@ -50,7 +50,7 @@ public class AuthenticationSessionProviderDefault implements AuthenticationSessi
 
         if(userAndRoleOverrides != null) {
             final String user = userAndRoleOverrides.getUser();
-            final List<String> roles = userAndRoleOverrides.getRoles();
+            final Stream<String> roles = userAndRoleOverrides.streamRoles();
             return new SimpleSession(user, roles);
         }
 
diff --git a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/sudo/SudoServiceDefault.java b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/sudo/SudoServiceDefault.java
index e6eaf00..e8974ab 100644
--- a/core/runtime/src/main/java/org/apache/isis/core/runtime/services/sudo/SudoServiceDefault.java
+++ b/core/runtime/src/main/java/org/apache/isis/core/runtime/services/sudo/SudoServiceDefault.java
@@ -26,7 +26,6 @@ import org.apache.isis.applib.annotation.DomainService;
 import org.apache.isis.applib.annotation.NatureOfService;
 import org.apache.isis.applib.annotation.Programmatic;
 import org.apache.isis.applib.services.sudo.SudoService;
-import org.apache.isis.applib.services.user.UserService;
 
 @DomainService(
         nature = NatureOfService.DOMAIN,
@@ -60,7 +59,7 @@ public class SudoServiceDefault implements SudoService {
 
     @Programmatic
     @Override
-    public void sudo(final String username, final List<String> roles, final Runnable runnable) {
+    public void sudo(final String username, final Iterable<String> roles, final Runnable runnable) {
         try {
             runAs(username, roles);
             runnable.run();
@@ -71,7 +70,7 @@ public class SudoServiceDefault implements SudoService {
 
     @Programmatic
     @Override
-    public <T> T sudo(final String username, final List<String> roles, final Callable<T> callable) {
+    public <T> T sudo(final String username, final Iterable<String> roles, final Callable<T> callable) {
         try {
             runAs(username, roles);
             return callable.call();
@@ -82,7 +81,7 @@ public class SudoServiceDefault implements SudoService {
         }
     }
 
-    private void runAs(final String username, final List<String> roles) {
+    private void runAs(final String username, final Iterable<String> roles) {
         if(spiServices != null) {
             for (SudoService.Spi spiService : spiServices) {
                 spiService.runAs(username, roles);
@@ -98,10 +97,6 @@ public class SudoServiceDefault implements SudoService {
         }
     }
 
-
-    @javax.inject.Inject
-    private UserService userService;
-
     @javax.inject.Inject
     private List<Spi> spiServices;
 
diff --git a/core/runtime/src/test/java/org/apache/isis/core/runtime/authentication/standard/SimpleSessionEncodabilityTestAbstract.java b/core/runtime/src/test/java/org/apache/isis/core/runtime/authentication/standard/SimpleSessionEncodabilityTestAbstract.java
index 0886aae..af88f6a 100644
--- a/core/runtime/src/test/java/org/apache/isis/core/runtime/authentication/standard/SimpleSessionEncodabilityTestAbstract.java
+++ b/core/runtime/src/test/java/org/apache/isis/core/runtime/authentication/standard/SimpleSessionEncodabilityTestAbstract.java
@@ -19,13 +19,17 @@
 
 package org.apache.isis.core.runtime.authentication.standard;
 
-import static org.hamcrest.CoreMatchers.equalTo;
-import static org.hamcrest.CoreMatchers.is;
-import static org.junit.Assert.assertThat;
+import java.util.stream.Stream;
 
+import org.apache.isis.commons.internal.collections._Arrays;
 import org.apache.isis.core.commons.encoding.EncodabilityContractTest;
 import org.apache.isis.core.security.authentication.standard.SimpleSession;
 
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertThat;
+
 public abstract class SimpleSessionEncodabilityTestAbstract extends EncodabilityContractTest {
 
     @Override
@@ -34,7 +38,16 @@ public abstract class SimpleSessionEncodabilityTestAbstract extends Encodability
         final SimpleSession original = (SimpleSession) originalEncodable;
 
         assertThat(decoded.getUserName(), is(equalTo(original.getUserName())));
-        assertThat(decoded.getRoles(), is(equalTo(original.getRoles())));
+        assertArrayEquals(
+                orderedArray(decoded.streamRoles()),
+                orderedArray(original.streamRoles()));
     }
 
+    // -- HELPER
+    
+    private final String[] orderedArray(Stream<String> stream) {
+        return stream.sorted()
+        .collect(_Arrays.toArray(String.class));
+    }
+    
 }
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequest.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequest.java
index e9ce204..1d7599a 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequest.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequest.java
@@ -19,14 +19,43 @@
 
 package org.apache.isis.core.security.authentication;
 
-import java.util.List;
+import java.util.Collection;
+import java.util.stream.Stream;
 
-public interface AuthenticationRequest {
+import javax.annotation.Nullable;
+
+import static org.apache.isis.commons.internal.base._NullSafe.stream;
 
-    String getName();
+public interface AuthenticationRequest {
 
-    List<String> getRoles();
+    /**
+     * Account's name. 
+     * @return nullable
+     */
+    public @Nullable String getName();
 
-    void setRoles(List<String> roles);
+    /**
+     * Account's roles as Stream.
+     * @return non-null
+     * @since 2.0.0-M2
+     */
+    public Stream<String> streamRoles();
+    
+    /**
+     * Add a role to associate with the account. Null or empty roles are ignored.
+     * @param role
+     * @since 2.0.0-M2
+     */
+    public void addRole(@Nullable String role);
+    
+    /**
+     * Add a roles to associate with the account. Null or empty roles are ignored.
+     * @param roles
+     * @since 2.0.0-M2
+     */
+    public default void addRoles(@Nullable Collection<String> roles) {
+        stream(roles)
+        .forEach(this::addRole);
+    }
 
 }
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequestAbstract.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequestAbstract.java
index 8d7fd8d..0b62ef9 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequestAbstract.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationRequestAbstract.java
@@ -19,15 +19,16 @@
 
 package org.apache.isis.core.security.authentication;
 
-import java.util.Collections;
-import java.util.List;
+import java.util.Set;
+import java.util.stream.Stream;
 
-import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.commons.internal.base._Strings;
+import org.apache.isis.commons.internal.collections._Sets;
 
 public abstract class AuthenticationRequestAbstract implements AuthenticationRequest {
 
     private final String name;
-    private final List<String> roles = _Lists.newArrayList();
+    private final Set<String> roles = _Sets.newHashSet();
 
     public AuthenticationRequestAbstract(final String name) {
         this.name = name;
@@ -39,14 +40,16 @@ public abstract class AuthenticationRequestAbstract implements AuthenticationReq
     }
 
     @Override
-    public List<String> getRoles() {
-        return Collections.unmodifiableList(roles);
+    public Stream<String> streamRoles() {
+        return roles.stream();
     }
-
+    
     @Override
-    public void setRoles(final List<String> roles) {
-        this.roles.clear();
-        this.roles.addAll(roles);
+    public void addRole(String role) {
+        if(_Strings.isNullOrEmpty(role)) {
+            return; // ignore
+        }
+        this.roles.add(role);
     }
 
 }
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSession.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSession.java
index 59a4f62..2424d32 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSession.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSession.java
@@ -20,7 +20,7 @@
 package org.apache.isis.core.security.authentication;
 
 import java.io.Serializable;
-import java.util.List;
+import java.util.stream.Stream;
 
 import org.apache.isis.applib.security.UserMemento;
 import org.apache.isis.core.commons.encoding.Encodable;
@@ -39,8 +39,16 @@ public interface AuthenticationSession extends Encodable, Serializable {
 
     /**
      * The roles this user belongs to
+     * @since 2.0.0-M2
      */
-    List<String> getRoles();
+    Stream<String> streamRoles();
+    
+    /**
+     * Whether this user has specified {@code role}
+     * @param role 
+     * @since 2.0.0-M2
+     */
+    boolean hasRole(String role);
 
     /**
      * A unique code given to this session during authentication.
@@ -69,4 +77,7 @@ public interface AuthenticationSession extends Encodable, Serializable {
     MessageBroker getMessageBroker();
 
     UserMemento createUserMemento();
+
+    
+    
 }
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSessionAbstract.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSessionAbstract.java
index 0c7b56d..19d1ada 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSessionAbstract.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/AuthenticationSessionAbstract.java
@@ -21,21 +21,25 @@ package org.apache.isis.core.security.authentication;
 
 import java.io.IOException;
 import java.io.Serializable;
-import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Stream;
 
 import org.apache.isis.applib.security.RoleMemento;
 import org.apache.isis.applib.security.UserMemento;
 import org.apache.isis.applib.util.ToString;
+import org.apache.isis.commons.internal.base._Strings;
 import org.apache.isis.commons.internal.collections._Lists;
+import org.apache.isis.commons.internal.collections._Sets;
 import org.apache.isis.core.commons.encoding.DataInputExtended;
 import org.apache.isis.core.commons.encoding.DataOutputExtended;
 
+import static org.apache.isis.commons.internal.base._NullSafe.stream;
+
 public abstract class AuthenticationSessionAbstract implements AuthenticationSession, Serializable {
 
     private static final long serialVersionUID = 1L;
@@ -43,22 +47,24 @@ public abstract class AuthenticationSessionAbstract implements AuthenticationSes
     // -- Constructor, fields
 
     private final String name;
-    private final List<String> roles = new ArrayList<String>();
+    private final Set<String> roles = _Sets.newHashSet();
     private final String validationCode;
 
     private final Map<String, Object> attributeByName = new HashMap<String, Object>();
 
     private final MessageBroker messageBroker;
 
-
-    @SuppressWarnings("unchecked")
     public AuthenticationSessionAbstract(final String name, final String code) {
-        this(name, Collections.EMPTY_LIST, code);
+        this(name, Stream.of(), code);
     }
 
-    public AuthenticationSessionAbstract(final String name, final List<String> roles, final String validationCode) {
+    public AuthenticationSessionAbstract(final String name, final Stream<String> roleStream, final String validationCode) {
         this.name = name;
-        this.roles.addAll(roles);
+        
+        stream(roleStream)
+        .filter(_Strings::isNotEmpty)
+        .forEach(this.roles::add);
+        
         this.validationCode = validationCode;
         this.messageBroker = new MessageBroker();
         // nothing to do
@@ -72,8 +78,6 @@ public abstract class AuthenticationSessionAbstract implements AuthenticationSes
         // nothing to do
     }
 
-
-
     // -- encode
 
     @Override
@@ -83,8 +87,6 @@ public abstract class AuthenticationSessionAbstract implements AuthenticationSes
         output.writeUTF(validationCode);
     }
 
-
-
     // -- User Name
 
     @Override
@@ -97,20 +99,18 @@ public abstract class AuthenticationSessionAbstract implements AuthenticationSes
         return Objects.equals(userName, getUserName());
     }
 
-
-
     // -- Roles
 
-    /**
-     * Can be overridden.
-     */
     @Override
-    public List<String> getRoles() {
-        return Collections.unmodifiableList(roles);
+    public Stream<String> streamRoles() {
+        return roles.stream();
     }
 
-
-
+    @Override
+    public boolean hasRole(String role) {
+        return roles.contains(role);
+    }
+    
     // -- Validation Code
 
     @Override
@@ -118,8 +118,6 @@ public abstract class AuthenticationSessionAbstract implements AuthenticationSes
         return validationCode;
     }
 
-
-
     // -- Attributes
 
     @Override
@@ -132,8 +130,6 @@ public abstract class AuthenticationSessionAbstract implements AuthenticationSes
         attributeByName.put(attributeName, attribute);
     }
 
-
-
     // -- MessageBroker
 
     @Override
@@ -141,14 +137,12 @@ public abstract class AuthenticationSessionAbstract implements AuthenticationSes
         return messageBroker;
     }
 
-
-
     // -- createUserMemento
 
     @Override
     public UserMemento createUserMemento() {
         final List<RoleMemento> roles = _Lists.newArrayList();
-        for (final String roleName : getRoles()) {
+        for (final String roleName : this.roles) {
             roles.add(new RoleMemento(roleName));
         }
         return new UserMemento(getUserName(), roles);
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/fixtures/AuthenticationRequestLogonFixture.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/fixtures/AuthenticationRequestLogonFixture.java
index 276894b..a2bc959 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/fixtures/AuthenticationRequestLogonFixture.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/fixtures/AuthenticationRequestLogonFixture.java
@@ -19,8 +19,8 @@
 
 package org.apache.isis.core.security.authentication.fixtures;
 
+import java.util.Collection;
 import java.util.Collections;
-import java.util.List;
 
 import org.apache.isis.commons.internal.collections._Lists;
 import org.apache.isis.core.security.authentication.AuthenticationRequestAbstract;
@@ -41,9 +41,9 @@ public class AuthenticationRequestLogonFixture extends AuthenticationRequestAbst
                         : _Lists.of(roles));
     }
     
-    public AuthenticationRequestLogonFixture(final String name, final List<String> roles) {
+    public AuthenticationRequestLogonFixture(final String name, final Collection<String> roles) {
         super(name);
-        setRoles(roles);
+        addRoles(roles);
     }
 
 }
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/health/HealthAuthSession.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/health/HealthAuthSession.java
index 6a4dc9b..d567594 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/health/HealthAuthSession.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/health/HealthAuthSession.java
@@ -20,7 +20,7 @@
 package org.apache.isis.core.security.authentication.health;
 
 import java.io.IOException;
-import java.util.Arrays;
+import java.util.stream.Stream;
 
 import org.apache.isis.core.commons.encoding.DataInputExtended;
 import org.apache.isis.core.security.authentication.AuthenticationSessionAbstract;
@@ -34,7 +34,7 @@ public class HealthAuthSession extends AuthenticationSessionAbstract {
     private static final String CODE = "";
 
     public HealthAuthSession() {
-        super(USER_NAME, Arrays.asList(ROLE), CODE);
+        super(USER_NAME, Stream.of(ROLE), CODE);
     }
 
     public HealthAuthSession(final DataInputExtended input) throws IOException {
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/singleuser/AuthenticationRequestSingleUser.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/singleuser/AuthenticationRequestSingleUser.java
index 38fac9b..e679dbf 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/singleuser/AuthenticationRequestSingleUser.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/singleuser/AuthenticationRequestSingleUser.java
@@ -19,10 +19,6 @@
 
 package org.apache.isis.core.security.authentication.singleuser;
 
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.List;
-
 import org.apache.isis.core.security.authentication.AuthenticationRequestAbstract;
 
 /**
@@ -31,17 +27,11 @@ import org.apache.isis.core.security.authentication.AuthenticationRequestAbstrac
 public class AuthenticationRequestSingleUser extends AuthenticationRequestAbstract {
 
     private static final String SINGLE_USER_NAME = "self";
-    private static final String SINGLE_USER_ROlE_NAME = "default_role";
-    private final List<String> roles;
+    private static final String SINGLE_USER_ROLE_NAME = "default_role";
 
     public AuthenticationRequestSingleUser() {
         super(SINGLE_USER_NAME);
-        roles = Collections.unmodifiableList(Arrays.asList(SINGLE_USER_ROlE_NAME));
-    }
-
-    @Override
-    public List<String> getRoles() {
-        return roles;
+        addRole(SINGLE_USER_ROLE_NAME);
     }
 
 }
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/AuthenticatorAbstract.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/AuthenticatorAbstract.java
index 2b46972..e16bd77 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/AuthenticatorAbstract.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/AuthenticatorAbstract.java
@@ -47,7 +47,7 @@ public abstract class AuthenticatorAbstract implements Authenticator {
         if (!isValid(request)) {
             return null;
         }
-        return new SimpleSession(request.getName(), request.getRoles(), code);
+        return new SimpleSession(request.getName(), request.streamRoles(), code);
     }
 
 
diff --git a/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/SimpleSession.java b/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/SimpleSession.java
index 22ee9e6..839abab 100644
--- a/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/SimpleSession.java
+++ b/core/security/src/main/java/org/apache/isis/core/security/authentication/standard/SimpleSession.java
@@ -20,39 +20,48 @@
 package org.apache.isis.core.security.authentication.standard;
 
 import java.io.IOException;
-import java.util.Arrays;
-import java.util.List;
+import java.util.stream.Stream;
 
 import org.apache.isis.core.commons.encoding.DataInputExtended;
 import org.apache.isis.core.security.authentication.AuthenticationSessionAbstract;
 
+import static org.apache.isis.commons.internal.base._NullSafe.stream;
+
 public class SimpleSession extends AuthenticationSessionAbstract {
 
     private static final long serialVersionUID = 1L;
+    private static final String DEFAULT_VALIDATION_CODE = "";
 
     // ///////////////////////////////////////////////////////////////
     // Constructor, encode
     // ///////////////////////////////////////////////////////////////
 
     /**
-     * as per {@link #SimpleSession(String, List)}.
+     * Defaults {@link #getValidationCode()} to empty string (<tt>""</tt>).
      */
     public SimpleSession(final String name, final String[] roles) {
-        this(name, Arrays.asList(roles));
+        this(name, stream(roles), DEFAULT_VALIDATION_CODE);
     }
 
     /**
      * Defaults {@link #getValidationCode()} to empty string (<tt>""</tt>).
      */
-    public SimpleSession(final String name, final List<String> roles) {
-        this(name, roles, "");
+    public SimpleSession(final String name, final Iterable<String> roles) {
+        this(name, stream(roles), DEFAULT_VALIDATION_CODE);
+    }
+    
+    /**
+     * Defaults {@link #getValidationCode()} to empty string (<tt>""</tt>).
+     */
+    public SimpleSession(final String name, final Stream<String> roles) {
+        this(name, roles, DEFAULT_VALIDATION_CODE);
     }
 
     public SimpleSession(final String name, final String[] roles, final String code) {
-        this(name, Arrays.asList(roles), code);
+        this(name, stream(roles), code);
     }
 
-    public SimpleSession(final String name, final List<String> roles, final String code) {
+    public SimpleSession(final String name, final Stream<String> roles, final String code) {
         super(name, roles, code);
     }
 
diff --git a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserReprRenderer.java b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserReprRenderer.java
index 0a5d525..497701a 100644
--- a/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserReprRenderer.java
+++ b/core/viewer-restfulobjects-server/src/main/java/org/apache/isis/viewer/restfulobjects/server/resources/UserReprRenderer.java
@@ -35,9 +35,10 @@ public class UserReprRenderer extends ReprRendererAbstract<UserReprRenderer, Aut
     public UserReprRenderer with(final AuthenticationSession authenticationSession) {
         representation.mapPut("userName", authenticationSession.getUserName());
         final JsonRepresentation roles = JsonRepresentation.newArray();
-        for (final String role : authenticationSession.getRoles()) {
-            roles.arrayAdd(role);
-        }
+        
+        authenticationSession.streamRoles()
+        .forEach(roles::arrayAdd);
+        
         representation.mapPut("roles", roles);
         return this;
     }
diff --git a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java
index 8582b40..72c268f 100644
--- a/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java
+++ b/core/viewer-wicket-impl/src/main/java/org/apache/isis/viewer/wicket/viewer/integration/wicket/AuthenticatedWebSessionForIsis.java
@@ -19,11 +19,14 @@
 
 package org.apache.isis.viewer.wicket.viewer.integration.wicket;
 
-import java.util.Arrays;
-import java.util.List;
-
 import javax.validation.constraints.NotNull;
 
+import org.apache.wicket.Session;
+import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
+import org.apache.wicket.authroles.authorization.strategies.role.Roles;
+import org.apache.wicket.request.Request;
+import org.apache.wicket.request.cycle.RequestCycle;
+
 import org.apache.isis.applib.clock.Clock;
 import org.apache.isis.applib.services.session.SessionLoggingService;
 import org.apache.isis.core.runtime.system.context.IsisContext;
@@ -36,11 +39,6 @@ import org.apache.isis.viewer.wicket.model.models.BookmarkedPagesModel;
 import org.apache.isis.viewer.wicket.ui.components.widgets.breadcrumbs.BreadcrumbModel;
 import org.apache.isis.viewer.wicket.ui.components.widgets.breadcrumbs.BreadcrumbModelProvider;
 import org.apache.isis.viewer.wicket.ui.pages.BookmarkedPagesModelProvider;
-import org.apache.wicket.Session;
-import org.apache.wicket.authroles.authentication.AuthenticatedWebSession;
-import org.apache.wicket.authroles.authorization.strategies.role.Roles;
-import org.apache.wicket.request.Request;
-import org.apache.wicket.request.cycle.RequestCycle;
 
 /**
  * Viewer-specific implementation of {@link AuthenticatedWebSession}, which
@@ -70,7 +68,7 @@ public class AuthenticatedWebSessionForIsis extends AuthenticatedWebSession impl
     @Override
     public synchronized boolean authenticate(final String username, final String password) {
         AuthenticationRequest authenticationRequest = new AuthenticationRequestPassword(username, password);
-        authenticationRequest.setRoles(Arrays.asList(USER_ROLE));
+        authenticationRequest.addRole(USER_ROLE);
         authenticationSession = getAuthenticationManager().authenticate(authenticationRequest);
         if (authenticationSession != null) {
             log(SessionLoggingService.Type.LOGIN, username, null);
@@ -129,8 +127,11 @@ public class AuthenticatedWebSessionForIsis extends AuthenticatedWebSession impl
         if (!isSignedIn()) {
             return null;
         }
-        final List<String> roles = authenticationSession.getRoles();
-        return new Roles(roles.toArray(new String[roles.size()]));
+        
+        final Roles roles = new Roles();
+        authenticationSession.streamRoles()
+        .forEach(roles::add);
+        return roles;
     }
 
     @Override