Injecting IOptions<> into ApiKeyAuthorizeAttribute - asp.net-core

I am using options pattern that stores different configurations, including API keys for different environments. So far I have been using it fine and injecting my values into classes as needed.
However, I faced a little challenge while trying to setup authorization in the controller and run validation against my ApiKey that is unique per environment, because I was not able to inject IOptions into ApiKeyAuthorizeAttribute class to perform validation.
Here is how my controller looks like now:
[ApiKeyAuthorize]
public class NotificationSettingsController : Controller
{
//some endpoints here
}
ApiKeyAuthorize Class:
public class ApiKeyAuthorizeAttribute : Attribute, IAuthorizationFilter
{
//////This...
private readonly IOptions<MyConfig> _config;
public ApiKeyAuthorizeAttribute(IOptions<MyConfig> config)
{
_config = config;
}
/////////...is what I am trying to accomplish
public void OnAuthorization(AuthorizationFilterContext context)
{
var request = context.HttpContext.Request;
var foundApiKeys = request.Headers.TryGetValue("ReplaceWithOptionsApiKeyName", out var requestApiKeys);
if (!foundApiKeys || requestApiKeys[0] != "ReplaceWithOptionsApiKeyValue")
{
context.Result = new UnauthorizedResult();
}
}
}
My problem is that injecting here isn't possible, but I need to get a value from IOptions<> to run ApiKey validation.

Attributes are constructed in-place, so it's not possible to inject dependencies into them. However, ASP.NET Core provides a workaround. Instead of applying the attribute directly, you can use the ServiceFilter attribute instead and pass it the type of the filter you want to apply:
[ServiceFilter(typeof(ApiAuthorizeAttribute))]
This will dynamically apply the filter to the controller/action while instantiating it with any dependencies it requires at the same time. However, it does limit you in the other direction. For example, if you need to do something like:
[ApiAuthorizeAttribute(Roles = "Admin")]
It would not be possible to achieve this with the ServiceFilter attribute, because you cannot pass property values, like Roles here, along with the type.

Related

ASP.Core ODataControllers not recognizing "get by key" operation

