Setting custom username to Initializer Application Insights - c#

I am looking to add a logged-in user's name to Application Insights to replace the cookie by default. I found this post that does something like this
public class RealUserIDTelemetryInitializer:ITelemetryInitializer
{
public void Initialize(Microsoft.ApplicationInsights.Channel.ITelemetry telemetry)
{
// Replace this with your custom logic
if (DateTime.Now.Ticks % 2 > 0)
{
telemetry.Context.User.Id = "Ron Weasley";
}
else
{
telemetry.Context.User.Id = "Hermione Granger";
}
}
}
My question, is how do I pass data into this? I have a login method on my Api that returns some info, but calling into this Initializer is something I am confused on.

We once had that same issue, although not for a user id but for an operation id. But the same principle can be applied here. We used a Func<T> that provides the data for the initializer (See full code of the initializerhere and the usage here).
public class RealUserIDTelemetryInitializer:ITelemetryInitializer
{
private readonly Func<string> usernameProvider;
public RealUserIDTelemetryInitializer(Func<string> usernameProvider)
{
this.usernameProvider = usernameProvider;
}
public void Initialize(Microsoft.ApplicationInsights.Channel.ITelemetry telemetry)
{
telemetry.Context.User.Id = usernameProvider.Invoke();
}
}
Then add the initializer using code:
configuration.TelemetryInitializers.Add(new RealUserIDTelemetryInitializer(() =>
CallContext.LogicalGetData(UserId)?.ToString()));
As you can see in our case the data was provided using data on the current thread but in your case I suppose there is easier access to the username so you wil probably inject another Func.

Related

Pass a value globally

Im new to xamarin/c#, im trying to make an application with login , and Im trying to pass the logged in userid inside the application, the question is , how do I pass or make the user id keeps floating inside after the login page? Should I keep passing it in every page using queryproperty or there's better way to keep it , like a specific file to to put it so that every page can call it?
You can use the Application.Properties collection to store things that need to be accessible to the entire application.
To store the user ID you would use
Application.Current.Properties("UserID") = UserID;
and to retrieve it you would use
UserID = Application.Current.Properties("UserID");
In C# it's not possible to define true global variables (meaning that they don't belong to any class). using a static class is a valid alternative option so you can create something like this:
public static class Globals
{
public Dictionary<int, UserObject> Users = new Dictionary<int, UserObject>();
}
Now, you'll be able to access The Users's dictionary property and add/remove/modify login users
Following Hans Kesting comment, Please note that An Xamarin app servers a single user at at time, so you can refactor the above from a dictionary to UserObject
Static classes - bad practic for contains info. You can use IoC-container. I don't know xamarin, if you have startup-class (how WPF), you can make ioc-container:
Install Autofac Install-Package Autofac -Version 5.0.0
Rigster user content:
class StartupClass
{
public static IContainer Container { get; set; }
public void OnStartup()
{
var containerBuilder = new ContainerBuilder();
var userContent = smthMethodForGiveUserContent();
containerBuilder.RegisterInstance<User>(userContent); //register UserType!
Container = containerBuilder.Build();
}
}
Resolve user content:
class SmthClass
{
public void Method()
{
using(var scope = StartupClass.Container.BeginLifetimeScope())
{
var userContent = scope.Resolve<User>(); //IT'S YOUR OBJECT, USE!!
//smth code..
}
}
}
The quickest way is to define a variable in App, it can be accessible to the entire project .
Because App itself has been defined inside Application class ,and it is a static property .
public partial class App : Xamarin.Forms.Application
{
public string UserID;
}
// set or get
(App.Current as App).UserID

Options pattern: Two option objects, one needs information from the other

