I'm using push pub/sub of Google Cloud Pub/Sub in my system,and I want to build my CI test code but I don't know how to do it.For example,some codes are like this:
final Pubsub pubsub = PubsubUtils.getClient();
final PubsubMessage pubsubMessage = new PubsubMessage();
pubsubMessage.encodeData(message.getBytes(StandardCharsets.UTF_8));
Map<String, String> attrs = new HashMap<String, String>();
attrs.put("key", "value");
pubsubMessage.setAttributes(attrs);
final List<PubsubMessage> messages = ImmutableList.of(pubsubMessage);
final PublishRequest publishRequest = new PublishRequest().setMessages(messages);
final PublishResponse publishResponse = pubsub.projects().topics().publish(topic, publishRequest).execute();
final List<String> messageIds = publishResponse.getMessageIds();
and this:
final ServletInputStream reader = request.getInputStream();
try {
// Parse the JSON message to the POJO model class.
final JsonParser parser = JacksonFactory.getDefaultInstance().createJsonParser(reader);
parser.skipToKey("message");
final PubsubMessage message = parser.parseAndClose(PubsubMessage.class);
Map<String, String> attrs = message.getAttributes();
String value = attrs.get("key");
// Base64-decode the data and work with it.
final String data = new String(message.decodeData(), StandardCharsets.UTF_8);
if (data != null || StringUtils.isNotEmpty(data)) {
Logger.getLogger("logger").info(data);
}
// Work with your message
// Respond with a 20X to acknowledge receipt of the message.
response.setStatus(HttpServletResponse.SC_OK);
} finally {
reader.close();
}
How to write a normally Junit test case for the two parts of code? and also,I'm using PowerMockito to mock the objects.
Hope someone can help me.
For unit testing the first example, you can mock the Pub/Sub client object with desired return values for API calls like:
import com.google.api.services.pubsub.Pubsub;
import com.google.api.services.pubsub.Pubsub.Projects;
import com.google.api.services.pubsub.Pubsub.Projects.Topics;
import com.google.api.services.pubsub.Pubsub.Projects.Topics.Create;
import com.google.api.services.pubsub.Pubsub.Projects.Topics.Publish;
// ...
#Mock private Pubsub mockPubsub;
#Mock private Projects mockProjects;
#Mock private Topics mockTopics;
#Mock private Create mockCreate;
#Mock private Publish mockPublish;
// ...
String topicName = "projects/myproject/topics/mytopic";
String messageId = "messageId";
List<String> messageIds = ImmutableList.of(messageId);
PublishResponse publishResponse = new PublishResponse()
.setMessageIds(messageIds);
when(mockPubsub.projects()).thenReturn(mockProjects);
when(mockProjects.topics()).thenReturn(mockTopics);
when(mockTopics.publish(eq(topicName), isA(PublishRequest.class)))
.thenReturn(mockPublish);
when(mockPublish.execute()).thenReturn(publishResponse);
For the second example, you can mock the HttpServletRequest and HttpServletResponse then call the servlet with these mock objects.
Related
I am currently working on a Spring Boot REST API. I have successfully added login using client credentials with Spring Oauth and Spring Security (I can successfully get access token and refresh token using /oauth/token endpoint).
But now I want to provide social login with Facebook and Google. As I understand, this is the flow.
User clicks Login with Social button in React frontend.
Then, he will be asked to grant access. (Still in React)
After that he will be redirected to the react front end with an access token.
Frontend sends that access token to the Spring Boot backend. (I don't know to what endpoint)
Then backend uses that access token to fetch details from the Facebook/Google and check whether a such user exists in our database.
If such user exists, backend will return access and refresh tokens to the frontend.
Now frontend can consume all the endpoints.
My problem is, I have no idea about the steps 4,5 and 6.
Do I have to make a custom endpoint to receive FB/Google access tokens?
How do I issue custom access and refresh tokens in Spring Boot?
I would really appreciate it if you could help me with this scenario.
The flow it's the following:
Front-End calls spring to /oauth2/authorization/facebook(or whatever client do you wanna use)
Back-end respond with a redirect to Facebook login page(including in the query params, client_id, scope, redirect_uri(must be the same present on your developer console) and state which is used to avoid XSRF attacks, according to OAuth2 Standards)
you can see more details here https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1
state
RECOMMENDED. An opaque value used by the client to maintain
state between the request and callback. The authorization
server includes this value when redirecting the user-agent back
to the client. The parameter SHOULD be used for preventing
cross-site request forgery as described in Section 10.12.
3) Once the user log-in and accept whatever popup facebook or other services will show, the user will be redirected to the page present in "redirect_uri", this page should be a component of your ReactJs. The callback will come with some datas put in the query params, usually those params are two, state(it's the same you sent to facebook) and code(which is used from the BE to end the login flow).
Once facebook or whatever service, called you back, you have to take those 2 params, from the url(using JS for instance) and call the /login/oauth2/code/facebook/?code=CODE_GENERATED_BY_FACEBOOK&?state=STATE_GENERATED_BY_SPRING
Spring will call the facebook service(with an implementation of OAuth2AccessTokenResponseClient, using your secret_token, client_id, code and few other fields. Once facebook responds with the access_token and refresh_token, spring call an implementation of OAuth2UserService, used to get user info from facebook using the access_token created a moment before, at facebook's response a session will be created including the principal. (You can intercept the login success creating an implementation of SimpleUrlAuthenticationSuccessHandlerand adding it to your spring security configuration. (For facebook, google and otka in theory OAuth2AccessTokenResponseClient and OAuth2UserService implementations should already exist.
In that handler you can put the logic to add and look for an existing user.
coming back to the default behavior
Once spring created the new session and gave you the JSESSIONID cookie, it will redirect you to the root (I believe, I don't remember exactly which is the default path after the login, but you can change it, creating your own implementation of the handler I told you before)
Note: access_token and refresh_token will be stored in a OAuth2AuthorizedClient, stored in the ClientRegistrationRepository.
This is the end. From now then you can call your back end with that cookie and the be will see you as a logged user. My suggestion is once you got the simple flow working, you should implement a JWT token to use and store in the localstorage of your browser instead of using the cookie.
Hopefully I gave you the infos you were looking for, if I missed something, misunderstood something or something it's not clear let me know in the comment.
UPDATE (some java samples)
My OAuth2 SecurityConfig :
NOTE:
PROTECTED_URLS it's just : public static final RequestMatcher PROTECTED_URLS = new NegatedRequestMatcher(PUBLIC_URLS);
PUBLIC_URLS it's just: private static final RequestMatcher PUBLIC_URLS = new OrRequestMatcher( new AntPathRequestMatcher("/api/v1/login"));
Also note I'm using a dual HttpSecurity configuration. (But in this case it's useless to public that too)
#Configuration
#EnableWebSecurity
#RequiredArgsConstructor
public class OAuth2ClientSecurityConfiguration extends WebSecurityConfigurerAdapter {
private final JWTService jwtService;
private final TempUserDataService tempUserDataService;
private final OAuth2AuthorizedClientRepo authorizedClientRepo;
private final OAuth2AuthorizedClientService clientService;
private final UserAuthenticationService authenticationService;
private final SimpleUrlAuthenticationSuccessHandler successHandler; //This is the default one, this bean has been created in another HttpSecurity Configuration file.
private final OAuth2TokenAuthenticationProvider authenticationProvider2;
private final CustomOAuth2AuthorizedClientServiceImpl customOAuth2AuthorizedClientService;
private final TwitchOAuth2UrlAuthSuccessHandler oauth2Filter; //This is the success handler customized.
//In this bean i set the default successHandler and the current AuthManager.
#Bean("oauth2TokenAuthenticaitonFilter")
TokenAuthenticationFilter oatuh2TokenAuthenticationFilter() throws Exception {
TokenAuthenticationFilter filter = new TokenAuthenticationFilter(PROTECTED_URLS);
filter.setAuthenticationSuccessHandler(successHandler);
filter.setAuthenticationManager(authenticationManager());
return filter;
}
#PostConstruct
public void setFilterSettings() {
oauth2Filter.setRedirectStrategy(new NoRedirectStrategy());
}
#Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.authenticationProvider(authenticationProvider2);
}
#Bean
public RestOperations restOperations() {
return new RestTemplate();
}
#Override
protected void configure(HttpSecurity http) throws Exception {
http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and().authorizeRequests().antMatchers("/twitch/**").authenticated()
.and().csrf().disable()
.formLogin().disable()
.httpBasic().disable()
.logout().disable().authenticationProvider(authenticationProvider2)
.exceptionHandling()
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
.and()
.addFilterBefore(oatuh2TokenAuthenticationFilter(), AnonymousAuthenticationFilter.class)
.oauth2Login().successHandler(oauth2Filter).tokenEndpoint()
.accessTokenResponseClient(new RestOAuth2AccessTokenResponseClient(restOperations()))
.and().authorizedClientService(customOAuth2AuthorizedClientService)
.userInfoEndpoint().userService(new RestOAuth2UserService(restOperations(), tempUserDataService, authorizedClientRepo));
}
#Bean
FilterRegistrationBean disableAutoRegistrationOAuth2Filter() throws Exception {
FilterRegistrationBean registration = new FilterRegistrationBean(oatuh2TokenAuthenticationFilter());
registration.setEnabled(false);
return registration;
}
}
By the fact that my SessionCreationPolicy.STATELESS the cookie created by spring after the end of the OAuth2 Flow is useless. So once the process its over I give to the user a TemporaryJWT Token used to access the only possible service (the register service)
My TokenAutheticationFilter:
public class TokenAuthenticationFilter extends AbstractAuthenticationProcessingFilter {
private static final String AUTHORIZATION = "Authorization";
private static final String BEARER = "Bearer";
public TokenAuthenticationFilter(RequestMatcher requiresAuthenticationRequestMatcher) {
super(requiresAuthenticationRequestMatcher);
}
#Override
public Authentication attemptAuthentication(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws AuthenticationException, IOException, ServletException {
String token = Optional.ofNullable(httpServletRequest.getHeader(AUTHORIZATION))
.map(v -> v.replace(BEARER, "").trim())
.orElseThrow(() -> new BadCredentialsException("Missing authentication token."));
Authentication auth = new UsernamePasswordAuthenticationToken(token, token);
return getAuthenticationManager().authenticate(auth);
}
#Override
protected void successfulAuthentication(
HttpServletRequest request,
HttpServletResponse response,
FilterChain chain,
Authentication authResult) throws IOException, ServletException {
super.successfulAuthentication(request, response, chain, authResult);
chain.doFilter(request, response);
}
#Override
protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException {
response.setStatus(401);
}
}
TwitchOAuth2UrlAuthSuccessHandler (This is where all the magic happens):
This handler is called once the userService and the userService is called when the user calls api.myweb.com/login/oauth2/code/facebook/?code=XXX&state=XXX. (please don't forget the state)
#Component
#RequiredArgsConstructor
public class TwitchOAuth2UrlAuthSuccessHandler extends SimpleUrlAuthenticationSuccessHandler {
private final OAuth2AuthorizedClientRepo oAuth2AuthorizedClientRepo;
private final UserAuthenticationService authenticationService;
private final JWTService jwtService;
private final Gson gson;
#Override
public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException {
super.onAuthenticationSuccess(request, response, authentication);
response.setStatus(200);
Cookie cookie = new Cookie("JSESSIONID", null);
cookie.setHttpOnly(true);
cookie.setSecure(true);
cookie.setPath("/");
cookie.setMaxAge(0);
response.addCookie(cookie);
Optional<OAuth2AuthorizedClientEntity> oAuth2AuthorizedClient = oAuth2AuthorizedClientRepo.findById(new OAuth2AuthorizedClientId(((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId(), authentication.getName()));
if (oAuth2AuthorizedClient.isPresent() && oAuth2AuthorizedClient.get().getUserDetails() != null) {
response.getWriter().write(gson.toJson(authenticationService.loginWithCryptedPassword(oAuth2AuthorizedClient.get().getUserDetails().getUsername(), oAuth2AuthorizedClient.get().getUserDetails().getPassword())));
response.setContentType("application/json");
response.setCharacterEncoding("UTF-8");
response.getWriter().flush();
} else {
response.setHeader("Authorization", jwtService.createTempToken(((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId(), authentication.getName()));
}
}
#Override
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response) {
return "";
}
}
RestOAuth2AccessTokenResponseClient (its responsable to take Access_token and refresh_token from FB)
public class RestOAuth2AccessTokenResponseClient implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
private final RestOperations restOperations;
public RestOAuth2AccessTokenResponseClient(RestOperations restOperations) {
this.restOperations = restOperations;
}
#Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
String tokenUri = clientRegistration.getProviderDetails().getTokenUri();
MultiValueMap<String, String> tokenRequest = new LinkedMultiValueMap<>();
tokenRequest.add("client_id", clientRegistration.getClientId());
tokenRequest.add("client_secret", clientRegistration.getClientSecret());
tokenRequest.add("grant_type", clientRegistration.getAuthorizationGrantType().getValue());
tokenRequest.add("code", authorizationGrantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode());
tokenRequest.add("redirect_uri", authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getRedirectUri());
tokenRequest.add("scope", String.join(" ", authorizationGrantRequest.getClientRegistration().getScopes()));
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
headers.add(HttpHeaders.USER_AGENT, "Discord Bot 1.0");
ResponseEntity<AccessResponse> responseEntity = restOperations.exchange(tokenUri, HttpMethod.POST, new HttpEntity<>(tokenRequest, headers), AccessResponse.class);
if (!responseEntity.getStatusCode().equals(HttpStatus.OK) || responseEntity.getBody() == null) {
throw new SecurityException("The result of token call returned error or the body returned null.");
}
AccessResponse accessResponse = responseEntity.getBody();
Set<String> scopes = accessResponse.getScopes().isEmpty() ?
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes() : accessResponse.getScopes();
return OAuth2AccessTokenResponse.withToken(accessResponse.getAccessToken())
.tokenType(accessResponse.getTokenType())
.expiresIn(accessResponse.getExpiresIn())
.refreshToken(accessResponse.getRefreshToken())
.scopes(scopes)
.build();
}
}
UserService
public class RestOAuth2UserService implements OAuth2UserService<OAuth2UserRequest, OAuth2User> {
private final RestOperations restOperations;
private final TempUserDataService tempUserDataService;
private final OAuth2AuthorizedClientRepo authorizedClientRepo;
public RestOAuth2UserService(RestOperations restOperations, TempUserDataService tempUserDataService, OAuth2AuthorizedClientRepo authorizedClientRepo) {
this.restOperations = restOperations;
this.tempUserDataService = tempUserDataService;
this.authorizedClientRepo = authorizedClientRepo;
}
#Override
public OAuth2User loadUser(OAuth2UserRequest oAuth2UserRequest) throws OAuth2AuthenticationException {
String userInfoUrl = oAuth2UserRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri();
HttpHeaders headers = new HttpHeaders();
headers.add(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", oAuth2UserRequest.getAccessToken().getTokenValue()));
headers.add(HttpHeaders.USER_AGENT, "Discord Bot 1.0");
if (oAuth2UserRequest.getClientRegistration().getClientName().equals("OAuth2 Twitch")) {
headers.add("client-id", oAuth2UserRequest.getClientRegistration().getClientId());
}
ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<Map<String, Object>>() {
};
ResponseEntity<Map<String, Object>> responseEntity = restOperations.exchange(userInfoUrl, HttpMethod.GET, new HttpEntity<>(headers), typeReference);
if (!responseEntity.getStatusCode().equals(HttpStatus.OK) || responseEntity.getBody() == null) {
throw new SecurityException("The result of token call returned error or the body returned null.");
}
Map<String, Object> userAttributes = responseEntity.getBody();
userAttributes = LinkedHashMap.class.cast(((ArrayList) userAttributes.get("data")).get(0));
OAuth2AuthorizedClientId clientId = new OAuth2AuthorizedClientId(oAuth2UserRequest.getClientRegistration().getRegistrationId(), String.valueOf(userAttributes.get("id")));
Optional<OAuth2AuthorizedClientEntity> clientEntity = this.authorizedClientRepo.findById(clientId);
if (!clientEntity.isPresent() || clientEntity.get().getUserDetails() == null) {
TempUserData tempUserData = new TempUserData();
tempUserData.setClientId(clientId);
tempUserData.setUsername(String.valueOf(userAttributes.get("login")));
tempUserData.setEmail(String.valueOf(userAttributes.get("email")));
tempUserDataService.save(tempUserData);
}
Set<GrantedAuthority> authorities = Collections.singleton(new OAuth2UserAuthority(userAttributes));
return new DefaultOAuth2User(authorities, userAttributes, oAuth2UserRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUserNameAttributeName());
}
As asked this is all the code you need, just to give you another hint. When you call /login/oauth2/code/facebook/?code=XXX&?state=XXX the chain is the following:
RestOAuth2AccessTokenResponseClient
RestOAuth2UserService
TwitchOAuth2UrlAuthSuccessHandler
I hope this can help you. Let me know if you need more explainations.
Currently I am using SOLR basic authentication feature on my SOLR cloud using Solrj library. According to documentation and only way I found, the code looks like this -
SolrRequest<QueryResponse> req = new QueryRequest(solrQuery);
req.setBasicAuthCredentials("admin", "foobar");
QueryResponse rsp = req.process(solrClient, liveStreamCollection);
documentList = rsp.getResults();
I am wondering if there is a way to avoid setBasicAuthCredentials for each request and perform it only once per session on solrClient?
Edit
Above approach worked both for SolrClient and SolrCloudClient (both core and collection). To avoid passing basic credentials for each request, I tried to build and use a HttpClient like this.
AuthScope authScope = new AuthScope("192.168.x.x", 8983);
UsernamePasswordCredentials creds = new UsernamePasswordCredentials("admin", "foobar");
CredentialsProvider credsProvider = new BasicCredentialsProvider();
credsProvider.setCredentials(authScope, creds);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.addInterceptorFirst(new PreemptiveAuthInterceptor());
builder.setDefaultCredentialsProvider(credsProvider);
CloseableHttpClient httpClient = builder.build();
solrClient = new HttpSolrClient("http://192.168.x.x:8983/solr/", httpClient);
And the PreemptiveAuthInterceptor class:
static class PreemptiveAuthInterceptor implements HttpRequestInterceptor {
#Override
public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
// If no auth scheme available yet, try to initialize it preemptively
if (authState.getAuthScheme() == null) {
CredentialsProvider credsProvider = (CredentialsProvider)
context.getAttribute(HttpClientContext.CREDS_PROVIDER);
HttpHost targetHost = (HttpHost) context.getAttribute(HttpCoreContext.HTTP_TARGET_HOST);
AuthScope authScope = new AuthScope(targetHost.getHostName(), targetHost.getPort());
Credentials creds = credsProvider.getCredentials(authScope);
if(creds == null){
}
authState.update(new BasicScheme(), creds);
}
}
}
This worked for a single machine Solr core but I don't know how to use this with Solr cloud(passing Zookeeper hosts etc). Any idea?
Thanks in advance!
I have an API that uses IdentityServer4 for token validation.
I want to unit test this API with an in-memory TestServer. I'd like to host the IdentityServer in the in-memory TestServer.
I have managed to create a token from the IdentityServer.
This is how far I've come, but I get an error "Unable to obtain configuration from http://localhost:54100/.well-known/openid-configuration"
The Api uses [Authorize]-attribute with different policies. This is what I want to test.
Can this be done, and what am I doing wrong?
I have tried to look at the source code for IdentityServer4, but have not come across a similar integration test scenario.
protected IntegrationTestBase()
{
var startupAssembly = typeof(Startup).GetTypeInfo().Assembly;
_contentRoot = SolutionPathUtility.GetProjectPath(#"<my project path>", startupAssembly);
Configure(_contentRoot);
var orderApiServerBuilder = new WebHostBuilder()
.UseContentRoot(_contentRoot)
.ConfigureServices(InitializeServices)
.UseStartup<Startup>();
orderApiServerBuilder.Configure(ConfigureApp);
OrderApiTestServer = new TestServer(orderApiServerBuilder);
HttpClient = OrderApiTestServer.CreateClient();
}
private void InitializeServices(IServiceCollection services)
{
var cert = new X509Certificate2(Path.Combine(_contentRoot, "idsvr3test.pfx"), "idsrv3test");
services.AddIdentityServer(options =>
{
options.IssuerUri = "http://localhost:54100";
})
.AddInMemoryClients(Clients.Get())
.AddInMemoryScopes(Scopes.Get())
.AddInMemoryUsers(Users.Get())
.SetSigningCredential(cert);
services.AddAuthorization(options =>
{
options.AddPolicy(OrderApiConstants.StoreIdPolicyName, policy => policy.Requirements.Add(new StoreIdRequirement("storeId")));
});
services.AddSingleton<IPersistedGrantStore, InMemoryPersistedGrantStore>();
services.AddSingleton(_orderManagerMock.Object);
services.AddMvc();
}
private void ConfigureApp(IApplicationBuilder app)
{
app.UseIdentityServer();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
var options = new IdentityServerAuthenticationOptions
{
Authority = _appsettings.IdentityServerAddress,
RequireHttpsMetadata = false,
ScopeName = _appsettings.IdentityServerScopeName,
AutomaticAuthenticate = false
};
app.UseIdentityServerAuthentication(options);
app.UseMvc();
}
And in my unit-test:
private HttpMessageHandler _handler;
const string TokenEndpoint = "http://localhost/connect/token";
public Test()
{
_handler = OrderApiTestServer.CreateHandler();
}
[Fact]
public async Task LeTest()
{
var accessToken = await GetToken();
HttpClient.SetBearerToken(accessToken);
var httpResponseMessage = await HttpClient.GetAsync("stores/11/orders/asdf"); // Fails on this line
}
private async Task<string> GetToken()
{
var client = new TokenClient(TokenEndpoint, "client", "secret", innerHttpMessageHandler: _handler);
var response = await client.RequestClientCredentialsAsync("TheMOON.OrderApi");
return response.AccessToken;
}
You were on the right track with the code posted in your initial question.
The IdentityServerAuthenticationOptions object has properties to override the default HttpMessageHandlers it uses for back channel communication.
Once you combine this with the CreateHandler() method on your TestServer object you get:
//build identity server here
var idBuilder = new WebBuilderHost();
idBuilder.UseStartup<Startup>();
//...
TestServer identityTestServer = new TestServer(idBuilder);
var identityServerClient = identityTestServer.CreateClient();
var token = //use identityServerClient to get Token from IdentityServer
//build Api TestServer
var options = new IdentityServerAuthenticationOptions()
{
Authority = "http://localhost:5001",
// IMPORTANT PART HERE
JwtBackChannelHandler = identityTestServer.CreateHandler(),
IntrospectionDiscoveryHandler = identityTestServer.CreateHandler(),
IntrospectionBackChannelHandler = identityTestServer.CreateHandler()
};
var apiBuilder = new WebHostBuilder();
apiBuilder.ConfigureServices(c => c.AddSingleton(options));
//build api server here
var apiClient = new TestServer(apiBuilder).CreateClient();
apiClient.SetBearerToken(token);
//proceed with auth testing
This allows the AccessTokenValidation middleware in your Api project to communicate directly with your In-Memory IdentityServer without the need to jump through hoops.
As a side note, for an Api project, I find it useful to add IdentityServerAuthenticationOptions to the services collection in Startup.cs using TryAddSingleton instead of creating it inline:
public void ConfigureServices(IServiceCollection services)
{
services.TryAddSingleton(new IdentityServerAuthenticationOptions
{
Authority = Configuration.IdentityServerAuthority(),
ScopeName = "api1",
ScopeSecret = "secret",
//...,
});
}
public void Configure(IApplicationBuilder app)
{
var options = app.ApplicationServices.GetService<IdentityServerAuthenticationOptions>()
app.UseIdentityServerAuthentication(options);
//...
}
This allows you to register the IdentityServerAuthenticationOptions object in your tests without having to alter the code in the Api project.
I understand there is a need for a more complete answer than what #james-fera posted. I have learned from his answer and made a github project consisting of a test project and API project. The code should be self-explanatory and not hard to understand.
https://github.com/emedbo/identityserver-test-template
The IdentityServerSetup.cs class https://github.com/emedbo/identityserver-test-template/blob/master/tests/API.Tests/Config/IdentityServerSetup.cs can be abstracted away e.g. NuGetted away, leaving the base class IntegrationTestBase.cs
The essences is that can make the test IdentityServer work just like a normal IdentityServer, with users, clients, scopes, passwords etc. I have made the DELETE method [Authorize(Role="admin)] to prove this.
Instead of posting code here, I recommend read #james-fera's post to get the basics then pull my project and run tests.
IdentityServer is such a great tool, and with the ability to use the TestServer framework it gets even better.
I think you probably need to make a test double fake for your authorization middleware depending on how much functionality you want. So basically you want a middleware that does everything that the Authorization middleware does minus the back channel call to the discovery doc.
IdentityServer4.AccessTokenValidation is a wrapper around two middlewares. The JwtBearerAuthentication middleware, and the OAuth2IntrospectionAuthentication middleware. Both of these grab the discovery document over http to use for token validation. Which is a problem if you want to do an in-memory self-contained test.
If you want to go through the trouble you will probably need to make a fake version of app.UseIdentityServerAuthentication that doesnt do the external call that fetches the discovery document. It only populates the HttpContext principal so that your [Authorize] policies can be tested.
Check out how the meat of IdentityServer4.AccessTokenValidation looks here. And follow up with a look at how JwtBearer Middleware looks here
We stepped away from trying to host a mock IdentityServer and used dummy/mock authorizers as suggested by others here.
Here's how we did that in case it's useful:
Created a function which takes a type, creates a test Authentication Middleware and adds it to the DI engine using ConfigureTestServices (so that it's called after the call to Startup.)
internal HttpClient GetImpersonatedClient<T>() where T : AuthenticationHandler<AuthenticationSchemeOptions>
{
var _apiFactory = new WebApplicationFactory<Startup>();
var client = _apiFactory
.WithWebHostBuilder(builder =>
{
builder.ConfigureTestServices(services =>
{
services.AddAuthentication("Test")
.AddScheme<AuthenticationSchemeOptions, T>("Test", options => { });
});
})
.CreateClient(new WebApplicationFactoryClientOptions
{
AllowAutoRedirect = false,
});
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Test");
return client;
}
Then we create what we called 'Impersonators' (AuthenticationHandlers) with the desired roles to mimic users with roles (We actually used this as a base class, and create derived classes based on this to mock different users):
public abstract class FreeUserImpersonator : AuthenticationHandler<AuthenticationSchemeOptions>
{
public Impersonator(
IOptionsMonitor<AuthenticationSchemeOptions> options,
ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock)
: base(options, logger, encoder, clock)
{
base.claims.Add(new Claim(ClaimTypes.Role, "FreeUser"));
}
protected List<Claim> claims = new List<Claim>();
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
{
var identity = new ClaimsIdentity(claims, "Test");
var principal = new ClaimsPrincipal(identity);
var ticket = new AuthenticationTicket(principal, "Test");
var result = AuthenticateResult.Success(ticket);
return Task.FromResult(result);
}
}
Finally, we can perform our integration tests as follows:
// Arrange
HttpClient client = GetImpersonatedClient<FreeUserImpersonator>();
// Act
var response = await client.GetAsync("api/things");
// Assert
Assert.That.IsSuccessful(response);
Test API startup:
public class Startup
{
public static HttpMessageHandler BackChannelHandler { get; set; }
public void Configuration(IAppBuilder app)
{
//accept access tokens from identityserver and require a scope of 'Test'
app.UseIdentityServerBearerTokenAuthentication(new IdentityServerBearerTokenAuthenticationOptions
{
Authority = "https://localhost",
BackchannelHttpHandler = BackChannelHandler,
...
});
...
}
}
Assigning the AuthServer.Handler to TestApi BackChannelHandler in my unit test project:
protected TestServer AuthServer { get; set; }
protected TestServer MockApiServer { get; set; }
protected TestServer TestApiServer { get; set; }
[OneTimeSetUp]
public void Setup()
{
...
AuthServer = TestServer.Create<AuthenticationServer.Startup>();
TestApi.Startup.BackChannelHandler = AuthServer.CreateHandler();
TestApiServer = TestServer.Create<TestApi.Startup>();
}
The trick is to create a handler using the TestServer that is configured to use IdentityServer4. Samples can be found here.
I created a nuget-package available to install and test using the Microsoft.AspNetCore.Mvc.Testing library and the latest version of IdentityServer4 for this purpose.
It encapsulates all the infrastructure code necessary to build an appropriate WebHostBuilder which is then used to create a TestServer by generating the HttpMessageHandler for the HttpClient used internally.
None of the other answers worked for me because they rely on 1) a static field to hold your HttpHandler and 2) the Startup class to have knowledge that it may be given a test handler. I've found the following to work, which I think is a lot cleaner.
First create an object that you can instantiate before your TestHost is created. This is because you won't have the HttpHandler until after the TestHost is created, so you need to use a wrapper.
public class TestHttpMessageHandler : DelegatingHandler
{
private ILogger _logger;
public TestHttpMessageHandler(ILogger logger)
{
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
_logger.Information($"Sending HTTP message using TestHttpMessageHandler. Uri: '{request.RequestUri.ToString()}'");
if (WrappedMessageHandler == null) throw new Exception("You must set WrappedMessageHandler before TestHttpMessageHandler can be used.");
var method = typeof(HttpMessageHandler).GetMethod("SendAsync", BindingFlags.Instance | BindingFlags.NonPublic);
var result = method.Invoke(this.WrappedMessageHandler, new object[] { request, cancellationToken });
return await (Task<HttpResponseMessage>)result;
}
public HttpMessageHandler WrappedMessageHandler { get; set; }
}
Then
var testMessageHandler = new TestHttpMessageHandler(logger);
var webHostBuilder = new WebHostBuilder()
...
services.PostConfigureAll<JwtBearerOptions>(options =>
{
options.Audience = "http://localhost";
options.Authority = "http://localhost";
options.BackchannelHttpHandler = testMessageHandler;
});
...
var server = new TestServer(webHostBuilder);
var innerHttpMessageHandler = server.CreateHandler();
testMessageHandler.WrappedMessageHandler = innerHttpMessageHandler;
I watch a gcs bucket with watchbucket command. After that i am doing a upload. The watch command sends a notification to my servlet on app engine.
To be sure about that, i have a look at the logs. But it seems so, that some how the servlet keeps on getting request after request. Four times successfully and after that it gets an nullpointer exception. Why are there so many requests?
* EDIT *
On client side i use a meteorJS a Javascript framework. I added the extension slingshot to process the upload.
first i have to provide a the necessary info like acl, bucket, etc. like this:
Slingshot.createDirective('uploadSpotCover', Slingshot.GoogleCloud, {
bucket: 'upload_spot_cover',
GoogleAccessId: 'accessId',
acl: 'project-private',
maxSize: 0,
cacheControl: 'no-cache',
...
}
As you can see in line 153 slingshot uses a XMLHttpRequest to upload to Cloudstorage
https://github.com/CulturalMe/meteor-slingshot/blob/master/lib/upload.js#L144
On serverside my Servlet and it's logic look like this.
public class Upload extends HttpServlet {
private BucketNotification notification;
#Override
public void doPost(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
this.notification = getBucketNotification(req);
UploadObject object = new UploadObject(notification);
CloudStorageHandler gcs = new CloudStorageHandler();
BlobstoreHandler bs = new BlobstoreHandler();
ImageTransformHandler is = new ImageTransformHandler();
/** GET DATA FROM GCS **/
byte[] data = gcs.getFileFromGoogleCloudStorage(object.getGcsBucket(), object.getGcsPath());
//BlobKey bk = bs.createBlobKey(object.getGcsBucket(), object.getGcsPath());
/******************/
/** TRANSFORMATION **/
byte[] newImageData = is.resizePicture(data, 1200, 1200, "JPEG", 65, 0.5, 0.5);
/******************/
/** STORE NEW RESIZED FILE INTO GCS BUCKET **/
UploadObject tmp = new UploadObject(object.getGcsPath(), "JPEG", "beispiel", 1200, 1200);
tmp.setData(newImageData);
gcs.saveFileToGoogleCloudStorage(newImageData, "beispiel", object.getGcsPath(), "JPEG", "public-read");
/******************/
/** CREATE SERVING URL via BLOBKEY **/
BlobKey bk_neu = bs.createBlobKey("beispiel", object.getGcsPath());
String servingUrl = is.createServingUrlWithBlobkey(bk_neu);
/******************/
log("Blobkey: "+ bk_neu.getKeyString());
log("url: "+ servingUrl);
res.setStatus(200);
}
private BucketNotification getBucketNotification(HttpServletRequest req) {
BucketNotification notification = null;
String jsonString ="";
try {
jsonString ="";
BufferedReader in = new BufferedReader(new InputStreamReader(req.getInputStream()));
for (String buffer;(buffer = in.readLine()) != null;jsonString+=buffer + "\n");
in.close();
notification = new Gson().fromJson(jsonString, BucketNotification.class);
} catch (IOException e) {
log("Failed to decode the notification: " + e.getMessage());
return null;
}
return notification;
}
}
I wrapped the specific service methods like save file to cloudstorage in its own handlers.
Because I do not know what you are using to upload, I'm guessing now. Most file uploads upload stuff in bulks, so not the whole file at once, but in smaller chunks until everything is up. This would explain the multiple calls you are getting.
Can you show us the server and the client code? Maybe that way we could see the problem.
I'm using Camel servlet component in order to receive xml documents and now I also need to receive files (jpegs, gifs, etc). So here is how my client app is sending a file:
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.mime.MultipartEntity;
import org.apache.http.entity.mime.content.FileBody;
import org.apache.http.impl.client.DefaultHttpClient;
public class HttpClientUploadHelper {
public boolean upload(final File file, final String url) {
boolean wasSent = false ;
HttpClient client = new DefaultHttpClient();
HttpPost post = new HttpPost(url);
MultipartEntity entity = new MultipartEntity();
entity.addPart(file.getName(), new FileBody(file));
post.setEntity(entity);
try {
HttpResponse response = client.execute(post);
wasSent = response.getStatusLine().getStatusCode()==200;
} catch (Exception e) {
}
return wasSent;
}
}
my Camel Processor then extracts the HttpServletRequest this way:
HttpServletRequest req = exchange.getIn().getHeader(Exchange.HTTP_SERVLET_REQUEST, HttpServletRequest.class);
then I have this method to finally parse and save the file:
import org.apache.commons.fileupload.FileItemFactory;
import org.apache.commons.fileupload.FileItemIterator;
import org.apache.commons.fileupload.FileItemStream;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import org.apache.commons.io.IOUtils
... class declaration, body, etc...
void parseAndSaveFile(final HttpServletRequest req) throws Exception {
// Check that we have a file upload request
boolean isMultipart = ServletFileUpload.isMultipartContent(req);
// Create a factory for disk-based file items
FileItemFactory factory = new DiskFileItemFactory();
// Create a new file upload handler
ServletFileUpload upload = new ServletFileUpload(factory);
// Parse the request
FileItemIterator receivedFiles = upload.getItemIterator(req);
while (receivedFiles.hasNext()) {
FileItemStream file = receivedFiles.next();
if (file.isFormField()) {
System.out.println("WTF?");
} else {
String fileName = file.getName();
File uploadedFile = new File("/home/myuser/" + fileName);
FileOutputStream out = new FileOutputStream(uploadedFile);
IOUtils.copy(file.openStream(), out);
}
}
}
when I use above code within Camel, that isMultipart flag is "true" but that receivedFiles iterator doesn't contains any element. When I use above code within another project with just a plain servlet, the code works. In both ways I'm using jetty as the web container.
So is there any other way to extract the file name and it's content within my camel processor ?
Thanks!
Since you're using Jetty, have you considered using the included MultipartFilter instead of the FileUpload project? Super clean and easy to use.
From the javadoc:
"This class decodes the multipart/form-data stream sent by a HTML form that uses a file input item. Any files sent are stored to a temporary file and a File object added to the request as an attribute. All other values are made available via the normal getParameter API and the setCharacterEncoding mechanism is respected when converting bytes to Strings."
Does this help?
public void process(Exchange exchange) throws Exception {
Message in = exchange.getIn();
Set names = in.getAttachmentNames();
for(String n: names) {
System.out.println("attachment "+n);
DataHandler h = in.getAttachment(n);
if(h!=null) {
try {
Object o = h.getContent();
System.out.println(o);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
if(!names.isEmpty())
return;
}