Just trying to play around with permissions, I want to grant CREATE table permission to user but not ALTER or DROP. Moreover user should be able to create table only(no stored proc, no function)
grant alter on schema::dbo to demo_db_user
grant create table to demo_db_user
By using above command, user is able to alter table as well.
You could write a DDL-Trigger and prevent the ALTER TABLE if the current User is not member in a specific role (or whatever), similar like this (untested):
CREATE TRIGGER [ddl_tr_prvent_alter_table] ON DATABASE
FOR alter_procedure
as
begin
if User_Name() <> 'dbo'
RAISERROR ('go ahead, you must not change a table', 16, 1);
end
Related
I have the following issue:
I have two different databases, db1 and db2. I have an application that loads data into db2 or db3. db1 has a few tables that the application uses to determine behavior, including which db the application should load data into.
Users need to have write access to db1 to operate the application (there is a console application that writes to tables in db1, operating with windows authentication).
Users should not have DML privileges to db2 and db3, with the exception of a few predetermined operations. We grant AD groups database roles to control access from and organization perspective. Specifically, I'm trying to build a stored procedure in db1 that operators can use to reverse data loaded to db2 or db3 with appropriate logging.
I'm attempting to use create proc ... execute as owner to accomplish this, but it does not seem to be working when I try to hit tables in db2/db3 (I'm thinking that "execute as owner" operates on db level users an not server level logins?). The following causes a permission error stating that the owner (myself) does not have permissions to db2/db3.
use db1
go
create proc dbo.wrapper #recordid int
as begin
/*
capturing user
*/
declare #usr varchar(255) = SUSER_SNAME()
exec dbo.inner #usr , #recordid
end
use db1
go
create proc dbo.inner #usr varchar(255), #recordid int
with execute as owner
as begin
/*
logic to determine whether to update db2 or db3 goes here
*/
insert db2.rolled_back
select * , #usr from db2.transactions where id = #recordid
delete from db2.transactions where id = #recordid
insert db3.rolled_back
select * , #usr from db3.transactions where id = #recordid
delete from db3.transactions where id = #recordid
end
Is there a way to get this to work? I've heard that certificate signing could do this, does anyone have any experience using certificate users. Our DBA's would rather not have to maintain certificates, so if there is a way to get this to work without certificates that would be best.
Any advice would be helpful.
Thank You!
I'm going to cover the cross database chaining side of thing here. note that there are certainly security considerations when using this method. For example someone with permissions to create objects in one database can give themselves access to data in another database with the owner, when they themselves have no access to the other database. The security concerns, however, are out of scope of this answer.
Firstly, let's create a couple of test databases.
USE master;
GO
CREATE DATABASE Chain1;
CREATE DATABASE Chain2;
Now I'm going to CREATE a LOGIN, which is disable and make that the owner of these 2 databases. The databases having the same owner is important, as otherwise the chaining won't work.
CREATE LOGIN ChainerOwner WITH PASSWORD = N'SomeSecurePassword123';
ALTER LOGIN ChainerOwner DISABLE;
GO
ALTER AUTHORIZATION ON DATABASE::Chain1 TO ChainerOwner;
ALTER AUTHORIZATION ON DATABASE::Chain2 TO ChainerOwner;
I'm also going to create a LOGIN which we're going to use to test on:
CREATE LOGIN SomeUser WITH PASSWORD = N'SomeSecurePassword123';
Great, now I can create a few objects; a table in Chain1, a PROCEDURE in Chain2 that accesses the TABLE, and a USER in both databases for SomeUser. In Chain1 the USER will be given no permissions, and in Chain2 the user will be given the permsision to EXECUTE the PROCEDURE:
USE Chain1;
GO
CREATE TABLE dbo.SomeTable (I int IDENTITY,
S varchar(10));
INSERT INTO dbo.SomeTable (S)
VALUES ('abc'),
('xyz');
GO
CREATE USER SomeUser FOR LOGIN SomeUser;
GO
USE Chain2;
GO
CREATE PROC dbo.CrossDBProc #I int AS
BEGIN
SELECT I,
S
FROM Chain1.dbo.SomeTable
WHERE I = #I;
END;
GO
CREATE USER SomeUser FOR LOGIN SomeUser;
GO
GRANT EXECUTE ON dbo.CrossDBProc TO SomeUser;
GO
Great, all the objects are created, now let's try to EXECUTE that PROCEDURE:
EXECUTE AS LOGIN = 'SomeUser';
GO
EXEC dbo.CrossDBProc 1; --This fails
GO
REVERT;
GO
This fails, with a permission error:
The SELECT permission was denied on the object 'SomeTable', database 'Chain1', schema 'dbo'.
This is expected, as there is no ownership chaining. let's, therefore enable that now.
USE master;
GO
ALTER DATABASE Chain1 SET DB_CHAINING ON;
ALTER DATABASE Chain2 SET DB_CHAINING ON;
Now if I try the same again, the same SQL works:
USE Chain2;
GO
EXECUTE AS LOGIN = 'SomeUser';
GO
EXEC dbo.CrossDBProc 1; --This now works
GO
REVERT;
GO
This successfully returns the result set
I
S
1
abc
So, yes you can chain cross database, but it requires some set up, and (again) there are security considerations you need think about.
Clean up:
USE master;
GO
DROP DATABASE Chain1;
DROP DATABASE Chain2;
GO
DROP LOGIN ChainerOwner;
DROP LOGIN SomeUser;
I have a user who is a member of the db_DataReader role (and no other roles apart from public), and has been granted explicit execute permission on a scalar function, but when they use the function
select hbp_plant.CatComments(42)
they get
The EXECUTE permission was denied on the object 'CatComments', database 'HBDevSIMCOA', schema 'HBP_Plant'*.
How do I give them permission to call the function without giving them any ability to modify the database?
Does the function access tables in different schemas, other than hbp_plant?
Instead of adding the db user to the db_datareader role, grant SELECT (for the whole db) and execute permissions on the function:
--db user = myreadonlyuser
grant select to myreadonlyuser; --can read from tables, table valued functions, views..in all schemas
grant execute on hbp_plant.CatComments to myreadonlyuser;
Give Exec Permission on tablename. Try this.
USE HBDevSIMCOA;
GRANT EXEC ON hbp_plant.CatComments TO PUBLIC
you can refer below link
The EXECUTE permission was denied on the object 'xxxxxxx', database 'zzzzzzz', schema 'dbo'
Try this:
CREATE SCHEMA [hbp_plant];
GO
CREATE FUNCTION [hbp_plant].[CatComments] (#A INT)
RETURNS INT
AS
BEGIN
RETURN #A
END;
GO
CREATE USER [StackOverflow] WITHOUT LOGIN;
-- do not work
EXECUTE AS USER = 'StackOverflow';
SELECT [hbp_plant].[CatComments](5) ;
REVERT;
GRANT EXECUTE ON [hbp_plant].[CatComments] TO [StackOverflow];
-- work
EXECUTE AS USER = 'StackOverflow';
SELECT [hbp_plant].[CatComments](5) ;
REVERT;
DROP USER [StackOverflow];
DROP FUNCTION [hbp_plant].[CatComments]
DROP SCHEMA [hbp_plant];
I have come across some interesting behaviour in SQL Server 2019 - it does not seem to occur in earlier versions.
If, in database1, I call a function in the same database, which calls a function in database2, which SELECTS a table in database2, I get "The SELECT permission was denied on the object '{TableName}', database '{DbName}', schema 'dbo'."
If, instead, I call the function in database2 directly (without using a function in database1), the query executes successfully.
My question is: what is the logic behind this? I don't understand why I am allowed to read a table in another database, without the SELECT permission, through a function, but not when I call that function using a function in my current database! Is it due to the function preventing the passing on of permissions? I am assuming at the moment that this is an intended change - but I don't understand the logic behind it.
Below is some code demonstrating the behaviour in a simple way.
/*******************************************
SET UP
*******************************************/
CREATE DATABASE TestDb1
GO
CREATE DATABASE TestDb2
GO
CREATE LOGIN [TestLogin] WITH PASSWORD = '123456a.'
GO
--Create users in each database and add to roles.
USE TestDb1
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db1Role
ALTER ROLE Db1Role ADD MEMBER [TestUser]
USE TestDb2
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db2Role
ALTER ROLE Db2Role ADD MEMBER [TestUser]
--Create table in db1, but do no GRANTs on it.
USE TestDb1
CREATE TABLE dbo._testDb1Table (Col1 INT)
GO
--Create a function in db1, and GRANT EXECUTE.
CREATE FUNCTION dbo._TestDb1Function()
RETURNS INT
AS
BEGIN
DECLARE #Result INT = (SELECT TOP (1) Col1 FROM dbo._testDb1Table)
RETURN #Result
END
GO
GRANT EXECUTE ON dbo._TestDb1Function TO Db1Role
GO
--Create a function in db2, and GRANT EXECUTE.
USE TestDb2
GO
CREATE FUNCTION dbo._TestDb2Function()
RETURNS INT
AS
BEGIN
DECLARE #Result INT = (SELECT TestDb1.dbo._TestDb1Function())
RETURN #Result
END
GO
GRANT EXECUTE ON dbo._TestDb2Function TO Db2Role
GO
/*******************************************
TESTS
*******************************************/
USE TestDb2
--Querying TestDb1 by calling the TestDb2 function directly works.
EXECUTE AS LOGIN = 'TestLogin'
SELECT TestDb1.dbo._TestDb1Function()
REVERT
GO
--Querying TestDb2 through a scalar function in db2 doesn't work.
--The SELECT permission was denied on the object '_testDb1Table', database 'TestDb1', schema 'dbo'.
EXECUTE AS LOGIN = 'TestLogin'
SELECT dbo._TestDb2Function()
REVERT
GO
/*******************************************
TIDY UP
*******************************************/
USE [master]
DROP LOGIN [TestLogin]
DROP DATABASE TestDb1
DROP DATABASE TestDb2
This was a bug in SQL Server 2019, caused by scalar UDF inlining.
It was fixed in SQL Server 2019 CU9 (published in February 2021).
For more details see KB4538581.
As per helpful comments by GSerg and Larnu, this behaviour appears to be caused by the scalar UDF inlining feature, added in SQL Server 2019.
It can be fixed by disabling scalar UDF inlining at the database level, in the function definition, or using a query hint.
Edit: as per the answer by Razvan Socol, this has been fixed in SQL Sever 2019 CU9.
Here is the same code as given in the original question, but with these 3 possible solutions inserted into the appropriate places (commented out). Uncommenting any of these 3 solutions allows the script to run without error in SQL Server 2019.
/*******************************************
SET UP
*******************************************/
CREATE DATABASE TestDb1
CREATE DATABASE TestDb2
GO
--SOLUTION 1: Turn off scalar UDF inlining at the database level.
--USE TestDb2
--ALTER DATABASE SCOPED CONFIGURATION SET TSQL_SCALAR_UDF_INLINING = OFF;
GO
CREATE LOGIN [TestLogin] WITH PASSWORD = '123456a.'
GO
--Create users in each database and add to roles.
USE TestDb1
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db1Role
ALTER ROLE Db1Role ADD MEMBER [TestUser]
USE TestDb2
CREATE USER [TestUser] FOR LOGIN [TestLogin]
CREATE ROLE Db2Role
ALTER ROLE Db2Role ADD MEMBER [TestUser]
--Create table in db1, but do no GRANTs on it.
USE TestDb1
CREATE TABLE dbo._testDb1Table (Col1 INT)
GO
--Create a function in db1, and GRANT EXECUTE.
CREATE FUNCTION dbo._TestDb1Function()
RETURNS INT
AS
BEGIN
DECLARE #Result INT = (SELECT TOP (1) Col1 FROM dbo._testDb1Table)
RETURN #Result
END
GO
GRANT EXECUTE ON dbo._TestDb1Function TO Db1Role
GO
--Create a function in db2, and GRANT EXECUTE.
USE TestDb2
GO
CREATE FUNCTION dbo._TestDb2Function()
RETURNS INT
--SOLUTION 2: Turn off scalar UDF inlining for the function.
--WITH INLINE = OFF
AS
BEGIN
DECLARE #Result INT = (SELECT TestDb1.dbo._TestDb1Function())
RETURN #Result
END
GO
GRANT EXECUTE ON dbo._TestDb2Function TO Db2Role
GO
/*******************************************
TESTS
*******************************************/
USE TestDb2
--Querying TestDb1 by calling the TestDb2 function directly works.
EXECUTE AS LOGIN = 'TestLogin'
SELECT TestDb1.dbo._TestDb1Function()
REVERT
GO
--Querying TestDb2 through a scalar function in db2 doesn't work.
--The SELECT permission was denied on the object '_testDb1Table', database 'TestDb1', schema 'dbo'.
EXECUTE AS LOGIN = 'TestLogin'
SELECT dbo._TestDb2Function()
--SOLUTION 3: Turn off scalar UDF inlining for the query which calls the function.
--OPTION (USE HINT('DISABLE_TSQL_SCALAR_UDF_INLINING')); --Added line
REVERT
GO
/*******************************************
TIDY UP
*******************************************/
USE [master]
DROP LOGIN [TestLogin]
DROP DATABASE TestDb1
DROP DATABASE TestDb2
Is it possible to create an Azure SQL Database user who can:
SELECT against all tables and views
CREATE/ALTER/DROP views
But the user should NOT have the below permissions:
INSERT/UPDATE/DELETE/TRUNCATE TABLE against any table or view
CREATE/ALTER/DROP any table
What is the proper statements to achieve the above requirements, or it is not possible in Azure SQL?
I tried the below statements, without the last statement, the user cannot create views, but adding the last statement the user can drop tables.
CREATE USER [TestUser] WITH PASSWORD=N'NAvCO_h2eMuX', DEFAULT_SCHEMA=[dbo];
CREATE ROLE [TestRole];
ALTER ROLE [TestRole] ADD MEMBER [TestUser];
ALTER ROLE [db_datareader] ADD MEMBER [TestRole];
GRANT CREATE VIEW TO [TestRole];
GRANT SELECT ON SCHEMA :: dbo TO [TestRole];
GRANT ALTER ON SCHEMA :: dbo TO [TestRole];
When you run the ALTER ROLE [db_datareader] ADD MEMBER [TestRole] or GRANT SELECT ON SCHEMA :: dbo TO [TestRole], the user [TestUser] will be the readonly role db_datareader of the database. You can not do any other opreations like "INSERT/UPDATE/DELETE against any table or view".
But you add the ALTER permission to [TestUser], the user both will have SELECT and ALTER permission, the user will be the role like db_owner. Off course it has the permission to create view or drop tables.
It's impossible to create an Azure SQL Database user which both have the readonly permission and CREATE VIEW permission.
Reference: Database-Level Roles
Hope this helps.
As a workaround, you can create a Database DDL Trigger that does not allow that user to create, drop or alter any table or specific tables.
ALTER TRIGGER [TR_ProtectTables]
ON DATABASE
FOR DROP_TABLE, ALTER_TABLE, CREATE_TABLE
AS
DECLARE #eventData XML,
#uname NVARCHAR(50),
#sname NVARCHAR(100),
#oname NVARCHAR(100),
#otext VARCHAR(MAX),
#etype NVARCHAR(100),
#edate DATETIME
SET #eventData = eventdata()
SELECT
#edate=GETDATE(),
#uname=#eventData.value('data(/EVENT_INSTANCE/UserName)[1]', 'SYSNAME'),
#sname=#eventData.value('data(/EVENT_INSTANCE/SchemaName)[1]', 'SYSNAME'),
#oname=#eventData.value('data(/EVENT_INSTANCE/ObjectName)[1]', 'SYSNAME'),
#otext=#eventData.value('data(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]', 'VARCHAR(MAX)'),
#etype=#eventData.value('data(/EVENT_INSTANCE/EventType)[1]', 'nvarchar(100)')
IF #oname IN ('tblBananas','tblApples','tblOranges') and #uname = 'UserName'
BEGIN
DECLARE #err varchar(100)
SET #err = 'Table ' + #sname + '.' + #oname + ' is super duper protected and cannot be dropped.'
RAISERROR (#err, 16, 1) ;
ROLLBACK;
END
GO
ENABLE TRIGGER [TR_ProtectTables] ON DATABASE
GO
To avoid the user can INSERT/UPDATE/DELETE any data you can create a Role for that and add the user to that role.
CREATE ROLE [DenyWriteOnly]
EXEC sp_addrolemember N'db_datareader', N'DenyWriteOnly'
--explicitly DENY access to writing
EXEC sp_addrolemember N'DB_DenyDataWriter', N'DenyWriteOnly'
--now add the user to the role
EXEC sp_addrolemember N'DenyWriteOnly', N'MyDomain\YourUser'
I see you have edited your original question and you want to prevent the truncate table also. The easiest is to prevent a user from truncating any table is to enable Change Data Capture, but that feature is available for Azure Managed Instance.
If you don't have Azure Managed Instance then solution will be more elaborated since the minimum permission required for a TRUNCATE is ALTER. The other options are making tables to participate on transactional replication, making tables part of indexed views, or creating an empty table simply for the purpose of creating a dummy foreign key on each table that reference the empty table.
I want to execute code on user IDK:
CREATE OR REPLACE TRIGGER sal_trig
AFTER UPDATE OF status ON TAB1
FOR EACH ROW
WHEN (new.status = 'ZAK')
CALL log_sal(1, 5, 8);
I have following grants:
GRANT CREATE PROCEDURE TO IDK;
GRANT CREATE SEQUENCE TO IDK;
GRANT CREATE TABLE TO IDK;
GRANT CREATE TRIGGER TO IDK;
GRANT CREATE TYPE TO IDK;
GRANT UNLIMITED TABLESPACE TO IDK;
GRANT SELECT ON TAB1 TO IDK;
What grants i need more?
I won't get update/delete/insert on TAB1.
I am getting error: not sufficient privileges.
I created procedure from user IDK:
CREATE OR REPLACE PROCEDURE log_sal (
emp_id NUMBER,
old_sal NUMBER,
new_sal NUMBER
)
AS LANGUAGE JAVA
NAME 'CaseWatch.logSal(int, float, float)';
According to your comments the TAB1 table created by different User, so the table is in a different schema, this is the main key.
When you want to grant privileges to create a trigger on a table in different schema then you need to use:
GRANT CREATE ANY TRIGGER TO IDK;
CREATE TRIGGER => in fact giving permission to create a database trigger in the grantee's schema, in your case if TAB1 created by IDK then this privilege is enough.
Regarding the CREATE ANY TRIGGER here you can find some more interesting info:
https://docs.oracle.com/cd/B19306_01/server.102/b14200/statements_9013.htm
Grant create any trigger vs grant create trigger