The Kotlin DSL should allow setting a custom AuthenticationDetailsSource when using OAuth2 Login

http {
    oauth2Login {
        authenticationDetailsSource = AUTHENTICATION_DETAILS_SOURCE
   }
}

Comment From: nmck257

Hey @eleftherias -- I'm looking at this, and I see that most of the existing fields on Oauth2LoginDsl don't have explicit test coverage. Would we want to add test coverage for this new item, or are we comfortable calling it trivial if the implementation follows the same pattern as all the other fields?

Comment From: eleftherias

Thanks for looking into this @nmck257! We should add test coverage for the authenticationDetailsSource. We have a similar test in HttpBasicDslTests called http basic when custom authentication details source then used.

Comment From: nmck257

Okay -- I think I'm in good shape for #9837 (which I was working in parallel), but, I'm seeing some complexity in the test case for OAuth2.

The existing test cases in OAuth2LoginDslTests don't actually perform a login; they just fetch the login page(s). To do a login for the new test, I tried something like this:

    @Test
    fun `oauth2Login when custom authentication details source then used`() {
        this.spring
            .register(CustomAuthenticationDetailsSourceConfig::class.java, ClientConfig::class.java)
            .autowire()
        mockkObject(CustomAuthenticationDetailsSourceConfig.AUTHENTICATION_DETAILS_SOURCE)
        every {
            CustomAuthenticationDetailsSourceConfig.AUTHENTICATION_DETAILS_SOURCE.buildDetails(any())
        } returns Any()

        this.mockMvc.get("/") {
            with(oauth2Login()) // ***********************  the difficult bit, discussed below
        }

        verify(exactly = 1) { CustomAuthenticationDetailsSourceConfig.AUTHENTICATION_DETAILS_SOURCE.buildDetails(any()) }
    }

    @EnableWebSecurity
    open class CustomAuthenticationDetailsSourceConfig : WebSecurityConfigurerAdapter() {

        companion object {
            val AUTHENTICATION_DETAILS_SOURCE: AuthenticationDetailsSource<HttpServletRequest, *> =
                AuthenticationDetailsSource<HttpServletRequest, Any> { Any() }
        }

        override fun configure(http: HttpSecurity) {
            http {
                oauth2Login {
                    authenticationDetailsSource = AUTHENTICATION_DETAILS_SOURCE
                }
                authorizeRequests {
                    authorize(anyRequest, authenticated)
                }
            }
        }
    }

...using a method from SecurityMockMvcRequestPostProcessors, like in HttpBasicDslTests. But, as I understand it, that oauth2Login method circumvents the code which would call our authenticationDetailsSource by directly adding an AuthorizedClient to the repository before executing the request.

I think I would instead need to supply a "fresh" token on the request and use the actual oauth2 mechanism to authenticate it, but, I'm not sure how to cleanly orchestrate that in the test. Some kind of MockClientRegistration which auto-accepts tokens?

Am I off-track? @eleftherias

Comment From: eleftherias

@nmck257 The easiest way is probably to mock the authorizationRequestRepository. You can provide a mock in the AuthorizationEndpointDsl.

oauth2Login {
    authenticationDetailsSource = AUTHENTICATION_DETAILS_SOURCE
    authorizationEndpoint {
        authorizationRequestRepository = AUTHORIZATION_REQUEST_REPOSITORY
    }
}

You can then mock the removeAuthorizationRequest method to return an OAuth2AuthorizationRequest.

The test should call the endpoint "/login/oauth2/code/{registration-id}" in order to reach the OAuth2LoginAuthenticationFilter which calls AUTHENTICATION_DETAILS_SOURCE.buildDetails.

Let me know if you have any questions.

Comment From: nmck257

@eleftherias Thanks for the direction -- it helped a bunch!

I've opened a PR #10071 (and I do have a question in there).