I'm using the Options pattern to configure my ASP.net Core 3.1 web app.
There are two options classes:
public class SystemOptions
{
public string RootPath { get; set; }
}
public class ModuleOptions
{
public string SubPath { get; set; }
// this should become something like RootPath + SubPath
public string FullPath { get; }
}
And the associated appsettings.json
{
"SystemOptions": {
"RootPath": "\\webdav"
},
"ModuleOptions": {
"SubPath": "\subdirformodule"
}
}
And in Startup:
public void ConfigureServices(IServiceCollection services)
{
services.Configure<SystemOptions>(configuration.GetSection("SystemOptions"));
services.Configure<ModuleOptions>(configuration.GetSection("ModuleOptions"));
}
Now I would like to initialize the FullPath in ModuleOptions once during app startup.
Therefore I need access to the SystemOptions.RootPath from within the ModuleOptions.
Is this possible?
I tried the following:
I added an InitializeFullPath() method to the ModuleOptions:
public string InitializeFullPath(string basePath)
{
// concat basePath and SubPath and return
... return fullPath;
}
and tried to use this in ConfigureServices:
services.Configure<SystemOptions>(configuration.GetSection("SystemOptions"));
services.AddOptions<ModuleOptions>()
.Configure<SystemOptions>((s, m) => m.FullPath = m.InitializeFullPath(s.RootPath));
But all I get is:
"No service for type '...SystemOptions' has been registered."
later on when Startup.Configure() is executed.
(And by the time this error occured, the InitializeFullPath method has not been executed at all - a breakpoint set there was not hit.)
So I have two questions:
how can I use the content of one option object during initialization of the second option object?
When will the delegate that you can specify in Configure() be executed?
I am going to answer your second question first. The configuration delegate is invoked the first time the Value property of the IOptions<YourOptions> is invoked. This interface is registered as a singleton so it's a one-time only thing. For IOptionsMonitor/IOptionsSnapshot they are similarly invoked on every new instance of the options.
Now to your first question... You were close! This should work:
services.AddOptions<ModuleOptions>()
.Configure<IOptions<SystemOptions>>(
(mod, sys) => mod.FullPath = mod.InitializeFullPath(sys.Value.RootPath)
);
Note that we are using IOptions<SystemOptions> and .Value. The Configure method that is chained to AddOptions is not the same as the one directly on the service collection; the generic arguments are the dependent service types and the first parameter is the options type from AddOptions. So that means that you reversed the arguments to the delegate (the option being configured is the first parameter).
Another...option is to use the IConfigureOptions interface. I typically go this route and don't use the form you have shown, even for "simple" dependent configuration:
public ModuleOptionsConfigurator : IConfigureOptions<ModuleOptions>
{
private readonly SystemOptions _sys;
public ModuleOptionsConfigurator(IOptions<SystemOptions> opts)
=> _sys = opts.Value;
public void Configure(ModuleOptions mod)
{
mod.FullPath = mod.InitializeFullPath(_sys.RootPath);
}
}
Which you then register with DI like so:
services.Configure<SystemOptions>(configuration.GetSection("SystemOptions"));
services.Configure<ModuleOptions>(configuration.GetSection("ModuleOptions"))
// register the configurator
services.ConfigureOptions<ModuleOptionsConfigurator>();
This allows you to encapsulate any sort of configurarion logic into a class. You can take zero dependencies up to however many you need.
The IPostConfigureOptions<> interface works similarly, but will run after all other Configure callbacks and IConfigureOptions<> implementations (and allows you to act differently for named options). Based on your description, this may be the better interface:
public ModuleOptionsPostConfigurator : IPostConfigureOptions<ModuleOptions>
{
private readonly SystemOptions _sys;
public ModuleOptionsPostConfigurator(IOptions<SystemOptions> opts)
=> _sys = opts.Value;
public void PostConfigure(string name, ModuleOptions mod)
{
mod.FullPath = mod.InitializeFullPath(_sys.RootPath);
}
}
IPostConfigureOptions is registered the same way as IConfigureOptions:
// register the configurator
services.ConfigureOptions<ModuleOptionsPostConfigurator>();
You can also combine the two interfaces in one implementing class, which I have often found a case for.
See the official documentation for more information on the options patterns.

Provide user information from signalr request in business logic layer using autofac

