Disable Authorization filter extending TypeFilterAttribute unit testing - asp.net

for below code I want to do setup in ConfigureTestServices so that this particular attribute gets disabled while running the test.
`public class AuthorizePermissionAttribute : TypeFilterAttribute
{
public AuthorizePermissionAttribute(Type type, params string[] permissions) : base(typeof(AuthorizePermissionPreferenceFilter<>).MakeGenericType(type))
{
Arguments = new object[] { permissions };
}
}
public class AuthorizePermissionPreFilter<T> : AuthorizePermissionFilter, IAsyncAuthorizationFilter where T : IPrefScope
{
public AuthorizePermissionPrefFilter(
IRequestContext context,
IPermissionProvider permissionProvider,
ILogger<AuthorizePermissionPreFilter<T>> logger,
IOptionsSnapshot<CoreAppConfiguration> config,
string[] permissions) : base(context, permissionProvider, logger, config, permissions)
{
}
public new async Task OnAuthorizationAsync(AuthorizationFilterContext authContext)
{
await base.OnAuthorizationAsync(authContext);
}
}`
This has been added through middleware app.UseEndpoints(endpoints => { endpoints.MapControllers(); });
I tried using MOQ, Moq.AutoMock with xunit but nothing helped

Related

Why RequiredScope attribute doesn't have any effect?

