From 4dff5811f4ca4253ad1a58662b9ec11ca464fddd Mon Sep 17 00:00:00 2001
From: RIHTARSIC Joze <joze.rihtarsic@ext.ec.europa.eu>
Date: Mon, 26 Aug 2024 18:40:01 +0200
Subject: [PATCH] [REST API] Set new HTTP headers for visibility and domain
 group

---
 changelog.txt                                 |  5 +-
 .../mysql-4.1_integration_test_data.sql       |  3 +-
 .../ec/edelivery/smp/data/dao/GroupDao.java   | 11 ----
 .../smp/data/enums/VisibilityType.java        | 18 +++++-
 .../resource/ResourceResolverService.java     | 56 +++++++++++++++++--
 .../smp/servlet/ResourceRequest.java          | 22 +++++++-
 .../edelivery/smp/servlet/WebConstants.java   |  2 +
 .../smp/controllers/ResourceController.java   |  2 +
 .../ResourceControllerSingleDomainTest.java   | 56 ++++++++++++++++++-
 .../smp/ui/edit/GroupEditControllerIT.java    |  4 +-
 .../webapp_integration_test_data.sql          |  6 +-
 11 files changed, 158 insertions(+), 27 deletions(-)

diff --git a/changelog.txt b/changelog.txt
index 9fcca8345..012e7793e 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,5 +1,8 @@
 eDelivery SMP 5.1
-- Added the HTTP parameter 'Resource-Owner' as alternative to ServiceGroup-Owner
+- added new HTTP headers when creating a new resource:
+    'Resource-Owner' as alternative to ServiceGroup-Owner
+    'Resource-Group': the name of the group: to define to which group in domain the resource belongs to. The group must be in one of the admin's groups. If not given the first group from the list is used.
+    'Resource-Visibility': The visibility of the resource: PUBLIC, PRIVATE. The default value is PUBLIC. Resource visibility can be set only at creation time. To change value of the existing resource it must be done via the UI.
 - added new properties:
     smp.instance.name: The SMP instance name
     smp.credentials.reset_request.url: The URL to reset the user password
