Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 63 additions & 1 deletion Consul.Test/TokenTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using NuGet.Versioning;
using Xunit;

namespace Consul.Test
Expand All @@ -29,12 +30,17 @@ public class TokenTest : BaseFixture
public async Task Token_CreateDelete()
{
Skip.If(string.IsNullOrEmpty(TestHelper.MasterToken));
var cutOffVersion = SemanticVersion.Parse("1.7.14");
Skip.If(AgentVersion == cutOffVersion, $"Node Identity is not supported in {AgentVersion}");

var nodeIdentity = new NodeIdentity { NodeName = "node-1", Datacenter = "dc1", };

var tokenEntry = new TokenEntry
{
Description = "API Testing Token",
SecretID = "1ED8D9E5-7868-4A0A-AC2F-6F75BEC71830",
Local = true
Local = true,
NodeIdentities = new NodeIdentity[] { nodeIdentity },
};

var newToken = await _client.Token.Create(tokenEntry);
Expand Down Expand Up @@ -236,6 +242,62 @@ public async Task Token_List()
Assert.True(aclList.Response.Length >= 1);
}

[SkippableFact]
public async Task Token_List_FilterByPolicy()
{
Skip.If(string.IsNullOrEmpty(TestHelper.MasterToken));

//Create a specific Policy to filter by
var policyEntry = new PolicyEntry
{
Name = "TokenListFilterPolicy",
Description = "Policy used to test token list filtering",
Rules = "key \"\" { policy = \"read\" }",
Datacenters = new[] { "dc1" }
};
var policy = await _client.Policy.Create(policyEntry);
Assert.NotNull(policy.Response);

//Create a Token linked to this Policy (Should be found)
var matchingTokenEntry = new TokenEntry
{
Description = "Token Linked to Filter Policy",
SecretID = Guid.NewGuid().ToString(),
Policies = new[] { new PolicyLink { ID = policy.Response.ID } },
Local = true
};
var matchingToken = await _client.Token.Create(matchingTokenEntry);

// Create a Token NOT linked to this Policy (It Should NOT be found)
var nonMatchingTokenEntry = new TokenEntry
{
Description = "Token NOT Linked to Filter Policy",
SecretID = Guid.NewGuid().ToString(),
Local = true
};
var nonMatchingToken = await _client.Token.Create(nonMatchingTokenEntry);

try
{
// List tokens filtering by the specific Policy ID
var filteredList = await _client.Token.List(policy.Response.ID, null, null, null);

Assert.NotNull(filteredList.Response);
Assert.NotEqual(TimeSpan.Zero, filteredList.RequestTime);

Assert.Contains(filteredList.Response, t => t.AccessorID == matchingToken.Response.AccessorID);

Assert.DoesNotContain(filteredList.Response, t => t.AccessorID == nonMatchingToken.Response.AccessorID);
}
finally
{
// Cleanup
await _client.Token.Delete(matchingToken.Response.AccessorID);
await _client.Token.Delete(nonMatchingToken.Response.AccessorID);
await _client.Policy.Delete(policy.Response.ID);
}
}

