How to WebFluxTest whit ReactiveSecurityContexHolder - spring-security

Here a simple Class reading the Principal user from the spring security context:
public Mono<Void> getAndCheckAccessRights(Integer agencyKey) {
return ReactiveSecurityContextHolder.getContext()
.map(securityContext -> getAccessRights(agencyKey, securityContext.getAuthentication().getName()))
.switchIfEmpty(Mono.defer(() -> {
log.error("No security context found!");
throw new AuthorizationException("No security context found!");
}))
.flatMap(accessRightsDtoMono -> checkAccessRights(accessRightsDtoMono))
.then();
}
private Mono<AccessRightsDto> getAccessRights(Integer agencyKey, String bensl) {
return dataServiceWebClient.get()
.uri("/access_rights/" + agencyKey + "/" + bensl)
.retrieve()
.bodyToMono(AccessRightsDto.class)
.switchIfEmpty(Mono.defer(() -> {
log.error("No user found!");
throw new AuthorizationException("No user found!");
}));
}
While testing it does not what it is supposed to do, execution just jump code lines over without executing method streams in .map or .flatMap,
Logs are not printed and there is no debug logging on any level, test just runs as everithing is terminated correctly, i dont have any clue whatsovewer why this happen:
#WebFluxTest(AccessRightService.class)
...
#Test
#WithMockUser
void getAndCheckAccessRights_NOT_AUTHORIZED() throws JsonProcessingException {
AccessRightsDto testAccessRightsDto = AccessRightsDto
.builder(123456789, "test", "test", PUBLISH, PUBLISH, PUBLISH, PUBLISH, PUBLISH,
PUBLISH, PUBLISH, PUBLISH, NO_ACCESS)
.build();
MockResponse response = new MockResponse();
response.setResponseCode(HttpStatus.OK.value()).setBody(objectMapper.writeValueAsString(testAccessRightsDto));
mockWebServer.enqueue(response);
assertThrows(AuthorizationException.class, () -> accessRightService.getAndCheckAccessRights(123456789));
}
Off course when running the application it just work correctly as expected, testing is strange!
App running with spring boot 2.2.2 and okhttp3 mockwebserver.

My bad, I forgot to put.block() after getAndCheckAccessRights which propagate the input of getting the object, whitout that nothing will happen.
It is actually not that clear how does it works in case of Netty, because it does not accept .block()'s but on tests is ok to call it.

Related

Test using StepVerifier blocks when using Spring WebClient with retry

