When an administrator has multiple logins in multi companies, It gets confusing when we log in via the same URL. Here comes the application where it needs to set up a different URL and login for a multi-company in Odoo with Nginx.
Different logins for multi companies with different URLs will make the ERP more user friendly, which will help the multi-company business end-users. When it is configured with SSL, it will make the business more secure.
Let us start with Nginx, Given below is an NGINX configuration:
server {
listen 80;
listen [::]:80;
server_name testing.com 111.111.111.111 *.testing.com api.testtesting.cybrosys.com;
rewrite ^(.*) https://$host$1 permanent;
}
server {
server_name *.*.testing.com 111.111.111.111 *.testing.com;
listen 443 ssl;
access_log /var/log/nginx/testing-access.log;
error_log /var/log/nginx/testing-error.log;
location /longpolling {
proxy_connect_timeout 3600;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
send_timeout 3600;
proxy_pass http://127.0.0.1:8072;
}
location / {
proxy_connect_timeout 3600;
proxy_read_timeout 3600;
proxy_send_timeout 3600;
send_timeout 3600;
proxy_redirect off;
proxy_pass http://127.0.0.1:8069/;
proxy_set_header Host $http_host;
proxy_set_header X-Forwarded-Host $http_host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
ssl on;
ssl_certificate /etc/nginx/ssl/odoo.chained.crt;
ssl_certificate_key /etc/nginx/ssl/test.key;
ssl_session_timeout 30m;
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RS$';
ssl_prefer_server_ciphers on;
gzip on;
gzip_min_length 1000;
}
upstream odoo {
server 127.0.0.1:8069 weight=1 fail_timeout=0;
}
Below given code is for any URL which ends with testing.com (eg: test_company.testing.com, this URL can be set while creating a company as exact_domain) will return to the parent domain.
server_name testing.com 111.111.111.111 *.testing.com api.testtesting.cybrosys.com;
rewrite ^(.*) https://$host$1 permanent;
Next, we move on to the methods which help in multi-company login from multiple URL,
class ResUsers(models.Model):
_inherit = 'res.users'
def init(self):
users = self.env['res.users'].search([])
for user in users:
if user.username:
user.login = user.username + "@" + (user.company_id.company_domain)
elif user.login and not user.username:
user.username = user.login.rsplit('@', 1)[0]
We have inherited the model, res.users, and added an init method. This method is to set the user.login ie, it should be username@company_domain. Where company_domain is a char field which can be filled while creating a company in the model res.company also while creating users in res.users
company_domain = fields.Char(string="Company Domain")
Next we override Create method from the model res.users,
def create(self, vals_list):
logins = []
if type(vals_list).__name__ == 'list':
for vals in vals_list:
if not 'username' in vals:
vals['username'] = vals['email']
if 'company_domain' in vals:
vals['login'] = vals['username'] + "@" + vals['company_domain']
elif 'company_ids' in vals:
vals['login'] = vals['username'] + "@"
+self.env['res.company'].with_user(1).browse(vals['company_ids'][0][2][0]).company_domain
if 'login' in vals:
vals['login'] = vals['login'].lower()
logins = self.env['res.users'].search([('login', '=', vals['login'])]).ids
if len(logins) > 0:
raise UserError(_("You cant have two users with same username."))
elif type(vals_list).__name__ == 'dict':
logins = []
if not 'username' in vals_list or vals_list['username'] == False:
vals_list['username'] = vals_list['email']
if 'company_domain' in vals_list and vals_list['company_domain'] != False:
vals_list['login'] = vals_list['username'] + "@" + vals_list['company_domain']
elif 'company_id' in vals_list:
vals_list['login'] = vals_list['username'] + "@" + str(self.env['res.company'].with_user(1).browse(
vals_list['company_id']).company_domain)
logins = self.env['res.users'].search([('login', '=', vals_list['login'])]).ids
if (not 'login' in vals_list) and ('username' in vals_list or 'email' in vals_list):
if 'username' in vals_list:
vals_list['login'] = vals_list['username'] + "@" + str(self.env['res.company'].with_user(1).browse(
vals_list.get('company_id', self.env.company.id)).company_domain)
elif 'email' in vals_list:
vals_list['login'] = vals_list['email'] + "@" + str(self.env['res.company'].with_user(1).browse(
vals_list.get('company_id', self.env.company.id)).company_domain)
if 'login' in vals_list:
vals_list['login'] = vals_list['login'].lower()
if len(logins) > 0:
raise UserError(_("You cant have two users with same username."))
res = super(ResUsers, self).create(vals_list)
return res
In this method, we set the login for the user. Here it contains mainly two conditions, where it checks whether the vals_list is dict or list. Let us discuss the code line by line,
logins = [] // A list is initialized for logins
if type(vals_list).__name__ == 'list': // Check whether vals_list is of the type list
for vals in vals_list: // looping vals_list
if not 'username' in vals: // If vals does not contain username
vals['username'] = vals['email'] //set username as email
if 'company_domain' in vals: // If vals contain company_domain
vals['login'] = vals['username'] + "@" + vals['company_domain'] // set login as username@company_domain
elif 'company_ids' in vals: // If vals doesn’t contain company_domain and it has company_ids
vals['login'] = vals['username'] + "@" +self.env['res.company'].with_user(1).browse(vals['company_ids'][0][2][0]).company_domain // set login as username@company_domain(It is taken from the login user’s company_domain)
if 'login' in vals: // if vals contain login
vals['login'] = vals['login'].lower() // convert login to lower case
logins = self.env['res.users'].search([('login', '=', vals['login'])]).ids // Get the user with corresponding login ie username
if len(logins) > 0: // Checking if logins is greater than 0
raise UserError(_("You cant have two users with same username.")) // If login is greater than 0, an alert message appears.
Similar things have been done if vals_list is of the type dict. Then we super the existing Create method
Next, we override the method authenticate in the model res.users itself.
@classmethod
def authenticate(cls, db, login, password, user_agent_env):
_logger.info("authenticate")
is_local_url = True if cls.is_valid_url(user_agent_env['base_location']) else False
# is_local_url = True
if 'HTTP_HOST' in user_agent_env and not is_local_url:
_logger.info(user_agent_env['HTTP_HOST'], login)
login = login + "@" + user_agent_env['HTTP_HOST'].split('.')[0]
return super(ResUsers, cls).authenticate(db, login.lower(), password, user_agent_env)
This method gets values such as db, login, password, and user_agent_env from the controller(which is defined in default odoo methods).
First we check whether the url is valid or not, from the method is_valid_url(),
It is as follows:
def is_valid_url(url):
import re
regex = re.compile(
r'^https?://' # http:// or https://
r'(localhost|' # localhost...
r'cybrosys|' # localhost...
r'\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})' # ...or ip
r'(?::\d+)?' # optional port
r'(?:/?|[/?]\S+)$', re.IGNORECASE)
return url is not None and regex.search(url)
If the url is valid, is_local_url is set to True.
if 'HTTP_HOST' in user_agent_env and not is_local_url:
If the user_agent_env contains HTTP_HOST and if is_local_url is not True,
login = login + "@" + user_agent_env['HTTP_HOST'].split('.')[0]
Then, login is set as the login@first part of the HTTP_HOST. For example: If the url is of test@testing.com and login we retrieved from the controller is abc then,
login = abc@testing
This is how we can set up different URL and login for multi company in odoo with Nginx