2025-02-15 14:41:12 +00:00
using AIStudio.Assistants.ERI ;
using AIStudio.Provider ;
using AIStudio.Settings ;
using AIStudio.Settings.DataModel ;
using AIStudio.Tools.ERIClient ;
using AIStudio.Tools.ERIClient.DataModel ;
namespace AIStudio.Tools.Services ;
public sealed class DataSourceService
{
2026-09-06 12:20:53 +00:00
//
// Trust is recorded for every participating provider, while only the chat provider's trust
// decides about external data sources below. The agent which validates retrieval contexts
// checks its own provider before it runs, so nothing slips through today. Keeping the value
// named here is what makes that asymmetry visible.
//
// ReSharper disable once NotAccessedPositionalProperty.Local
2026-08-14 10:03:16 +00:00
private readonly record struct ParticipatingProvider ( string Role , bool IsTrusted , ConfidenceLevel ConfidenceLevel ) ;
2025-02-15 14:41:12 +00:00
private readonly RustService rustService ;
private readonly SettingsManager settingsManager ;
private readonly ILogger < DataSourceService > logger ;
public DataSourceService ( SettingsManager settingsManager , ILogger < DataSourceService > logger , RustService rustService )
{
this . logger = logger ;
this . rustService = rustService ;
this . settingsManager = settingsManager ;
this . logger . LogInformation ( "The data source service has been initialized." ) ;
}
/// <summary>
/// Returns a list of data sources that are allowed for the selected LLM provider.
/// It also returns the data sources selected before when they are still allowed.
/// </summary>
/// <param name="selectedLLMProvider">The selected LLM provider.</param>
2026-08-14 10:03:16 +00:00
/// <param name="dataSourceOptions">The active data source options, which determine which agent providers participate.</param>
2025-02-15 14:41:12 +00:00
/// <param name="previousSelectedDataSources">The data sources selected before.</param>
/// <returns>The allowed data sources and the data sources selected before -- when they are still allowed.</returns>
2026-08-14 10:03:16 +00:00
public async Task < AllowedSelectedDataSources > GetDataSources ( AIStudio . Settings . Provider selectedLLMProvider , DataSourceOptions dataSourceOptions , IReadOnlyCollection < IDataSource > ? previousSelectedDataSources = null )
2025-02-15 14:41:12 +00:00
{
//
// Case: Somehow the selected LLM provider was not set. The default provider
// does not mean anything. We cannot filter the data sources by any means.
// We return an empty list. Better safe than sorry.
//
2025-08-26 08:59:56 +00:00
if ( selectedLLMProvider = = Settings . Provider . NONE )
2025-02-15 14:41:12 +00:00
{
this . logger . LogWarning ( "The selected LLM provider is not set. We cannot filter the data sources by any means." ) ;
return new ( [ ] , [ ] ) ;
}
2026-08-14 10:03:16 +00:00
var usingTrustedProvider = selectedLLMProvider . IsTrustedForDataSourceSecurityChecks ( this . settingsManager ) ;
var participatingProviders = this . GetParticipatingProviders ( selectedLLMProvider . Id , dataSourceOptions ,
new ( "chat provider" , usingTrustedProvider , selectedLLMProvider . GetConfidenceLevel ( this . settingsManager ) ) ) ;
return await this . GetDataSources ( usingTrustedProvider , participatingProviders , previousSelectedDataSources ) ;
2025-02-15 14:41:12 +00:00
}
2026-08-27 14:19:35 +00:00
/// <summary>
/// Returns the requested data sources that are allowed for the selected LLM provider.
/// Unlike see GetDataSources(AIStudio.Settings.Provider, IReadOnlyCollection{IDataSource}),
/// this method checks only the supplied data sources.
/// </summary>
/// <param name="selectedLLMProvider">The selected LLM provider.</param>
Merge branch 'main' into chunk-data
Resolved 29 conflicting files. The notable decisions:
Confidence: main's tool-calling gate (RequiredProviderConfidence) and this
branch's local-RAG gate (DataConfidenceLevel) turned out to be the same rule
on the same axis, so they are now one field. Both tool results and data
sources raise it through RequireProviderConfidence(). The gate checks the
level strictly and no longer exempts providers trusted by configuration:
TrustedProviderIds is documented as applying to data-source security checks
only, and organizations set confidence through DataConfidence
.CustomConfidenceScheme instead. The security axis (DataSecurity, ERI,
IsTrustedForDataSourceSecurityChecks) is unchanged.
Provider creation: main's CreateProvider signature won (hfEndpointKind,
capabilityOverrides, no model parameter); tokenizerPath was added to it and
is set for every provider, including the new Hetzner, IONOS and LiteLLM.
Provider and EmbeddingProvider combine the record parameters, Lua parsing and
Lua serialization of both sides.
File types: main's hierarchy (ODT leaf, WORD parent, PowerPoint without the
legacy .ppt, TABULAR instead of DELIMITED_TABLE) plus this branch's
SPREADSHEET parent with ODS and the xlsm/xlsb/xla/xlam extensions, which the
runtime already reads. Both sides had added a conflicting HTML filter; the
reading family keeps the name, and the export path uses a narrow
HTML_DOCUMENT, following the existing LATEX/TEX split.
Runtime: main's file_data.rs is the base, including the prompt-injection
sanitizer and the extraction routes. Token counting and chunk segmentation
moved into take_released, so they act on the text the filter has released
rather than on text it is still holding. A failed count is logged and left
out instead of ending the extraction, because the app counts such a segment
itself.
Data sources: the participating-provider checks of this branch are kept, and
main's GetAllowedDataSources overload now builds on them. DirectChatService
resolves the launched chat's data source options before the check, so filter
and chat see the same options.
.NET and Rust both build clean; I18N regenerated to 4060 keys.
2026-09-05 19:17:42 +00:00
/// <param name="dataSourceOptions">The active data source options, which determine which agent providers participate.</param>
2026-08-27 14:19:35 +00:00
/// <param name="requestedDataSources">The data sources to check.</param>
/// <returns>The requested data sources that are allowed for the provider.</returns>
Merge branch 'main' into chunk-data
Resolved 29 conflicting files. The notable decisions:
Confidence: main's tool-calling gate (RequiredProviderConfidence) and this
branch's local-RAG gate (DataConfidenceLevel) turned out to be the same rule
on the same axis, so they are now one field. Both tool results and data
sources raise it through RequireProviderConfidence(). The gate checks the
level strictly and no longer exempts providers trusted by configuration:
TrustedProviderIds is documented as applying to data-source security checks
only, and organizations set confidence through DataConfidence
.CustomConfidenceScheme instead. The security axis (DataSecurity, ERI,
IsTrustedForDataSourceSecurityChecks) is unchanged.
Provider creation: main's CreateProvider signature won (hfEndpointKind,
capabilityOverrides, no model parameter); tokenizerPath was added to it and
is set for every provider, including the new Hetzner, IONOS and LiteLLM.
Provider and EmbeddingProvider combine the record parameters, Lua parsing and
Lua serialization of both sides.
File types: main's hierarchy (ODT leaf, WORD parent, PowerPoint without the
legacy .ppt, TABULAR instead of DELIMITED_TABLE) plus this branch's
SPREADSHEET parent with ODS and the xlsm/xlsb/xla/xlam extensions, which the
runtime already reads. Both sides had added a conflicting HTML filter; the
reading family keeps the name, and the export path uses a narrow
HTML_DOCUMENT, following the existing LATEX/TEX split.
Runtime: main's file_data.rs is the base, including the prompt-injection
sanitizer and the extraction routes. Token counting and chunk segmentation
moved into take_released, so they act on the text the filter has released
rather than on text it is still holding. A failed count is logged and left
out instead of ending the extraction, because the app counts such a segment
itself.
Data sources: the participating-provider checks of this branch are kept, and
main's GetAllowedDataSources overload now builds on them. DirectChatService
resolves the launched chat's data source options before the check, so filter
and chat see the same options.
.NET and Rust both build clean; I18N regenerated to 4060 keys.
2026-09-05 19:17:42 +00:00
public async Task < IReadOnlyList < IDataSource > > GetAllowedDataSources ( AIStudio . Settings . Provider selectedLLMProvider , DataSourceOptions dataSourceOptions , IReadOnlyCollection < IDataSource > requestedDataSources )
2026-08-27 14:19:35 +00:00
{
if ( selectedLLMProvider = = Settings . Provider . NONE )
{
this . logger . LogWarning ( "The selected LLM provider is not set. We cannot filter the data sources by any means." ) ;
return [ ] ;
}
Merge branch 'main' into chunk-data
Resolved 29 conflicting files. The notable decisions:
Confidence: main's tool-calling gate (RequiredProviderConfidence) and this
branch's local-RAG gate (DataConfidenceLevel) turned out to be the same rule
on the same axis, so they are now one field. Both tool results and data
sources raise it through RequireProviderConfidence(). The gate checks the
level strictly and no longer exempts providers trusted by configuration:
TrustedProviderIds is documented as applying to data-source security checks
only, and organizations set confidence through DataConfidence
.CustomConfidenceScheme instead. The security axis (DataSecurity, ERI,
IsTrustedForDataSourceSecurityChecks) is unchanged.
Provider creation: main's CreateProvider signature won (hfEndpointKind,
capabilityOverrides, no model parameter); tokenizerPath was added to it and
is set for every provider, including the new Hetzner, IONOS and LiteLLM.
Provider and EmbeddingProvider combine the record parameters, Lua parsing and
Lua serialization of both sides.
File types: main's hierarchy (ODT leaf, WORD parent, PowerPoint without the
legacy .ppt, TABULAR instead of DELIMITED_TABLE) plus this branch's
SPREADSHEET parent with ODS and the xlsm/xlsb/xla/xlam extensions, which the
runtime already reads. Both sides had added a conflicting HTML filter; the
reading family keeps the name, and the export path uses a narrow
HTML_DOCUMENT, following the existing LATEX/TEX split.
Runtime: main's file_data.rs is the base, including the prompt-injection
sanitizer and the extraction routes. Token counting and chunk segmentation
moved into take_released, so they act on the text the filter has released
rather than on text it is still holding. A failed count is logged and left
out instead of ending the extraction, because the app counts such a segment
itself.
Data sources: the participating-provider checks of this branch are kept, and
main's GetAllowedDataSources overload now builds on them. DirectChatService
resolves the launched chat's data source options before the check, so filter
and chat see the same options.
.NET and Rust both build clean; I18N regenerated to 4060 keys.
2026-09-05 19:17:42 +00:00
var usingTrustedProvider = selectedLLMProvider . IsTrustedForDataSourceSecurityChecks ( this . settingsManager ) ;
var participatingProviders = this . GetParticipatingProviders ( selectedLLMProvider . Id , dataSourceOptions ,
new ( "chat provider" , usingTrustedProvider , selectedLLMProvider . GetConfidenceLevel ( this . settingsManager ) ) ) ;
return await this . GetAllowedDataSources ( usingTrustedProvider , participatingProviders , requestedDataSources ) ;
2026-08-27 14:19:35 +00:00
}
2025-02-15 14:41:12 +00:00
/// <summary>
/// Returns a list of data sources that are allowed for the selected LLM provider.
/// It also returns the data sources selected before when they are still allowed.
/// </summary>
/// <param name="selectedLLMProvider">The selected LLM provider.</param>
2026-08-14 10:03:16 +00:00
/// <param name="dataSourceOptions">The active data source options, which determine which agent providers participate.</param>
2025-02-15 14:41:12 +00:00
/// <param name="previousSelectedDataSources">The data sources selected before.</param>
/// <returns>The allowed data sources and the data sources selected before -- when they are still allowed.</returns>
2026-08-14 10:03:16 +00:00
public async Task < AllowedSelectedDataSources > GetDataSources ( IProvider selectedLLMProvider , DataSourceOptions dataSourceOptions , IReadOnlyCollection < IDataSource > ? previousSelectedDataSources = null )
2025-02-15 14:41:12 +00:00
{
//
// Case: Somehow the selected LLM provider was not set. The default provider
// does not mean anything. We cannot filter the data sources by any means.
// We return an empty list. Better safe than sorry.
//
if ( selectedLLMProvider is NoProvider )
{
this . logger . LogWarning ( "The selected LLM provider is the default provider. We cannot filter the data sources by any means." ) ;
return new ( [ ] , [ ] ) ;
}
2026-08-14 10:03:16 +00:00
var usingTrustedProvider = selectedLLMProvider . IsTrustedForDataSourceSecurityChecks ( this . settingsManager ) ;
var participatingProviders = this . GetParticipatingProviders ( selectedLLMProvider . ConfiguredProviderId , dataSourceOptions ,
new ( "chat provider" , usingTrustedProvider , selectedLLMProvider . GetConfidenceLevel ( this . settingsManager ) ) ) ;
return await this . GetDataSources ( usingTrustedProvider , participatingProviders , previousSelectedDataSources ) ;
2025-02-15 14:41:12 +00:00
}
2026-08-14 10:03:16 +00:00
private IReadOnlyList < ParticipatingProvider > GetParticipatingProviders ( string currentProviderId , DataSourceOptions dataSourceOptions , ParticipatingProvider currentProvider )
{
var providers = new List < ParticipatingProvider > { currentProvider } ;
if ( dataSourceOptions . AutomaticDataSourceSelection )
this . AddAgentProvider ( providers , Components . AGENT_DATA_SOURCE_SELECTION , currentProviderId , "data source selection agent" ) ;
if ( dataSourceOptions . AutomaticValidation & & this . settingsManager . ConfigurationData . AgentRetrievalContextValidation . EnableRetrievalContextValidation )
this . AddAgentProvider ( providers , Components . AGENT_RETRIEVAL_CONTEXT_VALIDATION , currentProviderId , "retrieval context validation agent" ) ;
return providers ;
}
private void AddAgentProvider ( List < ParticipatingProvider > providers , Components component , string currentProviderId , string role )
{
var provider = this . settingsManager . GetPreselectedProvider ( component , currentProviderId , true ) ;
if ( provider = = Settings . Provider . NONE )
{
this . logger . LogWarning ( $"No provider is available for the {role}. Data sources cannot be made available while this agent is enabled." ) ;
providers . Add ( new ( role , false , ConfidenceLevel . NONE ) ) ;
return ;
}
providers . Add ( new (
role ,
provider . IsTrustedForDataSourceSecurityChecks ( this . settingsManager ) ,
provider . GetConfidenceLevel ( this . settingsManager ) ) ) ;
}
private async Task < AllowedSelectedDataSources > GetDataSources ( bool usingTrustedProvider , IReadOnlyList < ParticipatingProvider > participatingProviders , IReadOnlyCollection < IDataSource > ? previousSelectedDataSources = null )
2025-02-15 14:41:12 +00:00
{
2026-07-05 12:46:31 +00:00
var allDataSources = this . settingsManager . ConfigurationData . DataSources . ToList ( ) ;
var previousSelectedDataSourceIds = previousSelectedDataSources ? . Select ( source = > source . Id ) . ToHashSet ( StringComparer . Ordinal ) ? ? [ ] ;
Merge branch 'main' into chunk-data
Resolved 29 conflicting files. The notable decisions:
Confidence: main's tool-calling gate (RequiredProviderConfidence) and this
branch's local-RAG gate (DataConfidenceLevel) turned out to be the same rule
on the same axis, so they are now one field. Both tool results and data
sources raise it through RequireProviderConfidence(). The gate checks the
level strictly and no longer exempts providers trusted by configuration:
TrustedProviderIds is documented as applying to data-source security checks
only, and organizations set confidence through DataConfidence
.CustomConfidenceScheme instead. The security axis (DataSecurity, ERI,
IsTrustedForDataSourceSecurityChecks) is unchanged.
Provider creation: main's CreateProvider signature won (hfEndpointKind,
capabilityOverrides, no model parameter); tokenizerPath was added to it and
is set for every provider, including the new Hetzner, IONOS and LiteLLM.
Provider and EmbeddingProvider combine the record parameters, Lua parsing and
Lua serialization of both sides.
File types: main's hierarchy (ODT leaf, WORD parent, PowerPoint without the
legacy .ppt, TABULAR instead of DELIMITED_TABLE) plus this branch's
SPREADSHEET parent with ODS and the xlsm/xlsb/xla/xlam extensions, which the
runtime already reads. Both sides had added a conflicting HTML filter; the
reading family keeps the name, and the export path uses a narrow
HTML_DOCUMENT, following the existing LATEX/TEX split.
Runtime: main's file_data.rs is the base, including the prompt-injection
sanitizer and the extraction routes. Token counting and chunk segmentation
moved into take_released, so they act on the text the filter has released
rather than on text it is still holding. A failed count is logged and left
out instead of ending the extraction, because the app counts such a segment
itself.
Data sources: the participating-provider checks of this branch are kept, and
main's GetAllowedDataSources overload now builds on them. DirectChatService
resolves the launched chat's data source options before the check, so filter
and chat see the same options.
.NET and Rust both build clean; I18N regenerated to 4060 keys.
2026-09-05 19:17:42 +00:00
var filteredDataSources = await this . GetAllowedDataSources ( usingTrustedProvider , participatingProviders , allDataSources ) ;
2026-08-27 14:19:35 +00:00
var filteredSelectedDataSources = filteredDataSources . Where ( source = > previousSelectedDataSourceIds . Contains ( source . Id ) ) . ToList ( ) ;
2025-02-15 14:41:12 +00:00
2026-08-27 14:19:35 +00:00
return new ( filteredDataSources , filteredSelectedDataSources ) ;
}
Merge branch 'main' into chunk-data
Resolved 29 conflicting files. The notable decisions:
Confidence: main's tool-calling gate (RequiredProviderConfidence) and this
branch's local-RAG gate (DataConfidenceLevel) turned out to be the same rule
on the same axis, so they are now one field. Both tool results and data
sources raise it through RequireProviderConfidence(). The gate checks the
level strictly and no longer exempts providers trusted by configuration:
TrustedProviderIds is documented as applying to data-source security checks
only, and organizations set confidence through DataConfidence
.CustomConfidenceScheme instead. The security axis (DataSecurity, ERI,
IsTrustedForDataSourceSecurityChecks) is unchanged.
Provider creation: main's CreateProvider signature won (hfEndpointKind,
capabilityOverrides, no model parameter); tokenizerPath was added to it and
is set for every provider, including the new Hetzner, IONOS and LiteLLM.
Provider and EmbeddingProvider combine the record parameters, Lua parsing and
Lua serialization of both sides.
File types: main's hierarchy (ODT leaf, WORD parent, PowerPoint without the
legacy .ppt, TABULAR instead of DELIMITED_TABLE) plus this branch's
SPREADSHEET parent with ODS and the xlsm/xlsb/xla/xlam extensions, which the
runtime already reads. Both sides had added a conflicting HTML filter; the
reading family keeps the name, and the export path uses a narrow
HTML_DOCUMENT, following the existing LATEX/TEX split.
Runtime: main's file_data.rs is the base, including the prompt-injection
sanitizer and the extraction routes. Token counting and chunk segmentation
moved into take_released, so they act on the text the filter has released
rather than on text it is still holding. A failed count is logged and left
out instead of ending the extraction, because the app counts such a segment
itself.
Data sources: the participating-provider checks of this branch are kept, and
main's GetAllowedDataSources overload now builds on them. DirectChatService
resolves the launched chat's data source options before the check, so filter
and chat see the same options.
.NET and Rust both build clean; I18N regenerated to 4060 keys.
2026-09-05 19:17:42 +00:00
private async Task < IReadOnlyList < IDataSource > > GetAllowedDataSources ( bool usingTrustedProvider , IReadOnlyList < ParticipatingProvider > participatingProviders , IReadOnlyCollection < IDataSource > requestedDataSources )
2026-08-27 14:19:35 +00:00
{
var filteredDataSources = new List < IDataSource > ( requestedDataSources . Count ) ;
var tasks = new List < Task < IDataSource ? > > ( requestedDataSources . Count ) ;
2025-02-15 14:41:12 +00:00
// Start all checks in parallel:
2026-08-27 14:19:35 +00:00
foreach ( var source in requestedDataSources )
2026-08-14 10:03:16 +00:00
tasks . Add ( this . CheckOneDataSource ( source , usingTrustedProvider , participatingProviders ) ) ;
Merge branch 'main' into chunk-data
Resolved 29 conflicting files. The notable decisions:
Confidence: main's tool-calling gate (RequiredProviderConfidence) and this
branch's local-RAG gate (DataConfidenceLevel) turned out to be the same rule
on the same axis, so they are now one field. Both tool results and data
sources raise it through RequireProviderConfidence(). The gate checks the
level strictly and no longer exempts providers trusted by configuration:
TrustedProviderIds is documented as applying to data-source security checks
only, and organizations set confidence through DataConfidence
.CustomConfidenceScheme instead. The security axis (DataSecurity, ERI,
IsTrustedForDataSourceSecurityChecks) is unchanged.
Provider creation: main's CreateProvider signature won (hfEndpointKind,
capabilityOverrides, no model parameter); tokenizerPath was added to it and
is set for every provider, including the new Hetzner, IONOS and LiteLLM.
Provider and EmbeddingProvider combine the record parameters, Lua parsing and
Lua serialization of both sides.
File types: main's hierarchy (ODT leaf, WORD parent, PowerPoint without the
legacy .ppt, TABULAR instead of DELIMITED_TABLE) plus this branch's
SPREADSHEET parent with ODS and the xlsm/xlsb/xla/xlam extensions, which the
runtime already reads. Both sides had added a conflicting HTML filter; the
reading family keeps the name, and the export path uses a narrow
HTML_DOCUMENT, following the existing LATEX/TEX split.
Runtime: main's file_data.rs is the base, including the prompt-injection
sanitizer and the extraction routes. Token counting and chunk segmentation
moved into take_released, so they act on the text the filter has released
rather than on text it is still holding. A failed count is logged and left
out instead of ending the extraction, because the app counts such a segment
itself.
Data sources: the participating-provider checks of this branch are kept, and
main's GetAllowedDataSources overload now builds on them. DirectChatService
resolves the launched chat's data source options before the check, so filter
and chat see the same options.
.NET and Rust both build clean; I18N regenerated to 4060 keys.
2026-09-05 19:17:42 +00:00
2026-08-27 14:19:35 +00:00
2025-02-15 14:41:12 +00:00
// Wait for all checks and collect the results:
foreach ( var task in tasks )
{
var source = await task ;
if ( source is not null )
filteredDataSources . Add ( source ) ;
}
2026-08-27 14:19:35 +00:00
return filteredDataSources ;
2025-02-15 14:41:12 +00:00
}
2026-08-14 10:03:16 +00:00
private async Task < IDataSource ? > CheckOneDataSource ( IDataSource source , bool usingTrustedProvider , IReadOnlyList < ParticipatingProvider > participatingProviders )
2025-02-15 14:41:12 +00:00
{
2026-08-14 10:03:16 +00:00
if ( source is IInternalDataSource internalSource )
2026-08-05 13:05:17 +00:00
{
2026-08-14 10:03:16 +00:00
foreach ( var provider in participatingProviders )
{
if ( ! provider . ConfidenceLevel . AllowsDataSourceConfidenceLevel ( internalSource . ConfidenceLevel ) )
{
this . logger . LogWarning ( $"The internal data source '{source.Name}' (id={source.Id}) requires provider confidence '{internalSource.ConfidenceLevel.GetName()}'. The {provider.Role} only has confidence '{provider.ConfidenceLevel.GetName()}'. We skip this source." ) ;
return null ;
}
}
if ( ! DataSourceEmbeddingProviders . TryResolve ( this . settingsManager , source , out var embeddingProvider ) )
{
this . logger . LogWarning ( $"The internal data source '{source.Name}' (id={source.Id}) has no usable embedding provider. We skip this source." ) ;
return null ;
}
var embeddingProviderConfidence = embeddingProvider . GetConfidenceLevel ( this . settingsManager ) ;
if ( ! embeddingProviderConfidence . AllowsDataSourceConfidenceLevel ( internalSource . ConfidenceLevel ) )
{
this . logger . LogWarning ( $"The internal data source '{source.Name}' (id={source.Id}) requires provider confidence '{internalSource.ConfidenceLevel.GetName()}'. Its embedding provider '{embeddingProvider.Name}' only has confidence '{embeddingProviderConfidence.GetName()}'. We skip this source." ) ;
return null ;
}
2026-08-05 13:05:17 +00:00
return source ;
2026-08-14 10:03:16 +00:00
}
2026-08-05 13:05:17 +00:00
2025-02-15 14:41:12 +00:00
//
// Unfortunately, we have to live-check any ERI source for its security requirements.
// Because the ERI server operator might change the security requirements at any time.
//
SecurityRequirements ? eriSourceRequirements = null ;
if ( source is DataSourceERI_V1 eriSource )
{
using var cancellationTokenSource = new CancellationTokenSource ( TimeSpan . FromSeconds ( 6 ) ) ;
using var client = ERIClientFactory . Get ( ERIVersion . V1 , eriSource ) ;
if ( client is null )
{
this . logger . LogError ( $"Could not create ERI client for source '{source.Name}' (id={source.Id}). We skip this source." ) ;
return null ;
}
this . logger . LogInformation ( $"Authenticating with ERI source '{source.Name}' (id={source.Id})..." ) ;
2025-03-11 12:57:47 +00:00
var loginResult = await client . AuthenticateAsync ( this . rustService , cancellationToken : cancellationTokenSource . Token ) ;
2025-02-15 14:41:12 +00:00
if ( ! loginResult . Successful )
{
this . logger . LogWarning ( $"Authentication with ERI source '{source.Name}' (id={source.Id}) failed. We skip this source. Reason: {loginResult.Message}" ) ;
return null ;
}
this . logger . LogInformation ( $"Checking security requirements for ERI source '{source.Name}' (id={source.Id})..." ) ;
var securityRequest = await client . GetSecurityRequirementsAsync ( cancellationTokenSource . Token ) ;
if ( ! securityRequest . Successful )
{
this . logger . LogWarning ( $"Could not retrieve security requirements for ERI source '{source.Name}' (id={source.Id}). We skip this source. Reason: {loginResult.Message}" ) ;
return null ;
}
eriSourceRequirements = securityRequest . Data ;
this . logger . LogInformation ( $"Security requirements for ERI source '{source.Name}' (id={source.Id}) retrieved successfully." ) ;
}
2026-08-14 10:03:16 +00:00
if ( source is not IExternalDataSource externalSource )
return source ;
switch ( externalSource . SecurityPolicy )
2025-02-15 14:41:12 +00:00
{
case DataSourceSecurity . ALLOW_ANY :
//
2026-06-21 13:16:37 +00:00
// Case: The data source allows any provider type. We want to use a trusted provider.
2025-02-15 14:41:12 +00:00
// There is no issue with this source. Accept it.
//
2026-06-21 13:16:37 +00:00
if ( usingTrustedProvider )
2025-02-15 14:41:12 +00:00
return source ;
//
// Case: This is a local data source. When the source allows any provider type, we can use it.
// Accept it.
//
if ( eriSourceRequirements is null )
return source ;
//
2026-06-21 13:16:37 +00:00
// Case: The ERI source requires a self-hosted or organization-trusted provider. This misconfiguration happens
2025-02-15 14:41:12 +00:00
// when the ERI server operator changes the security requirements. The ERI server
// operator owns the data -- we have to respect their rules. We skip this source.
//
if ( eriSourceRequirements is { AllowedProviderType : ProviderType . SELF_HOSTED } )
{
2026-06-21 13:16:37 +00:00
this . logger . LogWarning ( $"The ERI source '{source.Name}' (id={source.Id}) requires a self-hosted or organization-trusted provider. We skip this source." ) ;
2025-02-15 14:41:12 +00:00
return null ;
}
//
// Case: The ERI source allows any provider type. The data source configuration is correct.
// Accept it.
//
if ( eriSourceRequirements is { AllowedProviderType : ProviderType . ANY } )
return source ;
//
// Case: Missing rules. We skip this source. Better safe than sorry.
//
2026-06-21 13:16:37 +00:00
this . logger . LogWarning ( $"The ERI source '{source.Name}' (id={source.Id}) was filtered out due to missing rules." ) ;
2025-02-15 14:41:12 +00:00
return null ;
//
2026-06-21 13:16:37 +00:00
// Case: The data source requires a trusted provider. We want to use a trusted provider.
2025-02-15 14:41:12 +00:00
// There is no issue with this source. Accept it.
//
2026-06-21 13:16:37 +00:00
case DataSourceSecurity . SELF_HOSTED when usingTrustedProvider :
2025-02-15 14:41:12 +00:00
return source ;
//
2026-06-21 13:16:37 +00:00
// Case: The data source requires a trusted provider. We want to use an untrusted provider.
2025-02-15 14:41:12 +00:00
// We skip this source.
//
2026-06-21 13:16:37 +00:00
case DataSourceSecurity . SELF_HOSTED when ! usingTrustedProvider :
this . logger . LogWarning ( $"The data source '{source.Name}' (id={source.Id}) requires a self-hosted or organization-trusted provider. We skip this source." ) ;
2025-02-15 14:41:12 +00:00
return null ;
//
// Case: The data source did not specify a security policy. We skip this source.
// Better safe than sorry.
//
case DataSourceSecurity . NOT_SPECIFIED :
this . logger . LogWarning ( $"The data source '{source.Name}' (id={source.Id}) has no security policy. We skip this source." ) ;
return null ;
//
// Case: Some developer forgot to implement a security policy. We skip this source.
// Better safe than sorry.
//
default :
this . logger . LogWarning ( $"The data source '{source.Name}' (id={source.Id}) was filtered out due unknown security policy." ) ;
return null ;
}
}
2026-08-05 13:05:17 +00:00
}