I have a web application which currently only supports PostgreSQL as a backend and has it's own user management. Connection to the database is done using a generic user account using Postgres auth mechanisms. This application should now get a audit log for some created and changed data hopefully based on triggers and stored procedures only and for performance reasons it would be nice if everything works async and independent from the web application.
There is one main problem: We want to know which user of the web application made some changes or created data, but this piece of information is normally not available in stored procedures out of the box because we connect to Postgres using a generic account. What I would like to avoid doing is calling the stored procedures in the web application and provide user ids that way, as this would mean to add the relevant calls in may places in the web application. Besides that we make updates to the data model directly using the database without the web application, meaning we would need the triggers in any case.
Currently the web application is working with a transaction started on every request and committed on its end. Therefore I though of creating a temporary table within the transaction in every request which always gets the current user id of the request which should be available for the stored procedures executed by the triggers on created or changed data.
Some things I don't know are if the stored procedures executed can even access the current transaction of the database and therefore are able to retrieve the current user id from the temporary table? The table is not part of the changed data the trigger was created for. How would performance be affected if each request creates a temporary table to store just one Integer in the worst case? If the trigger is able to access the temporary table, the request may finish at any time because it's work is done or of errors. How would that affect a trigger which would have access to the temporary table which only exits within the transaction which gets committed or reverted as the stored procedures want to read the user id?
Is there any other way with which I can map a user id to some unique identifier of a transaction which the trigger has access to? Besides a temporary table, I could create a normal table in which I map user ids to transaction ids on start of every request but independent of the current transaction. If the trigger would now get the transaction id which was responsible for executing the trigger the stored procedures could use the transaction id to look up the user id which used the transaction.
Any thoughts? Thanks!
I've added a question about trigger lifetime and execution context which fits to this question:
execution context of database trigger in PostgreSQL
A temporary table seems overkill when you could just use a session setting.
Up to version 9.1, custom session variables needed to be declared in postgresql.conf through the
custom_variable_classes parameter, which made it a bit cumbersome, deployment-wise.
Since 9.2, it's no longer necessary. So you could just issue at connect time:
SET myapp.myusername='foobar';
and then in a trigger or in fact anywhere within the session, SELECT current_setting('myapp.myusername'), or SHOW myapp.myusername would get back the value.
Related
I'm a little lost and need some guidance on how to approach this feature I'd like to add.
Many operations I use require retrieving data from a remote server. My goal is to be able to receive an email notification if new data has been added to the remote server.
I thought about creating a stored procedure that uses "openquery" and compare data to a local table with a conditional statement that will send out an email if there are differences. Then scheduling a job that will execute this stored procedure frequently. But this does not feel elegant at all...
If I understood your question correctly, all depends on the permissions.
If I was the owner of the system
Find out which job is adding data to the system. Modify the process (ETL/ SQL job etc.) to send you an email. (best way)
If you have create permissions on the remote system
Create an after insert trigger, see the first example here. Refer to this link as well. (2nd best way)
If you have just permissions to create linked server
Whatever you wrote/ you can bring the data from the server (just the primary keys from the table) and keep on checking that by creating a job for new primary keys if any by copying the data to local.
How to choose between these two: depends on the size of data. Second method mentioned in point 3 will work even without a linked server.
But you will have to run this again and again, I can't think of any other way. Set up a SQL job/ ETL to do this for you.
I am currently in the process of turning a single-tenanted application into a multi-tenanted application.
As far as the database design goes, I have chosen the 'Shared Database, Separate Schema' solution as described within this article.
Following the instruction provided; I am required to perform the following steps, each and every time a new tenant is introduced:
Create new Schema for tenant.
Create new database user with access to the newly created schema(and only this schema).
Create necessary tables within the newly created schema.
My question is, where should this logic be placed?
My initial thought was within a stored procedure, however I am unsure of this decision and looking for some clarification.
I would like you to look into this implementation from a deployment perspective too.
Considering the fact that executing a job in Sql would be faster or even raising a tenant created event which can run a job that will be creating the necessary db infrastructure. At the end, the job will call a rest end point your app that can activate the tenant, so that the tenant can get into the app and use.
There are many options to be considered in the approach because it will be fully distributed and prone to failures.
Is it possible to have SQL Server create a temp table inside a particular database upon a user connecting to the database in such a way that the connecting user is the only one with access to the contents in this table (or even better, the connecting user is the only one that can even see the table)?
I tried using a logon trigger (including using a 'with execute as caller' clause) but although this creates the temp table, the connecting user can never see it/select from it.
All of this has to run inside SQL Server and require no user interaction at all...
Basically, this is the scenario I want to support:
user connects
a temp table is created inside a particular DB inside SQL (by SQL, kicked off by establishing of the connection)
some specific information is populated inside the table
for the duration of the connection; the user has (Read) access to the contents in this table; the information in this table is used by a sub-system inside a particular database
user disconnects
the temp table and all its contents is dropped by SQL
Thanks
First thoughts:
modify your client code to create the table on connection? Then it can be done only when needed not all the time
use a common, persisted table with a SessionID based on a GUID? This will provide some audit + troubleshooting information too
use table value parameters to send data on demand rather than have any server-side caching
And what I'd probably do:
create the table when it's populated when I need it. The user can connect to the database for a variety of reasons (I assume). So "connection" should be decoupled from "CREATE TABLE".
Using temp tables for this would not be the right approach if your data access is properly designed to open a connection-do an operation/query-close the connection. The moment you closed the connection, the temp table would be destroyed. It would be better to use a view or stored procedure to filter the information to which the user should have access. The structure of that view will depend greatly on how users connect to the database. Do users connect to the database using their own personal windows authentication account or do they connect indirectly through another account like many web servers do?
IMO, the better approach is the second bullet point of gbn's answer: a common persisted table with an indicator as to the session or user.
I am writing a trigger to audit updates and deletes in tables. I am using SQL Server 2008
My questions are,
Is there a way to find out what action is being taken on a record without going through the selection phase of the deleted and inserted tables?
Another question is, if the record is being deleted, how do I record within the audit table the user that is performing the delete. (NOTE: the user connected to the database is a general connection string with a set user, I need the user who is logged into either a web app or a windows app)
Please help?
For part one, you can either set up separate triggers or have one trigger that checks the special tables INSERTED and DELETED to discriminate between updates and deletes.
For part two, there's no way around it in this case, you're going to have to get that username to the database somehow via your web/windows app. Unfortunately you can't communicate with the trigger itself, and with a generic connection string the DB doesn't have any idea who it's dealing with.
I've found that it can be helpful to add a "LastModifiedBy" column to the tables that you plan to audit so that you can store that info on the original tables themselves. Then your trigger just copies that info into the audit table. This is also nice because if you only need to know who the last person to touch something was you don't have to look in the audit table at all, just check that one column.
Consider this, if you don't actually delete records but add a field to mark them as deleted, you can get the user from the last modified. If you want to actually delete records then, you can have a nightly job that deletes in a batch not one at time. This could even be set up to flag if too many records are being deleted and not run.
The easiest way to do this so that nothing breaks is to rename the table, add the column IsDeleted as a bit field and then create a view with the same name the table was orginally called. The view will select all the records where isdelted is null.
Don't let anyone talk you out of using triggers for this. You don't want people who are doing unauthorized changes to be able to escape the auditing. With a trigger (and no rights to anyone except a production dba to alter the table in any way), then no one except the dba can delete without being audited. In a typical system with no stored procedures to limit direct table access, all too many people can usually directly affect a table opening it wide for fraud. People committing fraud do not typically use the application they are supposed to use to change the data. You must protect data at the database level.
When you write your triggers make sure they can handle multi-row inserts/updates/deletes. Triggers operate on the whole set of data not one row at a time.
As roufamatic said, you can either set up triggers specific to each action or you can check against the INSERTED and DELETED tables.
As for the deleting user, it is possible to pass that information into the trigger as long as the code in your application handles it. I encountered this requirement about a year ago with a client and the solution that I came up with was to use SET CONTEXT_INFO and CONTEXT_INFO() to pass the user name along. All of our database access was through stored procedures, so I just needed to add a line or two of code to the delete stored procedures to SET CONTEXT_INFO then I changed the delete triggers to get the user from CONTEXT_INFO(). The user name had to be passed as a parameter from the application of course. If you aren't using stored procedures you might be able to just do the SET CONTEXT_INFO in the application. I don't know how connection pooling might affect that method. Obviously, if someone does a delete outside of the application there wouldn't be a record of that unless you also separately captured the USERNAME() in your trigger (probably a good idea, although it wasn't necessary for our audit log, which was more for reporting than security).
There was a little bit of trickiness because CONTEXT_INFO is a binary string, but it didn't take long to get that all sorted out.
I'm afraid that I don't have any of the code handy since it was for a past client. If you run into any problems after going through the help for CONTEXT_INFO and SET CONTEXT_INFO then feel free to post here and I'll see what I can remember.
To find out what action is being taken you can use the INSERTED and DELETED tables to compare before and after values. There is no magic way to tell which user of a web app has made a change. The usual method is to have a modified column in your table and have the web app code populated this with the relevant username
I'm not sure how to formulate that question but:
you have a webpage
the webpage got a specific user/pass in the web.config for the connection string
on a webpage you ask for a user/pass that is connected to a table (id, name, pass)
the user is recognized with a valid user/pass and now you know the id from the table above
the user change some data in a table and that table got a trigger
from that trigger, how to retrieve the user id from step 4
Let's say the user is logged using the asp.net membership table
Use SET CONTEXT_INFO and CONTEXT_INFO() to pass out-of-band parameters. Your Web layer must ensure it sets this correctly on each connection it uses prior to calling into the database, which means one extra additional round-trip to the database.
In step 4, when you say that YOU know it, what you really mean is that your application knows what user id is logged in. Your application's authentication is completely separate from your database authentication (excepting maybe using windows authentication with SQL server, but I don't think that's what you're doing).
As KM mentions, you would need to pass the application user id to the trigger by means of a "LastUpdatedUserID" column or some such thing on the table being updated.
#KM, or move your users into AD and use integrated auth. No other option here.
you need to have a LastChgID column (or similar) on the table they are changing based on the web page user/password, then INSERTED.LastChgID will tell you. otherwise, you are out of luck.
EDIT
When you save the change, store the web apps user ID into the table's LastChgID column, this may require passing it into the stored procedure, or just SET that column in the UPDATE statement. When the trigger fires, INSERTED.LastChgID will have the web apps user ID.
Since the username is just data it is tough to capture via a trigger.
Option #1 is similar to what KM said and your developer would have to pass the username via a query and update an audit column in the database. and the trigger would grab that column vlue on updates and do what ever you want with it.
Option #2 would be to programatically create the user in SQL server or your windows domain structure, give it access to the application and then impersonate that user upon entry for subsequent logins. This would be an administrative maintenance issue, but the application users would then access the database using their unique ID instead of the one configured in web.config and all updates to the database are as that user instead of the generic one supplied in web.config.
Hope this helps.
As has already been suggested (by Remus Rusanu) using the SET CONTEXT_INFO for this means you don't have to add parameters on all your stored procs to do this. A similar question from myself can be found here:
SQL Server: Modifying the "Application Name" property for auditing purposes