Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TenantAccessMiddleware & Authentification #666

Open
killer24vrccp opened this issue Aug 28, 2024 · 12 comments
Open

TenantAccessMiddleware & Authentification #666

killer24vrccp opened this issue Aug 28, 2024 · 12 comments
Labels
type: docs Related to documentation and information.

Comments

@killer24vrccp
Copy link

I have a problem with TenantAccessMiddleware. When user authentificated in public after in my public I receive this error: 'WSGIRequest' object has no attribute 'tenant'

SHARED_APPS = [
    "django_tenants",

    "tenant_users.permissions",
    "tenant_users.tenants",

    'app.generic.business',  # Tenant Model
    'app.generic.account',  # User model
    
    'django.contrib.auth',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.admin',
    'django.contrib.staticfiles',
    'django.contrib.admindocs',
    'django.contrib.sitemaps',
    'django.contrib.contenttypes',
]

TENANT_APPS = [
    "django.contrib.auth",
    "django.contrib.contenttypes",
    "django.contrib.sessions",
    "django.contrib.messages",
    "django.contrib.staticfiles",

    "tenant_users.permissions",
]

MIDDLEWARE = [
    'django_tenants.middleware.main.TenantMainMiddleware',
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.locale.LocaleMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',
    'django_user_agents.middleware.UserAgentMiddleware',
    'tenant_users.tenants.middleware.TenantAccessMiddleware',
]

AUTHENTICATION_BACKENDS = (
    # 'django.contrib.auth.backends.ModelBackend',
    "tenant_users.permissions.backend.UserBackend",
)

TENANT_MODEL = 'business.Business'

TENANT_DOMAIN_MODEL = 'business.Domain'

DATABASE_ROUTERS = (
    'django_tenants.routers.TenantSyncRouter',
)

Account model

class Account(UserProfile):
    first_name = models.CharField(max_length=50, verbose_name=_('First name'))
    last_name = models.CharField(max_length=50, verbose_name=_('Last name'))

    picture_profile = models.ImageField(upload_to='pool/', null=True, blank=True, verbose_name=_('Picture'))
    gender = models.IntegerField(default=Gender.MALE, choices=Gender.choices, verbose_name=_('Gender'), blank=True)

Tenant/Domain Model

class Business(TenantBase):
    name = models.CharField(_('Business Name'), max_length=255, unique=True)
    schema_name = models.CharField(max_length=63, unique=True, db_index=True,
                                   validators=[_check_schema_name], verbose_name=_('Schema Name'), blank=True)

    created_on = models.DateField(auto_now_add=True)

    def save(self, *args, **kwargs):
        # assignation to business_name slugify for schema_name field
        if not self.schema_name:
            self.schema_name = slugify(self.name)

        super().save(*args, **kwargs)

class Domain(DomainMixin):
    pass

When I'm on my tenant and I try to login i'm stay not authentificated. I have add this settings SESSION_COOKIE_DOMAIN = ".localhost". I'm in localhost for dev.

@Dresdn
Copy link
Collaborator

Dresdn commented Aug 28, 2024

The django-tenants middleware is responsible for adding the tenant key on the request object.

There are reasons it won't do that (see the try/except earlier in the method), so I would suggest adding a debug point in the process_request() and see why that key isn't being set.

@thubamamba
Copy link

Hey @killer24vrccp, the error is caused by the ordering in the middleware (weird, I know :P). Just ensure the tenant_user middleware comes after the django auth middleware like so:

"django.contrib.auth.middleware.AuthenticationMiddleware",
"tenant_users.tenants.middleware.TenantAccessMiddleware",

@Dresdn
Copy link
Collaborator

Dresdn commented Sep 2, 2024

Good catch @thubamamba! Yes, the tenant_users middleware requires the AuthenticationMiddle since it's checking for authentication status.

We should probably document this in the docs and middleware docstring. I did originally write this as a starting point since a few people were asking, but by no means is it meant to fit every use case.

@Dresdn Dresdn added the type: docs Related to documentation and information. label Sep 2, 2024
@killer24vrccp
Copy link
Author

Hey @thubamamba I have try but I have the same error. I don't knwo why.

image
image

Environment:


Request Method: GET
Request URL: http://localhost:8000/en/

Django Version: 4.2.15
Python Version: 3.10.11
Installed Applications:
['jazzmin',
 'django_tenants',
 'tenant_users.permissions',
 'tenant_users.tenants',
 'app.generic.business',
 'app.generic.account',
 'django.contrib.contenttypes',
 'django.contrib.auth',
 'django.contrib.sessions',
 'django.contrib.messages',
 'django.contrib.humanize',
 'django.contrib.admin',
 'django.contrib.staticfiles',
 'django.contrib.admindocs',
 'django.contrib.sitemaps',
 'rosetta',
 'django_user_agents',
 'rest_framework',
 'rest_framework.authtoken',
 'app.public.user_sessions',
 'app.public.core',
 'app.public.changelog',
 'app.generic.address',
 'app.generic.module',
 'bootstrap5',
 'app.api',
 'django_htmx',
 'crispy_forms',
 'crispy_bootstrap5',
 'django_countries',
 'parler']
Installed Middleware:
['django_tenants.middleware.main.TenantMainMiddleware',
 'django.middleware.security.SecurityMiddleware',
 'django.contrib.sessions.middleware.SessionMiddleware',
 'django.middleware.locale.LocaleMiddleware',
 'django.middleware.common.CommonMiddleware',
 'django.middleware.csrf.CsrfViewMiddleware',
 'django.contrib.auth.middleware.AuthenticationMiddleware',
 'tenant_users.tenants.middleware.TenantAccessMiddleware',
 'django.contrib.messages.middleware.MessageMiddleware',
 'django.middleware.clickjacking.XFrameOptionsMiddleware',
 'django_user_agents.middleware.UserAgentMiddleware']