EDIT: here https://github.com/wujek-srujek/reactor-retry-test is a repository with all the code.
I have the following Spring WebClient code to POST to a remote server (Kotlin code without imports for brevity):
private val logger = KotlinLogging.logger {}
#Component
class Client(private val webClient: WebClient) {
companion object {
const val maxRetries = 2L
val firstBackOff = Duration.ofSeconds(5L)
val maxBackOff = Duration.ofSeconds(20L)
}
fun send(uri: URI, data: Data): Mono<Void> {
return webClient
.post()
.uri(uri)
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(data)
.retrieve()
.toBodilessEntity()
.doOnSubscribe {
logger.info { "Calling backend, uri: $uri" }
}
.retryExponentialBackoff(maxRetries, firstBackOff, maxBackOff, jitter = false) {
logger.debug { "Call to $uri failed, will retry (#${it.iteration()} of max $maxRetries)" }
}
.doOnError {
logger.error { "Call to $uri with $maxRetries retries failed with $it" }
}
.doOnSuccess {
logger.info { "Call to $uri succeeded" }
}
.then()
}
}
(It returns an empty Mono as we don't expect an answer, nor do we care about it.)
I would like to test 2 cases, and one of them is giving me headaches, namely the one in which I want to test that all the retries have been fired. We are using MockWebServer (https://github.com/square/okhttp/tree/master/mockwebserver) and the StepVerifier from reactor-test. (The test for success is easy and doesn't need any virtual time scheduler magic, and works just fine.) Here is the code for the failing one:
#JsonTest
#ContextConfiguration(classes = [Client::class, ClientConfiguration::class])
class ClientITest #Autowired constructor(
private val client: Client
) {
lateinit var server: MockWebServer
#BeforeEach
fun `init mock server`() {
server = MockWebServer()
server.start()
}
#AfterEach
fun `shutdown server`() {
server.shutdown()
}
#Test
fun `server call is retried and eventually fails`() {
val data = Data()
val uri = server.url("/server").uri()
val responseStatus = HttpStatus.INTERNAL_SERVER_ERROR
repeat((0..Client.maxRetries).count()) {
server.enqueue(MockResponse().setResponseCode(responseStatus.value()))
}
StepVerifier.withVirtualTime { client.send(uri, data) }
.expectSubscription()
.thenAwait(Duration.ofSeconds(10)) // wait for the first retry
.expectNextCount(0)
.thenAwait(Duration.ofSeconds(20)) // wait for the second retry
.expectNextCount(0)
.expectErrorMatches {
val cause = it.cause
it is RetryExhaustedException &&
cause is WebClientResponseException &&
cause.statusCode == responseStatus
}
.verify()
// assertions
}
}
I am using withVirtualTime because I don't want the test to take nearly seconds.
The problem is that the test blocks indefinitely. Here is the (simplified) log output:
okhttp3.mockwebserver.MockWebServer : MockWebServer[51058] starting to accept connections
Calling backend, uri: http://localhost:51058/server
MockWebServer[51058] received request: POST /server HTTP/1.1 and responded: HTTP/1.1 500 Server Error
Call to http://localhost:51058/server failed, will retry (#1 of max 2)
Calling backend, uri: http://localhost:51058/server
MockWebServer[51058] received request: POST /server HTTP/1.1 and responded: HTTP/1.1 500 Server Error
Call to http://localhost:51058/server failed, will retry (#2 of max 2)
As you can see, the first retry works, but the second one blocks. I don't know how to write the test so that it doesn't happen. To make matters worse, the client will actually use jitter, which will make the timing hard to anticipate.
The following test using StepVerifier but without WebClient works fine, even with more retries:
#Test
fun test() {
StepVerifier.withVirtualTime {
Mono
.error<RuntimeException>(RuntimeException())
.retryExponentialBackoff(5,
Duration.ofSeconds(5),
Duration.ofMinutes(2),
jitter = true) {
println("Retrying")
}
.then()
}
.expectSubscription()
.thenAwait(Duration.ofDays(1)) // doesn't matter
.expectNextCount(0)
.expectError()
.verify()
}
Could anybody help me fix the test, and ideally, explain what is wrong?
This is a limitation of virtual time and the way the clock is manipulated in StepVerifier. The thenAwait methods are not synchronized with the underlying scheduling (that happens for example as part of the retryBackoff operation). This means that the operator submits retry tasks at a point where the clock has already been advanced by one day. So the second retry is scheduled for + 1 day and 10 seconds, since the clock is at +1 day. After that, the clock is never advanced so the additional request is never made to MockWebServer.
Your case is made even more complicated in the sense that there is an additional component involved, the MockWebServer, that still works "in real time".
Though advancing the virtual clock is a very quick operation, the response from the MockWebServer still goes through a socket and thus has some amount of latency to the retry scheduling, which makes things more complicated from the test writing perspective.
One possible solution to explore would be to externalize the creation of the VirtualTimeScheduler and tie advanceTimeBy calls to the mockServer.takeRequest(), in a parallel thread.

No errors are being raised when unsuccessfully writing to Azure service bus

When writing a message to the Azure Service Bus (using Microsoft.Azure.ServiceBus standard library, not the .Net Framework version) it works fine. However, when switching networks to a network that blocks that traffic and running it again I would expect an error being raised by SendAsync yet no error is thrown, therefor the function considers the send successful even though it is not.
Am I missing some logic to make sure that errors do get raised and trapped, it seems to be inline with all the examples I have seen.
I have tried this possible solution ..
Trouble catching exception on Azure Service Bus SendAsync method
.ContinueWith(t =>
{
Console.WriteLine(t.Status + "," + t.IsFaulted + "," + t.Exception.InnerException);
}, TaskContinuationOptions.OnlyOnFaulted);
.. and at no point does ContinueWith get hit.
[HttpPost]
[Consumes("application/json")]
[Produces("application/json")]
public ActionResult<Boolean> Post(Contract<T> contract)
{
Task.Run(() => SendMessage(contract));
// Other stuff
}
private async Task<ActionResult<Boolean>> SendMessage(Contract<T> contract)
{
JObject json = JObject.FromObject(contract);
Message message = new Message();
message.MessageId = Guid.NewGuid().ToString();
message.ContentType = ObjectType;
message.PartitionKey = ObjectType;
message.Body = Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(contract));
foreach (KeyValuePair<String, String> route in DataRouting)
{
JToken jToken = json.SelectToken(route.Value);
if (jToken != null)
{
message.UserProperties[route.Key] = jToken.Value<String>();
}
else
{
String routeError = $"Could not find routing information in request for: {route.Key} in {route.Value}";
Logger.LogError(routeError);
return new UnprocessableEntityObjectResult(routeError);
}
}
// Send the message
try
{
await topicClient.SendAsync(message);
}
catch(Exception ex)
{
return new UnprocessableEntityObjectResult($"'Could not transmit message to service bus - {ex.Message}'");
}
return new OkObjectResult(true);
}
I expect that the error trap would be hit if the SendAsync fails to send the message. However it essentially fire and forgets, the message send is blocked by the firewall but is never reported to the caller by throwing an error.
Ok, found the answer, but I will leave this out there in case anyone else does this to themselves. It was down to my general muppetry when putting the MVC Controller together. Set async on the Post action and configure the await on the send. Obvious really but I missed it.
public virtual async Task<ActionResult<Boolean>> Post(Contract<T> contract){}
...
// Send the message
try
{
await topicClient.SendAsync(message).ConfigureAwait(false);
return new OkObjectResult(true); // Success if we got here
}
catch(Exception ex)
{
return new UnprocessableEntityObjectResult($"'Could not transmit message to service bus - {ex.Message}'");
}

