diff --git a/src/Nest/XPack/Security/RoleMapping/Rules/Field/DistinguishedNameRule.cs b/src/Nest/XPack/Security/RoleMapping/Rules/Field/DistinguishedNameRule.cs index c9dafd9cb19..25aefbf9e9c 100644 --- a/src/Nest/XPack/Security/RoleMapping/Rules/Field/DistinguishedNameRule.cs +++ b/src/Nest/XPack/Security/RoleMapping/Rules/Field/DistinguishedNameRule.cs @@ -2,10 +2,14 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System.Collections.Generic; + namespace Nest { public class DistinguishedNameRule : FieldRuleBase { public DistinguishedNameRule(string name) => DistinguishedName = name; + + public DistinguishedNameRule(IEnumerable names) => DistinguishedNames = names; } } diff --git a/src/Nest/XPack/Security/RoleMapping/Rules/Field/FieldRuleBase.cs b/src/Nest/XPack/Security/RoleMapping/Rules/Field/FieldRuleBase.cs index 7e6199e9634..a0f1f77f0d2 100644 --- a/src/Nest/XPack/Security/RoleMapping/Rules/Field/FieldRuleBase.cs +++ b/src/Nest/XPack/Security/RoleMapping/Rules/Field/FieldRuleBase.cs @@ -20,6 +20,13 @@ protected string DistinguishedName set => BackingDictionary.Add("dn", value); } + [IgnoreDataMember] + protected IEnumerable DistinguishedNames + { + get => BackingDictionary.TryGetValue("dn", out var o) ? (IEnumerable)o : null; + set => BackingDictionary.Add("dn", value); + } + [IgnoreDataMember] protected IEnumerable Groups { diff --git a/src/Nest/XPack/Security/RoleMapping/Rules/Field/FieldRuleBaseFormatter.cs b/src/Nest/XPack/Security/RoleMapping/Rules/Field/FieldRuleBaseFormatter.cs index cd2eceec144..d599b2a3705 100644 --- a/src/Nest/XPack/Security/RoleMapping/Rules/Field/FieldRuleBaseFormatter.cs +++ b/src/Nest/XPack/Security/RoleMapping/Rules/Field/FieldRuleBaseFormatter.cs @@ -2,6 +2,7 @@ // Elasticsearch B.V licenses this file to you under the Apache 2.0 License. // See the LICENSE file in the project root for more information +using System; using System.Collections.Generic; using Nest.Utf8Json; namespace Nest @@ -38,6 +39,13 @@ public FieldRuleBase Deserialize(ref JsonReader reader, IJsonFormatterResolver f fieldRule = new UsernameRule(username); break; case 1: + if (reader.GetCurrentJsonToken() == JsonToken.BeginArray) + { + var fm = formatterResolver.GetFormatter>(); + var dns = fm.Deserialize(ref reader, formatterResolver); + fieldRule = new DistinguishedNameRule(dns); + break; + } var dn = reader.ReadString(); fieldRule = new DistinguishedNameRule(dn); break; diff --git a/src/Nest/XPack/Security/RoleMapping/Rules/Role/RoleMappingRulesDescriptor.cs b/src/Nest/XPack/Security/RoleMapping/Rules/Role/RoleMappingRulesDescriptor.cs index dca2ca3a724..88530e9f289 100644 --- a/src/Nest/XPack/Security/RoleMapping/Rules/Role/RoleMappingRulesDescriptor.cs +++ b/src/Nest/XPack/Security/RoleMapping/Rules/Role/RoleMappingRulesDescriptor.cs @@ -20,6 +20,8 @@ private RoleMappingRulesDescriptor Add(RoleMappingRuleBase m) return this; } + public RoleMappingRulesDescriptor DistinguishedName(IEnumerable names) => Add(new DistinguishedNameRule(names)); + public RoleMappingRulesDescriptor DistinguishedName(string name) => Add(new DistinguishedNameRule(name)); public RoleMappingRulesDescriptor Username(string username) => Add(new UsernameRule(username)); diff --git a/tests/Tests.Reproduce/GitHubIssue5270.cs b/tests/Tests.Reproduce/GitHubIssue5270.cs new file mode 100644 index 00000000000..e1a9ed764aa --- /dev/null +++ b/tests/Tests.Reproduce/GitHubIssue5270.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using Elastic.Transport; +using Elasticsearch.Net; +using FluentAssertions; +using Nest; + +namespace Tests.Reproduce +{ + public class GitHubIssue5270 + { + private static readonly byte[] ResponseBytes = Encoding.UTF8.GetBytes(@"{ + ""test admin role mapping"" : { + ""enabled"" : true, + ""roles"" : [ + ""apm_user"" + ], + ""rules"" : { + ""any"" : [ + { + ""field"" : { + ""dn"" : [ + ""CN=Bloggs Joe abcdef01,OU=Users,OU=_Central,OU=S1000,OU=SG001,DC=ad001,DC=example,DC=net"", + ""cn=bloggs joe abcdef02,ou=usersfunctional,ou=_central,ou=accadm,OU=SG001,DC=ad001,DC=example,DC=net"" + ] + } + } + ] + }, + ""metadata"" : { } + } +}"); + + [U] + public async Task GetRoleMappings() + { + var pool = new SingleNodeConnectionPool(new Uri($"http://localhost:9200")); + var settings = new ConnectionSettings(pool, new InMemoryConnection(ResponseBytes)); + var client = new ElasticClient(settings); + + // ReSharper disable once MethodHasAsyncOverload + var roleResponse = client.Security.GetRoleMapping(); + roleResponse.RoleMappings.Count.Should().Be(1); + Assert(roleResponse); + + roleResponse = await client.Security.GetRoleMappingAsync(); + roleResponse.RoleMappings.Count.Should().Be(1); + Assert(roleResponse); + } + + private static void Assert(GetRoleMappingResponse roleResponse) => + roleResponse.RoleMappings["test admin role mapping"] + .Rules.Should() + .BeAssignableTo() + .Subject.Any.First() + .Should() + .BeAssignableTo() + .Subject.Field.Should() + .BeAssignableTo() + .Subject["dn"].Should().BeAssignableTo>() + .Subject.Count().Should().Be(2); + } +} diff --git a/tests/Tests/XPack/Security/RoleMapping/DistinguishedNamesRoleMappingsTests.cs b/tests/Tests/XPack/Security/RoleMapping/DistinguishedNamesRoleMappingsTests.cs new file mode 100644 index 00000000000..8c566a3a656 --- /dev/null +++ b/tests/Tests/XPack/Security/RoleMapping/DistinguishedNamesRoleMappingsTests.cs @@ -0,0 +1,68 @@ +// Licensed to Elasticsearch B.V under one or more agreements. +// Elasticsearch B.V licenses this file to you under the Apache 2.0 License. +// See the LICENSE file in the project root for more information + +using System; +using System.Collections.Generic; +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +using Elastic.Transport; +using Nest; +using Tests.Core.ManagedElasticsearch.Clusters; +using Tests.Framework.EndpointTests; +using Tests.Framework.EndpointTests.TestState; + +namespace Tests.XPack.Security.RoleMapping +{ + [SkipVersion("<5.5.0", "Does not exist in earlier versions")] + public class DistinguishedNamesRoleMappingsTests + : ApiTestBase + { + public DistinguishedNamesRoleMappingsTests(XPackCluster cluster, EndpointUsage usage) : base(cluster, usage) { } + + protected override HttpMethod HttpMethod => HttpMethod.PUT; + protected override string UrlPath => "/_security/role_mapping/name"; + protected override bool SupportsDeserialization => false; + + protected override object ExpectJson => new + { + enabled = true, + roles = new[] { "user_role" }, + rules = new + { + any = new object[] + { + new + { + field = new { + dn = new [] { + "a", + "b" + } + } + } + } + } + }; + + protected override PutRoleMappingRequest Initializer { get; } = new("name") + { + Enabled = true, + Roles = new List { "user_role" }, + Rules = new AnyRoleMappingRule(new DistinguishedNameRule(new List + { + "a", "b" + })) + }; + + protected override PutRoleMappingDescriptor NewDescriptor() => new("name"); + + protected override Func Fluent => + d => d.Enabled().Roles("user_role").Rules(r => r.Any(a => a.DistinguishedName(new List { "a", "b" }))); + + protected override LazyResponses ClientUsage() => Calls( + (client, f) => client.Security.PutRoleMapping("name", f), + (client, f) => client.Security.PutRoleMappingAsync("name", f), + (client, r) => client.Security.PutRoleMapping(r), + (client, r) => client.Security.PutRoleMappingAsync(r)); + } +}