Traceback (most recent call last):
  File "D:\Projet\CVS_tenant\.venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Projet\CVS_tenant\.venv\lib\site-packages\tenant_users\tenants\middleware.py", line 44, in __call__
    if not self.has_tenant_access(request):
  File "D:\Projet\CVS_tenant\.venv\lib\site-packages\tenant_users\tenants\middleware.py", line 61, in has_tenant_access
    return request.user.tenants.filter(id=request.tenant.id).exists()

Exception Type: AttributeError at /en/
Exception Value: 'WSGIRequest' object has no attribute 'tenant'

@Dresdn
Copy link
Collaborator

Dresdn commented Sep 3, 2024

Exception Value: 'WSGIRequest' object has no attribute 'tenant'

Did you check out my prior comment? That tenant attribute is added to the request object by django-tenants middleware.

I would bet something is not setup right with django-tenants as this line will cause request.tenant not to be created. For the hostname you're trying to navigate to, is the domain setup? Try this from the shell:

from django_tenants.utils import get_tenant_domain_model
hostname = "whatever-you're-using"
domain_model = get_tenant_domain_model()
domain_model.objects.select_related('tenant').get(domain=hostname)

If that doesn't come up with anything, you'll get the error you're seeing.

@killer24vrccp
Copy link
Author

I don't know if that's can help me but I use this command for approvisionning public tenant : python .\manage.py create_public_tenant --domain_url public.localhost --owner_email [email protected]

@killer24vrccp
Copy link
Author

killer24vrccp commented Sep 3, 2024

I have setup few print : localhost

Debug:

localhost
Internal Server Error: /favicon.ico
Traceback (most recent call last):
  File "D:\Projet\CVS_tenant\.venv\lib\site-packages\django\core\handlers\exception.py", line 55, in inner
    response = get_response(request)
  File "D:\Projet\CVS_tenant\.venv\lib\site-packages\tenant_users\tenants\middleware.py", line 44, in __call__
    if not self.has_tenant_access(request):
  File "D:\Projet\CVS_tenant\.venv\lib\site-packages\tenant_users\tenants\middleware.py", line 61, in has_tenant_access
    return request.user.tenants.filter(id=request.tenant.id).exists()
AttributeError: 'WSGIRequest' object has no attribute 'tenant'
    def process_request(self, request):
        # Connection needs first to be at the public schema, as this is where
        # the tenant metadata is stored.

        connection.set_schema_to_public()
        try:
            hostname = self.hostname_from_request(request)
            print(hostname)
        except DisallowedHost:
            from django.http import HttpResponseNotFound
            return HttpResponseNotFound()

        domain_model = get_tenant_domain_model()
        try:
            tenant = self.get_tenant(domain_model, hostname)
            print(tenant)
        except domain_model.DoesNotExist:
            self.no_tenant_found(request, hostname)
            return

        tenant.domain_url = hostname
        request.tenant = tenant
        connection.set_tenant(request.tenant)
        self.setup_url_routing(request)

@Dresdn
Copy link
Collaborator

Dresdn commented Sep 3, 2024

I'm confused - did you not try the commands I suggested?

I don't know if that's can help me but I use this command for approvisionning public tenant : python .\manage.py create_public_tenant --domain_url public.localhost --owner_email [email protected]

You're creating a "domain" with the value public.localhost.

I have setup few print : localhost

Debug:

localhost

The domain you're accessing is localhost. Run the commands in my prior comment, or even Domain.objects.all(). If you're accessing localhost but the domain is set to public.localhost, then the django-tenants middleware won't find a match and will not create request.tenant, hence the failure.

@thubamamba
Copy link

I managed to get this working, the issue was indeed that there was no tenant being passed to the request. So, the first thing you have to ensure to do is create a public tenant.. check out the django-tenant docs or the django-tenant-users.

@killer24vrccp
Copy link
Author

@Dresdn Yes I have try. I have retry with python manage.py create_public_tenant domain_url localhost owner_email [email protected] And result of command:

>>> hostname = "localhost"            
>>> domain_model = get_tenant_domain_model()
>>> domain_model.objects.select_related('tenant').get(domain=hostname)
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "D:\Projet\CVS_tenant\.venv\lib\site-packages\django\db\models\query.py", line 637, in get
    raise self.model.DoesNotExist(
app.generic.business.models.Domain.DoesNotExist: Domain matching query does not exist.
>>> 

@Dresdn
Copy link
Collaborator

Dresdn commented Sep 4, 2024

Let's ensure we're on the same page. Can you confirm the following?

  1. You created the public tenant by using: python manage.py create_public_tenant --domain_url public.localhost --owner_email [email protected] I see you re-tried it, but was that a clean database?

  2. You're navigating to http://localhost:8000? If not, what "hostname" are you browsing to?

  3. Whatever that hostname is, is there a Domain object created for it? Update the script you tried in the last comment.

To your last note, if there is no Domain model, django-tenants isn't setup correctly. You can check by seeing what domains are there via Domain.objects.all() If you're trying to navigate to a URL that is not listed there, it will fail.

I would ensure you have everything setup as called out in the Installation Docs.

@killer24vrccp
Copy link
Author

killer24vrccp commented Sep 5, 2024

I have resolve the problem. I have juste use localhost then public.localhost. But raise a new problem. My tenant URL not work in my public tenant. Thats said NoReverseMatch 'interface' is not a registered namespace.

I use {% if request.tenant %}{% url 'interface.css-min %}{% endif %}.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
type: docs Related to documentation and information.
Projects
None yet
Development

No branches or pull requests

3 participants