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 ///