-
Notifications
You must be signed in to change notification settings - Fork 349
Expand file tree
/
Copy pathDataSource.cs
More file actions
202 lines (178 loc) · 9.05 KB
/
Copy pathDataSource.cs
File metadata and controls
202 lines (178 loc) · 9.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
using System.Text.Json.Serialization;
using Azure.DataApiBuilder.Config.HealthCheck;
using Azure.DataApiBuilder.Config.NamingPolicies;
namespace Azure.DataApiBuilder.Config.ObjectModel;
/// <summary>
/// Contains the information needed to connect to the backend database.
/// </summary>
/// <param name="DatabaseType">Type of database to use.</param>
/// <param name="ConnectionString">Connection string to access the database.</param>
/// <param name="Options">Custom options for the specific database. If there are no options, this could be null.</param>
/// <param name="Health">Health check configuration for the datasource.</param>
public record DataSource(
DatabaseType DatabaseType,
string ConnectionString,
Dictionary<string, object?>? Options = null,
DatasourceHealthCheckConfig? Health = null)
{
[JsonIgnore]
public bool IsDatasourceHealthEnabled =>
Health is null || Health.Enabled;
[JsonIgnore]
public int DatasourceThresholdMs
{
get
{
if (Health == null || Health?.ThresholdMs == null)
{
return HealthCheckConstants.DEFAULT_THRESHOLD_RESPONSE_TIME_MS;
}
else
{
return Health.ThresholdMs;
}
}
}
/// <summary>
/// Configuration for user-delegated authentication (OBO) against the
/// configured database.
/// </summary>
[JsonPropertyName("user-delegated-auth")]
public UserDelegatedAuthOptions? UserDelegatedAuth { get; init; }
/// <summary>
/// Indicates whether user-delegated authentication is enabled for this data source.
/// </summary>
[JsonIgnore]
public bool IsUserDelegatedAuthEnabled =>
UserDelegatedAuth is not null && UserDelegatedAuth.Enabled;
/// <summary>
/// Converts the <c>Options</c> dictionary into a typed options object.
/// May return null if the dictionary is null.
/// </summary>
/// <typeparam name="TOptionType">The strongly typed object for Options.</typeparam>
/// <returns>The strongly typed representation of Options.</returns>
/// <exception cref="NotSupportedException">Thrown when the provided <c>TOptionType</c> is not supported for parsing.</exception>
public TOptionType? GetTypedOptions<TOptionType>() where TOptionType : IDataSourceOptions
{
HyphenatedNamingPolicy namingPolicy = new();
if (typeof(TOptionType).IsAssignableFrom(typeof(CosmosDbNoSQLDataSourceOptions)))
{
return Options is not null ?
(TOptionType)(object)new CosmosDbNoSQLDataSourceOptions(
Database: ReadStringOption(namingPolicy.ConvertName(nameof(CosmosDbNoSQLDataSourceOptions.Database))),
Container: ReadStringOption(namingPolicy.ConvertName(nameof(CosmosDbNoSQLDataSourceOptions.Container))),
Schema: ReadStringOption(namingPolicy.ConvertName(nameof(CosmosDbNoSQLDataSourceOptions.Schema))),
// The "raw" schema will be provided via the controller to setup config, rather than parsed from the JSON file.
GraphQLSchema: ReadStringOption(namingPolicy.ConvertName(nameof(CosmosDbNoSQLDataSourceOptions.GraphQLSchema))))
: default;
}
if (typeof(TOptionType).IsAssignableFrom(typeof(MsSqlOptions)))
{
return (TOptionType)(object)new MsSqlOptions(
SetSessionContext: ReadBoolOption(namingPolicy.ConvertName(nameof(MsSqlOptions.SetSessionContext))));
}
if (typeof(TOptionType).IsAssignableFrom(typeof(PostgreSqlOptions)))
{
return (TOptionType)(object)new PostgreSqlOptions(
SetSessionContext: ReadBoolOption(namingPolicy.ConvertName(nameof(PostgreSqlOptions.SetSessionContext))));
}
throw new NotSupportedException($"The type {typeof(TOptionType).FullName} is not a supported strongly typed options object");
}
private string? ReadStringOption(string option)
{
if (Options is not null && Options.TryGetValue(option, out object? value) && value is string stringValue)
{
return stringValue;
}
return null;
}
private bool ReadBoolOption(string option)
{
if (Options is not null && Options.TryGetValue(option, out object? value) && value is bool boolValue)
{
return boolValue;
}
return false;
}
[JsonIgnore]
public string DatabaseTypeNotSupportedMessage => $"The provided database-type value: {DatabaseType} is currently not supported. Please check the configuration file.";
}
public interface IDataSourceOptions { }
/// <summary>
/// The CosmosDB NoSQL connection options.
/// </summary>
/// <param name="Database">Name of the default CosmosDB database.</param>
/// <param name="Container">Name of the default CosmosDB container.</param>
/// <param name="Schema">Path to the GraphQL schema file.</param>
/// <param name="GraphQLSchema">Raw contents of the GraphQL schema.</param>
public record CosmosDbNoSQLDataSourceOptions(string? Database, string? Container, string? Schema, string? GraphQLSchema) : IDataSourceOptions;
/// <summary>
/// Options for MsSql database.
/// </summary>
public record MsSqlOptions(bool SetSessionContext = true) : IDataSourceOptions;
/// <summary>
/// Options for PostgreSql database.
/// </summary>
public record PostgreSqlOptions(bool SetSessionContext = true) : IDataSourceOptions;
/// <summary>
/// Options for user-delegated authentication (OBO) for a data source.
///
/// When OBO is NOT enabled (default): DAB connects to the database using a single application principal,
/// either via Managed Identity or credentials supplied in the connection string. All requests execute
/// under the same database identity regardless of which user made the API call.
///
/// When OBO IS enabled: DAB exchanges the calling user's JWT for a database access token using the
/// On-Behalf-Of flow. This allows DAB to connect to the database as the actual user, enabling
/// Row-Level Security (RLS) filtering based on user identity.
///
/// OBO requires an Azure AD App Registration (separate from the DAB service's Managed Identity).
/// The operator deploying DAB must set the following environment variables for the OBO App Registration,
/// which DAB reads at startup via Environment.GetEnvironmentVariable():
/// - DAB_OBO_CLIENT_ID: The Application (client) ID of the OBO App Registration
/// - DAB_OBO_TENANT_ID: The Directory (tenant) ID where the OBO App Registration is registered
/// - DAB_OBO_CLIENT_SECRET: The client secret of the OBO App Registration (not a user secret)
///
/// These credentials belong to the OBO App Registration, which acts as a confidential client to exchange
/// the incoming user JWT for a database access token. The user provides only their JWT; DAB uses the
/// App Registration credentials to perform the OBO token exchange on their behalf.
///
/// These can be set in the hosting environment (e.g., Azure Container Apps secrets, Kubernetes secrets,
/// Docker environment variables, or local shell environment).
///
/// Note: DAB-specific prefixes (DAB_OBO_*) are used instead of AZURE_* to avoid conflict with
/// DefaultAzureCredential, which interprets AZURE_CLIENT_ID as a User-Assigned Managed Identity ID.
/// At startup (when no user context is available), DAB falls back to Managed Identity for metadata operations.
/// </summary>
/// <param name="Enabled">Whether user-delegated authentication is enabled.</param>
/// <param name="Provider">The authentication provider (currently only EntraId is supported).</param>
/// <param name="DatabaseAudience">Audience used when acquiring database tokens on behalf of the user.</param>
public record UserDelegatedAuthOptions(
[property: JsonPropertyName("enabled")] bool Enabled = false,
[property: JsonPropertyName("provider")] string? Provider = null,
[property: JsonPropertyName("database-audience")] string? DatabaseAudience = null)
{
/// <summary>
/// Default duration, in minutes, to cache tokens for a given delegated identity.
/// With a 5-minute early refresh buffer, tokens are refreshed at the 40-minute mark.
/// </summary>
public const int DEFAULT_TOKEN_CACHE_DURATION_MINUTES = 45;
/// <summary>
/// Environment variable name for OBO App Registration client ID.
/// Uses DAB-specific prefix to avoid conflict with AZURE_CLIENT_ID which is
/// interpreted by DefaultAzureCredential/ManagedIdentityCredential as a
/// User-Assigned Managed Identity ID.
/// </summary>
public const string DAB_OBO_CLIENT_ID_ENV_VAR = "DAB_OBO_CLIENT_ID";
/// <summary>
/// Environment variable name for OBO App Registration client secret.
/// Used for On-Behalf-Of token exchange.
/// </summary>
public const string DAB_OBO_CLIENT_SECRET_ENV_VAR = "DAB_OBO_CLIENT_SECRET";
/// <summary>
/// Environment variable name for OBO tenant ID.
/// Uses DAB-specific prefix for consistency with OBO client ID.
/// </summary>
public const string DAB_OBO_TENANT_ID_ENV_VAR = "DAB_OBO_TENANT_ID";
}