Accessing the calling context in modules that use EXECUTE AS
In many occasions, marking a module (i.e. SP, trigger, etc.) with execute as can be really useful as it allows a controlled impersonation during the module execution; but at the same time there are many cases that it is necessary to access information using the caller's execution context (i.e. revert to the default behavior), for example, for ad-hoc auditing where the auditor keeps a table with information on who accessed the data.
For such cases there is a mechanism that allows to impersonate the calling context, effectively switching back and forth at will: EXECUTE AS CALLER.
EXECUTE AS CALLER will impersonate the calling context, but since we are just reverting to the original module calling convention (executing under the caller's context), there are no permission checks for this call.
In this case I think a quick example will be worth a lot more than an in-depth technical discussion, so I will jump directly to the demo, but feel free to ask any technical questions in the comments.
-- Create a application-specific user
-- The SP will impersonate this context
-- as it has access to the necessary resources
--
CREATE USER [ApplicationUser] WITHOUT LOGIN
go
-- Create a schema for the application
-- In this example, the application-specific user
-- will also be the owner of the schema
--
CREATE SCHEMA [ApplicationSchema] AUTHORIZATION [ApplicationUser]
go
-- SP that will run under the impersonated context
-- and then revert to the caller
--
CREATE PROC [ApplicationSchema].[Demo_1]
WITH EXECUTE AS 'ApplicationUser'
AS
-- Verify the context for the module,
-- Should be "ApplicationUser"
SELECT user_name() as 'Impersonated context',
user_id() as 'UserId';
-- At this point, we want to do some
-- operation as the calling context,
-- for example, ad-hoc auditing
-- So I will revert to the caller
--
EXECUTE AS CALLER;
-- Verify the context
--
SELECT user_name() as 'Impersonated context',
user_id() as 'UserId';
-- After permorming the actions under the caller's context,
-- go back to the application-impersonated context
REVERT;
-- Verify the context for the module once again,
-- Should be back to "ApplicationUser"
SELECT user_name() as 'Impersonated context',
user_id() as 'UserId';
go
-- SP that will run under the impersonated context
-- And then view the original session login
--
CREATE PROC [ApplicationSchema].[Demo_2]
WITH EXECUTE AS 'ApplicationUser'
AS
-- Verify the context for the module,
-- Should be "ApplicationUser"
SELECT original_login() as 'original_login',
user_name() as 'Impersonated context',
user_id() as 'UserId';
go
---------------------------------
-- Testing
---------------------------------
CREATE USER [RegularUser] WITHOUT LOGIN
go
-- Grant EXECUTE on all modules on the app schema
-- to this test user
GRANT EXECUTE ON SCHEMA::[ApplicationSchema] TO [RegularUser]
go
-- Impersonate this low-priv user and test both SPs
--
EXECUTE AS USER = 'RegularUser'
go
-- Expected output:
-- Impersonated context | UserId
------------------------------------
-- ApplicationUser | x
------------------------------------
-- RegularUser | y
------------------------------------
-- ApplicationUser | x
--
EXEC [ApplicationSchema].[Demo_1]
go
-- original_login | Impersonated context | UserId
-------------------------------------------------------------------
-- Original session login | ApplicationUser | x
--
-- NOTE: The
--
EXEC [ApplicationSchema].[Demo_2]
go
-- Current user name | UserId
------------------------------------
-- RegularUser | y
--
SELECT user_name() as 'Current user name',