Question

I have mssql2008 r2 sql server

The problem: User has some column permissions on the table. He could update some of the columns of the table (not all). We need to create UPDATE statement so that it will not violate permissions. Preferably without dynamic query.

Is there this ability in MSSQL server?

Was it helpful?

Solution

Without dynamic SQL (or dynamic query construction in the app or API layer)? I don't think it will be very pretty. The UPDATE command doesn't have any inherent knowledge of what permissions the user might have on the affected column(s). It is going to submit the query to the engine and hope for the best. If the user doesn't have permissions on all the columns, it's going to return an error, not try to circumvent that by altering the intended statement. I think this would actually be a very bad thing to continue with the update even though not all intended columns have been updated.

That all said, I suppose you could do something like this, but it is not going to be pretty at all - in fact it will be a lot easier if you are not relying on database principals:

DECLARE 
    @dpid INT = DATABASE_PRINCIPAL_ID(),
    @obj  INT = OBJECT_ID('dbo.foo'),
    @col  SYSNAME = N'bar';

UPDATE dbo.foo SET bar = CASE 
  WHEN EXISTS -- check they've been granted UPDATE at column or table level:
  (
    SELECT 1 
      FROM sys.database_permissions AS dp
      INNER JOIN sys.objects AS o 
        ON dp.major_id = o.[object_id]
      LEFT OUTER JOIN  sys.columns AS c
        ON dp.minor_id = COALESCE(c.column_id, 0)
      WHERE dp.grantee_principal_id = @dpid
      AND o.[object_id] = @obj
      AND (c.name = @col OR c.column_id IS NULL)
      AND dp.[permission_name] = 'UPDATE'
      AND dp.[state] = 'G' -- GRANT
  ) 
  AND NOT EXISTS -- since DENY trumps GRANT, make sure that doesn't also exist:
  (
    SELECT 1
      FROM sys.database_permissions AS dp
      INNER JOIN sys.objects AS o
        ON dp.major_id = o.[object_id]
      LEFT OUTER JOIN  sys.columns AS c
        ON dp.minor_id = COALESCE(c.column_id, 0)
      WHERE dp.grantee_principal_id = @dpid
      AND o.[object_id] = @obj
      AND (c.name = @col OR c.column_id IS NULL)
      AND dp.[permission_name] = 'UPDATE'
      AND dp.[state] = 'D' -- DENY
)
THEN @bar ELSE bar END
-- WHERE...
;

This isn't exactly what you're asking for; technically it updates the column but sets it to itself (so it will still be indicated as an updated column in a trigger, for example) but it prevents the input from being applied to the table. I also did not check against permissions granted in ways other than an explicit GRANT UPDATE or DENY UPDATE to the specified user or role - for example GRANT ALL, or permissions inherited by AD group membership, can complicate this. Of course it is not going to be much fun at all to manage this if you have multiple columns to check.

You may want to add other conditionals to the WHEN clause, e.g. to avoid the check for dbo (who ) or users you want to explictly bypass the check, you could have:

CASE 
  WHEN DATABASE_PRINCIPAL_ID() = 1 THEN @bar
  WHEN SUSER_SNAME = 'some_user' THEN @bar
  WHEN (...stuff from above...)
  ELSE bar
END
-- WHERE...
;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top