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/13 12:15:40 UTC

[isis] branch master updated: ISIS-2905: adds role/permission model export (yaml)

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 a2ded16  ISIS-2905: adds role/permission model export (yaml)
a2ded16 is described below

commit a2ded162a2002eff00fddc4109688c97c3f6ab17
Author: Andi Huber <ah...@apache.org>
AuthorDate: Thu Jan 13 13:15:28 2022 +0100

    ISIS-2905: adds role/permission model export (yaml)
---
 .../isis/applib/value/NamedWithMimeType.java       |   9 +-
 .../isis/commons/internal/resources/_Yaml.java     |  40 +++++--
 .../demo/domain/src/main/resources/application.yml |   4 +-
 .../secman/applib/IsisModuleExtSecmanApplib.java   |   2 +
 ...va => ApplicationRoleManager_exportAsYaml.java} |  39 ++++---
 .../man/mixins/ApplicationRoleManager_newRole.java |   2 +-
 .../secman/applib/util/ApplicationSecurityDto.java | 123 +++++++++++++++++++++
 7 files changed, 191 insertions(+), 28 deletions(-)

diff --git a/api/applib/src/main/java/org/apache/isis/applib/value/NamedWithMimeType.java b/api/applib/src/main/java/org/apache/isis/applib/value/NamedWithMimeType.java
index cada0a7..9c199c5 100644
--- a/api/applib/src/main/java/org/apache/isis/applib/value/NamedWithMimeType.java
+++ b/api/applib/src/main/java/org/apache/isis/applib/value/NamedWithMimeType.java
@@ -107,6 +107,7 @@ public interface NamedWithMimeType extends Serializable {
         XML("application/xml"), /*alias*/ XSD("application/xml"),
 
         XUL("application/vnd.mozilla.xul+xml"),
+        YAML("text/vnd.yaml", "yml"),
         ZIP("application/zip"),
         _7Z("application/x-7z-compressed"),
 
@@ -149,10 +150,12 @@ public interface NamedWithMimeType extends Serializable {
 
         ;
 
-        private CommonMimeType(final String primaryType, final String ... additionalProposedFileExtension) {
+        private CommonMimeType(final String primaryType, final String ... proposedFileExtensions) {
             this.mimeType = newMimeType(primaryType);
-            this.proposedFileExtensions = Can.ofSingleton(name().toLowerCase())
-                    .addAll(Can.ofArray(additionalProposedFileExtension));
+            this.proposedFileExtensions =
+                    proposedFileExtensions.length>0
+                        ? Can.ofArray(proposedFileExtensions)
+                        : Can.ofSingleton(name().toLowerCase()); // default
         }
 
         @Getter final MimeType mimeType;
diff --git a/commons/src/main/java/org/apache/isis/commons/internal/resources/_Yaml.java b/commons/src/main/java/org/apache/isis/commons/internal/resources/_Yaml.java
index 5fae924..3d6b770 100644
--- a/commons/src/main/java/org/apache/isis/commons/internal/resources/_Yaml.java
+++ b/commons/src/main/java/org/apache/isis/commons/internal/resources/_Yaml.java
@@ -24,12 +24,16 @@ import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.StringWriter;
 
+import org.yaml.snakeyaml.DumperOptions;
+import org.yaml.snakeyaml.DumperOptions.LineBreak;
 import org.yaml.snakeyaml.Yaml;
 import org.yaml.snakeyaml.constructor.Constructor;
 
 import org.apache.isis.commons.functional.Result;
 
+import lombok.SneakyThrows;
 import lombok.val;
 
 /**
@@ -47,7 +51,7 @@ public class _Yaml {
 
     // -- FROM INPUT STREAM
 
-    private static <T> T _readYaml(final Class<T> clazz, InputStream content) {
+    private static <T> T _readYaml(final Class<T> clazz, final InputStream content) {
         val yaml = new Yaml(new Constructor(clazz));
         return yaml.load(content);
     }
@@ -59,13 +63,13 @@ public class _Yaml {
      * @param clazz
      * @param content
      */
-    public static <T> Result<T> readYaml(final Class<T> clazz, InputStream content) {
+    public static <T> Result<T> readYaml(final Class<T> clazz, final InputStream content) {
         return Result.of(()->_readYaml(clazz, content));
     }
 
     // -- FROM STRING
 
-    private static <T> T _readYaml(final Class<T> clazz, String content) {
+    private static <T> T _readYaml(final Class<T> clazz, final String content) {
         val yaml = new Yaml(new Constructor(clazz));
         return yaml.load(content);
     }
@@ -77,13 +81,13 @@ public class _Yaml {
      * @param clazz
      * @param content
      */
-    public static <T> Result<T> readYaml(final Class<T> clazz, String content) {
+    public static <T> Result<T> readYaml(final Class<T> clazz, final String content) {
         return Result.of(()->_readYaml(clazz, content));
     }
 
     // -- FROM FILE
 
-    private static <T> T _readYaml(final Class<T> clazz, File content) throws FileNotFoundException, IOException {
+    private static <T> T _readYaml(final Class<T> clazz, final File content) throws FileNotFoundException, IOException {
         try(val fis = new FileInputStream(content)) {
             val yaml = new Yaml(new Constructor(clazz));
             return yaml.load(fis);
@@ -97,13 +101,13 @@ public class _Yaml {
      * @param clazz
      * @param content
      */
-    public static <T> Result<T> readYaml(final Class<T> clazz, File content) {
+    public static <T> Result<T> readYaml(final Class<T> clazz, final File content) {
         return Result.of(()->_readYaml(clazz, content));
     }
 
     // -- FROM BYTE ARRAY
 
-    private static <T> T _readYaml(final Class<T> clazz, byte[] content) throws IOException {
+    private static <T> T _readYaml(final Class<T> clazz, final byte[] content) throws IOException {
         try(val bais = new ByteArrayInputStream(content)) {
             val yaml = new Yaml(new Constructor(clazz));
             return yaml.load(bais);
@@ -117,8 +121,28 @@ public class _Yaml {
      * @param clazz
      * @param content
      */
-    public static <T> Result<T> readYaml(final Class<T> clazz, byte[] content) {
+    public static <T> Result<T> readYaml(final Class<T> clazz, final byte[] content) {
         return Result.of(()->_readYaml(clazz, content));
     }
 
+    // -- WRITING
+
+    @SneakyThrows
+    private static String _toString(final Object pojo) {
+        try(val writer = new StringWriter()){
+            val options = new DumperOptions();
+            options.setIndent(2);
+            options.setLineBreak(LineBreak.UNIX); // fixated for consistency
+            //options.setPrettyFlow(true);
+            //options.setDefaultFlowStyle(FlowStyle.BLOCK);
+            val yaml = new Yaml(options);
+            yaml.dump(pojo, writer);
+            return writer.toString();
+        }
+    }
+
+    public static Result<String> toString(final Object pojo) {
+        return Result.of(()->_toString(pojo));
+    }
+
 }
diff --git a/examples/demo/domain/src/main/resources/application.yml b/examples/demo/domain/src/main/resources/application.yml
index dabd72f..84aaa68 100644
--- a/examples/demo/domain/src/main/resources/application.yml
+++ b/examples/demo/domain/src/main/resources/application.yml
@@ -34,7 +34,9 @@ isis:
             delete.*:fa-trash,
             find.*:fa-search,
             list.*:fa-list,
-            all.*:fa-list
+            all.*:fa-list,
+            export.*:fa-file-export,
+            import.*:fa-file-import
         css-class:
           patterns:
             delete.*:btn-danger
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/IsisModuleExtSecmanApplib.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/IsisModuleExtSecmanApplib.java
index e9fa913..50e2426 100644
--- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/IsisModuleExtSecmanApplib.java
+++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/IsisModuleExtSecmanApplib.java
@@ -41,6 +41,7 @@ import org.apache.isis.extensions.secman.applib.role.dom.mixins.ApplicationRole_
 import org.apache.isis.extensions.secman.applib.role.dom.mixins.ApplicationRole_updateDescription;
 import org.apache.isis.extensions.secman.applib.role.dom.mixins.ApplicationRole_updateName;
 import org.apache.isis.extensions.secman.applib.role.man.mixins.ApplicationRoleManager_allRoles;
+import org.apache.isis.extensions.secman.applib.role.man.mixins.ApplicationRoleManager_exportAsYaml;
 import org.apache.isis.extensions.secman.applib.role.man.mixins.ApplicationRoleManager_newRole;
 import org.apache.isis.extensions.secman.applib.role.menu.ApplicationRoleMenu;
 import org.apache.isis.extensions.secman.applib.seed.SeedSecurityModuleService;
@@ -173,6 +174,7 @@ import org.apache.isis.testing.fixtures.applib.IsisModuleTestingFixturesApplib;
         // ApplicationRoleManager
         ApplicationRoleManager_allRoles.class,
         ApplicationRoleManager_newRole.class,
+        ApplicationRoleManager_exportAsYaml.class,
 
         // other @Services
         SeedSecurityModuleService.class,
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/man/mixins/ApplicationRoleManager_newRole.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/man/mixins/ApplicationRoleManager_exportAsYaml.java
similarity index 68%
copy from extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/man/mixins/ApplicationRoleManager_newRole.java
copy to extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/man/mixins/ApplicationRoleManager_exportAsYaml.java
index 5c47fce..6384c58 100644
--- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/man/mixins/ApplicationRoleManager_newRole.java
+++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/man/mixins/ApplicationRoleManager_exportAsYaml.java
@@ -23,17 +23,19 @@ import javax.inject.Inject;
 import org.apache.isis.applib.annotation.Action;
 import org.apache.isis.applib.annotation.ActionLayout;
 import org.apache.isis.applib.annotation.MemberSupport;
-import org.apache.isis.applib.annotation.Optionality;
 import org.apache.isis.applib.annotation.Parameter;
-import org.apache.isis.applib.annotation.ParameterLayout;
 import org.apache.isis.applib.annotation.SemanticsOf;
+import org.apache.isis.applib.value.Clob;
+import org.apache.isis.applib.value.NamedWithMimeType.CommonMimeType;
 import org.apache.isis.extensions.secman.applib.IsisModuleExtSecmanApplib;
-import org.apache.isis.extensions.secman.applib.role.dom.ApplicationRole;
 import org.apache.isis.extensions.secman.applib.role.dom.ApplicationRoleRepository;
 import org.apache.isis.extensions.secman.applib.role.man.ApplicationRoleManager;
+import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUserRepository;
 import org.apache.isis.extensions.secman.applib.user.man.mixins.ApplicationUserManager_newLocalUser.DomainEvent;
+import org.apache.isis.extensions.secman.applib.util.ApplicationSecurityDto;
 
 import lombok.RequiredArgsConstructor;
+import lombok.val;
 
 @Action(
         domainEvent = DomainEvent.class,
@@ -41,27 +43,34 @@ import lombok.RequiredArgsConstructor;
 )
 @ActionLayout(
         associateWith = "allRoles",
-        sequence = "1"
+        sequence = "1.1"
 )
 @RequiredArgsConstructor
-public class ApplicationRoleManager_newRole {
+public class ApplicationRoleManager_exportAsYaml {
 
     public static class DomainEvent
-            extends IsisModuleExtSecmanApplib.ActionDomainEvent<ApplicationRoleManager_newRole> {}
+            extends IsisModuleExtSecmanApplib.ActionDomainEvent<ApplicationRoleManager_exportAsYaml> {}
 
     @Inject private ApplicationRoleRepository applicationRoleRepository;
+    @Inject private ApplicationUserRepository applicationUserRepository;
 
+    @SuppressWarnings("unused")
     private final ApplicationRoleManager target;
 
-    @MemberSupport public ApplicationRoleManager act (
-            @Parameter(maxLength = ApplicationRole.Name.MAX_LENGTH)
-            @ParameterLayout(named="Name", typicalLength= ApplicationRole.Name.TYPICAL_LENGTH)
-            final String name,
-            @Parameter(maxLength = ApplicationRole.Description.MAX_LENGTH, optionality = Optionality.OPTIONAL)
-            @ParameterLayout(named="Description", typicalLength= ApplicationRole.Description.TYPICAL_LENGTH)
-            final String description) {
-        applicationRoleRepository.newRole(name, description);
-        return target;
+    @MemberSupport public Clob act(
+            @Parameter
+            final String fileName) {
+
+        val yaml = ApplicationSecurityDto.create(
+                applicationRoleRepository,
+                applicationUserRepository)
+                .toYaml();
+
+        return Clob.of(fileName, CommonMimeType.YAML, yaml);
+    }
+
+    @MemberSupport public String defaultFileName() {
+        return "secman-roles.yml";
     }
 
 }
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/man/mixins/ApplicationRoleManager_newRole.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/man/mixins/ApplicationRoleManager_newRole.java
index 5c47fce..c1ef648 100644
--- a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/man/mixins/ApplicationRoleManager_newRole.java
+++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/role/man/mixins/ApplicationRoleManager_newRole.java
@@ -41,7 +41,7 @@ import lombok.RequiredArgsConstructor;
 )
 @ActionLayout(
         associateWith = "allRoles",
-        sequence = "1"
+        sequence = "2"
 )
 @RequiredArgsConstructor
 public class ApplicationRoleManager_newRole {
diff --git a/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/util/ApplicationSecurityDto.java b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/util/ApplicationSecurityDto.java
new file mode 100644
index 0000000..7956f0b
--- /dev/null
+++ b/extensions/security/secman/applib/src/main/java/org/apache/isis/extensions/secman/applib/util/ApplicationSecurityDto.java
@@ -0,0 +1,123 @@
+/*
+ *  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.
+ */
+package org.apache.isis.extensions.secman.applib.util;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.isis.applib.services.appfeat.ApplicationFeatureSort;
+import org.apache.isis.commons.internal.resources._Yaml;
+import org.apache.isis.extensions.secman.applib.permission.dom.ApplicationPermission;
+import org.apache.isis.extensions.secman.applib.permission.dom.ApplicationPermissionMode;
+import org.apache.isis.extensions.secman.applib.permission.dom.ApplicationPermissionRule;
+import org.apache.isis.extensions.secman.applib.role.dom.ApplicationRole;
+import org.apache.isis.extensions.secman.applib.role.dom.ApplicationRoleRepository;
+import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUser;
+import org.apache.isis.extensions.secman.applib.user.dom.ApplicationUserRepository;
+
+import lombok.Data;
+import lombok.NonNull;
+import lombok.val;
+
+/**
+ * In-memory model of users, roles and permissions.
+ *
+ * @since 2.0 {index}
+ */
+@Data
+public class ApplicationSecurityDto {
+
+    @Data
+    public static class PermissionDto {
+
+        static PermissionDto from(final ApplicationPermission permission) {
+            val permissionDto = new PermissionDto();
+            permissionDto.setFeatureFqn(permission.getFeatureFqn());
+            permissionDto.setFeatureSort(permission.getFeatureSort());
+            permissionDto.setMode(permission.getMode());
+            permissionDto.setRule(permission.getRule());
+            return permissionDto;
+        }
+
+        String featureFqn;
+        ApplicationFeatureSort featureSort;
+        ApplicationPermissionMode mode;
+        ApplicationPermissionRule rule;
+    }
+
+    @Data
+    public static class RoleDto {
+
+        static RoleDto from(final ApplicationRole role) {
+            val roleDto = new RoleDto();
+            roleDto.set__name(role.getName());
+            roleDto.setDescription(role.getDescription());
+            role.getPermissions().stream()
+            .map(PermissionDto::from)
+            .forEach(roleDto.getPermissions()::add);
+            return roleDto;
+        }
+
+        String __name; // secondary key - ensure earliest alphabetic order
+        String description;
+        List<PermissionDto> permissions = new ArrayList<>();
+    }
+
+    @Data
+    public static class UserDto {
+
+        static UserDto from(final ApplicationUser user) {
+            val userDto = new UserDto();
+            userDto.set__username(user.getUsername());
+
+            user.getRoles().stream()
+            .map(ApplicationRole::getName)
+            .forEach(userDto.getRoleNames()::add);
+
+            return userDto;
+        }
+
+        String __username; // secondary key - ensure earliest alphabetic order
+        List<String> roleNames = new ArrayList<>();
+    }
+
+    public static ApplicationSecurityDto create(
+            final @NonNull ApplicationRoleRepository applicationRoleRepository,
+            final @NonNull ApplicationUserRepository applicationUserRepository) {
+        val model = new ApplicationSecurityDto();
+
+        applicationRoleRepository.allRoles().stream()
+        .map(RoleDto::from)
+        .forEach(model.getRoles()::add);
+
+        applicationUserRepository.allUsers().stream()
+        .map(UserDto::from)
+        .forEach(model.getUsers()::add);
+
+        return model;
+    }
+
+    private List<RoleDto> roles = new ArrayList<>();
+    private List<UserDto> users = new ArrayList<>();
+
+    public String toYaml() {
+        return _Yaml.toString(this).presentElseFail();
+    }
+
+}