Permission denied when deleting entity
See original GitHub issueHello,
we are facing an issue in our application when deleting a User record from our production database. Apart from the Users table, there are other tables such as BillingInformation, Addresses and Activities related to the User. We are trying to delete a single User record from the database, and we are sure that the User has no Activities assigned. Every Activity record has a ProviderId property that is non-nullable and references the UserId in the Users table. We are connecting to the database using test_user account without access to the Activities table (REVOKE DELETE ON “Activities” FROM test_user). When the deletion transaction is committed, we get an error: “permission denied for table Activities”, although there are no Activities related to the User.
From the logs we can see multiple SQL queries being executed, most of them are related to the Address and BillingInformation and one of them is the delete query: DELETE FROM "Users"\r\nWHERE "Id" = @p0;"
We are hosting our database on AMAZON RDS and connecting using a connection string:
User ID=test_user;Password=OUR-PASSWORD;Server=OUR-SERVER;Port=5432;Database=our-database; SSL Mode=Require
Unfortunately, we were not able to reproduce this issue locally nor remotely using another AWS database (we copied the whole production database to another DB server for debugging purposes, but the error message can’t be reproduced, for unknown reasons).
Within the inner exception is a Where property that indicates that the Activities table is reached by the generated SQL query, even though the User being deleted is not linked to any Activity record. Note: the test_user is prevented from deleting Activities records, but not from other operations.
More details about the exception follow:
When the transaction is committed, the resulting exception is “An error occurred while updating the entries. See the inner exception for details.” The inner exception:
Message: 42501: permission denied for table Activities, Source: Npgsql, Statement: {DELETE FROM “Users” WHERE “Id” = $1} Where: SQL statement “DELETE FROM ONLY “public”.“Activities” WHERE $1 OPERATOR(pg_catalog.=) “ProviderId””
StackTrace:
At Npgsql.NpgsqlConnector.<>c__DisplayClass160_0.<<DoReadMessage>g__ReadMessageLong|0>d.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Npgsql.NpgsqlConnector.<>c__DisplayClass160_0.<<DoReadMessage>g__ReadMessageLong|0>d.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at Npgsql.NpgsqlDataReader.<NextResult>d__44.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at Npgsql.NpgsqlCommand.<ExecuteReaderAsync>d__102.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at Npgsql.NpgsqlCommand.<ExecuteDbDataReaderAsync>d__97.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.<ExecuteReaderAsync>d__17.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.<ExecuteReaderAsync>d__17.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at Microsoft.EntityFrameworkCore.Storage.RelationalCommand.<ExecuteReaderAsync>d__17.MoveNext()
at System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task)
at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)
at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult()
at Microsoft.EntityFrameworkCore.Update.ReaderModificationCommandBatch.<ExecuteAsync>d__29.MoveNext()
Our Entities models are as follows:
public class Activity {
[Key]
public long Id { get; set; }
public long ProviderId { get; set; }
public User Provider { get; set; } = null!;
public long? AddressId { get; set; }
public Address? Address { get; set; } = null!;
}
public class User {
[Key]
public long Id { get; set; }
public long? AddressId { get; set; }
public Address? Address { get; set; } = null!;
public long? BillingInformationId { get; set; }
public BillingInformation? BillingInformation { get; set; }
}
public class BillingInformation {
[Key]
public long Id { get; set; }
public long AddressId { get; set; }
public Address Address { get; set; } = null!;
}
public class Address {
[Key]
public long Id { get; set; }
public User? User { get; set; } = null!;
public BillingInformation? BillingInformation { get; set; } = null!;
}
The User we are trying to delete has one Address assigned and one BillingInformation. The Billing Information of the User has also an Address assigned. The User has no Activities assigned.
The test_user was created this way:
create role test_user with
LOGIN
NOSUPERUSER
NOCREATEDB
NOCREATEROLE
inherit
NOREPLICATION
connection limit -1
password '***'
grant usage on schema public to test_user;
grant all privileges on all tables in schema public to test_user;
revoke delete on "Activities" from test_user;
From the logs we were able to extract additional information:
// Excecuted SQL
DELETE FROM \"Address\"\r\nWHERE \"Id\" = @p3;"
DELETE FROM \"Address\"\r\nWHERE \"Id\" = @p1
DELETE FROM \"BillingInformation\"\r\nWHERE \"Id\" = @p2;"
DELETE FROM \"Users\"\r\nWHERE \"Id\" = @p0;"
// Entity tracker logs
{ "date": "2021-02-17 10:30:55.7664", "level": "Debug", "message": "The 'User' entity with key '{Id: 34617}' tracked by 'CmcContext' changed from 'Deleted' to 'Detached'." }
{ "date": "2021-02-17 10:30:55.7664", "level": "Debug", "message": "The 'Address' entity with key '{Id: 39353}' tracked by 'CmcContext' changed from 'Deleted' to 'Detached'." }
{ "date": "2021-02-17 10:30:55.7664", "level": "Debug", "message": "The 'BillingInformation' entity with key '{Id: 11975}' tracked by 'CmcContext' changed from 'Deleted' to 'Detached'." }
{ "date": "2021-02-17 10:30:55.7664", "level": "Debug", "message": "The 'Address' entity with key '{Id: 39354}' tracked by 'CmcContext' changed from 'Deleted' to 'Detached'." }
We have also tried to create a new database user - just to make sure the test_user is not misconfigured, but in the production application the exception is still thrown. Using a copy of our production database, we have also verified that no Activities are deleted, when the test_user has sufficient rights to delete from the Activities table.
Environment EF Core version: 3.1.11 Database provider: Npgsql.EntityFrameworkCore.PostgreSQL, v. 3.1.11 Target framework: .NET Core 3.1 IDE: Microsoft Visual Studio Enterprise 2019, Version 16.8.5
Issue Analytics
- State:
- Created 3 years ago
- Comments:12 (7 by maintainers)
Okay, I’ve finally got some time to take a look. And it’s very interesting, so: a) If the owner of the database attempts to delete a row with cascade delete on, the exception is always thrown b) If some user attempts to delete a row with cascade delete on, the exception is thrown only if there is anything to delete
Can’t really say that makes a lot of sense. But anyway, I’m closing this issue as this is mostly how PG behaves. @kejdajar @pikausp thank you for your research, I’m really curious why it was done so…
Made this repro:
And it always throws due to a lack of permissions to delete from table Posts. I can only assume the other database lacks the restrictions. Can you confirm they’re actually there?