Corvia / django-tenant-users

Adds global user authentication and tenant-specific permissions to django-tenants.
https://django-tenant-users.rtfd.io
MIT License
347 stars 65 forks source link

More complete example #2

Closed jgranek closed 7 years ago

jgranek commented 7 years ago

@Viatrak This looks like a nice addition to django-tenants, but do you have a more complete example than the dtu_test_project showing how to implement the authentication system with tenant specific apps? For example, a simple interface for each tenant to have their own addressbook? i.e.: Tenant1.mysite.com can have a different set of addresses than Tenant2.mysite.com, and users of both tenants can login using an email address from mysite.com and be redirected to their respective tenant subdomain. Any help is appreciated!

Viatrak commented 7 years ago

There shouldn't be anything special to do when using django-tenant-users. Take a look at the overview page (https://github.com/Corvia/django-tenant-users) to configure your project to use DTU.

A user will be able to log in directly to tenant1.mysite.com, tenant2.mysite.com, or to mysite.com and be redirected. Their authentication session becomes global. The user can switch between those different tenants (without having to log in again) and the appropriate permissions that the user has in the tenant will be applied.

I apologize if I'm overlooking something here. Is there a specific problem you've encountered?

Hope that helps.

jgranek commented 7 years ago

Do you have an example of a Login/registration interface? I think I understand that the User (or in this case TenantUser) model and the customer (Client) model must be shared and on the public tenant, but I’m having issues with the order of operations…

I’m trying to set up a Login/Registration page where every Company has its own Tenant, and each Company can have multiple Users (which belong to the same company tenant). I’m trying to set it up so that a new registration would first create a company (which creates a new tenant) and then registers users to that company/tenant, however it seems like in tenant_users the User must be created before the tenant (company) so that the Owner property of TenantBase can be set to the AUTH_USER_MODEL…is this correct?

What would be the right way to implement this then; for a new registration (new company) a User first signs up (all done on the public tenant), THEN they create a new company (new tenant)?

Sorry for all the questions but your example just doesn’t show any of the login/logout/registration stuff I’m trying to implement and I’m relatively new to django and multi-tenancy. I really appreciate all your help and feedback! Your package looks really nice, just trying to figure out how to properly implement it!

-- Cheers,

Justin Granek Phone: 778-549-7998 E-mail: justin.granek@gmail.com mailto:justin.granek@gmail.com

On Feb 3, 2017, at 7:06 PM, Viatrak notifications@github.com wrote:

There shouldn't be anything special to do when using django-tenant-users. Take a look at the overview page (https://github.com/Corvia/django-tenant-users https://github.com/Corvia/django-tenant-users) to configure your project to use DTU.

A user will be able to log in directly to tenant1.mysite.com, tenant2.mysite.com, or to mysite.com and be redirected. Their authentication session becomes global. The user can switch between those different tenants (without having to log in again) and the appropriate permissions that the user has in the tenant will be applied.

I apologize if I'm overlooking something here. Is there a specific problem you've encountered?

Hope that helps.

— You are receiving this because you authored the thread. Reply to this email directly, view it on GitHub https://github.com/Corvia/django-tenant-users/issues/2#issuecomment-277414082, or mute the thread https://github.com/notifications/unsubscribe-auth/ARjPzxu-Mlq_u869CxBZMOac_ZadPxpwks5rY-s2gaJpZM4L27IQ.

Viatrak commented 7 years ago

This is a good question because it highlights some nuances in DTU. In django-tenants, tenants are created first and then users are created inside. As a result, the notion of ownership over a tenant is impossible. That is to say... no user can exist 'globally' outside of a tenant -- they can only exist inside of a tenant after a tenant is created. In DTU, however, users ALL exist outside of individual tenants. Only a user's permissions 'live' inside the tenant space (and only the permissions for that tenant).

By having global users, it abstracts the idea of a user away from a company. User ownership over a tenant is kind of a natural extension of that abstraction. What if a user switches companies? What if a user belongs to multiple companies? What if a user wants to create multiple companies? A 'Company' really represents any type of organizational structure you can imagine being a tenant.

