From c1316d32bc749c35a5ea45f2e74553561d901bd6 Mon Sep 17 00:00:00 2001 From: Steve Gordon Date: Fri, 15 Jan 2021 08:06:32 +0000 Subject: [PATCH] Fix GetRoleMappings when multiple distinguished names returned (#5271) * Fix deserialisation of multiple dns * Support serialisation --- .../Rules/Field/DistinguishedNameRule.cs | 4 ++ .../RoleMapping/Rules/Field/FieldRuleBase.cs | 7 ++ .../Rules/Field/FieldRuleBaseFormatter.cs | 9 +++ .../Rules/Role/RoleMappingRulesDescriptor.cs | 2 + tests/Tests.Reproduce/GitHubIssue5270.cs | 66 ++++++++++++++++++ .../DistinguishedNamesRoleMappingsTests.cs | 68 +++++++++++++++++++ 6 files changed, 156 insertions(+) create mode 100644 tests/Tests.Reproduce/GitHubIssue5270.cs create mode 100644 tests/Tests/XPack/Security/RoleMapping/DistinguishedNamesRoleMappingsTests.cs 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 4fb3dc574f5..27717fe8372 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 9a74508ec51..0da505d079d 100644 --- a/src/Nest/XPack/Security/RoleMapping/Rules/Field/FieldRuleBaseFormatter.cs +++ b/src/Nest/XPack/Security/RoleMapping/Rules/Field/FieldRuleBaseFormatter.cs @@ -2,8 +2,10 @@ // 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 Elasticsearch.Net.Utf8Json; +using Elasticsearch.Net.Utf8Json.Formatters; using Elasticsearch.Net.Utf8Json.Internal; @@ -41,6 +43,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..433f5a1da83 --- /dev/null +++ b/tests/Tests.Reproduce/GitHubIssue5270.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Elastic.Elasticsearch.Xunit.XunitPlumbing; +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..b86c4c882e6 --- /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 Elasticsearch.Net; +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)); + } +}