diff --git a/domismp-tests/domismp-tests-api/groovy/mysql-4.1_integration_test_data.sql b/domismp-tests/domismp-tests-api/groovy/mysql-4.1_integration_test_data.sql
index b7127990e..bba97b98b 100644
--- a/domismp-tests/domismp-tests-api/groovy/mysql-4.1_integration_test_data.sql
+++ b/domismp-tests/domismp-tests-api/groovy/mysql-4.1_integration_test_data.sql
@@ -56,7 +56,8 @@ insert into SMP_DOMAIN_RESOURCE_DEF (ID, FK_RESOURCE_DEF_ID, FK_DOMAIN_ID,CREATE
 (2, 2, 1, NOW(),  NOW());
 
 insert into SMP_GROUP (ID, FK_DOMAIN_ID, NAME, VISIBILITY, CREATED_ON, LAST_UPDATED_ON) values
-(1, 1, 'test group', 'PUBLIC', NOW(),  NOW());
+(1, 1, 'Group001', 'PUBLIC', NOW(),  NOW()),
+(2, 1, 'Group002', 'PUBLIC', NOW(),  NOW());
 
 insert into SMP_GROUP_MEMBER (ID, FK_GROUP_ID, FK_USER_ID, MEMBERSHIP_ROLE, CREATED_ON, LAST_UPDATED_ON) values
 (1, 1, 2, 'ADMIN', NOW(),  NOW()),
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java
index 29f449c98..225561902 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/dao/GroupDao.java
@@ -48,17 +48,6 @@ import static org.apache.commons.lang3.StringUtils.trim;
 @Repository
 public class GroupDao extends BaseDao<DBGroup> {
 
-    /**
-     * Returns domain records from smp_domain table.
-     *
-     * @return the list of domain records from smp_domain table
-     * @throws IllegalStateException if no domain is configured
-     */
-    public List<DBGroup> getAllGroups() {
-        TypedQuery<DBGroup> query = memEManager.createNamedQuery(QUERY_GROUP_ALL, DBGroup.class);
-        return query.getResultList();
-    }
-
     /**
      * Get group list for domains
      *
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/VisibilityType.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/VisibilityType.java
index eab03e722..63d8038a9 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/VisibilityType.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/data/enums/VisibilityType.java
@@ -39,5 +39,21 @@ public enum VisibilityType {
     /**
      *  Access to the domain, group or  resource is possible only if you are only direct or un-direct   member of the domain, group or resource
      */
-    PRIVATE
+    PRIVATE;
+
+    /**
+     * Returns the VisibilityType from the string case-insensitive value. If value is blank or null, null is returned.
+     *
+     * @param value the value
+     * @return the VisibilityType
+     * @IllegalArgumentException if the value is not valid.
+     */
+    public static VisibilityType fromString(String value) {
+
+        String sValue = value == null ? null : value.trim();
+        if (sValue == null || sValue.isEmpty()) {
+            return null;
+        }
+        return valueOf(sValue.toUpperCase());
+    }
 }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceResolverService.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceResolverService.java
index a23e88bbe..b1c088929 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceResolverService.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/services/resource/ResourceResolverService.java
@@ -8,9 +8,9 @@
  * versions of the EUPL (the "Licence");
  * You may not use this work except in compliance with the Licence.
  * You may obtain a copy of the Licence at:
- * 
+ *
  * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software distributed under the Licence is
  * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the Licence for the specific language governing permissions and limitations under the Licence.
@@ -19,14 +19,16 @@
 package eu.europa.ec.edelivery.smp.services.resource;
 
 import eu.europa.ec.edelivery.smp.auth.SMPUserDetails;
-import eu.europa.ec.edelivery.smp.services.IdentifierService;
 import eu.europa.ec.edelivery.smp.data.dao.*;
+import eu.europa.ec.edelivery.smp.data.enums.MembershipRoleType;
 import eu.europa.ec.edelivery.smp.data.model.DBDomain;
+import eu.europa.ec.edelivery.smp.data.model.DBGroup;
 import eu.europa.ec.edelivery.smp.data.model.doc.DBDocument;
 import eu.europa.ec.edelivery.smp.data.model.doc.DBResource;
 import eu.europa.ec.edelivery.smp.data.model.doc.DBSubresource;
 import eu.europa.ec.edelivery.smp.data.model.ext.DBResourceDef;
 import eu.europa.ec.edelivery.smp.data.model.ext.DBSubresourceDef;
+import eu.europa.ec.edelivery.smp.data.model.user.DBUser;
 import eu.europa.ec.edelivery.smp.exceptions.ErrorCode;
 import eu.europa.ec.edelivery.smp.exceptions.SMPRuntimeException;
 import eu.europa.ec.edelivery.smp.identifiers.Identifier;
@@ -34,6 +36,7 @@ import eu.europa.ec.edelivery.smp.logging.SMPLogger;
 import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
 import eu.europa.ec.edelivery.smp.security.ResourceGuard;
 import eu.europa.ec.edelivery.smp.services.ConfigurationService;
+import eu.europa.ec.edelivery.smp.services.IdentifierService;
 import eu.europa.ec.edelivery.smp.servlet.ResourceAction;
 import eu.europa.ec.edelivery.smp.servlet.ResourceRequest;
 import org.apache.commons.lang3.StringUtils;
@@ -46,8 +49,7 @@ import java.util.Optional;
 
 import static eu.europa.ec.edelivery.smp.exceptions.ErrorCode.SML_INVALID_IDENTIFIER;
 import static eu.europa.ec.edelivery.smp.logging.SMPLogger.SECURITY_MARKER;
-import static org.apache.commons.lang3.StringUtils.equalsIgnoreCase;
-import static org.apache.commons.lang3.StringUtils.join;
+import static org.apache.commons.lang3.StringUtils.*;
 
 
 /**
@@ -66,6 +68,7 @@ public class ResourceResolverService {
     final ConfigurationService configurationService;
     final IdentifierService identifierService;
     final DomainDao domainDao;
+    final GroupDao groupDao;
     final ResourceDefDao resourceDefinitionDao;
     final DomainResourceDefDao domainResourceDefDao;
     final ResourceDao resourceDao;
@@ -76,6 +79,7 @@ public class ResourceResolverService {
                                    ConfigurationService configurationService,
                                    IdentifierService identifierService,
                                    DomainDao domainDao,
+                                   GroupDao groupDao,
                                    DomainResourceDefDao domainResourceDefDao,
                                    ResourceDefDao resourceDefinitionDao,
                                    ResourceDao resourceDao,
@@ -85,6 +89,7 @@ public class ResourceResolverService {
         this.configurationService = configurationService;
         this.identifierService = identifierService;
         this.domainDao = domainDao;
+        this.groupDao = groupDao;
         this.domainResourceDefDao = domainResourceDefDao;
         this.resourceDefinitionDao = resourceDefinitionDao;
         this.resourceDao = resourceDao;
@@ -134,6 +139,12 @@ public class ResourceResolverService {
                 throw new SMPRuntimeException(ErrorCode.SG_NOT_EXISTS, resourceId.getValue(), resourceId.getScheme());
             }
             resource = createNewResource(resourceId, resourceDef, domain);
+            // determine the group for the resource
+            DBGroup group = resolveResourceGroup(user.getUser(), domain,
+                    trimToNull(resourceRequest.getResourceGroupParameter()));
+
+            resource.setVisibility(resourceRequest.getResourceVisibilityParameter());
+            resource.setGroup(group);
         }
 
         locationVector.setResource(resource);
@@ -282,6 +293,41 @@ public class ResourceResolverService {
         return optResource.orElse(null);
     }
 
+    /**
+     * Method resolves the group for the given domain, admin user and group name.
+     * If the group name is null/not given, the first group is returned. If the group name is provided
+     * but the user is not admin for the group, the exception is thrown.
+     *
+     * @param user        admin user creating the resource
+     * @param domain      domain where the resource is created
+     * @param domainGroup group name
+     * @return DBGroup for the given domain, user and group name.
+     * @throws SMPRuntimeException if the user is not admin authorized to create the resource for the given group/domain
+     */
+    protected DBGroup resolveResourceGroup(DBUser user, DBDomain domain, String domainGroup) {
+        LOG.debug("Resolve group for domain [{}] and user [{}] and group [{}]", domain.getDomainCode(),
+                user.getUsername(),
+                domainGroup);
+        List<DBGroup> adminListGroup =
+                groupDao.getGroupsByDomainUserIdAndGroupRoles(domain.getId(), user.getId(), MembershipRoleType.ADMIN);
+        if (adminListGroup.isEmpty()) {
+            throw new SMPRuntimeException(ErrorCode.UNAUTHORIZED,
+                    "User [" + user.getUsername() + "] is not admin for any group in domain [" + domain.getDomainCode() + "]");
+        }
+        if (domainGroup == null) {
+            LOG.debug("Set first/default group [{}] for domain [{}]", adminListGroup.get(0).getGroupName(),
+                    domain.getDomainCode());
+            return adminListGroup.get(0);
+        }
+        return groupDao.getGroupsByDomainUserIdAndGroupRoles(domain.getId(), user.getId(), MembershipRoleType.ADMIN)
+                .stream()
+                .filter(group -> equalsIgnoreCase(group.getGroupName(), domainGroup))
+                .findFirst()
+                .orElseThrow(() -> new SMPRuntimeException(ErrorCode.UNAUTHORIZED,
+                        "User [" + user.getUsername() + "] is not authorized for group ["
+                                + domainGroup + "] in domain [" + domain.getDomainCode() + "]"));
+    }
+
     /**
      * Resolve subresource for given resource , subresource context and subresouce Identifier
      *
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/ResourceRequest.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/ResourceRequest.java
index fdd605905..0e7719dbc 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/ResourceRequest.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/ResourceRequest.java
@@ -8,9 +8,9 @@
  * versions of the EUPL (the "Licence");
  * You may not use this work except in compliance with the Licence.
  * You may obtain a copy of the Licence at:
- * 
+ *
  * [PROJECT_HOME]\license\eupl-1.2\license.txt or https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12
- * 
+ *
  * Unless required by applicable law or agreed to in writing, software distributed under the Licence is
  * distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the Licence for the specific language governing permissions and limitations under the Licence.
@@ -18,6 +18,7 @@
  */
 package eu.europa.ec.edelivery.smp.servlet;
 
+import eu.europa.ec.edelivery.smp.data.enums.VisibilityType;
 import eu.europa.ec.edelivery.smp.data.model.DBDomain;
 import eu.europa.ec.edelivery.smp.logging.SMPLogger;
 import eu.europa.ec.edelivery.smp.logging.SMPLoggerFactory;
@@ -37,7 +38,6 @@ import static org.apache.commons.lang3.StringUtils.trim;
  *
  * @author Joze Rihtarsic
  * @since 5.0
- *
  */
 public class ResourceRequest {
     private static final SMPLogger LOG = SMPLoggerFactory.getLogger(ResourceRequest.class);
@@ -88,6 +88,22 @@ public class ResourceRequest {
         return getHeader(WebConstants.HTTP_PARAM_RESOURCE_TYPE);
     }
 
+    /**
+     * Returns the visibility of the resource. If value can not be parsed,
+     * the IllegalArgumentException is thrown.
+     * If the visibility is not set, the default value is PUBLIC.
+     * @return the visibility of the resource
+     * @throws IllegalArgumentException if the value can not be parsed.
+     */
+    public VisibilityType getResourceVisibilityParameter() {
+        VisibilityType visibility = VisibilityType.fromString(getHeader(WebConstants.HTTP_PARAM_RESOURCE_VISIBILITY));
+        return visibility == null ? VisibilityType.PUBLIC : visibility;
+    }
+
+    public String getResourceGroupParameter() {
+        return getHeader(WebConstants.HTTP_PARAM_RESOURCE_GROUP);
+    }
+
     public List<String> getUrlPathParameters() {
         return urlPathParameters;
     }
diff --git a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/WebConstants.java b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/WebConstants.java
index 0b42b7206..6b7313b39 100644
--- a/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/WebConstants.java
+++ b/smp-server-library/src/main/java/eu/europa/ec/edelivery/smp/servlet/WebConstants.java
@@ -31,6 +31,8 @@ public class WebConstants {
     public static final String HTTP_PARAM_RESOURCE_TYPE = "Resource-Type";
     public static final String HTTP_PARAM_OWNER_OBSOLETE = "ServiceGroup-Owner";
     public static final String HTTP_PARAM_OWNER = "Resource-Owner";
+    public static final String HTTP_PARAM_RESOURCE_GROUP = "Resource-Group";
+    public static final String HTTP_PARAM_RESOURCE_VISIBILITY = "Resource-Visibility";
 
     private WebConstants() {
     }
diff --git a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ResourceController.java b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ResourceController.java
index 7b4ab905e..32fe3f859 100644
--- a/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ResourceController.java
+++ b/smp-webapp/src/main/java/eu/europa/ec/edelivery/smp/controllers/ResourceController.java
@@ -70,6 +70,8 @@ public class ResourceController {
     private static final List<String> SUPPORTED_HEADERS = Arrays.asList(lowerCase(HTTP_PARAM_DOMAIN),
             lowerCase(HTTP_PARAM_OWNER),
             lowerCase(HTTP_PARAM_OWNER_OBSOLETE),
+            lowerCase(HTTP_PARAM_RESOURCE_GROUP),
+            lowerCase(HTTP_PARAM_RESOURCE_VISIBILITY),
             lowerCase(HTTP_PARAM_RESOURCE_TYPE));
     final ResourceService resourceService;
     final DomainGuard domainGuard;
diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ResourceControllerSingleDomainTest.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ResourceControllerSingleDomainTest.java
index 1f58f212b..d4ac2abef 100644
--- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ResourceControllerSingleDomainTest.java
+++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/controllers/ResourceControllerSingleDomainTest.java
@@ -54,7 +54,8 @@ public class ResourceControllerSingleDomainTest extends AbstractControllerTest {
     private static final String SERVICE_GROUP_INPUT_BODY = getSampleServiceGroupBodyWithScheme(IDENTIFIER_SCHEME);
     private static final String HTTP_HEADER_KEY_DOMAIN = "Domain";
     private static final String HTTP_HEADER_KEY_SERVICE_GROUP_OWNER = "ServiceGroup-Owner";
-
+    private static final String HTTP_HEADER_KEY_RESOURCE_GROUP = "Resource-Group";
+    private static final String HTTP_HEADER_KEY_RESOURCE_VISIBILITY = "Resource-Visibility";
     private static final String OTHER_OWNER_NAME = "CN=EHEALTH_SMP_TEST_BRAZIL,O=European Commission,C=BE:48b681ee8e0dcc08";
 
     @BeforeEach
@@ -190,4 +191,57 @@ public class ResourceControllerSingleDomainTest extends AbstractControllerTest {
                         .content(SERVICE_GROUP_INPUT_BODY))
                 .andExpect(status().isCreated());
     }
+
+    @ParameterizedTest
+    @CsvSource({"Null group,,201",
+            "Empty group, '',201",
+            "Existing group:,'domain group',201",
+            "Existing group 2:,'Third group',201",
+            "Not authorized for domain:,'Second group',401",
+            "Not authorized for non existing domain:,'Not exists group',401"})
+    void createResourceForGroup(String testDesc, String groupName, int httpCode) throws Exception {
+
+        LOG.info(testDesc);
+        // create service group by group admin
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add(HTTP_HEADER_KEY_SERVICE_GROUP_OWNER, OTHER_OWNER_NAME);
+        httpHeaders.add(HTTP_HEADER_KEY_DOMAIN, "domain");
+        if (groupName != null) {
+            httpHeaders.add(HTTP_HEADER_KEY_RESOURCE_GROUP, groupName);
+        }
+
+        mvc.perform(put(URL_PATH)
+                        .with(ADMIN_CREDENTIALS)
+                        .contentType(APPLICATION_XML_VALUE)
+                        .headers(httpHeaders)
+                        .content(SERVICE_GROUP_INPUT_BODY))
+                .andExpect( status().is(httpCode) );
+    }
+
+    @ParameterizedTest
+    @CsvSource({"Null Visibility,,201",
+            "Empty Visibility, '',201",
+            "Public Visibility:,'PUBLIC',201",
+            "Private Visibility:,'PRIVATE',201",
+            "Case insensitive Visibility:,'PRiVaTE',201",
+            "Invalid Visibility:,'NotOKValue',400",
+            })
+    void createResourceWithVisibility(String testDesc, String visibility, int httpCode) throws Exception {
+
+        LOG.info(testDesc);
+        // create service group by group admin
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add(HTTP_HEADER_KEY_SERVICE_GROUP_OWNER, OTHER_OWNER_NAME);
+        httpHeaders.add(HTTP_HEADER_KEY_DOMAIN, "domain");
+        if (visibility != null) {
+            httpHeaders.add(HTTP_HEADER_KEY_RESOURCE_VISIBILITY, visibility);
+        }
+
+        mvc.perform(put(URL_PATH)
+                        .with(ADMIN_CREDENTIALS)
+                        .contentType(APPLICATION_XML_VALUE)
+                        .headers(httpHeaders)
+                        .content(SERVICE_GROUP_INPUT_BODY))
+                .andExpect( status().is(httpCode) );
+    }
 }
diff --git a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditControllerIT.java b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditControllerIT.java
index 1fe33961e..76e168b88 100644
--- a/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditControllerIT.java
+++ b/smp-webapp/src/test/java/eu/europa/ec/edelivery/smp/ui/edit/GroupEditControllerIT.java
@@ -62,8 +62,8 @@ class GroupEditControllerIT extends AbstractControllerTest {
 
     @ParameterizedTest
     @CsvSource({
-            ", 2",
-            "'', 2",
+            ", 3",
+            "'', 3",
             "group-admin, 1",
             "resource-admin, 1",
             "group-viewer, 0",
diff --git a/smp-webapp/src/test/resources/webapp_integration_test_data.sql b/smp-webapp/src/test/resources/webapp_integration_test_data.sql
index 1688a5596..9c726ef12 100644
--- a/smp-webapp/src/test/resources/webapp_integration_test_data.sql
+++ b/smp-webapp/src/test/resources/webapp_integration_test_data.sql
@@ -86,7 +86,8 @@ insert into SMP_DOMAIN (ID, VISIBILITY, DOMAIN_CODE, SML_SUBDOMAIN, SML_SMP_ID,
 
 insert into SMP_GROUP (ID, FK_DOMAIN_ID, NAME, VISIBILITY, CREATED_ON, LAST_UPDATED_ON) values
 (1, 1, 'domain group', 'PUBLIC', NOW(),  NOW()),
-(2, 1, 'Second group', 'PUBLIC', NOW(),  NOW());
+(2, 1, 'Second group', 'PUBLIC', NOW(),  NOW()),
+(3, 1, 'Third group', 'PUBLIC', NOW(),  NOW());
 
 -- --------------
 -- configure extension and document types service group and servicemetadata
@@ -127,7 +128,8 @@ insert into SMP_DOMAIN_MEMBER (ID, FK_DOMAIN_ID, FK_USER_ID, MEMBERSHIP_ROLE, CR
 
 insert into SMP_GROUP_MEMBER (ID, FK_GROUP_ID, FK_USER_ID, MEMBERSHIP_ROLE, CREATED_ON, LAST_UPDATED_ON) values
 (1, 1, 1, 'ADMIN', NOW(),  NOW()),
-(2, 1, 3, 'ADMIN', NOW(),  NOW());
+(2, 3, 1, 'ADMIN', NOW(),  NOW()),
+(3, 1, 3, 'ADMIN', NOW(),  NOW());
 -- set ownership
 insert into SMP_RESOURCE_MEMBER (ID, FK_RESOURCE_ID, FK_USER_ID, MEMBERSHIP_ROLE, CREATED_ON, LAST_UPDATED_ON) values
 (-1, -1, 1, 'ADMIN', NOW(),  NOW()),
-- 
GitLab