From ee3995c9bf06729689036ecd8554adc6841fc8b6 Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Thu, 2 Apr 2026 09:03:50 +0200 Subject: [PATCH 01/12] [bcakend/frontend] Fix localStrategy on force env (#15311) --- .../sso_definitions/SSODefinitions.tsx | 39 ++++---- .../src/domain/setting-auth.ts | 96 +++++++++++++++++++ .../opencti-graphql/src/domain/settings.js | 91 +----------------- .../opencti-graphql/src/http/httpPlatform.js | 6 +- .../authenticationProvider-migration.ts | 14 +-- .../providers-configuration.ts | 27 +++++- .../providers-env-deprecated.js | 3 + .../authenticationProvider/providers.ts | 50 ++++++++-- .../opencti-graphql/src/resolvers/settings.js | 4 +- .../opencti-graphql/src/types/settings.d.ts | 1 + .../authenticationProvider/providers-test.ts | 91 ++++++++++++++++++ 11 files changed, 292 insertions(+), 130 deletions(-) create mode 100644 opencti-platform/opencti-graphql/src/domain/setting-auth.ts create mode 100644 opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts diff --git a/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/SSODefinitions.tsx b/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/SSODefinitions.tsx index 807915c38369..e46cec0fe569 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/SSODefinitions.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/SSODefinitions.tsx @@ -326,24 +326,27 @@ const SSODefinitions = () => { { label: t_i18n('Authentications'), current: true }]} /> {settings.is_authentication_by_env && ( - - {t_i18n('Deprecated — Authentication management is disabled by environment configuration')} - - {t_i18n('Your platform is running with the legacy authentication configuration defined through environment variables. This safeguard was enabled in your configuration because the authentication migration to the new v7 model encountered issues that needed to be resolved first.')} - - - {t_i18n('This compatibility mode is deprecated and will be permanently removed in the next major version of OpenCTI.')}{' '} - {t_i18n('Once removed, the platform will no longer be able to start with this configuration, and authentication providers will have to be properly migrated.')} - - - {t_i18n('While this safeguard is active, authentication providers cannot be managed from this interface. The platform continues to operate with the previous environment-based implementation.')} - - - {t_i18n('To resolve this situation before the next version, please')}{' '} - {t_i18n('contact the Filigran team')}{' '} - {t_i18n('so they can assist you with the migration process.')} - - + <> + + + {t_i18n('Deprecated — Authentication management is disabled by environment configuration')} + + {t_i18n('Your platform is running with the legacy authentication configuration defined through environment variables. This safeguard was enabled in your configuration because the authentication migration to the new v7 model encountered issues that needed to be resolved first.')} + + + {t_i18n('This compatibility mode is deprecated and will be permanently removed in the next major version of OpenCTI.')}{' '} + {t_i18n('Once removed, the platform will no longer be able to start with this configuration, and authentication providers will have to be properly migrated.')} + + + {t_i18n('While this safeguard is active, authentication providers cannot be managed from this interface. The platform continues to operate with the previous environment-based implementation.')} + + + {t_i18n('To resolve this situation before the next version, please')}{' '} + {t_i18n('contact the Filigran team')}{' '} + {t_i18n('so they can assist you with the migration process.')} + + + )} {!settings.is_authentication_by_env && ( <> diff --git a/opencti-platform/opencti-graphql/src/domain/setting-auth.ts b/opencti-platform/opencti-graphql/src/domain/setting-auth.ts new file mode 100644 index 000000000000..bbc2b407c625 --- /dev/null +++ b/opencti-platform/opencti-graphql/src/domain/setting-auth.ts @@ -0,0 +1,96 @@ +// -- Built-in authentication strategy settings -- +// These mutations update the Settings entity AND trigger live re-registration +// of the corresponding authentication provider. + +import { patchAttribute } from '../database/middleware'; +import { publishUserAction } from '../listener/UserActionListener'; +import { CERT_PROVIDER } from '../modules/authenticationProvider/provider-cert'; +import { HEADERS_PROVIDER } from '../modules/authenticationProvider/provider-headers'; +import { LOCAL_PROVIDER } from '../modules/authenticationProvider/provider-local'; +import { AuthType, EnvStrategyType, isLocalAuthEnabled, PROVIDERS } from '../modules/authenticationProvider/providers-configuration'; +import { ENTITY_TYPE_SETTINGS } from '../schema/internalObject'; +import type { BasicStoreSettings } from '../types/settings'; +import type { AuthContext, AuthUser } from '../types/user'; +import { notify } from '../database/redis'; +import { BUS_TOPICS } from '../config/conf'; +import type { CertAuthConfigInput, HeadersAuthConfigInput, LocalAuthConfigInput } from '../generated/graphql'; + +export const buildAvailableProviders = async (platformSettings: BasicStoreSettings) => { + const availableProviders = [...PROVIDERS]; + if (isLocalAuthEnabled(platformSettings)) { + availableProviders.push({ + name: platformSettings.local_auth?.button_label_override || 'local', + type: AuthType.AUTH_FORM, + strategy: EnvStrategyType.STRATEGY_LOCAL, + provider: LOCAL_PROVIDER?.provider ?? '', + }); + } + if (platformSettings.cert_auth?.enabled) { + availableProviders.push({ + name: platformSettings.cert_auth?.button_label_override || 'cert', + type: AuthType.AUTH_SSO, + strategy: EnvStrategyType.STRATEGY_CERT, + provider: CERT_PROVIDER?.provider ?? '', + }); + } + if (platformSettings.headers_auth?.enabled) { + availableProviders.push({ + name: platformSettings.headers_auth?.button_label_override || 'headers', + type: AuthType.AUTH_SSO, + strategy: EnvStrategyType.STRATEGY_HEADER, + provider: HEADERS_PROVIDER?.provider ?? '', + }); + } + return availableProviders; +}; + +export const updateLocalAuth = async (context: AuthContext, user: AuthUser, settingsId: string, input: LocalAuthConfigInput) => { + const patch = { + local_auth: { enabled: input.enabled }, + ...(input.password_policy_min_length !== undefined && { password_policy_min_length: input.password_policy_min_length }), + ...(input.password_policy_max_length !== undefined && { password_policy_max_length: input.password_policy_max_length }), + ...(input.password_policy_min_symbols !== undefined && { password_policy_min_symbols: input.password_policy_min_symbols }), + ...(input.password_policy_min_numbers !== undefined && { password_policy_min_numbers: input.password_policy_min_numbers }), + ...(input.password_policy_min_words !== undefined && { password_policy_min_words: input.password_policy_min_words }), + ...(input.password_policy_min_lowercase !== undefined && { password_policy_min_lowercase: input.password_policy_min_lowercase }), + ...(input.password_policy_min_uppercase !== undefined && { password_policy_min_uppercase: input.password_policy_min_uppercase }), + }; + const { element } = await patchAttribute(context, user, settingsId, ENTITY_TYPE_SETTINGS, patch); + await publishUserAction({ + user, + event_type: 'mutation', + event_scope: 'update', + event_access: 'administration', + message: 'updates `local authentication settings` for `platform settings`', + context_data: { id: settingsId, entity_type: ENTITY_TYPE_SETTINGS, input: patch }, + }); + return notify(BUS_TOPICS[ENTITY_TYPE_SETTINGS].EDIT_TOPIC, element, user); +}; + +export const updateCertAuth = async (context: AuthContext, user: AuthUser, settingsId: string, input: CertAuthConfigInput) => { + const patch = { cert_auth: input }; + const { element } = await patchAttribute(context, user, settingsId, ENTITY_TYPE_SETTINGS, patch); + await publishUserAction({ + user, + event_type: 'mutation', + event_scope: 'update', + event_access: 'administration', + message: 'updates `cert authentication settings` for `platform settings`', + context_data: { id: settingsId, entity_type: ENTITY_TYPE_SETTINGS, input: patch }, + }); + return notify(BUS_TOPICS[ENTITY_TYPE_SETTINGS].EDIT_TOPIC, element, user); +}; + +export const updateHeaderAuth = async (context: AuthContext, user: AuthUser, settingsId: string, input: HeadersAuthConfigInput) => { + const patch = { headers_auth: input }; + const { element } = await patchAttribute(context, user, settingsId, ENTITY_TYPE_SETTINGS, patch); + await publishUserAction({ + user, + event_type: 'mutation', + event_scope: 'update', + event_access: 'administration', + message: 'updates `header authentication settings` for `platform settings`', + context_data: { id: settingsId, entity_type: ENTITY_TYPE_SETTINGS, input: patch }, + }); + return notify(BUS_TOPICS[ENTITY_TYPE_SETTINGS].EDIT_TOPIC, element, user); +}; diff --git a/opencti-platform/opencti-graphql/src/domain/settings.js b/opencti-platform/opencti-graphql/src/domain/settings.js index f09dafa8f8ef..cf934351ebef 100644 --- a/opencti-platform/opencti-graphql/src/domain/settings.js +++ b/opencti-platform/opencti-graphql/src/domain/settings.js @@ -20,10 +20,7 @@ import { getClusterInformation } from '../database/cluster-module'; import { completeXTMHubDataForRegistration } from '../utils/settings.helper'; import { XTM_ONE_CHATBOT_URL } from '../http/httpChatbotProxy'; import { findById as findThemeById } from '../modules/theme/theme-domain'; -import { LOCAL_PROVIDER } from '../modules/authenticationProvider/provider-local'; -import { AuthType, EnvStrategyType, PROVIDERS } from '../modules/authenticationProvider/providers-configuration'; -import { CERT_PROVIDER } from '../modules/authenticationProvider/provider-cert'; -import { HEADERS_PROVIDER } from '../modules/authenticationProvider/provider-headers'; +import { buildAvailableProviders } from './setting-auth'; export const getMemoryStatistics = () => { return { ...process.memoryUsage(), ...getHeapStatistics() }; @@ -104,37 +101,12 @@ export const getProtectedSensitiveConfig = async (context, user) => { }; }; -export const buildAvailableProviders = async (platformSettings) => { - const availableProviders = [...PROVIDERS]; - if (platformSettings.local_auth?.enabled) { - availableProviders.push({ - name: platformSettings.local_auth?.button_label_override || 'local', - type: AuthType.AUTH_FORM, - strategy: EnvStrategyType.STRATEGY_LOCAL, - provider: LOCAL_PROVIDER.provider, - }); - } - if (platformSettings.cert_auth?.enabled) { - availableProviders.push({ - name: platformSettings.cert_auth?.button_label_override || 'cert', - type: AuthType.AUTH_SSO, - strategy: EnvStrategyType.STRATEGY_CERT, - provider: CERT_PROVIDER.provider, - }); - } - if (platformSettings.headers_auth?.enabled) { - availableProviders.push({ - name: platformSettings.headers_auth?.button_label_override || 'headers', - type: AuthType.AUTH_SSO, - strategy: EnvStrategyType.STRATEGY_HEADER, - provider: HEADERS_PROVIDER.provider, - }); - } - return availableProviders; +export const getSettingsFromDatabase = async (context) => { + return await loadEntity(context, SYSTEM_USER, [ENTITY_TYPE_SETTINGS]); }; export const getSettings = async (context) => { - const platformSettings = await loadEntity(context, SYSTEM_USER, [ENTITY_TYPE_SETTINGS]); + const platformSettings = await getSettingsFromDatabase(context); const clusterInfo = await getClusterInformation(); const eeInfo = getEnterpriseEditionInfo(platformSettings); const platformTheme = await findThemeById(context, SYSTEM_USER, platformSettings.platform_theme); @@ -310,61 +282,6 @@ export const settingDeleteMessage = async (context, user, settingsId, messageId) return notify(BUS_TOPICS[ENTITY_TYPE_SETTINGS].EDIT_TOPIC, element, user); }; -// -- Built-in authentication strategy settings -- -// These mutations update the Settings entity AND trigger live re-registration -// of the corresponding authentication provider. - -export const updateLocalAuth = async (context, user, settingsId, input) => { - const patch = { - local_auth: { enabled: input.enabled }, - ...(input.password_policy_min_length !== undefined && { password_policy_min_length: input.password_policy_min_length }), - ...(input.password_policy_max_length !== undefined && { password_policy_max_length: input.password_policy_max_length }), - ...(input.password_policy_min_symbols !== undefined && { password_policy_min_symbols: input.password_policy_min_symbols }), - ...(input.password_policy_min_numbers !== undefined && { password_policy_min_numbers: input.password_policy_min_numbers }), - ...(input.password_policy_min_words !== undefined && { password_policy_min_words: input.password_policy_min_words }), - ...(input.password_policy_min_lowercase !== undefined && { password_policy_min_lowercase: input.password_policy_min_lowercase }), - ...(input.password_policy_min_uppercase !== undefined && { password_policy_min_uppercase: input.password_policy_min_uppercase }), - }; - const { element } = await patchAttribute(context, user, settingsId, ENTITY_TYPE_SETTINGS, patch); - await publishUserAction({ - user, - event_type: 'mutation', - event_scope: 'update', - event_access: 'administration', - message: 'updates `local authentication settings` for `platform settings`', - context_data: { id: settingsId, entity_type: ENTITY_TYPE_SETTINGS, input: patch }, - }); - return notify(BUS_TOPICS[ENTITY_TYPE_SETTINGS].EDIT_TOPIC, element, user); -}; - -export const updateCertAuth = async (context, user, settingsId, input) => { - const patch = { cert_auth: input }; - const { element } = await patchAttribute(context, user, settingsId, ENTITY_TYPE_SETTINGS, patch); - await publishUserAction({ - user, - event_type: 'mutation', - event_scope: 'update', - event_access: 'administration', - message: 'updates `cert authentication settings` for `platform settings`', - context_data: { id: settingsId, entity_type: ENTITY_TYPE_SETTINGS, input: patch }, - }); - return notify(BUS_TOPICS[ENTITY_TYPE_SETTINGS].EDIT_TOPIC, element, user); -}; - -export const updateHeaderAuth = async (context, user, settingsId, input) => { - const patch = { headers_auth: input }; - const { element } = await patchAttribute(context, user, settingsId, ENTITY_TYPE_SETTINGS, patch); - await publishUserAction({ - user, - event_type: 'mutation', - event_scope: 'update', - event_access: 'administration', - message: 'updates `header authentication settings` for `platform settings`', - context_data: { id: settingsId, entity_type: ENTITY_TYPE_SETTINGS, input: patch }, - }); - return notify(BUS_TOPICS[ENTITY_TYPE_SETTINGS].EDIT_TOPIC, element, user); -}; - export const getCriticalAlerts = async (context, user) => { // only 1 critical alert is checked: null confidence level on groups // it's for admins only (only them can take action) diff --git a/opencti-platform/opencti-graphql/src/http/httpPlatform.js b/opencti-platform/opencti-graphql/src/http/httpPlatform.js index 40ac6f1fdf1c..92db42736fc2 100644 --- a/opencti-platform/opencti-graphql/src/http/httpPlatform.js +++ b/opencti-platform/opencti-graphql/src/http/httpPlatform.js @@ -444,7 +444,7 @@ const createApp = async (app, schema) => { if (err) { const authLogger = strategy.logger; if (authLogger) { - authLogger.error('Callback processing error', {}, err); + authLogger.error('Callback processing error', { err }, err); } } setCookieError(res, err?.message); @@ -473,9 +473,9 @@ const createApp = async (app, schema) => { if (err || !user) { const authLogger = strategy.logger; if (authLogger) { - authLogger.error('Callback processing error', {}, err); + authLogger.error('Callback login error', { err }, err); } else { - logApp.error('Error auth provider callback', { cause: err, provider }); + logApp.error('Error auth provider login', { cause: err, provider }); } reject(err); } else { diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/authenticationProvider-migration.ts b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/authenticationProvider-migration.ts index 157e9e27a6fd..702ce6b46ea4 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/authenticationProvider-migration.ts +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/authenticationProvider-migration.ts @@ -12,15 +12,11 @@ import { convertAllSSOEnvProviders } from './authenticationProvider-migration-co import { addAuthenticationProvider, getAllIdentifiers, resolveProviderIdentifier } from './authenticationProvider-domain'; import { isUserHasCapability, SETTINGS_SET_ACCESSES } from '../../utils/access'; import { AuthRequired } from '../../config/errors'; -import { isAuthenticationProviderMigrated } from './providers-configuration'; +import { getProvidersFromEnvironment, isAuthenticationProviderMigrated, isLocalAuthEnabledInEnv } from './providers-configuration'; import nconf from 'nconf'; -import { getSettings, updateCertAuth, updateHeaderAuth, updateLocalAuth } from '../../domain/settings'; +import { getSettings } from '../../domain/settings'; import type { BasicStoreSettings } from '../../types/settings'; - -export const isLocalAuthEnabledInEnv = (envProviders: Record): boolean => { - const local = envProviders['local']; - return local?.config?.disabled !== true; -}; +import { updateCertAuth, updateHeaderAuth, updateLocalAuth } from '../../domain/setting-auth'; // --------------------------------------------------------------------------- // Provider type mapping @@ -69,7 +65,7 @@ const parseMappingStrings = (mapping: any) => { */ const migrateLocalAuthIfNeeded = async (context: AuthContext, user: AuthUser) => { const settings = await getSettings(context) as unknown as BasicStoreSettings; - const envConfigurations = nconf.get('providers') ?? {}; + const envConfigurations = getProvidersFromEnvironment() ?? {}; if (!settings.local_auth) { logApp.info('[SINGLETON-MIGRATION] local_auth is absent, creating with defaults'); await updateLocalAuth(context, user, settings.id, { enabled: isLocalAuthEnabledInEnv(envConfigurations) }); @@ -86,7 +82,7 @@ const migrateLocalAuthIfNeeded = async (context: AuthContext, user: AuthUser) => const migrateHeadersAuthIfNeeded = async (context: AuthContext, user: AuthUser) => { const settings = await getSettings(context) as unknown as BasicStoreSettings; if (!settings.headers_auth || !settings.headers_auth.button_label_override) { - const envConfigurations = nconf.get('providers') ?? {}; + const envConfigurations = getProvidersFromEnvironment() ?? {}; const certProvider: any | undefined = Object.values(envConfigurations).filter((pr: any) => pr.strategy === 'HeaderStrategy')?.[0]; const { config, enabled } = certProvider ?? {}; const groupsHeader = config?.groups_management?.groups_header; diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts index 4dbd52d8d72c..7340cd470fcd 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts @@ -4,12 +4,19 @@ import * as R from 'ramda'; import type { AuthenticationProviderType } from '../../generated/graphql'; import { addUserLoginCount } from '../../manager/telemetryManager'; import { logAuthError, logAuthInfo } from './providers-logger'; +import type { BasicStoreSettings } from '../../types/settings'; export const LOCAL_STRATEGY_IDENTIFIER = 'local'; export const HEADERS_STRATEGY_IDENTIFIER = 'headers'; export const CERT_STRATEGY_IDENTIFIER = 'cert'; -export const IS_AUTHENTICATION_FORCE_LOCAL = booleanConf('app:authentication:force_local', false); +// Force adding local AuthStrategy whatever env or database configuration is +const IS_AUTHENTICATION_FORCE_LOCAL = booleanConf('app:authentication:force_local', false); +export const isLocalAuthForcedEnabledFromEnv = () => { + return IS_AUTHENTICATION_FORCE_LOCAL; +}; + +// Force usage of providers configuration from environment const IS_AUTHENTICATION_FORCE_FROM_ENV = booleanConf('app:authentication:force_env', false); export const isAuthenticationForcedFromEnv = () => { return IS_AUTHENTICATION_FORCE_FROM_ENV; @@ -124,3 +131,21 @@ const CONFIGURATION_ADMIN_EXT = booleanConf('app:admin:externally_managed', fals export const isAdminExternallyManaged = () => { return CONFIGURATION_ADMIN_EXT; }; + +export const isLocalAuthEnabledInEnv = (envProviders: Record): boolean => { + const local = envProviders['local']; + return local?.config?.disabled !== true; +}; + +export const isLocalAuthEnabled = (platformSettings: BasicStoreSettings) => { + if (isLocalAuthForcedEnabledFromEnv()) { + return true; + } else { + if (isAuthenticationForcedFromEnv()) { + const envConfigurations = getProvidersFromEnvironment() ?? {}; + return isLocalAuthEnabledInEnv(envConfigurations); + } else { + return platformSettings.local_auth?.enabled; + } + } +}; diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-env-deprecated.js b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-env-deprecated.js index 889793769ba9..b2b96c7ccd5c 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-env-deprecated.js +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-env-deprecated.js @@ -113,6 +113,9 @@ export const initializeEnvAuthenticationProviders = async () => { const mappedConfig = configRemapping(config); if (config === undefined || !config.disabled) { const providerName = config?.label || providerIdent; + // SINGLETON + // DO we put back header and cert here ?? + // FORM Strategies if (strategy === EnvStrategyType.STRATEGY_LDAP) { const providerRef = identifier || 'ldapauth'; diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts index 15f6e8717995..b2df5dfd6e4a 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts @@ -11,7 +11,7 @@ import { initializeEnvAuthenticationProviders, registerAuthenticationProvider, u import { createSAMLStrategy } from './provider-saml'; import { createLDAPStrategy } from './provider-ldap'; import { createOpenIdStrategy } from './provider-oidc'; -import { IS_AUTHENTICATION_FORCE_LOCAL, isAuthenticationForcedFromEnv } from './providers-configuration'; +import { getProvidersFromEnvironment, isAuthenticationForcedFromEnv, isLocalAuthEnabledInEnv, isLocalAuthForcedEnabledFromEnv } from './providers-configuration'; import { PROVIDERS } from './providers-configuration'; import type { AuthContext, AuthUser } from '../../types/user'; import { findAllAuthenticationProvider, resolveProviderIdentifier } from './authenticationProvider-domain'; @@ -22,12 +22,13 @@ import { loginFromProvider } from '../../domain/user'; import { addUserLoginCount } from '../../manager/telemetryManager'; import { isEnterpriseEdition } from '../../enterprise-edition/ee'; import conf, { logApp } from '../../config/conf'; -import { getSettings, updateLocalAuth } from '../../domain/settings'; +import { getSettings, getSettingsFromDatabase } from '../../domain/settings'; import { getEnterpriseEditionInfo } from '../settings/licensing'; import type { BasicStoreSettings } from '../../types/settings'; import { runAuthenticationProviderMigration } from './authenticationProvider-migration'; import { registerCertStrategy } from './provider-cert'; import { elDeleteElements } from '../../database/engine'; +import { updateLocalAuth } from '../../domain/setting-auth'; export interface ProviderAuthInfo { userMapping: { @@ -139,7 +140,7 @@ export const registerStrategy = async (authenticationProvider: BasicStoreEntityA } logger.success('Provider started successfully'); } catch (e) { - logger.error('Provider failed to start', {}, e); + logger.error('Provider failed to start', { err: e }, e); } finally { startingProviders.delete(authenticationProvider.internal_id); } @@ -159,10 +160,8 @@ export const initDatabaseAuthenticationProviders = async (context: AuthContext, }; export const initializeAuthenticationProviders = async (context: AuthContext) => { - // Singleton strategies: always register - await registerLocalStrategy(); - await registerCertStrategy(); - await registerHeadersStrategy(context); + const settings = await getSettingsFromDatabase(context) as unknown as BasicStoreSettings; + // In force env // Settings must be aligned on env definition // AuthenticationProviders must be deleted from the database @@ -172,13 +171,41 @@ export const initializeAuthenticationProviders = async (context: AuthContext) => // Cleanup providers from database const authenticators = await findAllAuthenticationProvider(context, SYSTEM_USER); await elDeleteElements(context, SYSTEM_USER, authenticators, { forceDelete: true, forceRefresh: true }); + + // First manage local + const confProviders = getProvidersFromEnvironment(); + + // First Local singleton provider + if (isLocalAuthEnabledInEnv(confProviders)) { + await registerLocalStrategy(); + await updateLocalAuth(context, SYSTEM_USER, settings.id, { enabled: true }); + } else { + await updateLocalAuth(context, SYSTEM_USER, settings.id, { enabled: false }); + } + + // TODO what about cert signleton, here on in next method ?? + // For now keeping existing code but it's buggy IMO + await registerCertStrategy(); + await registerHeadersStrategy(context); + // Init providers from env await initializeEnvAuthenticationProviders(); } else { // Migration first (already created will be not replayed) await runAuthenticationProviderMigration(context, SYSTEM_USER); // In standard mode, init from providers in the database - // Singleton already initialized + // Singleton initialization + if (settings.local_auth?.enabled === true) { + await registerLocalStrategy(); + } + + if (settings.cert_auth?.enabled === true) { + await registerCertStrategy(); + } + + if (settings.headers_auth?.enabled === true) { + await registerHeadersStrategy(context); + } await initDatabaseAuthenticationProviders(context, SYSTEM_USER); } // Safety net: force local_auth enabled when no other provider is available @@ -189,9 +216,14 @@ export const initializeAuthenticationProviders = async (context: AuthContext) => const hasCert = finalSettings.cert_auth?.enabled === true && eeActive && isHttpsEnabled; const hasHeader = finalSettings.headers_auth?.enabled === true && eeActive; const dbProviders = await findAllAuthenticationProvider(context, SYSTEM_USER); + const hasEnvProviders = PROVIDERS.length > 0; const hasDbProvider = eeActive && dbProviders.some((p) => p.enabled); - if (IS_AUTHENTICATION_FORCE_LOCAL || (!hasCert && !hasHeader && !hasDbProvider)) { + + const hasSSOProviders = ((isAuthenticationForcedFromEnv() && hasEnvProviders) || (!isAuthenticationForcedFromEnv() && hasDbProvider)); + + if (isLocalAuthForcedEnabledFromEnv() || (!hasCert && !hasHeader && !hasSSOProviders)) { logApp.warn('[MIGRATION-SAFETY] No other provider available, forcing local_auth to enabled'); + await registerLocalStrategy(); await updateLocalAuth(context, SYSTEM_USER, finalSettings.id, { enabled: true }); } } diff --git a/opencti-platform/opencti-graphql/src/resolvers/settings.js b/opencti-platform/opencti-graphql/src/resolvers/settings.js index 625c0c9ff796..e38ccb032c92 100644 --- a/opencti-platform/opencti-graphql/src/resolvers/settings.js +++ b/opencti-platform/opencti-graphql/src/resolvers/settings.js @@ -15,9 +15,6 @@ import { settingsCleanContext, settingsEditContext, settingsEditField, - updateLocalAuth, - updateCertAuth, - updateHeaderAuth, } from '../domain/settings'; import { fetchEditContext } from '../database/redis'; import { subscribeToInstanceEvents, subscribeToPlatformSettingsEvents } from '../graphql/subscriptionWrapper'; @@ -32,6 +29,7 @@ import { CguStatus, PlatformType } from '../generated/graphql'; import { getEntityMetricsConfiguration } from '../modules/metrics/metrics-utils'; import { ALLOW_EMAIL_REWRITE, smtpConfiguredEmail } from '../database/smtp'; import { isAuthenticationForcedFromEnv } from '../modules/authenticationProvider/providers-configuration'; +import { updateCertAuth, updateHeaderAuth, updateLocalAuth } from '../domain/setting-auth'; const settingsResolvers = { Query: { diff --git a/opencti-platform/opencti-graphql/src/types/settings.d.ts b/opencti-platform/opencti-graphql/src/types/settings.d.ts index 1bf18956083b..1b7b494a1761 100644 --- a/opencti-platform/opencti-graphql/src/types/settings.d.ts +++ b/opencti-platform/opencti-graphql/src/types/settings.d.ts @@ -22,6 +22,7 @@ export type CertAuthConfig = { export type LocalAuthConfig = { enabled: boolean; + button_label_override: string; }; export type HeadersAuthConfig = { diff --git a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts new file mode 100644 index 000000000000..5580f1ceff28 --- /dev/null +++ b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts @@ -0,0 +1,91 @@ +import { describe, it, vi, expect } from 'vitest'; +import { initializeAuthenticationProviders } from '../../../../src/modules/authenticationProvider/providers'; +import { testContext } from '../../../utils/testQuery'; +import type { BasicStoreSettings } from '../../../../src/types/settings'; +import * as mockProviderEnv from '../../../../src/modules/authenticationProvider/providers-configuration'; +import { getSettings } from '../../../../src/domain/settings'; +import { buildAvailableProviders } from '../../../../src/domain/setting-auth'; +import { PROVIDERS } from '../../../../src/modules/authenticationProvider/providers-configuration'; + +describe('initializeAuthenticationProviders coverage', () => { + const clearProviderArray = () => { + const len = PROVIDERS.length; + for (let i = 0; i < len; i++) { + PROVIDERS.pop(); + } + }; + + it('should force env & local disabled along with a strategy be correct', async () => { + // GIVEN a force env, and a configuration with a local disabled and an OpenID configured + clearProviderArray(); + + vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); + vi.spyOn(mockProviderEnv, 'getProvidersFromEnvironment').mockReturnValue({ + local: { + strategy: 'LocalStrategy', + config: { + disabled: true, + }, + }, oick: { + identifier: 'oick', + strategy: 'OpenIDConnectStrategy', + enabled: true, + config: { + issuer: 'http://localhost:9999/realms/master', + client_id: 'openctioid', + client_secret: 'xxxxxxxxxxxxx', + redirect_uris: ['http://localhost:4000/auth/oick/callback'], + }, + }, + }); + + // WHEN initializing providers + await initializeAuthenticationProviders(testContext); + + const finalSettings = await getSettings(testContext) as unknown as BasicStoreSettings; + const settingsProviders = await buildAvailableProviders(finalSettings); + + // THEN + // Should have only the OpenID, and no local since local is disabled in env + expect(settingsProviders).toBe([ + { + logout_remote: undefined, + name: 'oick', + provider: 'oick', + strategy: 'OpenIDConnectStrategy', + type: 'SSO', + }, + ]); + }); + + it('should force env & local disabled with no strategy still register local', async () => { + // GIVEN a force env, and a configuration with only a local disabled + clearProviderArray(); + vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); + vi.spyOn(mockProviderEnv, 'getProvidersFromEnvironment').mockReturnValue({ + local: { + strategy: 'LocalStrategy', + config: { + disabled: true, + }, + }, + }); + + // WHEN initializing providers + await initializeAuthenticationProviders(testContext); + + // THEN + // Should have only the OpenID, and no local since local is disabled in env + const finalSettings = await getSettings(testContext) as unknown as BasicStoreSettings; + const settingsProviders = await buildAvailableProviders(finalSettings); + + expect(settingsProviders).toBe([ + { + name: 'local', + provider: 'local', + strategy: 'LocalStrategy', + type: 'FORM', + }, + ]); + }); +}); From 9901c46cd4f1ceb30995c3e51ddf13c88a8fc043 Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Thu, 2 Apr 2026 09:12:31 +0200 Subject: [PATCH 02/12] With tests green it's better --- .../10-modules/authenticationProvider/providers-test.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts index 5580f1ceff28..5ca430e2a52f 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts @@ -47,7 +47,7 @@ describe('initializeAuthenticationProviders coverage', () => { // THEN // Should have only the OpenID, and no local since local is disabled in env - expect(settingsProviders).toBe([ + expect(settingsProviders).toStrictEqual([ { logout_remote: undefined, name: 'oick', @@ -79,7 +79,7 @@ describe('initializeAuthenticationProviders coverage', () => { const finalSettings = await getSettings(testContext) as unknown as BasicStoreSettings; const settingsProviders = await buildAvailableProviders(finalSettings); - expect(settingsProviders).toBe([ + expect(settingsProviders).toStrictEqual([ { name: 'local', provider: 'local', From 9b1802dfd9493b38ef6b8fbaefb2b3579ed1c67a Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Thu, 2 Apr 2026 09:49:55 +0200 Subject: [PATCH 03/12] I missed an import change --- .../components/settings/sso_definitions/SSODefinitions.tsx | 1 + .../authenticationProvider-migration-test.ts | 2 +- .../10-modules/authenticationProvider/providers-test.ts | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/SSODefinitions.tsx b/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/SSODefinitions.tsx index e46cec0fe569..5f6529726b30 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/SSODefinitions.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/SSODefinitions.tsx @@ -328,6 +328,7 @@ const SSODefinitions = () => { {settings.is_authentication_by_env && ( <> + {t_i18n('Deprecated — Authentication management is disabled by environment configuration')} diff --git a/opencti-platform/opencti-graphql/tests/01-unit/modules/authenticationProvider/authenticationProvider-migration-test.ts b/opencti-platform/opencti-graphql/tests/01-unit/modules/authenticationProvider/authenticationProvider-migration-test.ts index 614a0ea0b7c7..27a68f4b55ea 100644 --- a/opencti-platform/opencti-graphql/tests/01-unit/modules/authenticationProvider/authenticationProvider-migration-test.ts +++ b/opencti-platform/opencti-graphql/tests/01-unit/modules/authenticationProvider/authenticationProvider-migration-test.ts @@ -1,5 +1,5 @@ import { describe, expect, it } from 'vitest'; -import { isLocalAuthEnabledInEnv } from '../../../../src/modules/authenticationProvider/authenticationProvider-migration'; +import { isLocalAuthEnabledInEnv } from '../../../../src/modules/authenticationProvider/providers-configuration'; // ========================================================================== // resolveLocalAuthEnabled diff --git a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts index 5ca430e2a52f..e90fb2ab5f51 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts @@ -8,7 +8,7 @@ import { buildAvailableProviders } from '../../../../src/domain/setting-auth'; import { PROVIDERS } from '../../../../src/modules/authenticationProvider/providers-configuration'; describe('initializeAuthenticationProviders coverage', () => { - const clearProviderArray = () => { + const clearEnvProviderArray = () => { const len = PROVIDERS.length; for (let i = 0; i < len; i++) { PROVIDERS.pop(); @@ -17,7 +17,7 @@ describe('initializeAuthenticationProviders coverage', () => { it('should force env & local disabled along with a strategy be correct', async () => { // GIVEN a force env, and a configuration with a local disabled and an OpenID configured - clearProviderArray(); + clearEnvProviderArray(); vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); vi.spyOn(mockProviderEnv, 'getProvidersFromEnvironment').mockReturnValue({ @@ -60,7 +60,7 @@ describe('initializeAuthenticationProviders coverage', () => { it('should force env & local disabled with no strategy still register local', async () => { // GIVEN a force env, and a configuration with only a local disabled - clearProviderArray(); + clearEnvProviderArray(); vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); vi.spyOn(mockProviderEnv, 'getProvidersFromEnvironment').mockReturnValue({ local: { From a635892fd466a3942cfddfee212ed8a07bae9e76 Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Thu, 2 Apr 2026 13:11:30 +0200 Subject: [PATCH 04/12] Update test and disable local toggle in UI --- .../sso_definitions/LocalStrategyForm.tsx | 9 +- .../src/domain/setting-auth.ts | 4 +- .../providers-configuration.ts | 13 - .../authenticationProvider/providers.ts | 2 +- .../authenticationProvider/providers-test.ts | 352 ++++++++++++++---- 5 files changed, 298 insertions(+), 82 deletions(-) diff --git a/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/LocalStrategyForm.tsx b/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/LocalStrategyForm.tsx index 7a60f3d99b77..e71155d4ec6b 100644 --- a/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/LocalStrategyForm.tsx +++ b/opencti-platform/opencti-front/src/private/components/settings/sso_definitions/LocalStrategyForm.tsx @@ -42,6 +42,7 @@ const localStrategyFormQuery = graphql` enabled } platform_https_enabled + is_authentication_by_env } } `; @@ -86,6 +87,7 @@ const LocalStrategyForm = ({ onCancel }: LocalStrategyFormProps) => { const theme = useTheme(); const data = useLazyLoadQuery(localStrategyFormQuery, {}); const settings = data.settings; + const isConfigurationFromEnv = settings.is_authentication_by_env ?? false; const [commitMutation] = useApiMutation( localStrategyFormMutation, @@ -159,8 +161,13 @@ const LocalStrategyForm = ({ onCancel }: LocalStrategyFormProps) => { type="checkbox" name="enabled" label={t_i18n('Enable local authentication')} - disabled={!canDisableLocal && initialValues.enabled} + disabled={isConfigurationFromEnv || (!canDisableLocal && initialValues.enabled)} /> + {isConfigurationFromEnv && ( + + + + )} {!canDisableLocal && initialValues.enabled && ( diff --git a/opencti-platform/opencti-graphql/src/domain/setting-auth.ts b/opencti-platform/opencti-graphql/src/domain/setting-auth.ts index bbc2b407c625..5193afc84e80 100644 --- a/opencti-platform/opencti-graphql/src/domain/setting-auth.ts +++ b/opencti-platform/opencti-graphql/src/domain/setting-auth.ts @@ -7,7 +7,7 @@ import { publishUserAction } from '../listener/UserActionListener'; import { CERT_PROVIDER } from '../modules/authenticationProvider/provider-cert'; import { HEADERS_PROVIDER } from '../modules/authenticationProvider/provider-headers'; import { LOCAL_PROVIDER } from '../modules/authenticationProvider/provider-local'; -import { AuthType, EnvStrategyType, isLocalAuthEnabled, PROVIDERS } from '../modules/authenticationProvider/providers-configuration'; +import { AuthType, EnvStrategyType, isLocalAuthForcedEnabledFromEnv, PROVIDERS } from '../modules/authenticationProvider/providers-configuration'; import { ENTITY_TYPE_SETTINGS } from '../schema/internalObject'; import type { BasicStoreSettings } from '../types/settings'; import type { AuthContext, AuthUser } from '../types/user'; @@ -17,7 +17,7 @@ import type { CertAuthConfigInput, HeadersAuthConfigInput, LocalAuthConfigInput export const buildAvailableProviders = async (platformSettings: BasicStoreSettings) => { const availableProviders = [...PROVIDERS]; - if (isLocalAuthEnabled(platformSettings)) { + if (platformSettings.local_auth?.enabled || isLocalAuthForcedEnabledFromEnv()) { availableProviders.push({ name: platformSettings.local_auth?.button_label_override || 'local', type: AuthType.AUTH_FORM, diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts index 7340cd470fcd..bf80fcbd9c3b 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts @@ -136,16 +136,3 @@ export const isLocalAuthEnabledInEnv = (envProviders: Record): bool const local = envProviders['local']; return local?.config?.disabled !== true; }; - -export const isLocalAuthEnabled = (platformSettings: BasicStoreSettings) => { - if (isLocalAuthForcedEnabledFromEnv()) { - return true; - } else { - if (isAuthenticationForcedFromEnv()) { - const envConfigurations = getProvidersFromEnvironment() ?? {}; - return isLocalAuthEnabledInEnv(envConfigurations); - } else { - return platformSettings.local_auth?.enabled; - } - } -}; diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts index b2df5dfd6e4a..2789b3e0b65b 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts @@ -175,7 +175,7 @@ export const initializeAuthenticationProviders = async (context: AuthContext) => // First manage local const confProviders = getProvidersFromEnvironment(); - // First Local singleton provider + // First Local singleton provider - updating settings with environment setup if (isLocalAuthEnabledInEnv(confProviders)) { await registerLocalStrategy(); await updateLocalAuth(context, SYSTEM_USER, settings.id, { enabled: true }); diff --git a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts index e90fb2ab5f51..c4f71921087a 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts @@ -1,91 +1,313 @@ -import { describe, it, vi, expect } from 'vitest'; +import { describe, it, vi, expect, beforeAll, afterAll } from 'vitest'; import { initializeAuthenticationProviders } from '../../../../src/modules/authenticationProvider/providers'; -import { testContext } from '../../../utils/testQuery'; +import { ADMIN_USER, testContext } from '../../../utils/testQuery'; import type { BasicStoreSettings } from '../../../../src/types/settings'; import * as mockProviderEnv from '../../../../src/modules/authenticationProvider/providers-configuration'; -import { getSettings } from '../../../../src/domain/settings'; -import { buildAvailableProviders } from '../../../../src/domain/setting-auth'; -import { PROVIDERS } from '../../../../src/modules/authenticationProvider/providers-configuration'; +import { getSettings, getSettingsFromDatabase } from '../../../../src/domain/settings'; +import { buildAvailableProviders, updateLocalAuth } from '../../../../src/domain/setting-auth'; +import { type ProviderConfiguration, PROVIDERS } from '../../../../src/modules/authenticationProvider/providers-configuration'; +import type { LocalAuthConfigInput } from '../../../../src/generated/graphql'; -describe('initializeAuthenticationProviders coverage', () => { - const clearEnvProviderArray = () => { +const clearEnvProviderArray = () => { + const len = PROVIDERS.length; + for (let i = 0; i < len; i++) { + PROVIDERS.pop(); + } +}; + +describe('Provider coverage', () => { + const PROVIDER_SAVE: ProviderConfiguration[] = []; + beforeAll(async () => { const len = PROVIDERS.length; for (let i = 0; i < len; i++) { - PROVIDERS.pop(); + PROVIDER_SAVE.push(PROVIDERS[i]); } - }; + }); - it('should force env & local disabled along with a strategy be correct', async () => { - // GIVEN a force env, and a configuration with a local disabled and an OpenID configured + afterAll(async () => { clearEnvProviderArray(); + const len = PROVIDER_SAVE.length; + for (let i = 0; i < len; i++) { + PROVIDERS.push(PROVIDER_SAVE[i]); + } + }); - vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); - vi.spyOn(mockProviderEnv, 'getProvidersFromEnvironment').mockReturnValue({ - local: { - strategy: 'LocalStrategy', - config: { - disabled: true, + describe('initializeAuthenticationProviders coverage', () => { + it('should force env & local disabled along with a strategy be correct', async () => { + // GIVEN a force env, and a configuration with a local disabled and an OpenID configured + clearEnvProviderArray(); + + vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); + vi.spyOn(mockProviderEnv, 'getProvidersFromEnvironment').mockReturnValue({ + local: { + strategy: 'LocalStrategy', + config: { + disabled: true, + }, + }, oick: { + identifier: 'oick', + strategy: 'OpenIDConnectStrategy', + enabled: true, + config: { + issuer: 'http://localhost:9999/realms/master', + client_id: 'openctioid', + client_secret: 'xxxxxxxxxxxxx', + redirect_uris: ['http://localhost:4000/auth/oick/callback'], + }, }, - }, oick: { - identifier: 'oick', - strategy: 'OpenIDConnectStrategy', - enabled: true, - config: { - issuer: 'http://localhost:9999/realms/master', - client_id: 'openctioid', - client_secret: 'xxxxxxxxxxxxx', - redirect_uris: ['http://localhost:4000/auth/oick/callback'], + }); + + // WHEN initializing providers + await initializeAuthenticationProviders(testContext); + + const finalSettings = await getSettings(testContext) as unknown as BasicStoreSettings; + const settingsProviders = await buildAvailableProviders(finalSettings); + + // THEN + // Should have only the OpenID, and no local since local is disabled in env + expect(settingsProviders).toStrictEqual([ + { + logout_remote: undefined, + name: 'oick', + provider: 'oick', + strategy: 'OpenIDConnectStrategy', + type: 'SSO', }, - }, + ]); }); - // WHEN initializing providers - await initializeAuthenticationProviders(testContext); + it('should force env & local disabled with no strategy still register local', async () => { + // GIVEN a force env, and a configuration with only a local disabled + clearEnvProviderArray(); + vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); + vi.spyOn(mockProviderEnv, 'getProvidersFromEnvironment').mockReturnValue({ + local: { + strategy: 'LocalStrategy', + config: { + disabled: true, + }, + }, + }); - const finalSettings = await getSettings(testContext) as unknown as BasicStoreSettings; - const settingsProviders = await buildAvailableProviders(finalSettings); + // WHEN initializing providers + await initializeAuthenticationProviders(testContext); - // THEN - // Should have only the OpenID, and no local since local is disabled in env - expect(settingsProviders).toStrictEqual([ - { - logout_remote: undefined, - name: 'oick', - provider: 'oick', - strategy: 'OpenIDConnectStrategy', - type: 'SSO', - }, - ]); + // THEN + // Should have only the OpenID, and no local since local is disabled in env + const finalSettings = await getSettings(testContext) as unknown as BasicStoreSettings; + const settingsProviders = await buildAvailableProviders(finalSettings); + + expect(settingsProviders).toStrictEqual([ + { + name: 'local', + provider: 'local', + strategy: 'LocalStrategy', + type: 'FORM', + }, + ]); + }); }); - it('should force env & local disabled with no strategy still register local', async () => { - // GIVEN a force env, and a configuration with only a local disabled - clearEnvProviderArray(); - vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); - vi.spyOn(mockProviderEnv, 'getProvidersFromEnvironment').mockReturnValue({ - local: { + describe('setting-auth file test coverage', () => { + it('should local auth enabled only work fine', async () => { + const settingsMock: Partial = { + local_auth: { + enabled: true, + button_label_override: 'testLocal', + }, + cert_auth: { + enabled: false, + description: '', + button_label_override: '', + user_info_mapping: { + email_expr: '', + name_expr: '', + }, + groups_mapping: { + default_groups: [], + groups_expr: [], + groups_mapping: [], + auto_create_groups: false, + prevent_default_groups: false, + }, + organizations_mapping: { + default_organizations: [], + organizations_expr: [], + organizations_mapping: [], + auto_create_organizations: false, + }, + }, + headers_auth: { + enabled: false, + description: '', + button_label_override: '', + user_info_mapping: { + email_expr: '', + name_expr: '', + }, + groups_mapping: { + default_groups: [], + groups_expr: [], + groups_mapping: [], + auto_create_groups: false, + prevent_default_groups: false, + }, + organizations_mapping: { + default_organizations: [], + organizations_expr: [], + organizations_mapping: [], + auto_create_organizations: false, + }, + headers_audit: [], + }, + }; + const availableProviders = await buildAvailableProviders(settingsMock as BasicStoreSettings); + expect(availableProviders).toStrictEqual([{ + name: 'testLocal', + provider: 'local', strategy: 'LocalStrategy', - config: { - disabled: true, + type: 'FORM', + }]); + }); + + it('should cert auth enabled only work fine', async () => { + const settingsMock: Partial = { + local_auth: { + enabled: false, + button_label_override: 'testLocal', + }, + cert_auth: { + enabled: true, + description: 'My cert auth for tests', + button_label_override: 'myCert', + user_info_mapping: { + email_expr: 'mèl', + name_expr: 'nom', + }, + groups_mapping: { + default_groups: [], + groups_expr: [], + groups_mapping: [], + auto_create_groups: false, + prevent_default_groups: false, + }, + organizations_mapping: { + default_organizations: [], + organizations_expr: [], + organizations_mapping: [], + auto_create_organizations: false, + }, }, - }, + headers_auth: { + enabled: false, + description: '', + button_label_override: '', + user_info_mapping: { + email_expr: '', + name_expr: '', + }, + groups_mapping: { + default_groups: [], + groups_expr: [], + groups_mapping: [], + auto_create_groups: false, + prevent_default_groups: false, + }, + organizations_mapping: { + default_organizations: [], + organizations_expr: [], + organizations_mapping: [], + auto_create_organizations: false, + }, + headers_audit: [], + }, + }; + const availableProviders = await buildAvailableProviders(settingsMock as BasicStoreSettings); + expect(availableProviders).toStrictEqual([{ + name: 'myCert', + provider: 'cert', + strategy: 'ClientCertStrategy', + type: 'SSO', + }]); }); - // WHEN initializing providers - await initializeAuthenticationProviders(testContext); + it('should header auth enabled only work fine', async () => { + const settingsMock: Partial = { + local_auth: { + enabled: false, + button_label_override: 'testLocal', + }, + cert_auth: { + enabled: false, + description: 'My cert auth for tests', + button_label_override: 'myCert', + user_info_mapping: { + email_expr: 'mèl', + name_expr: 'nom', + }, + groups_mapping: { + default_groups: [], + groups_expr: [], + groups_mapping: [], + auto_create_groups: false, + prevent_default_groups: false, + }, + organizations_mapping: { + default_organizations: [], + organizations_expr: [], + organizations_mapping: [], + auto_create_organizations: false, + }, + }, + headers_auth: { + enabled: true, + description: 'My header auth', + button_label_override: 'Header', + user_info_mapping: { + email_expr: 'mail', + name_expr: 'name', + }, + groups_mapping: { + default_groups: [], + groups_expr: [], + groups_mapping: [], + auto_create_groups: false, + prevent_default_groups: false, + }, + organizations_mapping: { + default_organizations: [], + organizations_expr: [], + organizations_mapping: [], + auto_create_organizations: false, + }, + headers_audit: [], + }, + }; + const availableProviders = await buildAvailableProviders(settingsMock as BasicStoreSettings); + expect(availableProviders).toStrictEqual([{ + name: 'Header', + provider: 'headers', + strategy: 'HeaderStrategy', + type: 'SSO', + }]); + }); - // THEN - // Should have only the OpenID, and no local since local is disabled in env - const finalSettings = await getSettings(testContext) as unknown as BasicStoreSettings; - const settingsProviders = await buildAvailableProviders(finalSettings); + it('should update password policy work', async () => { + const settings = await getSettingsFromDatabase(testContext) as unknown as BasicStoreSettings; + const localUpdateInput: LocalAuthConfigInput = { + enabled: true, + password_policy_max_length: 1, + password_policy_min_length: 2, + password_policy_min_lowercase: 3, + password_policy_min_numbers: 4, + password_policy_min_symbols: 5, + password_policy_min_uppercase: 6, + password_policy_min_words: 7, + }; + const result = await updateLocalAuth(testContext, ADMIN_USER, settings.id, localUpdateInput); - expect(settingsProviders).toStrictEqual([ - { - name: 'local', - provider: 'local', - strategy: 'LocalStrategy', - type: 'FORM', - }, - ]); + expect(result.local_auth.enabled).toBeTruthy(); + expect(result.password_policy_max_length).toBe(1); + }); }); }); From bf5b2674aa01c15a5b05d0889e1d88082006e84c Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Thu, 2 Apr 2026 13:29:18 +0200 Subject: [PATCH 05/12] A new test --- .../authenticationProvider/providers-test.ts | 76 ++++++++++++++++++- 1 file changed, 74 insertions(+), 2 deletions(-) diff --git a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts index c4f71921087a..4c8ac99392ab 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts @@ -4,9 +4,9 @@ import { ADMIN_USER, testContext } from '../../../utils/testQuery'; import type { BasicStoreSettings } from '../../../../src/types/settings'; import * as mockProviderEnv from '../../../../src/modules/authenticationProvider/providers-configuration'; import { getSettings, getSettingsFromDatabase } from '../../../../src/domain/settings'; -import { buildAvailableProviders, updateLocalAuth } from '../../../../src/domain/setting-auth'; +import { buildAvailableProviders, updateCertAuth, updateLocalAuth } from '../../../../src/domain/setting-auth'; import { type ProviderConfiguration, PROVIDERS } from '../../../../src/modules/authenticationProvider/providers-configuration'; -import type { LocalAuthConfigInput } from '../../../../src/generated/graphql'; +import type { CertAuthConfigInput, LocalAuthConfigInput } from '../../../../src/generated/graphql'; const clearEnvProviderArray = () => { const len = PROVIDERS.length; @@ -308,6 +308,78 @@ describe('Provider coverage', () => { expect(result.local_auth.enabled).toBeTruthy(); expect(result.password_policy_max_length).toBe(1); + expect(result.password_policy_min_length).toBe(2); + expect(result.password_policy_min_lowercase).toBe(3); + expect(result.password_policy_min_numbers).toBe(4); + expect(result.password_policy_min_symbols).toBe(5); + expect(result.password_policy_min_uppercase).toBe(6); + expect(result.password_policy_min_words).toBe(7); + }); + + it('should update cert work', async () => { + const settings = await getSettingsFromDatabase(testContext) as unknown as BasicStoreSettings; + const localUpdateInput: CertAuthConfigInput = { + button_label_override: 'MyCert', + description: 'Cert auth for tests', + groups_mapping: { + auto_create_groups: false, + default_groups: ['TestGroup'], + groups_expr: ['test.group'], + groups_mapping: [{ provider: 'Admin', platform: 'Administrator' }], + prevent_default_groups: false, + }, + organizations_mapping: { + auto_create_organizations: false, + default_organizations: [], + organizations_expr: ['test.org'], + organizations_mapping: [{ provider: 'Filigran', platform: 'Filigran' }], + }, + user_info_mapping: { + email_expr: 'user.email', + name_expr: 'user.name', + }, + enabled: true, + }; + const result = await updateCertAuth(testContext, ADMIN_USER, settings.id, localUpdateInput); + + expect(result.cert_auth).toStrictEqual({ + button_label_override: 'MyCert', + description: 'Cert auth for tests', + enabled: false, + groups_mapping: { + auto_create_groups: false, + default_groups: [ + 'TestGroup', + ], + groups_expr: [ + 'test.group', + ], + groups_mapping: [ + { + platform: 'Administrator', + provider: 'Admin', + }, + ], + prevent_default_groups: false, + }, + organizations_mapping: { + auto_create_organizations: false, + default_organizations: [], + organizations_expr: [ + 'test.org', + ], + organizations_mapping: [ + { + platform: 'Filigran', + provider: 'Filigran', + }, + ], + }, + user_info_mapping: { + email_expr: 'user.email', + name_expr: 'user.name', + }, + }); }); }); }); From cc3c96e2c82f51748c2a7c00d0f2a5c213e7dfdd Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Thu, 2 Apr 2026 15:44:55 +0200 Subject: [PATCH 06/12] Add translations --- opencti-platform/opencti-front/lang/front/de.json | 1 + opencti-platform/opencti-front/lang/front/en.json | 1 + opencti-platform/opencti-front/lang/front/es.json | 7 ++++--- opencti-platform/opencti-front/lang/front/fr.json | 1 + opencti-platform/opencti-front/lang/front/it.json | 1 + opencti-platform/opencti-front/lang/front/ja.json | 2 ++ opencti-platform/opencti-front/lang/front/ko.json | 1 + opencti-platform/opencti-front/lang/front/ru.json | 1 + opencti-platform/opencti-front/lang/front/zh.json | 1 + 9 files changed, 13 insertions(+), 3 deletions(-) diff --git a/opencti-platform/opencti-front/lang/front/de.json b/opencti-platform/opencti-front/lang/front/de.json index abbaaa10433b..24e565a705e9 100644 --- a/opencti-platform/opencti-front/lang/front/de.json +++ b/opencti-platform/opencti-front/lang/front/de.json @@ -2509,6 +2509,7 @@ "Live trigger": "Live-Trigger", "Loading current message count...": "Laden der aktuellen Nachrichtenanzahl...", "Local": "Lokal", + "Local authentication cannot be changed when authentication is managed by environment configuration": "Die lokale Authentifizierung kann nicht geändert werden, wenn die Authentifizierung über die Umgebungskonfiguration verwaltet wird", "Local authentication cannot be disabled when no other authentication provider is enabled": "Die lokale Authentifizierung kann nicht deaktiviert werden, wenn kein anderer Authentifizierungsanbieter aktiviert ist", "Local password policies": "Lokale Kennwortrichtlinien", "Local settings": "Lokale Einstellungen", diff --git a/opencti-platform/opencti-front/lang/front/en.json b/opencti-platform/opencti-front/lang/front/en.json index 70a1574a1687..0d20545fda26 100644 --- a/opencti-platform/opencti-front/lang/front/en.json +++ b/opencti-platform/opencti-front/lang/front/en.json @@ -2509,6 +2509,7 @@ "Live trigger": "Live trigger", "Loading current message count...": "Loading current message count...", "Local": "Local", + "Local authentication cannot be changed when authentication is managed by environment configuration": "Local authentication cannot be changed when authentication is managed by environment configuration", "Local authentication cannot be disabled when no other authentication provider is enabled": "Local authentication cannot be disabled when no other authentication provider is enabled", "Local password policies": "Local password policies", "Local settings": "Local settings", diff --git a/opencti-platform/opencti-front/lang/front/es.json b/opencti-platform/opencti-front/lang/front/es.json index 4ce4fb63e09d..0d068ce1052a 100644 --- a/opencti-platform/opencti-front/lang/front/es.json +++ b/opencti-platform/opencti-front/lang/front/es.json @@ -1928,9 +1928,9 @@ "Force reauthentication": "Forzar reautenticación", "Forecast": "Previsión", "Form": "Formulario", + "FORM": "FORMULARIO", "Form intakes": "Formularios de ingesta", "Form intakes | Ingestion | Data": "Formularios de ingesta | Ingestión | Datos", - "FORM": "FORMULARIO", "Form not found": "Formulario no encontrado", "Form schema in JSON format": "Esquema del formulario en formato JSON", "Form schema is required": "Se requiere esquema de formulario", @@ -2509,6 +2509,7 @@ "Live trigger": "Disparador en vivo", "Loading current message count...": "Cargando recuento de mensajes actual...", "Local": "Local", + "Local authentication cannot be changed when authentication is managed by environment configuration": "La autenticación local no puede modificarse cuando la autenticación se gestiona mediante la configuración del entorno", "Local authentication cannot be disabled when no other authentication provider is enabled": "La autenticación local no se puede deshabilitar cuando no hay otro proveedor de autenticación habilitado", "Local password policies": "Políticas locales de contraseñas", "Local settings": "Configuración local", @@ -4841,8 +4842,8 @@ "You see only marking definitions that can be shared (defined by the admin)": "Sólo se ven las definiciones de marcado que se pueden compartir (definidas por el administrador)", "You should activate EE to use this feature": "Debe activar EE para utilizar esta función", "You should provide a variable name": "Debe proporcionar un nombre de variable", - "You will be able to revert this change if needed. ": "Podrá revertir este cambio si es necesario.", "You were automatically logged out due to session expiration.": "Se ha cerrado automáticamente la sesión debido a la expiración de la misma.", + "You will be able to revert this change if needed. ": "Podrá revertir este cambio si es necesario.", "You will be automatically logged out at end of the timer.": "Se cerrará la sesión automáticamente al final del temporizador.", "You will find here the computed state.": "Aquí encontrará el estado calculado.", "You will find here the result in JSON format.": "Aquí encontrará el resultado en formato JSON.", @@ -4870,4 +4871,4 @@ "Zoom": "Zoom", "Zoom in": "Ampliar", "Zoom out": "Alejar" -} +} \ No newline at end of file diff --git a/opencti-platform/opencti-front/lang/front/fr.json b/opencti-platform/opencti-front/lang/front/fr.json index 22a2dfcd42da..93282d67d2b5 100644 --- a/opencti-platform/opencti-front/lang/front/fr.json +++ b/opencti-platform/opencti-front/lang/front/fr.json @@ -2509,6 +2509,7 @@ "Live trigger": "Déclencheur live", "Loading current message count...": "Chargement du nombre de messages en cours...", "Local": "Local", + "Local authentication cannot be changed when authentication is managed by environment configuration": "L'authentification locale ne peut pas être modifiée lorsque l'authentification est gérée par la configuration de l'environnement", "Local authentication cannot be disabled when no other authentication provider is enabled": "L'authentification locale ne peut pas être désactivée lorsqu'aucun autre fournisseur d'authentification n'est activé", "Local password policies": "Politiques locales en matière de mots de passe", "Local settings": "Paramètres locaux", diff --git a/opencti-platform/opencti-front/lang/front/it.json b/opencti-platform/opencti-front/lang/front/it.json index 455f78960ede..d2eb88a27347 100644 --- a/opencti-platform/opencti-front/lang/front/it.json +++ b/opencti-platform/opencti-front/lang/front/it.json @@ -2509,6 +2509,7 @@ "Live trigger": "Trigger live", "Loading current message count...": "Caricamento del numero di messaggi attuali...", "Local": "Locale", + "Local authentication cannot be changed when authentication is managed by environment configuration": "L'autenticazione locale non può essere modificata quando l'autenticazione è gestita dalla configurazione dell'ambiente", "Local authentication cannot be disabled when no other authentication provider is enabled": "L'autenticazione locale non può essere disabilitata se nessun altro provider di autenticazione è abilitato", "Local password policies": "Politiche locali sulla password", "Local settings": "Impostazioni locali", diff --git a/opencti-platform/opencti-front/lang/front/ja.json b/opencti-platform/opencti-front/lang/front/ja.json index 14cfa7ff5b0f..a14bc22b83d5 100644 --- a/opencti-platform/opencti-front/lang/front/ja.json +++ b/opencti-platform/opencti-front/lang/front/ja.json @@ -2144,6 +2144,7 @@ "If you want to keep the associated information, we recommend deactivating the user instead.": "関連情報を保持したい場合は、代わりにユーザーを非アクティブ化することをお勧めします。", "If your email address is found, an email will be sent to you.": "あなたのメールアドレスが見つかった場合、Eメールが送信されます。", "if your service account has been created originally as a service account (not transformed), please also change the email of your service account before/after transforming it to a user to ensure that the future user will be able to receive an email in the forgot password workflow.": "サービスアカウントが元々サービスアカウントとして作成されている(変換されていない)場合、パスワード忘れワークフローで将来のユーザーが確実にメールを受信できるように、ユーザーへの変換前/後のサービスアカウントのメールも変更してください。", + "Image URL": "画像URL", "IMEI values can only include digits, must be 15 to 16 characters": "IMEI値は数字のみで、15文字から16文字でなければなりません。", "Impact": "衝撃", "Impacted": "影響", @@ -2508,6 +2509,7 @@ "Live trigger": "ライブトリガー", "Loading current message count...": "現在のメッセージ数をロード中...", "Local": "ローカル", + "Local authentication cannot be changed when authentication is managed by environment configuration": "認証が環境設定で管理されている場合、ローカル認証は変更できない", "Local authentication cannot be disabled when no other authentication provider is enabled": "他の認証プロバイダーが有効になっていない場合、ローカル認証を無効にすることはできません", "Local password policies": "ローカルパスワードポリシー", "Local settings": "ローカル設定", diff --git a/opencti-platform/opencti-front/lang/front/ko.json b/opencti-platform/opencti-front/lang/front/ko.json index 9681d2102cf4..74a11d53741a 100644 --- a/opencti-platform/opencti-front/lang/front/ko.json +++ b/opencti-platform/opencti-front/lang/front/ko.json @@ -2509,6 +2509,7 @@ "Live trigger": "실시간 트리거", "Loading current message count...": "현재 메시지 수 로드 중...", "Local": "로컬", + "Local authentication cannot be changed when authentication is managed by environment configuration": "환경 구성으로 인증이 관리되는 경우 로컬 인증을 변경할 수 없습니다", "Local authentication cannot be disabled when no other authentication provider is enabled": "다른 인증 공급자가 활성화되어 있지 않으면 로컬 인증을 비활성화할 수 없습니다", "Local password policies": "로컬 비밀번호 정책", "Local settings": "로컬 설정", diff --git a/opencti-platform/opencti-front/lang/front/ru.json b/opencti-platform/opencti-front/lang/front/ru.json index 740875773c89..391da49d552d 100644 --- a/opencti-platform/opencti-front/lang/front/ru.json +++ b/opencti-platform/opencti-front/lang/front/ru.json @@ -2509,6 +2509,7 @@ "Live trigger": "Живой триггер", "Loading current message count...": "Загрузка текущего количества сообщений...", "Local": "Локальный", + "Local authentication cannot be changed when authentication is managed by environment configuration": "Локальная аутентификация не может быть изменена, если аутентификация управляется конфигурацией среды", "Local authentication cannot be disabled when no other authentication provider is enabled": "Локальная аутентификация не может быть отключена, если не включен другой провайдер аутентификации", "Local password policies": "Локальные политики паролей", "Local settings": "Локальные настройки", diff --git a/opencti-platform/opencti-front/lang/front/zh.json b/opencti-platform/opencti-front/lang/front/zh.json index 6544aa4b8a20..5a4567bc77e6 100644 --- a/opencti-platform/opencti-front/lang/front/zh.json +++ b/opencti-platform/opencti-front/lang/front/zh.json @@ -2509,6 +2509,7 @@ "Live trigger": "实时触发器", "Loading current message count...": "正在加载当前信息数...", "Local": "本地", + "Local authentication cannot be changed when authentication is managed by environment configuration": "当身份验证由环境配置管理时,无法更改本地身份验证", "Local authentication cannot be disabled when no other authentication provider is enabled": "当未启用其他身份验证提供程序时,无法禁用本地身份验证", "Local password policies": "本地密码策略", "Local settings": "本地设置", From 49e62ac0442b6cf4368609a40e013cd5fdc5cde2 Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Thu, 2 Apr 2026 18:47:18 +0200 Subject: [PATCH 07/12] Fixing all tests --- .../providers-configuration.ts | 1 - .../authenticationProvider/providers-test.ts | 127 +++++++++++++++--- 2 files changed, 112 insertions(+), 16 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts index bf80fcbd9c3b..92feb78544e2 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-configuration.ts @@ -4,7 +4,6 @@ import * as R from 'ramda'; import type { AuthenticationProviderType } from '../../generated/graphql'; import { addUserLoginCount } from '../../manager/telemetryManager'; import { logAuthError, logAuthInfo } from './providers-logger'; -import type { BasicStoreSettings } from '../../types/settings'; export const LOCAL_STRATEGY_IDENTIFIER = 'local'; export const HEADERS_STRATEGY_IDENTIFIER = 'headers'; diff --git a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts index 4c8ac99392ab..7cc3c4f80cc1 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts @@ -4,10 +4,17 @@ import { ADMIN_USER, testContext } from '../../../utils/testQuery'; import type { BasicStoreSettings } from '../../../../src/types/settings'; import * as mockProviderEnv from '../../../../src/modules/authenticationProvider/providers-configuration'; import { getSettings, getSettingsFromDatabase } from '../../../../src/domain/settings'; -import { buildAvailableProviders, updateCertAuth, updateLocalAuth } from '../../../../src/domain/setting-auth'; +import { buildAvailableProviders, updateCertAuth, updateHeaderAuth, updateLocalAuth } from '../../../../src/domain/setting-auth'; import { type ProviderConfiguration, PROVIDERS } from '../../../../src/modules/authenticationProvider/providers-configuration'; -import type { CertAuthConfigInput, LocalAuthConfigInput } from '../../../../src/generated/graphql'; +import type { CertAuthConfigInput, HeadersAuthConfigInput, LocalAuthConfigInput } from '../../../../src/generated/graphql'; +import { findAllAuthenticationProvider } from '../../../../src/modules/authenticationProvider/authenticationProvider-domain'; +import { SYSTEM_USER } from '../../../../src/utils/access'; +import { elDeleteElements } from '../../../../src/database/engine'; +const clearDbProvider = async () => { + const authenticators = await findAllAuthenticationProvider(testContext, SYSTEM_USER); + await elDeleteElements(testContext, SYSTEM_USER, authenticators, { forceDelete: true, forceRefresh: true }); +}; const clearEnvProviderArray = () => { const len = PROVIDERS.length; for (let i = 0; i < len; i++) { @@ -44,15 +51,16 @@ describe('Provider coverage', () => { config: { disabled: true, }, - }, oick: { - identifier: 'oick', - strategy: 'OpenIDConnectStrategy', - enabled: true, + }, saml_p_test: { + identifier: 'saml_p_test', + strategy: 'SamlStrategy', config: { - issuer: 'http://localhost:9999/realms/master', - client_id: 'openctioid', - client_secret: 'xxxxxxxxxxxxx', - redirect_uris: ['http://localhost:4000/auth/oick/callback'], + issuer: 'saml_p_test', + label: 'saml_p_test', + entry_point: 'http://localhost:9999/realms/master/protocol/saml_p_test', + saml_callback_url: 'http://localhost:4000/auth/saml_p_test/callback', + cert: 'xxxxxxxxxxxxxxxxxxxxxxxx', + logout_remote: false, }, }, }); @@ -67,10 +75,10 @@ describe('Provider coverage', () => { // Should have only the OpenID, and no local since local is disabled in env expect(settingsProviders).toStrictEqual([ { - logout_remote: undefined, - name: 'oick', - provider: 'oick', - strategy: 'OpenIDConnectStrategy', + logout_remote: false, + name: 'saml_p_test', + provider: 'saml_p_test', + strategy: 'SamlStrategy', type: 'SSO', }, ]); @@ -106,6 +114,29 @@ describe('Provider coverage', () => { }, ]); }); + + it('should force env & local disabled with no strategy still register local', async () => { + // GIVEN an empty configuration in DB + await clearDbProvider(); + vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(false); + + // WHEN initializing providers + await initializeAuthenticationProviders(testContext); + + // THEN + // Should have only the OpenID, and no local since local is disabled in env + const finalSettings = await getSettings(testContext) as unknown as BasicStoreSettings; + const settingsProviders = await buildAvailableProviders(finalSettings); + + expect(settingsProviders).toStrictEqual([ + { + name: 'local', + provider: 'local', + strategy: 'LocalStrategy', + type: 'FORM', + }, + ]); + }); }); describe('setting-auth file test coverage', () => { @@ -338,7 +369,7 @@ describe('Provider coverage', () => { email_expr: 'user.email', name_expr: 'user.name', }, - enabled: true, + enabled: false, }; const result = await updateCertAuth(testContext, ADMIN_USER, settings.id, localUpdateInput); @@ -381,5 +412,71 @@ describe('Provider coverage', () => { }, }); }); + + it('should update header work', async () => { + const settings = await getSettingsFromDatabase(testContext) as unknown as BasicStoreSettings; + const localUpdateInput: HeadersAuthConfigInput = { + button_label_override: 'MyHeader', + description: 'Header auth for tests', + groups_mapping: { + auto_create_groups: false, + default_groups: ['HeaderTestGroup'], + groups_expr: ['header.group'], + groups_mapping: [{ provider: 'Admin', platform: 'Administrator' }], + prevent_default_groups: false, + }, + organizations_mapping: { + auto_create_organizations: false, + default_organizations: [], + organizations_expr: ['header.org'], + organizations_mapping: [{ provider: 'Filigran', platform: 'Filigran' }], + }, + user_info_mapping: { + email_expr: 'header.email', + name_expr: 'header.name', + }, + enabled: false, + }; + const result = await updateHeaderAuth(testContext, ADMIN_USER, settings.id, localUpdateInput); + + expect(result.headers_auth).toStrictEqual({ + button_label_override: 'MyHeader', + description: 'Header auth for tests', + enabled: false, + groups_mapping: { + auto_create_groups: false, + default_groups: [ + 'HeaderTestGroup', + ], + groups_expr: [ + 'header.group', + ], + groups_mapping: [ + { + platform: 'Administrator', + provider: 'Admin', + }, + ], + prevent_default_groups: false, + }, + organizations_mapping: { + auto_create_organizations: false, + default_organizations: [], + organizations_expr: [ + 'header.org', + ], + organizations_mapping: [ + { + platform: 'Filigran', + provider: 'Filigran', + }, + ], + }, + user_info_mapping: { + email_expr: 'header.email', + name_expr: 'header.name', + }, + }); + }); }); }); From 8e3c1e6339400f564d7f919cfe794f9b03ce6591 Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Thu, 2 Apr 2026 20:51:21 +0200 Subject: [PATCH 08/12] Add use case on force local --- .../src/modules/authenticationProvider/providers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts index 2789b3e0b65b..643472af2bf2 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts @@ -176,7 +176,7 @@ export const initializeAuthenticationProviders = async (context: AuthContext) => const confProviders = getProvidersFromEnvironment(); // First Local singleton provider - updating settings with environment setup - if (isLocalAuthEnabledInEnv(confProviders)) { + if (isLocalAuthEnabledInEnv(confProviders) || isLocalAuthForcedEnabledFromEnv()) { await registerLocalStrategy(); await updateLocalAuth(context, SYSTEM_USER, settings.id, { enabled: true }); } else { @@ -195,7 +195,7 @@ export const initializeAuthenticationProviders = async (context: AuthContext) => await runAuthenticationProviderMigration(context, SYSTEM_USER); // In standard mode, init from providers in the database // Singleton initialization - if (settings.local_auth?.enabled === true) { + if (settings.local_auth?.enabled === true || isLocalAuthForcedEnabledFromEnv()) { await registerLocalStrategy(); } From e52f16d426f03fba615765c2d3f5f61ba5621dee Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Fri, 3 Apr 2026 09:22:02 +0200 Subject: [PATCH 09/12] Cleanup TODOs and taking PR feedbacks --- .../src/domain/setting-auth.ts | 16 ++++-- .../providers-env-deprecated.js | 3 -- .../authenticationProvider/providers.ts | 17 ++++--- .../authenticationProvider/providers-test.ts | 49 ++++++++++++++++++- 4 files changed, 68 insertions(+), 17 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/domain/setting-auth.ts b/opencti-platform/opencti-graphql/src/domain/setting-auth.ts index 5193afc84e80..c29b84227ec3 100644 --- a/opencti-platform/opencti-graphql/src/domain/setting-auth.ts +++ b/opencti-platform/opencti-graphql/src/domain/setting-auth.ts @@ -7,7 +7,15 @@ import { publishUserAction } from '../listener/UserActionListener'; import { CERT_PROVIDER } from '../modules/authenticationProvider/provider-cert'; import { HEADERS_PROVIDER } from '../modules/authenticationProvider/provider-headers'; import { LOCAL_PROVIDER } from '../modules/authenticationProvider/provider-local'; -import { AuthType, EnvStrategyType, isLocalAuthForcedEnabledFromEnv, PROVIDERS } from '../modules/authenticationProvider/providers-configuration'; +import { + AuthType, + CERT_STRATEGY_IDENTIFIER, + EnvStrategyType, + HEADERS_STRATEGY_IDENTIFIER, + isLocalAuthForcedEnabledFromEnv, + LOCAL_STRATEGY_IDENTIFIER, + PROVIDERS, +} from '../modules/authenticationProvider/providers-configuration'; import { ENTITY_TYPE_SETTINGS } from '../schema/internalObject'; import type { BasicStoreSettings } from '../types/settings'; import type { AuthContext, AuthUser } from '../types/user'; @@ -22,7 +30,7 @@ export const buildAvailableProviders = async (platformSettings: BasicStoreSettin name: platformSettings.local_auth?.button_label_override || 'local', type: AuthType.AUTH_FORM, strategy: EnvStrategyType.STRATEGY_LOCAL, - provider: LOCAL_PROVIDER?.provider ?? '', + provider: LOCAL_PROVIDER?.provider ?? LOCAL_STRATEGY_IDENTIFIER, }); } if (platformSettings.cert_auth?.enabled) { @@ -30,7 +38,7 @@ export const buildAvailableProviders = async (platformSettings: BasicStoreSettin name: platformSettings.cert_auth?.button_label_override || 'cert', type: AuthType.AUTH_SSO, strategy: EnvStrategyType.STRATEGY_CERT, - provider: CERT_PROVIDER?.provider ?? '', + provider: CERT_PROVIDER?.provider ?? CERT_STRATEGY_IDENTIFIER, }); } if (platformSettings.headers_auth?.enabled) { @@ -38,7 +46,7 @@ export const buildAvailableProviders = async (platformSettings: BasicStoreSettin name: platformSettings.headers_auth?.button_label_override || 'headers', type: AuthType.AUTH_SSO, strategy: EnvStrategyType.STRATEGY_HEADER, - provider: HEADERS_PROVIDER?.provider ?? '', + provider: HEADERS_PROVIDER?.provider ?? HEADERS_STRATEGY_IDENTIFIER, }); } return availableProviders; diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-env-deprecated.js b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-env-deprecated.js index b2b96c7ccd5c..889793769ba9 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-env-deprecated.js +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers-env-deprecated.js @@ -113,9 +113,6 @@ export const initializeEnvAuthenticationProviders = async () => { const mappedConfig = configRemapping(config); if (config === undefined || !config.disabled) { const providerName = config?.label || providerIdent; - // SINGLETON - // DO we put back header and cert here ?? - // FORM Strategies if (strategy === EnvStrategyType.STRATEGY_LDAP) { const providerRef = identifier || 'ldapauth'; diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts index 643472af2bf2..e865ed52022d 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts @@ -173,7 +173,7 @@ export const initializeAuthenticationProviders = async (context: AuthContext) => await elDeleteElements(context, SYSTEM_USER, authenticators, { forceDelete: true, forceRefresh: true }); // First manage local - const confProviders = getProvidersFromEnvironment(); + const confProviders = getProvidersFromEnvironment() ?? {}; // First Local singleton provider - updating settings with environment setup if (isLocalAuthEnabledInEnv(confProviders) || isLocalAuthForcedEnabledFromEnv()) { @@ -182,11 +182,13 @@ export const initializeAuthenticationProviders = async (context: AuthContext) => } else { await updateLocalAuth(context, SYSTEM_USER, settings.id, { enabled: false }); } - - // TODO what about cert signleton, here on in next method ?? - // For now keeping existing code but it's buggy IMO - await registerCertStrategy(); - await registerHeadersStrategy(context); + // Cert and Header are still persisted in setting, even with force env + if (settings.cert_auth?.enabled === true) { + await registerCertStrategy(); + } + if (settings.headers_auth?.enabled === true) { + await registerHeadersStrategy(context); + } // Init providers from env await initializeEnvAuthenticationProviders(); @@ -194,15 +196,14 @@ export const initializeAuthenticationProviders = async (context: AuthContext) => // Migration first (already created will be not replayed) await runAuthenticationProviderMigration(context, SYSTEM_USER); // In standard mode, init from providers in the database + // Singleton initialization if (settings.local_auth?.enabled === true || isLocalAuthForcedEnabledFromEnv()) { await registerLocalStrategy(); } - if (settings.cert_auth?.enabled === true) { await registerCertStrategy(); } - if (settings.headers_auth?.enabled === true) { await registerHeadersStrategy(context); } diff --git a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts index 7cc3c4f80cc1..575074ef2e30 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts @@ -1,4 +1,4 @@ -import { describe, it, vi, expect, beforeAll, afterAll } from 'vitest'; +import { describe, it, vi, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'; import { initializeAuthenticationProviders } from '../../../../src/modules/authenticationProvider/providers'; import { ADMIN_USER, testContext } from '../../../utils/testQuery'; import type { BasicStoreSettings } from '../../../../src/types/settings'; @@ -9,7 +9,10 @@ import { type ProviderConfiguration, PROVIDERS } from '../../../../src/modules/a import type { CertAuthConfigInput, HeadersAuthConfigInput, LocalAuthConfigInput } from '../../../../src/generated/graphql'; import { findAllAuthenticationProvider } from '../../../../src/modules/authenticationProvider/authenticationProvider-domain'; import { SYSTEM_USER } from '../../../../src/utils/access'; -import { elDeleteElements } from '../../../../src/database/engine'; +import { elDeleteElements, elIndexElements } from '../../../../src/database/engine'; +import { patchAttribute } from '../../../../src/database/middleware'; +import { ENTITY_TYPE_SETTINGS } from '../../../../src/schema/internalObject'; +import type { BasicStoreEntityAuthenticationProvider } from '../../../../src/modules/authenticationProvider/authenticationProvider-types'; const clearDbProvider = async () => { const authenticators = await findAllAuthenticationProvider(testContext, SYSTEM_USER); @@ -24,22 +27,64 @@ const clearEnvProviderArray = () => { describe('Provider coverage', () => { const PROVIDER_SAVE: ProviderConfiguration[] = []; + let savedDbProviders: BasicStoreEntityAuthenticationProvider[] = []; + let savedSettings: BasicStoreSettings; + beforeAll(async () => { + // Snapshot in-memory PROVIDERS const len = PROVIDERS.length; for (let i = 0; i < len; i++) { PROVIDER_SAVE.push(PROVIDERS[i]); } + // Snapshot DB authentication provider entities + savedDbProviders = await findAllAuthenticationProvider(testContext, SYSTEM_USER); + // Snapshot settings auth/password fields + savedSettings = await getSettingsFromDatabase(testContext) as unknown as BasicStoreSettings; }); afterAll(async () => { + // Restore in-memory PROVIDERS clearEnvProviderArray(); const len = PROVIDER_SAVE.length; for (let i = 0; i < len; i++) { PROVIDERS.push(PROVIDER_SAVE[i]); } + + // Restore settings auth/password fields + const settingsPatch: Record = { + local_auth: savedSettings.local_auth, + cert_auth: savedSettings.cert_auth, + headers_auth: savedSettings.headers_auth, + password_policy_min_length: (savedSettings as any).password_policy_min_length, + password_policy_max_length: (savedSettings as any).password_policy_max_length, + password_policy_min_symbols: (savedSettings as any).password_policy_min_symbols, + password_policy_min_numbers: (savedSettings as any).password_policy_min_numbers, + password_policy_min_words: (savedSettings as any).password_policy_min_words, + password_policy_min_lowercase: (savedSettings as any).password_policy_min_lowercase, + password_policy_min_uppercase: (savedSettings as any).password_policy_min_uppercase, + }; + await patchAttribute(testContext, ADMIN_USER, savedSettings.id, ENTITY_TYPE_SETTINGS, settingsPatch); + + // Restore DB authentication provider entities: + // delete whatever providers exist now, then re-index the originals + const currentProviders = await findAllAuthenticationProvider(testContext, SYSTEM_USER); + if (currentProviders.length > 0) { + await elDeleteElements(testContext, SYSTEM_USER, currentProviders, { forceDelete: true, forceRefresh: true }); + } + if (savedDbProviders.length > 0) { + await elIndexElements(testContext, SYSTEM_USER, undefined, savedDbProviders); + } }); describe('initializeAuthenticationProviders coverage', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + it('should force env & local disabled along with a strategy be correct', async () => { // GIVEN a force env, and a configuration with a local disabled and an OpenID configured clearEnvProviderArray(); From 804ed8840835e3e7b111b76f88577390fac8efcb Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Fri, 3 Apr 2026 09:28:08 +0200 Subject: [PATCH 10/12] Better test comment and naming --- .../authenticationProvider/providers-test.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts index 575074ef2e30..a625755488a1 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts @@ -86,7 +86,7 @@ describe('Provider coverage', () => { }); it('should force env & local disabled along with a strategy be correct', async () => { - // GIVEN a force env, and a configuration with a local disabled and an OpenID configured + // GIVEN a force env, and a configuration with a local disabled and an SAML configured clearEnvProviderArray(); vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); @@ -117,7 +117,7 @@ describe('Provider coverage', () => { const settingsProviders = await buildAvailableProviders(finalSettings); // THEN - // Should have only the OpenID, and no local since local is disabled in env + // Should have only the SAML, and no local since local is disabled in env expect(settingsProviders).toStrictEqual([ { logout_remote: false, @@ -146,7 +146,7 @@ describe('Provider coverage', () => { await initializeAuthenticationProviders(testContext); // THEN - // Should have only the OpenID, and no local since local is disabled in env + // Should have only the local since there is no other strategy const finalSettings = await getSettings(testContext) as unknown as BasicStoreSettings; const settingsProviders = await buildAvailableProviders(finalSettings); @@ -160,7 +160,7 @@ describe('Provider coverage', () => { ]); }); - it('should force env & local disabled with no strategy still register local', async () => { + it('should no force env and with no strategy still register local', async () => { // GIVEN an empty configuration in DB await clearDbProvider(); vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(false); @@ -169,7 +169,7 @@ describe('Provider coverage', () => { await initializeAuthenticationProviders(testContext); // THEN - // Should have only the OpenID, and no local since local is disabled in env + // Should have only the local const finalSettings = await getSettings(testContext) as unknown as BasicStoreSettings; const settingsProviders = await buildAvailableProviders(finalSettings); From 6de54f99c312f73e74e2d741f13a649e702ff981 Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Wed, 8 Apr 2026 09:20:25 +0200 Subject: [PATCH 11/12] Fix behavior with LDAP enabled and local disabled and add test coverage --- .../opencti-graphql/src/domain/user.js | 7 +- .../authenticationProvider/provider-local.ts | 2 +- .../authenticationProvider/providers.ts | 19 +- .../authenticationProvider/providers-test.ts | 182 +++++++++++++++++- 4 files changed, 194 insertions(+), 16 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/domain/user.js b/opencti-platform/opencti-graphql/src/domain/user.js index 4bf2aa9402cc..90b0373331db 100644 --- a/opencti-platform/opencti-graphql/src/domain/user.js +++ b/opencti-platform/opencti-graphql/src/domain/user.js @@ -110,15 +110,18 @@ import { memoize } from '../utils/memoize'; import { getSettings } from './settings'; import passport from 'passport'; import { + EnvStrategyType, getConfigurationAdminEmail, getConfigurationAdminPassword, getConfigurationAdminToken, + isLocalAuthForcedEnabledFromEnv, LOCAL_STRATEGY_IDENTIFIER, PROVIDERS, } from '../modules/authenticationProvider/providers-configuration'; import { addOrganization } from '../modules/organization/organization-domain'; import validator from 'validator'; import xtmOneClient from '../modules/xtm/one/xtm-one-client'; +import { logAuthInfo } from '../modules/authenticationProvider/providers-logger'; const BEARER = 'Bearer '; const BASIC = 'Basic '; @@ -1520,7 +1523,8 @@ export const sessionLogin = async (context, input) => { resolve({ user: authUser, provider: LOCAL_STRATEGY_IDENTIFIER }); })({ body }); }); - if (user && (settings.local_auth?.enabled || user.id === OPENCTI_ADMIN_UUID)) { + // Local auth can be force to be enabled in env with force_local, in which case any other configuration is bypass + if (user && (isLocalAuthForcedEnabledFromEnv() || settings.local_auth?.enabled || user.id === OPENCTI_ADMIN_UUID)) { loggedUser = await sessionAuthenticateUser(context, context.req, user, provider); } } @@ -1539,6 +1543,7 @@ export const sessionLogin = async (context, input) => { context_data: { username: ENABLED_DEMO_MODE ? REDACTED_USER.name : input.email, provider: 'form' }, }); // User cannot be authenticated in any providers + logAuthInfo('User cannot be authenticated in any providers', EnvStrategyType.STRATEGY_LOCAL, { username: input.email }); throw AuthenticationFailure(); }; diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/provider-local.ts b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/provider-local.ts index 1e34c38210f7..d0b6469123e7 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/provider-local.ts +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/provider-local.ts @@ -13,7 +13,7 @@ export const registerLocalStrategy = async () => { // @ts-ignore as per document new LocalStrategy is the right way, not sure what to do. const localStrategy = new LocalStrategy({}, (username: string, password: string, done: any) => { return login(username, password).then((info) => { - logAuthInfo('Successfully logged', EnvStrategyType.STRATEGY_LOCAL, { username }); + logAuthInfo('User found in database', EnvStrategyType.STRATEGY_LOCAL, { username }); addUserLoginCount(); return done(null, info); }).catch((err) => { diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts index e865ed52022d..af1f8990fee9 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts @@ -162,6 +162,12 @@ export const initDatabaseAuthenticationProviders = async (context: AuthContext, export const initializeAuthenticationProviders = async (context: AuthContext) => { const settings = await getSettingsFromDatabase(context) as unknown as BasicStoreSettings; + // Local auth is always enabled, to allow config admin login + // The filter is done afterward in user#sessionLogin + await registerLocalStrategy(); + await registerCertStrategy(); + await registerHeadersStrategy(context); + // In force env // Settings must be aligned on env definition // AuthenticationProviders must be deleted from the database @@ -175,20 +181,12 @@ export const initializeAuthenticationProviders = async (context: AuthContext) => // First manage local const confProviders = getProvidersFromEnvironment() ?? {}; - // First Local singleton provider - updating settings with environment setup + // For Local singleton provider - updating settings with environment setup if (isLocalAuthEnabledInEnv(confProviders) || isLocalAuthForcedEnabledFromEnv()) { - await registerLocalStrategy(); await updateLocalAuth(context, SYSTEM_USER, settings.id, { enabled: true }); } else { await updateLocalAuth(context, SYSTEM_USER, settings.id, { enabled: false }); } - // Cert and Header are still persisted in setting, even with force env - if (settings.cert_auth?.enabled === true) { - await registerCertStrategy(); - } - if (settings.headers_auth?.enabled === true) { - await registerHeadersStrategy(context); - } // Init providers from env await initializeEnvAuthenticationProviders(); @@ -198,9 +196,6 @@ export const initializeAuthenticationProviders = async (context: AuthContext) => // In standard mode, init from providers in the database // Singleton initialization - if (settings.local_auth?.enabled === true || isLocalAuthForcedEnabledFromEnv()) { - await registerLocalStrategy(); - } if (settings.cert_auth?.enabled === true) { await registerCertStrategy(); } diff --git a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts index a625755488a1..bc6f6142a732 100644 --- a/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts +++ b/opencti-platform/opencti-graphql/tests/03-integration/10-modules/authenticationProvider/providers-test.ts @@ -1,18 +1,21 @@ import { describe, it, vi, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest'; import { initializeAuthenticationProviders } from '../../../../src/modules/authenticationProvider/providers'; -import { ADMIN_USER, testContext } from '../../../utils/testQuery'; +import { ADMIN_USER, testContext, USER_EDITOR } from '../../../utils/testQuery'; import type { BasicStoreSettings } from '../../../../src/types/settings'; import * as mockProviderEnv from '../../../../src/modules/authenticationProvider/providers-configuration'; import { getSettings, getSettingsFromDatabase } from '../../../../src/domain/settings'; import { buildAvailableProviders, updateCertAuth, updateHeaderAuth, updateLocalAuth } from '../../../../src/domain/setting-auth'; import { type ProviderConfiguration, PROVIDERS } from '../../../../src/modules/authenticationProvider/providers-configuration'; -import type { CertAuthConfigInput, HeadersAuthConfigInput, LocalAuthConfigInput } from '../../../../src/generated/graphql'; +import type { CertAuthConfigInput, HeadersAuthConfigInput, LocalAuthConfigInput, UserLoginInput } from '../../../../src/generated/graphql'; import { findAllAuthenticationProvider } from '../../../../src/modules/authenticationProvider/authenticationProvider-domain'; import { SYSTEM_USER } from '../../../../src/utils/access'; import { elDeleteElements, elIndexElements } from '../../../../src/database/engine'; import { patchAttribute } from '../../../../src/database/middleware'; import { ENTITY_TYPE_SETTINGS } from '../../../../src/schema/internalObject'; import type { BasicStoreEntityAuthenticationProvider } from '../../../../src/modules/authenticationProvider/authenticationProvider-types'; +import { sessionLogin } from '../../../../src/domain/user'; +import type { AuthContext } from '../../../../src/types/user'; +import type Express from 'express'; const clearDbProvider = async () => { const authenticators = await findAllAuthenticationProvider(testContext, SYSTEM_USER); @@ -25,6 +28,22 @@ const clearEnvProviderArray = () => { } }; +const setLocalAuthToEnabled = async (enabled: boolean) => { + const settings = await getSettingsFromDatabase(testContext) as unknown as BasicStoreSettings; + const localUpdateInput: LocalAuthConfigInput = { + enabled, + password_policy_max_length: 0, + password_policy_min_length: 0, + password_policy_min_lowercase: 0, + password_policy_min_numbers: 0, + password_policy_min_symbols: 0, + password_policy_min_uppercase: 0, + password_policy_min_words: 0, + }; + const result = await updateLocalAuth(testContext, ADMIN_USER, settings.id, localUpdateInput); + expect(result.local_auth.enabled).toBe(enabled); +}; + describe('Provider coverage', () => { const PROVIDER_SAVE: ProviderConfiguration[] = []; let savedDbProviders: BasicStoreEntityAuthenticationProvider[] = []; @@ -524,4 +543,163 @@ describe('Provider coverage', () => { }); }); }); + + // Even if it's on user domain we put the test here to benefit from providers & settings reset. + describe('sessionLogin test coverage', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + const getMockAuthContextWithRequest = () => { + const request: Partial = { + headers: { 'x-forwarded-for': '127.0.0.1' }, + header: (_: string) => undefined, + session: { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore see user#sessionAuthenticateUser there is a session.save() there + save: () => {}, + }, + }; + + const reqContext: AuthContext = { + otp_mandatory: false, + req: request as Express.Request, + source: '', + tracing: undefined, + user: undefined, + user_inside_platform_organization: false, + }; + return reqContext; + }; + + it('should admin from configuration work with force_env + local disabled', async () => { + const reqContext: AuthContext = getMockAuthContextWithRequest(); + + // GIVEN using force env + local disabled + await setLocalAuthToEnabled(false); + vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); + vi.spyOn(mockProviderEnv, 'getProvidersFromEnvironment').mockReturnValue({ + local: { + strategy: 'LocalStrategy', + config: { + disabled: true, + }, + }, + }); + + // THEN admin from config should still work + const userInput: UserLoginInput = { email: 'admin@opencti.io', password: 'admin' }; // from test.json + await sessionLogin(reqContext, userInput); + + // THEN any other user should not + const userInputEditor: UserLoginInput = { email: USER_EDITOR.email, password: USER_EDITOR.password }; // from testQueryHelper + await expect(async () => { + await sessionLogin(reqContext, userInputEditor); + }).rejects.toThrowError('Bad login or password'); + }); + + it('should all local users work with force_env + local enabled', async () => { + const reqContext: AuthContext = getMockAuthContextWithRequest(); + + // GIVEN using force env + local enabled + await setLocalAuthToEnabled(true); + vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); + vi.spyOn(mockProviderEnv, 'getProvidersFromEnvironment').mockReturnValue({ + local: { + strategy: 'LocalStrategy', + config: { + disabled: false, + }, + }, + }); + + // THEN admin from config should still work + const userInput: UserLoginInput = { email: 'admin@opencti.io', password: 'admin' }; // from test.json + await sessionLogin(reqContext, userInput); + + // THEN any other user should also + const userInputEditor: UserLoginInput = { email: USER_EDITOR.email, password: USER_EDITOR.password }; // from testQueryHelper + await sessionLogin(reqContext, userInputEditor); + }); + + it('should admin from configuration work with database auth + local disabled', async () => { + const reqContext: AuthContext = getMockAuthContextWithRequest(); + + // GIVEN local disabled in database + await setLocalAuthToEnabled(false); + vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(false); + + // THEN admin from config should still work + const userInput: UserLoginInput = { email: 'admin@opencti.io', password: 'admin' }; // from test.json + await sessionLogin(reqContext, userInput); + + // THEN any other user should not + const userInputEditor: UserLoginInput = { email: USER_EDITOR.email, password: USER_EDITOR.password }; // from testQueryHelper + await expect(async () => { + await sessionLogin(reqContext, userInputEditor); + }).rejects.toThrowError('Bad login or password'); + }); + + it('should all local users work database auth + local enabled', async () => { + const reqContext: AuthContext = getMockAuthContextWithRequest(); + + // GIVEN local enabled in database + await setLocalAuthToEnabled(true); + vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(false); + + // THEN admin from config should still work + const userInput: UserLoginInput = { email: 'admin@opencti.io', password: 'admin' }; // from test.json + await sessionLogin(reqContext, userInput); + + // THEN any other user should also + const userInputEditor: UserLoginInput = { email: USER_EDITOR.email, password: USER_EDITOR.password }; // from testQueryHelper + await sessionLogin(reqContext, userInputEditor); + }); + + it('should all local users work database auth + local disabled + local forced in env', async () => { + const reqContext: AuthContext = getMockAuthContextWithRequest(); + + // GIVEN local disabled in database, but local force from env + await setLocalAuthToEnabled(false); + vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(false); + vi.spyOn(mockProviderEnv, 'isLocalAuthForcedEnabledFromEnv').mockReturnValue(true); + + // THEN admin from config should still work + const userInput: UserLoginInput = { email: 'admin@opencti.io', password: 'admin' }; // from test.json + await sessionLogin(reqContext, userInput); + + // THEN any other user should also + const userInputEditor: UserLoginInput = { email: USER_EDITOR.email, password: USER_EDITOR.password }; // from testQueryHelper + await sessionLogin(reqContext, userInputEditor); + }); + + it('should all local users work with force_env + local disabled + force local in env', async () => { + const reqContext: AuthContext = getMockAuthContextWithRequest(); + + // GIVEN local disabled in env, but local force from env also + await setLocalAuthToEnabled(false); + vi.spyOn(mockProviderEnv, 'isLocalAuthForcedEnabledFromEnv').mockReturnValue(true); + vi.spyOn(mockProviderEnv, 'isAuthenticationForcedFromEnv').mockReturnValue(true); + vi.spyOn(mockProviderEnv, 'getProvidersFromEnvironment').mockReturnValue({ + local: { + strategy: 'LocalStrategy', + config: { + disabled: true, + }, + }, + }); + + // THEN admin from config should still work + const userInput: UserLoginInput = { email: 'admin@opencti.io', password: 'admin' }; // from test.json + await sessionLogin(reqContext, userInput); + + // THEN any other user should also + const userInputEditor: UserLoginInput = { email: USER_EDITOR.email, password: USER_EDITOR.password }; // from testQueryHelper + await sessionLogin(reqContext, userInputEditor); + }); + }); }); From 0d9d2fc27521215dcec53f23193c5540e63f7ae8 Mon Sep 17 00:00:00 2001 From: Angelique Jard Date: Wed, 8 Apr 2026 09:24:52 +0200 Subject: [PATCH 12/12] Left over on provider revert --- .../src/modules/authenticationProvider/providers.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts index af1f8990fee9..d869dc77ef93 100644 --- a/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts +++ b/opencti-platform/opencti-graphql/src/modules/authenticationProvider/providers.ts @@ -194,14 +194,6 @@ export const initializeAuthenticationProviders = async (context: AuthContext) => // Migration first (already created will be not replayed) await runAuthenticationProviderMigration(context, SYSTEM_USER); // In standard mode, init from providers in the database - - // Singleton initialization - if (settings.cert_auth?.enabled === true) { - await registerCertStrategy(); - } - if (settings.headers_auth?.enabled === true) { - await registerHeadersStrategy(context); - } await initDatabaseAuthenticationProviders(context, SYSTEM_USER); } // Safety net: force local_auth enabled when no other provider is available