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

BE: Impl custom authentication page #635

Merged
merged 10 commits into from
Dec 28, 2024
Merged
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -18,7 +18,8 @@ protected AbstractAuthSecurityConfig() {
"/login",
"/logout",
"/oauth2/**",
"/static/**"
"/static/**",
"/api/config/authentication"
};

}
Original file line number Diff line number Diff line change
@@ -6,6 +6,7 @@
import io.kafbat.ui.api.ApplicationConfigApi;
import io.kafbat.ui.config.ClustersProperties;
import io.kafbat.ui.model.ActionDTO;
import io.kafbat.ui.model.AppAuthenticationSettingsDTO;
import io.kafbat.ui.model.ApplicationConfigDTO;
import io.kafbat.ui.model.ApplicationConfigPropertiesDTO;
import io.kafbat.ui.model.ApplicationConfigValidationDTO;
@@ -66,6 +67,13 @@ public Mono<ResponseEntity<ApplicationInfoDTO>> getApplicationInfo(ServerWebExch
return Mono.just(applicationInfoService.getApplicationInfo()).map(ResponseEntity::ok);
}

@Override
public Mono<ResponseEntity<AppAuthenticationSettingsDTO>> getAuthenticationSettings(
ServerWebExchange exchange) {
return Mono.just(applicationInfoService.getAuthenticationProperties())
.map(ResponseEntity::ok);
}

