diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthProperties.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthProperties.java index a76403bf70..0359082c46 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthProperties.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthProperties.java @@ -2,6 +2,7 @@ package com.provectus.kafka.ui.config.auth; import jakarta.annotation.PostConstruct; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Set; import lombok.Data; @@ -14,7 +15,16 @@ public class OAuthProperties { private Map client = new HashMap<>(); @PostConstruct - public void validate() { + public void init() { + getClient().values().forEach((provider) -> { + if (provider.getCustomParams() == null) { + provider.setCustomParams(new HashMap<>()); + } + if (provider.getScope() == null) { + provider.setScope(new HashSet<>()); + } + }); + getClient().values().forEach(this::validateProvider); } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthPropertiesConverter.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthPropertiesConverter.java index 90daa36273..f7f986f5ea 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthPropertiesConverter.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthPropertiesConverter.java @@ -73,8 +73,7 @@ public final class OAuthPropertiesConverter { } private static boolean isGoogle(OAuth2Provider provider) { - return provider.getCustomParams() != null - && GOOGLE.equalsIgnoreCase(provider.getCustomParams().get(TYPE)); + return GOOGLE.equalsIgnoreCase(provider.getCustomParams().get(TYPE)); } } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthSecurityConfig.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthSecurityConfig.java index 1d237e0173..d488c00e3f 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthSecurityConfig.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/OAuthSecurityConfig.java @@ -114,17 +114,17 @@ public class OAuthSecurityConfig extends AbstractAuthSecurityConfig { @Nullable private ProviderAuthorityExtractor getExtractor(final String providerId, AccessControlService acs) { - final String provider = getProviderByProviderId(providerId); + final var provider = getProviderByProviderId(providerId); Optional extractor = acs.getExtractors() .stream() - .filter(e -> e.isApplicable(provider)) + .filter(e -> e.isApplicable(provider.getProvider(), provider.getCustomParams())) .findFirst(); return extractor.orElse(null); } - private String getProviderByProviderId(final String providerId) { - return properties.getClient().get(providerId).getProvider(); + private OAuthProperties.OAuth2Provider getProviderByProviderId(final String providerId) { + return properties.getClient().get(providerId); } } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/logout/CognitoLogoutSuccessHandler.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/logout/CognitoLogoutSuccessHandler.java index e9e5159e1b..3d0da9d05a 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/logout/CognitoLogoutSuccessHandler.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/logout/CognitoLogoutSuccessHandler.java @@ -46,10 +46,8 @@ public class CognitoLogoutSuccessHandler implements LogoutSuccessHandler { .fragment(null) .build(); - Assert.isTrue( - provider.getCustomParams() != null && provider.getCustomParams().containsKey("logoutUrl"), - "Custom params should contain 'logoutUrl'" - ); + Assert.isTrue(provider.getCustomParams().containsKey("logoutUrl"), + "Custom params should contain 'logoutUrl'"); final var uri = UriComponentsBuilder .fromUri(URI.create(provider.getCustomParams().get("logoutUrl"))) .queryParam("client_id", provider.getClientId()) diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/provider/Provider.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/provider/Provider.java index 27f7a56ada..a2cde9158c 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/provider/Provider.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/model/rbac/provider/Provider.java @@ -10,6 +10,8 @@ public enum Provider { OAUTH_COGNITO, + OAUTH, + LDAP, LDAP_AD; @@ -22,6 +24,8 @@ public enum Provider { public static String GOOGLE = "google"; public static String GITHUB = "github"; public static String COGNITO = "cognito"; + + public static String OAUTH = "oauth"; } } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/AccessControlService.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/AccessControlService.java index 3178feae34..02387c0820 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/AccessControlService.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/AccessControlService.java @@ -20,6 +20,7 @@ import com.provectus.kafka.ui.service.rbac.extractor.CognitoAuthorityExtractor; import com.provectus.kafka.ui.service.rbac.extractor.GithubAuthorityExtractor; import com.provectus.kafka.ui.service.rbac.extractor.GoogleAuthorityExtractor; import com.provectus.kafka.ui.service.rbac.extractor.LdapAuthorityExtractor; +import com.provectus.kafka.ui.service.rbac.extractor.OauthAuthorityExtractor; import com.provectus.kafka.ui.service.rbac.extractor.ProviderAuthorityExtractor; import jakarta.annotation.PostConstruct; import java.util.Collections; @@ -71,6 +72,7 @@ public class AccessControlService { case OAUTH_COGNITO -> new CognitoAuthorityExtractor(); case OAUTH_GOOGLE -> new GoogleAuthorityExtractor(); case OAUTH_GITHUB -> new GithubAuthorityExtractor(); + case OAUTH -> new OauthAuthorityExtractor(); case LDAP, LDAP_AD -> new LdapAuthorityExtractor(); }).collect(Collectors.toSet())) .flatMap(Set::stream) diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/CognitoAuthorityExtractor.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/CognitoAuthorityExtractor.java index f7da0a19db..9a1f3d8f12 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/CognitoAuthorityExtractor.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/CognitoAuthorityExtractor.java @@ -1,5 +1,7 @@ package com.provectus.kafka.ui.service.rbac.extractor; +import static com.provectus.kafka.ui.model.rbac.provider.Provider.Name.COGNITO; + import com.provectus.kafka.ui.model.rbac.Role; import com.provectus.kafka.ui.model.rbac.provider.Provider; import com.provectus.kafka.ui.service.rbac.AccessControlService; @@ -18,8 +20,8 @@ public class CognitoAuthorityExtractor implements ProviderAuthorityExtractor { private static final String COGNITO_GROUPS_ATTRIBUTE_NAME = "cognito:groups"; @Override - public boolean isApplicable(String provider) { - return Provider.Name.COGNITO.equalsIgnoreCase(provider); + public boolean isApplicable(String provider, Map customParams) { + return COGNITO.equalsIgnoreCase(provider) || COGNITO.equalsIgnoreCase(customParams.get(TYPE)); } @Override diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GithubAuthorityExtractor.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GithubAuthorityExtractor.java index 0f66e45917..3cc33035e8 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GithubAuthorityExtractor.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GithubAuthorityExtractor.java @@ -1,5 +1,7 @@ package com.provectus.kafka.ui.service.rbac.extractor; +import static com.provectus.kafka.ui.model.rbac.provider.Provider.Name.GITHUB; + import com.provectus.kafka.ui.model.rbac.Role; import com.provectus.kafka.ui.model.rbac.provider.Provider; import com.provectus.kafka.ui.service.rbac.AccessControlService; @@ -28,8 +30,8 @@ public class GithubAuthorityExtractor implements ProviderAuthorityExtractor { private static final String DUMMY = "dummy"; @Override - public boolean isApplicable(String provider) { - return Provider.Name.GITHUB.equalsIgnoreCase(provider); + public boolean isApplicable(String provider, Map customParams) { + return GITHUB.equalsIgnoreCase(provider) || GITHUB.equalsIgnoreCase(customParams.get(TYPE)); } @Override diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GoogleAuthorityExtractor.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GoogleAuthorityExtractor.java index 747a9dea05..32af23ceef 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GoogleAuthorityExtractor.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/GoogleAuthorityExtractor.java @@ -1,5 +1,7 @@ package com.provectus.kafka.ui.service.rbac.extractor; +import static com.provectus.kafka.ui.model.rbac.provider.Provider.Name.GOOGLE; + import com.provectus.kafka.ui.model.rbac.Role; import com.provectus.kafka.ui.model.rbac.provider.Provider; import com.provectus.kafka.ui.service.rbac.AccessControlService; @@ -19,8 +21,8 @@ public class GoogleAuthorityExtractor implements ProviderAuthorityExtractor { public static final String EMAIL_ATTRIBUTE_NAME = "email"; @Override - public boolean isApplicable(String provider) { - return Provider.Name.GOOGLE.equalsIgnoreCase(provider); + public boolean isApplicable(String provider, Map customParams) { + return GOOGLE.equalsIgnoreCase(provider) || GOOGLE.equalsIgnoreCase(customParams.get(TYPE)); } @Override diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/LdapAuthorityExtractor.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/LdapAuthorityExtractor.java index 6284bb2923..c137daa2ee 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/LdapAuthorityExtractor.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/LdapAuthorityExtractor.java @@ -11,7 +11,7 @@ import reactor.core.publisher.Mono; public class LdapAuthorityExtractor implements ProviderAuthorityExtractor { @Override - public boolean isApplicable(String provider) { + public boolean isApplicable(String provider, Map params) { return false; // TODO #2752 } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/OauthAuthorityExtractor.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/OauthAuthorityExtractor.java index a97efe85a8..f412c59d88 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/OauthAuthorityExtractor.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/OauthAuthorityExtractor.java @@ -1,8 +1,19 @@ package com.provectus.kafka.ui.service.rbac.extractor; +import static com.provectus.kafka.ui.model.rbac.provider.Provider.Name.OAUTH; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.provectus.kafka.ui.model.rbac.Role; +import com.provectus.kafka.ui.model.rbac.provider.Provider; import com.provectus.kafka.ui.service.rbac.AccessControlService; +import java.util.Collection; +import java.util.Collections; +import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import lombok.extern.slf4j.Slf4j; import org.springframework.security.oauth2.core.user.DefaultOAuth2User; import reactor.core.publisher.Mono; @@ -11,12 +22,14 @@ import reactor.core.publisher.Mono; public class OauthAuthorityExtractor implements ProviderAuthorityExtractor { @Override - public boolean isApplicable(String provider) { - return false; // TODO #2844 + public boolean isApplicable(String provider, Map customParams) { + return OAUTH.equalsIgnoreCase(provider) || OAUTH.equalsIgnoreCase(customParams.get(TYPE)); } @Override public Mono> extract(AccessControlService acs, Object value, Map additionalParams) { + log.debug("Extracting OAuth2 user authorities"); + DefaultOAuth2User principal; try { principal = (DefaultOAuth2User) value; @@ -25,7 +38,52 @@ public class OauthAuthorityExtractor implements ProviderAuthorityExtractor { throw new RuntimeException(); } - return Mono.just(Set.of(principal.getName())); // TODO #2844 + Set groupsByUsername = acs.getRoles() + .stream() + .filter(r -> r.getSubjects() + .stream() + .filter(s -> s.getProvider().equals(Provider.OAUTH)) + .filter(s -> s.getType().equals("user")) + .anyMatch(s -> s.getValue().equals(principal.getName()))) + .map(Role::getName) + .collect(Collectors.toSet()); + + Set groupsByGroupField = acs.getRoles() + .stream() + .filter(role -> role.getSubjects() + .stream() + .filter(s -> s.getProvider().equals(Provider.OAUTH)) + .filter(s -> s.getType().equals("groupsfield")) + .anyMatch(subject -> convertGroups(principal.getAttribute(subject.getValue())).contains(role.getName())) + ) + //subject.getValue() + .map(Role::getName) + .collect(Collectors.toSet()); + + return Mono.just(Stream.concat(groupsByUsername.stream(), groupsByGroupField.stream()).collect(Collectors.toSet())); + } + + @SuppressWarnings("unchecked") + private Collection convertGroups(Object groups) { + try { + if ((groups instanceof List) || (groups instanceof Set)) { + log.trace("The field is either a set or a list, returning as is"); + return (Collection) groups; + } + + if (!(groups instanceof String)) { + log.debug("The field is not a string, skipping"); + return Collections.emptySet(); + } + + log.trace("Trying to deserialize the field"); + //@formatter:off + return new ObjectMapper().readValue((String) groups, new TypeReference<>() {}); + //@formatter:on + } catch (Exception e) { + log.error("Error deserializing field", e); + return Collections.emptySet(); + } } } diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/ProviderAuthorityExtractor.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/ProviderAuthorityExtractor.java index 7cc25e4c61..02c6d3017f 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/ProviderAuthorityExtractor.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/service/rbac/extractor/ProviderAuthorityExtractor.java @@ -7,7 +7,9 @@ import reactor.core.publisher.Mono; public interface ProviderAuthorityExtractor { - boolean isApplicable(String provider); + String TYPE = "type"; + + boolean isApplicable(String provider, Map customParams); Mono> extract(AccessControlService acs, Object value, Map additionalParams); diff --git a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/DynamicConfigOperations.java b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/DynamicConfigOperations.java index 75c6d25f95..8163defe34 100644 --- a/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/DynamicConfigOperations.java +++ b/kafka-ui-api/src/main/java/com/provectus/kafka/ui/util/DynamicConfigOperations.java @@ -224,7 +224,7 @@ public class DynamicConfigOperations { Optional.ofNullable(auth) .flatMap(a -> Optional.ofNullable(a.oauth2)) - .ifPresent(OAuthProperties::validate); + .ifPresent(OAuthProperties::init); Optional.ofNullable(webclient) .ifPresent(WebclientProperties::validate);