Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement extra properties/soft-deleting features #23

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
4 changes: 2 additions & 2 deletions .config/dotnet-tools.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
"isRoot": true,
"tools": {
"dotnet-sonarscanner": {
"version": "4.10.0",
"version": "5.13.1",
"commands": [
"dotnet-sonarscanner"
]
}
}
}
}
6 changes: 3 additions & 3 deletions .github/workflows/pull-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
3
Expand All @@ -22,8 +22,8 @@ jobs:
uses: swisslife-oss/actions/pull-request@main
with:
sonar_token: ${{ secrets.SONAR_TOKEN }}
sonar_project_key: 'SwissLife-OSS_HotChocolateExtensions'
sonar_project_name: 'hotchocolate-extensions'
sonar_project_key: 'SwissLife-OSS_Bewit'
sonar_project_name: 'bewit'
pr_number: ${{ github.event.pull_request.number }}
pr_source_branch: ${{ github.head_ref }}
pr_target_branch: ${{ github.base_ref }}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Setup .NET
uses: actions/setup-dotnet@v3
uses: actions/setup-dotnet@v4
with:
dotnet-version: |
3
Expand Down
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,17 @@ ISchema schema = SchemaBuilder.New()
d.Name("Mutation");
d.Field("RequestAccessUrl")
.Type<NonNullType<StringType>>()
.Resolver(ctx => $"{mvcApiUrl}/api/file/123")
.Resolver(ctx =>
{
Dictionary<string, string> extraProperties = new()
{
["foo"] = "bar"
};

ctx.AddBewitTokenExtraProperties(extraProperties);

return $"{mvcApiUrl}/api/file/123";
})
.UseBewitUrlProtection();
}))
.Create();
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"sdk": {
"version": "8.0.402"
"version": "8.0.405"
}
}
3 changes: 2 additions & 1 deletion samples/Endpoint_SecuredUrl/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
var id = c.Request.RouteValues.GetValueOrDefault("id");

BewitToken<string> token =
await generator.GenerateBewitTokenAsync($"/download/{id}", default);
await generator.GenerateBewitTokenAsync($"/download/{id}",
new Dictionary<string, object>(), default);

string html = @$"<html><a href=""/download/{id}?bewit={token}"">download</a>
<br>{(string)token}</html>";
Expand Down
9 changes: 7 additions & 2 deletions samples/HotChocolate_SecuredArgument/Types/Mutation.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Bewit.Generation;
Expand Down Expand Up @@ -29,14 +30,18 @@ public async Task<string> InvalidateBewitTokens(
public async Task<string> CreateBewitToken(string value)
{
return (await _fooPayloadGenerator
.GenerateBewitTokenAsync(new FooPayload {Value = value}, default))
.GenerateBewitTokenAsync(
new FooPayload {Value = value},
new Dictionary<string, object>(),
default))
.ToString();
}

public async Task<string> CreateIdentifiableBewitToken(string identifier)
{
return (await _barPayloadGenerator
.GenerateIdentifiableBewitTokenAsync(new BarPayload(), identifier, default))
.GenerateIdentifiableBewitTokenAsync(
new BarPayload(), identifier, new Dictionary<string, object>(), default))
.ToString();
}
}
Expand Down
7 changes: 7 additions & 0 deletions src/Core/Token.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace Bewit
Expand All @@ -22,6 +23,12 @@ protected Token(string nonce, DateTime expirationDate)

public DateTime ExpirationDate { get; private set; }

[JsonIgnore]
public bool? IsDeleted { get; set; } = false;

[JsonIgnore]
public Dictionary<string, object> ExtraProperties { get; set; }

