Skip to content

Commit 5c723d9

Browse files
HaaroleanMgrdichVladSenyuta
authored
Role based access control (#2790)
* Role based access control * Fix build + checkstyle * Refactoring, some bug fixes, review fixes * Compile permission value patterns * Make the resource a enum instead of a string * Refactoring * Make clusters required * Fix formatting * switch the switch case to a smart switch case * Get rid of topic analysis actions * Rename endpoints, fix an issue * Return a flag indicating if rbac is on and a username * Fix yaml indent in editorconfig * Fix github & cognito role name fetching * Fix case matching for actions * Update readme * Add an endpoint to determine if a user can create a resource * Fix tests (I hope so) * Fix tests * Use spring configs instead of a separate file, rename endpoints * Add "ALL" action Get rid of unnecessary cache, save groups into spring auth Review fixes * Make "all" action case-insensitive * Role based access control / FrontEnd (#2933) * Initial modifications and mocking the For the RoleAccess * fix the Suspense issue in the components , comment the Tests to implement later * minor test comment * Roles and configuration and santization of data * initialize RoleCheck hook * make the App test file visible + minor modification in the permission hook * Structure the data so the Burger header toggle does not rerender the whole application * add tests to the NavBar and the Page container , add tests * NavBar and PageContainer bug fixes * Roles Testing code modification * covering Topics create button Actions, and Schema create button Actions * minor typescript code modifications for the cluster required parameter in the rolesHelper * minor typescript code modifications for the cluster required parameter in the rolesHelper * minor code modification to describe the Permission tests more clearly * Produce message Permissions with Tests Suites for Topic * Add Schema Edit Permission with tests * Minor role changes * Add ActionButton Component to handle the Button with tooltip * Add ActionButton Component to handle the Button with tooltip * Add Action Button to every Button create Action * ActionButton add test suites * usePermission code modification to include regular expressions * Abstract Actions Component for code repetition, add Configs Edit button Permission + add the tests suites to it. * Schema Remove functionality Permission and Test Suites + creation of the ActionDropdownItem for Actions * Topic Edit Clear and delete Topic , Permissions with test suites * ActionsCell For Topic Message Overview for permissions with tests suites * Connector Delete , Consumer Groups Permission + writing test suites * Add Permissions to the Topics ActionCell * Topic Table Permissions Tests Suites * Headless Logic for the Permission Part * add documentation for the headless Part of the permission + add modification of the data version 2 for efficient algorithmic lookup * replace modify data logic and isPermitted function to have faster access to the data * Add Permission helpers tests suites * usePermission hook test suites * BatchActionsBar add Permissions + minor modification in TopicTable tests suites * Statistics and Metrics code Permission + add test suites * Recreate Topic Permissions in the Topic page, add tests suites * Actions for the Connector components * Messages NavLink View Permission * Test suites messages code modifications * Permissions comment code modifications * Replacing the Mock Data With the actual code * Add ActionNavLink test suites * BatchActionsBar code smell modifications * maximizing the permissions tests suites * maximizing the permissions tests suites * maximizing the permissions tests suites * Tooltip code refactoring and fix the positions issue * permissions increase the tests coverage * add user info at the navigation header and tests suites * Add Global Schema Selector Permissions with test suites * Roles minor code removal * Change the Action Component form hook mixin approach to declarative props approach * add isPermitted function for multiple Actions , adding tests suites for this particular case * remove redundant Permissions test blocks from the components * remove redundant Permissions test blocks from the components * Action Buttons test suites' coverage + generalizing the code of the Actions * add invalid Permission check in Action Components tests suites * Modularization of Actions Components * Modularization of Actions Components by adding DropDownAction to it. * Reflect the BE Changes to the UI , by changing the default behavior or the testing of roles. * Reflect the BE Changes to the UI , by changing the default behavior or the testing of roles. * Get rid of not necessary usePermission mocks * Modifications in the UserInfo data , to consider the UI without any login functionality * minor code modifications in the BatchActionBar component * change the Query key for the user info * change the default message for the tooltip * Fix the Create Role Access for Topics and Schemas * ListPage Connector create permissions * add Headless logic for Create Permission with test suites. + add react hook render-er * Create Button ActionButton logic implementation * Remove Code smells , by removing the duplications * increase the test suites for isPermittedToCreate logic * increase the test suites for isPermittedToCreate logic * Change the UserResourceType Enum with the new value * Apply New Resource Creation validation, for Topic, Schema, Connector * Apply New Resource Creation validation, for Topic, Schema, Connector * minor code refactor modifications * minor code modification in the topics useCreate hook * Async Validation for all the Create Pages * caching test for optimal performance in async validation schemas * Reverting the Front End Validation * Reverting the Front End Validation * Authorization API minor syntax modifications * fix SmokeTests Co-authored-by: Roman Zabaluev <[email protected]> Co-authored-by: VladSenyuta <[email protected]> Co-authored-by: Mgrdich <[email protected]> Co-authored-by: VladSenyuta <[email protected]>
1 parent c2be45f commit 5c723d9

File tree

141 files changed

+5771
-1120
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

141 files changed

+5771
-1120
lines changed

.editorconfig

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -279,3 +279,8 @@ ij_java_wrap_long_lines = false
279279
insert_final_newline = false
280280
trim_trailing_whitespace = false
281281

282+
[*.yaml]
283+
indent_size = 2
284+
[*.yml]
285+
indent_size = 2
286+

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ the cloud.
3131
* **Dynamic Topic Configuration** — create and configure new topics with dynamic configuration
3232
* **Configurable Authentification** — secure your installation with optional Github/Gitlab/Google OAuth 2.0
3333
* **Custom serialization/deserialization plugins** - use a ready-to-go serde for your data like AWS Glue or Smile, or code your own!
34+
* **Role based access control** - manage permissions to access the UI with granular precision
3435

3536
# The Interface
3637
UI for Apache Kafka wraps major functions of Apache Kafka with an intuitive user interface.

kafka-ui-api/pom.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@
306306
</configuration>
307307
</execution>
308308
</executions>
309+
309310
</plugin>
310311
<plugin>
311312
<groupId>org.antlr</groupId>
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package com.provectus.kafka.ui.config.auth;
2+
3+
import java.util.Collection;
4+
import lombok.Value;
5+
6+
public record AuthenticatedUser(String principal, Collection<String> groups) {
7+
8+
}

kafka-ui-api/src/main/java/com/provectus/kafka/ui/config/auth/CognitoOAuthSecurityConfig.java

Lines changed: 0 additions & 80 deletions
This file was deleted.
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
package com.provectus.kafka.ui.config.auth;
2+
3+
import java.util.HashMap;
4+
import java.util.Map;
5+
import java.util.Set;
6+
import javax.annotation.PostConstruct;
7+
import lombok.Data;
8+
import org.springframework.boot.context.properties.ConfigurationProperties;
9+
import org.springframework.util.Assert;
10+
11+
@ConfigurationProperties("auth.oauth2")
12+
@Data
13+
public class OAuthProperties {
14+
private Map<String, OAuth2Provider> client = new HashMap<>();
15+
16+
@PostConstruct
17+
public void validate() {
18+
getClient().values().forEach(this::validateProvider);
19+
}
20+
21+
private void validateProvider(final OAuth2Provider provider) {
22+
Assert.hasText(provider.getClientId(), "Client id must not be empty.");
23+
Assert.hasText(provider.getProvider(), "Provider name must not be empty");
24+
}
25+
26+
@Data
27+
public static class OAuth2Provider {
28+
private String provider;
29+
private String clientId;
30+
private String clientSecret;
31+
private String clientName;
32+
private String redirectUri;
33+
private String authorizationGrantType;
34+
private Set<String> scope;
35+
private String issuerUri;
36+
private String authorizationUri;
37+
private String tokenUri;
38+
private String userInfoUri;
39+
private String jwkSetUri;
40+
private String userNameAttribute;
41+
private Map<String, String> customParams;
42+
}
43+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
package com.provectus.kafka.ui.config.auth;
2+
3+
import static com.provectus.kafka.ui.config.auth.OAuthProperties.OAuth2Provider;
4+
import static org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Provider;
5+
import static org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties.Registration;
6+
7+
import lombok.AccessLevel;
8+
import lombok.NoArgsConstructor;
9+
import org.apache.commons.lang3.StringUtils;
10+
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
11+
12+
@NoArgsConstructor(access = AccessLevel.PRIVATE)
13+
public final class OAuthPropertiesConverter {
14+
15+
private static final String TYPE = "type";
16+
private static final String GOOGLE = "google";
17+
18+
public static OAuth2ClientProperties convertProperties(final OAuthProperties properties) {
19+
final var result = new OAuth2ClientProperties();
20+
properties.getClient().forEach((key, provider) -> {
21+
var registration = new Registration();
22+
registration.setClientId(provider.getClientId());
23+
registration.setClientSecret(provider.getClientSecret());
24+
registration.setClientName(provider.getClientName());
25+
registration.setScope(provider.getScope());
26+
registration.setRedirectUri(provider.getRedirectUri());
27+
registration.setAuthorizationGrantType(provider.getAuthorizationGrantType());
28+
29+
result.getRegistration().put(key, registration);
30+
31+
var clientProvider = new Provider();
32+
applyCustomTransformations(provider);
33+
34+
clientProvider.setAuthorizationUri(provider.getAuthorizationUri());
35+
clientProvider.setIssuerUri(provider.getIssuerUri());
36+
clientProvider.setJwkSetUri(provider.getJwkSetUri());
37+
clientProvider.setTokenUri(provider.getTokenUri());
38+
clientProvider.setUserInfoUri(provider.getUserInfoUri());
39+
clientProvider.setUserNameAttribute(provider.getUserNameAttribute());
40+
41+
result.getProvider().put(key, clientProvider);
42+
});
43+
return result;
44+
}
45+
46+
private static void applyCustomTransformations(OAuth2Provider provider) {
47+
applyGoogleTransformations(provider);
48+
}
49+
50+
private static void applyGoogleTransformations(OAuth2Provider provider) {
51+
if (!isGoogle(provider)) {
52+
return;
53+
}
54+
55+
String allowedDomain = provider.getCustomParams().get("allowedDomain");
56+
if (StringUtils.isEmpty(allowedDomain)) {
57+
return;
58+
}
59+
60+
final String newUri = provider.getAuthorizationUri() + "?hd=" + allowedDomain;
61+
provider.setAuthorizationUri(newUri);
62+
}
63+
64+
private static boolean isGoogle(OAuth2Provider provider) {
65+
return provider.getCustomParams().get(TYPE).equalsIgnoreCase(GOOGLE);
66+
}
67+
}
68+
Lines changed: 101 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,131 @@
11
package com.provectus.kafka.ui.config.auth;
22

3-
import lombok.AllArgsConstructor;
3+
import com.provectus.kafka.ui.config.auth.logout.OAuthLogoutSuccessHandler;
4+
import com.provectus.kafka.ui.service.rbac.AccessControlService;
5+
import com.provectus.kafka.ui.service.rbac.extractor.ProviderAuthorityExtractor;
6+
import java.util.ArrayList;
7+
import java.util.List;
8+
import java.util.Map;
9+
import java.util.Optional;
10+
import lombok.RequiredArgsConstructor;
411
import lombok.extern.log4j.Log4j2;
12+
import org.jetbrains.annotations.Nullable;
513
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
6-
import org.springframework.context.ApplicationContext;
14+
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientProperties;
15+
import org.springframework.boot.autoconfigure.security.oauth2.client.OAuth2ClientPropertiesRegistrationAdapter;
16+
import org.springframework.boot.context.properties.EnableConfigurationProperties;
717
import org.springframework.context.annotation.Bean;
818
import org.springframework.context.annotation.Configuration;
19+
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
920
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
1021
import org.springframework.security.config.web.server.ServerHttpSecurity;
22+
import org.springframework.security.oauth2.client.oidc.userinfo.OidcReactiveOAuth2UserService;
23+
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
24+
import org.springframework.security.oauth2.client.oidc.web.server.logout.OidcClientInitiatedServerLogoutSuccessHandler;
25+
import org.springframework.security.oauth2.client.registration.ClientRegistration;
26+
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
27+
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
28+
import org.springframework.security.oauth2.client.userinfo.DefaultReactiveOAuth2UserService;
29+
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
30+
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
31+
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
32+
import org.springframework.security.oauth2.core.user.OAuth2User;
1133
import org.springframework.security.web.server.SecurityWebFilterChain;
12-
import org.springframework.util.ClassUtils;
34+
import org.springframework.security.web.server.authentication.logout.ServerLogoutSuccessHandler;
35+
import reactor.core.publisher.Mono;
1336

1437
@Configuration
15-
@EnableWebFluxSecurity
1638
@ConditionalOnProperty(value = "auth.type", havingValue = "OAUTH2")
17-
@AllArgsConstructor
39+
@EnableConfigurationProperties(OAuthProperties.class)
40+
@EnableWebFluxSecurity
41+
@EnableReactiveMethodSecurity
42+
@RequiredArgsConstructor
1843
@Log4j2
1944
public class OAuthSecurityConfig extends AbstractAuthSecurityConfig {
2045

21-
public static final String REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME =
22-
"org.springframework.security.oauth2.client.registration."
23-
+ "ReactiveClientRegistrationRepository";
24-
25-
private static final boolean IS_OAUTH2_PRESENT = ClassUtils.isPresent(
26-
REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME,
27-
OAuthSecurityConfig.class.getClassLoader()
28-
);
29-
30-
private final ApplicationContext context;
46+
private final OAuthProperties properties;
3147

3248
@Bean
33-
public SecurityWebFilterChain configure(ServerHttpSecurity http) {
49+
public SecurityWebFilterChain configure(ServerHttpSecurity http, OAuthLogoutSuccessHandler logoutHandler) {
3450
log.info("Configuring OAUTH2 authentication.");
35-
http.authorizeExchange()
51+
52+
return http.authorizeExchange()
3653
.pathMatchers(AUTH_WHITELIST)
3754
.permitAll()
3855
.anyExchange()
39-
.authenticated();
56+
.authenticated()
57+
58+
.and()
59+
.oauth2Login()
60+
61+
.and()
62+
.logout()
63+
.logoutSuccessHandler(logoutHandler)
64+
65+
.and()
66+
.csrf().disable()
67+
.build();
68+
}
69+
70+
@Bean
71+
public ReactiveOAuth2UserService<OidcUserRequest, OidcUser> customOidcUserService(AccessControlService acs) {
72+
final OidcReactiveOAuth2UserService delegate = new OidcReactiveOAuth2UserService();
73+
return request -> delegate.loadUser(request)
74+
.flatMap(user -> {
75+
String providerId = request.getClientRegistration().getRegistrationId();
76+
final var extractor = getExtractor(providerId, acs);
77+
if (extractor == null) {
78+
return Mono.just(user);
79+
}
4080

41-
if (IS_OAUTH2_PRESENT && OAuth2ClasspathGuard.shouldConfigure(this.context)) {
42-
OAuth2ClasspathGuard.configure(http);
43-
}
81+
return extractor.extract(acs, user, Map.of("request", request))
82+
.map(groups -> new RbacOidcUser(user, groups));
83+
});
84+
}
85+
86+
@Bean
87+
public ReactiveOAuth2UserService<OAuth2UserRequest, OAuth2User> customOauth2UserService(AccessControlService acs) {
88+
final DefaultReactiveOAuth2UserService delegate = new DefaultReactiveOAuth2UserService();
89+
return request -> delegate.loadUser(request)
90+
.flatMap(user -> {
91+
String providerId = request.getClientRegistration().getRegistrationId();
92+
final var extractor = getExtractor(providerId, acs);
93+
if (extractor == null) {
94+
return Mono.just(user);
95+
}
4496

45-
return http.csrf().disable().build();
97+
return extractor.extract(acs, user, Map.of("request", request))
98+
.map(groups -> new RbacOAuth2User(user, groups));
99+
});
46100
}
47101

48-
private static class OAuth2ClasspathGuard {
49-
static void configure(ServerHttpSecurity http) {
50-
http
51-
.oauth2Login()
52-
.and()
53-
.oauth2Client();
54-
}
55-
56-
static boolean shouldConfigure(ApplicationContext context) {
57-
ClassLoader loader = context.getClassLoader();
58-
Class<?> reactiveClientRegistrationRepositoryClass =
59-
ClassUtils.resolveClassName(REACTIVE_CLIENT_REGISTRATION_REPOSITORY_CLASSNAME, loader);
60-
return context.getBeanNamesForType(reactiveClientRegistrationRepositoryClass).length == 1;
61-
}
102+
@Bean
103+
public InMemoryReactiveClientRegistrationRepository clientRegistrationRepository() {
104+
final OAuth2ClientProperties props = OAuthPropertiesConverter.convertProperties(properties);
105+
final List<ClientRegistration> registrations =
106+
new ArrayList<>(OAuth2ClientPropertiesRegistrationAdapter.getClientRegistrations(props).values());
107+
return new InMemoryReactiveClientRegistrationRepository(registrations);
62108
}
63109

110+
@Bean
111+
public ServerLogoutSuccessHandler defaultOidcLogoutHandler(final ReactiveClientRegistrationRepository repository) {
112+
return new OidcClientInitiatedServerLogoutSuccessHandler(repository);
113+
}
114+
115+
@Nullable
116+
private ProviderAuthorityExtractor getExtractor(final String providerId, AccessControlService acs) {
117+
final String provider = getProviderByProviderId(providerId);
118+
Optional<ProviderAuthorityExtractor> extractor = acs.getExtractors()
119+
.stream()
120+
.filter(e -> e.isApplicable(provider))
121+
.findFirst();
122+
123+
return extractor.orElse(null);
124+
}
125+
126+
private String getProviderByProviderId(final String providerId) {
127+
return properties.getClient().get(providerId).getProvider();
128+
}
64129

65130
}
66131

0 commit comments

Comments
 (0)