Skip to content

Commit

Permalink
Merge pull request #298 from meilisearch/changes_tenant_token
Browse files Browse the repository at this point in the history
  • Loading branch information
alallema authored Jul 19, 2022
2 parents 644ef55 + 5a8c36f commit 340d6da
Show file tree
Hide file tree
Showing 4 changed files with 50 additions and 16 deletions.
19 changes: 19 additions & 0 deletions src/Meilisearch/Errors/MeilisearchTenantTokenApiKeyUidInvalid.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;

namespace Meilisearch
{
/// <summary>
/// Represents an exception thrown when `apiKey` is not present
/// to sign correctly the Tenant Token generation.
/// </summary>
public class MeilisearchTenantTokenApiKeyUidInvalid : Exception
{
/// <summary>
/// Initializes a new instance of the <see cref="MeilisearchTenantTokenApiKeyUidInvalid"/> class.
/// </summary>
public MeilisearchTenantTokenApiKeyUidInvalid()
: base("Cannot generate a signed token without a valid apiKeyUid. Provide one in the method params.")
{
}
}
}
9 changes: 7 additions & 2 deletions src/Meilisearch/TenantToken.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,13 @@ public class TenantToken
/// Generates a Tenant Token in a JWT string format.
/// </summary>
/// <returns>JWT string</returns>
public static string GenerateToken(TenantTokenRules searchRules, string apiKey, DateTime? expiresAt)
public static string GenerateToken(string apiKeyUid, TenantTokenRules searchRules, string apiKey, DateTime? expiresAt)
{
if (String.IsNullOrEmpty(apiKeyUid))
{
throw new MeilisearchTenantTokenApiKeyUidInvalid();
}

if (String.IsNullOrEmpty(apiKey) || apiKey.Length < 8)
{
throw new MeilisearchTenantTokenApiKeyInvalid();
Expand All @@ -21,7 +26,7 @@ public static string GenerateToken(TenantTokenRules searchRules, string apiKey,
var builder = JwtBuilder
.Create()
.WithAlgorithm(new HMACSHA256Algorithm())
.AddClaim("apiKeyPrefix", apiKey.Substring(0, 8))
.AddClaim("apiKeyUid", apiKeyUid)
.AddClaim("searchRules", searchRules.ToClaim())
.WithSecret(apiKey);

Expand Down
2 changes: 1 addition & 1 deletion src/Meilisearch/TenantTokenRules.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Meilisearch
/// </summary>
public class TenantTokenRules
{
private object _rules;
private readonly object _rules;

public TenantTokenRules(Dictionary<string, object> rules)
{
Expand Down
36 changes: 23 additions & 13 deletions tests/Meilisearch.Tests/TenantTokenTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,19 +12,21 @@ namespace Meilisearch.Tests
{
public abstract class TenantTokenTests<TFixture> : IAsyncLifetime where TFixture : IndexFixture
{
private TenantTokenRules _searchRules = new TenantTokenRules(new string[] { "*" });
private readonly TenantTokenRules _searchRules = new TenantTokenRules(new string[] { "*" });

private readonly TFixture _fixture;
private JwtBuilder _builder;
private Index _basicIndex;
private readonly MeilisearchClient _client;
private readonly string _indexName = "books";
private string _key;
private readonly string _uid;
private readonly string _key;

public TenantTokenTests(TFixture fixture)
{
_fixture = fixture;
_client = fixture.DefaultClient;
_uid = Guid.NewGuid().ToString();
_key = Guid.NewGuid().ToString();
}

Expand All @@ -44,14 +46,22 @@ public async Task InitializeAsync()
public void DoesNotGenerateASignedTokenWithoutAKey()
{
Assert.Throws<MeilisearchTenantTokenApiKeyInvalid>(
() => TenantToken.GenerateToken(_searchRules, null, null)
() => TenantToken.GenerateToken(_uid, _searchRules, null, null)
);
}

[Fact]
public void DoesNotGenerateASignedTokenWithoutAUid()
{
Assert.Throws<MeilisearchTenantTokenApiKeyUidInvalid>(
() => TenantToken.GenerateToken(null, _searchRules, _key, null)
);
}

[Fact]
public void SignsTokenWithGivenKey()
{
var token = TenantToken.GenerateToken(_searchRules, _key, null);
var token = TenantToken.GenerateToken(_uid, _searchRules, _key, null);

Assert.Throws<SignatureVerificationException>(
() => _builder.WithSecret("other-key").Decode(token)
Expand All @@ -64,7 +74,7 @@ public void SignsTokenWithGivenKey()
public void GeneratesTokenWithExpiresAt()
{
var expiration = DateTimeOffset.UtcNow.AddDays(1).DateTime;
var token = TenantToken.GenerateToken(_searchRules, _key, expiration);
var token = TenantToken.GenerateToken(_uid, _searchRules, _key, expiration);

_builder.WithSecret(_key).Decode(token);
}
Expand All @@ -75,33 +85,33 @@ public void ThrowsExceptionWhenExpiresAtIsInThePast()
var expiresAt = new DateTime(1995, 12, 20);

Assert.Throws<MeilisearchTenantTokenExpired>(
() => TenantToken.GenerateToken(_searchRules, _key, expiresAt)
() => TenantToken.GenerateToken(_uid, _searchRules, _key, expiresAt)
);
}

[Fact]
public void ContainsValidClaims()
{
var token = TenantToken.GenerateToken(_searchRules, _key, null);
var token = TenantToken.GenerateToken(_uid, _searchRules, _key, null);

var claims = _builder.WithSecret(_key).Decode<IDictionary<string, object>>(token);

Assert.Equal(claims["apiKeyPrefix"], _key.Substring(0, 8));
Assert.Equal(claims["apiKeyUid"], _uid);
Assert.Equal(claims["searchRules"], _searchRules.ToClaim());
}

[Fact]
public void ClientDecodesSuccessfullyUsingApiKeyFromInstance()
{
var token = _client.GenerateTenantToken(_searchRules);
var token = _client.GenerateTenantToken(_uid, _searchRules);

_builder.WithSecret(_client.ApiKey).Decode(token);
}

[Fact]
public void ClientDecodesSuccessfullyUsingApiKeyFromArgument()
{
var token = _client.GenerateTenantToken(_searchRules, apiKey: _key);
var token = _client.GenerateTenantToken(_uid, _searchRules, apiKey: _key);

_builder.WithSecret(_key).Decode(token);
}
Expand All @@ -112,7 +122,7 @@ public void ClientThrowsIfNoKeyIsAvailable()
var customClient = new MeilisearchClient(_fixture.MeilisearchAddress);

Assert.Throws<MeilisearchTenantTokenApiKeyInvalid>(
() => customClient.GenerateTenantToken(_searchRules)
() => customClient.GenerateTenantToken(_uid, _searchRules)
);
}

Expand All @@ -130,9 +140,9 @@ public async void SearchesSuccessfullyWithTheNewToken(dynamic data)
var createdKey = await _client.CreateKeyAsync(keyOptions);
var admClient = new MeilisearchClient(_fixture.MeilisearchAddress, createdKey.KeyUid);
var task = await admClient.Index(_indexName).UpdateFilterableAttributesAsync(new string[] { "tag", "book_id" });
await admClient.Index(_indexName).WaitForTaskAsync(task.Uid);
await admClient.Index(_indexName).WaitForTaskAsync(task.TaskUid);

var token = admClient.GenerateTenantToken(new TenantTokenRules(data));
var token = admClient.GenerateTenantToken(createdKey.Uid, new TenantTokenRules(data));
var customClient = new MeilisearchClient(_fixture.MeilisearchAddress, token);

await customClient.Index(_indexName).SearchAsync<Movie>(string.Empty);
Expand Down

0 comments on commit 340d6da

Please sign in to comment.