Skip to content

Instantly share code, notes, and snippets.

@binki
Last active June 28, 2021 00:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save binki/fa7c0cc31e0624aabc862fda82888462 to your computer and use it in GitHub Desktop.
Save binki/fa7c0cc31e0624aabc862fda82888462 to your computer and use it in GitHub Desktop.
Permissions for SqlDependency

Permissions for SqlDependency

Issues With Original Post

This is derived from the original post. However, the original post has two issues. Both of these issues are related to the SqlDependencySchemaOwner user it creates in the database. This user owns and executes the stored procedures which SqlDependency creates dynamically.

Microsoft archived that forum and even the original author cannot update the post. The original author has reposted this to stackoverflow.

You can see the improved version of the script below.

Default Schema

The first issue is that SqlDependencySchemaOwner’s default database schema is not defined. The default schema of the user is dbo. This schema is used when resolving unqualified identifiers. If you haven’t noticed, the stored procedure created by SqlDependency contains a lot of unqualified identifier references. Let us show an actual example with indentation and comments pulled from the C# source into the SQL added for readability:

CREATE PROCEDURE [SqlQueryNotificationStoredProcedure-25fcb454-7c62-4eae-bd41-952277c603fe] AS BEGIN
  BEGIN TRANSACTION;
  RECEIVE TOP(0) conversation_handle FROM [SqlQueryNotificationService-25fcb454-7c62-4eae-bd41-952277c603fe];
  IF (SELECT COUNT(*) FROM [SqlQueryNotificationService-25fcb454-7c62-4eae-bd41-952277c603fe] WHERE message_type_name = 'http://schemas.microsoft.com/SQL/ServiceBroker/DialogTimer') > 0 BEGIN
    if ((SELECT COUNT(*) FROM sys.services WHERE name = 'SqlQueryNotificationService-25fcb454-7c62-4eae-bd41-952277c603fe') > 0)
      DROP SERVICE [SqlQueryNotificationService-25fcb454-7c62-4eae-bd41-952277c603fe];
    if (OBJECT_ID('SqlQueryNotificationService-25fcb454-7c62-4eae-bd41-952277c603fe', 'SQ') IS NOT NULL)
      DROP QUEUE [SqlQueryNotificationService-25fcb454-7c62-4eae-bd41-952277c603fe];
    -- Don't need conditional because this is self
    DROP PROCEDURE [SqlQueryNotificationStoredProcedure-25fcb454-7c62-4eae-bd41-952277c603fe];
  END
  COMMIT TRANSACTION;
END

Now, when the procedure is created by SqlNotificationUser, whose default schema is SqlDependency, the fully qualified name of the procedure ends up being SqlDependency."SqlQueryNotificationStoredProcedure-25fcb454-7c62-4eae-bd41-952277c603fe". Because the QUEUE object referenced by the RECEIVE statement already exists, the reference to the queue is resolve as the procedure is defined, allowing it to be found within the SqlDependency schema. However, because the procedure does not exist yet, the procedure referenced by DROP PROCEDURE uses Deferred Name Resolution. This means that it is not resolved in the context of the caller who is running CREATE PROCEDURE—instead it will be resolved by the user the procedure executes as. Because they are VIEW queries and dynamic functions, the OBJECT_ID() check will also run and resolve objects in the context of the user the procedure executes as. As stated above, the user these are being executed by is SqlDependencySchemaOwner which has a default schema of dbo. As a result, when the cleanup clause runs, you will see an error like this in your Event Viewer logs and the procedure will stick around even though the system’s cleanup mechanism was triggered:

The activated proc '[SqlDependency].[SqlQueryNotificationStoredProcedure-25fcb454-7c62-4eae-bd41-952277c603fe]' running on queue 'dcxMain40test.SqlDependency.SqlQueryNotificationService-25fcb454-7c62-4eae-bd41-952277c603fe' output the following: 'Cannot drop the procedure 'SqlQueryNotificationStoredProcedure-25fcb454-7c62-4eae-bd41-952277c603fe', because it does not exist or you do not have permission.'

