How to use Row-Level Security to weed out deleted records - sql-server-2017

We're looking for help with an issue that I hope can be solved with row-level security. We have 160 tables feed by a data lake that are in turn used by another application. There is a field in all tables called IsDeleted that is 0 if the record is good and 1 if deleted. What we need is for any user except the system application called ETLapp, they should only see records where IsDeleted=0.
Using the Microsoft examples,
CREATE users
CREATE USER mylogin WITHOUT LOGIN;
CREATE USER yourlogin WITHOUT LOGIN;
CREATE USER ETLapp WITHOUT LOGIN;
GO
Then create table
CREATE SCHEMA SALES
GO
CREATE TABLE SALES.Orders
(OrderID int,
SalesRep nvarchar(50),
Product nvarchar(50),
Quantity smallint,
IsDeleted bit
);
Populate the Table
INSERT INTO SALES.Orders VALUES (1, 'Fred', 'Valve', 5, 0);
INSERT INTO SALES.Orders VALUES (2, 'Daphne', 'Wheel', 2, 0);
INSERT INTO SALES.Orders VALUES (3, 'Scooby', 'Pizza', 1, 1);
SELECT * from SALES.Orders;
Grant Access
GRANT SELECT ON Sales.Orders TO mylogin;
GRANT SELECT ON Sales.Orders to yourlogin;
GRANT SELECT ON Sales.Orders to ETLapp;
Here's the part I can't make work right.
CREATE FUNCTION rls.SalesPredicate(#UserName as sysname)
RETURNS TABLE
WITH SCHEMABINDING
AS RETURN SELECT 1 AS SalesPredicate_result
WHERE IsDeleted =0 OR UserName = 'ETLapp';
GO
It executes okay, but when I create the security policy, it won't accept username because username is not in the table, and if I pass it IsDeleted then we get all the rows.
CREATE SECURITY POLICY SalesFilter
ADD FILTER PREDICATE rls.SalesPredicate(UserName)
ON Sales.Orders
WITH (STATE=ON);
GO
user ETLapp should see all 3 records, but anyone else should only see the two where IsDeleted=0.

Related

Trigger creation/modification to ensure field equals insertion date

I have a table named Customer and the column in question is dbupddate. This column should contain the datetime of the query that resulted in the record bein inserted.
I have already made a default constraint to getdate():
CREATE TABLE [dbo].[customer]
(
[dbupddate] [DATETIME] NOT NULL
CONSTRAINT [DF_customer_dbupddate] DEFAULT (GETDATE()),...
but this does not prevent someone ofaccidentally entering an irrelevant value.
How can I ensure the column dbupddate has the insert datetime?
I guess the answer will contain a trigger. In this case, consider the following already existing trigger, that should not have its effects lost/modified in any way:
CREATE TRIGGER [dbo].[customer_ins_trig]
ON [dbo].[customer]
AFTER INSERT
AS
BEGIN
DELETE u
FROM transfer_customer_unprocessed u
WHERE EXISTS (SELECT 1 FROM inserted i WHERE i.code = u.code)
INSERT INTO transfer_customer_unprocessed (code, dbupddate)
SELECT code, dbupddate
FROM inserted
END
Maybe I could add some lines to that one to suit my needs? Or maybe create another one?
In the procedure which is inserting the data, just don't provide a variable for that column. Granted someone could open SSMS if they have the rights and update it, but you could restrict this with access too.
Additionally, you may want to look into rowversion if this is part of a larger initiative to track changes.
Here's a trigger that does what you want, I think. Note that the user cannot control content going into InsertDate.
This is a reasonable approach for keeping "last updated" info for your data. However, #scsimon if you are doing this for other reasons, ROWVERSION is worth exploring, does not require a trigger, and will be much more performant.
DROP TABLE IF EXISTS Test;
GO
CREATE TABLE Test (
Id INT NOT NULL ,
Content NVARCHAR(MAX) NOT NULL ,
InsertDate DATETIME NULL
);
GO
CREATE TRIGGER TR_Test
ON Test
AFTER INSERT, UPDATE
AS BEGIN
UPDATE t SET t.InsertDate = GETDATE() FROM Test t INNER JOIN inserted i ON i.Id = t.Id;
END;
GO
INSERT Test VALUES (1, '1', NULL), (2, '2', NULL), (3, '3', NULL);
SELECT * FROM Test;
GO
UPDATE Test SET Id = 4, Content = 4 WHERE Id = 1;
UPDATE Test SET Id = 5, Content = 5, InsertDate = NULL WHERE Id = 2;
SELECT * FROM Test;
GO

How to create trigger to check if table changed

I'm beginner and i trying to create trigger to check if table changed then i track the changed and insert it into another table
i created table called history and this is my code
create table history
(
ProjectNo INT ,
UserName NVARCHAR(50),
ModifiedDate date,
Budget_Old int,
Budget_New int,
)
and created this trigger
CREATE TRIGGER t1 on history
on project AFTER UPDATE
AS BEGIN
IF UPDATE(Project.Pnumber,Project.Budget)
INSERT INTO dbo.history (ProjectNo , Username,ModifiedDate,Budget_Old,Budget_New)
SELECT
d.ProjectNo, suser_name() ,GETDATE(),d.Budget,i.budget
FROM
Deleted d , inserted i
where d.projectno = i.projectno
END
i think my if statment is wrong but what i should do to make my query run right
to insert this values in history table ? plz help me and sorry for bad English
Triggers have access to two logical tables that have an identical structure to the table they are defined on which us Project as I assume.
INSERTED, which is the new data to go into the table
DELETED, which is the old data the is in the table
So You can get the old values in this way:
CREATE TRIGGER t1
ON dbo.Project AFTER UPDATE
AS
INSERT INTO dbo.history (ProjectNo , Username)
SELECT d.ProjectNo , d.Username
FROM DELETED d
If you need to just have one record for specific project in the table history, then you would use inner join based on the projectNo and update the history table accordingly.
ITS WORKS BUDDY ENJOY!!!!!!!!!!!
CREATE TRIGGER tRIGGERNAME1
BEFORE UPDATE on history
FOR EACH ROW
BEGIN
INSERT INTO dbo.history (ProjectNo ,Budget_Old,Budget_New)
VALUES(:OLD.ProjectNo,:OLD.Budget_Old,:NEW.Budget_New);
END;

Track logins sql

I have a job that runs on my server to track the last login on my sql server so I can audit inactive users.
First I enabled track successful logins on the server
I created a table called TRACK_LOGIN and run this daily:
INSERT INTO dbadb.dbo.TRACK_LOGIN (logontime, logon, loginname) EXEC XP_READERRORLOG 0, 1, [LOGIN SUCCEEDED FOR USER]
Now that that information is in the TRACK_LOGIN table I query DISTINCT out of that table and put it in another table with this query:
SELECT DISTINCT SUBSTRING(LOGINNAME,PATINDEX('%''%',LOGINNAME)+1,PATINDEX('%.%',LOGINNAME)-PATINDEX('%''%',LOGINNAME))FROM TRACK_LOGIN
I would also like to query the column logontime along with the distinct login so I have a list daily of who logs in and what time they login?
Please help modify the select statement above to include distinct logins along with their last logontime.
This is intended on allowing me to look back at my users last login and eliminate those on the server that are not used.
I understand that you have already put some real effort into make this work, but I would still suggest to go with a different approach that yields a much cleaner result:
Logon triggers
This will allow you to insert the right type of data into your table and will not force you to parse back log entries.
This example here shows a different use case, but I think you will have no issue to port it to your own problem.
CREATE TRIGGER MyLogonTrigger ON ALL SERVER FOR LOGON
AS
BEGIN
IF SUSER_SNAME() <> 'sa'
INSERT INTO Test.dbo.LogonAudit (UserName, LogonDate, spid)
VALUES (SUSER_SNAME(), GETDATE(), ##SPID);
END;
GO
ENABLE TRIGGER MyLogonTrigger ON ALL SERVER;
Ok to track logins I did this, I abounded the first method and implemented this:
First I created a table called logonaudit:
CREATE TABLE LogonAudit
(
AuditID INT NOT NULL CONSTRAINT PK_LogonAudit_AuditID
PRIMARY KEY CLUSTERED IDENTITY(1,1)
, UserName NVARCHAR(255)
, LogonDate DATETIME
, spid INT NOT NULL
);
I then had to grant insert on that table:
GRANT INSERT ON dbadb.dbo.LogonAudit TO public;
I created another table called auditloginresults:
create table auditLoginResults
(
AuditID INT,
Username NVARCHAR(255),
LogonDate DATETIME,
SPID INT
);
I then created a trigger to log all logins and times to the first table LogonAudit. I had to create a logon called login_audit and allow it to insert into my tables. I then had to use the origional_login() to log the users login, if you dont do this it will block all logins that are not sa
CREATE TRIGGER MyLogonTrigger
ON ALL SERVER WITH EXECUTE AS 'login_audit'
FOR LOGON
AS
BEGIN
INSERT INTO DBADb.dbo.LogonAudit (UserName, LogonDate, spid)
VALUES (ORIGINAL_LOGIN(), GETDATE(), ##SPID);
END;
Now I created a job (you will need to create a job to run at a specific time with this code, This is not the code for the job just the code you would run in your job) to query the first table LogonAudit and put the results into the auditloginResults table, after that step I cleaned out the first table LogonAudit by running another step to delete data in the first table. Im not going to post the job to keep the threat clean but here is what is run in the job
The create job step 1--------------------------------------------------------
INSERT INTO DBADb.dbo.auditLoginResults
SELECT I.*
FROM DBADb.[dbo].[LogonAudit] AS I
INNER JOIN
(SELECT UserName, MAX([logondate]) AS MaxDate
FROM DBADb.[dbo].[LogonAudit]
GROUP BY UserName
) AS M ON I.logondate = M.MaxDate
AND I.UserName = M.UserName
`
-----NOW create job to purge the logonaudit table step 2
DELETE FROM dbadb.dbo.auditLoginResults;
-----now create a stored procedure to execute this will query the auditloginreaults and provide you the last login of everyone that has ever logged into the database
SELECT I.*
FROM DBADb.[dbo].[auditLoginResults] AS I
INNER JOIN
(SELECT UserName, MAX([logondate]) AS MaxDate
FROM DBADb.[dbo].[ auditLoginResults]
GROUP BY UserName
) AS M ON I.logondate = M.MaxDate
AND I.UserName = M.UserName

Four (4) Sub Queries

Is there any possible way to do this kind of sub query:
DELETE (INSERT (SELECT (INSERT)))
I know how to: INSERT (SELECT):
INSERT INTO r (u_id,role)
VALUES ((SELECT u_id FROM USER WHERE email="mumair1992#gmail.com"),'Agent');
My problem is:
User is present in request table when he verifies his account, system must have to:
Create user in user table
Create user role in role table
Delete user from request table
The reason you can do INSERT INTO .... SELECT ... is that the SELECT is being used as the input into the INSERT query.
However, an INSERT query doesn't return anything in that way. You are much better off just writing 3 distinct queries like this:
--Create user in user table
INSERT INTO UserTable VALUES (...)
--Create user role in role table
INSERT INTO UserRoles VALUES (...)
--Delete user from request table
DELETE FROM Requests WHERE ...
You could even wrap that all in a transaction to ensure all or none of the queries run:
BEGIN TRAN
--Create user in user table
INSERT INTO UserTable VALUES (...)
--Create user role in role table
INSERT INTO UserRoles VALUES (...)
--Delete user from request table
DELETE FROM Requests WHERE ...
COMMIT
I suspect also that you are wanting to use the ID of the user that you've created. To do this, assuming your UserTable has an IDENTITY column, you can use the SCOPE_IDENTITY function:
BEGIN TRAN
--Create user in user table
INSERT INTO UserTable VALUES (...)
DECLARE #UserID INT
SET #UserID = SCOPE_IDENTITY()
--Create user role in role table
INSERT INTO UserRoles (UserID, RoleID) VALUES (#UserID, ...)
--Delete user from request table
DELETE FROM Requests WHERE ...
COMMIT

Trigger that creates a row in another table that is referenced by current table

I have 2 tables User and Account. I'd like to have a trigger that creates an account automatically when a user is created. Here is my code:
alter trigger Add_user on [user] for insert as
begin
insert into [account] (name) values ('Main')
declare #newAccountId int, #insertedId int
set #newAccountId = (select scope_identity())
set #insertedId = (select id from inserted)
update [user]
set accountId = #newAccountId
where id = #insertedId
end
I want to have AccountId in the User table be not null however when I try and create a new user it won't let me and I get an error complaining about the not null AccountId column :(
If you make [user].AccountId nullable, it should work.
Also consider following things:
does [account] table contain only column "name"? I.e. is it global
for all users? Then why create new account for each user? If it's
user-specific then add [account].[userId] column.
I would recommend to write stored procedure instead of trigger (first create
account record then user record), it's more explicit and safe. Be
careful with triggers, it's likely to be a surprise for other
developers that inserting user also creates account.