Depending on your situation, the workflow you proposed is a great way to do it (it's my preferred workflow for a lot of scenarios) -- have a user sign up first and then create 1 or more companies. Or when a user signs up, instead of creating a company, they join an existing one. For example, if the domain of their signup email address belongs to a company that's already in the system it could auto register them to that tenant. This last part wouldn't apply to domains like gmail/yahoo/etc but it would be nice for enterprisey situations where a business and its email addresses all fall under a fixed domain. Obviously in this case you would have to validate that they own the email address before automatically adding them to an organization. Email validation is always a good idea anyways.

However, if you don't want to go that route, you can simply have an administrator account (you) be the creator of all of the companies (tenants). Their user account can be created at the same time and just given permissions inside that tenant. See the 'Migrate and Create the Public Tenant' section to create the first user and public tenant. That same user could be the creator of all other tenants in your system.

Viatrak commented 7 years ago

Nothing in DTU uses DomainMixin from django-tenants so it is not imported in compat.py.

Can you elaborate on what the issue is and how this solves it? I am open to a pull request if there is a problem here.

jgranek commented 7 years ago

Ya sorry, i deleted the comment because I think it was my mistake..in compat.py you try to import django-tenants FIRST, and because I've been playing with both (tenants and tenant-schemas) it pulled that one, but in tenants they specify Domain as a separate model than Client; domain_url is specified in Domain and linked to tenant with another 'tenant' field, so your example of setting up the public schema wasn't working because it was looking for a domain_url field in Client. It was a simple fix to just import DomainMixin from django-tenant and create a Domain model (as per the django-tenant docs). Setting up the public tenant was pretty straightforward after that, just required the extra step of creating a Domain (as well as a client).

jgranek commented 7 years ago

Maybe I'm missing something obvious but I'm having issues creating users...I'm creating a new user as the following (after grabbing the corresponding values from a form):

user = TenantUser.objects.create_user(email=email, password=password, is_active=True)
            user.username = username
            user.first_name = f_name
            user.last_name = l_name
            user.save()

and the user is being created properly but I can't login with them...I think this must have to do with the permissions. I don't really understand what provisioning the tenant means but I'm guessing it's this line in your example that I'm missing, just not entirely sure how to use it

from tenants.tasks import provision_tenant
fqdn = provision_tenant("EvilCorp", "evilcorp", "admin@evilcorp.com")

Currently all users are being created in the public tenant, with the idea being that once the user is logged in they can create a new company, which corresponds to creating a new tenant (on a new subdomain). I can see all the users I'm creating from the admin panel (after logging in from my superuser account), but I can't login with them. I can't seem to login to the public tenant with my superuser either... something seems screwy...

Viatrak commented 7 years ago

I think the best path is to take small steps towards your goal. For example, I suggest getting a regular django project working with authentication/permissions, then making a project with django tenants/or django tenant schemas and get that working, and then graduate on to making a project with django tenant users with django tenant schemas or django tenants.

Provisioning a tenant means creating the tenant. A new schema in Postgres has to be made when you make a new tenant. In this new schema, all of the tables from the models for the tenant have to be made (migrated). If you look in provision_tenant function in tenant_users/tenants/task.py you will see the code that executes during the provisioning.

All users get created in the public tenant, always. Only permissions will get created inside a tenant. The user will have specific permissions then on a per-tenant basis. Permissions don't affect authentication, just what is permissible by an authenticated user.

There are a lot of ways to handle authentication (cookies/sessions, tokens, etc). Find an example with django.contrib.auth. I would suggest a read through the django.contrib.auth docs as well. For example, if a user has is_staff set to false, they will not have access to django admin.

Make sure you set the authentication backend properly.

Make sure to read the note about using django admin and set up multi-site if thats the route you go.

jgranek commented 7 years ago

Hey, I've had a lot more success implementing all this. Now I'm trying to set up all the permissions for different tenants etc, and I'm not sure I understand how to do this..it doesn't seem to be working currently. My tenant owners never have superuser or staff permissions' and I'm not sure how to specify the default roles...I've done something like this in my settings.py file...

TENANT_DEFAULT_ROLES = {
    'Admin' : [
        {
            'app': 'company',
            'permissions': ['view', 'add', 'change', 'delete'],
        },
        {
            'app': 'users',
            'permissions': ['view', 'add', 'change', 'delete'],
        },
    ],
    'User' : [
        {
            'app': 'company',
            'permissions': ['view'],
        },
        {
            'app': 'users',
            'permissions': ['view'],
        },
    ]
}

where 'company' and 'users' are two different apps. Am I doing this properly?

To create my tenant/domain and permissions I'm doing the following:

# Primary contact default
        primary_contact = TenantUser.objects.get(email=request.user.email)

# Create the client
            client = Client()
            client.schema_name = slugify(company_name)
            client.name = company_name
            client.owner = primary_contact
            client.save()

            # Create the domain
            domain = Domain()
            domain.tenant = client
            domain_url = slugify(company_name) + '.mysite.com'
            domain.domain = domain_url
            domain.is_primary = True
            domain.save()

             # Permissions
            client.create_roles(TENANT_DEFAULT_ROLES)
            client.assign_user_role(primary_contact, 'Admin', True)

Does this seem reasonable?...

Thanks again for the assistance!

Viatrak commented 7 years ago

That's great news!

A few things:

By default 'view' is not an available permission. You will have to add support for that if you want that granularity (see notes on the README page).

You should probably be using the provision_tenant() function (or a modified version of it). See definition here: https://github.com/Corvia/django-tenant-users/blob/master/tenant_users/tenants/tasks.py

Viatrak commented 7 years ago

Just FYI, I just pushed a new large version update (0.3) to the project that removes and decouples all roles/permission behavior from tenant_users package. Now the package is strictly focused on global tenant users and the permission system becomes stock Django permissions. This should add a lot more flexibility and simplify the integration of this package.

I am closing this issue now as it has been inactive for a while. If you experience bugs with the new version please file a new issue! Thanks

Viatrak commented 7 years ago

Just an FYI a PR was merged in today that enhances the dtu_test_project with a working example.