Skip to content

Commit c8283f3

Browse files
authored
Merge pull request #337 from neuroglia-io/auth-runtime-expression-parameter
Added a new 'AUTHZ' runtime expression parameter, which describes a function's authorization scheme, if any
2 parents 0e466cd + 3b0bf6c commit c8283f3

11 files changed

+246
-150
lines changed

deployment/docker-compose/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
secrets/
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright © 2022-Present The Synapse Authors
3+
* <p>
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*
16+
*/
17+
18+
using Microsoft.Extensions.DependencyInjection;
19+
using System.Text;
20+
21+
namespace Synapse.Worker;
22+
23+
/// <summary>
24+
/// Describes the credentials used to authenticate a user agent with an application
25+
/// </summary>
26+
public class AuthorizationInfo
27+
{
28+
29+
/// <summary>
30+
/// Initializes a new <see cref="AuthorizationInfo"/>
31+
/// </summary>
32+
public AuthorizationInfo() { }
33+
34+
/// <summary>
35+
/// Initializes a new <see cref="AuthorizationInfo"/>
36+
/// </summary>
37+
/// <param name="scheme">The authorization scheme</param>
38+
/// <param name="parameters">The authorization parameters</param>
39+
public AuthorizationInfo(string scheme, string parameters)
40+
{
41+
this.Scheme = scheme;
42+
this.Parameters = parameters;
43+
}
44+
45+
/// <summary>
46+
/// Gets the authorization scheme
47+
/// </summary>
48+
public virtual string Scheme { get; set; } = null!;
49+
50+
/// <summary>
51+
/// Gets the authorization parameters
52+
/// </summary>
53+
public virtual string Parameters { get; set; } = null!;
54+
55+
/// <inheritdoc/>
56+
public override string ToString() => $"{this.Scheme} {this.Parameters}";
57+
58+
/// <summary>
59+
/// Creates a new <see cref="AuthorizationInfo"/> from the specified <see cref="AuthenticationDefinition"/>
60+
/// </summary>
61+
/// <param name="serviceProvider">The current <see cref="ISerializerProvider"/></param>
62+
/// <param name="authentication">The <see cref="AuthenticationDefinition"/> that describes the authentication mechanism to use</param>
63+
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
64+
/// <returns>A new <see cref="AuthorizationInfo"/></returns>
65+
/// <exception cref="NullReferenceException"></exception>
66+
/// <exception cref="NotSupportedException"></exception>
67+
public static async Task<AuthorizationInfo?> CreateAsync(IServiceProvider serviceProvider, AuthenticationDefinition? authentication, CancellationToken cancellationToken = default)
68+
{
69+
if (authentication == null) return null;
70+
string scheme;
71+
string value;
72+
switch (authentication.Properties)
73+
{
74+
case BasicAuthenticationProperties basic:
75+
scheme = "Basic";
76+
value = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{basic.Username}:{basic.Password}"));
77+
break;
78+
case BearerAuthenticationProperties bearer:
79+
scheme = "Bearer";
80+
value = bearer.Token;
81+
break;
82+
case OAuth2AuthenticationProperties oauth:
83+
scheme = "Bearer";
84+
var token = await serviceProvider.GetRequiredService<IOAuth2TokenManager>().GetTokenAsync(oauth, cancellationToken);
85+
if (token == null) throw new NullReferenceException($"Failed to generate an OAUTH2 token");
86+
value = token.AccessToken!;
87+
break;
88+
default:
89+
throw new NotSupportedException($"The specified authentication schema '{EnumHelper.Stringify(authentication.Scheme)}' is not supported");
90+
}
91+
return new(scheme, value);
92+
}
93+
94+
}

src/apps/Synapse.Worker/Extensions/HttpClientExtensions.cs

Lines changed: 5 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,7 @@
1515
*
1616
*/
1717

18-
using Microsoft.Extensions.DependencyInjection;
1918
using System.Net.Http.Headers;
20-
using System.Text;
2119