public static Token Create(string nonce, DateTime expirationDate)
{
if (string.IsNullOrWhiteSpace(nonce))
Expand Down
3 changes: 2 additions & 1 deletion src/Extensions.HotChocolate/Generation/BewitMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ public async Task InvokeAsync(
if (context.Result is TPayload result)
{
BewitToken<TPayload> bewit
= await tokenGenerator.GenerateBewitTokenAsync(result, context.RequestAborted);
= await tokenGenerator.GenerateBewitTokenAsync(
result, context.GetBewitTokenExtraProperties(), context.RequestAborted);

context.Result = (string)bewit;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System.Collections.Generic;
using System.Linq;
using HotChocolate.Resolvers;

namespace Bewit.Extensions.HotChocolate.Generation;

public static class BewitTokenExtraPropertiesHelper

{
private const string ExtraPropertyPrefix = "BewitTokenExtraProperty:";

public static void AddBewitTokenExtraProperties(
this IResolverContext resolverContext, Dictionary<string, object> extraProperties)
{
if (extraProperties == null)
{
return;
}

resolverContext.ScopedContextData =
resolverContext.ScopedContextData.SetItems(
extraProperties.ToDictionary(
ctx => $"{ExtraPropertyPrefix}{ctx.Key}",
ctx => ctx.Value));
}

public static Dictionary<string, object> GetBewitTokenExtraProperties(this IMiddlewareContext context)
{
Dictionary<string, object> extraProperties = new Dictionary<string, object>();

foreach (var key in context.ScopedContextData.Keys)
{
if (!key.StartsWith(ExtraPropertyPrefix))
{
continue;
}

object extraPropertyValue = context.ScopedContextData.GetValueOrDefault(key);

if (extraPropertyValue != null)
{
extraProperties.Add(
key.Substring(ExtraPropertyPrefix.Length),
extraPropertyValue);
}
}

return extraProperties;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ public async Task InvokeAsync(

BewitToken<string> bewit =
await tokenGenerator.GenerateBewitTokenAsync(
uri.PathAndQuery, context.RequestAborted);
uri.PathAndQuery, context.GetBewitTokenExtraProperties(), context.RequestAborted);

var parametersToAdd = new Dictionary<string, string>
{
Expand Down
8 changes: 7 additions & 1 deletion src/Generation/BewitTokenGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Text;
using System.Threading;
Expand Down Expand Up @@ -43,19 +44,24 @@ public BewitTokenGenerator(

public Task<BewitToken<T>> GenerateBewitTokenAsync(
T payload,
CancellationToken cancellationToken)
Dictionary<string, object> extraProperties,
CancellationToken cancellationToken
)
{
var token = Token.Create(CreateNextToken(), CreateExpirationDate());
token.ExtraProperties = extraProperties;
return GenerateBewitTokenImplAsync(payload, token, cancellationToken);

}

public Task<BewitToken<T>> GenerateIdentifiableBewitTokenAsync(
T payload,
string identifier,
Dictionary<string, object> extraProperties,
CancellationToken cancellationToken)
{
var token = new IdentifiableToken(identifier, CreateNextToken(), CreateExpirationDate());
token.ExtraProperties = extraProperties;
return GenerateBewitTokenImplAsync(payload, token, cancellationToken);
}

Expand Down
2 changes: 2 additions & 0 deletions src/Generation/IBewitTokenGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -7,6 +8,7 @@ public interface IBewitTokenGenerator<T>
{
Task<BewitToken<T>> GenerateBewitTokenAsync(
T payload,
Dictionary<string, object> extraProperties,
CancellationToken cancellationToken);
}
}
2 changes: 2 additions & 0 deletions src/Generation/IIdentifiableBewitTokenGenerator.cs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

Expand All @@ -8,6 +9,7 @@ public interface IIdentifiableBewitTokenGenerator<T>
Task<BewitToken<T>> GenerateIdentifiableBewitTokenAsync(
T payload,
string identifier,
Dictionary<string, object> extraProperties,
CancellationToken cancellationToken);

Task InvalidateIdentifier(
Expand Down
3 changes: 3 additions & 0 deletions src/Storage.MongoDB/MongoNonceOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -26,5 +26,8 @@ public class MongoNonceOptions
/// ReUse will keep it in the storage.
/// </summary>
public NonceUsage NonceUsage { get; set; } = NonceUsage.OneTime;


public int RecordExpireAfterDays { get; set; } = 365 * 2;
}
}
77 changes: 75 additions & 2 deletions src/Storage.MongoDB/MongoNonceRepository.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using System;
using System.Collections.Generic;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using MongoDB.Bson.Serialization;
Expand Down Expand Up @@ -27,6 +29,8 @@ static MongoNonceRepository()
{
cm.MapIdMember(c => c.Nonce);
cm.MapField(c => c.ExpirationDate);
cm.MapField(c => c.IsDeleted);
cm.MapField(c => c.ExtraProperties);
cm.SetIgnoreExtraElements(true);
});
}
Expand All @@ -38,24 +42,46 @@ public MongoNonceRepository(IMongoDatabase database, MongoNonceOptions options)

_collection.Indexes.CreateOne(new CreateIndexModel<Token>(
Builders<Token>.IndexKeys.Ascending(nameof(IdentifiableToken.Identifier))));

_collection.Indexes.CreateOne(new CreateIndexModel<Token>(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

compound index can be removed (nonce being already unique)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed

Builders<Token>.IndexKeys.Ascending(nameof(Token.ExpirationDate)),
new CreateIndexOptions
{
ExpireAfter = TimeSpan.FromDays(options.RecordExpireAfterDays)
}));
}

public async ValueTask InsertOneAsync(
Token token, CancellationToken cancellationToken)
{
token.ExtraProperties = token.ExtraProperties ?? new Dictionary<string, object>();

IReadOnlySet<string> propertyNamesWithPrimitiveValueType =
GetPropertyNamesWithPrimitiveValueType(token.ExtraProperties);

SetJsonStringValue(token.ExtraProperties, propertyNamesWithPrimitiveValueType);

await _collection.InsertOneAsync(token, cancellationToken: cancellationToken);

CreateIndexes(propertyNamesWithPrimitiveValueType);
}

public async ValueTask<Token?> TakeOneAsync(
string token,
CancellationToken cancellationToken)
{
FilterDefinition<Token> findFilter = Builders<Token>.Filter.Eq(n => n.Nonce, token);
FilterDefinition<Token> findFilter =
Builders<Token>.Filter.Eq(n => n.Nonce, token) &
(Builders<Token>.Filter.Not(Builders<Token>.Filter.Exists(n => n.IsDeleted)) |
Builders<Token>.Filter.Eq(n => n.IsDeleted, false));

UpdateDefinition<Token> updateDefinition =
Builders<Token>.Update.Set(x => x.IsDeleted, true);

if (_options.NonceUsage == NonceUsage.OneTime)
{
return await _collection
.FindOneAndDeleteAsync(findFilter, cancellationToken: cancellationToken);
.FindOneAndUpdateAsync(findFilter, updateDefinition, cancellationToken: cancellationToken);
}

return await _collection
Expand All @@ -77,5 +103,52 @@ public static void Initialize()
{
//ensure static constructor is called
}

private void CreateIndexes(IEnumerable<string> extraPropertyNames)
{
var indexOptions = new CreateIndexOptions { Background = true };

foreach (string extraPropertyName in extraPropertyNames)
{
_collection.Indexes.CreateOne(new CreateIndexModel<Token>(
Builders<Token>.IndexKeys.Ascending(
$"{nameof(Token.ExtraProperties)}.{extraPropertyName}"), indexOptions));
}
}

private IReadOnlySet<string> GetPropertyNamesWithPrimitiveValueType(
Dictionary<string, object> extraProperties)
{
HashSet<string> names = new HashSet<string>();

foreach (KeyValuePair<string, object> keyValue in extraProperties)
{
if (keyValue.Value == null)
{
continue;
}

if (TypeChecker.IsPrimitiveType(keyValue.Value.GetType()))
{
names.Add(keyValue.Key);
}
}

return names;
}

private void SetJsonStringValue(
Dictionary<string, object> extraProperties, IReadOnlySet<string> namesToSkip)
{
foreach(string name in extraProperties.Keys)
{
if (namesToSkip.Contains(name))
{
continue;
}

extraProperties[name] = JsonSerializer.Serialize(extraProperties[name]);
}
}
}
}
Loading
Loading