I have an ASP.NET MVC 5 Application with a SignalR 2 hub and using autofac for the DI.
The entire business logic is encapsulated in manager classes in their own layer. Some manager methods need informations about the current logged in user (UserId, TenantId, ..).
I solved this problem by injecting an AuthorizationProvider into each manager class that needs the user information.
public interface IAuthorizationProvider
{
long? GetUserId();
long? GteTenantId();
}
public class MyManager : IMyManager
{
private IAuthorizationProvider _authorizationProvider;
public MyManager(IAuthorizationProvider authorizationProvider)
{
_authorizationProvider = authorizationProvider;
}
public void MyMethod()
{
// Getting the User information here is pretty simple
long userId = _authorizationProvider.GetUserId();
}
}
Normally I can get the user information from the HttpContext and from the session. So I wrote a SessionAuthorizationProvider:
public class SessionAuthorizationProvider{
public long? GetUserId()
{
HttpContext.Current?.Session?[SessionKeys.User]?.Id;
}
public long? GteTenantId() { ... }
}
But now I have a new method in the SignalR hub that use the same mechanism.
[HubName("myHub")]
public class MyHub : Hub
{
private IMyManager _myManager;
public MyHub(IMyManager myManager)
{
_myManager = myManager;
}
[HubMethodName("myHubMethod")]
public void MyHubMethod(long userId, long tenantId)
{
_myManager.MyMethod();
}
}
The problem is that a SignalR request doesn't have a session. Therefore I have also set the required user information in the hub method as parameters postet from the client.
So I thought it is the best solution for this problem to write a new AuthorizationProvider for SignalR and adapt the depdendency resolver. But I can't get the current user in the new SignalrAuthorizationProvider.
public class SignalrAuthorizationProvider{
public long? GetUserId()
{
// How to get the user information here???
}
public long? GteTenantId() { /* and here??? */ }
}
Is there a recommended solution to this problem?
Of course, I can extend MyMethod to accept the user information as a parameter. But MyMethod calls another method from another manager and that manager also calls another method. The user information is only needed for the last method call. So I had to change at least 3 methods and many more in the future.
Here is a sketch of the problem
This is a potential solution. But it's very bad
Session is not supported by SignalR by default and you should avoid using it. See No access to the Session information through SignalR Hub. Is my design is wrong?. But you still can use cookie or querystring to get the desired value.
In both case you need to have access to the HubCallerContext of the underlying hub, the one that is accessible through the Context property of the Hub.
In a ideal word you should just have to had the dependency to the SignalAuthorizationProvider
ie :
public class SignalrAuthorizationProvider {
public SignalrAuthorizationProvider(HubCallerContext context){
this._context = context;
}
private readonly HubCallerContext _context;
public long? GetUserId() {
return this._context.Request.QueryString["UserId"]
}
}
But due to SignalR design it is not possible. Context property is assigned after construction of the Hub and AFAIK there is no way to change it.
Source code here : HubDispatcher.cs
One possible solution would be to inject a mutable dependency inside the Hub and alter the object in the OnConnected, OnReconnected methods.
public class SignalrAuthorizationProvider : IAuthorizationProvider
{
private Boolean _isInitialized;
private String _userId;
public String UserId
{
get
{
if (!_isInitialized)
{
throw new Exception("SignalR hack not initialized");
}
return this._userId;
}
}
public void OnConnected(HubCallerContext context)
{
this.Initialize(context);
}
public void OnReconnected(HubCallerContext context)
{
this.Initialize(context);
}
private void Initialize(HubCallerContext context) {
this._userId = context.QueryString["UserId"];
this._isInitialized = true;
}
}
and the Hub
public abstract class CustomHub : Hub
{
public CustomHub(IAuthorizationProvider authorizationProvider)
{
this._authorizationProvider = authorizationProvider;
}
private readonly IAuthorizationProvider _authorizationProvider;
public override Task OnConnected()
{
this._authorizationProvider.OnConnected(this.Context);
return base.OnConnected();
}
public override Task OnReconnected()
{
this._authorizationProvider.OnReconnected(this.Context);
return base.OnReconnected();
}
}
Having a mutable dependency is not the best design but I can't see any other way to have access to IRequest or HubCallerContext.
Instead of having an abstract Hub class which is not a perfect solution. You can change the RegisterHubs autofac method to use AOP with Castle.Core and let the interceptor calls the methods for you.

How to access Session from a custom data annotation in an ASP.NET MVC project?

