I have class named AuthenticatToken which looks like this
public class TokenBasedAuthentication : ActionFilterAttribute, IAuthenticationFilter
{
ILocalData local = null;
string token = string.Empty;
bool istokenvalid = false;
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
istokenvalid = false;
await Task.Factory.StartNew(() =>
{
var req = context.Request;
IEnumerable<string> headervalues = new List<string>();
bool hastoken = req.Headers.TryGetValues(Constants.TOKEN, out headervalues);
if (hastoken)
{
local = new LocalData();
token = headervalues.FirstOrDefault();
istokenvalid = local.ValidateToken(token);
}
if (!istokenvalid)
{
context.ErrorResult = new AuthenticationFailureResult("Invalid token when accessing service", req);
}
else
{
IPrincipal incomingprincipal = context.ActionContext.RequestContext.Principal;
context.Principal = incomingprincipal;
}
});
}
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
return Task.FromResult(0);
}
}
the issue is when calling the above class as custom data annotation in controller the variables are not initialized it uses previous values for second request to controller.
[TokenBasedAuthentication]
public class SettingsController : ApiController, ISettings
{
}
so my question is are data annotation static ?
Related
There is a JSON-RPC API, which I'm currently implementing. It can be tested here.
The problem is that if an incorrect DTO model is passed to SendAsync<TResponse>, JsonSerializer.Deserialize is going to throw a JsonException, which is not handled by my code. I know I've got to use SetException in some way, but I don't know how to do it, so here is the question. The exception message should be printed in the console as well.
public sealed class Client : IDisposable
{
...
private readonly ConcurrentDictionary<long, IResponseHandler> _handlers = new();
...
public Task StartAsync(CancellationToken cancellationToken)
{
_ = Task.Run(async () =>
{
await foreach (var message in _client.Start(cancellationToken))
{
using var response = JsonDocument.Parse(message);
try
{
var requestId = response.RootElement.GetProperty("id").GetInt32();
// TODO: Handle JsonException errors via `SetException`?
// TODO: Show error when incorrect input parameters are filled
if (_handlers.ContainsKey(requestId))
{
_handlers[requestId].SetResult(message);
_handlers.TryRemove(requestId, out _);
}
}
catch (KeyNotFoundException)
{
// My point is that a message should be processed only if it doesn't include `id`,
// because that means that the message is an actual web socket subscription.
_messageReceivedSubject.OnNext(message);
}
}
}, cancellationToken);
...
return Task.CompletedTask;
}
public Task<TResponse> SendAsync<TResponse>(string method, object #params)
{
var request = new JsonRpcRequest<object>
{
JsonRpc = "2.0",
Id = NextId(),
Method = method,
Params = #params
};
//var tcs = new TaskCompletionSource<TResponse>();
//_requestManager.Add(request.Id, request, tcs);
var handler = new ResponseHandlerBase<TResponse>();
_handlers[request.Id] = handler;
var message = JsonSerializer.Serialize(request);
_ = _client.SendAsync(message);
return handler.Task;
//return tcs.Task;
}
public async Task<AuthResponse?> AuthenticateAsync(string clientId, string clientSecret)
{
var #params = new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", clientId},
{"client_secret", clientSecret}
};
var response = await SendAsync<SocketResponse<AuthResponse>>("public/auth", #params).ConfigureAwait(false);
return response.Result;
}
...
private interface IResponseHandler
{
void SetResult(string payload);
}
private class ResponseHandlerBase<TRes> : IResponseHandler
{
private readonly TaskCompletionSource<TRes> _tcs = new();
public Task<TRes> Task => _tcs.Task;
public void SetResult(string payload)
{
var result = JsonSerializer.Deserialize(payload, typeof(TRes));
_tcs.SetResult((TRes) result);
}
}
}
Coincidentally, I did something very similar while live-coding a TCP/IP chat application last week.
Since in this case you already have an IAsyncEnumerable<string> - and since you can get messages other than responses - I recommend also exposing that IAsyncEnumerable<string>:
public sealed class Client : IDisposable
{
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
// TODO: parse and handle responses for our requests
yield return message;
}
}
}
You can change this to be Rx-based if you want (_messageReceivedSubject.OnNext), but I figure if you already have IAsyncEnumerable<T>, then you may as well keep the same abstraction.
Then, you can parse and detect responses, passing along all other messages:
public sealed class Client : IDisposable
{
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
var (requestId, response) = TryParseResponse(message);
if (requestId != null)
{
// TODO: handle
}
else
{
yield return message;
}
}
(long? RequestId, JsonDocument? Response) TryParseResponse(string message)
{
try
{
var document = JsonDocument.Parse(message);
var requestId = response.RootElement.GetProperty("id").GetInt32();
return (document, requestId);
}
catch
{
return (null, null);
}
}
}
}
Then, you can define your collection of outstanding requests and handle messages that are for those requests:
public sealed class Client : IDisposable
{
private readonly ConcurrentDictionary<int, TaskCompletionSource<JsonDocument>> _requests = new();
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
var (requestId, response) = TryParseResponse(message);
if (requestId != null && _requests.TryRemove(requestId.Value, out var tcs))
{
tcs.TrySetResult(response);
}
else
{
yield return message;
}
}
(long? RequestId, JsonDocument? Response) TryParseResponse(string message)
{
try
{
var document = JsonDocument.Parse(message);
var requestId = response.RootElement.GetProperty("id").GetInt32();
return (document, requestId);
}
catch
{
return (null, null);
}
}
}
}
Note the usage of ConcurrentDictionary.TryRemove, which is safer than accessing the value and then removing it.
Now you can write your general SendAsync. As I note in my video, I prefer to split up the code that runs synchronously in SendAsync and the code that awaits the response:
public sealed class Client : IDisposable
{
...
public Task<TResponse> SendAsync<TResponse>(string method, object #params)
{
var request = new JsonRpcRequest<object>
{
JsonRpc = "2.0",
Id = NextId(),
Method = method,
Params = #params,
};
var tcs = new TaskCompletionSource<JsonDocument>(TaskCreationOptions.RunContinuationsAsynchronously);
_requests.TryAdd(request.Id, tcs);
return SendRequestAndWaitForResponseAsync();
async Task<TResponse> SendRequestAndWaitForResponseAsync()
{
var message = JsonSerializer.Serialize(request);
await _client.SendAsync(message);
var response = await tcs.Task;
return JsonSerializer.Deserialize(response, typeof(TResponse));
}
}
}
I've removed the "handler" concept completely, since it was just providing the type for JsonSerializer.Deserialize. Also, by using a local async method, I can use the async state machine to propagate exceptions naturally.
Then, your higher-level methods can be built on this:
public sealed class Client : IDisposable
{
...
public async Task<AuthResponse?> AuthenticateAsync(string clientId, string clientSecret)
{
var #params = new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", clientId},
{"client_secret", clientSecret}
};
var response = await SendAsync<SocketResponse<AuthResponse>>("public/auth", #params);
return response.Result;
}
}
So the final code ends up being:
public sealed class Client : IDisposable
{
private readonly ConcurrentDictionary<int, TaskCompletionSource<JsonDocument>> _requests = new();
public async IAsyncEnumerable<string> Start(CancellationToken cancellationToken)
{
await foreach (var message in _client.Start(cancellationToken))
{
var (requestId, response) = TryParseResponse(message);
if (requestId != null && _requests.TryRemove(requestId.Value, out var tcs))
{
tcs.TrySetResult(response);
}
else
{
yield return message;
}
}
(long? RequestId, JsonDocument? Response) TryParseResponse(string message)
{
try
{
var document = JsonDocument.Parse(message);
var requestId = response.RootElement.GetProperty("id").GetInt32();
return (document, requestId);
}
catch
{
return (null, null);
}
}
}
public Task<TResponse> SendAsync<TResponse>(string method, object #params)
{
var request = new JsonRpcRequest<object>
{
JsonRpc = "2.0",
Id = NextId(),
Method = method,
Params = #params,
};
var tcs = new TaskCompletionSource<JsonDocument>(TaskCreationOptions.RunContinuationsAsynchronously);
_requests.TryAdd(request.Id, tcs);
return SendRequestAndWaitForResponseAsync();
async Task<TResponse> SendRequestAndWaitForResponseAsync()
{
var message = JsonSerializer.Serialize(request);
await _client.SendAsync(message);
var response = await tcs.Task;
return JsonSerializer.Deserialize(response, typeof(TResponse));
}
}
public async Task<AuthResponse?> AuthenticateAsync(string clientId, string clientSecret)
{
var #params = new Dictionary<string, string>
{
{"grant_type", "client_credentials"},
{"client_id", clientId},
{"client_secret", clientSecret}
};
var response = await SendAsync<SocketResponse<AuthResponse>>("public/auth", #params);
return response.Result;
}
}
You may also want to check out David Fowler's Project Bedrock, which may simplify this code quite a bit.
I need to sent custom exceptions message to client.
I have the following code:
in Startup.cs ConfigureServices method
services.AddGrpc(options => options.Interceptors.Add<ErrorInterceptor>());
in ErrorInterceptor.cs
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(TRequest request, ServerCallContext context, UnaryServerMethod<TRequest, TResponse> continuation)
{
try
{
return await continuation(request, context);
}
catch (ValidationException validationExc)
{
await WriteResponseHeadersAsync(StatusCode.InvalidArgument, translation =>
translation.GetEnumTranslation(validationExc.Error, validationExc.Parameters));
}
catch (Exception)
{
await WriteResponseHeadersAsync(StatusCode.Internal, translation =>
translation.GetEnumTranslation(HttpStatusCode.InternalServerError));
}
return default;
Task WriteResponseHeadersAsync(StatusCode statusCode, Func<ITranslationService, string> getMessage)
{
var httpContext = context.GetHttpContext();
var translationService = httpContext.RequestServices.GetService<ITranslationService>();
var errorMessage = getMessage(translationService);
var responseHeaders = new Metadata
{
{ nameof(errorMessage) , errorMessage },//1) can see in browser's devTools, but not in the code
{ "content-type" , errorMessage },//2) ugly, but works
};
context.Status = new Status(statusCode, errorMessage);//3) not working
return context.WriteResponseHeadersAsync(responseHeaders);//4) alternative?
}
}
in mask-http.service.ts
this.grpcClient.add(request, (error, reply: MaskInfoReply) => {
this.grpcBaseService.handleResponse<MaskInfoReply.AsObject>(error, reply, response => {
const mask = new Mask(response.id, response.name);
callback(mask);
});
});
in grpc-base.service.ts
handleResponse<T>(error: ServiceError,
reply: {
toObject(includeInstance?: boolean): T;
},
func: (response: T) => void) {
if (error) {
const errorMessage = error.metadata.headersMap['content-type'][0];
this.toasterService.openSnackBar(errorMessage, "Ok");
console.error(error);
return;
}
const response = reply.toObject();
func(response);
}
I wanted to send error using Status (comment 3), but it doesn't get changed
I wonder if there is an alternative way to send it not in response headers (comment 4)
I tried to add custom response header (comment 1), but the only one I received in client code was 'content-type' so I decided to overwrite it (comment 2)
I hit the same dead end recently and decided to do it this way:
Create an error model:
message ValidationErrorDto {
// A path leading to a field in the request body.
string field = 1;
// A description of why the request element is bad.
string description = 2;
}
message ErrorSynopsisDto {
string traceTag = 1;
repeated ValidationErrorDto validationErrors = 2;
}
Create an extension for the error model that serializes the object to JSON:
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
public static class ErrorSynopsisDtoExtension
{
public static string ToJson(this ErrorSynopsisDto errorSynopsisDto) =>
JsonConvert.SerializeObject(
errorSynopsisDto,
new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
});
}
Create a custom exception that encapsulates error model:
public class OperationException : Exception
{
private readonly List<ValidationErrorDto> validationErrors = new();
public bool HasValidationErrors => this.validationErrors.Count > 0;
public OperationException(string traceTag) : base
(
new ErrorSynopsisDto
{
TraceTag = traceTag
}.ToJson() // <- here goes that extension
) => ErrorTag = traceTag;
public OperationException(
string traceTag,
List<ValidationErrorDto> validationErrors
) : base
(
new ErrorSynopsisDto
{
TraceTag = traceTag,
ValidationErrors = { validationErrors }
}.ToJson() // <- here goes that extension again
)
{
ErrorTag = traceTag;
this.validationErrors = validationErrors;
}
}
Throw custom exception from service call handlers:
throw new OperationException(
"MY_CUSTOM_VALIDATION_ERROR_CODE",
// the following block can be simplified with a mapper, for reduced boilerplate
new()
{
new()
{
Field = "Profile.FirstName",
Description = "Is Required."
}
}
);
And lastly, the exception interceptor:
public class ExceptionInterceptor : Interceptor
{
private readonly ILogger<ExceptionInterceptor> logger;
public ExceptionInterceptor(ILogger<ExceptionInterceptor> logger) => this.logger = logger;
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation
)
{
try
{
return await continuation(request, context);
}
catch (OperationException ex)
{
this.logger.LogError(ex, context.Method);
var httpContext = context.GetHttpContext();
if (ex.HasValidationErrors)
{
httpContext.Response.StatusCode = StatusCodes.Status400BadRequest;
}
else
{
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
}
throw;
}
catch (Exception ex)
{
this.logger.LogError(ex, context.Method);
var httpContext = context.GetHttpContext();
httpContext.Response.StatusCode = StatusCodes.Status500InternalServerError;
var opEx = new OperationException("MY_CUSTOM_INTERNAL_ERROR_CODE");
throw new RpcException(
new Status(
StatusCode.Internal,
opEx.Message
)
);
}
}
}
On the TypeScript-based frontend, I simply catch RPC errors and hydrate the message like this:
JSON.parse(err.message ?? {}) as ErrorSynopsisDto
After publishing the bot, the user needs to chat with the bot again first in order send a proactive message. I followed this sample but instead of storing the conversation reference in the variable, I stored it in cosmosDB. However I still can't send proactive message if the user has not chatted with the bot after publishing. Is there a way to send proactive message even when the user has not chatted with the bot after publishing?
DialogBot
private async void AddConversationReference(ITurnContext turnContext)
{
var userstate = await _userProfileAccessor.GetAsync(turnContext, () => new BasicUserState());
userstate.SavedConversationReference = turnContext.Activity.GetConversationReference();
_conversationReferences.AddOrUpdate(userstate.SavedConversationReference.User.Id, userstate.SavedConversationReference, (key, newValue) => userstate.SavedConversationReference);
}
protected override Task OnConversationUpdateActivityAsync(ITurnContext<IConversationUpdateActivity> turnContext, CancellationToken cancellationToken)
{
AddConversationReference(turnContext);
return base.OnConversationUpdateActivityAsync(turnContext, cancellationToken);
}
protected override async Task OnMessageActivityAsync(ITurnContext<IMessageActivity> turnContext, CancellationToken cancellationToken)
{
Logger.LogInformation("Running dialog with Message Activity.");
AddConversationReference(turnContext);
await Dialog.RunAsync(turnContext, ConversationState.CreateProperty<DialogState>("DialogState"), cancellationToken);
}
api/notify
namespace SabikoBotV2.Controllers
{
[Route("api/notify")]
public class NotifyController : ControllerBase
{
private readonly IBotFrameworkHttpAdapter _adapter;
private readonly string _appId;
private readonly ConcurrentDictionary<string, ConversationReference> _conversationReferences;
private readonly IStatePropertyAccessor<BasicUserState> _userProfileAccessor;
public NotifyController(UserState userState, IBotFrameworkHttpAdapter adapter, ICredentialProvider credentials, ConcurrentDictionary<string, ConversationReference> conversationReferences)
{
_userProfileAccessor = userState.CreateProperty<BasicUserState>("UserProfile");
_adapter = adapter;
_conversationReferences = conversationReferences;
_appId = ((SimpleCredentialProvider)credentials).AppId;
if (string.IsNullOrEmpty(_appId))
{
_appId = Guid.NewGuid().ToString(); //if no AppId, use a random Guid
}
}
public async Task<IActionResult> Get()
{
try
{
foreach (var conversationReference in _conversationReferences.Values)
{
await ((BotAdapter)_adapter).ContinueConversationAsync(_appId, conversationReference, BotCallback, default(CancellationToken));
}
return new ContentResult()
{
Content = "<html><body><h1>Proactive messages have been sent.</h1></body></html>",
ContentType = "text/html",
StatusCode = (int)HttpStatusCode.OK,
};
}
catch (Exception ex)
{
return BadRequest(ex.Message);
}
}
private async Task BotCallback(ITurnContext turnContext, CancellationToken cancellationToken)
{
var userstate = await _userProfileAccessor.GetAsync(turnContext, () => new BasicUserState(), cancellationToken);
if (userstate.SavedConversationReference.ServiceUrl != null && userstate.SavedConversationReference.ServiceUrl != string.Empty)
{
MicrosoftAppCredentials.TrustServiceUrl(userstate.SavedConversationReference.ServiceUrl);
}
else if (turnContext.Activity.ServiceUrl != null && turnContext.Activity.ServiceUrl != string.Empty)
{
MicrosoftAppCredentials.TrustServiceUrl(turnContext.Activity.ServiceUrl);
}
else
{
MicrosoftAppCredentials.TrustServiceUrl("https://facebook.botframework.com/");
}
if(userstate.Reminders != null)
{
foreach (var reminder in userstate.Reminders)
{
if (reminder.DateAndTime.TrimMilliseconds() == DateTimeNowInGmt().TrimMilliseconds())
{
var timeProperty = new TimexProperty(reminder.DateAndTimeTimex);
var naturalDate = timeProperty.ToNaturalLanguage(DateTimeNowInGmt().TrimMilliseconds());
await turnContext.SendActivityAsync($"It's {DateTimeNowInGmt().ToLongDateString()}. \n\nReminding you to {reminder.Subject}.");
}
}
}
}
public static DateTime DateTimeNowInGmt()
{
TimeZoneInfo phTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Taipei Standard Time");
var t = DateTime.Now;
DateTime phTime = TimeZoneInfo.ConvertTime(t, phTimeZone);
return phTime;
}
}
}
I want to change the value of a claim via refresh tokens. My refresh token provider is like this:
public class MyRefreshTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
...
var claim = context.Ticket.Identity.FindFirst(ClaimTypes.UserData);
if (claim != null)
{
context.Ticket.Identity.RemoveClaim(claim);
context.Ticket.Identity.AddClaim(new Claim(ClaimTypes.UserData, "New Value"));
}
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
}
And in the startup class:
app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions
{
...
RefreshTokenProvider = new MyRefreshTokenProvider()
});
The refresh token request completes with no error. But when I use the new access token, the claim value is still the old one.
Is my approach right? Or how can I change a claim value in the Bearer authentication?
Finally I've found the solution. I have to extend the AccessTokenProvider OAuthAuthorizationServerOptions instead of the RefreshTokenProvider:
app.UseOAuthBearerTokens(new OAuthAuthorizationServerOptions
{
...
AccessTokenProvider = new MyAccessTokenProvider(),
RefreshTokenProvider = new MyRefreshTokenProvider()
});
public class MyAccessTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
...
var claim = context.Ticket.Identity.FindFirst(ClaimTypes.UserData);
if (claim != null)
{
context.Ticket.Identity.RemoveClaim(claim);
context.Ticket.Identity.AddClaim(new Claim(ClaimTypes.UserData, "New Value"));
}
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
}
public class MyRefreshTokenProvider : AuthenticationTokenProvider
{
public override void Create(AuthenticationTokenCreateContext context)
{
context.SetToken(context.SerializeTicket());
}
public override void Receive(AuthenticationTokenReceiveContext context)
{
context.DeserializeTicket(context.Token);
}
}
According to the OAuthAuthorizationServerHandler class in the Microsoft.Owin.Security.OAuth the AccessTokenProvider only can update the refreshing token. For changing the claims the AccessTokenProvider should be extended:
private async Task InvokeTokenEndpointAsync()
{
...
var accessTokenContext = new AuthenticationTokenCreateContext(
Context,
Options.AccessTokenFormat,
ticket);
await Options.AccessTokenProvider.CreateAsync(accessTokenContext);
string accessToken = accessTokenContext.Token;
if (string.IsNullOrEmpty(accessToken))
{
accessToken = accessTokenContext.SerializeTicket();
}
DateTimeOffset? accessTokenExpiresUtc = ticket.Properties.ExpiresUtc;
var refreshTokenCreateContext = new AuthenticationTokenCreateContext(
Context,
Options.RefreshTokenFormat,
accessTokenContext.Ticket);
await Options.RefreshTokenProvider.CreateAsync(refreshTokenCreateContext);
string refreshToken = refreshTokenCreateContext.Token;
var memory = new MemoryStream();
byte[] body;
using (var writer = new JsonTextWriter(new StreamWriter(memory)))
{
writer.WriteStartObject();
writer.WritePropertyName(Constants.Parameters.AccessToken);
writer.WriteValue(accessToken);
writer.WritePropertyName(Constants.Parameters.TokenType);
writer.WriteValue(Constants.TokenTypes.Bearer);
if (accessTokenExpiresUtc.HasValue)
{
TimeSpan? expiresTimeSpan = accessTokenExpiresUtc - currentUtc;
var expiresIn = (long)expiresTimeSpan.Value.TotalSeconds;
if (expiresIn > 0)
{
writer.WritePropertyName(Constants.Parameters.ExpiresIn);
writer.WriteValue(expiresIn);
}
}
if (!String.IsNullOrEmpty(refreshToken))
{
writer.WritePropertyName(Constants.Parameters.RefreshToken);
writer.WriteValue(refreshToken);
}
...
I came across this code and wasn't able to test it as it references HttpContext. How do I remove the dependency on HttpContext to make this testable?
public class AuthorizeByUserStatus :AuthorizationFilterAttribute
{
private readonly UserStatusEnum status;
public AuthorizeByUserStatus(UserStatusEnum status)
{
this.status = status;
}
public override async Task OnAuthorizationAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
var authorize = false;
var user = HttpContext.Current.User;
var userIdentity = user.GetIdentity();
if (userIdentity.UserStatus >= status)
{
authorize = true;
}
if (authorize)
{
base.OnAuthorization(actionContext);
}
else
{
throw new HttpResponseException(HttpStatusCode.Unauthorized);
}
}
}
Replace the static HttpContext methods and reference the passed in actionContext which has the information we need!
var userIdentity = actionContext.RequestContext.Principal.GetIdentity();
you can now pass in the actionContext with the correctly set Request details to get at the requesting users identity.