Django User doesn’t inherit permissions from its group when using multidatabase

Hi !!

I have a project where I must use different databases, each representing a country. For that reason, users can be saved in different databases, depending on the user’s nationality. Thus, I found the following situation.

u2 = get_object_or_404(User.objects.using(‘colombia’), pk=u2_id)

u2.get_all_permissions()

set()

g2 = u2.groups.all()[0]

g2.permissions.all()

<QuerySet [<Permission: auth | group | Can add group>]>

u2.get_group_permissions()

set()

u2.get_group_permissions(g2)

set()

Clearly, Django User does not inherit permission from its groups when the user belongs to no default database.

I know that implementing some method in the User model to inherit the permissions can solve this situation. However, I want to raise the above situation to see If I am doing something wrong or if Django needs to fix the problem with the method “get_group_permissions()”.

You should create all users in one database. If you need to, create a profile-style model in the country-specific database. But you’re best off by storing all users and permissions in one location.

Note, if you absolutely want to divide your user environment among multiple databases, then be prepared to rewrite portions of Django’s authentication code to account for that, because Django itself isn’t designed to support that kind of structure.

Thank you for taking some time and answering me. I already did some custom authentications to support users from multiple databases. It seems working fine so far. However, I would like to follow your suggestion about storing user information in one location and using profiles in countries’ databases, but with this option, I do not know how to do a proper relationship between the entities User and Profiles. To the best of my knowledge, It is not possible to do a relationship between entities that come from different databases. In my project, I am using PostgreSQL. I read some articles that state that by using MySql it is possible to do a fake relationship, but this option is only available for MySql.

from rest_framework import authentication
from rest_framework import HTTP_HEADER_ENCODING, exceptions
from django.conf import settings
from django.utils.translation import gettext as _

class BearerAuthentication(authentication.TokenAuthentication):    
    '''    
    Simple token based authentication using utvsapitoken.    
    Clients should authenticate by passing the token key in the 'Authorization'    
    HTTP header, prepended with the string 'Bearer '.  
    For example:    Authorization: Bearer 956e252a-513c-48c5-92dd-bfddc364e812   
    '''    
    keyword = ['token','bearer']    
    def authenticate(self, request):        
        auth = authentication.get_authorization_header(request).split()        
        if not auth:            
            return None        
        if auth[0].lower().decode() not in self.keyword:            
            return None
        if len(auth) == 1:            
            msg = ('Invalid token header. No credentials provided.')            
            raise authentication.exceptions.AuthenticationFailed(msg)        
        elif len(auth) > 2:            
            msg = ('Invalid token header. Token string should not contain spaces.')            
            raise authentication.exceptions.AuthenticationFailed(msg)        
        try:            
            token = auth[1].decode()        
        except UnicodeError:            
            msg = ('Invalid token header. Token string should not contain invalid characters.')            
            raise authentication.TokenAuthentication.exceptions.AuthenticationFailed(msg)        
        return self.authenticate_credentials(token)
    
    def authenticate_credentials(self, key):
        model = self.get_model()
        token = None
        for database in settings.DATABASES.keys():
            try:
                token = model.objects.using(database).select_related('user').get(key=key)
                break
            except model.DoesNotExist:
                pass
        if token is None:
            raise exceptions.AuthenticationFailed(_('Invalid token.'))
        
        if not token.user.is_active:
            raise exceptions.AuthenticationFailed(_('User inactive or deleted.'))

        return (token.user, token)

In the code above, I modified the toke authenticator of rest django. As stated before, I have users in different databases. Therefore, the authenticator has to look for the token value in all databases to identify a user. It works fine so far, however, I do not know how Django creates that token, and I do know if using different databases, the token value could be the same, i.e., Django creates a toke_1 for the user_1 in the database_1 and also creates another token, let say token_2 for the user_2 in database_2, but the token_1 and token_2 have the same value. If that could happen, I would have some problems.

You are correct, it would not be a “proper” relationship. It would be more of an ad-hoc relationship.

However, my opinion remains that that is going to be a far better Django-style solution architecturally than trying to disperse user data across multiple databases.