Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1639 - added s2s token support #1646

Merged
merged 3 commits into from
Mar 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.opendatadiscovery.oddplatform.auth;

import jakarta.annotation.PostConstruct;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class S2sTokenProvider {
@Value("${auth.s2s.token:#{null}}")
private String s2sToken;
@Value("${auth.s2s.enabled:false}")
private boolean s2sEnabled;

public boolean isValidToken(final String token) {
if (StringUtils.isBlank(token)) {
return false;
}

return s2sToken.equals(token);
}

@PostConstruct
public void validate() {
if (s2sEnabled && StringUtils.isBlank(s2sToken)) {
throw new IllegalStateException("Long Term Token is not defined");
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.opendatadiscovery.oddplatform.auth.filter;

import java.util.List;
import lombok.RequiredArgsConstructor;
import org.opendatadiscovery.oddplatform.auth.S2sTokenProvider;
import org.opendatadiscovery.oddplatform.auth.mapper.GrantedAuthorityExtractor;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.ReactiveSecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;

@Component
@RequiredArgsConstructor
public class S2sAuthenticationFilter implements WebFilter {
private static final String X_API_KEY_HEADER = "X-API-Key";

private final GrantedAuthorityExtractor grantedAuthorityExtractor;
private final S2sTokenProvider s2sTokenProvider;

@Override
public Mono<Void> filter(final ServerWebExchange exchange, final WebFilterChain chain) {
if (!s2sTokenProvider.isValidToken(extractTokenFromRequest(exchange))) {
return chain.filter(exchange);
}

final UserDetails userDetails = User.withUsername("ADMIN")
.password("")
.roles("ADMIN")
.build();

return chain.filter(exchange)
.contextWrite(ReactiveSecurityContextHolder.withAuthentication(
new UsernamePasswordAuthenticationToken(userDetails, null,
grantedAuthorityExtractor.getAuthorities(true))));
}

private String extractTokenFromRequest(final ServerWebExchange exchange) {
final List<String> authorizationHeaders = exchange.getRequest().getHeaders().get(X_API_KEY_HEADER);
if (authorizationHeaders != null && !authorizationHeaders.isEmpty()) {
return authorizationHeaders.get(0);
}
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@
import org.apache.commons.lang3.StringUtils;
import org.opendatadiscovery.oddplatform.auth.ODDLDAPProperties;
import org.opendatadiscovery.oddplatform.auth.authorization.AuthorizationCustomizer;
import org.opendatadiscovery.oddplatform.auth.filter.S2sAuthenticationFilter;
import org.opendatadiscovery.oddplatform.auth.manager.extractor.ResourceExtractor;
import org.opendatadiscovery.oddplatform.auth.mapper.GrantedAuthorityExtractor;
import org.opendatadiscovery.oddplatform.service.permission.PermissionService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
Expand All @@ -27,6 +29,7 @@
import org.springframework.security.config.Customizer;
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.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
Expand All @@ -53,6 +56,7 @@
@Slf4j
public class LDAPSecurityConfiguration {
private final ODDLDAPProperties properties;
private final S2sAuthenticationFilter s2sAuthenticationFilter;

@Bean
public ReactiveAuthenticationManager authenticationManager(final LdapContextSource contextSource,
Expand Down Expand Up @@ -132,15 +136,21 @@ public LdapTemplate ldapTemplate(final ContextSource contextSource) {
@Order(Ordered.HIGHEST_PRECEDENCE)
public SecurityWebFilterChain configureLdap(final ServerHttpSecurity http,
final List<ResourceExtractor> extractors,
final PermissionService permissionService) {
return http
final PermissionService permissionService,
@Value("${auth.s2s.enabled:false}") final boolean s2sEnabled) {
final ServerHttpSecurity sec = http
.cors(Customizer.withDefaults())
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.securityMatcher(new PathPatternParserServerWebExchangeMatcher("/**"))
.authorizeExchange(new AuthorizationCustomizer(permissionService, extractors))
.logout(Customizer.withDefaults())
.formLogin(Customizer.withDefaults())
.build();
.formLogin(Customizer.withDefaults());

if (s2sEnabled) {
sec.addFilterAt(s2sAuthenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC);
}

return sec.build();
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@
import java.util.stream.Collectors;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.opendatadiscovery.oddplatform.auth.filter.S2sAuthenticationFilter;
import org.opendatadiscovery.oddplatform.auth.mapper.GrantedAuthorityExtractor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.Customizer;
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.core.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
Expand All @@ -31,12 +33,13 @@
@RequiredArgsConstructor
public class LoginFormSecurityConfiguration {
private final GrantedAuthorityExtractor grantedAuthorityExtractor;
private final S2sAuthenticationFilter s2sAuthenticationFilter;

@Bean
public SecurityWebFilterChain securityWebFilterChainLoginForm(
final ServerHttpSecurity http,
@Value("${auth.login-form-redirect:}") final String redirectURIString
) {
@Value("${auth.login-form-redirect:}") final String redirectURIString,
@Value("${auth.s2s.enabled:false}") final boolean s2sEnabled) {
final URI redirectURI = parseURI(redirectURIString);

final ServerAuthenticationSuccessHandler authHandler = redirectURI != null
Expand All @@ -46,14 +49,20 @@ public SecurityWebFilterChain securityWebFilterChainLoginForm(
final String[] permittedPaths = new String[] {
"/actuator/health", "/favicon.ico", "/ingestion/entities", "/api/slack/events"
};
return http

final ServerHttpSecurity sec = http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.authorizeExchange(authorizeExchangeSpec -> authorizeExchangeSpec
.pathMatchers(permittedPaths).permitAll()
.pathMatchers("/**").authenticated())
.formLogin(formLoginSpec -> formLoginSpec.authenticationSuccessHandler(authHandler))
.logout(Customizer.withDefaults())
.build();
.logout(Customizer.withDefaults());

if (s2sEnabled) {
sec.addFilterAt(s2sAuthenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC);
}

return sec.build();
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,13 @@
import org.opendatadiscovery.oddplatform.auth.ODDOAuth2PropertiesConverter;
import org.opendatadiscovery.oddplatform.auth.Provider;
import org.opendatadiscovery.oddplatform.auth.authorization.AuthorizationCustomizer;
import org.opendatadiscovery.oddplatform.auth.filter.S2sAuthenticationFilter;
import org.opendatadiscovery.oddplatform.auth.handler.OAuthUserHandler;
import org.opendatadiscovery.oddplatform.auth.logout.OAuthLogoutSuccessHandler;
import org.opendatadiscovery.oddplatform.auth.manager.extractor.ResourceExtractor;
import org.opendatadiscovery.oddplatform.dto.security.UserProviderRole;
import org.opendatadiscovery.oddplatform.service.permission.PermissionService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesMapper;
Expand Down Expand Up @@ -73,6 +75,7 @@
@RequiredArgsConstructor
public class OAuthSecurityConfiguration {
private final ODDOAuth2Properties properties;
private final S2sAuthenticationFilter s2sAuthenticationFilter;
private final List<OAuthUserHandler<OAuth2User, OAuth2UserRequest>> oauthUserHandlers;
private final List<OAuthUserHandler<OidcUser, OidcUserRequest>> oidcUserHandlers;

Expand All @@ -83,7 +86,8 @@ public SecurityWebFilterChain securityWebFilterChainOauth2Client(
final ReactiveClientRegistrationRepository repo,
final PermissionService permissionService,
final List<ResourceExtractor> extractors,
final TemplateEngine templateEngine) {
final TemplateEngine templateEngine,
@Value("${auth.s2s.enabled:false}") final boolean s2sEnabled) {
final List<ClientRegistration> clientRegistrations =
IteratorUtils.toList(((InMemoryReactiveClientRegistrationRepository) repo).iterator());

Expand All @@ -101,6 +105,10 @@ public SecurityWebFilterChain securityWebFilterChainOauth2Client(
SecurityWebFiltersOrder.CSRF);
}

if (s2sEnabled) {
sec.addFilterAt(s2sAuthenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC);
}

return sec.build();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package org.opendatadiscovery.oddplatform.config;

import java.time.Duration;
import org.springframework.beans.factory.annotation.Value;
import lombok.RequiredArgsConstructor;
import org.opendatadiscovery.oddplatform.config.properties.GenAIProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.reactive.ClientHttpConnector;
Expand All @@ -10,20 +12,20 @@
import reactor.netty.http.client.HttpClient;

@Configuration
@EnableConfigurationProperties(GenAIProperties.class)
@RequiredArgsConstructor
public class WebClientConfiguration {
@Value("${genai.url:}")
private String genAIUrl;
@Value("${genai.request_timeout:2}")
private Integer getAiRequestTimeout;
private final GenAIProperties genAIProperties;

@Bean("genAiWebClient")
public WebClient webClient() {
final HttpClient httpClient = HttpClient.create().responseTimeout(Duration.ofMinutes(getAiRequestTimeout));
final HttpClient httpClient = HttpClient.create()
.responseTimeout(Duration.ofMinutes(genAIProperties.getRequestTimeout()));
final ClientHttpConnector connector = new ReactorClientHttpConnector(httpClient);

return WebClient.builder()
.clientConnector(connector)
.baseUrl(genAIUrl)
.baseUrl(genAIProperties.getUrl())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.opendatadiscovery.oddplatform.config.properties;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties("genai")
@Data
public class GenAIProperties {
private boolean enabled;
private String url;
private int requestTimeout;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,14 @@
import io.netty.handler.timeout.ReadTimeoutException;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.StringEscapeUtils;
import org.opendatadiscovery.oddplatform.api.contract.model.GenAIRequest;
import org.opendatadiscovery.oddplatform.api.contract.model.GenAIResponse;
import org.opendatadiscovery.oddplatform.config.properties.GenAIProperties;
import org.opendatadiscovery.oddplatform.exception.BadUserRequestException;
import org.opendatadiscovery.oddplatform.exception.GenAIException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;
Expand All @@ -23,22 +22,19 @@ public class GenAIServiceImpl implements GenAIService {
public static final String QUERY_DATA = "/query_data";
public static final String QUESTION_FIELD = "question";

private final String genAIUrl;
private final Integer getAiRequestTimeout;
private final GenAIProperties genAIProperties;
private final WebClient webClient;

@Autowired
public GenAIServiceImpl(@Value("${genai.url:}") final String genAIUrl,
@Value("${genai.request_timeout:2}") final Integer getAiRequestTimeout,
public GenAIServiceImpl(final GenAIProperties genAIProperties,
@Qualifier("genAiWebClient") final WebClient webClient) {
this.genAIUrl = genAIUrl;
this.getAiRequestTimeout = getAiRequestTimeout;
this.genAIProperties = genAIProperties;
this.webClient = webClient;
}

@Override
public Mono<GenAIResponse> getResponseFromGenAI(final GenAIRequest request) {
if (StringUtils.isBlank(genAIUrl)) {
if (!genAIProperties.isEnabled()) {
return Mono.error(new BadUserRequestException("Gen AI is disabled"));
}

Expand All @@ -51,7 +47,7 @@ public Mono<GenAIResponse> getResponseFromGenAI(final GenAIRequest request) {
.body(StringEscapeUtils.unescapeJava(CharMatcher.is('\"').trimFrom(item))))
.onErrorResume(e -> e.getCause() instanceof ReadTimeoutException
? Mono.error(new GenAIException(
"Gen AI request take longer that %s min".formatted(getAiRequestTimeout)))
"Gen AI request take longer that %s min".formatted(genAIProperties.getRequestTimeout())))
: Mono.error(new GenAIException(e)));
}
}
8 changes: 7 additions & 1 deletion odd-platform-api/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ spring:
codec:
max-in-memory-size: 20MB

#genai:
genai:
enabled: false
# url: http://localhost:5000
# request_timeout: 2

Expand All @@ -35,6 +36,11 @@ auth:
# For dev/demo purposes only -- username1:password1,username2:password2,etc
login-form-credentials: admin:admin,root:root

# Server-To-Server token. Header 'X-API-Key: token'
s2s:
enabled: false
# token:

# For dev purposes only -- successful auth redirect URI
login-form-redirect:
ingestion:
Expand Down