[SkippableFact]
public async Task Token_ReadSelfToken()
{
Expand Down
7 changes: 4 additions & 3 deletions Consul/Interfaces/ITokenEndpoint.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,11 @@ public interface ITokenEndpoint
Task<WriteResult<TokenEntry>> Create(TokenEntry token, WriteOptions q, CancellationToken ct = default);
Task<WriteResult<bool>> Delete(string id, CancellationToken ct = default);
Task<WriteResult<bool>> Delete(string id, WriteOptions q, CancellationToken ct = default);
Task<QueryResult<TokenEntry>> Read(string id, CancellationToken ct = default);
Task<QueryResult<TokenEntry>> Read(string id, QueryOptions q, CancellationToken ct = default);
Task<QueryResult<TokenEntry>> Read(string id, bool expanded = false, CancellationToken ct = default);
Task<QueryResult<TokenEntry>> Read(string id, bool expanded, QueryOptions q, CancellationToken ct = default);
Task<QueryResult<TokenEntry[]>> List(CancellationToken ct = default);
Task<QueryResult<TokenEntry[]>> List(QueryOptions q, CancellationToken ct = default);
Task<QueryResult<TokenEntry[]>> List(string policy, string role, string serviceName, string authMethod, CancellationToken ct = default);
Task<QueryResult<TokenEntry[]>> List(string policy, string role, string serviceName, string authMethod, QueryOptions q, CancellationToken ct = default);
Task<WriteResult<TokenEntry>> Update(TokenEntry token, CancellationToken ct = default);
Task<WriteResult<TokenEntry>> Update(TokenEntry token, WriteOptions q, CancellationToken ct = default);
Task<QueryResult<TokenEntry>> ReadSelfToken(string token, CancellationToken ct = default);
Expand Down
111 changes: 98 additions & 13 deletions Consul/Token.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,22 @@

namespace Consul
{
public class NodeIdentity
{
[JsonProperty("NodeName")]
public string NodeName { get; set; }
[JsonProperty("Datacenter")]
public string Datacenter { get; set; }
}

public class TemplatedPolicy
{
[JsonProperty("TemplateName")]
public string TemplateName { get; set; }

[JsonProperty("TemplateVariables")]
public Dictionary<string, object> TemplateVariables { get; set; }
}
/// <summary>
/// TokenEntry is used to represent an ACL Token in Consul
/// </summary>
Expand All @@ -37,11 +53,20 @@ public class TokenEntry
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public PolicyLink[] Policies { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public TemplatedPolicy[] TemplatedPolicies { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public RoleLink[] Roles { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public ServiceIdentity[] ServiceIdentities { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public NodeIdentity[] NodeIdentities { get; set; }
public bool Local { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public DateTime? ExpirationTime { get; set; }

[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public TimeSpan? ExpirationTTL { get; set; }
[JsonProperty(NullValueHandling = NullValueHandling.Ignore)]
public string AuthMethod { get; set; }


Expand All @@ -55,38 +80,53 @@ public static bool ShouldSerializeModifyIndex()
return false;
}

public bool ShouldSerializeTemplatedPolicies()
{
return TemplatedPolicies != null && TemplatedPolicies.Length > 0;
}

public bool ShouldSerializeNodeIdentities()
{
return NodeIdentities != null && NodeIdentities.Length > 0;
}

public TokenEntry()
: this(string.Empty, string.Empty, Array.Empty<PolicyLink>(), Array.Empty<RoleLink>(), Array.Empty<ServiceIdentity>())
: this(string.Empty, string.Empty, Array.Empty<PolicyLink>(), Array.Empty<RoleLink>(), Array.Empty<ServiceIdentity>(), false, Array.Empty<TemplatedPolicy>(), Array.Empty<NodeIdentity>(), null, null)
{
}

public TokenEntry(string description, PolicyLink[] policies)
: this(string.Empty, description, policies, Array.Empty<RoleLink>(), Array.Empty<ServiceIdentity>())
: this(string.Empty, description, policies, Array.Empty<RoleLink>(), Array.Empty<ServiceIdentity>(), false, Array.Empty<TemplatedPolicy>(), Array.Empty<NodeIdentity>(), null, null)
{
}

public TokenEntry(string description, RoleLink[] roles)
: this(string.Empty, description, Array.Empty<PolicyLink>(), roles, Array.Empty<ServiceIdentity>())
: this(string.Empty, description, Array.Empty<PolicyLink>(), roles, Array.Empty<ServiceIdentity>(), false, Array.Empty<TemplatedPolicy>(), Array.Empty<NodeIdentity>(), null, null)
{
}

public TokenEntry(string description, ServiceIdentity[] serviceIdentities)
: this(string.Empty, description, Array.Empty<PolicyLink>(), Array.Empty<RoleLink>(), serviceIdentities)
: this(string.Empty, description, Array.Empty<PolicyLink>(), Array.Empty<RoleLink>(), serviceIdentities, false, Array.Empty<TemplatedPolicy>(), Array.Empty<NodeIdentity>(), null, null)
{
}

public TokenEntry(string accessorId, string description)
: this(accessorId, description, Array.Empty<PolicyLink>(), Array.Empty<RoleLink>(), Array.Empty<ServiceIdentity>())
: this(accessorId, description, Array.Empty<PolicyLink>(), Array.Empty<RoleLink>(), Array.Empty<ServiceIdentity>(), false, Array.Empty<TemplatedPolicy>(), Array.Empty<NodeIdentity>(), null, null)
{
}

public TokenEntry(string accessorId, string description, PolicyLink[] policies, RoleLink[] roles, ServiceIdentity[] serviceIdentities)
public TokenEntry(string accessorId, string description, PolicyLink[] policies, RoleLink[] roles, ServiceIdentity[] serviceIdentities, bool local, TemplatedPolicy[] templatedPolicies, NodeIdentity[] nodeIdentities, DateTime? expirationTime, TimeSpan? expirationTTL)
{
AccessorID = accessorId;
Description = description;
Policies = policies;
Roles = roles;
ServiceIdentities = serviceIdentities;
Local = local;
TemplatedPolicies = templatedPolicies;
NodeIdentities = nodeIdentities;
ExpirationTime = expirationTime;
ExpirationTTL = expirationTTL;
}
}

Expand Down Expand Up @@ -253,23 +293,30 @@ public async Task<WriteResult<TokenEntry>> Clone(string id, string description,
/// Gets an existing ACL Token from Consul
/// </summary>
/// <param name="id">The Accessor ID of the ACL Token to get</param>
/// <param name="expanded">If true, the contents of all policies and roles affecting the token will also be returned</param>
/// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param>
/// <returns>A query result containing the requested ACL Token</returns>
public Task<QueryResult<TokenEntry>> Read(string id, CancellationToken ct = default)
public Task<QueryResult<TokenEntry>> Read(string id, bool expanded = false, CancellationToken ct = default)
{
return Read(id, QueryOptions.Default, ct);
return Read(id, expanded, QueryOptions.Default, ct);
}

/// <summary>
/// Gets an existing ACL Token from Consul
/// </summary>
/// <param name="id">The Accessor ID of the ACL Token to get</param>
/// <param name="expanded">If true, the contents of all policies and roles affecting the token will also be returned</param>
/// <param name="queryOptions">Customized query options</param>
/// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param>
/// <returns>A query result containing the requested ACL Token</returns>
public async Task<QueryResult<TokenEntry>> Read(string id, QueryOptions queryOptions, CancellationToken ct = default)
public async Task<QueryResult<TokenEntry>> Read(string id, bool expanded, QueryOptions queryOptions, CancellationToken ct = default)
{
var res = await _client.Get<TokenActionResult>($"/v1/acl/token/{id}", queryOptions).Execute(ct).ConfigureAwait(false);
var req = _client.Get<TokenActionResult>($"/v1/acl/token/{id}", queryOptions);
if (expanded)
{
req.Params["expanded"] = "true";
}
var res = await req.Execute(ct).ConfigureAwait(false);
return new QueryResult<TokenEntry>(res, res.Response);
}

Expand All @@ -280,18 +327,56 @@ public async Task<QueryResult<TokenEntry>> Read(string id, QueryOptions queryOpt
/// <returns>A query result containing an array of ACL Tokens</returns>
public Task<QueryResult<TokenEntry[]>> List(CancellationToken ct = default)
{
return List(QueryOptions.Default, ct);
return List(null, null, null, null, QueryOptions.Default, ct);
}
/// <summary>
/// Lists the existing ACL Tokens in Consul
/// </summary>
/// <param name="policy">Filters the token list to those tokens that are linked with this specific policy ID</param>
/// <param name="role">Filters the token list to those tokens that are linked with this specific role ID</param>
/// <param name="serviceName">Filters the token list to those tokens that are linked with this specific service name in their service identity</param>
/// <param name="authMethod">Filters the token list to those tokens that are linked with this specific named auth method</param>
/// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param>
/// <returns>A query result containing an array of ACL Tokens</returns>
public Task<QueryResult<TokenEntry[]>> List(string policy, string role, string serviceName, string authMethod, CancellationToken ct = default)
{
return List(policy, role, serviceName, authMethod, QueryOptions.Default, ct);
}

/// <summary>
/// Lists the existing ACL Tokens in Consul
/// </summary>
/// <param name="queryOptions">Customized query options</param>
/// <param name="policy">Filters the token list to those tokens that are linked with this specific policy ID</param>
/// <param name="role">Filters the token list to those tokens that are linked with this specific role ID</param>
/// <param name="serviceName">Filters the token list to those tokens that are linked with this specific service name in their service identity</param>
/// <param name="authMethod">Filters the token list to those tokens that are linked with this specific named auth method</param>
/// <param name="ct">Cancellation token for long poll request. If set, OperationCanceledException will be thrown if the request is cancelled before completing</param>
/// <returns>A query result containing an array of ACL Tokens</returns>
public Task<QueryResult<TokenEntry[]>> List(QueryOptions queryOptions, CancellationToken ct = default)
public Task<QueryResult<TokenEntry[]>> List(string policy, string role, string serviceName, string authMethod, QueryOptions queryOptions, CancellationToken ct = default)
{
return _client.Get<TokenEntry[]>("/v1/acl/tokens", queryOptions).Execute(ct);
var req = _client.Get<TokenEntry[]>("/v1/acl/tokens", queryOptions);
if (!string.IsNullOrEmpty(policy))
{
req.Params["policy"] = policy;
}

if (!string.IsNullOrEmpty(role))
{
req.Params["role"] = role;
}

if (!string.IsNullOrEmpty(serviceName))
{
req.Params["servicename"] = serviceName;
}

if (!string.IsNullOrEmpty(authMethod))
{
req.Params["authmethod"] = authMethod;
}

return req.Execute(ct);
}

/// <summary>
Expand Down
Loading