Skip to content

Commit

Permalink
Add controls in config page to set the groups list to restrict search…
Browse files Browse the repository at this point in the history
…able users (#249)

* add controls to ui

* add plumbing

* update logging

* due to current design, minimum value for TenantDataCacheLifetimeInMinutes is 1 (not 0)
  • Loading branch information
Yvand authored Apr 22, 2024
1 parent 421f9a3 commit ca111f1
Show file tree
Hide file tree
Showing 8 changed files with 99 additions and 38 deletions.
12 changes: 6 additions & 6 deletions Yvand.EntraCP.Tests/ClaimsProviderTestsBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ protected List<EntraIdTestGroupSettings> GroupsWhichUsersMustBeMemberOfAny
{
if (_GroupsWhichUsersMustBeMemberOfAny != null) { return _GroupsWhichUsersMustBeMemberOfAny; }
_GroupsWhichUsersMustBeMemberOfAny = new List<EntraIdTestGroupSettings>();
string groupsWhichUsersMustBeMemberOfAny = Settings.GroupsWhichUsersMustBeMemberOfAny;
string groupsWhichUsersMustBeMemberOfAny = Settings.RestrictSearchableUsersByGroups;
if (!String.IsNullOrWhiteSpace(groupsWhichUsersMustBeMemberOfAny))
{
string[] groupIds = groupsWhichUsersMustBeMemberOfAny.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
Expand Down Expand Up @@ -152,18 +152,18 @@ public void TestSearchAndValidateForEntraIDUser(EntraIdTestUser entity)
}
else
{
if (!String.IsNullOrWhiteSpace(Settings.GroupsWhichUsersMustBeMemberOfAny))
if (!String.IsNullOrWhiteSpace(Settings.RestrictSearchableUsersByGroups))
{
lock (_LockVerifyIfCurrentUserShouldBeFound) // TODO: understand why this lock is necessary
{
// Test 1: Does Settings.GroupsWhichUsersMustBeMemberOfAny contain any group where all test users are members?
// 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.GroupsWhichUsersMustBeMemberOfAny contains group: \"{groupSettings.DisplayName}\" with AllTestUsersAreMembers {groupSettings.AllTestUsersAreMembers}.");
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
}
}
Expand All @@ -178,14 +178,14 @@ public void TestSearchAndValidateForEntraIDUser(EntraIdTestUser entity)
{
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.GroupsWhichUsersMustBeMemberOfAny has AllTestUsersAreMembers set to true.");
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.");
}
}
}
}
else
{
Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] Property Settings.GroupsWhichUsersMustBeMemberOfAny IsNullOrWhiteSpace.");
Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] Property Settings.RestrictSearchableUsersByGroups IsNullOrWhiteSpace.");
}

// If shouldValidate is false, user should not be found anyway so no need to do additional checks
Expand Down
16 changes: 8 additions & 8 deletions Yvand.EntraCP.Tests/FilterUsersBasedOnGroupsTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class FilterUsersBasedOnSingleGroupTests : ClaimsProviderTestsBase
public override void InitializeSettings()
{
base.InitializeSettings();
Settings.GroupsWhichUsersMustBeMemberOfAny = EntraIdTestGroupsSource.ASecurityEnabledGroup.Id;
Settings.RestrictSearchableUsersByGroups = EntraIdTestGroupsSource.ASecurityEnabledGroup.Id;
base.ApplySettings();
}

Expand Down Expand Up @@ -47,16 +47,16 @@ public override void InitializeSettings()
{
base.InitializeSettings();

// Pick the Id of 18 (max possible) random groups, and it in property GroupsWhichUsersMustBeMemberOfAny
// Pick the Id of 18 (max possible) random groups, and it in property RestrictSearchableUsersByGroups
List<string> groupIdsList = new List<string>();
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.GroupsWhichUsersMustBeMemberOfAny = String.Join(",", groupIdsList);
Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] Set property GroupsWhichUsersMustBeMemberOfAny: \"{Settings.GroupsWhichUsersMustBeMemberOfAny}\".");
Settings.RestrictSearchableUsersByGroups = String.Join(",", groupIdsList);
Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] Set property RestrictSearchableUsersByGroups: \"{Settings.RestrictSearchableUsersByGroups}\".");

base.ApplySettings();
}
Expand Down Expand Up @@ -92,10 +92,10 @@ public class DebugFilterUsersBasedOnMultipleGroupsTests : ClaimsProviderTestsBas
public override void InitializeSettings()
{
base.InitializeSettings();
Settings.GroupsWhichUsersMustBeMemberOfAny = "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.GroupsWhichUsersMustBeMemberOfAny = "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.GroupsWhichUsersMustBeMemberOfAny = "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";
Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] Set property GroupsWhichUsersMustBeMemberOfAny: \"{Settings.GroupsWhichUsersMustBeMemberOfAny}\".");
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";
Trace.TraceInformation($"{DateTime.Now:s} [{this.GetType().Name}] Set property RestrictSearchableUsersByGroups: \"{Settings.RestrictSearchableUsersByGroups}\".");
base.ApplySettings();
}

