5đź‘Ť
A general answer is to find the distance between the document owner and a given contact. In Computer Science terms, this is a directed graph.
There’s a good article with some SQL queries that covers this topic at http://techportal.inviqa.com/2009/09/07/graphs-in-the-database-sql-meets-social-networks/. Rather than trying to summarize the entire article, here’s how to conceptualize the problem:
- Start with a blank piece of paper.
- Draw a dot somewhere on the page for each person (in this case, Users A, B, and C). In CS terms, this is a “node”.
- Draw an arrow from a user to all of their contacts. In CS terms, this is a “directed edge”, or an “arc”.
- This isn’t explicit in the question, but it looks like User C must be a contact of User B, or a contact another of User A’s other contacts (since User A can read C2 and C4).
- So in this case, you would draw from User A -> User B, and User B -> User C.
As an aside, if being a “contact” is mutual, you can draw a line segment (or bidirectional arrow) instead of an arrow. In CS terms, this would be an “undirected” vs. a “directed” graph. Facebook relationships are an undirected relationship; if someone is my friend, then I am also their friend. By contrast, if someone is in my Outlook address book, I’m not necessarily in theirs. So this is a directed relationship.
As more users are added to the drawing, you’ll notice that a user’s contacts are one step away, and their contacts-of-contacts are two steps away. But you can only travel in the direction of the arrow.
So the problem for contacts is, “How do I find all nodes whose graph distance is one?” And the question for contacts-of-contacts is, “How do I find all nodes whose graph distance is two?”. Although “two or less” is probably more appropriate, since you’d expect direct contacts to have access to all of the “contacts-of-contacts” content.
For the general case, there are some SQL queries described in the article that might provide some insight. But for your specific need, I’d consider just using some joins.
Let’s consider a Users
table, with primary key id
along with its other fields, and a HasContact
table which has only two columns: userId
and contactId
. We’ll assume that User A has id 1, User B is 2, and User C is 3. HasContact has rows (1, 2) and (2, 3) to represent the relationships described above.
A pretty simple set of SQL joins can produce a list of all friends, or all friends-of-friends.
The following query would return all IDs of a User’s contacts:
SELECT contact.id
FROM Users "user"
LEFT JOIN Relationships "rel"
ON user.id = rel.userid
LEFT JOIN Users "contact"
ON rel.contactId = contact.id
WHERE user.id = $id_of_current_user
If you know the user IDs, an authorization query could be quite simple:
SELECT count(*)
FROM Relationships "rel"
WHERE rel.userid = $document_owner_user_id
AND rel.contactid = $id_of_current_user
If the query returns 0, then we know that the current user is not one of the document owner’s contacts.
We can update that second query to indicate whether a user is a contact-of-a-contact:
SELECT count(*)
FROM Relationships "rel_1"
INNER JOIN Relationships "rel_2"
ON rel_1.contactId = rel_2.userId
WHERE rel_1.userid = $document_owner_user_id
AND rel_2.contactid = $id_of_current_user
This should return nonzero, as long as there are entries in the Relationships table such that ($document_owner_user_id, X)
and (X, $id_of_current_user)
both exist. Otherwise, it will return zero.
I know this is a long and somewhat indirect answer, so please comment if you have any questions.
7đź‘Ť
Unfortunately Django’s authorization system does not allow you to assign permissions per object, only per class. Here I assume that each of your “Document” is an instance of a model class.
There are, however, reusable apps that greatly simplify this task. Have a look at django-guardian or other packages that work on object (or row) level.
- Readonly fields in the django admin/inline
- Django delete cache with specific key_prefix
- How to redirect users to a specific url after registration in django registration?
- Django admin inline with custom queryset
- How to make UUID field default when there's already ID field
3đź‘Ť
What you basically need is to Limit access to logged-in users that pass a test. But the part with “contacts of contacts” can lead to very complicated sql-queries. And I suggest you to rethink that requirement. (I have lots of good friends whom I like and trust. But they have all kinds of weird people as friends …)
3đź‘Ť
What you need is an access control list(ACL) which will change according to each users network. ACL as well as object based permissions is not available in standard django.contrib.auth
module. I’ve actually implemented an ACL in Django, But it’s class based not object based. In the context of your application it’d work like User A
can view anyones documents as long as he is in a certain authorized group(say Admins
group). But it can be made to work like User A
can view User B
s documents If User A
is in the Contacts
group for User B
.
I can explain how do it in terms of model structure for a customized auth app. The User
and Permission
model will be the same as standard Django auth app(You can copy it from models.py). Then you need a model to represent different levels of permissions(Everyone, ContactsOfContact, Contact, Myself). Doing this is simple as adding another field to the standard django auth groups model. This field will be a foreign key to the same model, Which will represent the group each group will inherit from. Now the model looks like this,
class SocialGroup(models.Model):
name = models.CharField(unique=True, max_length=150)
parent = models.ForeignKey('self')
Now you can add relationships such as Contacts
group can view everything that Everyone
group can view by setting Everyone
group as the parent to Contacts
group. Note that there’s no foreign key relationship with Permission
model. Next we need a way to specify the relationships between users and groups.
class Relationship(models.Model):
user = models.ForeignKey(User)
related_user = models.ForeignKey(User)
related_by = models.ForeignKey(SocialGroup)
With this model we can say User A
(related_user) is related to User B
(user) by being in User B
s Contacts
(related_by) group. Then we need a way to specify permission for each document.
class DocumentPermissions(models.Model):
document = models.ForeignKey(Document)
group = models.ForeignKey(SocialGroup)
Permission = models.ForeignKey(Permission)
Now we can say Contacts
group has can_view
permission for Document B1
. Note that this model should go to wherever the Document model is located NOT to auth models.py in our new auth app. That’s it, Now all we have to do is to write the logic to find permissions for a given user for a given document. So here’s what we need to do when a User B
tries to view Document A1
,
- First check which person the document belongs to(
User A
) - Then find the relationship group between owner and the one trying access the document from Relationship model(Contacts).
- Then check
DocumentPermissions
to see whether that group or any group that it inherits from has permissions to view the document.
This logic can be implemented in a new authentication backend(which will use our new auth models) for Django which can go into the new auth app. You can check has_perm
function in standard Django model backend. That’s all you need to do. All the decorators and stuff will still work.
- Making Django admin display the Primary Key rather than each object's Object type
- Fail to push to Heroku: /app/.heroku/python/bin/pip:No such file or directory
- Django 'ManagementForm data is missing or has been tampered with' when saving modelForms with foreign key link
- Django-cms: urls used by apphooks don't work with reverse() or {% url %}
0đź‘Ť
You could include a “friends of friends” field. It would be a very big table (say, 200*200*N = 40,000*N … or really huge if you have no limit to friends, and someone is friends with a million people – that’s 1 million people with 1 million FoF) but it would be easier than hitting the database 200 times per view.
- Django: How can I find which of my models refer to a model
- Django admin – how to get all registered models in templatetag?
- In Django admin, can I require fields in a model but not when it is inline?