This is because, even though the error message itself proves that the procedure exists, the DROP PROCEDURE is effectively searching for and not finding dbo."SqlQueryNotificationStoredProcedure-25fcb454-7c62-4eae-bd41-952277c603fe" which, indeed, does not exist.

This can be addressed by setting the default schema of the SqlDependencySchemaOwner user. This also fixes the OBJECT_ID() check.

ALTER USER SqlDependencySchemaOwner WITH DEFAULT_SCHEMA = SqlDependency;

Service Deletion Permission

The SqlDependencySchemaOwner user which executes the cleanup procedure has sufficient permission to remove both the QUEUE object and the PROCEDURE object because these objects are both in the SqlDependency schema which that user owns. However, the SERVICE object is a database-wide object and cannot be associated with a schema. The creator of the SERVICE, SqlDependencyUser in this case, can remove it because it is the owner. However, the cleanup sproc needs to be able to both see and remove it to complete its cleanup steps. With the permissions granted by the original instructions, SqlDependencySchemaOwner cannot see any services in sys.services. Thus, it won’t even try to remove the service. This then results in an error when the cleanup procedure tries to clean up the queue:

The activated proc '[SqlDependency].[SqlQueryNotificationStoredProcedure-25fcb454-7c62-4eae-bd41-952277c603fe]' running on queue 'dcxMain40test.SqlDependency.SqlQueryNotificationService-25fcb454-7c62-4eae-bd41-952277c603fe' output the following: 'The queue 'SqlQueryNotificationService-25fcb454-7c62-4eae-bd41-952277c603fe' cannot be dropped because it is bound to one or more service.'

To address this, the SqlDependencySchemaOwner simply needs to be given permission to alter any services in the database.

GRANT ALTER ANY SERVICE TO SqlDependencySchemaOwner;

Required Permissions

When combined with the original post, a more complete listing of the permissions required to use SqlDependency is described by the following script:

USE YourDatabase;
GO

--create user for schema ownership
CREATE USER SqlDependencySchemaOwner WITHOUT LOGIN;
GO

--create schema for SqlDependency ojbects
CREATE SCHEMA SqlDependency AUTHORIZATION SqlDependencySchemaOwner;
GO

--set the default schema of the user which will execute the cleanup sproc so that it can resolve objects created by SqlNotificationUser
ALTER USER SqlDependencySchemaOwner WITH DEFAULT_SCHEMA = SqlDependency;

--set the default schema of minimally privileged user to SqlDependency
ALTER USER SqlNotificationUser WITH DEFAULT_SCHEMA = SqlDependency;

--grant user control permissions on SqlDependency schema
GRANT CONTROL ON SCHEMA::SqlDependency TO SqlNotificationUser;

--grant cleanup user ability to see and cleanup services
GRANT ALTER ANY SERVICE TO SqlDependencySchemaOwner;

--grant user impersonate permissions on SqlDependency schema owner
GRANT IMPERSONATE ON USER::SqlDependencySchemaOwner TO SqlNotificationUser;
GO

--grant database permissions needed to create and use SqlDependency objects
GRANT CREATE PROCEDURE TO SqlNotificationUser;
GRANT CREATE QUEUE TO SqlNotificationUser;
GRANT CREATE SERVICE TO SqlNotificationUser;
GRANT REFERENCES ON
    CONTRACT::[http://schemas.microsoft.com/SQL/Notifications/PostQueryNotification] TO SqlNotificationUser;
GRANT VIEW DEFINITION TO SqlNotificationUser;
GRANT SELECT to SqlNotificationUser;
GRANT SUBSCRIBE QUERY NOTIFICATIONS TO SqlNotificationUser;
GRANT RECEIVE ON QueryNotificationErrorsQueue TO SqlNotificationUser;
GO
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment