Skip to content

Commit

Permalink
Update datadog example app to include traces and add to cart
Browse files Browse the repository at this point in the history
  • Loading branch information
lojzatran committed Apr 9, 2024
1 parent 616f79b commit 0e477d3
Show file tree
Hide file tree
Showing 15 changed files with 743 additions and 201 deletions.
23 changes: 19 additions & 4 deletions examples/spring-datadog/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,13 @@ dependencies {
implementation "com.commercetools.sdk:commercetools-sdk-java-api:${versions.commercetools}"
implementation "com.commercetools.sdk:commercetools-apachehttp-client:${versions.commercetools}"
implementation "com.commercetools.sdk:commercetools-monitoring-datadog:${versions.commercetools}"
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'javax.inject:javax.inject:1'
implementation 'com.dynatrace.metric.util:dynatrace-metric-utils-java:2.2.0'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity6'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-webflux'
implementation 'org.springframework.session:spring-session-core'
implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5:3.0.4.RELEASE'
implementation "com.datadoghq:datadog-api-client:2.20.0"
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
Expand All @@ -44,3 +46,16 @@ tasks.named('test') {
useJUnitPlatform()
}


tasks.register('downloadDatadog', Download) {
mkdir 'datadog'
src 'https://repo1.maven.org/maven2/com/datadoghq/dd-java-agent/1.32.0/dd-java-agent-1.32.0.jar'
dest file('datadog')
}

if (project.file("datadog/dd-java-agent-1.32.0.jar").exists()) {
tasks.withType(JavaExec)
{
jvmArgs "-javaagent:datadog/dd-java-agent-1.32.0.jar", "-Ddd.logs.injection=true", "-Ddd.service=example-app", "-Ddd.env=test"
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,169 @@

package com.commercetools.sdk.examples.springmvc.config;

import java.time.Duration;

import com.commercetools.api.client.ProjectApiRoot;
import com.commercetools.api.defaultconfig.ApiRootBuilder;
import com.commercetools.api.defaultconfig.ServiceRegion;

import com.commercetools.sdk.examples.springmvc.service.CtpReactiveAuthenticationManager;
import com.commercetools.sdk.examples.springmvc.service.MeRepository;
import io.vrap.rmf.base.client.ApiHttpClient;
import io.vrap.rmf.base.client.oauth2.ClientCredentials;
import io.vrap.rmf.base.client.oauth2.TokenStorage;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.*;
import org.springframework.security.web.server.authentication.logout.*;
import org.springframework.security.web.server.context.ServerSecurityContextRepository;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono;

@Configuration
@EnableWebSecurity
@EnableMethodSecurity
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
public class CtpSecurityConfig {
private final ReactiveAuthenticationManagerResolver<ServerWebExchange> authenticationManagerResolver;

@Autowired
public CtpSecurityConfig(
final ReactiveAuthenticationManagerResolver<ServerWebExchange> authenticationManagerResolver) {
this.authenticationManagerResolver = authenticationManagerResolver;
}

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) throws Exception {
ServerSecurityContextRepository securityContextRepository = new WebSessionServerSecurityContextRepository();
return http.securityContextRepository(securityContextRepository)
.anonymous()
.and()
.authorizeHttpRequests((requests) -> requests
.requestMatchers("**").permitAll()
.requestMatchers("/resources/**").permitAll()
.anyRequest().permitAll()
);
.addFilterBefore(new LoginWebFilter(authenticationManagerResolver, securityContextRepository),
SecurityWebFiltersOrder.FORM_LOGIN)
.logout()
.logoutUrl("/logout")
.requiresLogout(ServerWebExchangeMatchers.pathMatchers(HttpMethod.GET, "/logout"))
.logoutHandler(new DelegatingServerLogoutHandler(new WebSessionServerLogoutHandler(),
new SecurityContextServerLogoutHandler()))
.logoutSuccessHandler(new RedirectServerLogoutSuccessHandler())
.and()
.formLogin()
.loginPage("/login")
.requiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers("none"))
.authenticationManager(Mono::just)
.and()
.authorizeExchange()
.pathMatchers("/login")
.permitAll()
.pathMatchers("/")
.permitAll()
.pathMatchers("/resources/**")
.permitAll()
.pathMatchers("/home")
.permitAll()
.pathMatchers("/p/**")
.permitAll()
.pathMatchers("/cart/**")
.permitAll()
.pathMatchers("/me/**")
.authenticated()
.anyExchange()
.authenticated()
.and()
.build();
}

@Component
public static class CtpReactiveAuthenticationManagerResolver
implements ReactiveAuthenticationManagerResolver<ServerWebExchange> {
private final ApiHttpClient apiHttpClient;

@Value(value = "${ctp.client.id}")
private String clientId;

@Value(value = "${ctp.client.secret}")
private String clientSecret;

@Value(value = "${ctp.project.key}")
private String projectKey;

private ClientCredentials credentials() {
return ClientCredentials.of().withClientId(clientId).withClientSecret(clientSecret).build();
}

@Autowired
public CtpReactiveAuthenticationManagerResolver(final ApiHttpClient apiHttpClient) {
this.apiHttpClient = apiHttpClient;
}

@Override
public Mono<ReactiveAuthenticationManager> resolve(final ServerWebExchange context) {
return Mono.just(new CtpReactiveAuthenticationManager(meClient(apiHttpClient, context.getSession()),
credentials(), projectKey));
}

private ProjectApiRoot meClient(final ApiHttpClient client, final Mono<WebSession> session) {
TokenStorage storage = new SessionTokenStorage(session);

ApiRootBuilder builder = ApiRootBuilder.of(client)
.withApiBaseUrl(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
.withProjectKey(projectKey)
.withAnonymousRefreshFlow(credentials(), ServiceRegion.GCP_EUROPE_WEST1, storage);

return builder.build(projectKey);
}
}

public static class LoginWebFilter extends AuthenticationWebFilter {
public LoginWebFilter(ReactiveAuthenticationManagerResolver<ServerWebExchange> authenticationManager,
ServerSecurityContextRepository securityContextRepository) {
super(authenticationManager);
setServerAuthenticationConverter(new ServerFormLoginAuthenticationConverter());
setRequiresAuthenticationMatcher(ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/login"));
setAuthenticationFailureHandler(new RedirectServerAuthenticationFailureHandler("/login?error"));
setAuthenticationSuccessHandler(new WebFilterChainServerAuthenticationSuccessHandler());
setSecurityContextRepository(securityContextRepository);
}
}

private static class WebFilterChainServerAuthenticationSuccessHandler
implements ServerAuthenticationSuccessHandler {

@Override
public Mono<Void> onAuthenticationSuccess(WebFilterExchange webFilterExchange, Authentication authentication) {
ServerWebExchange exchange = webFilterExchange.getExchange();
TokenStorage storage = new SessionTokenStorage(exchange.getSession());
TokenGrantedAuthority authority = (TokenGrantedAuthority) authentication.getAuthorities()
.stream()
.filter(grantedAuthority -> grantedAuthority instanceof TokenGrantedAuthority)
.findFirst()
.get();
storage.setToken(authority.getToken());

return http.build();
if (authentication.getPrincipal() instanceof CtpUserDetails) {
exchange.getSession().blockOptional(Duration.ofMillis(500)).ifPresent(session -> {
session.getAttributes()
.put(MeRepository.SESSION_CART, ((CtpUserDetails) authentication.getPrincipal()).getCart());
session.save();
});
}
return webFilterExchange.getChain().filter(exchange);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.commercetools.sdk.examples.springmvc.config;

import java.util.*;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import com.commercetools.api.models.cart.CartReference;
import com.commercetools.api.models.customer.Customer;

import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import reactor.util.annotation.Nullable;

public class CtpUserDetails implements UserDetails {
private final String customerName;
private final CartReference cartReference;
private final Collection<? extends GrantedAuthority> authorities;

public CtpUserDetails(Customer customer, CartReference cart, Collection<? extends GrantedAuthority> authorities) {
this.customerName = userName(customer);
this.cartReference = cart;
this.authorities = authorities;
}

private String userName(Customer customer) {
return Stream.of(customer.getFirstName(), customer.getMiddleName(), customer.getLastName())
.filter(s -> s != null && !s.isEmpty())
.collect(Collectors.joining(" "));
}

@Nullable
public CartReference getCart() {
return cartReference;
}

@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}

@Override
public String getPassword() {
return null;
}

@Override
public String getUsername() {
return customerName;
}

@Override
public boolean isAccountNonExpired() {
return true;
}

@Override
public boolean isAccountNonLocked() {
return true;
}

@Override
public boolean isCredentialsNonExpired() {
return true;
}

@Override
public boolean isEnabled() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.commercetools.sdk.examples.springmvc.config;

import com.commercetools.api.client.ProjectApiRoot;
import com.commercetools.api.defaultconfig.ApiRootBuilder;
import com.commercetools.api.defaultconfig.ServiceRegion;

import io.vrap.rmf.base.client.*;
import io.vrap.rmf.base.client.oauth2.ClientCredentials;
import io.vrap.rmf.base.client.oauth2.TokenStorage;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import org.springframework.web.server.WebSession;
import reactor.core.publisher.Mono;

import java.util.Map;

@Component
public class MeClientFilter implements WebFilter {
private final ApiHttpClient client;

@Value(value = "${ctp.client.id}")
private String clientId;

@Value(value = "${ctp.client.secret}")
private String clientSecret;

@Value(value = "${ctp.project.key}")
private String projectKey;

private ClientCredentials credentials() {
return ClientCredentials.of().withClientId(clientId).withClientSecret(clientSecret).build();
}

@Autowired
public MeClientFilter(ApiHttpClient client) {
this.client = client;
}

@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
final ContextApiHttpClient contextClient = contextClient(client, new MDCContext(Map.of("requestId", exchange.getRequest().getId())));
final ProjectApiRoot meClient = exchange.getAttributeOrDefault("meClient",
meClient(contextClient, exchange.getSession()));
exchange.getAttributes().put("meClient", meClient);
exchange.getAttributes().put("contextRoot", contextRoot(contextClient));

return chain.filter(exchange);
}

private ProjectApiRoot meClient(ApiHttpClient client, Mono<WebSession> session) {
TokenStorage storage = new SessionTokenStorage(session);

ApiRootBuilder builder = ApiRootBuilder.of(client)
.withApiBaseUrl(ServiceRegion.GCP_EUROPE_WEST1.getApiUrl())
.withProjectKey(projectKey)
.withAnonymousRefreshFlow(credentials(), ServiceRegion.GCP_EUROPE_WEST1, storage);

return builder.build(projectKey);
}

private ProjectApiRoot contextRoot(ContextApiHttpClient apiHttpClient) {
return ProjectApiRoot.fromClient(projectKey, apiHttpClient);
}

private ContextApiHttpClient contextClient(ApiHttpClient client, Context context) {
return ContextApiHttpClient.of(client, context);
}
}
Loading

0 comments on commit 0e477d3

Please sign in to comment.