For some experimental/exercise purposes, I'm trying to implement a primitive login system before proceeding to Identity services.
My idea was creating a custom data annotation -just like [Authorize] of Identity- and passing inputted form data carried by Session[]. Hence, I would use my custom annotation to authorize certain controllers or methods.
public class IdCheck: ValidationAttribute
{
public string currentId { get; set; }
public override bool IsValid(object value)
{
currentId = value as string;
if (currentId != null)
{
return true;
}
else
{
return false;
}
}
}
and I tried to use it like that:
[IdCheck(currentId = Session["UserId"])]
public class SomeController : Controller
{
//methods
}
However, I got an error with the message: "An object reference is required for the non static field, method or property 'Controller.Session'"
I can neither access the Session[] before a controller nor before method declaration. I have no idea how to instantiate or refere.
Is there a way to utilize Session[] in my case? Because I don't want to manually check each method with if statements like
if(Session["UserId"] != null) {}
Any alternative solution is more than welcomed.
Note: I am aware of that using Session is not a recommended way of implementing such a task. But my intention is to have an understanding of how login operations work before utilizing advanced Identity services.
Is there a way to utilize Session[] in my case?
Not as you're doing now, but you could use HttpContext.Current.Session inside your custom attribute code instead of passing it as property.
Thanks a lot Oscar. What you proposed actually worked. However, I managed to find a better way yo carry out my "primitive login" operation using a custom data annotation.
After some more research it turned out that implementing FilterAttribute class along with IAuthorizationFilter filter does the trick. I believe this could help someone else as well:
public class IdCheck : FilterAttribute, IAuthorizationFilter
{
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext.HttpContext.Session["UserId"] == null)
{
filterContext.Result = new RedirectResult("/");
}
}
}
and the usage is the same of course:
[IdCheck]
public class SomeController : Controller
{
//methods
}

DI into a Requirement/Policy in ASP.NET MVC 6

I'm looking for a way to program a custom authorization filter in ASP.NET 5 as the current implementation relies in Policies/Requirements which in turn rely solely in the use of Claims, thus on the umpteenth and ever-changing Identity System of which I'm really tired of (I've tried all it's flavors).
I have a large set of permissions (over 200) which I don't want to code as Claims as I have my own repository for them and a lot faster way to be check against it than comparing hundreds of strings (that is what claims are in the end).
I need to pass a parameter in each attribute that should be checked against my custom repository of permissions:
[Authorize(Requires = enumPermission.DeleteCustomer)]
I know this is not the most frequent scenario, but I think it isn't an edge case. I've tried implementing it in the way described by #leastprivilege on his magnificent post "The State of Security in ASP.NET 5 and MVC 6: Authorization", but I've hit the same walls as the author, who has even opened an issue on the ASP.NET 5 github repo, which has been closed in a not too much clarifying manner: link
Any idea of how to achieve this? Maybe using other kind of filter? In that case, how?
Following is an example of how you can achieve this scenario:
Let's assume you have a service called IPermissionStore which validates if a given user has the required permissions specified on the attribute.
public class MyCustomAuthorizationFilterAttribute : Attribute, IFilterFactory, IOrderedFilter
{
private readonly Permision[] _permissions;
public MyCustomAuthorizationFilterAttribute(params Permision[] permissions)
{
_permissions = permissions;
}
public int Order { get; set; }
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
var store = serviceProvider.GetRequiredService<IPermissionStore>();
return new MyCustomAuthorizationFilter(store, _permissions)
{
Order = Order
};
}
}
public class MyCustomAuthorizationFilter : IAuthorizationFilter, IOrderedFilter
{
private readonly IPermissionStore _store;
private readonly Permision[] _permissions;
public int Order { get; set; }
public MyCustomAuthorizationFilter(IPermissionStore store, params Permision[] permissions)
{
_store = store;
_permissions = permissions;
}
public void OnAuthorization(AuthorizationContext context)
{
// Check if the action has an AllowAnonymous filter
if (!HasAllowAnonymous(context))
{
var user = context.HttpContext.User;
var userIsAnonymous =
user == null ||
user.Identity == null ||
!user.Identity.IsAuthenticated;
if (userIsAnonymous)
{
Fail(context);
}
else
{
// check the store for permissions for the current user
}
}
}
private bool HasAllowAnonymous(AuthorizationContext context)
{
return context.Filters.Any(item => item is Microsoft.AspNet.Authorization.IAllowAnonymous);
}
private void Fail(AuthorizationContext context)
{
context.Result = new HttpUnauthorizedResult();
}
}
// Your action
[HttpGet]
[MyCustomAuthorizationFilter(Permision.CreateCustomer)]
public IEnumerable<string> Get()
{
//blah
}

Categories