From a2d8c408c4b750046b1e0b8abb81f4601918accb Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Mon, 22 Jun 2026 14:14:47 +0100 Subject: [PATCH] Revert `2ea214cf` SQL permission verification from `release/4.5.2` (#144) * Initial plan * Revert "feat(connector): add SQL permission verification during connection testing (#139)" This reverts commit 2ea214cfb354a6dfd317f0c17bbcde9610e67ade. --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- docs/4.5.2-release-notes.md | 3 - docs/minimum-sql-permissions.md | 152 -------------- .../Connector/SqlServerConnector.cs | 190 +----------------- 3 files changed, 3 insertions(+), 342 deletions(-) delete mode 100644 docs/4.5.2-release-notes.md delete mode 100644 docs/minimum-sql-permissions.md diff --git a/docs/4.5.2-release-notes.md b/docs/4.5.2-release-notes.md deleted file mode 100644 index af70a28..0000000 --- a/docs/4.5.2-release-notes.md +++ /dev/null @@ -1,3 +0,0 @@ -### Features -- Added SQL permission verification during connection testing. When a user explicitly tests a connection (not during automatic health checks), the connector now validates that the configured SQL user has all required permissions including: SELECT on INFORMATION_SCHEMA views, CREATE TABLE and CREATE TYPE at the database level, and ALTER, EXECUTE, SELECT, INSERT, UPDATE, DELETE on the target schema, as well as EXECUTE on sp_rename. This helps identify permission issues upfront rather than encountering errors during data export operations. - diff --git a/docs/minimum-sql-permissions.md b/docs/minimum-sql-permissions.md deleted file mode 100644 index 76f6726..0000000 --- a/docs/minimum-sql-permissions.md +++ /dev/null @@ -1,152 +0,0 @@ -# Minimum Required SQL Server Permissions - -This document describes the minimum SQL Server permissions required for the CluedIn SQL Server Connector to operate correctly. - -## Overview - -The SQL Server Connector requires specific permissions to create tables, store data, and manage export streams. When configuring a SQL Server connection, ensure the database user has all the permissions listed below. - -## Quick Setup - -For a quick setup, you can use the following SQL script to grant all required permissions to your connector user: - -```sql --- Replace 'YourDatabaseName' with your target database name --- Replace 'YourSchema' with your target schema (default is 'dbo') --- Replace 'CluedInConnector' with your SQL user name - -USE [YourDatabaseName]; -GO - --- Grant database-level permissions -GRANT CREATE TABLE TO [CluedInConnector]; -GRANT CREATE TYPE TO [CluedInConnector]; - --- Grant schema-level permissions -GRANT ALTER ON SCHEMA::[YourSchema] TO [CluedInConnector]; -GRANT EXECUTE ON SCHEMA::[YourSchema] TO [CluedInConnector]; -GRANT SELECT ON SCHEMA::[YourSchema] TO [CluedInConnector]; -GRANT INSERT ON SCHEMA::[YourSchema] TO [CluedInConnector]; -GRANT UPDATE ON SCHEMA::[YourSchema] TO [CluedInConnector]; -GRANT DELETE ON SCHEMA::[YourSchema] TO [CluedInConnector]; -``` - -## Detailed Permission Requirements - -### Database-Level Permissions - -| Permission | Purpose | -|------------|---------| -| **CREATE TABLE** | Required to create export tables when setting up new data streams | -| **CREATE TYPE** | Required to create table-valued types used for efficient bulk data operations | - -### Schema-Level Permissions - -These permissions must be granted on the schema where export tables will be created (default: `dbo`): - -| Permission | Purpose | -|------------|---------| -| **ALTER** | Required to create tables within the schema and modify table structure during upgrades | -| **EXECUTE** | Required to use table-valued types for bulk data operations | -| **SELECT** | Required to read data for existence checks and data retrieval | -| **INSERT** | Required to insert new records into export tables | -| **UPDATE** | Required to update existing records in Sync mode | -| **DELETE** | Required to remove records when entities are deleted in CluedIn | - -### System Procedure Permissions - -| Permission | Purpose | -|------------|---------| -| **EXECUTE on sp_rename** | Required for archive and rename operations on export streams | - -### Information Schema Access - -The connector also requires SELECT access to the following system views (typically granted by default): - -- `INFORMATION_SCHEMA.TABLES` - Used to check if tables exist -- `INFORMATION_SCHEMA.COLUMNS` - Used to verify table structure -- `INFORMATION_SCHEMA.SCHEMATA` - Used to verify the target schema exists - -## Permission Verification - -When you test a connection in CluedIn, the connector automatically verifies that all required permissions are in place. If any permissions are missing, you will receive a detailed error message listing the specific permissions that need to be granted. - -> **Note:** Permission verification only runs when you explicitly test a connection. It does not run during automatic health checks to avoid impacting existing installations. - -## Alternative: Using db_owner Role - -If fine-grained permissions are not required for your environment, you can simplify setup by adding the user to the `db_owner` role: - -```sql -USE [YourDatabaseName]; -GO - -ALTER ROLE db_owner ADD MEMBER [CluedInConnector]; -``` - -> **Warning:** The `db_owner` role grants full control over the database. Only use this option if your security policies allow it. - -## Using a Custom Schema - -If you prefer to isolate CluedIn export tables in a dedicated schema, you can create a custom schema and grant permissions on it: - -```sql --- Replace 'YourDatabaseName' with your target database name --- Replace 'YourSchema' with your target schema (default is 'dbo') --- Replace 'CluedInConnector' with your SQL user name - -USE [YourDatabaseName]; -GO - --- Create a dedicated schema for CluedIn exports -CREATE SCHEMA [YourSchema] AUTHORIZATION [dbo]; -GO - --- Grant all required permissions on the custom schema -GRANT ALTER ON SCHEMA::[YourSchema] TO [CluedInConnector]; -GRANT EXECUTE ON SCHEMA::[YourSchema] TO [CluedInConnector]; -GRANT SELECT ON SCHEMA::[YourSchema] TO [CluedInConnector]; -GRANT INSERT ON SCHEMA::[YourSchema] TO [CluedInConnector]; -GRANT UPDATE ON SCHEMA::[YourSchema] TO [CluedInConnector]; -GRANT DELETE ON SCHEMA::[YourSchema] TO [CluedInConnector]; - --- Don't forget database-level permissions -GRANT CREATE TABLE TO [CluedInConnector]; -GRANT CREATE TYPE TO [CluedInConnector]; -``` - -Then configure the connector to use the same schema (for example, `YourSchema`) in the connection settings. - -## Troubleshooting - -### Common Permission Errors - -| Error Message | Solution | -|---------------|----------| -| "CREATE TABLE permission denied" | Grant `CREATE TABLE` permission at the database level | -| "ALTER permission denied on schema" | Grant `ALTER` permission on the target schema | -| "EXECUTE permission denied on object '...Type'" | Grant `EXECUTE` permission on the target schema | -| "The INSERT permission was denied" | Grant `INSERT` permission on the target schema | - -### Verifying Permissions - -You can verify the permissions granted to a user by running: - -```sql --- Check database-level permissions -SELECT * FROM sys.fn_my_permissions(NULL, 'DATABASE'); - --- Check schema-level permissions (replace 'YourSchema' with your schema name) -SELECT * FROM sys.fn_my_permissions('YourSchema', 'SCHEMA'); -``` - -## Security Considerations - -- **Principle of Least Privilege**: We recommend granting only the minimum required permissions listed above rather than using `db_owner`. -- **Dedicated User**: Consider creating a dedicated SQL user specifically for the CluedIn connector. -- **Custom Schema**: Using a custom schema helps isolate CluedIn tables and makes permission management easier. -- **Regular Audits**: Periodically review the permissions granted to the connector user to ensure they align with your security policies. - -## Questions? - -If you have questions about configuring SQL Server permissions for the CluedIn connector, please contact CluedIn support. diff --git a/src/Connector.SqlServer/Connector/SqlServerConnector.cs b/src/Connector.SqlServer/Connector/SqlServerConnector.cs index a9d0da6..e5bf4ca 100644 --- a/src/Connector.SqlServer/Connector/SqlServerConnector.cs +++ b/src/Connector.SqlServer/Connector/SqlServerConnector.cs @@ -328,28 +328,17 @@ public override async Task VerifyConnection(Execut schema = SqlTableName.DefaultSchema; } + var schemaExists = await _client.VerifySchemaExists(connectionAndTransaction.Transaction, schema); + await connectionAndTransaction.DisposeAsync(); + if (!schemaExists) { _logger.LogWarning("SqlServerConnector connection verification failed, schema '{schema}' does not exist", schema); return new ConnectionVerificationResult(false, "Schema does not exist"); } - // Only perform detailed permission checks when explicitly invoked by a user - // This avoids running heavy checks every 30 seconds during automatic health checks - // additionally existing installations may be working fine without all permissions, so we don't want to break them by enforcing permission checks in health checks - if (executionContext.Principal != null) - { - var permissionResult = await VerifyRequiredPermissions(connectionAndTransaction.Transaction, schema); - if (!permissionResult.Success) - { - return permissionResult; - } - } - - await connectionAndTransaction.DisposeAsync(); - return new ConnectionVerificationResult(true); } catch (Exception e) @@ -359,179 +348,6 @@ public override async Task VerifyConnection(Execut } } - private async Task VerifyRequiredPermissions(SqlTransaction transaction, string schema) - { - var missingPermissions = new List(); - - // Check SELECT permission on INFORMATION_SCHEMA.TABLES (required for table existence checks and GetContainers) - if (!await CheckPermission(transaction, "SELECT", "INFORMATION_SCHEMA", "TABLES")) - { - missingPermissions.Add("SELECT on INFORMATION_SCHEMA.TABLES"); - } - - // Check SELECT permission on INFORMATION_SCHEMA.COLUMNS (required for schema upgrades and GetContainers) - if (!await CheckPermission(transaction, "SELECT", "INFORMATION_SCHEMA", "COLUMNS")) - { - missingPermissions.Add("SELECT on INFORMATION_SCHEMA.COLUMNS"); - } - - // To create a table in a schema, user needs: - // 1. CREATE TABLE permission at the database level - // 2. ALTER permission on the schema - if (!await CheckDatabasePermission(transaction, "CREATE TABLE")) - { - missingPermissions.Add("CREATE TABLE on database"); - } - - // Check CREATE TYPE permission at the database level (required for table-valued parameters used in bulk operations) - if (!await CheckDatabasePermission(transaction, "CREATE TYPE")) - { - missingPermissions.Add("CREATE TYPE on database"); - } - - // Check ALTER permission on the schema (required for CREATE TABLE in schema and ALTER TABLE during upgrades) - if (!await CheckSchemaPermission(transaction, "ALTER", schema)) - { - missingPermissions.Add($"ALTER on schema [{schema}]"); - } - - // Check EXECUTE permission on the schema (required to execute table-valued types created in the schema) - if (!await CheckSchemaPermission(transaction, "EXECUTE", schema)) - { - missingPermissions.Add($"EXECUTE on schema [{schema}]"); - } - - // Check SELECT permission on the schema - if (!await CheckSchemaPermission(transaction, "SELECT", schema)) - { - missingPermissions.Add($"SELECT on schema [{schema}]"); - } - - // Check INSERT permission on the schema - if (!await CheckSchemaPermission(transaction, "INSERT", schema)) - { - missingPermissions.Add($"INSERT on schema [{schema}]"); - } - - // Check UPDATE permission on the schema - if (!await CheckSchemaPermission(transaction, "UPDATE", schema)) - { - missingPermissions.Add($"UPDATE on schema [{schema}]"); - } - - // Check DELETE permission on the schema - if (!await CheckSchemaPermission(transaction, "DELETE", schema)) - { - missingPermissions.Add($"DELETE on schema [{schema}]"); - } - - // Check EXECUTE permission on sp_rename (required for Archive and Rename operations) - if (!await CheckExecutePermission(transaction, "sp_rename")) - { - missingPermissions.Add("EXECUTE on sp_rename"); - } - - if (missingPermissions.Any()) - { - var errorMessage = $"Missing required SQL permissions: {string.Join(", ", missingPermissions)}"; - _logger.LogError("SqlServerConnector connection verification failed: {ErrorMessage}", errorMessage); - return new ConnectionVerificationResult(false, errorMessage); - } - - return new ConnectionVerificationResult(true); - } - - private async Task CheckPermission(SqlTransaction transaction, string permission, string schemaName, string objectName) - { - try - { - // Use QUOTENAME to safely escape identifiers and prevent SQL injection - const string commandText = "SELECT HAS_PERMS_BY_NAME(QUOTENAME(@SchemaName) + '.' + QUOTENAME(@ObjectName), 'OBJECT', @Permission)"; - - var command = transaction.Connection.CreateCommand(); - command.Transaction = transaction; - command.CommandText = commandText; - command.Parameters.AddWithValue("@SchemaName", schemaName); - command.Parameters.AddWithValue("@ObjectName", objectName); - command.Parameters.AddWithValue("@Permission", permission); - - var result = await command.ExecuteScalarAsync(); - return result is 1; - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to check permission {Permission} on [{Schema}].[{Object}]", permission, schemaName, objectName); - return false; - } - } - - private async Task CheckDatabasePermission(SqlTransaction transaction, string permission) - { - try - { - const string commandText = "SELECT HAS_PERMS_BY_NAME(DB_NAME(), 'DATABASE', @Permission)"; - - var command = transaction.Connection.CreateCommand(); - command.Transaction = transaction; - command.CommandText = commandText; - command.Parameters.AddWithValue("@Permission", permission); - - var result = await command.ExecuteScalarAsync(); - return result is 1; - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to check database permission {Permission}", permission); - return false; - } - } - - private async Task CheckSchemaPermission(SqlTransaction transaction, string permission, string schemaName) - { - try - { - // Use QUOTENAME to safely escape identifiers and prevent SQL injection - const string commandText = "SELECT HAS_PERMS_BY_NAME(QUOTENAME(@SchemaName), 'SCHEMA', @Permission)"; - - var command = transaction.Connection.CreateCommand(); - command.Transaction = transaction; - command.CommandText = commandText; - command.Parameters.AddWithValue("@SchemaName", schemaName); - command.Parameters.AddWithValue("@Permission", permission); - - var result = await command.ExecuteScalarAsync(); - return result is 1; - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to check schema permission {Permission} on schema [{Schema}]", permission, schemaName); - return false; - } - } - - private async Task CheckExecutePermission(SqlTransaction transaction, string procedureName) - { - try - { - // Use QUOTENAME to safely escape identifiers and prevent SQL injection - // sp_rename is in sys schema - const string commandText = "SELECT HAS_PERMS_BY_NAME(QUOTENAME('sys') + '.' + QUOTENAME(@ProcedureName), 'OBJECT', 'EXECUTE')"; - - var command = transaction.Connection.CreateCommand(); - command.Transaction = transaction; - command.CommandText = commandText; - command.Parameters.AddWithValue("@ProcedureName", procedureName); - - var result = await command.ExecuteScalarAsync(); - return result is 1; - } - catch (Exception ex) - { - _logger.LogWarning(ex, "Failed to check EXECUTE permission on {Procedure}", procedureName); - return false; - } - } - public override async Task CreateContainer(ExecutionContext executionContext, Guid connectorProviderDefinitionId, IReadOnlyCreateContainerModelV2 model) { await ExecuteWithRetryAsync(async () =>