From 1012465170b0c87251695afe8bfbf8d21779600b Mon Sep 17 00:00:00 2001 From: Yvan Duhamel Date: Wed, 21 Aug 2024 09:40:08 +0200 Subject: [PATCH] Publish EntraCP v27.0 (#283) * Merge changes for EntraCP v27.0 to release branch (#282) * Update Yvand.EntraCP.nuspec * Fix missing group members when only users members of some groups can be found (#273) * Update Populate-EntraIDTenant.ps1 * Update EntraIDEntityProvider.cs * add pagination to get all group members * fix init of lists * improve tests * update tests * return entities cloned from their source list * Update CHANGELOG.md * Update tests (#276) * start work * in progress * continue work * progress * work * rename types * Update UnitTestsHelper.cs * Update UnitTestsHelper.cs * Update UnitTestsHelper.cs * work * Add sample of a custom claims provider (#278) * publish sample * Add README files * update README * Add a Bruno collection to replay requests to Microsoft Graph (#280) * initial commit * rename files * set auth to inherit * remove tests * update doc * Update README.md * Update get access token.bru * Update CHANGELOG.md * reorganize folders * Update UnitTestsHelper.cs * remove outdated pipelines * Improve tests (#281) * work * work * work * Update Populate-EntraIDTenant.ps1 * work * work * work; * work * Update Populate-EntraIDTenant.ps1 * Update reusable-prepare-dtl-env.yml * Update reusable-prepare-dtl-env.yml * Update run-tests.yml * simplify the setup of tests * Update run-tests.yml * Update run-tests.yml * Update Yvand.EntraCP.sln * update workflows * update workflows * Update Yvand.EntraCP.Tests.csproj * Update CHANGELOG.md * Update reusable-build.yml * Revert "Update reusable-build.yml" This reverts commit d4b71d6d474f2a8cd0d70d74a67401355ddce1b2. * update build settings * Update prepare-dtl-env.yml * Update prepare-dtl-env.yml * Update CHANGELOG.md --- .../{verify-prs-and-commits.yml => build.yml} | 8 +- .github/workflows/prepare-dtl-env.yml | 18 +- .github/workflows/{stale.yaml => stale.yml} | 1 - CHANGELOG.md | 8 + .../BasicConfigurationTests.cs | 41 +-- Yvand.EntraCP.Tests/BypassDirectoryTests.cs | 39 ++- .../ClaimsProviderTestsBase.cs | 153 ++++----- Yvand.EntraCP.Tests/ExcludeAUserTypeTests.cs | 36 +- .../ExtensionAttributeTests.cs | 2 +- .../FilterUsersBasedOnGroupsTests.cs | 101 ++++-- Yvand.EntraCP.Tests/GuestAccountsUPNTests.cs | 12 +- Yvand.EntraCP.Tests/RequireExactMatchTests.cs | 4 +- .../SecurityEnabledGroupsTests.cs | 16 +- .../Setup/Populate-EntraIDTenant.ps1 | 90 +++-- Yvand.EntraCP.Tests/UnitTestsHelper.cs | 316 ++++++++---------- .../Yvand.EntraCP.Tests.csproj | 23 +- Yvand.EntraCP.Tests/local.runsettings | 10 +- Yvand.EntraCP.sln | 27 +- Yvand.EntraCP/Yvand.EntraCP.csproj | 29 +- Yvand.EntraCP/Yvand.EntraCP.nuspec | 12 +- .../EntraIDEntityProvider.cs | 49 ++- azure-pipelines/ci-apply-dtl-artifacts.yml | 131 -------- azure-pipelines/ci-compile.yml | 132 -------- azure-pipelines/ci-create-dtl-environment.yml | 21 -- .../ADMIN/SharePointProjectItem.spdata | 4 + .../CustomClaimsProvider.csproj | 139 ++++++++ .../CustomClaimsProvider.sln | 27 ++ .../CustomClaimsProvider/EntraCP_Custom.cs | 37 ++ ...EntraCP.Custom.ClaimsProvider.Template.xml | 3 + .../EntraCP.Custom.ClaimsProvider.feature | 2 + .../EntraCP.Custom.EventReceiver.cs | 74 ++++ .../Package/Package.Template.xml | 3 + .../Package/Package.package | 47 +++ .../Properties/AssemblyInfo.cs | 38 +++ .../CustomClaimsProvider/README.md | 9 + custom-claims-provider-samples/README.md | 4 + graph-requests-samples/.env.example | 3 + graph-requests-samples/README.md | 13 + ...ay search users and groups using batch.bru | 36 ++ .../1- get user by userprincipalname.bru | 23 ++ .../augmentation/2- get group membership.bru | 15 + .../authentication/get access token.bru | 22 ++ graph-requests-samples/bruno.json | 9 + graph-requests-samples/collection.bru | 15 + .../environments/entracp-environment.bru | 7 + .../search/list members of a group.bru | 19 ++ ...ay search users and groups using batch.bru | 36 ++ .../search/run a batch request.bru | 44 +++ .../search/search groups.bru | 16 + .../search/search users copy.bru | 27 ++ .../search/search users.bru | 20 ++ .../search/show a group.bru | 19 ++ .../list extension properties.bru | 11 + ...msmappingpolicy to a service principal.bru | 18 + ...assigned to a service principal copy 2.bru | 11 + ...aimsmappingpolicy for saml claims copy.bru | 24 ++ .../delete a claimsmappingpolicy.bru | 24 ++ ...st claimsmappingpolicies in the tenant.bru | 11 + ...aimsmappingpolicy for saml claims copy.bru | 24 ++ ...s with their extension attribute value.bru | 21 ++ ...r to set the extension attribute value.bru | 22 ++ .../unit tests/list test groups.bru | 20 ++ .../unit tests/list test users.bru | 21 ++ .../unit tests/show group by id.bru | 19 ++ .../unit tests/show group members.bru | 19 ++ .../validation/validate group.bru | 17 + .../validation/validate user.bru | 21 ++ 67 files changed, 1503 insertions(+), 770 deletions(-) rename .github/workflows/{verify-prs-and-commits.yml => build.yml} (76%) rename .github/workflows/{stale.yaml => stale.yml} (98%) delete mode 100644 azure-pipelines/ci-apply-dtl-artifacts.yml delete mode 100644 azure-pipelines/ci-compile.yml delete mode 100644 azure-pipelines/ci-create-dtl-environment.yml create mode 100644 custom-claims-provider-samples/CustomClaimsProvider/ADMIN/SharePointProjectItem.spdata create mode 100644 custom-claims-provider-samples/CustomClaimsProvider/CustomClaimsProvider.csproj create mode 100644 custom-claims-provider-samples/CustomClaimsProvider/CustomClaimsProvider.sln create mode 100644 custom-claims-provider-samples/CustomClaimsProvider/EntraCP_Custom.cs create mode 100644 custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.ClaimsProvider.Template.xml create mode 100644 custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.ClaimsProvider.feature create mode 100644 custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.EventReceiver.cs create mode 100644 custom-claims-provider-samples/CustomClaimsProvider/Package/Package.Template.xml create mode 100644 custom-claims-provider-samples/CustomClaimsProvider/Package/Package.package create mode 100644 custom-claims-provider-samples/CustomClaimsProvider/Properties/AssemblyInfo.cs create mode 100644 custom-claims-provider-samples/CustomClaimsProvider/README.md create mode 100644 custom-claims-provider-samples/README.md create mode 100644 graph-requests-samples/.env.example create mode 100644 graph-requests-samples/README.md create mode 100644 graph-requests-samples/Search/replay search users and groups using batch.bru create mode 100644 graph-requests-samples/augmentation/1- get user by userprincipalname.bru create mode 100644 graph-requests-samples/augmentation/2- get group membership.bru create mode 100644 graph-requests-samples/authentication/get access token.bru create mode 100644 graph-requests-samples/bruno.json create mode 100644 graph-requests-samples/collection.bru create mode 100644 graph-requests-samples/environments/entracp-environment.bru create mode 100644 graph-requests-samples/search/list members of a group.bru create mode 100644 graph-requests-samples/search/replay search users and groups using batch.bru create mode 100644 graph-requests-samples/search/run a batch request.bru create mode 100644 graph-requests-samples/search/search groups.bru create mode 100644 graph-requests-samples/search/search users copy.bru create mode 100644 graph-requests-samples/search/search users.bru create mode 100644 graph-requests-samples/search/show a group.bru create mode 100644 graph-requests-samples/unit tests/extension attribute/extension properties/list extension properties.bru create mode 100644 graph-requests-samples/unit tests/extension attribute/service principal policies/assign a claimsmappingpolicy to a service principal.bru create mode 100644 graph-requests-samples/unit tests/extension attribute/service principal policies/list claimsmappingpolicies assigned to a service principal copy 2.bru create mode 100644 graph-requests-samples/unit tests/extension attribute/tenant policies/create a claimsmappingpolicy for saml claims copy.bru create mode 100644 graph-requests-samples/unit tests/extension attribute/tenant policies/delete a claimsmappingpolicy.bru create mode 100644 graph-requests-samples/unit tests/extension attribute/tenant policies/list claimsmappingpolicies in the tenant.bru create mode 100644 graph-requests-samples/unit tests/extension attribute/tenant policies/update a claimsmappingpolicy for saml claims copy.bru create mode 100644 graph-requests-samples/unit tests/extension attribute/users/list users with their extension attribute value.bru create mode 100644 graph-requests-samples/unit tests/extension attribute/users/update user to set the extension attribute value.bru create mode 100644 graph-requests-samples/unit tests/list test groups.bru create mode 100644 graph-requests-samples/unit tests/list test users.bru create mode 100644 graph-requests-samples/unit tests/show group by id.bru create mode 100644 graph-requests-samples/unit tests/show group members.bru create mode 100644 graph-requests-samples/validation/validate group.bru create mode 100644 graph-requests-samples/validation/validate user.bru diff --git a/.github/workflows/verify-prs-and-commits.yml b/.github/workflows/build.yml similarity index 76% rename from .github/workflows/verify-prs-and-commits.yml rename to .github/workflows/build.yml index 6a223cdb..eeebe267 100644 --- a/.github/workflows/verify-prs-and-commits.yml +++ b/.github/workflows/build.yml @@ -1,11 +1,13 @@ -name: Verify PRs and commits +name: Build solution on: workflow_dispatch: push: - branches: [ "master", "dev" ] + branches: + - dev + - 'releases/**' pull_request: - branches: [ "master", "dev" ] + branches: [ "dev", "master", "releases/**" ] jobs: call-build: diff --git a/.github/workflows/prepare-dtl-env.yml b/.github/workflows/prepare-dtl-env.yml index 24fc868f..18d4d3c0 100644 --- a/.github/workflows/prepare-dtl-env.yml +++ b/.github/workflows/prepare-dtl-env.yml @@ -32,12 +32,14 @@ jobs: dtl-env-addAzureBastion: ${{ vars.dtl_addAzureBastion == 'true' }} test-prepareSharePointVmScriptUris: ${{ vars.DTL_PrepareSharePointVmScriptUris }} test-claimsProviderPackageUri: ${{ vars.DTL_ClaimsProviderPackageUri }} - unittestfiles-storageAccountSourceRelativePath: ${{ vars.unittestfiles_storageAccountSourceRelativePath }} - test_registerRunner_scriptUrl: ${{ vars.test_registerRunner_scriptUrl }} + unittestfiles_azure_storage_share_relative_path: ${{ vars.unittestfiles_azure_storage_share_relative_path }} + test_registerRunner_scriptUrl: "" secrets: - AZURE_CREDENTIALS: ${{ secrets.AZURE_CREDENTIALS }} - azdevops-pat-registerAgent: ${{ secrets.dtl_azdevopsPassword }} - dtl-env-accountsPassword: ${{ secrets.dtl_accountsPassword }} - unittestfiles-storageAccountEndpoint: ${{ secrets.unittestfiles_storageAccountEndpoint }} - unittestfiles-storageAccountShareName: ${{ secrets.unittestfiles_storageAccountShareName }} - GH_TOKEN_ADD_RUNNER: ${{ secrets.GH_TOKEN_ADD_RUNNER }} + AZURE_CLIENT_ID: ${{ secrets.AZURE_CLIENT_ID }} + AZURE_SUBSCRIPTION_ID: ${{ secrets.AZURE_SUBSCRIPTION_ID }} + AZURE_TENANT_ID: ${{ secrets.AZURE_TENANT_ID }} + ADO_PAT_REGISTERAGENT: ${{ secrets.ADO_PAT_REGISTERAGENT }} + DTL_ACCOUNTSPASSWORD: ${{ secrets.DTL_ACCOUNTSPASSWORD }} + UNITTESTFILES_AZURE_STORAGE_CONNECTION_STRING: ${{ secrets.UNITTESTFILES_AZURE_STORAGE_CONNECTION_STRING }} + UNITTESTFILES_AZURE_STORAGE_SHARE_NAME: ${{ secrets.UNITTESTFILES_AZURE_STORAGE_SHARE_NAME }} + GH_TOKEN_ADD_RUNNER: "" diff --git a/.github/workflows/stale.yaml b/.github/workflows/stale.yml similarity index 98% rename from .github/workflows/stale.yaml rename to .github/workflows/stale.yml index 29b8e145..7f441b35 100644 --- a/.github/workflows/stale.yaml +++ b/.github/workflows/stale.yml @@ -5,7 +5,6 @@ on: permissions: issues: write pull-requests: write - actions: write jobs: stale: diff --git a/CHANGELOG.md b/CHANGELOG.md index ed8d3261..12de5993 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change log for ~~AzureCP~~ EntraCP +## EntraCP v27.0 - enhancements & bug-fixes - Published in August 21, 2024 + +* Ensure that restrict searchable users feature works for all members, instead of only 100 members maximum - https://github.com/Yvand/EntraCP/issues/264 +* Update the script that provisions tenant with test users and groups, to be more reliable and provision 999 users (instead of 50), so tests are more realistics +* Improve tests +* Publish a sample project that developers can use to create a custom version of EntraCP, for specific needs +* Add a [Bruno](https://www.usebruno.com/) collection to replay the requests sent to Microsoft Graph by EntraCP + ## EntraCP v26.0.20240627.35 enhancements & bug-fixes - Published in June 27, 2024 * Fix an NullReferenceException in a very rare scenario where ClaimsPrincipal.Identity is null diff --git a/Yvand.EntraCP.Tests/BasicConfigurationTests.cs b/Yvand.EntraCP.Tests/BasicConfigurationTests.cs index 433603fe..d940bc40 100644 --- a/Yvand.EntraCP.Tests/BasicConfigurationTests.cs +++ b/Yvand.EntraCP.Tests/BasicConfigurationTests.cs @@ -1,6 +1,5 @@ using NUnit.Framework; using System; -using System.Linq; namespace Yvand.EntraClaimsProvider.Tests { @@ -20,32 +19,33 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestGroupsSource), nameof(EntraIdTestGroupsSource.GetTestData), new object[] { true })] - public void TestAllTestGroups(EntraIdTestGroup group) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) { - TestSearchAndValidateForEntraIDGroup(group); + TestSearchAndValidateForTestGroup(group); } - [Test] - public void TestRandomTestGroups([Random(0, UnitTestsHelper.TestGroupsCount - 1, 5)] int idx) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeUsers), new object[] { TestEntitySourceManager.MaxNumberOfUsersToTest })] + public void TestUsers(TestUser user) { - EntraIdTestGroup group = EntraIdTestGroupsSource.Groups[idx]; - TestSearchAndValidateForEntraIDGroup(group); - } - - [Test, TestCaseSource(typeof(EntraIdTestUsersSource), nameof(EntraIdTestUsersSource.GetTestData), null)] - public void TestAllTestUsers(EntraIdTestUser user) - { - base.TestSearchAndValidateForEntraIDUser(user); + base.TestSearchAndValidateForTestUser(user); + base.TestAugmentationAgainst1RandomGroup(user); } [Test] - public void TestRandomTestUsers([Random(0, UnitTestsHelper.TestUsersCount - 1, 5)] int idx) + public void TestAGuestUser() { - var user = EntraIdTestUsersSource.Users[idx]; - base.TestSearchAndValidateForEntraIDUser(user); + TestUser user = TestEntitySourceManager.GetOneUser(UserType.Guest); + base.TestSearchAndValidateForTestUser(user); } + //[Test] + //public void TestRandomUsers([Random(0, UnitTestsHelper.TotalNumberTestUsers - 1, 5)] int idx) + //{ + // var user = EntraIdTestUsersSource.Users[idx]; + // base.TestSearchAndValidateForTestUser(user); + //} + [Test] [Repeat(5)] public override void TestAugmentationOfGoldUsersAgainstRandomGroups() @@ -55,11 +55,12 @@ public override void TestAugmentationOfGoldUsersAgainstRandomGroups() #if DEBUG [TestCase("testEntraCPUser_001")] - [TestCase("testEntraCPUser_020")] + [TestCase("testEntraCPUser_326")] public void DebugTestUser(string upnPrefix) { - EntraIdTestUser user = EntraIdTestUsersSource.Users.Find(x => x.UserPrincipalName.StartsWith(upnPrefix)); - base.TestSearchAndValidateForEntraIDUser(user); + TestUser user = TestEntitySourceManager.FindUser(upnPrefix); + base.TestSearchAndValidateForTestUser(user); + base.TestAugmentationAgainst1RandomGroup(user); } [TestCase(@"testentracp", 30, "")] diff --git a/Yvand.EntraCP.Tests/BypassDirectoryTests.cs b/Yvand.EntraCP.Tests/BypassDirectoryTests.cs index 474cf070..3d2463ad 100644 --- a/Yvand.EntraCP.Tests/BypassDirectoryTests.cs +++ b/Yvand.EntraCP.Tests/BypassDirectoryTests.cs @@ -1,7 +1,6 @@ using Microsoft.SharePoint.Administration.Claims; using NUnit.Framework; using System.Security.Claims; -using Yvand.EntraClaimsProvider.Configuration; namespace Yvand.EntraClaimsProvider.Tests { @@ -9,8 +8,8 @@ namespace Yvand.EntraClaimsProvider.Tests [Parallelizable(ParallelScope.Children)] public class BypassDirectoryOnClaimTypesTests : ClaimsProviderTestsBase { - string PrefixBypassUserSearch = "bypass-user:"; - string PrefixBypassGroupSearch = "bypass-group:"; + const string PrefixBypassUserSearch = "bypass-user:"; + const string PrefixBypassGroupSearch = "bypass-group:"; public override void InitializeSettings() { base.InitializeSettings(); @@ -26,28 +25,28 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestUsersSource), nameof(EntraIdTestUsersSource.GetTestData), null)] - public void TestAllEntraIDUsers(EntraIdTestUser user) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeUsers), new object[] { TestEntitySourceManager.MaxNumberOfUsersToTest })] + public void TestUsers(TestUser user) { - base.TestSearchAndValidateForEntraIDUser(user); + base.TestSearchAndValidateForTestUser(user); user.UserPrincipalName = user.DisplayName; user.Mail = user.DisplayName; user.DisplayName = $"{PrefixBypassUserSearch}{user.DisplayName}"; - base.TestSearchAndValidateForEntraIDUser(user); + base.TestSearchAndValidateForTestUser(user); } - [Test, TestCaseSource(typeof(EntraIdTestGroupsSource), nameof(EntraIdTestGroupsSource.GetTestData), new object[] { true })] - public void TestAllEntraIDGroups(EntraIdTestGroup group) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) { - TestSearchAndValidateForEntraIDGroup(group); + TestSearchAndValidateForTestGroup(group); group.Id = group.DisplayName; group.DisplayName = $"{PrefixBypassGroupSearch}{group.DisplayName}"; - TestSearchAndValidateForEntraIDGroup(group); + TestSearchAndValidateForTestGroup(group); } - [TestCase("bypass-user:externalUser@contoso.com", 1, "externalUser@contoso.com")] - [TestCase("bypass-user:", 0, "")] - [TestCase("bypass-group:", 0, "")] + [TestCase(PrefixBypassUserSearch + "externalUser@contoso.com", 1, "externalUser@contoso.com")] + [TestCase(PrefixBypassUserSearch, 0, "")] + [TestCase(PrefixBypassGroupSearch, 0, "")] public void TestBypassDirectoryByClaimType(string inputValue, int expectedCount, string expectedClaimValue) { TestSearchOperation(inputValue, expectedCount, expectedClaimValue); @@ -77,16 +76,16 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestGroupsSource), nameof(EntraIdTestGroupsSource.GetTestData), new object[] { true })] - public void TestAllEntraIDGroups(EntraIdTestGroup group) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) { - TestSearchAndValidateForEntraIDGroup(group); + TestSearchAndValidateForTestGroup(group); } - [Test, TestCaseSource(typeof(EntraIdTestUsersSource), nameof(EntraIdTestUsersSource.GetTestData), null)] - public void TestAllEntraIDUsers(EntraIdTestUser user) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeUsers), new object[] { TestEntitySourceManager.MaxNumberOfUsersToTest })] + public void TestUsers(TestUser user) { - base.TestSearchAndValidateForEntraIDUser(user); + base.TestSearchAndValidateForTestUser(user); } [Test] diff --git a/Yvand.EntraCP.Tests/ClaimsProviderTestsBase.cs b/Yvand.EntraCP.Tests/ClaimsProviderTestsBase.cs index 8492a7a5..dd893ba9 100644 --- a/Yvand.EntraCP.Tests/ClaimsProviderTestsBase.cs +++ b/Yvand.EntraCP.Tests/ClaimsProviderTestsBase.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Security.Claims; using System.Text; @@ -47,35 +48,34 @@ public string GroupIdentifierClaimType protected EntraIDProviderSettings Settings = new EntraIDProviderSettings(); - private object _LockVerifyIfCurrentUserShouldBeFound = new object(); private object _LockInitGroupsWhichUsersMustBeMemberOfAny = new object(); - private List _GroupsWhichUsersMustBeMemberOfAny; - protected List GroupsWhichUsersMustBeMemberOfAny + private bool GroupsWhichUsersMustBeMemberOfAnyReady = false; + private List _GroupsWhichUsersMustBeMemberOfAny = new List(); + protected List GroupsWhichUsersMustBeMemberOfAny { get { - if (_GroupsWhichUsersMustBeMemberOfAny != null) { return _GroupsWhichUsersMustBeMemberOfAny; } + if (GroupsWhichUsersMustBeMemberOfAnyReady) { return _GroupsWhichUsersMustBeMemberOfAny; } lock (_LockInitGroupsWhichUsersMustBeMemberOfAny) { - if (_GroupsWhichUsersMustBeMemberOfAny != null) { return _GroupsWhichUsersMustBeMemberOfAny; } - _GroupsWhichUsersMustBeMemberOfAny = new List(); + if (GroupsWhichUsersMustBeMemberOfAnyReady) { return _GroupsWhichUsersMustBeMemberOfAny; } string groupsWhichUsersMustBeMemberOfAny = Settings.RestrictSearchableUsersByGroups; if (!String.IsNullOrWhiteSpace(groupsWhichUsersMustBeMemberOfAny)) { string[] groupIds = groupsWhichUsersMustBeMemberOfAny.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); foreach (string groupId in groupIds) { - EntraIdTestGroupSettings groupSettings = EntraIdTestGroupsSource.GroupsSettings.FirstOrDefault(x => x.Id == groupId); - if (groupSettings == null) { groupSettings = new EntraIdTestGroupSettings(); } - _GroupsWhichUsersMustBeMemberOfAny.Add(groupSettings); + TestGroup group = TestEntitySourceManager.AllTestGroups.First(x => x.Id == groupId); + _GroupsWhichUsersMustBeMemberOfAny.Add(group); } } + GroupsWhichUsersMustBeMemberOfAnyReady = true; + Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] Initialized List of {nameof(GroupsWhichUsersMustBeMemberOfAny)} with {GroupsWhichUsersMustBeMemberOfAny.Count} items: {String.Join(", ", GroupsWhichUsersMustBeMemberOfAny.Select(x => x.Id).ToArray())}"); return _GroupsWhichUsersMustBeMemberOfAny; } } } - /// /// Initialize settings /// @@ -90,7 +90,9 @@ public virtual void InitializeSettings() Settings.Timeout = 99999; #endif - Settings.EntraIDTenants = new List { UnitTestsHelper.TenantConnection }; + string json = File.ReadAllText(UnitTestsHelper.AzureTenantsJsonFile); + List azureTenants = JsonConvert.DeserializeObject>(json); + Settings.EntraIDTenants = azureTenants; foreach (EntraIDTenant tenant in Settings.EntraIDTenants) { tenant.ExcludeMemberUsers = ExcludeMemberUsers; @@ -116,28 +118,12 @@ public virtual void CheckSettingsTest() } } - public void TestSearchAndValidateForEntraIDGroup(EntraIdTestGroup entity) - { - string inputValue = entity.DisplayName; - int expectedCount = 1; - bool shouldValidate = true; - - if (Settings.AlwaysResolveUserInput) - { - inputValue = entity.Id; - expectedCount = Settings.ClaimTypes.GetConfigsMappedToClaimType().Count(); - } - if (Settings.FilterSecurityEnabledGroupsOnly && entity.SecurityEnabled == false) - { - expectedCount = 0; - shouldValidate = false; - } - - TestSearchOperation(inputValue, expectedCount, entity.Id); - TestValidationOperation(GroupIdentifierClaimType, entity.Id, shouldValidate); - } - - public void TestSearchAndValidateForEntraIDUser(EntraIdTestUser entity) + /// + /// Tests the search and validation operations for the user specified and against the current configuration. + /// The property DisplayName is used as the people picker input + /// + /// + public void TestSearchAndValidateForTestUser(TestUser entity) { int expectedCount = 1; string inputValue = entity.DisplayName; @@ -146,46 +132,31 @@ public void TestSearchAndValidateForEntraIDUser(EntraIdTestUser entity) if (Settings.AlwaysResolveUserInput) { - inputValue = entity.UserPrincipalName; - claimValue = entity.UserPrincipalName; + claimValue = inputValue; expectedCount = Settings.ClaimTypes.GetConfigsMappedToClaimType().Count(); } else { if (!String.IsNullOrWhiteSpace(Settings.RestrictSearchableUsersByGroups)) { - lock (_LockVerifyIfCurrentUserShouldBeFound) // TODO: understand why this lock is necessary - { - // Test 1: Does Settings.RestrictSearchableUsersByGroups contain any group where all test users are members? - bool groupWithAllTestUsersAreMembersFound = false; - foreach (var groupSettings in GroupsWhichUsersMustBeMemberOfAny) - { - if (groupSettings.AllTestUsersAreMembers) - { - groupWithAllTestUsersAreMembersFound = true; - Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] User \"{entity.UserPrincipalName}\" may be found because Settings.RestrictSearchableUsersByGroups contains group: \"{groupSettings.DisplayName}\" with AllTestUsersAreMembers {groupSettings.AllTestUsersAreMembers}."); - break; // No need to change shouldValidate, which is true by default, or process other groups - } - } + // Test 1: Does Settings.RestrictSearchableUsersByGroups contain any group where all test users are members? + bool groupEveryoneIsMemberOfFound = GroupsWhichUsersMustBeMemberOfAny.Any(x => x.EveryoneIsMember == true); + Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] User \"{entity.UserPrincipalName}\" may be found because at least 1 of the groups in Settings.RestrictSearchableUsersByGroups has property EveryoneIsMember true."); - // Test 2: If test 1 is false, is current entity member of all the test groups? - if (!groupWithAllTestUsersAreMembersFound) + // Test 2: If test 1 is false, is current entity member of all the test groups? + if (!groupEveryoneIsMemberOfFound) + { + if (!entity.IsMemberOfAllGroups) { - - EntraIdTestUserSettings userSettings = EntraIdTestUsersSource.UsersWithSpecificSettings.FirstOrDefault(x => String.Equals(x.UserPrincipalName, entity.UserPrincipalName, StringComparison.InvariantCultureIgnoreCase)); - if (userSettings == null) { userSettings = new EntraIdTestUserSettings(); } - if (!userSettings.IsMemberOfAllGroups) - { - shouldValidate = false; - expectedCount = 0; - Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] User \"{entity.UserPrincipalName}\" should not be found because it has IsMemberOfAllGroups {userSettings.IsMemberOfAllGroups} and no group set in Settings.RestrictSearchableUsersByGroups has AllTestUsersAreMembers set to true."); - } + shouldValidate = false; + expectedCount = 0; + Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] User \"{entity.UserPrincipalName}\" should not be found because it has IsMemberOfAllGroups {entity.IsMemberOfAllGroups} and no group set in Settings.RestrictSearchableUsersByGroups has AllTestUsersAreMembers set to true."); } } } else { - Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] Property Settings.RestrictSearchableUsersByGroups IsNullOrWhiteSpace."); + Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] Property Settings.RestrictSearchableUsersByGroups is not set."); } // If shouldValidate is false, user should not be found anyway so no need to do additional checks @@ -214,32 +185,55 @@ public void TestSearchAndValidateForEntraIDUser(EntraIdTestUser entity) } /// - /// Gold users are the test users who are members of all the test groups + /// Tests the search and validation operations for the group specified and against the current configuration. + /// The property DisplayName is used as the people picker input /// - public virtual void TestAugmentationOfGoldUsersAgainstRandomGroups() + /// + public void TestSearchAndValidateForTestGroup(TestGroup entity) { - Random rnd = new Random(); - int randomIdx = rnd.Next(0, UnitTestsHelper.TestGroupsCount - 1); - Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] TestAugmentationOfGoldUsersAgainstRandomGroups: Get group in EntraIdTestGroupsSource.Groups at index {randomIdx}."); - EntraIdTestGroup randomGroup = null; - try + string inputValue = entity.DisplayName; + string claimValue = entity.Id; + int expectedCount = 1; + bool shouldValidate = true; + + if (Settings.AlwaysResolveUserInput) { - randomGroup = EntraIdTestGroupsSource.Groups[randomIdx]; + claimValue = inputValue; + expectedCount = Settings.ClaimTypes.GetConfigsMappedToClaimType().Count(); } - catch (ArgumentOutOfRangeException) + if (Settings.FilterSecurityEnabledGroupsOnly && entity.SecurityEnabled == false) { - string errorMessage = $"{DateTime.Now:s} [{this.GetType().Name}] TestAugmentationOfGoldUsersAgainstRandomGroups: Could not get group in EntraIdTestGroupsSource.Groups at index {randomIdx}. EntraIdTestGroupsSource.Groups has {EntraIdTestGroupsSource.Groups.Count} items."; - Trace.TraceError(errorMessage); - throw new ArgumentOutOfRangeException(errorMessage); + expectedCount = 0; + shouldValidate = false; } - bool shouldBeMember = Settings.FilterSecurityEnabledGroupsOnly && !randomGroup.SecurityEnabled ? false : true; - foreach (string userPrincipalName in EntraIdTestUsersSource.UsersWithSpecificSettings.Where(x => x.IsMemberOfAllGroups).Select(x => x.UserPrincipalName)) + TestSearchOperation(inputValue, expectedCount, claimValue); + TestValidationOperation(GroupIdentifierClaimType, claimValue, shouldValidate); + } + + /// + /// Gold users are the test users who are members of all the test groups + /// + public virtual void TestAugmentationOfGoldUsersAgainstRandomGroups() + { + foreach (TestUser user in TestEntitySourceManager.GetUsersMembersOfAllGroups()) { - TestAugmentationOperation(userPrincipalName, shouldBeMember, randomGroup.Id); + TestAugmentationAgainst1RandomGroup(user); } } + /// + /// Pick a random group, and check if the claims provider returns the expected membership (should or should not be member) for this group + /// + /// + public void TestAugmentationAgainst1RandomGroup(TestUser user) + { + TestGroup randomGroup = TestEntitySourceManager.GetOneGroup(Settings.FilterSecurityEnabledGroupsOnly); + bool userShouldBeMember = user.IsMemberOfAllGroups || randomGroup.EveryoneIsMember ? true : false; + Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] TestAugmentationAgainst1RandomGroup for user \"{user.UserPrincipalName}\", IsMemberOfAllGroupsp: {user.IsMemberOfAllGroups} against group \"{randomGroup.DisplayName}\". userShouldBeMember: {userShouldBeMember}"); + TestAugmentationOperation(user.UserPrincipalName, userShouldBeMember, randomGroup.Id); + } + /// /// Applies the to the configuration object and save it in the configuration database /// @@ -351,10 +345,11 @@ protected void TestValidationOperation(SPClaim inputClaim, bool shouldValidate, /// /// /// - protected void TestAugmentationOperation(string claimValue, bool isMemberOfTrustedGroup, string groupNameToTestInGroupMembership) + /// + protected void TestAugmentationOperation(string claimValue, bool isMemberOfTrustedGroup, string groupClaimValueToTest) { string claimType = UserIdentifierClaimType; - SPClaim groupClaimToTestInGroupMembership = new SPClaim(GroupIdentifierClaimType, groupNameToTestInGroupMembership, ClaimValueTypes.String, SPOriginalIssuers.Format(SPOriginalIssuerType.TrustedProvider, UnitTestsHelper.SPTrust.Name)); + SPClaim groupClaimToTestInGroupMembership = new SPClaim(GroupIdentifierClaimType, groupClaimValueToTest, ClaimValueTypes.String, SPOriginalIssuers.Format(SPOriginalIssuerType.TrustedProvider, UnitTestsHelper.SPTrust.Name)); try { Stopwatch timer = new Stopwatch(); @@ -372,11 +367,11 @@ protected void TestAugmentationOperation(string claimValue, bool isMemberOfTrust if (isMemberOfTrustedGroup) { - Assert.That(groupFound, Is.True, $"Entity \"{claimValue}\" should be member of group \"{groupNameToTestInGroupMembership}\", but this group was not found in the claims returned by the claims provider."); + Assert.That(groupFound, Is.True, $"Entity \"{claimValue}\" should be member of group \"{groupClaimValueToTest}\", but this group was not found in the claims returned by the claims provider."); } else { - Assert.That(groupFound, Is.False, $"Entity \"{claimValue}\" should NOT be member of group \"{groupNameToTestInGroupMembership}\", but this group was found in the claims returned by the claims provider."); + Assert.That(groupFound, Is.False, $"Entity \"{claimValue}\" should NOT be member of group \"{groupClaimValueToTest}\", but this group was found in the claims returned by the claims provider."); } timer.Stop(); Trace.TraceInformation($"{DateTime.Now:s} TestAugmentationOperation finished in {timer.ElapsedMilliseconds} ms. Parameters: claimType: '{claimType}', claimValue: '{claimValue}', isMemberOfTrustedGroup: '{isMemberOfTrustedGroup}'."); diff --git a/Yvand.EntraCP.Tests/ExcludeAUserTypeTests.cs b/Yvand.EntraCP.Tests/ExcludeAUserTypeTests.cs index 1b1b07d8..f1f1602e 100644 --- a/Yvand.EntraCP.Tests/ExcludeAUserTypeTests.cs +++ b/Yvand.EntraCP.Tests/ExcludeAUserTypeTests.cs @@ -21,16 +21,16 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestGroupsSource), nameof(EntraIdTestGroupsSource.GetTestData), new object[] { true })] - public void TestAllEntraIDGroups(EntraIdTestGroup group) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) { - TestSearchAndValidateForEntraIDGroup(group); + TestSearchAndValidateForTestGroup(group); } - [Test, TestCaseSource(typeof(EntraIdTestUsersSource), nameof(EntraIdTestUsersSource.GetTestData), null)] - public void TestAllEntraIDUsers(EntraIdTestUser user) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeUsers), new object[] { TestEntitySourceManager.MaxNumberOfUsersToTest })] + public void TestUsers(TestUser user) { - base.TestSearchAndValidateForEntraIDUser(user); + base.TestSearchAndValidateForTestUser(user); } [Test] @@ -60,16 +60,16 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestGroupsSource), nameof(EntraIdTestGroupsSource.GetTestData), new object[] { true })] - public void TestAllEntraIDGroups(EntraIdTestGroup group) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) { - TestSearchAndValidateForEntraIDGroup(group); + TestSearchAndValidateForTestGroup(group); } - [Test, TestCaseSource(typeof(EntraIdTestUsersSource), nameof(EntraIdTestUsersSource.GetTestData), null)] - public void TestAllEntraIDUsers(EntraIdTestUser user) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeUsers), new object[] { TestEntitySourceManager.MaxNumberOfUsersToTest })] + public void TestUsers(TestUser user) { - base.TestSearchAndValidateForEntraIDUser(user); + base.TestSearchAndValidateForTestUser(user); } [Test] @@ -99,16 +99,16 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestGroupsSource), nameof(EntraIdTestGroupsSource.GetTestData), new object[] { true })] - public void TestAllEntraIDGroups(EntraIdTestGroup group) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) { - TestSearchAndValidateForEntraIDGroup(group); + TestSearchAndValidateForTestGroup(group); } - [Test, TestCaseSource(typeof(EntraIdTestUsersSource), nameof(EntraIdTestUsersSource.GetTestData), null)] - public void TestAllEntraIDUsers(EntraIdTestUser user) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeUsers), new object[] { TestEntitySourceManager.MaxNumberOfUsersToTest })] + public void TestUsers(TestUser user) { - base.TestSearchAndValidateForEntraIDUser(user); + base.TestSearchAndValidateForTestUser(user); } [Test] diff --git a/Yvand.EntraCP.Tests/ExtensionAttributeTests.cs b/Yvand.EntraCP.Tests/ExtensionAttributeTests.cs index 79686cfa..a3c47378 100644 --- a/Yvand.EntraCP.Tests/ExtensionAttributeTests.cs +++ b/Yvand.EntraCP.Tests/ExtensionAttributeTests.cs @@ -13,7 +13,7 @@ public override void InitializeSettings() ClaimTypeConfig ctConfigExtensionAttribute = new ClaimTypeConfig { ClaimType = TestContext.Parameters["MultiPurposeCustomClaimType"], - ClaimTypeDisplayName = "extattr1", + ClaimTypeDisplayName = "extrattr1", EntityProperty = DirectoryObjectProperty.extensionAttribute1, EntityType = DirectoryObjectType.User, SharePointEntityType = ClaimsProviderConstants.GroupClaimEntityType, diff --git a/Yvand.EntraCP.Tests/FilterUsersBasedOnGroupsTests.cs b/Yvand.EntraCP.Tests/FilterUsersBasedOnGroupsTests.cs index 0b2a49a1..693df47d 100644 --- a/Yvand.EntraCP.Tests/FilterUsersBasedOnGroupsTests.cs +++ b/Yvand.EntraCP.Tests/FilterUsersBasedOnGroupsTests.cs @@ -1,7 +1,7 @@ using NUnit.Framework; using System; -using System.Collections.Generic; using System.Diagnostics; +using System.Linq; namespace Yvand.EntraClaimsProvider.Tests { @@ -12,7 +12,7 @@ public class FilterUsersBasedOnSingleGroupTests : ClaimsProviderTestsBase public override void InitializeSettings() { base.InitializeSettings(); - Settings.RestrictSearchableUsersByGroups = EntraIdTestGroupsSource.ASecurityEnabledGroup.Id; + Settings.RestrictSearchableUsersByGroups = TestEntitySourceManager.GetOneGroup(true).Id; base.ApplySettings(); } @@ -22,10 +22,24 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestUsersSource), nameof(EntraIdTestUsersSource.GetTestData), null)] - public void TestAllTestUsers(EntraIdTestUser user) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeUsers), new object[] { TestEntitySourceManager.MaxNumberOfUsersToTest })] + public void TestUsers(TestUser user) { - base.TestSearchAndValidateForEntraIDUser(user); + base.TestSearchAndValidateForTestUser(user); + base.TestAugmentationAgainst1RandomGroup(user); + } + + [Test] + public void TestAGuestUser() + { + TestUser user = TestEntitySourceManager.GetOneUser(UserType.Guest); + base.TestSearchAndValidateForTestUser(user); + } + + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) + { + TestSearchAndValidateForTestGroup(group); } #if DEBUG @@ -33,8 +47,8 @@ public void TestAllTestUsers(EntraIdTestUser user) [TestCase("testEntraCPUser_020")] public void DebugTestUser(string upnPrefix) { - EntraIdTestUser user = EntraIdTestUsersSource.Users.Find(x => x.UserPrincipalName.StartsWith(upnPrefix)); - base.TestSearchAndValidateForEntraIDUser(user); + TestUser user = TestEntitySourceManager.FindUser(upnPrefix); + base.TestSearchAndValidateForTestUser(user); } #endif } @@ -47,17 +61,11 @@ public override void InitializeSettings() { base.InitializeSettings(); - // Pick the Id of 18 (max possible) random groups, and it in property RestrictSearchableUsersByGroups - List groupIdsList = new List(); - Random rnd = new Random(); - for (int groupsCount = 1; groupsCount <= 18; groupsCount++) - { - int randomIdx = rnd.Next(0, UnitTestsHelper.TestGroupsCount - 1); - groupIdsList.Add(EntraIdTestGroupsSource.Groups[randomIdx].Id); - } - Settings.RestrictSearchableUsersByGroups = String.Join(",", groupIdsList); + // Pick the Id of 18 (max possible) random groups, and set them in property RestrictSearchableUsersByGroups + Settings.RestrictSearchableUsersByGroups = String.Join(",", TestEntitySourceManager.GetSomeGroups(18, true).Select(x => x.Id).ToArray()); + //Settings.RestrictSearchableUsersByGroups = "3c1c6c1a-2565-4cfd-b5f8-8ec732f93077,3c98541c-9601-47c0-aeea-fc0679b9d756,807c95cd-88de-49d9-a06e-12ce2329dfb7,807c95cd-88de-49d9-a06e-12ce2329dfb7,1beb24dd-0fae-46cb-b321-dd0baf5c9ecc,01572e9f-4a9a-4dd1-9314-05972d87d1c2,89d4f192-8eb0-4011-ada7-4a1d4f678b1c,bdd53ff1-866c-442b-b6d5-ac43b4306aa7,2d407401-192c-4a25-9f0e-3693cfad6f27,1c607c55-f1a0-408c-ae52-306cd89de742,1090383f-7ea5-4a16-9ba8-0551a061d7f9,874b1dcf-aa82-428a-b107-b71a09c3d452,1090383f-7ea5-4a16-9ba8-0551a061d7f9,1831bd90-e413-4b86-a8ab-5d26d8a75498,04ec1e1c-196d-4b85-85b2-c3b982644114,043e997e-0b2c-412c-b8f0-13253251569c,40d53a73-130b-48e1-946b-5fec5ec35d4f,3c98541c-9601-47c0-aeea-fc0679b9d756"; + //Settings.RestrictSearchableUsersByGroups = "3c98541c-9601-47c0-aeea-fc0679b9d756"; Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] Set property RestrictSearchableUsersByGroups: \"{Settings.RestrictSearchableUsersByGroups}\"."); - base.ApplySettings(); } @@ -67,10 +75,24 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestUsersSource), nameof(EntraIdTestUsersSource.GetTestData), null)] - public void TestAllTestUsers(EntraIdTestUser user) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeUsers), new object[] { TestEntitySourceManager.MaxNumberOfUsersToTest })] + public void TestUsers(TestUser user) + { + base.TestSearchAndValidateForTestUser(user); + base.TestAugmentationAgainst1RandomGroup(user); + } + + [Test] + public void TestAGuestUser() + { + TestUser user = TestEntitySourceManager.GetOneUser(UserType.Guest); + base.TestSearchAndValidateForTestUser(user); + } + + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) { - base.TestSearchAndValidateForEntraIDUser(user); + TestSearchAndValidateForTestGroup(group); } #if DEBUG @@ -78,13 +100,12 @@ public void TestAllTestUsers(EntraIdTestUser user) [TestCase("testEntraCPUser_020")] public void DebugTestUser(string upnPrefix) { - EntraIdTestUser user = EntraIdTestUsersSource.Users.Find(x => x.UserPrincipalName.StartsWith(upnPrefix)); - base.TestSearchAndValidateForEntraIDUser(user); + TestUser user = TestEntitySourceManager.FindUser(upnPrefix); + base.TestSearchAndValidateForTestUser(user); } #endif } -#if DEBUG [TestFixture] [Parallelizable(ParallelScope.Children)] public class DebugFilterUsersBasedOnMultipleGroupsTests : ClaimsProviderTestsBase @@ -92,27 +113,37 @@ public class DebugFilterUsersBasedOnMultipleGroupsTests : ClaimsProviderTestsBas public override void InitializeSettings() { base.InitializeSettings(); - Settings.RestrictSearchableUsersByGroups = "dbcdaf68-5949-4a15-b2f8-f385b41a9fca,72860e7b-93d5-46f7-ab56-480112f76548,461e8865-0f39-4199-86c6-0c8e25ad57ea,33780f8d-8345-4402-bc6a-d9c7e5d40d36,2c6b2fde-4f89-417a-9d8c-5e459e1520cf,7059e0ae-0cbc-4f4d-9d87-fef7289a7f50,dbcdaf68-5949-4a15-b2f8-f385b41a9fca,3cc758d6-7198-470f-878a-e5cd41a35e02,b05ddc92-b639-4ba2-95b3-9fdbc1bff2f2,40a7290c-9b67-4b7c-98ff-0c9871544423,71aa60d9-c4d1-4eaf-b398-3099b12afd88,0f51d30e-e03c-44ea-8d13-df0ca0df7a16,0f51d30e-e03c-44ea-8d13-df0ca0df7a16,982369ed-e88f-4b21-9b89-29067a0fa326,a40d2ded-2ab0-463f-b000-b3351ca6341d,ce5a4725-e719-4c2d-89bc-1c356facde99,de600b84-29aa-470c-b6ca-1459591728fb,152456d1-73a0-46d6-ac02-0403f2b5593e"; - Settings.RestrictSearchableUsersByGroups = "db41d655-d796-43fb-9e23-351ee8b5bdb0,461e8865-0f39-4199-86c6-0c8e25ad57ea,21dd6198-c447-48dd-9ea8-347f804c4dec,dcf1e533-6d55-4b00-9788-f0d81e287c8a,21dd6198-c447-48dd-9ea8-347f804c4dec,719006f9-8eb0-48fd-8f95-556f07b0123b,d6896744-f16b-4802-9f13-0e2c9fa06274,dcf1e533-6d55-4b00-9788-f0d81e287c8a,2ae8ff19-0e4f-45cc-98ea-84f1c53e60f2,bea2607f-513a-4324-a6a1-620ee1c0ced4,bcd82b83-97c5-4c1c-9cce-58643a286298,34fe3af4-7ed9-4b5e-a64e-c0b230d5dfb4,136f71a2-c57c-4a3f-8aec-4d694e442b87,dbf5a5c3-5f51-42d6-a519-258c76960f75,33780f8d-8345-4402-bc6a-d9c7e5d40d36,a40d2ded-2ab0-463f-b000-b3351ca6341d,21dd6198-c447-48dd-9ea8-347f804c4dec,34fe3af4-7ed9-4b5e-a64e-c0b230d5dfb4"; - Settings.RestrictSearchableUsersByGroups = "71aa60d9-c4d1-4eaf-b398-3099b12afd88,56e7a2e2-f565-450e-94cd-0d7d314217d1,c9a94341-89b5-4109-a501-2a14027b5bf0,8962bad6-ceca-43ff-a4be-9258ff81af2f,36d78c5a-80f2-4f3d-8f37-3a347d000d56,152456d1-73a0-46d6-ac02-0403f2b5593e,d6896744-f16b-4802-9f13-0e2c9fa06274,de600b84-29aa-470c-b6ca-1459591728fb,d51f225f-b484-4898-b425-5d48553aad16,36d78c5a-80f2-4f3d-8f37-3a347d000d56,0f51d30e-e03c-44ea-8d13-df0ca0df7a16,1eb5e51e-0bea-40c3-9ffb-0b85dbe2f9bf,dcf1e533-6d55-4b00-9788-f0d81e287c8a,c9a94341-89b5-4109-a501-2a14027b5bf0,3cc758d6-7198-470f-878a-e5cd41a35e02,dcf1e533-6d55-4b00-9788-f0d81e287c8a,21dd6198-c447-48dd-9ea8-347f804c4dec,253fe6d4-8e07-49a1-8a74-143d265eefbe"; + Settings.RestrictSearchableUsersByGroups = String.Join(",", TestEntitySourceManager.GetSomeGroups(18, true).Select(x => x.Id).ToArray()); Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] Set property RestrictSearchableUsersByGroups: \"{Settings.RestrictSearchableUsersByGroups}\"."); base.ApplySettings(); } - [TestCase("testEntraCPUser_001")] - [TestCase("testEntraCPUser_020")] - public void DebugTestUser(string upnPrefix) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeUsers), new object[] { TestEntitySourceManager.MaxNumberOfUsersToTest })] + public void TestUsers(TestUser user) { - EntraIdTestUser user = EntraIdTestUsersSource.Users.Find(x => x.UserPrincipalName.StartsWith(upnPrefix)); - base.TestSearchAndValidateForEntraIDUser(user); + base.TestSearchAndValidateForTestUser(user); + base.TestAugmentationAgainst1RandomGroup(user); } [Test] - public void DebugGuestUser() + public void TestAGuestUser() + { + TestUser user = TestEntitySourceManager.GetOneUser(UserType.Guest); + base.TestSearchAndValidateForTestUser(user); + } + + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) { - EntraIdTestUser user = EntraIdTestUsersSource.Users.Find(x => x.Mail.StartsWith("testEntraCPGuestUser_001")); - base.TestSearchAndValidateForEntraIDUser(user); + TestSearchAndValidateForTestGroup(group); + } + + [TestCase("testEntraCPUser_001")] + [TestCase("testEntraCPUser_020")] + public void DebugTestUser(string upnPrefix) + { + TestUser user = TestEntitySourceManager.FindUser(upnPrefix); + base.TestSearchAndValidateForTestUser(user); } } -#endif } diff --git a/Yvand.EntraCP.Tests/GuestAccountsUPNTests.cs b/Yvand.EntraCP.Tests/GuestAccountsUPNTests.cs index b6cd6ad5..69bd4671 100644 --- a/Yvand.EntraCP.Tests/GuestAccountsUPNTests.cs +++ b/Yvand.EntraCP.Tests/GuestAccountsUPNTests.cs @@ -24,16 +24,16 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestGroupsSource), nameof(EntraIdTestGroupsSource.GetTestData), new object[] { true })] - public void TestAllEntraIDGroups(EntraIdTestGroup group) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) { - TestSearchAndValidateForEntraIDGroup(group); + TestSearchAndValidateForTestGroup(group); } - [Test, TestCaseSource(typeof(EntraIdTestUsersSource), nameof(EntraIdTestUsersSource.GetTestData), null)] - public void TestAllEntraIDUsers(EntraIdTestUser user) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeUsers), new object[] { TestEntitySourceManager.MaxNumberOfUsersToTest })] + public void TestUsers(TestUser user) { - base.TestSearchAndValidateForEntraIDUser(user); + base.TestSearchAndValidateForTestUser(user); } [Test] diff --git a/Yvand.EntraCP.Tests/RequireExactMatchTests.cs b/Yvand.EntraCP.Tests/RequireExactMatchTests.cs index 12bb5589..aa156c28 100644 --- a/Yvand.EntraCP.Tests/RequireExactMatchTests.cs +++ b/Yvand.EntraCP.Tests/RequireExactMatchTests.cs @@ -21,8 +21,8 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestUsersSource), nameof(EntraIdTestUsersSource.GetTestData), null)] - public void TestAllEntraIDUsers(EntraIdTestUser user) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeUsers), new object[] { TestEntitySourceManager.MaxNumberOfUsersToTest })] + public void TestUsers(TestUser user) { // Input is not the full UPN value: it should not return any result TestSearchOperation(user.UserPrincipalName.Substring(0, 5), 0, String.Empty); diff --git a/Yvand.EntraCP.Tests/SecurityEnabledGroupsTests.cs b/Yvand.EntraCP.Tests/SecurityEnabledGroupsTests.cs index 4b7220d1..bb8634a1 100644 --- a/Yvand.EntraCP.Tests/SecurityEnabledGroupsTests.cs +++ b/Yvand.EntraCP.Tests/SecurityEnabledGroupsTests.cs @@ -1,6 +1,4 @@ -using Microsoft.SharePoint.Administration.Claims; -using NUnit.Framework; -using System.Security.Claims; +using NUnit.Framework; using Yvand.EntraClaimsProvider.Configuration; namespace Yvand.EntraClaimsProvider.Tests @@ -24,10 +22,10 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestGroupsSource), nameof(EntraIdTestGroupsSource.GetTestData), new object[] { true })] - public void TestAllEntraIDGroups(EntraIdTestGroup group) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) { - TestSearchAndValidateForEntraIDGroup(group); + TestSearchAndValidateForTestGroup(group); } [Test] @@ -57,10 +55,10 @@ public override void CheckSettingsTest() base.CheckSettingsTest(); } - [Test, TestCaseSource(typeof(EntraIdTestGroupsSource), nameof(EntraIdTestGroupsSource.GetTestData), new object[] { true })] - public void TestAllEntraIDGroups(EntraIdTestGroup group) + [Test, TestCaseSource(typeof(TestEntitySourceManager), nameof(TestEntitySourceManager.GetSomeGroups), new object[] { TestEntitySourceManager.MaxNumberOfGroupsToTest, true })] + public void TestGroups(TestGroup group) { - TestSearchAndValidateForEntraIDGroup(group); + TestSearchAndValidateForTestGroup(group); } [Test] diff --git a/Yvand.EntraCP.Tests/Setup/Populate-EntraIDTenant.ps1 b/Yvand.EntraCP.Tests/Setup/Populate-EntraIDTenant.ps1 index 907d32ce..4733dad6 100644 --- a/Yvand.EntraCP.Tests/Setup/Populate-EntraIDTenant.ps1 +++ b/Yvand.EntraCP.Tests/Setup/Populate-EntraIDTenant.ps1 @@ -1,4 +1,4 @@ -#Requires -Modules Microsoft.Graph.Identity.SignIns, Microsoft.Graph.Users +#Requires -Modules Microsoft.Graph.Authentication, Microsoft.Graph.Identity.DirectoryManagement, Microsoft.Graph.Users, Microsoft.Graph.Groups <# .SYNOPSIS @@ -12,9 +12,14 @@ Connect-MgGraph -Scopes "User.ReadWrite.All", "Group.ReadWrite.All" -UseDeviceCode $tenantName = (Get-MgOrganization).VerifiedDomains[0].Name +$exportedUsersFullFilePath = "C:\YvanData\dev\EntraCP_Tests_Users.csv" +$exportedGroupsFullFilePath = "C:\YvanData\dev\EntraCP_Tests_Groups.csv" + $memberUsersNamePrefix = "testEntraCPUser_" $guestUsersNamePrefix = "testEntraCPGuestUser_" $groupNamePrefix = "testEntraCPGroup_" +$usersCount = 999 +$groupsCount = 50 $confirmation = Read-Host "Connected to tenant '$tenantName' and about to process users starting with '$memberUsersNamePrefix' and groups starting with '$groupNamePrefix'. Are you sure you want to proceed? [y/n]" if ($confirmation -ne 'y') { @@ -22,7 +27,6 @@ if ($confirmation -ne 'y') { return } -# Set specific attributes for some users $usersWithSpecificSettings = @( @{ UserPrincipalName = "$($memberUsersNamePrefix)001@$($tenantName)"; IsMemberOfAllGroups = $true } @{ UserPrincipalName = "$($memberUsersNamePrefix)002@$($tenantName)"; UserAttributes = @{ "GivenName" = "firstname 002" } } @@ -38,16 +42,16 @@ $guestUsersList = @( @{ Mail = "$($guestUsersNamePrefix)002@contoso.local"; Id = ""; UserPrincipalName = "" } @{ Mail = "$($guestUsersNamePrefix)003@contoso.local"; Id = ""; UserPrincipalName = "" } ) -#$guestUsers = @("$($guestUsersNamePrefix)001@contoso.local", "$($guestUsersNamePrefix)002@contoso.local", "$($guestUsersNamePrefix)003@contoso.local") +$usersMemberOfAllGroups = [System.Linq.Enumerable]::Where($usersWithSpecificSettings, [Func[object, bool]] { param($x) $x.IsMemberOfAllGroups -eq $true }) $groupsWithSpecificSettings = @( @{ GroupName = "$($groupNamePrefix)001" SecurityEnabled = $false - AllTestUsersAreMembers = $true + EveryoneIsMember = $true }, @{ GroupName = "$($groupNamePrefix)005" - AllTestUsersAreMembers = $true + EveryoneIsMember = $true }, @{ GroupName = "$($groupNamePrefix)008" @@ -56,11 +60,11 @@ $groupsWithSpecificSettings = @( @{ GroupName = "$($groupNamePrefix)018" SecurityEnabled = $false - AllTestUsersAreMembers = $true + EveryoneIsMember = $true }, @{ GroupName = "$($groupNamePrefix)025" - AllTestUsersAreMembers = $true + EveryoneIsMember = $true }, @{ GroupName = "$($groupNamePrefix)028" @@ -69,7 +73,7 @@ $groupsWithSpecificSettings = @( @{ GroupName = "$($groupNamePrefix)038" SecurityEnabled = $false - AllTestUsersAreMembers = $true + EveryoneIsMember = $true }, @{ GroupName = "$($groupNamePrefix)048" @@ -94,11 +98,11 @@ $passwordProfile = @{ } # Bulk add users -$totalUsers = 50 -for ($i = 1; $i -le $totalUsers; $i++) { +$allUsersInEntra = @() +for ($i = 1; $i -le $usersCount; $i++) { $accountName = "$($memberUsersNamePrefix)$("{0:D3}" -f $i)" $userPrincipalName = "$($accountName)@$($tenantName)" - $user = Get-MgUser -Filter "UserPrincipalName eq '$userPrincipalName'" + $user = Get-MgUser -Filter "UserPrincipalName eq '$userPrincipalName'" -Property Id, UserPrincipalName, Mail, UserType, DisplayName, GivenName if ($null -eq $user) { $additionalUserAttributes = New-Object -TypeName HashTable $userHasSpecificAttributes = [System.Linq.Enumerable]::FirstOrDefault($usersWithSpecificSettings, [Func[object, bool]] { param($x) $x.UserPrincipalName -like $userPrincipalName }) @@ -106,30 +110,28 @@ for ($i = 1; $i -le $totalUsers; $i++) { $additionalUserAttributes = $userHasSpecificAttributes.UserAttributes } - New-MgUser -DisplayName $accountName -PasswordProfile $passwordProfile -AccountEnabled -MailNickName $accountName -UserPrincipalName $userPrincipalName @additionalUserAttributes + New-MgUser -UserPrincipalName $userPrincipalName -DisplayName $accountName -PasswordProfile $passwordProfile -AccountEnabled -MailNickName $accountName @additionalUserAttributes Write-Host "Created user '$userPrincipalName'" -ForegroundColor Green + $user = Get-MgUser -Filter "UserPrincipalName eq '$userPrincipalName'" -Property Id, UserPrincipalName, Mail, UserType, DisplayName, GivenName } + $allUsersInEntra += $user } # Add the guest users foreach ($guestUser in $guestUsersList) { - $guestUserInGraph = Get-MgUser -Filter "Mail eq '$($guestUser.Mail)'" - if ($null -eq $guestUserInGraph) { + $user = Get-MgUser -Filter "Mail eq '$($guestUser.Mail)'" -Property Id, UserPrincipalName, Mail, UserType, DisplayName, GivenName + if ($null -eq $user) { $invitedUser = New-MgInvitation -InvitedUserDisplayName $guestUser.Mail -InvitedUserEmailAddress $guestUser.Mail -SendInvitationMessage:$false -InviteRedirectUrl "https://myapplications.microsoft.com" Write-Host "Invited guest user $($invitedUser.InvitedUserEmailAddress)" -ForegroundColor Green - $guestUserInGraph = $invitedUser.InvitedUser + $user = $invitedUser.InvitedUser + $user = Get-MgUser -Filter "Mail eq '$($guestUser.Mail)'" -Property Id, UserPrincipalName, Mail, UserType, DisplayName, GivenName } - $guestUser.Id = $guestUserInGraph.Id - $guestUser.UserPrincipalName = $guestUserInGraph.UserPrincipalName + $allUsersInEntra += $user } -# groups -$allMemberUsersInEntra = Get-MgUser -ConsistencyLevel eventual -Count userCount -Filter "startsWith(DisplayName, '$($memberUsersNamePrefix)')" -OrderBy UserPrincipalName -$usersMemberOfAllGroups = [System.Linq.Enumerable]::Where($usersWithSpecificSettings, [Func[object, bool]] { param($x) $x.IsMemberOfAllGroups -eq $true }) - -# Bulk add groups -$totalGroups = 50 -for ($i = 1; $i -le $totalGroups; $i++) { +# Bulk add groups and set their membership +$allGroupsInEntra = @() +for ($i = 1; $i -le $groupsCount; $i++) { $groupName = "$($groupNamePrefix)$("{0:D3}" -f $i)" $groupSettings = [System.Linq.Enumerable]::FirstOrDefault($groupsWithSpecificSettings, [Func[object, bool]] { param($x) $x.GroupName -like $groupName }) $entraGroup = Get-MgGroup -Filter "DisplayName eq '$($groupName)'" @@ -147,30 +149,31 @@ for ($i = 1; $i -le $totalGroups; $i++) { Write-Host "Created group $groupName" -ForegroundColor Green $entraGroupJustCreated = $true } + $allGroupsInEntra += $entraGroup if ($false -eq $entraGroupJustCreated) { # Remove all members - $existingGroupMembers = Get-MgGroupMember -GroupId $entraGroup.Id + $existingGroupMembers = Get-MgGroupMember -GroupId $entraGroup.Id -All foreach ($groupMember in $existingGroupMembers) { Remove-MgGroupMemberByRef -GroupId $entraGroup.Id -DirectoryObjectId $groupMember.Id } Write-Host "Removed all members of existing group $($entraGroup.DisplayName)." -ForegroundColor Green } - # Set membership - $newGroupMembers = $usersMemberOfAllGroups | Select-Object -ExpandProperty UserPrincipalName + # Set group membership $newGroupMemberIds = New-Object -TypeName "System.Collections.Generic.List[System.String]" - if ($null -ne $groupSettings -and $groupSettings.ContainsKey("AllTestUsersAreMembers") -and $groupSettings["AllTestUsersAreMembers"] -eq $true) { - $newGroupMembers = $allMemberUsersInEntra.UserPrincipalName - - foreach ($guestUser in $guestUsersList) { - $newGroupMemberIds.Add($guestUser.Id) + if ($null -ne $groupSettings -and $groupSettings.ContainsKey("EveryoneIsMember") -and $groupSettings["EveryoneIsMember"] -eq $true) { + # Everyone is mmember of this group + foreach($userInEntra in $allUsersInEntra) { + $newGroupMemberIds.Add($userInEntra.Id) + } + } else { + # Only users with IsMemberOfAllGroups true are members of this group + foreach($upnOfUserMemberOfAllGroups in $usersMemberOfAllGroups | Select-Object -ExpandProperty UserPrincipalName) { + $upnOfUserMemberOfAllGroups + $userInEntra = [System.Linq.Enumerable]::First($allUsersInEntra, [Func[object, bool]] { param($x) $x.UserPrincipalName -like $upnOfUserMemberOfAllGroups }) + $newGroupMemberIds.Add($userInEntra.Id) } - } - - foreach ($groupMember in $newGroupMembers) { - $entraUser = [System.Linq.Enumerable]::FirstOrDefault($allMemberUsersInEntra, [Func[object, bool]] { param($x) $x.UserPrincipalName -like $groupMember }) - $newGroupMemberIds.Add($entraUser.Id) } # $newGroupMemberIds = $newGroupMemberIds | Select-Object -Unique @@ -179,3 +182,16 @@ for ($i = 1; $i -le $totalGroups; $i++) { } Write-Host "Added $($newGroupMemberIds.Count) member(s) to group $($entraGroup.DisplayName)" -ForegroundColor Green } + +# export users and groups to their CSV file +$allUsersInEntra | +Select-Object -Property Id, UserPrincipalName, Mail, UserType, DisplayName, GivenName, @{ Name = "IsMemberOfAllGroups"; Expression = { if ([System.Linq.Enumerable]::FirstOrDefault($usersWithSpecificSettings, [Func[object, bool]] { param($x) $x.UserPrincipalName -like $_.UserPrincipalName }).IsMemberOfAllGroups) { $true } else { $false } } } | +Export-Csv -Path $exportedUsersFullFilePath -NoTypeInformation +Write-Host "Exported Entra users to CSV file $($exportedUsersFullFilePath)" -ForegroundColor Green + +$allGroupsInEntra | +Select-Object -Property Id, DisplayName, SecurityEnabled, +@{ Name = "EveryoneIsMember"; Expression = { if ([System.Linq.Enumerable]::FirstOrDefault($groupsWithSpecificSettings, [Func[object, bool]] { param($x) $x.GroupName -like $_.DisplayName }).EveryoneIsMember) { $true } else { $false } } }, +@{ Name = "GroupType"; Expression = { $_.GroupTypes[0] } } | +Export-Csv -Path $exportedGroupsFullFilePath -NoTypeInformation +Write-Host "Exported Entra groups to CSV file $($exportedGroupsFullFilePath)" -ForegroundColor Green diff --git a/Yvand.EntraCP.Tests/UnitTestsHelper.cs b/Yvand.EntraCP.Tests/UnitTestsHelper.cs index df7e7825..e3e41ac5 100644 --- a/Yvand.EntraCP.Tests/UnitTestsHelper.cs +++ b/Yvand.EntraCP.Tests/UnitTestsHelper.cs @@ -2,15 +2,12 @@ using Microsoft.SharePoint; using Microsoft.SharePoint.Administration; using Microsoft.SharePoint.Administration.Claims; -using Newtonsoft.Json; using NUnit.Framework; using System; using System.Collections.Generic; using System.Diagnostics; -using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; using System.Security.Claims; using Yvand.EntraClaimsProvider.Configuration; @@ -19,10 +16,11 @@ namespace Yvand.EntraClaimsProvider.Tests [SetUpFixture] public class UnitTestsHelper { - public static readonly EntraCP ClaimsProvider = new EntraCP(TestContext.Parameters["ClaimsProviderName"]); - public static SPTrustedLoginProvider SPTrust => Utils.GetSPTrustAssociatedWithClaimsProvider(TestContext.Parameters["ClaimsProviderName"]); + public static readonly string ClaimsProviderName = TestContext.Parameters["ClaimsProviderName"]; + public static readonly EntraCP ClaimsProvider = new EntraCP(ClaimsProviderName); + public static SPTrustedLoginProvider SPTrust => Utils.GetSPTrustAssociatedWithClaimsProvider(ClaimsProviderName); public static Uri TestSiteCollUri; - public static string TestSiteRelativePath => $"/sites/{TestContext.Parameters["TestSiteCollectionName"]}"; + public static string TestSiteRelativePath => $"/sites/{ClaimsProviderName}.UnitTests"; public const int MaxTime = 50000; public const int TestRepeatCount = 1; public static string FarmAdmin => TestContext.Parameters["FarmAdmin"]; @@ -34,45 +32,30 @@ public class UnitTestsHelper public static string AzureTenantsJsonFile => TestContext.Parameters["AzureTenantsJsonFile"]; public static string DataFile_EntraId_TestUsers => TestContext.Parameters["DataFile_EntraId_TestUsers"]; public static string DataFile_EntraId_TestGroups => TestContext.Parameters["DataFile_EntraId_TestGroups"]; - public static string TestUsersAccountNamePrefix => TestContext.Parameters["UserAccountNamePrefix"]; - public static string TestGroupsAccountNamePrefix => TestContext.Parameters["GroupAccountNamePrefix"]; - public const int TestUsersCount = 50 + 3; // 50 members + 3 guests - public const int TestGroupsCount = 50; + public static string GroupsClaimType => TestContext.Parameters["GroupsClaimType"]; + static TextWriterTraceListener Logger { get; set; } public static EntraIDProviderConfiguration PersistedConfiguration; private static IEntraIDProviderSettings OriginalSettings; - private static EntraIDTenant _TenantConnection; - public static EntraIDTenant TenantConnection - { - get - { - if (_TenantConnection != null) { return _TenantConnection; } - string json = File.ReadAllText(UnitTestsHelper.AzureTenantsJsonFile); - List azureTenants = JsonConvert.DeserializeObject>(json); - _TenantConnection = azureTenants.First(); - return _TenantConnection; - } - } - [OneTimeSetUp] public static void InitializeSiteCollection() { - Logger = new TextWriterTraceListener(TestContext.Parameters["TestLogFileName"]); + Logger = new TextWriterTraceListener($"{ClaimsProviderName}IntegrationTests.log"); Trace.Listeners.Add(Logger); Trace.AutoFlush = true; - Trace.TraceInformation($"{DateTime.Now:s} Start integration tests of {EntraCP.ClaimsProviderName} {FileVersionInfo.GetVersionInfo(Assembly.GetAssembly(typeof(EntraCP)).Location).FileVersion}."); - Trace.TraceInformation($"{DateTime.Now:s} DataFile_EntraId_TestGroups: {DataFile_EntraId_TestGroups}"); - Trace.TraceInformation($"{DateTime.Now:s} DataFile_EntraId_TestUsers: {DataFile_EntraId_TestUsers}"); - Trace.TraceInformation($"{DateTime.Now:s} TestSiteCollectionName: {TestContext.Parameters["TestSiteCollectionName"]}"); + Trace.TraceInformation($"{DateTime.Now:s} [SETUP] Start integration tests of {EntraCP.ClaimsProviderName} {FileVersionInfo.GetVersionInfo(Assembly.GetAssembly(typeof(EntraCP)).Location).FileVersion}."); + Trace.TraceInformation($"{DateTime.Now:s} [SETUP] DataFile_EntraId_TestGroups: {DataFile_EntraId_TestGroups}"); + Trace.TraceInformation($"{DateTime.Now:s} [SETUP] DataFile_EntraId_TestUsers: {DataFile_EntraId_TestUsers}"); + Trace.TraceInformation($"{DateTime.Now:s} [SETUP] TestSiteCollectionName: {TestContext.Parameters["TestSiteCollectionName"]}"); if (SPTrust == null) { - Trace.TraceError($"{DateTime.Now:s} SPTrust: is null"); + Trace.TraceError($"{DateTime.Now:s} [SETUP] SPTrust: is null"); } else { - Trace.TraceInformation($"{DateTime.Now:s} SPTrust: {SPTrust.Name}"); + Trace.TraceInformation($"{DateTime.Now:s} [SETUP] SPTrust: {SPTrust.Name}"); } PersistedConfiguration = EntraCP.GetConfiguration(true); @@ -89,7 +72,7 @@ public static void InitializeSiteCollection() #if DEBUG TestSiteCollUri = new Uri($"http://spsites{TestSiteRelativePath}"); - return; // Uncommented when debugging from unit tests + //return; // Uncommented when debugging from unit tests #endif var service = SPFarm.Local.Services.GetValue(String.Empty); @@ -109,16 +92,17 @@ public static void InitializeSiteCollection() }); if (wa == null) { - Trace.TraceError($"{DateTime.Now:s} Web application was NOT found."); + Trace.TraceError($"{DateTime.Now:s} [SETUP] Web application was NOT found."); return; } - Trace.TraceInformation($"{DateTime.Now:s} Web application {wa.Name} found."); + Trace.TraceInformation($"{DateTime.Now:s} [SETUP] Web application {wa.Name} found."); Uri waRootAuthority = wa.AlternateUrls[0].Uri; TestSiteCollUri = new Uri($"{waRootAuthority.GetLeftPart(UriPartial.Authority)}{TestSiteRelativePath}"); SPClaimProviderManager claimMgr = SPClaimProviderManager.Local; - //string encodedClaim = claimMgr.EncodeClaim(TrustedGroup); - //SPUserInfo userInfo = new SPUserInfo { LoginName = encodedClaim, Name = TrustedGroupToAdd_ClaimValue }; + string trustedGroupName = TestEntitySourceManager.GetOneGroup().Id; + string encodedGroupClaim = claimMgr.EncodeClaim(new SPClaim(GroupsClaimType, trustedGroupName, ClaimValueTypes.String, SPOriginalIssuers.Format(SPOriginalIssuerType.TrustedProvider, UnitTestsHelper.SPTrust.Name))); + SPUserInfo groupInfo = new SPUserInfo { LoginName = encodedGroupClaim, Name = trustedGroupName }; FileVersionInfo spAssemblyVersion = FileVersionInfo.GetVersionInfo(Assembly.GetAssembly(typeof(SPSite)).Location); string spSiteTemplate = "STS#3"; // modern team site template @@ -131,23 +115,23 @@ public static void InitializeSiteCollection() // The root site may not exist, but it must be present for tests to run if (!SPSite.Exists(waRootAuthority)) { - Trace.TraceInformation($"{DateTime.Now:s} Creating root site collection {waRootAuthority.AbsoluteUri}..."); + Trace.TraceInformation($"{DateTime.Now:s} [SETUP] Creating root site collection {waRootAuthority.AbsoluteUri}..."); SPSite spSite = wa.Sites.Add(waRootAuthority.AbsoluteUri, "root", "root", 1033, spSiteTemplate, FarmAdmin, String.Empty, String.Empty); spSite.RootWeb.CreateDefaultAssociatedGroups(FarmAdmin, FarmAdmin, spSite.RootWeb.Title); SPGroup membersGroup = spSite.RootWeb.AssociatedMemberGroup; - //membersGroup.AddUser(userInfo.LoginName, userInfo.Email, userInfo.Name, userInfo.Notes); + membersGroup.AddUser(groupInfo.LoginName, groupInfo.Email, groupInfo.Name, groupInfo.Notes); spSite.Dispose(); } if (!SPSite.Exists(TestSiteCollUri)) { - Trace.TraceInformation($"{DateTime.Now:s} Creating site collection {TestSiteCollUri.AbsoluteUri} with template '{spSiteTemplate}'..."); + Trace.TraceInformation($"{DateTime.Now:s} [SETUP] Creating site collection {TestSiteCollUri.AbsoluteUri} with template '{spSiteTemplate}'..."); SPSite spSite = wa.Sites.Add(TestSiteCollUri.AbsoluteUri, EntraCP.ClaimsProviderName, EntraCP.ClaimsProviderName, 1033, spSiteTemplate, FarmAdmin, String.Empty, String.Empty); spSite.RootWeb.CreateDefaultAssociatedGroups(FarmAdmin, FarmAdmin, spSite.RootWeb.Title); SPGroup membersGroup = spSite.RootWeb.AssociatedMemberGroup; - //membersGroup.AddUser(userInfo.LoginName, userInfo.Email, userInfo.Name, userInfo.Notes); + membersGroup.AddUser(groupInfo.LoginName, groupInfo.Email, groupInfo.Name, groupInfo.Notes); spSite.Dispose(); } else @@ -155,7 +139,7 @@ public static void InitializeSiteCollection() using (SPSite spSite = new SPSite(TestSiteCollUri.AbsoluteUri)) { SPGroup membersGroup = spSite.RootWeb.AssociatedMemberGroup; - //membersGroup.AddUser(userInfo.LoginName, userInfo.Email, userInfo.Name, userInfo.Notes); + membersGroup.AddUser(groupInfo.LoginName, groupInfo.Email, groupInfo.Name, groupInfo.Notes); } } } @@ -186,184 +170,170 @@ public static void Cleanup() } } - public class EntraIdTestGroup + public abstract class TestEntity : ICloneable { public string Id; public string DisplayName; + + public object Clone() + { + return this.MemberwiseClone(); + } + + public abstract void SetEntityFromDataSourceRow(Row row); + } + + public class TestUser : TestEntity + { + public string UserPrincipalName; + public UserType UserType; + public string Mail; + public string GivenName; + public bool IsMemberOfAllGroups = false; + + public override void SetEntityFromDataSourceRow(Row row) + { + Id = row["id"]; + DisplayName = row["displayName"]; + UserPrincipalName = row["userPrincipalName"]; + UserType = String.Equals(row["userType"], ClaimsProviderConstants.MEMBER_USERTYPE, StringComparison.InvariantCultureIgnoreCase) ? UserType.Member : UserType.Guest; + Mail = row["mail"]; + GivenName = row["givenName"]; + IsMemberOfAllGroups = Convert.ToBoolean(row["IsMemberOfAllGroups"]); + } + } + + public class TestGroup : TestEntity + { public string GroupType; public bool SecurityEnabled = true; + public bool EveryoneIsMember; + + public override void SetEntityFromDataSourceRow(Row row) + { + Id = row["id"]; + DisplayName = row["displayName"]; + GroupType = row["groupType"]; + SecurityEnabled = Convert.ToBoolean(row["SecurityEnabled"]); + EveryoneIsMember = Convert.ToBoolean(row["EveryoneIsMember"]); + } } - public class EntraIdTestGroupSettings : EntraIdTestGroup + public enum UserType { - public bool AllTestUsersAreMembers = false; + Member, + Guest } - public class EntraIdTestGroupsSource + public class TestEntitySource where T : TestEntity, new() { - private static object _LockInitGroupsList = new object(); - private static List _Groups; - public static List Groups + private object _LockInitEntitiesList = new object(); + private bool EntitiesReady = false; + private List _Entities = new List(); + public List Entities { get { - if (_Groups != null) { return _Groups; } - lock (_LockInitGroupsList) + if (EntitiesReady) { return _Entities; } + lock (_LockInitEntitiesList) { - if (_Groups != null) { return _Groups; } - _Groups = new List(); - foreach (EntraIdTestGroup group in GetTestData(false)) + if (EntitiesReady) { return _Entities; } + foreach (T entity in ReadDataSource()) { - _Groups.Add(group); + _Entities.Add(entity); } - Trace.TraceInformation($"{DateTime.Now:s} [{typeof(EntraIdTestGroupsSource).Name}] Initialized List of {nameof(Groups)} with {_Groups.Count} items."); - return _Groups; + EntitiesReady = true; + Trace.TraceInformation($"{DateTime.Now:s} [{typeof(T).Name}] Initialized List of {nameof(Entities)} with {Entities.Count} items."); + return _Entities; } } } - public static EntraIdTestGroup ASecurityEnabledGroup => Groups.First(x => x.SecurityEnabled); - public static EntraIdTestGroup ANonSecurityEnabledGroup => Groups.First(x => !x.SecurityEnabled); + private Random RandomNumber = new Random(); + private string DataSourceFilePath; - private static object _LockInitGroupsSettingsList = new object(); - private static List _GroupsSettings; - public static List GroupsSettings + public TestEntitySource(string dataSourceFilePath) { - get + DataSourceFilePath = dataSourceFilePath; + } + + private IEnumerable ReadDataSource() + { + DataTable dt = DataTable.New.ReadCsv(DataSourceFilePath); + foreach (Row row in dt.Rows) { - if (_GroupsSettings != null) { return _GroupsSettings; } - lock (_LockInitGroupsSettingsList) - { - if (_GroupsSettings != null) { return _GroupsSettings; } - _GroupsSettings = new List - { - new EntraIdTestGroupSettings { DisplayName = $"{UnitTestsHelper.TestGroupsAccountNamePrefix}001" , SecurityEnabled = false, AllTestUsersAreMembers = true}, - new EntraIdTestGroupSettings { DisplayName = $"{UnitTestsHelper.TestGroupsAccountNamePrefix}005" , SecurityEnabled = true, AllTestUsersAreMembers = true }, - new EntraIdTestGroupSettings { DisplayName = $"{UnitTestsHelper.TestGroupsAccountNamePrefix}008" , SecurityEnabled = false, AllTestUsersAreMembers = false }, - new EntraIdTestGroupSettings { DisplayName = $"{UnitTestsHelper.TestGroupsAccountNamePrefix}018" , SecurityEnabled = false, AllTestUsersAreMembers = true }, - new EntraIdTestGroupSettings { DisplayName = $"{UnitTestsHelper.TestGroupsAccountNamePrefix}025" , SecurityEnabled = true, AllTestUsersAreMembers = true }, - new EntraIdTestGroupSettings { DisplayName = $"{UnitTestsHelper.TestGroupsAccountNamePrefix}028" , SecurityEnabled = false, AllTestUsersAreMembers = false, }, - new EntraIdTestGroupSettings { DisplayName = $"{UnitTestsHelper.TestGroupsAccountNamePrefix}038" , SecurityEnabled = false, AllTestUsersAreMembers = true, }, - new EntraIdTestGroupSettings { DisplayName = $"{UnitTestsHelper.TestGroupsAccountNamePrefix}048" , SecurityEnabled = false, AllTestUsersAreMembers = false, }, - }; - foreach (EntraIdTestGroupSettings groupsSetting in _GroupsSettings) - { - groupsSetting.Id = Groups.First(x => x.DisplayName == groupsSetting.DisplayName).Id; - } - Trace.TraceInformation($"{DateTime.Now:s} [{typeof(EntraIdTestGroupSettings).Name}] Initialized List of {nameof(GroupsSettings)} with {_GroupsSettings.Count} items."); - } - return _GroupsSettings; + T entity = new T(); + entity.SetEntityFromDataSourceRow(row); + yield return entity; } } - public static IEnumerable GetTestData(bool securityEnabledGroupsOnly = false) + public IEnumerable GetSomeEntities(int count, Func filter = null) { - string csvPath = UnitTestsHelper.DataFile_EntraId_TestGroups; - DataTable dt = DataTable.New.ReadCsv(csvPath); - foreach (Row row in dt.Rows) + IEnumerable entitiesFiltered = Entities.Where(filter ?? (x => true)); + int entitiesFilteredCount = entitiesFiltered.Count(); + if (count > entitiesFilteredCount) { count = entitiesFilteredCount; } + for (int i = 0; i < count; i++) { - var registrationData = new EntraIdTestGroup(); - registrationData.Id = row["id"]; - registrationData.DisplayName = row["displayName"]; - registrationData.GroupType = row["groupType"]; - registrationData.SecurityEnabled = Convert.ToBoolean(row["SecurityEnabled"]); - if (securityEnabledGroupsOnly && !registrationData.SecurityEnabled) - { - continue; - } - yield return registrationData; + int randomIdx = RandomNumber.Next(0, entitiesFilteredCount); + yield return entitiesFiltered.ElementAt(randomIdx).Clone() as T; } } } - public enum UserType + public class TestEntitySourceManager { - Member, - Guest - } + private static TestEntitySource TestUsersSource = new TestEntitySource(UnitTestsHelper.DataFile_EntraId_TestUsers); + public static List AllTestUsers + { + get => TestUsersSource.Entities; + } + private static TestEntitySource TestGroupsSource = new TestEntitySource(UnitTestsHelper.DataFile_EntraId_TestGroups); + public static List AllTestGroups + { + get => TestGroupsSource.Entities; + } + public const int MaxNumberOfUsersToTest = 100; + public const int MaxNumberOfGroupsToTest = 100; - public class EntraIdTestUser - { - public string Id; - public string DisplayName; - public string UserPrincipalName; - public UserType UserType; - public string Mail; - public string GivenName; - } + public static IEnumerable GetSomeUsers(int count) + { + return TestUsersSource.GetSomeEntities(count, null); + } - public class EntraIdTestUserSettings : EntraIdTestUser - { - public bool IsMemberOfAllGroups = false; - } + public static IEnumerable GetUsersMembersOfAllGroups() + { + Func filter = x => x.IsMemberOfAllGroups == true; + return TestUsersSource.GetSomeEntities(Int16.MaxValue, filter); + } - public class EntraIdTestUsersSource - { - private static object _LockInitList = new object(); - private static List _Users; - public static List Users + public static TestUser FindUser(string upnPrefix) { - get - { - if (_Users != null) { return _Users; } - lock (_LockInitList) - { - if (_Users != null) { return _Users; } - _Users = new List(); - foreach (EntraIdTestUser user in GetTestData()) - { - _Users.Add(user); - } - Trace.TraceInformation($"{DateTime.Now:s} [{typeof(EntraIdTestUsersSource).Name}] Initialized List of {nameof(Users)} with {_Users.Count} items."); - return _Users; - } - } + Func filter = x => x.UserPrincipalName.StartsWith(upnPrefix); + return TestUsersSource.GetSomeEntities(1, filter).First(); } - public static EntraIdTestUser AGuestUser => Users.FirstOrDefault(x => x.UserType == UserType.Guest); - public static IEnumerable AllGuestUsers => Users.Where(x => x.UserType == UserType.Guest); + public static TestUser GetOneUser(UserType userType) + { + Func filter = x => x.UserType == userType; + return TestUsersSource.GetSomeEntities(1, filter).First(); + } - private static object _LockInitUsersWithSpecificSettingsList = new object(); - private static List _UsersWithSpecificSettings; - public static List UsersWithSpecificSettings + public static IEnumerable GetSomeGroups(int count, bool securityEnabledOnly) { - get - { - if (_UsersWithSpecificSettings != null) { return _UsersWithSpecificSettings; } - lock (_LockInitUsersWithSpecificSettingsList) - { - if (_UsersWithSpecificSettings != null) { return _UsersWithSpecificSettings; } - _UsersWithSpecificSettings = new List - { - new EntraIdTestUserSettings { UserPrincipalName = $"{UnitTestsHelper.TestUsersAccountNamePrefix}001@{UnitTestsHelper.TenantConnection.Name}" , IsMemberOfAllGroups = true }, - new EntraIdTestUserSettings { UserPrincipalName = $"{UnitTestsHelper.TestUsersAccountNamePrefix}010@{UnitTestsHelper.TenantConnection.Name}" , IsMemberOfAllGroups = true }, - new EntraIdTestUserSettings { UserPrincipalName = $"{UnitTestsHelper.TestUsersAccountNamePrefix}011@{UnitTestsHelper.TenantConnection.Name}" , IsMemberOfAllGroups = true }, - new EntraIdTestUserSettings { UserPrincipalName = $"{UnitTestsHelper.TestUsersAccountNamePrefix}012@{UnitTestsHelper.TenantConnection.Name}" , IsMemberOfAllGroups = true }, - new EntraIdTestUserSettings { UserPrincipalName = $"{UnitTestsHelper.TestUsersAccountNamePrefix}013@{UnitTestsHelper.TenantConnection.Name}" , IsMemberOfAllGroups = true }, - new EntraIdTestUserSettings { UserPrincipalName = $"{UnitTestsHelper.TestUsersAccountNamePrefix}014@{UnitTestsHelper.TenantConnection.Name}" , IsMemberOfAllGroups = true }, - new EntraIdTestUserSettings { UserPrincipalName = $"{UnitTestsHelper.TestUsersAccountNamePrefix}015@{UnitTestsHelper.TenantConnection.Name}" , IsMemberOfAllGroups = true }, - }; - } - Trace.TraceInformation($"{DateTime.Now:s} [{typeof(EntraIdTestUserSettings).Name}] Initialized List of {nameof(UsersWithSpecificSettings)} with {_UsersWithSpecificSettings.Count} items."); - return _UsersWithSpecificSettings; - } + Func filter = x => x.SecurityEnabled == securityEnabledOnly; + return TestGroupsSource.GetSomeEntities(count, filter); } - public static IEnumerable GetTestData() + public static TestGroup GetOneGroup() { - string csvPath = UnitTestsHelper.DataFile_EntraId_TestUsers; - DataTable dt = DataTable.New.ReadCsv(csvPath); - foreach (Row row in dt.Rows) - { - var registrationData = new EntraIdTestUser(); - registrationData.Id = row["id"]; - registrationData.DisplayName = row["displayName"]; - registrationData.UserPrincipalName = row["userPrincipalName"]; - registrationData.UserType = String.Equals(row["userType"], ClaimsProviderConstants.MEMBER_USERTYPE, StringComparison.InvariantCultureIgnoreCase) ? UserType.Member : UserType.Guest; - registrationData.Mail = row["mail"]; - registrationData.GivenName = row["givenName"]; - yield return registrationData; - } + return TestGroupsSource.GetSomeEntities(1, null).First(); + } + + public static TestGroup GetOneGroup(bool securityEnabledOnly) + { + Func filter = x => x.SecurityEnabled == securityEnabledOnly; + return TestGroupsSource.GetSomeEntities(1, filter).First(); } } } \ No newline at end of file diff --git a/Yvand.EntraCP.Tests/Yvand.EntraCP.Tests.csproj b/Yvand.EntraCP.Tests/Yvand.EntraCP.Tests.csproj index a54c0800..0700f2f7 100644 --- a/Yvand.EntraCP.Tests/Yvand.EntraCP.Tests.csproj +++ b/Yvand.EntraCP.Tests/Yvand.EntraCP.Tests.csproj @@ -2,7 +2,7 @@ Debug - AnyCPU + x64 {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88} Library Properties @@ -20,22 +20,23 @@ - + true - full - false bin\Debug\ DEBUG;TRACE + full + x64 + 7.3 prompt - 4 - - pdbonly - true + bin\Release\ TRACE + true + pdbonly + x64 + 7.3 prompt - 4 @@ -76,12 +77,12 @@ 4.1.0 - 4.2.0 + 4.3.0 runtime; build; native; contentfiles; analyzers; buildtransitive all - 4.5.0 + 4.6.0 runtime; build; native; contentfiles; analyzers; buildtransitive all diff --git a/Yvand.EntraCP.Tests/local.runsettings b/Yvand.EntraCP.Tests/local.runsettings index 91ea265f..abc2f4f6 100644 --- a/Yvand.EntraCP.Tests/local.runsettings +++ b/Yvand.EntraCP.Tests/local.runsettings @@ -6,15 +6,11 @@ - - - - - - - + + + diff --git a/Yvand.EntraCP.sln b/Yvand.EntraCP.sln index 53437dba..5d508e67 100644 --- a/Yvand.EntraCP.sln +++ b/Yvand.EntraCP.sln @@ -12,29 +12,18 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Yvand.EntraCP.Tests", "Yvan EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 - Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Debug|Any CPU.Build.0 = Debug|Any CPU - {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Debug|x64.ActiveCfg = Debug|Any CPU - {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Debug|x64.Build.0 = Debug|Any CPU - {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Release|Any CPU.ActiveCfg = Release|Any CPU - {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Release|Any CPU.Build.0 = Release|Any CPU - {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Release|Any CPU.Deploy.0 = Release|Any CPU - {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Release|x64.ActiveCfg = Release|Any CPU - {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Release|x64.Build.0 = Release|Any CPU - {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Debug|Any CPU.Build.0 = Debug|Any CPU - {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Debug|x64.ActiveCfg = Debug|Any CPU - {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Debug|x64.Build.0 = Debug|Any CPU - {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Release|Any CPU.ActiveCfg = Release|Any CPU - {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Release|Any CPU.Build.0 = Release|Any CPU - {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Release|x64.ActiveCfg = Release|Any CPU - {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Release|x64.Build.0 = Release|Any CPU + {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Debug|x64.ActiveCfg = Debug|x64 + {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Debug|x64.Build.0 = Debug|x64 + {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Release|x64.ActiveCfg = Release|x64 + {EEC47949-34B5-4805-A04D-A372BE75D3CB}.Release|x64.Build.0 = Release|x64 + {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Debug|x64.ActiveCfg = Debug|x64 + {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Debug|x64.Build.0 = Debug|x64 + {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Release|x64.ActiveCfg = Release|x64 + {DB8C79E5-F7F7-4841-8A8C-A9832A9CAE88}.Release|x64.Build.0 = Release|x64 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Yvand.EntraCP/Yvand.EntraCP.csproj b/Yvand.EntraCP/Yvand.EntraCP.csproj index a498e307..a06e8915 100644 --- a/Yvand.EntraCP/Yvand.EntraCP.csproj +++ b/Yvand.EntraCP/Yvand.EntraCP.csproj @@ -31,31 +31,32 @@ - - embedded + + + true + + true - false bin\Debug\ DEBUG;TRACE - prompt - 4 + embedded + x64 false + 8.0 + prompt MinimumRecommendedRules.ruleset - - - portable - true + bin\Release\ TRACE - prompt - 4 + true + portable + x64 false + 8.0 + prompt MinimumRecommendedRules.ruleset - - true - diff --git a/Yvand.EntraCP/Yvand.EntraCP.nuspec b/Yvand.EntraCP/Yvand.EntraCP.nuspec index f6cf9f79..90553e3d 100644 --- a/Yvand.EntraCP/Yvand.EntraCP.nuspec +++ b/Yvand.EntraCP/Yvand.EntraCP.nuspec @@ -8,18 +8,20 @@ $description$ true Apache-2.0 - https://github.com/Yvand/AzureCP + https://github.com/Yvand/EntraCP $copyright$ SharePoint ClaimsProvider - https://github.com/Yvand/AzureCP/blob/master/CHANGELOG.md - + https://github.com/Yvand/EntraCP/blob/master/CHANGELOG.md + - - + + + + \ No newline at end of file diff --git a/Yvand.EntraCP/Yvand.EntraClaimsProvider/EntraIDEntityProvider.cs b/Yvand.EntraCP/Yvand.EntraClaimsProvider/EntraIDEntityProvider.cs index bd8c2e4e..f8d4eb73 100644 --- a/Yvand.EntraCP/Yvand.EntraClaimsProvider/EntraIDEntityProvider.cs +++ b/Yvand.EntraCP/Yvand.EntraClaimsProvider/EntraIDEntityProvider.cs @@ -461,11 +461,11 @@ protected virtual async Task> QueryEntraIDTenantAsync(Oper } // List of groups that users must be member of, to be returned to SharePoint - string[] restrictSearchableUsersByGroupsRequestsId = null; - string restrictSearchableUsersByGroups = this.Settings.RestrictSearchableUsersByGroups; + string[] allowedGroupMembersOfGroupsRequestsId = null; + string allowedGroupsIDs = this.Settings.RestrictSearchableUsersByGroups; //restrictSearchableUsersByGroups = "c9a94341-89b5-4109-a501-2a14027b5bf0"; // testEntraCPGroup_005 - everyone member //restrictSearchableUsersByGroups = "cd5f135c-9fe5-4ec2-90d9-114e9ad2e236"; // testEntraCPGroup_004 - testEntraCPUser_001 and testEntraCPUser_010 members - if (!String.IsNullOrWhiteSpace(restrictSearchableUsersByGroups) && cachedTenantData.SearchableUsersId == null) + if (!String.IsNullOrWhiteSpace(allowedGroupsIDs) && cachedTenantData.SearchableUsersId == null) { await cachedTenantData.WriteDataLock.WaitAsync().ConfigureAwait(false); lockToWriteInCachedDataWasTaken = true; @@ -476,23 +476,25 @@ protected virtual async Task> QueryEntraIDTenantAsync(Oper } else { - string[] groupsId = restrictSearchableUsersByGroups.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); - restrictSearchableUsersByGroupsRequestsId = new string[groupsId.Length]; + string[] groupsId = allowedGroupsIDs.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries); + allowedGroupMembersOfGroupsRequestsId = new string[groupsId.Length]; int groupIdx = 0; - foreach (string groupId in groupsId) + foreach (string allowedGroupId in groupsId) { - RequestInformation usersMembersOfGroupRequest = tenant.GraphService.Groups[groupId].Members.GraphUser.ToGetRequestInformation(conf => + RequestInformation allowedGroupMembersRequest = tenant.GraphService.Groups[allowedGroupId].Members.GraphUser.ToGetRequestInformation(conf => { conf.QueryParameters = new Microsoft.Graph.Groups.Item.Members.GraphUser.GraphUserRequestBuilder.GraphUserRequestBuilderGetQueryParameters { Select = new string[] { "Id" }, + // max items count per page is 999: https://learn.microsoft.com/en-us/graph/api/group-list-members?view=graph-rest-1.0&tabs=http#optional-query-parameters + Top = 100, }; conf.Options = new List { retryHandlerOption, }; }); - restrictSearchableUsersByGroupsRequestsId[groupIdx] = await batchRequestContent.AddBatchRequestStepAsync(usersMembersOfGroupRequest).ConfigureAwait(false); + allowedGroupMembersOfGroupsRequestsId[groupIdx] = await batchRequestContent.AddBatchRequestStepAsync(allowedGroupMembersRequest).ConfigureAwait(false); groupIdx++; } } @@ -532,28 +534,39 @@ protected virtual async Task> QueryEntraIDTenantAsync(Oper } } - if (restrictSearchableUsersByGroupsRequestsId != null) + if (allowedGroupMembersOfGroupsRequestsId != null) { // only need 1 list that contains unique user ids cachedTenantData.SearchableUsersId = new List(); - foreach (string restrictSearchableUsersByGroupsRequestId in restrictSearchableUsersByGroupsRequestsId) + foreach (string allowedGroupMembersOfGroupRequestId in allowedGroupMembersOfGroupsRequestsId) { - HttpStatusCode restrictSearchableUsersByGroupsResponseStatus; - UserCollectionResponse restrictSearchableUsersByGroupsResponse = null; - if (requestsStatusInBatchResponse.TryGetValue(restrictSearchableUsersByGroupsRequestId, out restrictSearchableUsersByGroupsResponseStatus)) + HttpStatusCode allowedGroupMembersOfGroupResponseStatus; + //UserCollectionResponse restrictSearchableUsersByGroupsResponse = null; + if (requestsStatusInBatchResponse.TryGetValue(allowedGroupMembersOfGroupRequestId, out allowedGroupMembersOfGroupResponseStatus)) { - if (restrictSearchableUsersByGroupsResponseStatus == HttpStatusCode.OK) + if (allowedGroupMembersOfGroupResponseStatus == HttpStatusCode.OK) { - restrictSearchableUsersByGroupsResponse = await batchResponse.GetResponseByIdAsync(restrictSearchableUsersByGroupsRequestId).ConfigureAwait(false); - cachedTenantData.SearchableUsersId.AddRange(restrictSearchableUsersByGroupsResponse.Value.Where(x => !cachedTenantData.SearchableUsersId.Contains(x.Id)).Select(x => x.Id).ToList()); + UserCollectionResponse allowedGroupMembersOfGroupResponse = await batchResponse.GetResponseByIdAsync(allowedGroupMembersOfGroupRequestId).ConfigureAwait(false); + PageIterator allowedGroupMembersPageIterator = PageIterator.CreatePageIterator( + tenant.GraphService, + allowedGroupMembersOfGroupResponse, + (user) => + { + if (!cachedTenantData.SearchableUsersId.Contains(user.Id)) + { + cachedTenantData.SearchableUsersId.Add(user.Id); + } + return true; + }); + await allowedGroupMembersPageIterator.IterateAsync().ConfigureAwait(false); } - else if (restrictSearchableUsersByGroupsResponseStatus == HttpStatusCode.NotFound) + else if (allowedGroupMembersOfGroupResponseStatus == HttpStatusCode.NotFound) { Logger.Log($"[{ClaimsProviderName}] Request inside the batch to get the members of a group on tenant \"{tenant.Name}\" returned nothing (the group was not found).", TraceSeverity.Verbose, EventSeverity.Information, TraceCategory.Lookup); } else { - Logger.Log($"[{ClaimsProviderName}] Request inside the batch to get the members of a group on tenant \"{tenant.Name}\" returned unexpected status '{restrictSearchableUsersByGroupsResponseStatus}'", TraceSeverity.Unexpected, EventSeverity.Error, TraceCategory.Lookup); + Logger.Log($"[{ClaimsProviderName}] Request inside the batch to get the members of a group on tenant \"{tenant.Name}\" returned unexpected status '{allowedGroupMembersOfGroupResponseStatus}'", TraceSeverity.Unexpected, EventSeverity.Error, TraceCategory.Lookup); } } } diff --git a/azure-pipelines/ci-apply-dtl-artifacts.yml b/azure-pipelines/ci-apply-dtl-artifacts.yml deleted file mode 100644 index bbfb89e2..00000000 --- a/azure-pipelines/ci-apply-dtl-artifacts.yml +++ /dev/null @@ -1,131 +0,0 @@ -resources: -- repo: self - -jobs: -- job: ApplyArtifactsSP2013 - condition: eq(variables['Deployment.ProvisionSharePoint2013'], true) - displayName: Apply artifacts on SP2013 - timeoutInMinutes: 30 - variables: - jobSharePointVersion: 2013 - pool: - vmImage: 'windows-2019' - steps: - - checkout: none #skip checking out the default repository resource - - task: automagically.DownloadFile.DownloadFile.DownloadFile@1 - displayName: 'Download apply-dtl-artifact.ps1' - inputs: - FileUrl: 'https://raw.githubusercontent.com/Yvand/AzureRM-Templates/master/DevTestLabs-Artifacts/manage-artifacts/apply-dtl-artifact.ps1' - DestinationFolder: '$(System.DefaultWorkingDirectory)\scripts' - - - task: AzurePowerShell@3 - displayName: 'Create and register a VSTS agent in DevOps agent pools by applying artifact "Azure Pipelines Agent"' - inputs: - azureSubscription: '$(DevTestLabs.AzureConnectionName)' - ScriptPath: '$(System.DefaultWorkingDirectory)\scripts\apply-dtl-artifact.ps1' - ScriptArguments: '-DevTestLabName "$(DevTestLabs.LabName)" -VirtualMachineName "SP$(jobSharePointVersion)" -RepositoryName "Yvand/AzureRM-Templates" -ArtifactName "windows-vsts-build-agent" -param_vstsAccount "$(DevOps.OrganizationName)" -param_vstsPassword "$(DevOps.AccessToken)" -param_poolName "$(system.teamProject)-Tests-$(jobSharePointVersion)" -param_windowsLogonAccount "$(Deployment.DomainName)\$(Deployment.AdminUserName)" -param_windowsLogonPassword "$(Deployment.AdminPassword)" -param_agentName "SP$(jobSharePointVersion)" -param_agentNameSuffix "-$(Build.BuildNumber)" -param_RunAsAutoLogon false -param_driveLetter C -param_workDirectory ""' - preferredAzurePowerShellVersion: 5.1.1 - - - task: AzurePowerShell@3 - displayName: 'Apply artifact "Download Azure Pipelines Artifact and Run Script"' - inputs: - azureSubscription: '$(DevTestLabs.AzureConnectionName)' - ScriptPath: '$(System.DefaultWorkingDirectory)\scripts\apply-dtl-artifact.ps1' - ScriptArguments: -DevTestLabName '$(DevTestLabs.LabName)' -VirtualMachineName 'SP$(jobSharePointVersion)' -RepositoryName 'Yvand/AzureRM-Templates' -ArtifactName 'windows-vsts-download-and-run-script' -param_vstsProjectUri 'https://dev.azure.com/$(DevOps.OrganizationName)/$(system.teamProject)' -param_buildDefinitionName '$(DevOps.BuildArtifactsPipelineName)' -param_personalAccessToken $(DevOps.AccessToken) -param_pathToScript 'drop_$(Tests.BuildConfiguration)\$(Deployment.ConfigureServerFolderName)\ConfigureLab.ps1' -param_scriptArguments "-pathToPackage '..\$(system.teamProject)\bin\$(Tests.BuildConfiguration)\$(system.teamProject).wsp' -claimsProviderName '$(system.teamProject)' -spTrustName '$(Deployment.DomainFQDN)' -adminUserName '$(Deployment.DomainName)\$(Deployment.AdminUserName)' -adminPassword '$(Deployment.AdminPassword)'" - preferredAzurePowerShellVersion: 5.1.1 - -- job: ApplyArtifactsSP2016 - condition: eq(variables['Deployment.ProvisionSharePoint2016'], true) - displayName: Apply artifacts on SP2016 - timeoutInMinutes: 30 - variables: - jobSharePointVersion: 2016 - pool: - vmImage: 'windows-2019' - steps: - - checkout: none #skip checking out the default repository resource - - task: automagically.DownloadFile.DownloadFile.DownloadFile@1 - displayName: 'Download apply-dtl-artifact.ps1' - inputs: - FileUrl: 'https://raw.githubusercontent.com/Yvand/AzureRM-Templates/master/DevTestLabs-Artifacts/manage-artifacts/apply-dtl-artifact.ps1' - DestinationFolder: '$(System.DefaultWorkingDirectory)\scripts' - - - task: AzurePowerShell@3 - displayName: 'Create and register a VSTS agent in DevOps agent pools by applying artifact "Azure Pipelines Agent"' - inputs: - azureSubscription: '$(DevTestLabs.AzureConnectionName)' - ScriptPath: '$(System.DefaultWorkingDirectory)\scripts\apply-dtl-artifact.ps1' - ScriptArguments: '-DevTestLabName "$(DevTestLabs.LabName)" -VirtualMachineName "SP$(jobSharePointVersion)" -RepositoryName "Yvand/AzureRM-Templates" -ArtifactName "windows-vsts-build-agent" -param_vstsAccount "$(DevOps.OrganizationName)" -param_vstsPassword "$(DevOps.AccessToken)" -param_poolName "$(system.teamProject)-Tests-$(jobSharePointVersion)" -param_windowsLogonAccount "$(Deployment.DomainName)\$(Deployment.AdminUserName)" -param_windowsLogonPassword "$(Deployment.AdminPassword)" -param_agentName "SP$(jobSharePointVersion)" -param_agentNameSuffix "-$(Build.BuildNumber)" -param_RunAsAutoLogon false -param_driveLetter C -param_workDirectory ""' - preferredAzurePowerShellVersion: 5.1.1 - - - task: AzurePowerShell@3 - displayName: 'Apply artifact "Download Azure Pipelines Artifact and Run Script"' - inputs: - azureSubscription: '$(DevTestLabs.AzureConnectionName)' - ScriptPath: '$(System.DefaultWorkingDirectory)\scripts\apply-dtl-artifact.ps1' - ScriptArguments: -DevTestLabName '$(DevTestLabs.LabName)' -VirtualMachineName 'SP$(jobSharePointVersion)' -RepositoryName 'Yvand/AzureRM-Templates' -ArtifactName 'windows-vsts-download-and-run-script' -param_vstsProjectUri 'https://dev.azure.com/$(DevOps.OrganizationName)/$(system.teamProject)' -param_buildDefinitionName '$(DevOps.BuildArtifactsPipelineName)' -param_personalAccessToken $(DevOps.AccessToken) -param_pathToScript 'drop_$(Tests.BuildConfiguration)\$(Deployment.ConfigureServerFolderName)\ConfigureLab.ps1' -param_scriptArguments "-pathToPackage '..\$(system.teamProject)\bin\$(Tests.BuildConfiguration)\$(system.teamProject).wsp' -claimsProviderName '$(system.teamProject)' -spTrustName '$(Deployment.DomainFQDN)' -adminUserName '$(Deployment.DomainName)\$(Deployment.AdminUserName)' -adminPassword '$(Deployment.AdminPassword)'" - preferredAzurePowerShellVersion: 5.1.1 - -- job: ApplyArtifactsSP2019 - condition: eq(variables['Deployment.ProvisionSharePoint2019'], true) - displayName: Apply artifacts on SP2019 - timeoutInMinutes: 30 - variables: - jobSharePointVersion: 2019 - pool: - vmImage: 'windows-2019' - steps: - - checkout: none #skip checking out the default repository resource - - task: automagically.DownloadFile.DownloadFile.DownloadFile@1 - displayName: 'Download apply-dtl-artifact.ps1' - inputs: - FileUrl: 'https://raw.githubusercontent.com/Yvand/AzureRM-Templates/master/DevTestLabs-Artifacts/manage-artifacts/apply-dtl-artifact.ps1' - DestinationFolder: '$(System.DefaultWorkingDirectory)\scripts' - - - task: AzurePowerShell@3 - displayName: 'Create and register a VSTS agent in DevOps agent pools by applying artifact "Azure Pipelines Agent"' - inputs: - azureSubscription: '$(DevTestLabs.AzureConnectionName)' - ScriptPath: '$(System.DefaultWorkingDirectory)\scripts\apply-dtl-artifact.ps1' - ScriptArguments: '-DevTestLabName "$(DevTestLabs.LabName)" -VirtualMachineName "SP$(jobSharePointVersion)" -RepositoryName "Yvand/AzureRM-Templates" -ArtifactName "windows-vsts-build-agent" -param_vstsAccount "$(DevOps.OrganizationName)" -param_vstsPassword "$(DevOps.AccessToken)" -param_poolName "$(system.teamProject)-Tests-$(jobSharePointVersion)" -param_windowsLogonAccount "$(Deployment.DomainName)\$(Deployment.AdminUserName)" -param_windowsLogonPassword "$(Deployment.AdminPassword)" -param_agentName "SP$(jobSharePointVersion)" -param_agentNameSuffix "-$(Build.BuildNumber)" -param_RunAsAutoLogon false -param_driveLetter C -param_workDirectory ""' - preferredAzurePowerShellVersion: 5.1.1 - - - task: AzurePowerShell@3 - displayName: 'Apply artifact "Download Azure Pipelines Artifact and Run Script"' - inputs: - azureSubscription: '$(DevTestLabs.AzureConnectionName)' - ScriptPath: '$(System.DefaultWorkingDirectory)\scripts\apply-dtl-artifact.ps1' - ScriptArguments: -DevTestLabName '$(DevTestLabs.LabName)' -VirtualMachineName 'SP$(jobSharePointVersion)' -RepositoryName 'Yvand/AzureRM-Templates' -ArtifactName 'windows-vsts-download-and-run-script' -param_vstsProjectUri 'https://dev.azure.com/$(DevOps.OrganizationName)/$(system.teamProject)' -param_buildDefinitionName '$(DevOps.BuildArtifactsPipelineName)' -param_personalAccessToken $(DevOps.AccessToken) -param_pathToScript 'drop_$(Tests.BuildConfiguration)\$(Deployment.ConfigureServerFolderName)\ConfigureLab.ps1' -param_scriptArguments "-pathToPackage '..\$(system.teamProject)\bin\$(Tests.BuildConfiguration)\$(system.teamProject).wsp' -claimsProviderName '$(system.teamProject)' -spTrustName '$(Deployment.DomainFQDN)' -adminUserName '$(Deployment.DomainName)\$(Deployment.AdminUserName)' -adminPassword '$(Deployment.AdminPassword)'" - preferredAzurePowerShellVersion: 5.1.1 - -- job: ApplyArtifactsSPSubscription - condition: eq(variables['Deployment.ProvisionSharePointSubscription'], true) - displayName: Apply artifacts on SPSubscription - timeoutInMinutes: 30 - variables: - jobSharePointVersion: Subscription - pool: - vmImage: 'windows-latest' - steps: - - checkout: none #skip checking out the default repository resource - - task: automagically.DownloadFile.DownloadFile.DownloadFile@1 - displayName: 'Download apply-dtl-artifact.ps1' - inputs: - FileUrl: 'https://raw.githubusercontent.com/Yvand/AzureRM-Templates/master/DevTestLabs-Artifacts/manage-artifacts/apply-dtl-artifact.ps1' - DestinationFolder: '$(System.DefaultWorkingDirectory)\scripts' - - - task: AzurePowerShell@3 - displayName: 'Create and register a VSTS agent in DevOps agent pools by applying artifact "Azure Pipelines Agent"' - inputs: - azureSubscription: '$(DevTestLabs.AzureConnectionName)' - ScriptPath: '$(System.DefaultWorkingDirectory)\scripts\apply-dtl-artifact.ps1' - ScriptArguments: '-DevTestLabName "$(DevTestLabs.LabName)" -VirtualMachineName "SP$(jobSharePointVersion)" -RepositoryName "Yvand/AzureRM-Templates" -ArtifactName "windows-vsts-build-agent" -param_vstsAccount "$(DevOps.OrganizationName)" -param_vstsPassword "$(DevOps.AccessToken)" -param_poolName "$(system.teamProject)-Tests-$(jobSharePointVersion)" -param_windowsLogonAccount "$(Deployment.DomainName)\$(Deployment.AdminUserName)" -param_windowsLogonPassword "$(Deployment.AdminPassword)" -param_agentName "SP$(jobSharePointVersion)" -param_agentNameSuffix "-$(Build.BuildNumber)" -param_RunAsAutoLogon false -param_driveLetter C -param_workDirectory ""' - preferredAzurePowerShellVersion: 5.1.1 - - - task: AzurePowerShell@3 - displayName: 'Apply artifact "Download Azure Pipelines Artifact and Run Script"' - inputs: - azureSubscription: '$(DevTestLabs.AzureConnectionName)' - ScriptPath: '$(System.DefaultWorkingDirectory)\scripts\apply-dtl-artifact.ps1' - ScriptArguments: -DevTestLabName '$(DevTestLabs.LabName)' -VirtualMachineName 'SP$(jobSharePointVersion)' -RepositoryName 'Yvand/AzureRM-Templates' -ArtifactName 'windows-vsts-download-and-run-script' -param_vstsProjectUri 'https://dev.azure.com/$(DevOps.OrganizationName)/$(system.teamProject)' -param_buildDefinitionName '$(DevOps.BuildArtifactsPipelineName)' -param_personalAccessToken $(DevOps.AccessToken) -param_pathToScript 'drop_$(Tests.BuildConfiguration)\$(Deployment.ConfigureServerFolderName)\ConfigureLab.ps1' -param_scriptArguments "-pathToPackage '..\$(system.teamProject)\bin\$(Tests.BuildConfiguration)\$(system.teamProject).wsp' -claimsProviderName '$(system.teamProject)' -spTrustName '$(Deployment.DomainFQDN)' -adminUserName '$(Deployment.DomainName)\$(Deployment.AdminUserName)' -adminPassword '$(Deployment.AdminPassword)'" - preferredAzurePowerShellVersion: 5.1.1 \ No newline at end of file diff --git a/azure-pipelines/ci-compile.yml b/azure-pipelines/ci-compile.yml deleted file mode 100644 index 786a2538..00000000 --- a/azure-pipelines/ci-compile.yml +++ /dev/null @@ -1,132 +0,0 @@ -name: $(BuildVersion).$(date:yyyyMMdd).$(Build.BuildId) -resources: -- repo: self - -pr: -- master - -variables: - SolutionFileName: '$(system.teamProject).sln' - -jobs: -- job: Compile - strategy: - maxParallel: 2 - matrix: - debugJob: - configuration: debug - releaseJob: - configuration: release - displayName: Compile - pool: - vmImage: 'windows-2019' - demands: - - msbuild - - visualstudio - - azureps - steps: - - task: DownloadSecureFile@1 - displayName: 'Download signing key' - inputs: - secureFile: '$(SigningKeySecureFileID)' - - - powershell: | - # Set variables - $azureStorageBaseDirectory = "Resources\$(system.teamProject)" - $projectLocalPath = "$(System.DefaultWorkingDirectory)\$(system.teamProject)" - $devTestLabsLocalPath = "$(Build.ArtifactStagingDirectory)\$(Tests.DataFolderName)" - - # Create now folder that will contain later scripts to configure local test server - #if ((Test-Path -Path "$(Build.ArtifactStagingDirectory)\$(Deployment.ConfigureServerFolderName)" -PathType Container) -eq $false) { - #New-Item -ItemType Directory -Path "$(Build.ArtifactStagingDirectory)\$(Deployment.ConfigureServerFolderName)" - #} - - Write-Output ("Copy signing key from $(DownloadSecureFile.secureFilePath) to $projectLocalPath") - Copy-Item "$(DownloadSecureFile.secureFilePath)" -Destination "$projectLocalPath" - - Write-Output ("Copy Microsoft.SharePoint.dll from Azure storage account") - $azureContext = New-AzureStorageContext $(AzureStorageAccountName) $(AzureStorageAccountKey) - $azureShare = Get-AzureStorageShare $(AzureStorageShareName) -Context $azureContext - Get-AzureStorageFileContent -Share $azureShare -Path "$azureStorageBaseDirectory\SharePoint 2013\Microsoft.SharePoint.dll" "$projectLocalPath\Microsoft.SharePoint.dll" - Write-Output ("Add Microsoft.SharePoint.dll to the GAC") - [System.Reflection.Assembly]::Load("System.EnterpriseServices, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a") - $publish = New-Object System.EnterpriseServices.Internal.Publish - $publish.GacInstall("$projectLocalPath\Microsoft.SharePoint.dll") - - Write-Output ("Copy integration tests data from the Azure storage account") - # Create the destination directory - if ((Test-Path -Path $devTestLabsLocalPath -PathType Container) -eq $false) { - New-Item -ItemType Directory -Path $devTestLabsLocalPath - } - $azurePath = Join-Path -Path $azureStorageBaseDirectory -ChildPath "$(Tests.DataFolderName)" - Get-AzureStorageFile -ShareName $(AzureStorageShareName) -Context $azureContext -Path $azurePath | Get-AzureStorageFile | ?{$_.GetType().Name -eq "CloudFile"} | Get-AzureStorageFileContent -Destination $devTestLabsLocalPath - - displayName: 'Import resources' - - - task: automagically.DownloadFile.DownloadFile.DownloadFile@1 - displayName: 'Download ConfigureLab.ps1' - inputs: - FileUrl: 'https://raw.githubusercontent.com/Yvand/AzureRM-Templates/master/DevTestLabs-Artifacts/manage-artifacts/ConfigureLab.ps1' - DestinationFolder: '$(Build.ArtifactStagingDirectory)\$(Deployment.ConfigureServerFolderName)' - - - task: automagically.DownloadFile.DownloadFile.DownloadFile@1 - displayName: 'Download ConfigureLab.psm1' - inputs: - FileUrl: 'https://raw.githubusercontent.com/Yvand/AzureRM-Templates/master/DevTestLabs-Artifacts/manage-artifacts/ConfigureLab.psm1' - DestinationFolder: '$(Build.ArtifactStagingDirectory)\$(Deployment.ConfigureServerFolderName)' - - - task: NuGetToolInstaller@0 - displayName: 'Use NuGet 4.4.1' - inputs: - versionSpec: 4.4.1 - - - task: NuGetCommand@2 - displayName: 'NuGet restore' - inputs: - restoreSolution: '$(SolutionFileName)' - - - task: bleddynrichards.Assembly-Info-Task.Assembly-Info-Task.Assembly-Info-NetFramework@2 - displayName: 'Set $(system.teamProject) assemblies manifest' - inputs: - FileNames: '**\AssemblyInfo.cs' - Title: $(system.teamProject) - Product: $(system.teamProject) - Description: '$(ProductDescription)' - Company: GitHub.com/Yvand - Copyright: 'Copyright © $(date:YYYY) Yvan Duhamel, All rights reserved' - Trademark: '$(system.teamProject)' - VersionNumber: 1.0.0.0 - FileVersionNumber: '$(Build.BuildNumber)' - InformationalVersion: '$(Build.BuildNumber)' - - - task: VSBuild@1 - displayName: 'Build $(system.teamProject) solution' - inputs: - solution: '$(SolutionFileName)' - msbuildArgs: '/p:IsPackaging=true' - platform: '$(BuildPlatform)' - configuration: '$(configuration)' - msbuildArchitecture: x64 - - - task: CopyFiles@2 - displayName: 'Copy binaries to artifacts' - inputs: - SourceFolder: '$(Build.SourcesDirectory)' - Contents: | - $(system.teamProject)/bin/$(configuration)/?($(system.teamProject).*) - $(system.teamProject).Tests/bin/$(configuration)/?(*.dll) - TargetFolder: '$(Build.ArtifactStagingDirectory)' - - - task: CopyFiles@2 - displayName: 'Copy dependencies to artifacts' - inputs: - SourceFolder: '$(Build.SourcesDirectory)' - Contents: | - $(system.teamProject)/bin/$(configuration)/?(*.dll) - TargetFolder: '$(Build.ArtifactStagingDirectory)/dependencies' - - - task: PublishBuildArtifacts@1 - displayName: 'Publish Artifact: drop' - inputs: - pathtoPublish: '$(Build.ArtifactStagingDirectory)' - artifactName: 'drop_$(configuration)' diff --git a/azure-pipelines/ci-create-dtl-environment.yml b/azure-pipelines/ci-create-dtl-environment.yml deleted file mode 100644 index 658ef1ea..00000000 --- a/azure-pipelines/ci-create-dtl-environment.yml +++ /dev/null @@ -1,21 +0,0 @@ -resources: -- repo: self - -jobs: -- job: CreateTestEnvironment - displayName: Create test environment - timeoutInMinutes: 90 - pool: - vmImage: 'windows-latest' - steps: - - checkout: none #skip checking out the default repository resource - - task: ms-azuredevtestlabs.tasks.azure-dtl-task-createEnvironment.AzureDevTestLabsCreateEnvironment@3 - displayName: 'Create Azure DevTest Labs Environment' - inputs: - azureSubscription: '$(DevTestLabs.AzureConnectionName)' - LabId: '/subscriptions/$(DevTestLabs.AzureSubscriptionId)/resourceGroups/$(DevTestLabs.LabName)/providers/Microsoft.DevTestLab/labs/$(DevTestLabs.LabName)' - RepositoryId: '/subscriptions/$(DevTestLabs.AzureSubscriptionId)/resourcegroups/$(DevTestLabs.LabName)/providers/microsoft.devtestlab/labs/$(DevTestLabs.LabName)/artifactsources/$(DevTestLabs.RepoID)' - TemplateId: '/subscriptions/$(DevTestLabs.AzureSubscriptionId)/resourceGroups/$(DevTestLabs.LabName)/providers/Microsoft.DevTestLab/labs/$(DevTestLabs.LabName)/artifactSources/$(DevTestLabs.RepoID)/armTemplates/$(DevTestLabs.ARMTemplateName)' - EnvironmentName: 'Tests-$(system.teamProject)' - ParameterOverrides: "-provisionSharePoint2013 $(Deployment.ProvisionSharePoint2013) -provisionSharePoint2016 $(Deployment.ProvisionSharePoint2016) -provisionSharePoint2019 $(Deployment.ProvisionSharePoint2019) -provisionSharePointSubscription $(Deployment.ProvisionSharePointSubscription) -adminUserName '$(Deployment.AdminUserName)' -adminPassword '$(Deployment.AdminPassword)' -serviceAccountsPassword '$(Deployment.ServiceAccountsPassword)' -addPublicIPAddressToEachVM true -configureADFS true -addAzureBastion true -enableHybridBenefitServerLicenses true -enableAutomaticUpdates false" - timeoutInMinutes: 90 diff --git a/custom-claims-provider-samples/CustomClaimsProvider/ADMIN/SharePointProjectItem.spdata b/custom-claims-provider-samples/CustomClaimsProvider/ADMIN/SharePointProjectItem.spdata new file mode 100644 index 00000000..42b38d5b --- /dev/null +++ b/custom-claims-provider-samples/CustomClaimsProvider/ADMIN/SharePointProjectItem.spdata @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/custom-claims-provider-samples/CustomClaimsProvider/CustomClaimsProvider.csproj b/custom-claims-provider-samples/CustomClaimsProvider/CustomClaimsProvider.csproj new file mode 100644 index 00000000..ab675b36 --- /dev/null +++ b/custom-claims-provider-samples/CustomClaimsProvider/CustomClaimsProvider.csproj @@ -0,0 +1,139 @@ + + + + Debug + AnyCPU + {CC278266-3F09-4908-BCE8-725D2AA9153E} + Library + Properties + CustomClaimsProvider + CustomClaimsProvider + v4.8 + 19.0 + 512 + {C1CDDADD-2546-481F-9697-4EA41081F2FC};{14822709-B5A1-4724-98CA-57A101D1B079};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + 15.0 + 14.1 + False + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + false + x64 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + false + + + true + + + key.snk + + + + + + + False + ..\Yvand.EntraCP.dll + + + + + + EntraCP.Custom.ClaimsProvider.feature + + + + + + {956d496d-b13e-4306-9550-b8f56d869023} + + + {f3dbfba8-995d-41a4-a06b-b3f188a81106} + + + + {034f2ce9-76ca-4b10-a136-85143ef303c9} + + + Package.package + + + + + EntraCP.Custom.ClaimsProvider.feature + + + + + 1.12.0 + + + 5.56.0 + + + + + 10.0 + $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) + + + + "C:\Program Files (x86)\Microsoft SDKs\Windows\v10.0A\bin\NETFX 4.8 Tools\x64\gacutil.exe" /f /i "$(TargetPath)" +copy /Y "$(TargetDir)Azure.Core.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Azure.Identity.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Bcl.AsyncInterfaces.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Graph.Core.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Graph.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Identity.Client.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Identity.Client.Extensions.Msal.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.IdentityModel.Abstractions.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.IdentityModel.JsonWebTokens.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.IdentityModel.Logging.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.IdentityModel.Protocols.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.IdentityModel.Protocols.OpenIdConnect.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.IdentityModel.Tokens.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Kiota.Abstractions.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Kiota.Authentication.Azure.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Kiota.Http.HttpClientLibrary.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Kiota.Serialization.Form.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Kiota.Serialization.Json.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Kiota.Serialization.Multipart.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Microsoft.Kiota.Serialization.Text.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Std.UriTemplate.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Buffers.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.ClientModel.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Diagnostics.DiagnosticSource.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.IdentityModel.Tokens.Jwt.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.IO.FileSystem.AccessControl.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Memory.Data.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Memory.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Net.Http.WinHttpHandler.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Numerics.Vectors.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Runtime.CompilerServices.Unsafe.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Security.AccessControl.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Security.Cryptography.ProtectedData.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Security.Principal.Windows.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Text.Encodings.Web.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Text.Json.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.Threading.Tasks.Extensions.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)System.ValueTuple.dll" $(ProjectDir)\bin +copy /Y "$(TargetDir)Yvand.EntraCP.dll" $(ProjectDir)\bin + + \ No newline at end of file diff --git a/custom-claims-provider-samples/CustomClaimsProvider/CustomClaimsProvider.sln b/custom-claims-provider-samples/CustomClaimsProvider/CustomClaimsProvider.sln new file mode 100644 index 00000000..b6bde26b --- /dev/null +++ b/custom-claims-provider-samples/CustomClaimsProvider/CustomClaimsProvider.sln @@ -0,0 +1,27 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.10.35013.160 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CustomClaimsProvider", "CustomClaimsProvider.csproj", "{CC278266-3F09-4908-BCE8-725D2AA9153E}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CC278266-3F09-4908-BCE8-725D2AA9153E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CC278266-3F09-4908-BCE8-725D2AA9153E}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CC278266-3F09-4908-BCE8-725D2AA9153E}.Debug|Any CPU.Deploy.0 = Debug|Any CPU + {CC278266-3F09-4908-BCE8-725D2AA9153E}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CC278266-3F09-4908-BCE8-725D2AA9153E}.Release|Any CPU.Build.0 = Release|Any CPU + {CC278266-3F09-4908-BCE8-725D2AA9153E}.Release|Any CPU.Deploy.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {75E70229-6D16-4FD1-B776-1D3CF6994E65} + EndGlobalSection +EndGlobal diff --git a/custom-claims-provider-samples/CustomClaimsProvider/EntraCP_Custom.cs b/custom-claims-provider-samples/CustomClaimsProvider/EntraCP_Custom.cs new file mode 100644 index 00000000..e994989c --- /dev/null +++ b/custom-claims-provider-samples/CustomClaimsProvider/EntraCP_Custom.cs @@ -0,0 +1,37 @@ +using System.Collections.Generic; +using Yvand.EntraClaimsProvider; +using Yvand.EntraClaimsProvider.Configuration; + +namespace CustomClaimsProvider +{ + public class EntraCP_Custom : EntraCP + { + /// + /// Sets the name of the claims provider, also set in (Get-SPTrustedIdentityTokenIssuer).ClaimProviderName property + /// + public new const string ClaimsProviderName = "EntraCP_Custom"; + + /// + /// Do not remove or change this property + /// + public override string Name => ClaimsProviderName; + + public EntraCP_Custom(string displayName) : base(displayName) + { + } + + public override IEntraIDProviderSettings GetSettings() + { + ClaimsProviderSettings settings = ClaimsProviderSettings.GetDefaultSettings(ClaimsProviderName); + EntraIDTenant tenant = new EntraIDTenant + { + AzureCloud = AzureCloudName.AzureGlobal, + Name = "TENANTNAME.onmicrosoft.com", + ClientId = "CLIENTID", + ClientSecret = "CLIENTSECRET", + }; + settings.EntraIDTenants = new List() { tenant }; + return settings; + } + } +} diff --git a/custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.ClaimsProvider.Template.xml b/custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.ClaimsProvider.Template.xml new file mode 100644 index 00000000..c27273d5 --- /dev/null +++ b/custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.ClaimsProvider.Template.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.ClaimsProvider.feature b/custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.ClaimsProvider.feature new file mode 100644 index 00000000..2736cb58 --- /dev/null +++ b/custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.ClaimsProvider.feature @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.EventReceiver.cs b/custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.EventReceiver.cs new file mode 100644 index 00000000..12a70a39 --- /dev/null +++ b/custom-claims-provider-samples/CustomClaimsProvider/Features/EntraCP.Custom.ClaimsProvider/EntraCP.Custom.EventReceiver.cs @@ -0,0 +1,74 @@ +using Microsoft.SharePoint; +using Microsoft.SharePoint.Administration; +using Microsoft.SharePoint.Administration.Claims; +using System; +using System.Runtime.InteropServices; +using System.Security.Permissions; +using Yvand.EntraClaimsProvider.Logging; + +namespace CustomClaimsProvider.Features +{ + /// + /// This class handles events raised during feature activation, deactivation, installation, uninstallation, and upgrade. + /// + /// + /// The GUID attached to this class may be used during packaging and should not be modified. + /// + + [Guid("09a62d43-b866-4ff1-bd8b-a194c6dcf80c")] + public class EntraCPCustomEventReceiver : SPClaimProviderFeatureReceiver + { + public override string ClaimProviderAssembly => typeof(EntraCP_Custom).Assembly.FullName; + + public override string ClaimProviderDescription => EntraCP_Custom.ClaimsProviderName; + + public override string ClaimProviderDisplayName => EntraCP_Custom.ClaimsProviderName; + + public override string ClaimProviderType => typeof(EntraCP_Custom).FullName; + + public override void FeatureActivated(SPFeatureReceiverProperties properties) + { + ExecBaseFeatureActivated(properties); + } + + private void ExecBaseFeatureActivated(Microsoft.SharePoint.SPFeatureReceiverProperties properties) + { + // Wrapper function for base FeatureActivated. + // Used because base keywork can lead to unverifiable code inside lambda expression + base.FeatureActivated(properties); + SPSecurity.RunWithElevatedPrivileges((SPSecurity.CodeToRunElevated)delegate () + { + try + { + Logger svc = Logger.Local; + Logger.Log($"[{EntraCP_Custom.ClaimsProviderName}] Activating farm-scoped feature for claims provider \"{EntraCP_Custom.ClaimsProviderName}\"", TraceSeverity.High, EventSeverity.Information, TraceCategory.Configuration); + } + catch (Exception ex) + { + Logger.LogException((string)EntraCP_Custom.ClaimsProviderName, $"activating farm-scoped feature for claims provider \"{EntraCP_Custom.ClaimsProviderName}\"", TraceCategory.Configuration, ex); + } + }); + } + + public override void FeatureUninstalling(SPFeatureReceiverProperties properties) + { + } + + public override void FeatureDeactivating(SPFeatureReceiverProperties properties) + { + SPSecurity.RunWithElevatedPrivileges((SPSecurity.CodeToRunElevated)delegate () + { + try + { + Logger.Log($"[{EntraCP_Custom.ClaimsProviderName}] Deactivating farm-scoped feature for claims provider \"{EntraCP_Custom.ClaimsProviderName}\": Removing claims provider from the farm (but not its configuration)", TraceSeverity.High, EventSeverity.Information, TraceCategory.Configuration); + base.RemoveClaimProvider((string)EntraCP_Custom.ClaimsProviderName); + } + catch (Exception ex) + { + Logger.LogException((string)EntraCP_Custom.ClaimsProviderName, $"deactivating farm-scoped feature for claims provider \"{EntraCP_Custom.ClaimsProviderName}\"", TraceCategory.Configuration, ex); + } + }); + } + + } +} diff --git a/custom-claims-provider-samples/CustomClaimsProvider/Package/Package.Template.xml b/custom-claims-provider-samples/CustomClaimsProvider/Package/Package.Template.xml new file mode 100644 index 00000000..640ff0f3 --- /dev/null +++ b/custom-claims-provider-samples/CustomClaimsProvider/Package/Package.Template.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/custom-claims-provider-samples/CustomClaimsProvider/Package/Package.package b/custom-claims-provider-samples/CustomClaimsProvider/Package/Package.package new file mode 100644 index 00000000..b012b45e --- /dev/null +++ b/custom-claims-provider-samples/CustomClaimsProvider/Package/Package.package @@ -0,0 +1,47 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/custom-claims-provider-samples/CustomClaimsProvider/Properties/AssemblyInfo.cs b/custom-claims-provider-samples/CustomClaimsProvider/Properties/AssemblyInfo.cs new file mode 100644 index 00000000..bebe4c07 --- /dev/null +++ b/custom-claims-provider-samples/CustomClaimsProvider/Properties/AssemblyInfo.cs @@ -0,0 +1,38 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Security; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("EntraCP.Custom")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("EntraCP.Custom")] +[assembly: AssemblyCopyright("Copyright © 2024")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("cc278266-3f09-4908-bce8-725d2aa9153e")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] + diff --git a/custom-claims-provider-samples/CustomClaimsProvider/README.md b/custom-claims-provider-samples/CustomClaimsProvider/README.md new file mode 100644 index 00000000..a854b433 --- /dev/null +++ b/custom-claims-provider-samples/CustomClaimsProvider/README.md @@ -0,0 +1,9 @@ +# Sample with a hard-coded configuration and a manual reference to EntraCP + +This project shows how to create a claims provider that inherits EntraCP. It uses a simple, hard-coded configuration to specify the tenant. + +> [!WARNING] +> Do NOT deploy this solution in a SharePoint farm that already has EntraCP deployed, unless both use **exactly** the same versions of NuGet dependencies. If they use different versions, that may cause errors when loading DLLs, due to mismatches with the assembly bindings in the machine.config file. + +> [!IMPORTANT] +> You need to manually add a reference to `Yvand.EntraCP.dll`, and its version will determine the version of the Nuget packages `Azure.Identity` and `Microsoft.Graph` you should use in your project, because that will allow you to reuse the same assembly bindings provided in the file `assembly-bindings.config` and avoid the tedious task of figuring them all out by yourself. diff --git a/custom-claims-provider-samples/README.md b/custom-claims-provider-samples/README.md new file mode 100644 index 00000000..e0a0728a --- /dev/null +++ b/custom-claims-provider-samples/README.md @@ -0,0 +1,4 @@ +# Sample projects for developers + +This folder contains Visual Studio projects that developers can use to create their custom clainms providers based on EntraCP. +This is useful only for specific needs. diff --git a/graph-requests-samples/.env.example b/graph-requests-samples/.env.example new file mode 100644 index 00000000..1393d3d6 --- /dev/null +++ b/graph-requests-samples/.env.example @@ -0,0 +1,3 @@ +TENANTPREFIX= +CLIENTID= +CLIENTSECRET= \ No newline at end of file diff --git a/graph-requests-samples/README.md b/graph-requests-samples/README.md new file mode 100644 index 00000000..21e8e237 --- /dev/null +++ b/graph-requests-samples/README.md @@ -0,0 +1,13 @@ +# Replay the requests sent to Microsoft Graph by EntraCP + +This is the root folder of a [Bruno](https://www.usebruno.com/) collection (an alternative API debugger to Postman), that contains the typical requests sent to Microsoft Graph by EntraCP. + +To use it: + +* Clone this folder +* Rename/copy the file `.env.example` to `.env` and edit it with your tenant data +* Install [Bruno](https://www.usebruno.com/) and open this collection in Bruno +* Load environment `entracp-environment` +* Open request authentication > get access token. Click tab `Auth` and `Get Access Token` + +You can not replay the requests. diff --git a/graph-requests-samples/Search/replay search users and groups using batch.bru b/graph-requests-samples/Search/replay search users and groups using batch.bru new file mode 100644 index 00000000..7a3873ee --- /dev/null +++ b/graph-requests-samples/Search/replay search users and groups using batch.bru @@ -0,0 +1,36 @@ +meta { + name: replay search users and groups using batch + type: http + seq: 8 +} + +post { + url: https://graph.microsoft.com/v1.0/$batch + body: json + auth: inherit +} + +body:json { + { + "requests": [ + { + "id": "9d99b7fa-428a-4025-acd5-8947f35dbffc", + "url": "/users?%24top=30\u0026%24filter=%28%20%28startswith%28UserPrincipalName%2C%20%27admini%27%29%20and%20UserType%20eq%20%27Member%27%29%20or%20%28startswith%28Mail%2C%20%27admini%27%29%20and%20UserType%20eq%20%27Guest%27%29%20%29%20or%20startswith%28DisplayName%2C%20%27admini%27%29%20or%20startswith%28GivenName%2C%20%27admini%27%29%20or%20startswith%28Surname%2C%20%27admini%27%29%20or%20startswith%28Mail%2C%20%27admini%27%29\u0026%24count=true\u0026%24select=UserType,Mail,UserPrincipalName,DisplayName,GivenName,Surname,Mail,DisplayName,Mail,MobilePhone,JobTitle,Department,OfficeLocation", + "method": "GET", + "headers": { + "Accept": "application/json", + "ConsistencyLevel": "eventual" + } + }, + { + "id": "7937d970-ad05-42af-96bf-559fe776a986", + "url": "/groups?%24top=30\u0026%24filter=startswith%28DisplayName%2C%20%27admini%27%29\u0026%24count=true\u0026%24select=Id,securityEnabled,DisplayName,DisplayName,Mail", + "method": "GET", + "headers": { + "Accept": "application/json", + "ConsistencyLevel": "eventual" + } + } + ] + } +} diff --git a/graph-requests-samples/augmentation/1- get user by userprincipalname.bru b/graph-requests-samples/augmentation/1- get user by userprincipalname.bru new file mode 100644 index 00000000..039724d9 --- /dev/null +++ b/graph-requests-samples/augmentation/1- get user by userprincipalname.bru @@ -0,0 +1,23 @@ +meta { + name: 1- Get user by UserPrincipalName + type: http + seq: 1 +} + +get { + url: https://graph.microsoft.com/v1.0/users?$select=UserType, Id, Mail, UserPrincipalName, DisplayName, GivenName, Surname, DisplayName, Mail, MobilePhone, JobTitle, Department, OfficeLocation&$filter=UserPrincipalName eq '{{entityUpn}}' + body: none + auth: inherit +} + +params:query { + $select: UserType, Id, Mail, UserPrincipalName, DisplayName, GivenName, Surname, DisplayName, Mail, MobilePhone, JobTitle, Department, OfficeLocation + $filter: UserPrincipalName eq '{{entityUpn}}' + ~$filter: UserPrincipalName eq 'AdeleV@{{tenantPrefix}}.OnMicrosoft.com' + ~$filter: UserPrincipalName eq 'yvand_microsoft.com%23EXT%23@{{tenantPrefix}}.onmicrosoft.com' +} + +vars:post-response { + userId: res.body.value[0].id +} + diff --git a/graph-requests-samples/augmentation/2- get group membership.bru b/graph-requests-samples/augmentation/2- get group membership.bru new file mode 100644 index 00000000..86c64054 --- /dev/null +++ b/graph-requests-samples/augmentation/2- get group membership.bru @@ -0,0 +1,15 @@ +meta { + name: 2- Get group membership + type: http + seq: 2 +} + +post { + url: https://graph.microsoft.com/v1.0/users/{{userId}}/microsoft.graph.getMemberGroups + body: json + auth: inherit +} + +body:json { + {"securityEnabledOnly":false} +} diff --git a/graph-requests-samples/authentication/get access token.bru b/graph-requests-samples/authentication/get access token.bru new file mode 100644 index 00000000..8fd6f009 --- /dev/null +++ b/graph-requests-samples/authentication/get access token.bru @@ -0,0 +1,22 @@ +meta { + name: get access token + type: http + seq: 1 +} + +get { + url: + auth: oauth2 +} + +auth:oauth2 { + grant_type: client_credentials + access_token_url: https://login.microsoftonline.com/{{tenantPrefix}}.onmicrosoft.com/oauth2/v2.0/token + client_id: {{clientId}} + client_secret: {{clientSecret}} + scope: https://graph.microsoft.com/.default +} + +vars:post-response { + accessToken: res.body.access_token +} diff --git a/graph-requests-samples/bruno.json b/graph-requests-samples/bruno.json new file mode 100644 index 00000000..8b19c25b --- /dev/null +++ b/graph-requests-samples/bruno.json @@ -0,0 +1,9 @@ +{ + "version": "1", + "name": "EntraCP", + "type": "collection", + "ignore": [ + "node_modules", + ".git" + ] +} \ No newline at end of file diff --git a/graph-requests-samples/collection.bru b/graph-requests-samples/collection.bru new file mode 100644 index 00000000..15566b2c --- /dev/null +++ b/graph-requests-samples/collection.bru @@ -0,0 +1,15 @@ +headers { + Authorization: Bearer {{accessToken}} +} + +auth { + mode: oauth2 +} + +auth:oauth2 { + grant_type: client_credentials + access_token_url: https://login.microsoftonline.com/{{tenantPrefix}}.onmicrosoft.com/oauth2/v2.0/token + client_id: {{clientId}} + client_secret: {{clientSecret}} + scope: https://graph.microsoft.com/.default +} diff --git a/graph-requests-samples/environments/entracp-environment.bru b/graph-requests-samples/environments/entracp-environment.bru new file mode 100644 index 00000000..7fd12a4a --- /dev/null +++ b/graph-requests-samples/environments/entracp-environment.bru @@ -0,0 +1,7 @@ +vars { + tenantPrefix: {{process.env.TENANTPREFIX}} + entityUpn: clouduser1@{{process.env.TENANTPREFIX}}.onmicrosoft.com + entityStartsWithValue: cloud + clientId: {{process.env.CLIENTID}} + clientSecret: {{process.env.CLIENTSECRET}} +} diff --git a/graph-requests-samples/search/list members of a group.bru b/graph-requests-samples/search/list members of a group.bru new file mode 100644 index 00000000..c9c48e59 --- /dev/null +++ b/graph-requests-samples/search/list members of a group.bru @@ -0,0 +1,19 @@ +meta { + name: List members of a group + type: http + seq: 5 +} + +get { + url: https://graph.microsoft.com/v1.0/groups/{{groupId}}/members/microsoft.graph.user?$select=id, userPrincipalName, mail + body: none + auth: inherit +} + +params:query { + $select: id, userPrincipalName, mail +} + +headers { + Accept: application/json +} diff --git a/graph-requests-samples/search/replay search users and groups using batch.bru b/graph-requests-samples/search/replay search users and groups using batch.bru new file mode 100644 index 00000000..7a3873ee --- /dev/null +++ b/graph-requests-samples/search/replay search users and groups using batch.bru @@ -0,0 +1,36 @@ +meta { + name: replay search users and groups using batch + type: http + seq: 8 +} + +post { + url: https://graph.microsoft.com/v1.0/$batch + body: json + auth: inherit +} + +body:json { + { + "requests": [ + { + "id": "9d99b7fa-428a-4025-acd5-8947f35dbffc", + "url": "/users?%24top=30\u0026%24filter=%28%20%28startswith%28UserPrincipalName%2C%20%27admini%27%29%20and%20UserType%20eq%20%27Member%27%29%20or%20%28startswith%28Mail%2C%20%27admini%27%29%20and%20UserType%20eq%20%27Guest%27%29%20%29%20or%20startswith%28DisplayName%2C%20%27admini%27%29%20or%20startswith%28GivenName%2C%20%27admini%27%29%20or%20startswith%28Surname%2C%20%27admini%27%29%20or%20startswith%28Mail%2C%20%27admini%27%29\u0026%24count=true\u0026%24select=UserType,Mail,UserPrincipalName,DisplayName,GivenName,Surname,Mail,DisplayName,Mail,MobilePhone,JobTitle,Department,OfficeLocation", + "method": "GET", + "headers": { + "Accept": "application/json", + "ConsistencyLevel": "eventual" + } + }, + { + "id": "7937d970-ad05-42af-96bf-559fe776a986", + "url": "/groups?%24top=30\u0026%24filter=startswith%28DisplayName%2C%20%27admini%27%29\u0026%24count=true\u0026%24select=Id,securityEnabled,DisplayName,DisplayName,Mail", + "method": "GET", + "headers": { + "Accept": "application/json", + "ConsistencyLevel": "eventual" + } + } + ] + } +} diff --git a/graph-requests-samples/search/run a batch request.bru b/graph-requests-samples/search/run a batch request.bru new file mode 100644 index 00000000..f5c5c6c5 --- /dev/null +++ b/graph-requests-samples/search/run a batch request.bru @@ -0,0 +1,44 @@ +meta { + name: Run a batch request + type: http + seq: 7 +} + +post { + url: https://graph.microsoft.com/v1.0/$batch + body: json + auth: inherit +} + +body:json { + { + "requests": [ + { + "id": "9d99b7fa-428a-4025-acd5-8947f35dbffc", + "url": "/users?%24top=30\u0026%24filter=%28%20%28startswith%28UserPrincipalName%2C%20%27{{entityStartsWithValue}}%27%29%20and%20UserType%20eq%20%27Member%27%29%20or%20%28startswith%28Mail%2C%20%27{{entityStartsWithValue}}%27%29%20and%20UserType%20eq%20%27Guest%27%29%20%29%20or%20startswith%28DisplayName%2C%20%27{{entityStartsWithValue}}%27%29%20or%20startswith%28GivenName%2C%20%27{{entityStartsWithValue}}%27%29%20or%20startswith%28Surname%2C%20%27{{entityStartsWithValue}}%27%29%20or%20startswith%28Mail%2C%20%27{{entityStartsWithValue}}%27%29\u0026%24count=true\u0026%24select=UserType,Mail,UserPrincipalName,DisplayName,GivenName,Surname,Mail,DisplayName,Mail,MobilePhone,JobTitle,Department,OfficeLocation", + "method": "GET", + "headers": { + "Accept": "application/json", + "ConsistencyLevel": "eventual" + } + }, + { + "id": "7937d970-ad05-42af-96bf-559fe776a986", + "url": "/groups?%24top=30\u0026%24filter=startswith%28DisplayName%2C%20%27{{entityStartsWithValue}}%27%29\u0026%24count=true\u0026%24select=Id,securityEnabled,DisplayName,DisplayName,Mail", + "method": "GET", + "headers": { + "Accept": "application/json", + "ConsistencyLevel": "eventual" + } + }, + { + "id": "b762c874-eccd-4fd9-abf8-0334561bfd5b", + "url": "/groups/{{groupId}}/members/microsoft.graph.user?$select=id, userPrincipalName, mail", + "method": "GET", + "headers": { + "Accept": "application/json" + } + } + ] + } +} diff --git a/graph-requests-samples/search/search groups.bru b/graph-requests-samples/search/search groups.bru new file mode 100644 index 00000000..85690af4 --- /dev/null +++ b/graph-requests-samples/search/search groups.bru @@ -0,0 +1,16 @@ +meta { + name: Search groups + type: http + seq: 4 +} + +get { + url: https://graph.microsoft.com/v1.0/groups?$select=Id, DisplayName&$filter=startswith(DisplayName,'{{entityStartsWithValue}}') + body: none + auth: inherit +} + +params:query { + $select: Id, DisplayName + $filter: startswith(DisplayName,'{{entityStartsWithValue}}') +} diff --git a/graph-requests-samples/search/search users copy.bru b/graph-requests-samples/search/search users copy.bru new file mode 100644 index 00000000..4081adec --- /dev/null +++ b/graph-requests-samples/search/search users copy.bru @@ -0,0 +1,27 @@ +meta { + name: Search users Copy + type: http + seq: 3 +} + +get { + url: https://graph.microsoft.com/v1.0/users?$filter=startswith(UserPrincipalName, 'onprem') + body: none + auth: inherit +} + +params:query { + $filter: startswith(UserPrincipalName, 'onprem') + ~$top: 30 + ~$count: true + ~$select: UserType,Mail,UserPrincipalName,BusinessPhones,DisplayName,GivenName,Surname,Mail,DisplayName,Mail,MobilePhone,JobTitle,Department,OfficeLocation + ~$filter: ( (startswith(UserPrincipalName, '+1 ') and UserType eq 'Member') or (startswith(Mail, '+1 ') and UserType eq 'Guest') ) or startswith(BusinessPhones, '+1 ') or startswith(DisplayName, '+1 ') or startswith(GivenName, '+1 ') or startswith(Surname, '+1 ') or startswith(Mail, '+1 ') + ~$filter: businessPhones/any(p:startsWith(p, '%2B1')) + ~$filter: businessPhones/any(s:s eq '1234') + ~$filter: identities/any(c:c/issuerAssignedId:startsWith(c:c/issuerAssignedId, 'admin')) + ~$filter: identities/any(c:c/issuerAssignedId eq 'admin@{{tenantPrefix}}.onmicrosoft.com' and c/issuer eq '{{tenantPrefix}}.onmicrosoft.com') +} + +headers { + ConsistencyLevel: Eventual +} diff --git a/graph-requests-samples/search/search users.bru b/graph-requests-samples/search/search users.bru new file mode 100644 index 00000000..a2c58674 --- /dev/null +++ b/graph-requests-samples/search/search users.bru @@ -0,0 +1,20 @@ +meta { + name: Search users + type: http + seq: 1 +} + +get { + url: https://graph.microsoft.com/v1.0/users?$select=UserType, Mail, UserPrincipalName&$filter=accountEnabled eq true and startswith(UserPrincipalName,'{{entityStartsWithValue}}')&$expand=memberOf($select=id, displayName)&$top=30 + body: none + auth: inherit +} + +params:query { + $select: UserType, Mail, UserPrincipalName + $filter: accountEnabled eq true and startswith(UserPrincipalName,'{{entityStartsWithValue}}') + $expand: memberOf($select=id, displayName) + $top: 30 + ~$select: UserType, Mail, UserPrincipalName, DisplayName, GivenName, Surname, DisplayName, Mail, MobilePhone, JobTitle, Department, OfficeLocation + ~$filter: accountEnabled eq true and (startswith(UserPrincipalName,'{{entityStartsWithValue}}') or startswith(DisplayName,'{{entityStartsWithValue}}') or startswith(GivenName,'{{entityStartsWithValue}}') or startswith(Surname,'{{entityStartsWithValue}}')) +} diff --git a/graph-requests-samples/search/show a group.bru b/graph-requests-samples/search/show a group.bru new file mode 100644 index 00000000..7ef83702 --- /dev/null +++ b/graph-requests-samples/search/show a group.bru @@ -0,0 +1,19 @@ +meta { + name: Show a group + type: http + seq: 6 +} + +get { + url: https://graph.microsoft.com/v1.0/groups/{{groupId}}?$select=id, displayName + body: none + auth: inherit +} + +params:query { + $select: id, displayName +} + +headers { + Accept: application/json +} diff --git a/graph-requests-samples/unit tests/extension attribute/extension properties/list extension properties.bru b/graph-requests-samples/unit tests/extension attribute/extension properties/list extension properties.bru new file mode 100644 index 00000000..0fcae2be --- /dev/null +++ b/graph-requests-samples/unit tests/extension attribute/extension properties/list extension properties.bru @@ -0,0 +1,11 @@ +meta { + name: List extension properties + type: http + seq: 1 +} + +post { + url: https://graph.microsoft.com/v1.0/directoryObjects/getAvailableExtensionProperties + body: json + auth: inherit +} diff --git a/graph-requests-samples/unit tests/extension attribute/service principal policies/assign a claimsmappingpolicy to a service principal.bru b/graph-requests-samples/unit tests/extension attribute/service principal policies/assign a claimsmappingpolicy to a service principal.bru new file mode 100644 index 00000000..537b9de2 --- /dev/null +++ b/graph-requests-samples/unit tests/extension attribute/service principal policies/assign a claimsmappingpolicy to a service principal.bru @@ -0,0 +1,18 @@ +meta { + name: Assign a claimsMappingPolicy to a service principal + type: http + seq: 2 +} + +post { + url: https://graph.microsoft.com/v1.0/servicePrincipals(appId='7ade56f8-12b0-472b-a923-102874ee083a')/claimsMappingPolicies/$ref + body: json + auth: inherit +} + +body:json { + + { + "@odata.id":"https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies/fc3beba5-5869-4020-9366-8bf7d0e8d924" + } +} diff --git a/graph-requests-samples/unit tests/extension attribute/service principal policies/list claimsmappingpolicies assigned to a service principal copy 2.bru b/graph-requests-samples/unit tests/extension attribute/service principal policies/list claimsmappingpolicies assigned to a service principal copy 2.bru new file mode 100644 index 00000000..5ad0ae40 --- /dev/null +++ b/graph-requests-samples/unit tests/extension attribute/service principal policies/list claimsmappingpolicies assigned to a service principal copy 2.bru @@ -0,0 +1,11 @@ +meta { + name: List claimsMappingPolicies assigned to a service principal Copy 2 + type: http + seq: 1 +} + +get { + url: https://graph.microsoft.com/v1.0/servicePrincipals(appId='7ade56f8-12b0-472b-a923-102874ee083a')/claimsMappingPolicies + body: json + auth: inherit +} diff --git a/graph-requests-samples/unit tests/extension attribute/tenant policies/create a claimsmappingpolicy for saml claims copy.bru b/graph-requests-samples/unit tests/extension attribute/tenant policies/create a claimsmappingpolicy for saml claims copy.bru new file mode 100644 index 00000000..e6412dde --- /dev/null +++ b/graph-requests-samples/unit tests/extension attribute/tenant policies/create a claimsmappingpolicy for saml claims copy.bru @@ -0,0 +1,24 @@ +meta { + name: Create a claimsMappingPolicy for SAML claims Copy + type: http + seq: 2 +} + +post { + url: https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies + body: json + auth: inherit +} + +headers { + Content-type: application/json +} + +body:json { + { + "definition": [ + "{\"ClaimsMappingPolicy\": { \"Version\": 1, \"IncludeBasicClaimSet\": \"false\", \"ClaimsSchema\": [{ \"Source\": \"User\", \"ExtensionID\": \"extension_7ade56f812b0472ba923102874ee083a_extensionAttribute1\", \"SamlClaimType\": \"http://schemas.yvand.org/claims/type1\" }] }}" + ], + "displayName": "Yvand claims policy with extension attribute" + } +} diff --git a/graph-requests-samples/unit tests/extension attribute/tenant policies/delete a claimsmappingpolicy.bru b/graph-requests-samples/unit tests/extension attribute/tenant policies/delete a claimsmappingpolicy.bru new file mode 100644 index 00000000..dafcf7ae --- /dev/null +++ b/graph-requests-samples/unit tests/extension attribute/tenant policies/delete a claimsmappingpolicy.bru @@ -0,0 +1,24 @@ +meta { + name: Delete a claimsMappingPolicy + type: http + seq: 3 +} + +delete { + url: https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies/042e63eb-5d1f-44dd-8e63-af3700401f52 + body: json + auth: inherit +} + +headers { + Content-type: application/json +} + +body:json { + { + "definition": [ + "{\"ClaimsMappingPolicy\": { \"Version\": 1, \"IncludeBasicClaimSet\": \"false\", \"ClaimsSchema\": [{ \"Source\": \"User\", \"ExtensionID\": \"extension_7ade56f812b0472ba923102874ee083a_extensionAttribute1\", \"SamlClaimType\": \"http://schemas.yvand.org/claims/type1\" }] }}" + ], + "displayName": "Yvand claims policy with extension attribute" + } +} diff --git a/graph-requests-samples/unit tests/extension attribute/tenant policies/list claimsmappingpolicies in the tenant.bru b/graph-requests-samples/unit tests/extension attribute/tenant policies/list claimsmappingpolicies in the tenant.bru new file mode 100644 index 00000000..721da9e6 --- /dev/null +++ b/graph-requests-samples/unit tests/extension attribute/tenant policies/list claimsmappingpolicies in the tenant.bru @@ -0,0 +1,11 @@ +meta { + name: List claimsMappingPolicies in the tenant + type: http + seq: 1 +} + +get { + url: https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies + body: json + auth: inherit +} diff --git a/graph-requests-samples/unit tests/extension attribute/tenant policies/update a claimsmappingpolicy for saml claims copy.bru b/graph-requests-samples/unit tests/extension attribute/tenant policies/update a claimsmappingpolicy for saml claims copy.bru new file mode 100644 index 00000000..1ecc8425 --- /dev/null +++ b/graph-requests-samples/unit tests/extension attribute/tenant policies/update a claimsmappingpolicy for saml claims copy.bru @@ -0,0 +1,24 @@ +meta { + name: Update a claimsMappingPolicy for SAML claims Copy + type: http + seq: 4 +} + +patch { + url: https://graph.microsoft.com/v1.0/policies/claimsMappingPolicies/fc3beba5-5869-4020-9366-8bf7d0e8d924 + body: json + auth: inherit +} + +headers { + Content-type: application/json +} + +body:json { + { + "definition": [ + "{\"ClaimsMappingPolicy\": { \"Version\": 1, \"IncludeBasicClaimSet\": \"false\", \"ClaimsSchema\": [{ \"Source\": \"User\", \"ExtensionID\": \"extension_7ade56f812b0472ba923102874ee083a_extensionAttribute1\", \"SamlClaimType\": \"http://schemas.yvand.org/claims/type1\" }, { \"Source\": \"User\", \"ID\": \"localuserprincipalname\", \"SamlClaimType\": \"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier\" }, { \"Source\": \"User\", \"ID\": \"localuserprincipalname\", \"SamlClaimType\": \"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name\" }] }}" + ], + "displayName": "Yvand claims policy with extension attribute" + } +} diff --git a/graph-requests-samples/unit tests/extension attribute/users/list users with their extension attribute value.bru b/graph-requests-samples/unit tests/extension attribute/users/list users with their extension attribute value.bru new file mode 100644 index 00000000..0cb8b3d6 --- /dev/null +++ b/graph-requests-samples/unit tests/extension attribute/users/list users with their extension attribute value.bru @@ -0,0 +1,21 @@ +meta { + name: List users with their extension attribute value + type: http + seq: 1 +} + +get { + url: https://graph.microsoft.com/v1.0/users?$select=id, displayName, userPrincipalName, UserType, mail, givenName, extension_7ade56f812b0472ba923102874ee083a_extensionAttribute1&$filter=startswith(userPrincipalName, 'testEntraCPUser_001') + body: none + auth: inherit +} + +params:query { + $select: id, displayName, userPrincipalName, UserType, mail, givenName, extension_7ade56f812b0472ba923102874ee083a_extensionAttribute1 + $filter: startswith(userPrincipalName, 'testEntraCPUser_001') +} + +headers { + Content-Type: application/json + Accept: application/json +} diff --git a/graph-requests-samples/unit tests/extension attribute/users/update user to set the extension attribute value.bru b/graph-requests-samples/unit tests/extension attribute/users/update user to set the extension attribute value.bru new file mode 100644 index 00000000..ab8d880e --- /dev/null +++ b/graph-requests-samples/unit tests/extension attribute/users/update user to set the extension attribute value.bru @@ -0,0 +1,22 @@ +meta { + name: Update user to set the extension attribute value + type: http + seq: 2 +} + +patch { + url: https://graph.microsoft.com/v1.0/users/testEntraCPUser_001@Yvand.onmicrosoft.com + body: json + auth: inherit +} + +headers { + Content-Type: application/json + Accept: application/json +} + +body:json { + { + "extension_7ade56f812b0472ba923102874ee083a_extensionAttribute1": "value1" + } +} diff --git a/graph-requests-samples/unit tests/list test groups.bru b/graph-requests-samples/unit tests/list test groups.bru new file mode 100644 index 00000000..f78477a4 --- /dev/null +++ b/graph-requests-samples/unit tests/list test groups.bru @@ -0,0 +1,20 @@ +meta { + name: List test groups + type: http + seq: 2 +} + +get { + url: https://graph.microsoft.com/v1.0/groups?$select=ID, displayName, groupTypes, securityEnabled&$filter=startswith(DisplayName, 'testentracp') + body: none + auth: inherit +} + +params:query { + $select: ID, displayName, groupTypes, securityEnabled + $filter: startswith(DisplayName, 'testentracp') +} + +headers { + Content-Type: application/json +} diff --git a/graph-requests-samples/unit tests/list test users.bru b/graph-requests-samples/unit tests/list test users.bru new file mode 100644 index 00000000..a3c9a6d6 --- /dev/null +++ b/graph-requests-samples/unit tests/list test users.bru @@ -0,0 +1,21 @@ +meta { + name: List test users + type: http + seq: 1 +} + +get { + url: https://graph.microsoft.com/v1.0/users?$select=id, displayName, userPrincipalName, UserType, mail, givenName&$filter=startswith(userPrincipalName, 'testEntraCP') + body: none + auth: inherit +} + +params:query { + $select: id, displayName, userPrincipalName, UserType, mail, givenName + $filter: startswith(userPrincipalName, 'testEntraCP') +} + +headers { + Content-Type: application/json + Accept: application/json +} diff --git a/graph-requests-samples/unit tests/show group by id.bru b/graph-requests-samples/unit tests/show group by id.bru new file mode 100644 index 00000000..88480422 --- /dev/null +++ b/graph-requests-samples/unit tests/show group by id.bru @@ -0,0 +1,19 @@ +meta { + name: Show group by Id + type: http + seq: 3 +} + +get { + url: https://graph.microsoft.com/v1.0/groups/{{groupId}}?$select=id, displayName, groupTypes + body: none + auth: inherit +} + +params:query { + $select: id, displayName, groupTypes +} + +headers { + Content-Type: application/json +} diff --git a/graph-requests-samples/unit tests/show group members.bru b/graph-requests-samples/unit tests/show group members.bru new file mode 100644 index 00000000..d99bb0a7 --- /dev/null +++ b/graph-requests-samples/unit tests/show group members.bru @@ -0,0 +1,19 @@ +meta { + name: Show group members + type: http + seq: 4 +} + +get { + url: https://graph.microsoft.com/v1.0/groups/{{groupId}}/members?$select=userPrincipalName + body: none + auth: inherit +} + +params:query { + $select: userPrincipalName +} + +headers { + Content-Type: application/json +} diff --git a/graph-requests-samples/validation/validate group.bru b/graph-requests-samples/validation/validate group.bru new file mode 100644 index 00000000..42d65e1c --- /dev/null +++ b/graph-requests-samples/validation/validate group.bru @@ -0,0 +1,17 @@ +meta { + name: Validate group + type: http + seq: 2 +} + +get { + url: https://graph.microsoft.com/v1.0/groups?$select=Id, Id, DisplayName, Mail&$filter=Id eq '{{groupId}}'&$top=1 + body: none + auth: inherit +} + +params:query { + $select: Id, Id, DisplayName, Mail + $filter: Id eq '{{groupId}}' + $top: 1 +} diff --git a/graph-requests-samples/validation/validate user.bru b/graph-requests-samples/validation/validate user.bru new file mode 100644 index 00000000..25ecc79c --- /dev/null +++ b/graph-requests-samples/validation/validate user.bru @@ -0,0 +1,21 @@ +meta { + name: Validate user + type: http + seq: 1 +} + +get { + url: https://graph.microsoft.com/v1.0/users?$select=id, userType, mail, userPrincipalName, displayName, Mail, mobilePhone, jobTitle, department, officeLocation&$filter=( (UserPrincipalName eq '{{entityUpn}}' and UserType eq 'Member') or (Mail eq '{{EntityUPN}}' and UserType eq 'Guest') )&$top=1 + body: none + auth: inherit +} + +params:query { + $select: id, userType, mail, userPrincipalName, displayName, Mail, mobilePhone, jobTitle, department, officeLocation + $filter: ( (UserPrincipalName eq '{{entityUpn}}' and UserType eq 'Member') or (Mail eq '{{EntityUPN}}' and UserType eq 'Guest') ) + $top: 1 +} + +vars:post-response { + userId: res.body.value[0].id +}