How to handle incorrect credentials with google oauth2 Installed Application code flow

I'm implementing the authorization code flow for Installed Application.
Code is similar to snippet below:
public static void main(String[] args) {
try {
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
dataStoreFactory = new FileDataStoreFactory(DATA_STORE_DIR);
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(httpTransport,
JSON_FACTORY,
IA_CLIENT,
IA_SECRET,
Collections.singleton(DriveScopes.DRIVE)).setDataStoreFactory(dataStoreFactory)
.build();
Credential credential = new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize("user");
drive = new Drive.Builder(httpTransport, JSON_FACTORY, credential).setApplicationName(APPLICATION_NAME).build();
System.out.println(drive.about());
return;
} catch (IOException e) {
System.err.println(e.getMessage());
} catch (Throwable t) {
t.printStackTrace();
}
System.exit(1);
}
Everything is working fine except in the case when I provide an invalid client_id. (I've the same issue if I use a json file and alter its content).
I get this get of error message from Google server:
401. That’s an error.
Error: invalid_client
The OAuth client was not found.
Request Details
client_id=573900000-hsoobsdsstem84tg8br4pmdsds.apps.googleusercontent.com
redirect_uri=http://localhost:40441/Callback
response_type=code
scope=https://www.googleapis.com/auth/drive
... and the callback server never receives any feedback. So, the application
is still running endlessly.
I've looked at the LocalServerReceiver class but could find any way to provide a
timeout or any potential solution.
What's the cleanest way to handle this case ?

How to better troubleshoot this 500 Error in MVC Web API

I have an MVC Web API project that I am working on. I created a controller with an action. I am able to hit the action properly using Postman, but when an external system tries to reach my controller, it gets a 500 error. The owner of the external service cannot give me any details beyond that, they can only retry the request.
Here is one of the log entries of their requests in IIS log
#Fields: date time s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs(User-Agent) cs(Referer) sc-status sc-substatus sc-win32-status time-taken
2017-02-15 20:38:58 192.168.2.34 POST /Route/to/actionName 8002 - 192.168.2.37 Apache-HttpClient/4.5.2+(Java/1.8.0_102) - 500 0 0 146
First I thought may be the action is being hit, so I added an exception handler and added logging.
[Route("actionName")]
[HttpPost]
public IHttpActionResult actionName(MessageModel message)
{
try
{
// code to handle the action
}
catch (Exception e)
{
// Code to log exception in the log file
}
}
Tried above and saw nothing in the log, I have run tests for failed requests to make sure the above exception handler logs and it does.
So the next thing I decided to do was to handle application level errors in Global.asax and log exception there.
protected void Application_Error(object sender, EventArgs e)
{
if (Request.HttpMethod == "POST")
{
var request = SomeMethodToReadRequestContentsInString();
var service = new SomeExceptionLoggingService();
var exception = Server.GetLastError();
if (exception == null)
{
exception = new ApplicationException("Unknown error occurred");
}
service.LogException(exception, Request.UserHostAddress, Request.UserAgent, request);
}
}
And to my surprise, nothing in the log file.
So then I decided to log ALL Post requests and see if I register ANYTHING in the log.
protected void Application_EndRequest(object sender, EventArgs e)
{
if (Request.HttpMethod == "POST")
{
var request = Helper.ReadStreamUnknownEncoding(Request.InputStream);
var service = new InterfaceTestingService();
var exception = Server.GetLastError();
if (exception == null)
{
exception = new ApplicationException("No Error in this request");
}
service.LogException(exception, Request.UserHostAddress, Request.UserAgent, request);
}
}
And again, nothing!
How do I catch this bug? My goal is to see the Content-Type, and contents.
I tried to add a Custom Field in IIS log settings to include `Content-Type', but the log files still don't have that.
I added a handler for Application_BeginRequest logging everything I did in Application_EndRequest. And it turns out, the content-length was zero, and there was no content. I also restarted IIS Web Server to get it to log custom fields too.
What's strange is that if I send empty content through Postman, I get the action code executed but for some reason when they do it, it doesn't.

Windows service always writes to custom log and application log

I am using a custom EventLog for my Windows service. The service creates the event source after installtion. I don't have any problems.
However, I have setup my service so that I can run it from the IDE using the following mechanism:
static void Main(string[] args)
{
AppDomain.CurrentDomain.UnhandledException += new UnhandledExceptionEventHandler(UnhandledException);
string firstArgument = string.Empty;
if (args.Length > 0)
firstArgument = args[0].ToUpperInvariant();
if (string.Compare(firstArgument, "-CONSOLE", true) == 0)
{
new SchedulerService().RunConsole(args);
}
else
{
ServiceBase[] services = new ServiceBase[] { new SchedulerService() };
ServiceBase.Run(services);
}
}
When writing to the event log, it seems to write my custom event log AND the application log. How can I prevent this from occurring?
Below is the code I am using to write to the event log: (The EventLog app setting is the same for the source and name)
using (System.Diagnostics.EventLog eventLog =
new EventLog(
System.Configuration.ConfigurationManager.AppSettings["EventLog"], ".",
System.Configuration.ConfigurationManager.AppSettings["EventLog"]))
{
eventLog.WriteEntry(msg, entryType);
}
It seems that a reboot of my machine has fixed this problem. I am not sure why yet, but I am going to assume the Event Viewer mechanism got in to some kind of weird state.

Resources