I'm investigating how to use OData in ASP.Core.
I've created a BooksController, subclassed from ODataController, within which I've defined two Actions: Get(), and Get(int id).
/odata/books resolves to the first Action, but /odata/books(1) does not find the second Action.
Once the Models are defined, it is able to find the following Controller:
[ODataRoutePrefix("Books")]
public class BooksController : ODataController
{
private BookStoreContext _db;
public BooksController(BookStoreContext context)
{
_db = context;
}
[ODataRoute]
[EnableQuery]
public IActionResult Get()
{
return Ok(_db.Books);
}
[EnableQuery]
[ODataRoute("({key})")]
public IActionResult Get([FromODataUri] int key)
{
return Ok(_db.Books.FirstOrDefault(c => c.Id == key.ToGuid()));
}
}
The site has the default Convention rules for all routes (see below).
But I think this is not in play, as the BooksController is decorated with [ODataRoutePrefix("Books")] and the actions with [ODataRoute] (and [EnableQuery]) -- which I think, being Attribute based routing, take precedence (is that a correct assumption?).
My dto models are are registered using Reflection...), but the key part is where Startup invokes UseMvc(...), and defines the routes, which ends up calling here:
private void CreateODataRoutes(IRouteBuilder routeBuilder)
{
// register the convention routes for MVC first...
routeBuilder.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
...
// then do the OData stuff...
routeBuilder.Count().Expand().Filter()
.MaxTop(100).OrderBy().Select();
// Use method further down the page
// to create a Build Model by reflection, using
// all OData Model definitions (ie, classes that implement
// IAllModulesOdataModelBuilderConfiguration)
var oDataConventionModelBuilder = BuildODataModelUsingReflectionAcrossAllAssemblies();
// Use the modelBuilder as the basis of defining routes:
RegisterRoutesToODataController(routeBuilder, oDataConventionModelBuilder);
}
Where BuildODataModelUsingReflectionAcrossAllAssemblies uses reflection to find individual model definitions, each one pretty simple, only defining their id (relying on convention for the rest).
Note that I'm not defining Actions as that used to be by convention (see further down).
public class BookODataModelBuilderConfigurationBase<T> : IAllModulesOdataModelBuilderConfiguration
where T : class, IHasGuidId, new()
{
public virtual void Apply(ODataModelBuilder builder ...)
{
var _controllerName = this.GetControllerNameByConvention(typeof(Book));
var entity = builder.EntitySet<T>(this._controllerName).EntityType;
entity.HasKey(x => x.Id);
//Note...no Actions defined, as planning to rely on default conventions (routing by Verb to method starting with Get...)
}
}
When the model is created, it is registered as follows;
private void RegisterRoutesToODataController(IRouteBuilder routeBuilder,
ODataConventionModelBuilder oDataConventionModelBuilder)
{
string routePrefix = $"{App.Modules.Core.Shared.Constants.ModuleSpecific.Module.AssemblyNamePrefix}.";
// Build the Edm model used to parse commands:
var edmModel = oDataConventionModelBuilder.GetEdmModel();
// Register the Odata paths
routeBuilder.MapODataServiceRoute(
routeName: $"{routePrefix}odataDefault",
routePrefix: "odata",
edmModel,
pathHandler:new DefaultODataPathHandler(),
// By convention? So that Get verb goes to Get action, etc.
routingConventions: ODataRoutingConventions.CreateDefault()
);
}
When the path is /odata/book(1) it returns HTTP ERROR 404, the page does not exist.
Thank you!
Other things I've tried include:
commented out configuration of SwaggerAPI
Removed [FromODataUri] on the key param (is it necessary?)
Added/removed [ODataRoute("({key})")]
Registered the Controller as BooksController in plural/singular
Changed the name of the Action to GetBook and back again to Get
Added/removed ODataRoutePrefix
registering the OData routes before registering the default convention routes (think that should be the case all the time, right?).
...all of which is starting to look more like desperation than coding :-( ...
Still looking. Thanks for any guidance.
OMG. (sheepishly) solved.
It wasn't the Framework, Nuget, the Controller base class, routeprefix, routes, or anything glorious, it was ...me.
The only place I was not looking was the Model itself, which defined the Id as a Guid. The Action was using an int, converting it to a Guid.
ASP.Core could not find it because it was building routes based on the Model (not the Controller), so ignored the Action as it made no sense to its convention based route building as an int != Guid. duh.
If you were wondering why the heck I used an int...it was because when I had seeded the Db I wanted a Guid Key, but for testing purposes I wanted some records to have specific Ids that I could refer back to, and I was lazy, and didn't want to type in a full Guid.
That was a DUMB idea, in retrospect...:-(
But thank you for looking into it! Appreciate the time spent.

MVC 4 How to process a url parameter on every page, base controller?

Looking for some guidance in designing my new MVC 4 app.
I would like to have a url parameter s=2011 on every page of the app to let me know what year of data I'm working with. Obviously, the user will have a way to change that parameter as needed.
I will need that parameter in every controller and wondering the best way to do this. I was thinking of creating a base controller that reads Request.QueryString and puts the year into a public property. However, considering all the extensability points in MVC, I'm wondering if there's a better way to do this?
This very much depends on the design of your app, but just to give you two alternatives
IActionFilter
If you are doing data context per request you can use a global IActionFilter to hook pre-action execution globally and apply a query filter to your data context behind the scenes.
Major down-side of this is that to test the controller you will need to have the full MVC pipeline setup so that the actionfilter gets applied properly.
Dependency Injection
Instead of using sub-classing (base controller as you say) you can use dependency injection . Keeping things more loose will allow you to pull the filter from query string, cookie, user setting in the database or whatever else - without your controller knowing where it comes from.
Here is some pseudo code how I would do it if I was using something like Entity Framework or Nhibernate (also I am sure applicable with other technologies as well)
public Car
{
public string Year { get; set; }
}
public class CarsDataContext : DbContext
{
private IQuerable<Cars> _cars = null;
private Func<Car, bool> _carsFilter = null;
public IQuerable<Car> Cars {
get {
if (_carsFitler != null)
return _cars.Where(_carsFitler);
return _cars;
}
set { _cars = value; }
}
public void ApplyCarsFilter(Func<Car, bool> predicate)
{
_carsFilter = predicate;
}
}
Assuming you have dependency injection setup already (NInject or whichever other framework) in you can configure how the context to be intialized
Bind<CarsDataContext>().ToMethod(() => {
string yearFilter = GetYearFilter(); // can be coming from anywhere
CarsDataContext dataContext = new CarsDataContext();
dataContext.Applyfilter(car => car.Year == yearFilter);
return dataContext;
}).InRequestScope();
Then my controller knows nothing about the data filtering and I can easily test it:
class MyController : Controller
{
public MyController(CarsDataContext dataContext)
{
}
...
}
However I would only do this is filtering the dataset was across many controllers and important part of my software. Otherwise it's pure over-engineering.

How to tackle behavior variation with StructureMap?

I have a set of componentes registered to StructureMap. What should be the best way to resolve a component depending on the actual Tenant?
Small example:
There are two tenants, say, Yellow and Green.
I have an IValidator that has two implementations: YellowValidator and GreenValidator.
Say the application is MVC and that the tentant comes form the URL.
So, I just need the proper IValidator to be injected depending on the tenant.
I've seen many solutions for multi-tenant applications that deals only with multitenancy of data, normaly configuring different databases depending on the tenant. That involves only parameter passing. But this is the case where variation occurs in behavior, not in data. I want the IoC container to Resolve the right instance transparently.
EDIT: more info:
The IValidator interface have a simple method bool Validate(), but the implementation require some injection.
There are other custom validators, but they are used by both tenants.
There is a clear tentant strategy based on the URL. This means that each request can have a different tenant, and that a single application serves both tenants.
There are many ways to skin a cat. It's hard for me to guess the design of your application, so here is an idea. Things that come in mind are to hide validators behind a composite, to allow users of the IValidator interface to know nothing about having many implementations. Such composite can look like this:
public class ValidatorComposite : IValidator
{
private IEnumerable<IValidator> validators;
public ValidatorComposite(
IEnumerable<IValidator> validators)
{
this.validators = validators;
}
public bool Validate(object instance)
{
return this.validators.All(v => v.Validate(instance));
}
}
You can create multiple composites and register them by key where the key is the name of the tenant (but without keyed registrations is probably just as easy). Those composites can be wrapped in yet another composite that will delegate to the proper tenant-specific composite. Such a tenant-selecting composite could look like this:
public class TenantValidatorComposite : IValidator
{
private ITenantContext tenantContext;
private IValidator defaultValidator;
private IDictionary<string, IValidator> tenantValidators;
public ValidatorComposite(
ITenantContext tenantContext,
IValidator defaultValidator,
IDictionary<string, IValidator> tenantValidators)
{
this.tenantContext = tenantContext;
this.defaultValidator = defaultValidator;
this.tenantValidators = tenantValidators;
}
public bool Validate(object instance)
{
string name = this.tenantContext.CurrentTenant.Name;
return this.defaultValidator.Validate(instance) &&
this.tenantValidators[name].Validate(instance);
}
}
The ITenantContext is an abstraction that allows you to get the current tenant within the current context. You probably already have something like that in place, but I imagine an implementation to look something like this:
class UrlBasedTenantContext : ITenantContext
{
public Tenant Current
{
get
{
// Naive implementation.
if (HttpContext.Current.Request.Url.Contains("tenant1"))
{
return Tenant1;
}
return Tenant2;
}
}
}
Create a TenantValidatorComposite would be easy:
var defaultValidator = CompositeValidator(
GetAllDefaultValidators());
var tenantValidators = new Dictionary<string, IValidator>()
{
{ "tenant1", new CompositeValidator(GetValidatorsFor("tenant1")) },
{ "tenant2", new CompositeValidator(GetValidatorsFor("tenant2")) },
};
var tenantValidator = new TenantValidatorComposite(
new UrlBasedTenantContext(),
defaultValidator,
tenantValidators);
I hope this helps.

WCF Attribute injection using an IOC Container

Using ASP.NET MVC I am able to replace the FilterProvider as so
var oldProvider = FilterProviders.Providers.Single(f => f is FilterAttributeFilterProvider);
FilterProviders.Providers.Remove(oldProvider);
FilterProviders.Providers.Add(new CustomFilterProvider(_container));
Using my own custom provider. It does not give me the ability to use a factory pattern to create the controller filter attributes but I do get the ability to use property injection to set dependencies the attributes may need using the container.
Is it possible to do something similar using WCF so that I can inject dependencies (property injection is fine) into my user defined classes that derive from Attribute that I use on my service methods (the services are created using IOC)?
I am using CastleWindsors WcfFacility, but a generalised solution (that applied to any container) would probably be a better answer.
One way to do this is to use the containers OnCreate method or similar and do something like the following at registration
Container.Register(Component.For<IMyService>().ImplementedBy<MyService>().OnCreate(WireUpAttibutes).LifeStyle.Transient);
then have the following methods
private static void WireUpAttibutes<T>(IKernel kernel, T instance) {
var attributes = instance.GetType().GetCustomAttributes(typeof(MyAttribute), false);
foreach (var attribute in attributes) {
WireUp(kernel, attribute.GetType(), attribute);
}
}
private static void WireUp(IKernel kernel, Type type, object instance) {
var properties = type.GetProperties().Where(p => p.CanWrite && p.PropertyType.IsPublic);
foreach (var propertyInfo in properties.Where(propertyInfo => kernel.HasComponent(propertyInfo.PropertyType))) {
propertyInfo.SetValue(instance, kernel.Resolve(propertyInfo.PropertyType), null);
}
}

Authorize Attribute not filtering on DbDataController

I'm trying to use the new MVC4 DbDataController to expose a restful data api.
My problem is trying to secure this. I have created custom authorization attributes that derive from Authorize Attribute
public class AdminOnlyAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (!IsAllowed()) {
filterContext.Result = new HttpUnauthorizedResult("Not logged in");
}
...
}
And that works fine when applied to my normal controller actions. I'm trying to use the same thing in my data service like this:
[AdminOnlyAttribute]
public class DataServiceController : DbDataController<AppBuilderDataContext>
{
[AdminOnlyAttribute]
public IQueryable<Thing> GetThings()
{
return DbContext.AllMyThings();
}
}
You can see I've tried my attribute on both the controller and the action, but it's not firing for either one. I've set a breakpoint inside my authorize attribute function, and it's not getting called.
I'm pretty sure Scott Guthrie said this was going to work. Am I doing it wrong, or do I need a completely different method to secure these?
To work with an DataController or any other type derived from ApiController your attribute must derive from System.Web.Http.AuthorizeAttribute