Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
e1e189d
Added ops command to get and update sso configuration
aji-aju Jan 27, 2026
7743ca3
Removed unwanted fields from ldap, Added enhanced validations for ldap
aji-aju Jan 27, 2026
02e9bfb
Doc change for ldap
aji-aju Jan 27, 2026
db7b9f3
Hided unwanted fields from saml configuration
aji-aju Jan 27, 2026
37c7167
Hided unwanted fields from oidc
aji-aju Jan 28, 2026
beb0777
Auto populate public key url for confidential clients
aji-aju Jan 28, 2026
6095421
Advanced config oidc, public kye url bug fix, saml acs ui corrected
aji-aju Jan 28, 2026
085adcc
Ldap state issue solved, user attribute id hidden
aji-aju Jan 28, 2026
d0248d5
public key url visible in public flow
aji-aju Jan 28, 2026
d242a6e
Update generated TypeScript types
github-actions[bot] Jan 28, 2026
0933a83
Merge branch 'main' into sso_issues_revisit
aji-aju Jan 29, 2026
a70b32c
Addressed githar bot review comments
aji-aju Jan 29, 2026
ce7bfc7
Added unit TCs
aji-aju Jan 29, 2026
b2fbda1
Add more unit TCs
aji-aju Jan 29, 2026
787b9f5
Sidebar doc improvements
aji-aju Jan 29, 2026
431107e
fix tests
chirag-madlani Jan 29, 2026
bf66181
update translation
chirag-madlani Jan 29, 2026
80b43f1
Address review comments: Move field array constants to SSO.constant.t…
Copilot Jan 29, 2026
823453c
Merge branch 'main' into sso_issues_revisit
aniketkatkar97 Jan 30, 2026
cbec6d3
Fix the form bugs and add playwright tests
aniketkatkar97 Jan 30, 2026
b528d77
Merge branch 'main' into sso_issues_revisit
aniketkatkar97 Jan 30, 2026
796e677
icon and styling fixes
aniketkatkar97 Jan 30, 2026
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

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@
import lombok.extern.slf4j.Slf4j;
import org.openmetadata.common.utils.CommonUtil;
import org.openmetadata.schema.api.search.SearchSettings;
import org.openmetadata.schema.api.security.AuthenticationConfiguration;
import org.openmetadata.schema.auth.EmailRequest;
import org.openmetadata.schema.configuration.EntityRulesSettings;
import org.openmetadata.schema.configuration.SecurityConfiguration;
Expand Down Expand Up @@ -71,6 +72,7 @@
import org.openmetadata.service.secrets.masker.PasswordEntityMasker;
import org.openmetadata.service.security.Authorizer;
import org.openmetadata.service.security.JwtFilter;
import org.openmetadata.service.security.SecurityUtil;
import org.openmetadata.service.security.auth.SecurityConfigurationManager;
import org.openmetadata.service.security.policyevaluator.OperationContext;
import org.openmetadata.service.security.policyevaluator.ResourceContext;
Expand Down Expand Up @@ -641,11 +643,14 @@ public Response updateSecurityConfig(
authorizer.authorizeAdmin(securityContext);

try {
AuthenticationConfiguration authConfig = securityConfig.getAuthenticationConfiguration();

// Auto-populate publicKeyUrls for OIDC confidential clients before saving
systemRepository.autoPopulatePublicKeyUrlsIfNeeded(authConfig);

// Update both configurations in a transaction
Settings authSettings =
new Settings()
.withConfigType(AUTHENTICATION_CONFIGURATION)
.withConfigValue(securityConfig.getAuthenticationConfiguration());
new Settings().withConfigType(AUTHENTICATION_CONFIGURATION).withConfigValue(authConfig);

Settings authzSettings =
new Settings()
Expand Down Expand Up @@ -710,8 +715,10 @@ public Response patchSecurityConfig(
SecurityConfiguration updatedConfig =
JsonUtils.readValue(jsonString, SecurityConfiguration.class);

String currentUsername = SecurityUtil.getUserName(securityContext);
SecurityValidationResponse validationResponse =
systemRepository.validateSecurityConfiguration(updatedConfig, applicationConfig);
systemRepository.validateSecurityConfiguration(
updatedConfig, applicationConfig, currentUsername);

boolean isValidConfig =
validationResponse.getStatus() == SecurityValidationResponse.Status.SUCCESS;
Expand Down Expand Up @@ -775,7 +782,9 @@ public Response patchSecurityConfig(
public SecurityValidationResponse validateSecurityConfig(
@Context SecurityContext securityContext, @Valid SecurityConfiguration securityConfig) {
authorizer.authorizeAdmin(securityContext);
return systemRepository.validateSecurityConfiguration(securityConfig, applicationConfig);
String currentUsername = SecurityUtil.getUserName(securityContext);
return systemRepository.validateSecurityConfiguration(
securityConfig, applicationConfig, currentUsername);
}

@GET
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.openmetadata.schema.api.security.ClientType;
import org.openmetadata.schema.configuration.SecurityConfiguration;
import org.openmetadata.schema.services.connections.metadata.AuthProvider;
import org.openmetadata.service.Entity;
import org.openmetadata.service.OpenMetadataApplication;
import org.openmetadata.service.OpenMetadataApplicationConfig;
import org.openmetadata.service.exception.AuthenticationException;
Expand Down Expand Up @@ -94,6 +95,13 @@ public void initialize(
}

public SecurityConfiguration getCurrentSecurityConfig() {
// Apply LDAP default values before returning to prevent JSON PATCH errors
// when updating fields that were previously null in the database
if (currentAuthConfig != null && currentAuthConfig.getLdapConfiguration() != null) {
Entity.getSystemRepository()
.ensureLdapConfigDefaultValues(currentAuthConfig.getLdapConfiguration());
}

return new SecurityConfiguration()
.withAuthenticationConfiguration(currentAuthConfig)
.withAuthorizerConfiguration(currentAuthzConfig);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -285,10 +285,11 @@ private FieldError validatePublicKeyUrls(
AuthenticationConfiguration authConfig, String auth0Domain) {
try {
List<String> publicKeyUrls = authConfig.getPublicKeyUrls();
// Skip validation if publicKeyUrls is empty - it's auto-populated for confidential clients
if (publicKeyUrls == null || publicKeyUrls.isEmpty()) {
return ValidationErrorBuilder.createFieldError(
ValidationErrorBuilder.FieldPaths.AUTH_PUBLIC_KEY_URLS,
"Public key URLs are required for Auth0 clients");
LOG.debug(
"publicKeyUrls is empty, skipping validation (auto-populated for confidential clients)");
return null;
}

String expectedJwksUrl = auth0Domain + "/.well-known/jwks.json";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ private FieldError validateAzureConfidentialClient(
return tenantValidation;
}

// Then validate against the discovery document
// Validate against the discovery document
FieldError discoveryCheck =
discoveryValidator.validateAgainstDiscovery(discoveryUri, authConfig, oidcConfig);
if (discoveryCheck != null) {
Expand Down Expand Up @@ -357,9 +357,11 @@ private FieldError validatePublicKeyUrls(
AuthenticationConfiguration authConfig, String tenantId) {
try {
List<String> publicKeyUrls = authConfig.getPublicKeyUrls();
// Skip validation if publicKeyUrls is empty - it's auto-populated for confidential clients
if (publicKeyUrls == null || publicKeyUrls.isEmpty()) {
return ValidationErrorBuilder.createFieldError(
ValidationErrorBuilder.FieldPaths.AUTH_PUBLIC_KEY_URLS, "Public key urls are required");
LOG.debug(
"publicKeyUrls is empty, skipping validation (auto-populated for confidential clients)");
return null;
}

String expectedJwksUrl = AZURE_LOGIN_BASE + "/" + tenantId + "/discovery/v2.0/keys";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -223,10 +223,11 @@ private FieldError validatePublicKeyUrls(
AuthenticationConfiguration authConfig, CognitoDetails cognitoDetails) {
try {
List<String> publicKeyUrls = authConfig.getPublicKeyUrls();
// Skip validation if publicKeyUrls is empty - it's auto-populated for confidential clients
if (publicKeyUrls == null || publicKeyUrls.isEmpty()) {
return ValidationErrorBuilder.createFieldError(
ValidationErrorBuilder.FieldPaths.AUTH_PUBLIC_KEY_URLS,
"Public key URLs are required for Cognito clients");
LOG.debug(
"publicKeyUrls is empty, skipping validation (auto-populated for confidential clients)");
return null;
}

String expectedJwksUri =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -221,10 +221,11 @@ private FieldError validateJwksEndpoint(String jwksUri, AuthenticationConfigurat

// Verify publicKeyUrls is configured and contains the JWKS URI
List<String> publicKeyUrls = authConfig.getPublicKeyUrls();
// Skip validation if publicKeyUrls is empty - it's auto-populated for confidential clients
if (publicKeyUrls == null || publicKeyUrls.isEmpty()) {
return ValidationErrorBuilder.createFieldError(
ValidationErrorBuilder.FieldPaths.AUTH_PUBLIC_KEY_URLS,
"publicKeyUrls is required. Please configure it with the JWKS URI: " + jwksUri);
LOG.debug(
"publicKeyUrls is empty, skipping validation (auto-populated for confidential clients)");
return null;
}

// Check if the JWKS URI from discovery is in publicKeyUrls
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
Expand Down Expand Up @@ -325,4 +326,51 @@ private String determineFieldPathFromErrors(List<String> errors) {
// Default to discovery URI for general OIDC configuration errors
return ValidationErrorBuilder.FieldPaths.OIDC_DISCOVERY_URI;
}

/**
* Auto-populates publicKeyUrls from OIDC discovery document if not already set
*
* @param discoveryUri The OIDC discovery URI
* @param authConfig The authentication configuration to populate
* @throws Exception if discovery document cannot be fetched or parsed
*/
public void autoPopulatePublicKeyUrls(String discoveryUri, AuthenticationConfiguration authConfig)
throws Exception {

// Skip if publicKeyUrls already populated
if (authConfig.getPublicKeyUrls() != null && !authConfig.getPublicKeyUrls().isEmpty()) {
LOG.debug("publicKeyUrls already set, skipping auto-population");
return;
}

if (nullOrEmpty(discoveryUri)) {
throw new IOException("Discovery URI is required for auto-populating publicKeyUrls");
}

// Fetch discovery document
ValidationHttpUtil.HttpResponseData response = ValidationHttpUtil.safeGet(discoveryUri);
if (response.getStatusCode() != 200) {
throw new IOException(
"Failed to fetch discovery document from: "
+ discoveryUri
+ " (HTTP "
+ response.getStatusCode()
+ ")");
}

// Parse and extract jwks_uri
JsonNode discoveryDoc = OBJECT_MAPPER.readTree(response.getBody());
if (!discoveryDoc.has("jwks_uri")) {
throw new IOException("Discovery document missing required 'jwks_uri' field");
}

String jwksUri = discoveryDoc.get("jwks_uri").asText();
if (nullOrEmpty(jwksUri)) {
throw new IOException("Discovery document contains empty 'jwks_uri' field");
}

// Auto-populate publicKeyUrls
authConfig.setPublicKeyUrls(List.of(jwksUri));
LOG.debug("Auto-populated publicKeyUrls from discovery document: {}", jwksUri);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,6 @@ private FieldError validateOktaConfidentialClient(
}

// Step 3: Validate against OIDC discovery document (scopes, response types, etc.)
// String discoveryUri = oktaDomain + OKTA_WELL_KNOWN_PATH;
FieldError discoveryCheck =
discoveryValidator.validateAgainstDiscovery(
oidcConfig.getDiscoveryUri(), authConfig, oidcConfig);
Expand Down Expand Up @@ -234,8 +233,11 @@ private FieldError validatePublicKeyUrls(
AuthenticationConfiguration authConfig, String oktaDomain, @Nullable String discoveryUri) {
try {
List<String> publicKeyUrls = authConfig.getPublicKeyUrls();
// Skip validation if publicKeyUrls is empty - it's auto-populated for confidential clients
if (publicKeyUrls == null || publicKeyUrls.isEmpty()) {
throw new IllegalArgumentException("Public key URLs are required");
LOG.debug(
"publicKeyUrls is empty, skipping validation (auto-populated for confidential clients)");
return null;
}

// Determine expected JWKS URL based on client type
Expand Down
Loading
Loading