No service for type 'IdentityServer4.Configuration.IdentityServerOptions' has been registered. - Mocking HttpContext.SignInAsync - asp.net

I'm trying to cover External auth with Unit Tests.
Mocked all dependencies including SignInAsync(). But execution fails with the message "No service for type 'IdentityServer4.Configuration.IdentityServerOptions' has been registered." when it hits SignInAsync().
Stack trace here
Test method.
public async Task CallBack_User_Is_Defined_In_AAD_ReturnChallenge()
{
var ticket = new AuthenticationTicket(TestUsers.Principal, "Test");
var result = AuthenticateResult.Success(ticket);
result.Properties?.Items.Add("returnUrl", "sign-callback");
result.Properties?.Items.Add("scheme", "aad");
authenticationService.Setup(c => c.AuthenticateAsync(It.IsAny<HttpContext>(), IdentityServerConstants.ExternalCookieAuthenticationScheme)).Returns(Task.FromResult(result)); ;
var serviceProviderMock = new Mock<IServiceProvider>();
serviceProviderMock
.Setup(_ => _.GetService(typeof(IAuthenticationService)))
.Returns(authenticationService.Object);
serviceProviderMock
.Setup(_ => _.GetService(typeof(IdentityOptions)))
.Returns(authenticationService.Object);
var identityServiceUserResponse = new IdentityServiceUserResponse
{
Email = TestUsers.Principal.FindFirstValue(ClaimTypes.Email),
CompanyID = "1",
Login = "john"
};
identityServiceAuthService.Setup(c => c.GetUserByExternalEmailAsync(It.IsAny<string>()))
.Returns(Task.FromResult(identityServiceUserResponse));
var authRequest = new AuthorizationRequest
{
Client = new Client { ClientId = "client" },
ValidatedResources = resourceValidationResult,
Parameters = { { "returnUrl", "signin-callback" } },
};
interactionService.Setup(i => i.GetAuthorizationContextAsync(It.IsAny<string>()))
.Returns(Task.FromResult(authRequest));
authenticationService
.Setup(_ => _.SignInAsync(It.IsAny<HttpContext>(), It.IsAny<string>(), It.IsAny<ClaimsPrincipal>(), It.IsAny<AuthenticationProperties>()))
.Returns(Task.FromResult((object)null));
controller = new ExternalController(configuration.Object, interactionService.Object,
identityServiceAuthService.Object, clientStore.Object, events.Object, logger.Object);
controller.ControllerContext = controller.CreateControllerContext(TestUsers.Principal);
controller.ControllerContext.HttpContext.RequestServices = serviceProviderMock.Object;
await controller.Callback();
}

Related

React-Admin with .net .The response to 'getList' must be like { data : [{ id: 123, ...}, ...] }, but the received data items do not have an 'id' key

I have an ASP.NET Core Web API and a React client. I'm trying to build admin dashboard with React-Admin. My problem is when I receive the data from server, my object are with property Id (uppercase), then in console I'm getting an error
The response to 'getList' must be like { data : [{ id: 123, ...}, ...] }, but the received data items do not have an 'id' key
I tried making new test class with property id (lowercase) in my server and then the problem is gone.
How can I fix this issue?
This is my test class and its working.
public class CityModel
{
public string id { get; set; }
public string Name { get; set; }
}
[HttpGet("Cities")]
public CityModel[] GetCities()
{
var city1 = new CityModel()
{
id = "ahsxge",
Name = "Berlin"
};
var city2 = new CityModel()
{
id = "axhdagw",
Name = "London"
};
var list = new List<CityModel>();
list.Add(city1);
list.Add(city2);
Response.Headers.Add("Access-Control-Expose-Headers", "X-Total-Count");
Response.Headers.Add("X-Total-Count", list.Count.ToString());
return list.ToArray();
}
This is my component in react :
const AppAdmin = () => {
const jwt = localStorage.getItem("jwt");
const httpClient = (url, options = {}) => {
options.user = {
authenticated: true,
token: 'Bearer ' + jwt
};
return fetchUtils.fetchJson(url, options);
};
const dataProvider = jsonServerProvider('https://localhost:44366/api', httpClient);
dataProvider.getList('Cities/Cities', {
pagination: { page: 1, perPage: 15 },
sort: { field: 'Name', order: 'ASC' },
})
.then(response => console.log(response));
return (
<Admin dataProvider={dataProvider}>
<Resource name='Cities/Cities' list={CitiesList} />
</Admin>
)
}
export default AppAdmin
You can configure the json converter to use camelCase serialization int the ConfigureServices method in the Startup.cs file the following way:
services
.AddControllers()
.AddJsonOptions(opts =>
{
opts.JsonSerializerOptions.PropertyNamingPolicy = JsonNamingPolicy.CamelCase;
})
This way you can use PascalCase properties in your c# code (which you should do), but your client will recieve camelCase json properties.