Expand Down
22 changes: 22 additions & 0 deletions Yvand.EntraCP/TEMPLATE/ADMIN/EntraCP/GlobalSettings.ascx
Original file line number Diff line number Diff line change
Expand Up @@ -386,6 +386,28 @@
<wssawc:InputFormTextBox title="Proxy address" class="ms-input" ID="InputProxyAddress" Columns="50" runat="server" />
</Template_InputFormControls>
</wssuc:InputFormSection>

<wssuc:InputFormSection runat="server" Title="Restrict searchable users">
<Template_Description>
<wssawc:EncodedLiteral runat="server" Text="Restrict the users who can be searched, by specifying a list of Entra groups. Only users members of any of those groups may be returned." EncodeMethod='NoEncode' />
<br />
<wssawc:EncodedLiteral runat="server" Text="You can specify up to 18 groups." EncodeMethod='NoEncode' />
<br />
<br />
<wssawc:EncodedLiteral runat="server" Text="For performance reasons, the members of those groups are stored in a local cache." EncodeMethod='NoEncode' />
<br />
<span>You can customize its lifetime (default value is <%= DefaultTenantDataCacheLifetimeInMinutes %> minutes), and the minimum possible value is 1 minute.</span>
</Template_Description>
<Template_InputFormControls>
<label for="<%= InputRestrictSearchableUsersByGroups.ClientID %>" title="Example: 1D6C19BB-DA3B-48D7-98FF-066CB9CA3F14,755D28C4-A2D8-4ACB-ADCE-68D4C6570938">List of groups ID separated by a comma &#9432;:</label><br />
<wssawc:InputFormTextBox title="Groups ID separated by a comma" class="ms-input" ID="InputRestrictSearchableUsersByGroups" Columns="50" runat="server" />
<br />
<br />
<label for="<%= InputTenantDataCacheLifetimeInMinutes.ClientID %>">Lifetime of the cache in minutes:</label><br />
<wssawc:InputFormTextBox class="ms-input" ID="InputTenantDataCacheLifetimeInMinutes" Columns="50" runat="server" />
</Template_InputFormControls>
</wssuc:InputFormSection>

<wssuc:InputFormSection runat="server" Title="Reset EntraCP configuration" Description="Restore configuration to its default values. All changes, including in claim types mappings, will be lost.">
<Template_InputFormControls>
<asp:Button runat="server" ID="BtnResetConfig" Text="Reset EntraCP configuration" OnClick="BtnResetConfig_Click" class="ms-ButtonHeightWidth" OnClientClick="return confirm('Do you really want to reset EntraCP configuration?');" />
Expand Down
6 changes: 6 additions & 0 deletions Yvand.EntraCP/TEMPLATE/ADMIN/EntraCP/GlobalSettings.ascx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ namespace Yvand.EntraClaimsProvider.Administration
{
public partial class GlobalSettingsUserControl : EntraCPUserControl
{
protected int DefaultTenantDataCacheLifetimeInMinutes = ClaimsProviderConstants.DefaultTenantDataCacheLifetimeInMinutes;

readonly string TextErrorNewTenantFieldsMissing = "Some mandatory fields are missing.";
readonly string TextErrorTestAzureADConnection = "Unable to get access token for tenant '{0}': {1}";
readonly string TextConnectionSuccessful = "Connection successful.";
Expand Down Expand Up @@ -122,6 +124,8 @@ private void PopulateFields()
this.ChkAlwaysResolveUserInput.Checked = Settings.AlwaysResolveUserInput;
this.ChkFilterExactMatchOnly.Checked = Settings.FilterExactMatchOnly;
this.InputProxyAddress.Text = Settings.ProxyAddress;
this.InputRestrictSearchableUsersByGroups.Text = Settings.RestrictSearchableUsersByGroups;
this.InputTenantDataCacheLifetimeInMinutes.Text = Settings.TenantDataCacheLifetimeInMinutes.ToString();

AzureCloudName[] azureCloudNames = (AzureCloudName[])Enum.GetValues(typeof(AzureCloudName));
foreach (var azureCloudName in azureCloudNames)
Expand Down Expand Up @@ -218,6 +222,8 @@ protected bool UpdateConfiguration(bool commitChanges)
Settings.AlwaysResolveUserInput = this.ChkAlwaysResolveUserInput.Checked;
Settings.FilterExactMatchOnly = this.ChkFilterExactMatchOnly.Checked;
Settings.ProxyAddress = this.InputProxyAddress.Text;
Settings.RestrictSearchableUsersByGroups = this.InputRestrictSearchableUsersByGroups.Text;
Settings.TenantDataCacheLifetimeInMinutes = Convert.ToInt32(this.InputTenantDataCacheLifetimeInMinutes.Text);

if (commitChanges) { CommitChanges(); }
return true;
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,8 @@ public static string ClaimsProviderVersion
#else
public static int DEFAULT_TIMEOUT = 15 * 1000;
#endif

public static readonly int DefaultTenantDataCacheLifetimeInMinutes = 15;
}

public enum AzureCloudName
Expand Down
Loading

0 comments on commit ca111f1

Please sign in to comment.