@Override
public Mono<ResponseEntity<ApplicationConfigDTO>> getCurrentConfig(ServerWebExchange exchange) {
var context = AccessContext.builder()
@@ -109,7 +117,7 @@ public Mono<ResponseEntity<UploadedFileInfoDTO>> uploadConfigRelatedFile(Flux<Pa
.then(fileFlux.single())
.flatMap(file ->
dynamicConfigOperations.uploadConfigRelatedFile((FilePart) file)
.map(path -> new UploadedFileInfoDTO().location(path.toString()))
.map(path -> new UploadedFileInfoDTO(path.toString()))
.map(ResponseEntity::ok))
.doOnEach(sig -> audit(context, sig));
}
Original file line number Diff line number Diff line change
@@ -13,13 +13,13 @@
@RestController
@RequiredArgsConstructor
@Slf4j
public class AuthController {
public class AuthenticationController {

@GetMapping(value = "/auth", produces = {"text/html"})
public Mono<byte[]> getAuth(ServerWebExchange exchange) {
Mono<CsrfToken> token = exchange.getAttributeOrDefault(CsrfToken.class.getName(), Mono.empty());
return token
.map(AuthController::csrfToken)
.map(AuthenticationController::csrfToken)
.defaultIfEmpty("")
.map(csrfTokenHtmlInput -> createPage(exchange, csrfTokenHtmlInput));
}
Original file line number Diff line number Diff line change
@@ -26,7 +26,7 @@
@RestController
@RequiredArgsConstructor
@Slf4j
public class AccessController implements AuthorizationApi {
public class AuthorizationController implements AuthorizationApi {

private final AccessControlService accessControlService;

46 changes: 46 additions & 0 deletions api/src/main/java/io/kafbat/ui/service/ApplicationInfoService.java
Original file line number Diff line number Diff line change
@@ -1,37 +1,51 @@
package io.kafbat.ui.service;

import static io.kafbat.ui.api.model.AuthType.DISABLED;
import static io.kafbat.ui.api.model.AuthType.OAUTH2;
import static io.kafbat.ui.model.ApplicationInfoDTO.EnabledFeaturesEnum;
import static io.kafbat.ui.util.GithubReleaseInfo.GITHUB_RELEASE_INFO_TIMEOUT;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.Streams;
import io.kafbat.ui.model.AppAuthenticationSettingsDTO;
import io.kafbat.ui.model.ApplicationInfoBuildDTO;
import io.kafbat.ui.model.ApplicationInfoDTO;
import io.kafbat.ui.model.ApplicationInfoLatestReleaseDTO;
import io.kafbat.ui.model.AuthTypeDTO;
import io.kafbat.ui.model.OAuthProviderDTO;
import io.kafbat.ui.util.DynamicConfigOperations;
import io.kafbat.ui.util.GithubReleaseInfo;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.Properties;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.info.BuildProperties;
import org.springframework.boot.info.GitProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.core.ResolvableType;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.stereotype.Service;

@Service
public class ApplicationInfoService {
private final GithubReleaseInfo githubReleaseInfo;
private final ApplicationContext applicationContext;
private final DynamicConfigOperations dynamicConfigOperations;
private final BuildProperties buildProperties;
private final GitProperties gitProperties;

public ApplicationInfoService(DynamicConfigOperations dynamicConfigOperations,
ApplicationContext applicationContext,
@Autowired(required = false) BuildProperties buildProperties,
@Autowired(required = false) GitProperties gitProperties,
@Value("${" + GITHUB_RELEASE_INFO_TIMEOUT + ":10}") int githubApiMaxWaitTime) {
this.applicationContext = applicationContext;
this.dynamicConfigOperations = dynamicConfigOperations;
this.buildProperties = Optional.ofNullable(buildProperties).orElse(new BuildProperties(new Properties()));
this.gitProperties = Optional.ofNullable(gitProperties).orElse(new GitProperties(new Properties()));
@@ -70,6 +84,38 @@ private List<EnabledFeaturesEnum> getEnabledFeatures() {
return enabledFeatures;
}

public AppAuthenticationSettingsDTO getAuthenticationProperties() {
return new AppAuthenticationSettingsDTO()
.authType(AuthTypeDTO.fromValue(getAuthType()))
.oAuthProviders(getOAuthProviders());
}

private String getAuthType() {
return Optional.ofNullable(applicationContext.getEnvironment().getProperty("auth.type"))
.orElse(DISABLED.getValue());
}

@SuppressWarnings("unchecked")
private List<OAuthProviderDTO> getOAuthProviders() {
if (!getAuthType().equalsIgnoreCase(OAUTH2.getValue())) {
return Collections.emptyList();
}
var type = ResolvableType.forClassWithGenerics(Iterable.class, ClientRegistration.class);
String[] names = this.applicationContext.getBeanNamesForType(type);
var bean = (Iterable<ClientRegistration>) (names.length == 1 ? this.applicationContext.getBean(names[0]) : null);

if (bean == null) {
return Collections.emptyList();
}

return Streams.stream(bean.iterator())
.filter(r -> AuthorizationGrantType.AUTHORIZATION_CODE.equals(r.getAuthorizationGrantType()))
.map(r -> new OAuthProviderDTO()
.clientName(r.getClientName())
.authorizationUri("/oauth2/authorization/" + r.getRegistrationId()))
.toList();
}

// updating on startup and every hour
@Scheduled(fixedRateString = "${github-release-info-update-rate:3600000}")
public void updateGithubReleaseInfo() {
42 changes: 41 additions & 1 deletion contract/src/main/resources/swagger/kafbat-ui-api.yaml
Original file line number Diff line number Diff line change
@@ -2150,7 +2150,7 @@ paths:
get:
tags:
- Authorization
summary: Get user authentication related info
summary: Get user authorization related info
operationId: getUserAuthInfo
responses:
200:
@@ -2244,6 +2244,20 @@ paths:
schema:
$ref: '#/components/schemas/UploadedFileInfo'

/api/config/authentication:
get:
tags:
- ApplicationConfig
summary: Get authentication methods enabled for the app and other related settings
operationId: getAuthenticationSettings
responses:
200:
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/AppAuthenticationSettings'

components:
schemas:
TopicSerdeSuggestion:
@@ -2354,6 +2368,32 @@ components:
htmlUrl:
type: string

AppAuthenticationSettings:
type: object
properties:
authType:
$ref: '#/components/schemas/AuthType'
oAuthProviders:
type: array
items:
$ref: '#/components/schemas/OAuthProvider'

OAuthProvider:
type: object
properties:
clientName:
type: string
authorizationUri:
type: string

AuthType:
type: string
enum:
- DISABLED
- OAUTH2
- LOGIN_FORM
- LDAP

Cluster:
type: object
properties:
Loading