2220
namespace Synapse.Worker
2321
{
@@ -29,40 +27,15 @@ public static class HttpClientExtensions
2927
{
3028

3129
/// <summary>
32-
/// Configures the <see cref="HttpClient"/> to use the specified <see cref="AuthenticationDefinition"/>
30+
/// Configures the <see cref="HttpClient"/> to use the specified <see cref="AuthorizationInfo"/>
3331
/// </summary>
3432
/// <param name="httpClient">The <see cref="HttpClient"/> to configure</param>
35-
/// <param name="serviceProvider">The current <see cref="IServiceProvider"/></param>
36-
/// <param name="authentication">The <see cref="AuthenticationDefinition"/> that describes how to configure authorization</param>
37-
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
33+
/// <param name="authorization">An object that describes the authorization mechanism to use</param>
3834
/// <returns>A new awaitable <see cref="Task"/></returns>
39-
public static async Task ConfigureAuthorizationAsync(this HttpClient httpClient, IServiceProvider serviceProvider, AuthenticationDefinition? authentication, CancellationToken cancellationToken = default)
35+
public static void UseAuthorization(this HttpClient httpClient, AuthorizationInfo? authorization)
4036
{
41-
if (authentication == null)
42-
return;
43-
string? scheme;
44-
string? value;
45-
switch (authentication.Properties)
46-
{
47-
case BasicAuthenticationProperties basic:
48-
scheme = "Basic";
49-
value = Convert.ToBase64String(Encoding.UTF8.GetBytes($"{basic.Username}:{basic.Password}"));
50-
break;
51-
case BearerAuthenticationProperties bearer:
52-
scheme = "Bearer";
53-
value = bearer.Token;
54-
break;
55-
case OAuth2AuthenticationProperties oauth:
56-
scheme = "Bearer";
57-
var token = await serviceProvider.GetRequiredService<IOAuth2TokenManager>().GetTokenAsync(oauth, cancellationToken);
58-
if (token == null)
59-
throw new NullReferenceException($"Failed to generate an OAUTH2 token");
60-
value = token.AccessToken;
61-
break;
62-
default:
63-
throw new NotSupportedException($"The specified authentication schema '{EnumHelper.Stringify(authentication.Scheme)}' is not supported");
64-
}
65-
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(scheme, value);
37+
if (authorization == null) return;
38+
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(authorization.Scheme, authorization.Parameters);
6639
}
6740

6841
}

src/apps/Synapse.Worker/Extensions/IWorkflowRuntimeContextExtensions.cs

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,16 @@ public static class IWorkflowRuntimeContextExtensions
3636
/// <param name="context">The current <see cref="IWorkflowRuntimeContext"/></param>
3737
/// <param name="expressionObject">The object to evaluate</param>
3838
/// <param name="data">The data to evaluate the object against</param>
39+
/// <param name="authorization">The current <see cref="AuthorizationInfo"/>, if any</param>
3940
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
4041
/// <returns>The evaluated object</returns>
41-
public static async Task<object?> EvaluateObjectAsync(this IWorkflowRuntimeContext context, object expressionObject, object data, CancellationToken cancellationToken = default)
42+
public static async Task<object?> EvaluateObjectAsync(this IWorkflowRuntimeContext context, object expressionObject, object data, AuthorizationInfo? authorization, CancellationToken cancellationToken = default)
4243
{
4344
var json = JsonConvert.SerializeObject(expressionObject, new JsonSerializerSettings() { NullValueHandling = NullValueHandling.Ignore }); ;
4445
foreach (var match in Regex.Matches(json, @"""\$\{.+?\}""", RegexOptions.Compiled).Cast<Match>())
4546
{
4647
var expression = Regex.Unescape(match.Value[3..^2].Trim().Replace(@"\""", @""""));
47-
var evaluationResult = await context.EvaluateAsync(expression, data, cancellationToken);
48+
var evaluationResult = await context.EvaluateAsync(expression, data, authorization, cancellationToken);
4849
if (evaluationResult == null) continue;
4950
var valueToken = JToken.FromObject(evaluationResult);
5051
var value = null as string;
@@ -62,6 +63,19 @@ public static class IWorkflowRuntimeContextExtensions
6263
return JsonConvert.DeserializeObject<ExpandoObject>(json)!;
6364
}
6465

66+
/// <summary>
67+
/// Evaluates an object against the specified data
68+
/// </summary>
69+
/// <param name="context">The current <see cref="IWorkflowRuntimeContext"/></param>
70+
/// <param name="expressionObject">The object to evaluate</param>
71+
/// <param name="data">The data to evaluate the object against</param>
72+
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
73+
/// <returns>The evaluated object</returns>
74+
public static Task<object?> EvaluateObjectAsync(this IWorkflowRuntimeContext context, object expressionObject, object data, CancellationToken cancellationToken = default)
75+
{
76+
return EvaluateObjectAsync(context, expressionObject, data, null, cancellationToken);
77+
}
78+
6579
/// <summary>
6680
/// Evaluates the specified condition expression
6781
/// </summary>
@@ -123,9 +137,7 @@ public static async Task<bool> EvaluateConditionAsync(this IWorkflowRuntimeConte
123137
/// <returns>The filtered output</returns>
124138
public static async Task<object?> FilterOutputAsync(this IWorkflowRuntimeContext context, StateDefinition state, object output, CancellationToken cancellationToken = default)
125139
{
126-
if (state.DataFilter == null
127-
|| string.IsNullOrWhiteSpace(state.DataFilter.Output))
128-
return output;
140+
if (state.DataFilter == null || string.IsNullOrWhiteSpace(state.DataFilter.Output)) return output;
129141
return await context.EvaluateAsync(state.DataFilter.Output, output, cancellationToken);
130142
}
131143

@@ -135,14 +147,28 @@ public static async Task<bool> EvaluateConditionAsync(this IWorkflowRuntimeConte
135147
/// <param name="context">The <see cref="IWorkflowRuntimeContext"/> to use</param>
136148
/// <param name="action">The <see cref="ActionDefinition"/> to filter the output for</param>
137149
/// <param name="output">The output data to filter</param>
150+
/// <param name="authorization">The current <see cref="AuthorizationInfo"/>, if any</param>
138151
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
139152
/// <returns>The filtered output</returns>
140-
public static async Task<object?> FilterOutputAsync(this IWorkflowRuntimeContext context, ActionDefinition action, object output, CancellationToken cancellationToken = default)
153+
public static async Task<object?> FilterOutputAsync(this IWorkflowRuntimeContext context, ActionDefinition action, object output, AuthorizationInfo? authorization, CancellationToken cancellationToken = default)
141154
{
142155
if (action.ActionDataFilter == null
143156
|| string.IsNullOrWhiteSpace(action.ActionDataFilter.Results))
144157
return output;
145-
return await context.EvaluateAsync(action.ActionDataFilter.Results, output, cancellationToken);
158+
return await context.EvaluateAsync(action.ActionDataFilter.Results, output, authorization, cancellationToken);
159+
}
160+
161+
/// <summary>
162+
/// Filters the output of the specified <see cref="ActionDefinition"/>
163+
/// </summary>
164+
/// <param name="context">The <see cref="IWorkflowRuntimeContext"/> to use</param>
165+
/// <param name="action">The <see cref="ActionDefinition"/> to filter the output for</param>
166+
/// <param name="output">The output data to filter</param>
167+
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
168+
/// <returns>The filtered output</returns>
169+
public static Task<object?> FilterOutputAsync(this IWorkflowRuntimeContext context, ActionDefinition action, object output, CancellationToken cancellationToken = default)
170+
{
171+
return FilterOutputAsync(context, action, output, cancellationToken);
146172
}
147173

148174
/// <summary>

src/apps/Synapse.Worker/Services/IWorkflowRuntimeContext.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,16 @@ public interface IWorkflowRuntimeContext
4141
/// <returns>A new awaitable <see cref="Task"/></returns>
4242
Task InitializeAsync(CancellationToken cancellationToken);
4343

44+
/// <summary>
45+
/// Evaluates a runtime expression against an object
46+
/// </summary>
47+
/// <param name="runtimeExpression">The runtime expression to evaluate</param>
48+
/// <param name="data">The data to evaluate the expression against</param>
49+
/// <param name="authorization">The current <see cref="AuthorizationInfo"/>, if any</param>
50+
/// <param name="cancellationToken">A <see cref="CancellationToken"/></param>
51+
/// <returns>The evaluation result</returns>
52+
Task<object?> EvaluateAsync(string runtimeExpression, object? data, AuthorizationInfo? authorization, CancellationToken cancellationToken = default);
53+
4454
/// <summary>
4555
/// Evaluates a runtime expression against an object
4656
/// </summary>

src/apps/Synapse.Worker/Services/OAuth2TokenManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ public virtual async Task<OAuth2Token> GetTokenAsync(OAuth2AuthenticationPropert
8686
}
8787
}
8888
var discoveryDocument = await this.HttpClient.GetDiscoveryDocumentAsync(oauthProperties.Authority.ToString(), cancellationToken);
89-
using var request = new HttpRequestMessage(HttpMethod.Post, discoveryDocument.TokenEndpoint)
89+
using var request = new HttpRequestMessage(HttpMethod.Post, new Uri(oauthProperties.Authority, discoveryDocument.TokenEndpoint))
9090
{
9191
Content = new FormUrlEncodedContent(properties)
9292
};

0 commit comments

Comments
 (0)