HttpContext.Request.Body is disposed when accessing via ApplicationInsights ITelemetryInitializer

I'm trying to log POST, PUT, PATCH json bodies to app insights using ITelemetryInitializer. Every time a post comes in though it seems that my body stream is already disposed of some how. I'm assuming there is something in the request pipeline that I'm not registering/doing properly.
public class RequestBodyLogger : ITelemetryInitializer
{
readonly IHttpContextAccessor httpContextAccessor;
public RequestBodyLogger(IHttpContextAccessor httpContextAccessor)
{
this.httpContextAccessor = httpContextAccessor;
}
public void Initialize(ITelemetry telemetry)
{
try
{
if (telemetry is RequestTelemetry requestTelemetry)
{
if ((httpContextAccessor.HttpContext.Request.Method == HttpMethods.Post ||
httpContextAccessor.HttpContext.Request.Method == HttpMethods.Put ||
httpContextAccessor.HttpContext.Request.Method == HttpMethods.Patch) &&
httpContextAccessor.HttpContext.Request.Body.CanRead)
{
const string jsonBody = "JsonBody";
if (requestTelemetry.Properties.ContainsKey(jsonBody))
{
return;
}
//Allows re-usage of the stream
httpContextAccessor.HttpContext.Request.EnableRewind();
var stream = new StreamReader(httpContextAccessor.HttpContext.Request.Body);
var body = stream.ReadToEnd(); <<Blows here object disposed (stream)
//Reset the stream so data is not lost
httpContextAccessor.HttpContext.Request.Body.Position = 0;
requestTelemetry.Properties.Add(jsonBody, body);
}
}
}
catch (Exception e)
{
}
}
}
Configure services method...
public void ConfigureServices(IServiceCollection services)
{
if (_env.IsDevelopment())
{
services.AddSwaggerGen(c =>
{
c.SwaggerDoc(Constants.ApiVersion, new Info { Title = Constants.ApiName, Version = Constants.ApiVersion });
c.AddSecurityDefinition("Bearer", new ApiKeyScheme { In = "header", Description = "Please enter Bearer Token", Name = "Authorization", Type = "apiKey" });
c.AddSecurityRequirement(new Dictionary<string, IEnumerable<string>> { { "Bearer", Enumerable.Empty<string>() } });
c.IncludeXmlComments($"{AppDomain.CurrentDomain.BaseDirectory}\\TradeJournal.Api.xml");
});
}
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.Authority = Configuration["IdentityAuthority"];
options.TokenValidationParameters = new Microsoft.IdentityModel.Tokens.TokenValidationParameters
{
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
var json = new WebClient().DownloadString(parameters.ValidIssuer + "/.well-known/jwks.json");
var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
return (IEnumerable<SecurityKey>)keys;
},
ValidIssuer = Configuration["IdentityAuthority"],
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateLifetime = true,
ValidateAudience = false
};
});
services
.AddCors(c =>
{
c.AddPolicy("AllowOrigin", options => options.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod().AllowCredentials());
});
services.AddMvc(opts =>
{
opts.Filters.Add(typeof(ModelStateValidationFilter));
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2)
.AddJsonOptions(opt =>
{
opt.SerializerSettings.DateFormatString = "yyyy-MM-ddTHH:mm:ssZ";
});
services.AddSingleton<ITelemetryInitializer, RequestBodyLogger>();
services.AddTransient<ExceptionToHttpResponseMiddleware>();
services.AddTransient<MaintenanceMiddleware>();
services.AddRouting(opts =>
{
opts.LowercaseUrls = true;
opts.LowercaseQueryStrings = true;
});
BootstrapLayers(services);
}
```
Configure method...
public void Configure(IApplicationBuilder app)
{
if (_env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseSwagger();
// Enable middleware to serve swagger-ui (HTML, JS, CSS, etc.),
// specifying the Swagger JSON endpoint.
app.UseSwaggerUI(c =>
{
c.SwaggerEndpoint($"/swagger/{Constants.ApiVersion}/swagger.json", Constants.ApiName);
});
}
else
{
//The default HSTS value is 30 days.You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseAuthentication();
app.UseCors(options => options.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader().AllowCredentials());
app.UseMiddleware<ExceptionToHttpResponseMiddleware>();
app.UseMiddleware<MaintenanceMiddleware>();
app.UseHttpsRedirection();
app.UseMvc();
}
**Why is the context disposed by the time AI calls this telemetry initializer?**
This is also discussed in github: https://github.com/microsoft/ApplicationInsights-aspnetcore/issues/940#issuecomment-513297006
Since by the time TelemetryInitializers are run, the request body is disposed, your best bet is to read and populate the body into RequestTelemetry when the body is still available. If its a controller or middleware - then retrieve RequestTelemetry there, add body to it. The following is sample code to be written in controller/middleware where you can retrieve request body.
RequestTelemetry reqTelemetry = httpContext?.Features.Get<RequestTelemetry>();
reqTelemetry.Properties.Add("body","body contents").

Error UseHealthChecksUI Unexpected character encountered

I'm trying to implement the ASP.NET Core 2.2 health check feature. Setting up the health check itself isn't the problem, but I also want to be able to use the UI feature in other project to monitoring all my apis. Right now I get the exception message
Unexpected character encountered while parsing value: <.
What I'm doing bad?
API Project:
var healthCheckOptions = new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = async (c, r) =>
{
c.Response.ContentType = MediaTypeNames.Application.Json;
var result = JsonConvert.SerializeObject(
new
{
Checks = r.Entries.Select(e =>
new
{
Description = e.Key,
Status = e.Value.Status.ToString(),
ResponseTime = e.Value.Duration.TotalMilliseconds
}),
TotalResponseTime = r.TotalDuration.TotalMilliseconds
});
await c.Response.WriteAsync(result);
}
};
app.UseHealthChecks("/live", new HealthCheckOptions
{
Predicate = _ => true
});
app.UseHealthChecks("/hc", healthCheckOptions);
app.UseHealthChecksUI(options => options.UIPath = "/healtcheck");
// Registers required services for health checks
services
.AddHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy())
.AddCheck("ComunContext Database", new SqlServerHealthCheck(configuration["ConnectionStrings:ComunContext"]));
Web project:
services.AddHealthChecksUI();
app.UseHealthChecksUI(config =>
{
config.UIPath = "/healthcheck";
});
appsettings.json
{
"HealthChecks-UI": {
"HealthChecks": [
{
"Name": "Local",
"Uri": "http://localhost:27365/hc"
}
],
"EvaluationTimeOnSeconds": 10,
"MinimumSecondsBetweenFailureNotifications": 60
}
}
Try adding a ResponseWriter:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseHealthChecks("/healthchecks", new HealthCheckOptions
{
ResponseWriter = async (context, report) =>
{
context.Response.ContentType = "application/json; charset=utf-8";
var bytes = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(report));
await context.Response.Body.WriteAsync(bytes);
}
});
app.UseHealthChecksUI();
}
After a few days struggling with this parser error, I've figured out that there are 2 problems:
1 - If you have an Exception, Health UI tries to convert Exception object field, resulting on error;
2 - If you try to pass your own anonymous object, Health UI fails to convert Entries collection, because it need to be an specific anonymous Dictionary.
Try this:
var healthCheckOptions = new HealthCheckOptions
{
Predicate = _ => true,
ResponseWriter = async (c, r) =>
{
c.Response.ContentType = MediaTypeNames.Application.Json;
var result = JsonConvert.SerializeObject(
new
{
Checks = r.Entries.ToDictionary(
e => e.Key,
e =>
new
{
Description = e.Key,
Status = e.Value.Status.ToString(),
ResponseTime = e.Value.Duration.TotalMilliseconds
}),
TotalResponseTime = r.TotalDuration.TotalMilliseconds
});
await c.Response.WriteAsync(result);
}
};

Razor Pages get class Attributes in Unit Tests

currently I'm trying to check if there is a way to get the class names of all attributes on razor page classes.
I unit tests all my controllers to look for an AuthorizeFilter so that it is impossible to forget it
Currently this is how I do it via MVC (does not work in Razor Pages):
var values = actionDescriptorCollectionProvider
.ActionDescriptors
.Items
.OfType<ControllerActionDescriptor>()
.Select(a => new
{
a.DisplayName,
a.ControllerName,
a.ActionName,
AttributeRouteTemplate = a.AttributeRouteInfo?.Template,
HttpMethods = string.Join(", ", a.ActionConstraints?.OfType<HttpMethodActionConstraint>().SingleOrDefault()?.HttpMethods ?? new string[] { "any" }),
Parameters = a.Parameters?.Select(p => new
{
Type = p.ParameterType.Name,
p.Name
}),
ControllerClassName = a.ControllerTypeInfo.FullName,
ActionMethodName = a.MethodInfo.Name,
Filters = a.FilterDescriptors?.Select(f => new
{
ClassName = f.Filter.GetType().FullName,
f.Scope //10 = Global, 20 = Controller, 30 = Action
}),
Constraints = a.ActionConstraints?.Select(c => new
{
Type = c.GetType().Name
}),
RouteValues = a.RouteValues.Select(r => new
{
r.Key,
r.Value
}),
});
The problem is, that this code won't work with Razor Pages, i.e. FilterDescriptors is empty for PageActionDescriptor.
You need to use PageActionDescriptor for RazorPages instead of ControllerActionDescriptor:
var values = actionDescriptorCollectionProvider
.ActionDescriptors
.Items
.OfType<PageActionDescriptor>()
.Select(descriptor => new
{
// descriptor...,
// ...
});

Reference members in Moq lambda

I have a function in a class that returns a value based on the state of a class property. In this example, I want HasName() to return true if Name is not null. I could simply do Returns(false), however I want it evaluate as a lambda so that it works properly if Name is modified during the test.
public interface IThing
{
string Name { get; set; }
bool HasName();
}
var mocks = new Dictionary<string, IThing>();
Mock<IThing> mockThing;
mockThing = new Mock<IThing>();
mockThing.SetupProperty(m => m.Name, "test");
mockThing.Setup(m => m.HasName()).Returns(() =>
{
return mockThing.Object.Name != null;
});
mocks["first"] = mockThing.Object;
mockThing = new Mock<IThing>();
mockThing.SetupProperty(m => m.Name, "test");
mockThing.Setup(m => m.HasName()).Returns(() =>
{
return mockThing.Object.Name != null;
});
mocks["second"] = mockThing.Object;
Console.WriteLine(mocks["first"].HasName());
mocks["first"].Name = null;
Console.WriteLine(mocks["first"].HasName());
The 2nd Console.WriteLine prints true instead of false due to scoping (referencing the 2nd mock). Resharper actually complains of "Access to modified closure". What is the correct way to do this?
Although your design a little bit strange but you can access the generated mock object with mockThing.Object in your Setup function:
mockThing.Setup(m => m.HasName()).Returns(() =>
{
return mockThing.Object.Name != null;
});
var thing = mockThing.Object;
var hasName = thing.HasName(); // true because Name returns "test"
thing.Name = null;
hasName = thing.HasName(); // false
The problem is that you are referencing the mockThing with your lambdas and then you are reasigning it. So both setup will end up using the same instance.
Use the mocks from the dictionary and it will work:
var mocks = new Dictionary<string, IThing>();
Mock<IThing> mockThing;
mockThing = new Mock<IThing>();
mocks["first"] = mockThing.Object;
mockThing.SetupProperty(m => m.Name, "test");
mockThing.Setup(m => m.HasName()).Returns(() =>
{
return mocks["first"].Name != null;
});
mockThing = new Mock<IThing>();
mocks["second"] = mockThing.Object;
mockThing.SetupProperty(m => m.Name, "test");
mockThing.Setup(m => m.HasName()).Returns(() =>
{
return mocks["second"].Name != null;
});
Console.WriteLine(mocks["first"].HasName());
mocks["first"].Name = null;
Console.WriteLine(mocks["first"].HasName());

Resources