# Part of Odoo. See LICENSE file for full copyright and licensing details. import requests from datetime import datetime, timedelta, timezone from odoo import models, fields, _ from odoo.exceptions import ValidationError, UserError class CloudStorageSettings(models.TransientModel): """ Instructions: cloud_storage_azure_account_name, cloud_storage_azure_container_name: if changed and old container names are still in use, you should promise the current application registration has the permission to access all old containers. cloud_storage_azure_invalidate_user_delegation_key: invalidate the cached value for get_cloud_storage_azure_user_delegation_key """ _inherit = 'res.config.settings' cloud_storage_provider = fields.Selection(selection_add=[('azure', 'Azure Cloud Storage')]) cloud_storage_azure_account_name = fields.Char( string='Azure Account Name', config_parameter='cloud_storage_azure_account_name') cloud_storage_azure_container_name = fields.Char( string='Azure Container Name', config_parameter='cloud_storage_azure_container_name') # Application Registry Info cloud_storage_azure_tenant_id = fields.Char( string='Azure Tenant ID', config_parameter='cloud_storage_azure_tenant_id') cloud_storage_azure_client_id = fields.Char( string='Azure Client ID', config_parameter='cloud_storage_azure_client_id') cloud_storage_azure_client_secret = fields.Char( string='Azure Client Secret', config_parameter='cloud_storage_azure_client_secret') cloud_storage_azure_invalidate_user_delegation_key = fields.Boolean( string='Invalidate Cached Azure User Delegation Key', ) def _get_cloud_storage_configuration(self): ICP = self.env['ir.config_parameter'].sudo() if ICP.get_param('cloud_storage_provider') != 'azure': return super()._get_cloud_storage_configuration configuration = { 'container_name': ICP.get_param('cloud_storage_azure_container_name'), 'account_name': ICP.get_param('cloud_storage_azure_account_name'), 'tenant_id': ICP.get_param('cloud_storage_azure_tenant_id'), 'client_id': ICP.get_param('cloud_storage_azure_client_id'), 'client_secret': ICP.get_param('cloud_storage_azure_client_secret'), } return configuration if all(configuration.values()) else {} def _setup_cloud_storage_provider(self): ICP = self.env['ir.config_parameter'].sudo() if ICP.get_param('cloud_storage_provider') != 'azure': return super()._setup_cloud_storage_provider() blob_info = { 'account_name': ICP.get_param('cloud_storage_azure_account_name'), 'container_name': ICP.get_param('cloud_storage_azure_container_name'), # use different blob names in case the credentials are allowed to # overwrite an existing blob created by previous tests 'blob_name': f'0/{datetime.now(timezone.utc)}.txt', } # check blob create permission upload_expiry = datetime.now(timezone.utc) + timedelta(seconds=self.env['ir.attachment']._cloud_storage_upload_url_time_to_expiry) upload_url = self.env['ir.attachment']._generate_cloud_storage_azure_sas_url(**blob_info, permission='c', expiry=upload_expiry) upload_response = requests.put(upload_url, data=b'', headers={'x-ms-blob-type': 'BlockBlob'}, timeout=5) if upload_response.status_code != 201: raise ValidationError(_('The connection string is not allowed to upload blobs to the container.\n%s', str(upload_response.text))) # check blob read permission download_expiry = datetime.now(timezone.utc) + timedelta(seconds=self.env['ir.attachment']._cloud_storage_download_url_time_to_expiry) download_url = self.env['ir.attachment']._generate_cloud_storage_azure_sas_url(**blob_info, permission='r', expiry=download_expiry) download_response = requests.get(download_url, timeout=5) if download_response.status_code != 200: raise ValidationError(_('The connection string is not allowed to download blobs from the container.\n%s', str(download_response.text))) def _check_cloud_storage_uninstallable(self): if self.env['ir.config_parameter'].get_param('cloud_storage_provider') != 'azure': return super()._check_cloud_storage_uninstallable() cr = self.env.cr cr.execute( """ SELECT 1 FROM ir_attachment WHERE type = 'cloud_storage' AND url LIKE 'https://%.blob.core.windows.net/%' LIMIT 1 """, ) if cr.fetchone(): raise UserError(_('Some Azure attachments are in use, please migrate their cloud storages before disable this module')) def set_values(self): super().set_values() if self.cloud_storage_azure_invalidate_user_delegation_key: ICP = self.env['ir.config_parameter'] old_seq = int(ICP.get_param('cloud_storage_azure_user_delegation_key_sequence', 0)) ICP.set_param('cloud_storage_azure_user_delegation_key_sequence', old_seq + 1)