According to this Microsoft document you should be able to apply attribute like [RequiredScope("SomeScopeName")] to either controller level or action level to protect the API. But when I try it in my API, it doesn't seem to have any effect at all - regardless what scope name I use (I made sure I don't have the scope by that name in the token), I always get right in to the API actions that I supposed to fail. But at the same time, my policy attributes, such as [Authorize(Policy = "PolicyName")], works just fine. What am I missing?
[ApiController]
[RequiredScope("AnyRandomName")]
public class MyApiController : ControllerBase
{
UPDATE
Here is my Startup.cs
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
public void ConfigureServices(IServiceCollection services)
{
IdentityModelEventSource.ShowPII = true;
services.AddControllers();
services.AddSwaggerGen(opt =>
{
opt.CustomSchemaIds(type => type.ToString() + type.GetHashCode());
});
services.Configure<HostOptions>(Configuration.GetSection(HostOptions.HOST));
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
JwtSecurityTokenHandler.DefaultOutboundClaimTypeMap.Clear();
services.AddAuthentication("Bearer").AddJwtBearer(options =>
{
options.Authority = Configuration[HostOptions.IDENTITYGATEWAY];
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false
};
});
services.AddTransient<gRPCServiceHelper>();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseExceptionHandler("/error-local-development");
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "GroupDemographicEFCore v1"));
}
else
{
app.UseExceptionHandler("/error");
}
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
}
and here is my API controller
[ApiController]
[Authorize]
[RequiredScope("NoSuchScope")]
public class MyApiController : ControllerBase
{
public MyApiController([NotNull] IConfiguration configuration, [NotNull] ILogger<MyApiController> logger,
[NotNull] gRPCServiceHelper helper) : base(configuration, logger, helper)
{
}
[HttpGet]
[Route("/clients/summary")]
public async Task<IActionResult> ClientsSummaryGet()
{
...
Note that I applied the attributes here on the controller level. But it makes no difference if I move them down to action level - the RequiredScope attributes always gets ignored.
UPDATE-1
I left out the AddAuthorization from my last post update, as I believe it is irrelevant to my issue here. I added it back now, with a few of the policies that I use. Once again, these policies are all working fine, and I don't see how this is relevant to the issue I have.
services.AddAuthorization(options =>
{
options.AddPolicy("OperatorCode", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("OperatorCode");
});
options.AddPolicy("OperatorCode:oprtr0", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireClaim("OperatorCode", "oprtr0");
});
options.AddPolicy("Role:User+OperatorCode:oprtr0", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("User");
policy.RequireClaim("OperatorCode", "oprtr0");
});
options.AddPolicy("Role:Admin||Role:User", policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireRole("Admin", "User");
});
});
Here is the access_token header
Here is the body of access_token
All we need to do is add
services.AddRequiredScopeAuthorization();
For the RequireScopeAttrubute to work, which is what AddMicrosoftIdentityWebApiAuthentication does under the hood to get it to work anyway.
What you need to do is to add and configure authorization in Startup.cs like, like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthorization(options =>
{
options.AddPolicy("ViewReports", policy =>
policy.RequireAuthenticatedUser()
.RequireRole("Finance")
.RequireRole("Management")
);
});
The policy says that the user must be authenticated and be in both roles. In this example RequireAuthenticatedUser() is optional.
Then you can use that policy like:
[Authorize(Policy = "ViewReports")]
public IActionResult ViewReports()
{
return View();
}
To get the role claim to work, you must define what the name of your role claim is in the token, by doing this:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters.NameClaimType = "name";
options.TokenValidationParameters.RoleClaimType = "role";
});
Otherwise the role might not be found, because OpenIDConnect and Microsoft have different opinion on what the claim should be called.
In the long run, using polices will gives you better and cleaner code, because if you need to change the scopes in the future, you need to update all controllers classes. With a policy , you change it in one place.
Also, according to this issue at GitHub, it says:
RequiredScopes just checks at the scp or
http://schemas.microsoft.com/identity/claims/scope claims.
This means that you might need to do some claims transformation (renaming) to get the RequiredScope to map to the scope claim in your access token.
My codes:
installing these 2 packages:
<PackageReference Include="Microsoft.Azure.AppConfiguration.AspNetCore" Version="4.5.1" />
<PackageReference Include="Microsoft.Identity.Web" Version="1.21.1" />
Startup.cs, adding code in ConfigureServices method.
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebApiAuthentication(Configuration, "AzureAd");
services.AddControllers();
}
don't forget these two lines in Configure method:
app.UseAuthentication();
app.UseAuthorization();
My test controller:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Identity.Web.Resource;
using System.Collections.Generic;
namespace WebApi.Controllers
{
[Route("api/[controller]")]
[ApiController]
[Authorize]
[RequiredScope("User.Read")]
public class HomeController : ControllerBase
{
[HttpGet]
public ActionResult<IEnumerable<string>> Get()
{
return new string[] { "value1", "value2" };
}
[HttpPost]
public string getRes() {
return "hello world";
}
}
}
Test result :
==============================================================
To protect an ASP.NET or ASP.NET Core web API, you must add the
[Authorize] attribute to one of the following items:
The controller itself if you want all controller actions to be
protected The individual controller action for your API
According to this section's example,
adding [Authorize] before the line [RequiredScope("AnyRandomName")] ?
[ApiController]
[Authorize]
[RequiredScope("AnyRandomName")]
public class MyApiController : ControllerBase
{

REST API controller name discarded (ignored)

In a REST API project dotnet Core 5.0, I have two controllers: MyAController and MyBController in separate classes. They derive from MediatorApiController that derives from ControllerBase. They are mainly identical except for the entity they manage (CRUD). When I type https://localhost:5001/api it goes to the [HttpGet("/{lang}")] corresponding method in MyAController and display the objects {lang}='api' ??.
If I type https://localhost:5001/api/MyA/1 or https://localhost:5001/api/MyB/1 I receive an error
Microsoft.AspNetCore.Routing.Matching.AmbiguousMatchException: The request matched multiple endpoints
MyAController.Get
MyBController.Get ???
Can anyone help ?
I did use edi.routedebugger, but it does not get to it (error 500), if I display the controllers they are all shown properly.
public class AController : MediatorApiController
public class BController : MediatorApiController
public class MediatorApiController : ControllerBase
[Route("api/[controller]")]
[ApiController]
public class MyAController : MediatorApiController
{
[HttpGet("/{lang}")]
public async Task<ActionResult<List<AlimentDto>>> GetAll(string lang)
{
return await Mediator.Send(new GetAllAlimentsQuery(lang));
}
[HttpGet("/{id}/{lang}")]
public async Task<ActionResult<AlimentDto>> Get(int id, string lang)
{
return await Mediator.Send(new GetAlimentByIdQuery { Id = id, Lang = lang });
}
}
startup.cs file:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwaggerUi3();
app.UseRouteDebugger();
}
//app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader());
app.UseOpenApi();
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
Try to remove "/", make it
[HttpGet("{lang}")]
public async Task<ActionResult<List<AlimentDto>>> GetAll(string lang)
for lang=en your url should be
../api/MyAControlle/en
But I prefer and recomend to use the full route attribute
[Route("~/api/MyAControlle/GetAll/{lang}]
public async Task<ActionResult<List<AlimentDto>>> GetAll(string lang)

How to use Rebus.Logging.ILog

Is it possible to use ILog below with Azure Function logging or Serilog etc?
I cannot find code example on how to use it.
Rebus.Logging.ILog
.Options(o =>
{
o.Decorate<IErrorHandler>(c =>
new ErrorMessageHandler(c.Get<IErrorHandler>(), c.Get<ILog>()));
It's certainly possibly – but since Rebus' loggers are created with a type (the type works as a context of sorts – I think Serilog calls it "source context"), you do not inject the logger, you inject a logger factory:
.Options(o =>
{
o.Decorate<IErrorHandler>(c => {
var errorHandler = c.Get<IErrorHandler>();
var loggerFactory = c.Get<IRebusLoggerFactory>();
return new ErrorMessageHandler(errorHandler, loggerFactory));
});
}
and then, in the constructor of ErrorMessageHandler, you can get the logger:
public class ErrorMessageHandler : IErrorHandler
{
readonly IErrorHandler errorHandler;
readonly ILog log;
public ErrorMessageHandler(IErrorHandler errorHandler, IRebusLoggerFactory loggerFactory)
{
this.errorHandler = errorHandler;
log = loggerFactory.GetLogger<ErrorMessageHandler>();
}
public async Task HandlePoisonMessage(TransportMessage transportMessage, ITransactionContext transactionContext, Exception exception)
{
// do stuff in here
}
}

Get transistent/scoped Database access in singletonservice

i updating my app from asp core 1.0 to 2.0. In 1.0 i have a soulution for my longlive import-task, initialated as singleton. The singleton used the DBContext. But in core 2.0 this soulution dosn't work. Can you help me?
My soulution in aps core 1.0 was
services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("LocalConnection")));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddTransient<IDataStore, DataStore>();
services.AddSingleton<IImportRepository, ImportRepository>();
with
public class ImportRepository : IImportRepository
{
Importer Importer;
private readonly ApplicationDbContext DBContext;
private readonly IDataStore store;
private ImportSet runningSet = null;
public ImportRepository(ApplicationDbContext context, IDataStore store)
{
this.DBContext = context;
this.store = store;
Importer = new Importer(DBContext, store);
}
With this soulutions i get errormessages (in german, but i try to translate). "you cannot use scoped services in singleton"
Last attempt i used this solution
services.AddSingleton<ImportService>(
provider => new ImportService((ApplicationDbContext)provider.GetService(typeof(ApplicationDbContext)))
);
But here i get the errormessage "Cannot resolve scoped service 'Portal.Data.ApplicationDbContext' from root provider."
How can i get access to my database in my Import-Service?
You may resolve dependencies manually using IServiceProvider instance.
public class ImportRepository : IImportRepository
{
private readonly IServiceProvider _provider;
public ImportRepository(IServiceProvider provider)
{
_provider = provider;
...
}
public void DoSomething()
{
var dBContext = (ApplicationDbContext) provider.GetService(typeof(ApplicationDbContext));
...
}
}
By the way, there is an extension method GetService<T>(); defined in Microsoft.Extensions.DependencyInjection namespace:
// using Microsoft.Extensions.DependencyInjection;
var dBContext = provider.GetService<ApplicationDbContext>();
Since your singleton lives longer and is shared, the only option I see is that you take it as a parameter to the functions.
public class ImportRepository : IImportRepository
{
public void DoSomething(ApplicationDbContext context, IDataStore store)
{
}
}
The other option is to make ImportRepository scoped as well.
Ok. I have a soulution, that works, but not perfektly.
Like Juunas example i build a long life funktion
public async Task RunImportAsync(string fileName, DataService data)
{
await Task.Run(() =>
{
if (!System.IO.File.Exists(internalPath + fileName))
{
throw new Exception($"Datei {fileName} nicht gefunden.");
}
[long Operations...]
data.DBContext.Add(new ImportHistory(set));
data.DBContext.SaveChanges();
});
}
the call is simple
[HttpPost]
[Route("runImport")]
public async Task<IActionResult> RunImport([FromBody]dynamic body)
{
string id = "";
try
{
id = body.filename;
_logger.LogInformation($"Import from {id}");
await ImportService.RunImportAsync(id, DB);
return StatusCode(StatusCodes.Success_2xx.OK);
}
catch (Exception e)
{
return SendError(e);
}
}
But postmen get no Response with this solution. Is there a idea, how i can fix it?

ASP.NET Core ApiVersioning change middleware hierarchy

I have a problem with middleware hierarchy. I created very simple web api with one controller/action
[Route("api/v1/values")]
public class ValuesController : Controller
{
// GET api/v1/values/5
[HttpGet("{id}")]
public async Task<IActionResult> Get(string id)
{
return await Task.Run<IActionResult>(() =>
{
if (id == "nf")
{
return NotFound();
}
return Ok($"value: {id}");
});
}
}
I configured routing to use Mvc if match or write Default to response if not.
app.UseMvc();
app.Use(async (context, next) =>
{
await context.Response.WriteAsync("Default");
});
And this works great, below you can see sample requests/responses:
/api/v1/values/1 -> 200, Body: "value: 1"
/api/v1/values/nf -> 404
/api/v1/values -> "Default"
Great, but then I added ApiVersioning, so controller looks like this:
[ApiVersion("1")]
[Route("api/v{version:apiVersion}/values")]
public class ValuesController : Controller
and I added to Startup : services.AddApiVersioning();. It break routing completely, now for each request i get response "Default", when i remove last app.Use, routing works fine for values controller, but i don't have default middleware. Do you know how i can get responses the same as prevously, but with ApiVersioning?
Try this.
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/foo")]
public class FooController {}
and in Startup class
public void ConfigureServices(IServiceCollection services)
{
services.AddApiVersioning(options =>
{
options.ReportApiVersions = true;
options.AssumeDefaultVersionWhenUnspecified = true;
options.DefaultApiVersion = new ApiVersion(1, 0);
});
}
I have tried and it worked for me.

Resources