From 9fbd09dd86c0b7a8edbee3e362276f731bf79a95 Mon Sep 17 00:00:00 2001
From: Alexander van Delft <56023674+lxatrhea@users.noreply.github.com>
Date: Wed, 14 Jul 2021 08:57:45 +0200
Subject: [PATCH] Fixes #211 Check SAME_AS_CONTAINER for container write access
* [Add] Check container write access in case of SAME_AS_CONTAINER ParticipantAccessRight
* Add extra unit tests
---
.../PermissionServiceTestFixture.cs | 283 ++++++++++++++++++
.../Authorization/PermissionService.cs | 79 ++++-
2 files changed, 353 insertions(+), 9 deletions(-)
create mode 100644 CDP4WebServices.API.Tests/Services/Authorization/PermissionServiceTestFixture.cs
diff --git a/CDP4WebServices.API.Tests/Services/Authorization/PermissionServiceTestFixture.cs b/CDP4WebServices.API.Tests/Services/Authorization/PermissionServiceTestFixture.cs
new file mode 100644
index 00000000..154a637a
--- /dev/null
+++ b/CDP4WebServices.API.Tests/Services/Authorization/PermissionServiceTestFixture.cs
@@ -0,0 +1,283 @@
+// --------------------------------------------------------------------------------------------------------------------
+//
+// Copyright (c) 2015-2021 RHEA System S.A.
+//
+// Author: Sam Gerené, Merlin Bieze, Alex Vorobiev, Naron Phou, Alexander van Delft.
+//
+// This file is part of CDP4 Web Services Community Edition.
+// The CDP4 Web Services Community Edition is the RHEA implementation of ECSS-E-TM-10-25 Annex A and Annex C.
+//
+// The CDP4 Web Services Community Edition is free software; you can redistribute it and/or
+// modify it under the terms of the GNU Affero General Public
+// License as published by the Free Software Foundation; either
+// version 3 of the License, or (at your option) any later version.
+//
+// The CDP4 Web Services Community Edition is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+// Lesser General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see .
+//
+// --------------------------------------------------------------------------------------------------------------------
+
+namespace CDP4WebServices.API.Tests
+{
+ using System;
+ using System.Collections;
+ using System.Collections.Generic;
+
+ using CDP4Authentication;
+
+ using CDP4Common.CommonData;
+ using CDP4Common.DTO;
+
+ using CDP4Orm.Dao;
+ using CDP4Orm.Dao.Resolve;
+
+ using CDP4WebServices.API.Services;
+ using CDP4WebServices.API.Services.Authentication;
+ using CDP4WebServices.API.Services.Authorization;
+
+ using Moq;
+
+ using Npgsql;
+
+ using NUnit.Framework;
+
+ using Definition = CDP4Common.DTO.Definition;
+ using Thing = CDP4Common.DTO.Thing;
+
+ ///
+ /// Test fixture for the class
+ ///
+ [TestFixture]
+ public class PermissionServiceTestFixture
+ {
+ ///
+ /// The EngineeringModel partition.
+ ///
+ private const string SiteDirectoryPartition = "SiteDirectory";
+
+ ///
+ /// The EngineeringModel partition.
+ ///
+ private const string EngineeringModelPartition = "EngineeringModel";
+
+ ///
+ /// The Iteration partition.
+ ///
+ private const string IterationPartition = "Iteration";
+
+ private PermissionService permissionService;
+ private Mock accessRightKindService;
+ private Mock resolveService;
+ private Mock participantDao;
+
+ private AuthenticationPerson authenticationPerson;
+ private static EngineeringModel engineeringModel = new(Guid.NewGuid(), 0);
+ private static ParameterType parameterType = new TextParameterType(Guid.NewGuid(), 0);
+ private static Iteration iteration = new(Guid.NewGuid(), 0);
+ private static Requirement requirement = new(Guid.NewGuid(), 0);
+ private static Definition definition = new(Guid.NewGuid(), 0);
+ private static Definition definition2 = new(Guid.NewGuid(), 0);
+ private static RequirementsSpecification requirementsSpecification = new(Guid.NewGuid(), 0);
+ private static DomainOfExpertise domain = new(Guid.NewGuid(), 0);
+ private static SiteDirectory siteDirectory = new(Guid.NewGuid(), 0);
+ private Participant participant;
+
+ private Thing addContainerThingToCache = null;
+
+ [SetUp]
+ public void TestSetup()
+ {
+ this.authenticationPerson = new AuthenticationPerson(Guid.NewGuid(), 0)
+ {
+ UserName = "TestRunner"
+ };
+
+ this.permissionService = new PermissionService();
+
+ this.permissionService.Credentials = new Credentials
+ {
+ Person = this.authenticationPerson,
+ EngineeringModelSetup = new EngineeringModelSetup(Guid.NewGuid(), 0)
+ };
+
+ this.resolveService = new Mock();
+
+ this.resolveService.Setup(x => x.ResolveItems(null, It.IsAny(), It.IsAny>()))
+ .Callback>
+ ((npgsqlTransaction, partition, operationThingContainerCache) =>
+ {
+ if (this.addContainerThingToCache != null)
+ {
+ operationThingContainerCache.Add(new ContainerInfo(this.addContainerThingToCache.ClassKind.ToString(), this.addContainerThingToCache.Iid), new DtoResolveHelper(this.addContainerThingToCache));
+ }
+ });
+
+ this.permissionService.ResolveService = this.resolveService.Object;
+
+ this.accessRightKindService = new Mock();
+
+ this.permissionService.AccessRightKindService = this.accessRightKindService.Object;
+
+ this.participant = new Participant(Guid.NewGuid(), 0)
+ {
+ Domain = new List { domain.Iid },
+ Person = this.authenticationPerson.Iid
+ };
+
+ this.permissionService.Credentials.EngineeringModelSetup.Participant.Add(this.participant.Iid);
+
+ this.participantDao = new Mock();
+
+ this.participantDao.Setup(
+ x =>
+ x.Read(null, It.IsAny(), null, false))
+ .Returns(new List() { this.participant });
+
+ this.permissionService.ParticipantDao = this.participantDao.Object;
+
+ engineeringModel.Iteration.Add(iteration.Iid);
+ requirement.Definition.Add(definition.Iid);
+ parameterType.Definition.Add(definition2.Iid);
+ siteDirectory.Domain.Add(domain.Iid);
+ }
+
+ [Test]
+ [TestCaseSource(nameof(TestCases))]
+ public void VerifySameAsContainerPermissionAutorization(Thing containerThing, Thing thing, string partition)
+ {
+ //-------------------------------------------------------------
+ // Setup
+ //-------------------------------------------------------------
+ this.addContainerThingToCache = containerThing;
+ engineeringModel.Iteration.Add(iteration.Iid);
+
+ this.accessRightKindService.Setup(
+ x =>
+ x.QueryPersonAccessRightKind(It.IsAny(), thing.ClassKind.ToString()))
+ .Returns(PersonAccessRightKind.SAME_AS_CONTAINER);
+
+ this.accessRightKindService.Setup(
+ x =>
+ x.QueryParticipantAccessRightKind(It.IsAny(), thing.ClassKind.ToString()))
+ .Returns(ParticipantAccessRightKind.SAME_AS_CONTAINER);
+
+ var securityRequestContext = new RequestSecurityContext
+ {
+ ContainerReadAllowed = true, ContainerWriteAllowed = true
+ };
+
+ //-------------------------------------------------------------
+
+ //-------------------------------------------------------------
+ // container modify is allowed
+ //-------------------------------------------------------------
+ this.accessRightKindService.Setup(
+ x =>
+ x.QueryParticipantAccessRightKind(It.IsAny(), containerThing.ClassKind.ToString()))
+ .Returns(ParticipantAccessRightKind.MODIFY);
+
+ this.accessRightKindService.Setup(
+ x =>
+ x.QueryPersonAccessRightKind(It.IsAny(), containerThing.ClassKind.ToString()))
+ .Returns(PersonAccessRightKind.MODIFY);
+
+ Assert.IsTrue(
+ this.permissionService.CanWrite(
+ null,
+ thing,
+ thing.ClassKind.ToString(),
+ partition,
+ ServiceBase.UpdateOperation,
+ securityRequestContext
+ )
+ );
+
+ //-------------------------------------------------------------
+
+ //-------------------------------------------------------------
+ // container modify is NOT allowed
+ //-------------------------------------------------------------
+ this.accessRightKindService.Setup(
+ x =>
+ x.QueryParticipantAccessRightKind(It.IsAny(), containerThing.ClassKind.ToString()))
+ .Returns(ParticipantAccessRightKind.READ);
+
+ this.accessRightKindService.Setup(
+ x =>
+ x.QueryPersonAccessRightKind(It.IsAny(), containerThing.ClassKind.ToString()))
+ .Returns(PersonAccessRightKind.READ);
+
+ Assert.IsFalse(
+ this.permissionService.CanWrite(
+ null,
+ thing,
+ thing.ClassKind.ToString(),
+ partition,
+ ServiceBase.UpdateOperation,
+ securityRequestContext
+ )
+ );
+
+ //-------------------------------------------------------------
+
+ //-------------------------------------------------------------
+ // Create operation does not check container, but returns
+ // RequestSecurityContext setting
+ //-------------------------------------------------------------
+ Assert.IsTrue(
+ this.permissionService.CanWrite(
+ null,
+ thing,
+ thing.ClassKind.ToString(),
+ partition,
+ ServiceBase.CreateOperation,
+ securityRequestContext
+ )
+ );
+
+ //-------------------------------------------------------------
+
+ //-------------------------------------------------------------
+ // container thing not found returns RequestSecurityContext setting
+ //-------------------------------------------------------------
+ this.addContainerThingToCache = null;
+
+ this.accessRightKindService.Setup(
+ x =>
+ x.QueryParticipantAccessRightKind(It.IsAny(), containerThing.ClassKind.ToString()))
+ .Returns(ParticipantAccessRightKind.MODIFY);
+
+ Assert.IsFalse(
+ this.permissionService.CanWrite(
+ null,
+ thing,
+ thing.ClassKind.ToString(),
+ partition,
+ ServiceBase.UpdateOperation,
+ securityRequestContext
+ )
+ );
+
+ //-------------------------------------------------------------
+ }
+
+ ///
+ /// Different Cases we want to check access rights for
+ ///
+ /// an of type
+ /// containing the method's parameters.
+ public static IEnumerable TestCases()
+ {
+ yield return new object[] { requirement, definition, IterationPartition };
+ yield return new object[] { engineeringModel, iteration, EngineeringModelPartition };
+ yield return new object[] { parameterType, definition2, SiteDirectoryPartition };
+ yield return new object[] { iteration, requirementsSpecification, IterationPartition };
+ yield return new object[] { siteDirectory, domain, SiteDirectoryPartition };
+ }
+ }
+}
diff --git a/CDP4WebServices.API/Services/Authorization/PermissionService.cs b/CDP4WebServices.API/Services/Authorization/PermissionService.cs
index 95032b92..61c3056c 100644
--- a/CDP4WebServices.API/Services/Authorization/PermissionService.cs
+++ b/CDP4WebServices.API/Services/Authorization/PermissionService.cs
@@ -1,6 +1,6 @@
// --------------------------------------------------------------------------------------------------------------------
//
-// Copyright (c) 2015-2020 RHEA System S.A.
+// Copyright (c) 2015-2021 RHEA System S.A.
//
// Author: Sam Gerené, Merlin Bieze, Alex Vorobiev, Naron Phou, Alexander van Delft.
//
@@ -27,7 +27,6 @@ namespace CDP4WebServices.API.Services.Authorization
using System;
using System.Collections.Generic;
using System.Linq;
- using System.Net;
using Authentication;
@@ -35,7 +34,8 @@ namespace CDP4WebServices.API.Services.Authorization
using CDP4Common.DTO;
using CDP4Orm.Dao;
-
+ using CDP4Orm.Dao.Resolve;
+
using NLog;
using Npgsql;
@@ -87,6 +87,11 @@ public class PermissionService : IPermissionService
/// Gets or sets the (injected) for this request
///
public IAccessRightKindService AccessRightKindService { get; set; }
+
+ ///
+ /// Gets or sets the resolve service.
+ ///
+ public IResolveService ResolveService { get; set; }
///
/// A instance
@@ -322,9 +327,9 @@ public bool CanWrite(NpgsqlTransaction transaction, Thing thing, string typeName
switch (personAccessRightKind)
{
case PersonAccessRightKind.SAME_AS_CONTAINER:
- {
- return securityContext.ContainerWriteAllowed;
- }
+ {
+ return this.ComputeSameAsContainerWriteAllowed(transaction, thing, partition, modifyOperation, securityContext);
+ }
case PersonAccessRightKind.SAME_AS_SUPERCLASS:
{
@@ -426,9 +431,9 @@ public bool CanWrite(NpgsqlTransaction transaction, Thing thing, string typeName
switch (participantAccessRightKind)
{
case ParticipantAccessRightKind.SAME_AS_CONTAINER:
- {
- return securityContext.ContainerWriteAllowed;
- }
+ {
+ return this.ComputeSameAsContainerWriteAllowed(transaction, thing, partition, modifyOperation, securityContext);
+ }
case ParticipantAccessRightKind.SAME_AS_SUPERCLASS:
{
@@ -452,6 +457,62 @@ public bool CanWrite(NpgsqlTransaction transaction, Thing thing, string typeName
}
}
+ ///
+ /// Compute WRITE permission for , or
+ ///
+ ///
+ /// The transaction object.
+ ///
+ ///
+ /// The to compute permissions for.
+ ///
+ ///
+ /// The database partition (schema) where the requested resource is stored.
+ ///
+ ///
+ /// The string representation of the type of the modify operation.
+ ///
+ ///
+ /// The security context of the current request.
+ ///
+ ///
+ private bool ComputeSameAsContainerWriteAllowed(NpgsqlTransaction transaction, Thing thing, string partition, string modifyOperation, ISecurityContext securityContext)
+ {
+ if (modifyOperation != ServiceBase.UpdateOperation)
+ {
+ return securityContext.ContainerWriteAllowed;
+ }
+
+ var dtoInfo = thing.GetInfoPlaceholder();
+
+ var operationThingContainerCache =
+ new Dictionary
+ {
+ {
+ dtoInfo,
+ new DtoResolveHelper(dtoInfo)
+ }
+ };
+
+ var resolvePartition = partition.StartsWith(Utils.IterationSubPartition)
+ ? partition.Replace(Utils.IterationSubPartition, Utils.EngineeringModelPartition)
+ : partition;
+
+ this.ResolveService.ResolveItems(transaction, resolvePartition, operationThingContainerCache);
+
+ var containerThing = operationThingContainerCache.SingleOrDefault(x => x.Key is ContainerInfo).Value;
+
+ return containerThing != null
+ &&
+ this.CanWrite(
+ transaction,
+ containerThing.Thing,
+ containerThing.Thing.ClassKind.ToString(),
+ containerThing.Partition,
+ modifyOperation,
+ securityContext);
+ }
+
///
/// Determines whether it is allowed for the current to modify
///