|
|
@ -19,21 +19,35 @@ |
|
|
|
# If not, see <http://www.gnu.org/licenses/>. |
|
|
|
# |
|
|
|
############################################################################# |
|
|
|
from odoo import api, fields, models, _ |
|
|
|
import datetime |
|
|
|
|
|
|
|
from odoo.http import request |
|
|
|
from odoo.service import db |
|
|
|
from datetime import timedelta |
|
|
|
import boto3 |
|
|
|
import errno |
|
|
|
import dropbox |
|
|
|
import ftplib |
|
|
|
import json |
|
|
|
import logging |
|
|
|
import os |
|
|
|
import nextcloud_client |
|
|
|
import odoo |
|
|
|
import os |
|
|
|
import paramiko |
|
|
|
import requests |
|
|
|
from nextcloud import NextCloud |
|
|
|
from requests.auth import HTTPBasicAuth |
|
|
|
import tempfile |
|
|
|
from odoo import api, fields, models, _ |
|
|
|
from werkzeug import urls |
|
|
|
from odoo.exceptions import UserError, ValidationError |
|
|
|
from odoo.service import db |
|
|
|
|
|
|
|
_logger = logging.getLogger(__name__) |
|
|
|
ONEDRIVE_SCOPE = ['offline_access openid Files.ReadWrite.All'] |
|
|
|
MICROSOFT_GRAPH_END_POINT = "https://graph.microsoft.com" |
|
|
|
GOOGLE_AUTH_ENDPOINT = 'https://accounts.google.com/o/oauth2/auth' |
|
|
|
GOOGLE_TOKEN_ENDPOINT = 'https://accounts.google.com/o/oauth2/token' |
|
|
|
GOOGLE_API_BASE_URL = 'https://www.googleapis.com' |
|
|
|
|
|
|
|
|
|
|
|
class DbBackupConfigure(models.Model): |
|
|
@ -56,7 +70,11 @@ class DbBackupConfigure(models.Model): |
|
|
|
('local', 'Local Storage'), |
|
|
|
('google_drive', 'Google Drive'), |
|
|
|
('ftp', 'FTP'), |
|
|
|
('sftp', 'SFTP') |
|
|
|
('sftp', 'SFTP'), |
|
|
|
('dropbox', 'Dropbox'), |
|
|
|
('onedrive', 'Onedrive'), |
|
|
|
('next_cloud', 'Next Cloud'), |
|
|
|
('amazon_s3', 'Amazon S3') |
|
|
|
], string='Backup Destination', |
|
|
|
help='Specify the location that the backup need to store') |
|
|
|
backup_path = fields.Char(string='Backup Path', |
|
|
@ -71,30 +89,483 @@ class DbBackupConfigure(models.Model): |
|
|
|
ftp_user = fields.Char(string='FTP User', help='FTP User') |
|
|
|
ftp_password = fields.Char(string='FTP Password', help='FTP password') |
|
|
|
ftp_path = fields.Char(string='FTP Path', help='FTP Path') |
|
|
|
active = fields.Boolean(default=True, |
|
|
|
help='To know the configuration is active') |
|
|
|
save_to_drive = fields.Boolean(help='To know that the backup need ' |
|
|
|
'to save in drive') |
|
|
|
dropbox_client_key = fields.Char(string='Dropbox Client ID', copy=False, |
|
|
|
help='Client id of the dropbox') |
|
|
|
dropbox_client_secret = fields.Char(string='Dropbox Client Secret', |
|
|
|
copy=False, |
|
|
|
help='Client secret id of the dropbox') |
|
|
|
dropbox_refresh_token = fields.Char(string='Dropbox Refresh Token', |
|
|
|
copy=False, |
|
|
|
help='Refresh token for the dropbox') |
|
|
|
is_dropbox_token_generated = fields.Boolean( |
|
|
|
string='Dropbox Token Generated', |
|
|
|
compute='_compute_is_dropbox_token_generated', |
|
|
|
copy=False, help='Is the dropbox token generated or not?') |
|
|
|
dropbox_folder = fields.Char(string='Dropbox Folder', help='Dropbox folder') |
|
|
|
active = fields.Boolean(default=True, help='Checking the configuration' |
|
|
|
' is active or not') |
|
|
|
save_to_drive = fields.Boolean(help='Checking whether the backup need ' |
|
|
|
'to store in drive') |
|
|
|
auto_remove = fields.Boolean(string='Remove Old Backups', |
|
|
|
help='Remove old backup if the value is true') |
|
|
|
help='Remove Old Backups') |
|
|
|
days_to_remove = fields.Integer(string='Remove After', |
|
|
|
help='Automatically delete stored backups ' |
|
|
|
'after this specified number of days') |
|
|
|
google_drive_folder = fields.Char(string='Drive Folder ID') |
|
|
|
notify_user = fields.Boolean(string='Notify User', |
|
|
|
help='Send an email notification to user ' |
|
|
|
'when the backup operation is successful' |
|
|
|
' or failed') |
|
|
|
help='Send an email notification to user when ' |
|
|
|
'the backup operation is successful or ' |
|
|
|
'failed') |
|
|
|
user_id = fields.Many2one('res.users', string='User') |
|
|
|
backup_filename = fields.Char(string='Backup Filename', |
|
|
|
help='For Storing generated backup filename') |
|
|
|
generated_exception = fields.Char(string='Exception', |
|
|
|
help='Exception Encountered while Backup ' |
|
|
|
'generation') |
|
|
|
gdrive_refresh_token = fields.Char(string='Google drive Refresh Token', |
|
|
|
copy=False, |
|
|
|
help='Refresh token for google drive') |
|
|
|
gdrive_access_token = fields.Char(string='Google Drive Access Token', |
|
|
|
copy=False, |
|
|
|
help='Access token for google drive') |
|
|
|
is_google_drive_token_generated = fields.Boolean( |
|
|
|
string='Google drive Token Generated', |
|
|
|
compute='_compute_is_google_drive_token_generated', copy=False, |
|
|
|
help='Google drive token generated or not') |
|
|
|
gdrive_client_key = fields.Char(string='Google Drive Client ID', copy=False, |
|
|
|
help='Client id of the google drive') |
|
|
|
gdrive_client_secret = fields.Char(string='Google Drive Client Secret', |
|
|
|
copy=False, |
|
|
|
help='Client secret id of the google' |
|
|
|
' drive') |
|
|
|
gdrive_token_validity = fields.Datetime( |
|
|
|
string='Google Drive Token Validity', copy=False, |
|
|
|
help='Token validity of the google drive') |
|
|
|
gdrive_redirect_uri = fields.Char(string='Google Drive Redirect URI', |
|
|
|
compute='_compute_redirect_uri', |
|
|
|
help='Redirect URI of the google drive') |
|
|
|
domain = fields.Char(string='Domain Name', help="Field used to store the " |
|
|
|
"name of a domain") |
|
|
|
next_cloud_user_name = fields.Char(string='User Name', |
|
|
|
help="Field used to store the user name" |
|
|
|
" for a Nextcloud account.") |
|
|
|
next_cloud_password = fields.Char(string='Password', |
|
|
|
help="Field used to store the password" |
|
|
|
" for a Nextcloud account.") |
|
|
|
nextcloud_folder_key = fields.Char(string='Next Cloud Folder Id', |
|
|
|
help="Field used to store the unique " |
|
|
|
"identifier for a Nextcloud " |
|
|
|
"folder.") |
|
|
|
onedrive_client_key = fields.Char(string='Onedrive Client ID', copy=False, |
|
|
|
help='Client ID of the onedrive') |
|
|
|
onedrive_client_secret = fields.Char(string='Onedrive Client Secret', |
|
|
|
copy=False, help='Client secret id of' |
|
|
|
' the onedrive') |
|
|
|
onedrive_access_token = fields.Char(string='Onedrive Access Token', |
|
|
|
copy=False, |
|
|
|
help='Access token for one drive') |
|
|
|
onedrive_refresh_token = fields.Char(string='Onedrive Refresh Token', |
|
|
|
copy=False, |
|
|
|
help='Refresh token for one drive') |
|
|
|
onedrive_token_validity = fields.Datetime(string='Onedrive Token Validity', |
|
|
|
copy=False, |
|
|
|
help='Token validity date') |
|
|
|
onedrive_folder_key = fields.Char(string='Folder ID', |
|
|
|
help='Folder id of the onedrive') |
|
|
|
is_onedrive_token_generated = fields.Boolean( |
|
|
|
string='onedrive Tokens Generated', |
|
|
|
compute='_compute_is_onedrive_token_generated', |
|
|
|
copy=False, help='Whether to generate onedrive token?') |
|
|
|
onedrive_redirect_uri = fields.Char(string='Onedrive Redirect URI', |
|
|
|
compute='_compute_redirect_uri', |
|
|
|
help='Redirect URI of the onedrive') |
|
|
|
aws_access_key = fields.Char(string="Amazon S3 Access Key", |
|
|
|
help="Field used to store the Access Key" |
|
|
|
" for an Amazon S3 bucket.") |
|
|
|
aws_secret_access_key = fields.Char(string='Amazon S3 Secret Key', |
|
|
|
help="Field used to store the Secret" |
|
|
|
" Key for an Amazon S3 bucket.") |
|
|
|
bucket_file_name = fields.Char(string='Bucket Name', |
|
|
|
help="Field used to store the name of an" |
|
|
|
" Amazon S3 bucket.") |
|
|
|
aws_folder_name = fields.Char(string='File Name', |
|
|
|
help="field used to store the name of a" |
|
|
|
" folder in an Amazon S3 bucket.") |
|
|
|
success_message = fields.Char('Success Message', readonly=True) |
|
|
|
success_test = fields.Boolean(string="Success Test") |
|
|
|
fail_test = fields.Boolean(string="Fail Test") |
|
|
|
gdrive_backup_error_test = fields.Boolean(string="Google Drive Error Test") |
|
|
|
onedrive_backup_error_test = fields.Boolean(string="OneDrive Error Test") |
|
|
|
|
|
|
|
@api.depends('gdrive_access_token', 'gdrive_refresh_token') |
|
|
|
def _compute_is_google_drive_token_generated(self): |
|
|
|
"""Set True if the Google Drive refresh token is generated""" |
|
|
|
for rec in self: |
|
|
|
rec.is_google_drive_token_generated = bool( |
|
|
|
rec.gdrive_access_token) and bool(rec.gdrive_refresh_token) |
|
|
|
|
|
|
|
@api.onchange('backup_destination') |
|
|
|
def _onchange_backup_destination(self): |
|
|
|
self.write({ |
|
|
|
"fail_test": False, |
|
|
|
"success_test": False, |
|
|
|
"gdrive_backup_error_test": False, |
|
|
|
"onedrive_backup_error_test": False |
|
|
|
}) |
|
|
|
|
|
|
|
@api.onchange('gdrive_client_key', 'gdrive_client_secret', |
|
|
|
'google_drive_folder', 'onedrive_client_key', |
|
|
|
'onedrive_client_secret', 'onedrive_folder_key', 'sftp_host', |
|
|
|
'sftp_port', 'sftp_user', 'sftp_password', 'sftp_path', |
|
|
|
'ftp_host', 'ftp_port', 'ftp_user', 'ft_password', 'ftp_path') |
|
|
|
def _onchange_gdrive_backup_error_test(self): |
|
|
|
if self.backup_destination == 'ftp': |
|
|
|
self.write({"success_test": False, "fail_test": True}) |
|
|
|
if self.backup_destination == 'sftp': |
|
|
|
self.write({"success_test": False, "fail_test": True}) |
|
|
|
if self.backup_destination == 'google_drive': |
|
|
|
if self.gdrive_backup_error_test: |
|
|
|
self.write({ |
|
|
|
"gdrive_backup_error_test": False |
|
|
|
}) |
|
|
|
if self.backup_destination == 'onedrive': |
|
|
|
if self.onedrive_backup_error_test: |
|
|
|
self.write({ |
|
|
|
"onedrive_backup_error_test": False |
|
|
|
}) |
|
|
|
|
|
|
|
def action_s3cloud(self): |
|
|
|
"""If it has aws_secret_access_key, which will perform s3_cloud |
|
|
|
operations for connection test""" |
|
|
|
if self.aws_access_key and self.aws_secret_access_key: |
|
|
|
try: |
|
|
|
bo3 = boto3.client( |
|
|
|
's3', |
|
|
|
aws_access_key_id=self.aws_access_key, |
|
|
|
aws_secret_access_key=self.aws_secret_access_key) |
|
|
|
response = bo3.list_buckets() |
|
|
|
for bucket in response['Buckets']: |
|
|
|
if self.bucket_file_name == bucket['Name']: |
|
|
|
self.active = True |
|
|
|
self.hide_active = True |
|
|
|
self.write({"success_test": True, "fail_test": False}) |
|
|
|
return |
|
|
|
raise UserError( |
|
|
|
_("Bucket not found. Please check the bucket name and" |
|
|
|
" try again.")) |
|
|
|
except Exception: |
|
|
|
self.write({"fail_test": True, "success_test": False}) |
|
|
|
self.active = False |
|
|
|
self.hide_active = False |
|
|
|
|
|
|
|
def action_nextcloud(self): |
|
|
|
"""If it has next_cloud_password, domain, and next_cloud_user_name |
|
|
|
which will perform an action for nextcloud connection test""" |
|
|
|
if self.domain and self.next_cloud_password and \ |
|
|
|
self.next_cloud_user_name: |
|
|
|
try: |
|
|
|
ncx = NextCloud(self.domain, |
|
|
|
auth=HTTPBasicAuth(self.next_cloud_user_name, |
|
|
|
self.next_cloud_password)) |
|
|
|
|
|
|
|
data = ncx.list_folders('/').__dict__ |
|
|
|
if data['raw'].status_code == 207: |
|
|
|
self.active = True |
|
|
|
self.hide_active = True |
|
|
|
self.write({"fail_test": False, "success_test": True}) |
|
|
|
return |
|
|
|
else: |
|
|
|
self.active = False |
|
|
|
self.hide_active = False |
|
|
|
self.write({"fail_test": True, "success_test": False}) |
|
|
|
except Exception: |
|
|
|
self.active = False |
|
|
|
self.hide_active = False |
|
|
|
self.write({"fail_test": True, "success_test": False}) |
|
|
|
|
|
|
|
def _compute_redirect_uri(self): |
|
|
|
"""Compute the redirect URI for onedrive and Google Drive""" |
|
|
|
for rec in self: |
|
|
|
base_url = request.env['ir.config_parameter'].get_param( |
|
|
|
'web.base.url') |
|
|
|
rec.onedrive_redirect_uri = base_url + '/onedrive/authentication' |
|
|
|
rec.gdrive_redirect_uri = base_url + '/google_drive/authentication' |
|
|
|
|
|
|
|
@api.depends('onedrive_access_token', 'onedrive_refresh_token') |
|
|
|
def _compute_is_onedrive_token_generated(self): |
|
|
|
"""Set true if onedrive tokens are generated""" |
|
|
|
for rec in self: |
|
|
|
rec.is_onedrive_token_generated = bool( |
|
|
|
rec.onedrive_access_token) and bool(rec.onedrive_refresh_token) |
|
|
|
|
|
|
|
@api.depends('dropbox_refresh_token') |
|
|
|
def _compute_is_dropbox_token_generated(self): |
|
|
|
"""Set True if the dropbox refresh token is generated""" |
|
|
|
for rec in self: |
|
|
|
rec.is_dropbox_token_generated = bool(rec.dropbox_refresh_token) |
|
|
|
|
|
|
|
def action_get_dropbox_auth_code(self): |
|
|
|
"""Open a wizards to set up dropbox Authorization code""" |
|
|
|
return { |
|
|
|
'type': 'ir.actions.act_window', |
|
|
|
'name': 'Dropbox Authorization Wizard', |
|
|
|
'res_model': 'dropbox.auth.code', |
|
|
|
'view_mode': 'form', |
|
|
|
'target': 'new', |
|
|
|
'context': {'dropbox_auth': True} |
|
|
|
} |
|
|
|
|
|
|
|
def action_get_onedrive_auth_code(self): |
|
|
|
"""Generate onedrive authorization code""" |
|
|
|
AUTHORITY = \ |
|
|
|
'https://login.microsoftonline.com/common/oauth2/v2.0/authorize' |
|
|
|
action = self.env.ref( |
|
|
|
"auto_database_backup.action_db_backup_configure") |
|
|
|
action_data = { |
|
|
|
'id': action.id, |
|
|
|
'name': action.name, |
|
|
|
'type': action.type, |
|
|
|
'xml_id': action.xml_id, |
|
|
|
'help': action.help, |
|
|
|
'binding_model_id': action.binding_model_id, |
|
|
|
'binding_type': action.binding_type, |
|
|
|
'display_name': action.display_name, |
|
|
|
'res_model': action.res_model, |
|
|
|
'target': action.target, |
|
|
|
'view_mode': action.view_mode, |
|
|
|
'views': action.views, |
|
|
|
'groups_id': [(6, 0, action.groups_id.ids)], |
|
|
|
'search_view_id': action.search_view_id.id if action.search_view_id else False, |
|
|
|
'filter': action.filter, |
|
|
|
'search_view': action.search_view, |
|
|
|
'limit': action.limit, |
|
|
|
} |
|
|
|
base_url = request.env['ir.config_parameter'].get_param('web.base.url') |
|
|
|
url_return = base_url + \ |
|
|
|
'/web#id=%d&action=%d&view_type=form&model=%s' % ( |
|
|
|
self.id, action_data['id'], 'db.backup.configure') |
|
|
|
state = { |
|
|
|
'backup_config_id': self.id, |
|
|
|
'url_return': url_return |
|
|
|
} |
|
|
|
encoded_params = urls.url_encode({ |
|
|
|
'response_type': 'code', |
|
|
|
'client_id': self.onedrive_client_key, |
|
|
|
'state': json.dumps(state), |
|
|
|
'scope': ONEDRIVE_SCOPE, |
|
|
|
'redirect_uri': base_url + '/onedrive/authentication', |
|
|
|
'prompt': 'consent', |
|
|
|
'access_type': 'offline' |
|
|
|
}) |
|
|
|
auth_url = "%s?%s" % (AUTHORITY, encoded_params) |
|
|
|
return { |
|
|
|
'type': 'ir.actions.act_url', |
|
|
|
'target': 'self', |
|
|
|
'url': auth_url, |
|
|
|
} |
|
|
|
|
|
|
|
def action_get_gdrive_auth_code(self): |
|
|
|
"""Generate google drive authorization code""" |
|
|
|
action = self.env.ref( |
|
|
|
"auto_database_backup.action_db_backup_configure") |
|
|
|
action_data = { |
|
|
|
'id': action.id, |
|
|
|
'name': action.name, |
|
|
|
'type': action.type, |
|
|
|
'xml_id': action.xml_id, |
|
|
|
'help': action.help, |
|
|
|
'binding_model_id': action.binding_model_id, |
|
|
|
'binding_type': action.binding_type, |
|
|
|
'display_name': action.display_name, |
|
|
|
'res_model': action.res_model, |
|
|
|
'target': action.target, |
|
|
|
'view_mode': action.view_mode, |
|
|
|
'views': action.views, |
|
|
|
'groups_id': [(6, 0, action.groups_id.ids)], |
|
|
|
'search_view_id': action.search_view_id.id if action.search_view_id else False, |
|
|
|
'filter': action.filter, |
|
|
|
'search_view': action.search_view, |
|
|
|
'limit': action.limit, |
|
|
|
} |
|
|
|
base_url = request.env['ir.config_parameter'].get_param('web.base.url') |
|
|
|
url_return = base_url + \ |
|
|
|
'/web#id=%d&action=%d&view_type=form&model=%s' % ( |
|
|
|
self.id, action_data['id'], 'db.backup.configure') |
|
|
|
state = { |
|
|
|
'backup_config_id': self.id, |
|
|
|
'url_return': url_return |
|
|
|
} |
|
|
|
encoded_params = urls.url_encode({ |
|
|
|
'response_type': 'code', |
|
|
|
'client_id': self.gdrive_client_key, |
|
|
|
'scope': 'https://www.googleapis.com/auth/drive https://www.googleapis.com/auth/drive.file', |
|
|
|
'redirect_uri': base_url + '/google_drive/authentication', |
|
|
|
'access_type': 'offline', |
|
|
|
'state': json.dumps(state), |
|
|
|
'approval_prompt': 'force', |
|
|
|
}) |
|
|
|
auth_url = "%s?%s" % (GOOGLE_AUTH_ENDPOINT, encoded_params) |
|
|
|
return { |
|
|
|
'type': 'ir.actions.act_url', |
|
|
|
'target': 'self', |
|
|
|
'url': auth_url, |
|
|
|
} |
|
|
|
|
|
|
|
def get_gdrive_tokens(self, authorize_code): |
|
|
|
"""Generate onedrive tokens from authorization code.""" |
|
|
|
base_url = request.env['ir.config_parameter'].get_param('web.base.url') |
|
|
|
headers = {"content-type": "application/x-www-form-urlencoded"} |
|
|
|
data = { |
|
|
|
'code': authorize_code, |
|
|
|
'client_id': self.gdrive_client_key, |
|
|
|
'client_secret': self.gdrive_client_secret, |
|
|
|
'grant_type': 'authorization_code', |
|
|
|
'redirect_uri': base_url + '/google_drive/authentication' |
|
|
|
} |
|
|
|
try: |
|
|
|
res = requests.post(GOOGLE_TOKEN_ENDPOINT, params=data, |
|
|
|
headers=headers) |
|
|
|
res.raise_for_status() |
|
|
|
response = res.content and res.json() or {} |
|
|
|
if response: |
|
|
|
expires_in = response.get('expires_in') |
|
|
|
self.write({ |
|
|
|
'gdrive_access_token': response.get('access_token'), |
|
|
|
'gdrive_refresh_token': response.get('refresh_token'), |
|
|
|
'gdrive_token_validity': fields.Datetime.now() + timedelta( |
|
|
|
seconds=expires_in) if expires_in else False, |
|
|
|
}) |
|
|
|
if self.gdrive_backup_error_test: |
|
|
|
self.write({ |
|
|
|
'gdrive_backup_error_test': False |
|
|
|
}) |
|
|
|
except Exception: |
|
|
|
if not self.gdrive_backup_error_test: |
|
|
|
self.write({"gdrive_backup_error_test": True}) |
|
|
|
|
|
|
|
def generate_onedrive_refresh_token(self): |
|
|
|
"""Generate onedrive access token from refresh token if expired""" |
|
|
|
base_url = request.env['ir.config_parameter'].get_param('web.base.url') |
|
|
|
headers = {"Content-type": "application/x-www-form-urlencoded"} |
|
|
|
data = { |
|
|
|
'client_id': self.onedrive_client_key, |
|
|
|
'client_secret': self.onedrive_client_secret, |
|
|
|
'scope': ONEDRIVE_SCOPE, |
|
|
|
'grant_type': "refresh_token", |
|
|
|
'redirect_uri': base_url + '/onedrive/authentication', |
|
|
|
'refresh_token': self.onedrive_refresh_token |
|
|
|
} |
|
|
|
try: |
|
|
|
res = requests.post( |
|
|
|
"https://login.microsoftonline.com/common/oauth2/v2.0/token", |
|
|
|
data=data, headers=headers) |
|
|
|
res.raise_for_status() |
|
|
|
response = res.content and res.json() or {} |
|
|
|
if response: |
|
|
|
expires_in = response.get('expires_in') |
|
|
|
self.write({ |
|
|
|
'onedrive_access_token': response.get('access_token'), |
|
|
|
'onedrive_refresh_token': response.get('refresh_token'), |
|
|
|
'onedrive_token_validity': fields.Datetime.now() + timedelta( |
|
|
|
seconds=expires_in) if expires_in else False, |
|
|
|
}) |
|
|
|
except requests.HTTPError as error: |
|
|
|
_logger.exception("Bad microsoft onedrive request : %s !", |
|
|
|
error.response.content) |
|
|
|
raise error |
|
|
|
|
|
|
|
def get_onedrive_tokens(self, authorize_code): |
|
|
|
"""Generate onedrive tokens from authorization code.""" |
|
|
|
headers = {"content-type": "application/x-www-form-urlencoded"} |
|
|
|
base_url = request.env['ir.config_parameter'].get_param('web.base.url') |
|
|
|
data = { |
|
|
|
'code': authorize_code, |
|
|
|
'client_id': self.onedrive_client_key, |
|
|
|
'client_secret': self.onedrive_client_secret, |
|
|
|
'grant_type': 'authorization_code', |
|
|
|
'scope': ONEDRIVE_SCOPE, |
|
|
|
'redirect_uri': base_url + '/onedrive/authentication' |
|
|
|
} |
|
|
|
try: |
|
|
|
res = requests.post( |
|
|
|
"https://login.microsoftonline.com/common/oauth2/v2.0/token", |
|
|
|
data=data, headers=headers) |
|
|
|
res.raise_for_status() |
|
|
|
response = res.content and res.json() or {} |
|
|
|
if response: |
|
|
|
expires_in = response.get('expires_in') |
|
|
|
self.write({ |
|
|
|
'onedrive_access_token': response.get('access_token'), |
|
|
|
'onedrive_refresh_token': response.get('refresh_token'), |
|
|
|
'onedrive_token_validity': fields.Datetime.now() + timedelta( |
|
|
|
seconds=expires_in) if expires_in else False, |
|
|
|
}) |
|
|
|
if self.onedrive_backup_error_test: |
|
|
|
self.write({ |
|
|
|
'onedrive_backup_error_test': False |
|
|
|
}) |
|
|
|
except Exception: |
|
|
|
if not self.onedrive_backup_error_test: |
|
|
|
self.write({"onedrive_backup_error_test": True}) |
|
|
|
|
|
|
|
def generate_gdrive_refresh_token(self): |
|
|
|
"""Generate Google Drive access token from refresh token if expired""" |
|
|
|
headers = {"content-type": "application/x-www-form-urlencoded"} |
|
|
|
data = { |
|
|
|
'refresh_token': self.gdrive_refresh_token, |
|
|
|
'client_id': self.gdrive_client_key, |
|
|
|
'client_secret': self.gdrive_client_secret, |
|
|
|
'grant_type': 'refresh_token', |
|
|
|
} |
|
|
|
try: |
|
|
|
res = requests.post(GOOGLE_TOKEN_ENDPOINT, data=data, |
|
|
|
headers=headers) |
|
|
|
res.raise_for_status() |
|
|
|
response = res.content and res.json() or {} |
|
|
|
if response: |
|
|
|
expires_in = response.get('expires_in') |
|
|
|
self.write({ |
|
|
|
'gdrive_access_token': response.get('access_token'), |
|
|
|
'gdrive_token_validity': fields.Datetime.now() + timedelta( |
|
|
|
seconds=expires_in) if expires_in else False, |
|
|
|
}) |
|
|
|
except requests.HTTPError as error: |
|
|
|
error_key = error.response.json().get("error", "nc") |
|
|
|
error_msg = _( |
|
|
|
"An error occurred while generating the token. Your" |
|
|
|
"authorization code may be invalid or has already expired [%s]." |
|
|
|
"You should check your Client ID and secret on the Google APIs" |
|
|
|
" platform or try to stop and restart your calendar" |
|
|
|
" synchronisation.", |
|
|
|
error_key) |
|
|
|
raise UserError(error_msg) |
|
|
|
|
|
|
|
def get_dropbox_auth_url(self): |
|
|
|
"""Return dropbox authorization url""" |
|
|
|
dbx_auth = dropbox.oauth.DropboxOAuth2FlowNoRedirect( |
|
|
|
self.dropbox_client_key, |
|
|
|
self.dropbox_client_secret, |
|
|
|
token_access_type='offline') |
|
|
|
return dbx_auth.start() |
|
|
|
|
|
|
|
def set_dropbox_refresh_token(self, auth_code): |
|
|
|
"""Generate and set the dropbox refresh token from authorization code""" |
|
|
|
try: |
|
|
|
dbx_auth = dropbox.oauth.DropboxOAuth2FlowNoRedirect( |
|
|
|
self.dropbox_client_key, |
|
|
|
self.dropbox_client_secret, |
|
|
|
token_access_type='offline') |
|
|
|
outh_result = dbx_auth.finish(auth_code) |
|
|
|
self.dropbox_refresh_token = outh_result.refresh_token |
|
|
|
except Exception: |
|
|
|
raise ValidationError( |
|
|
|
'Please Enter Valid Authentication Code') |
|
|
|
|
|
|
|
@api.constrains('db_name', 'master_pwd') |
|
|
|
def _check_db_credentials(self): |
|
|
|
"""Validate entered database name and master password""" |
|
|
|
""" |
|
|
|
Validate entered database name and master password |
|
|
|
""" |
|
|
|
database_list = db.list_dbs() |
|
|
|
if self.db_name not in database_list: |
|
|
|
raise ValidationError(_("Invalid Database Name!")) |
|
|
@ -115,8 +586,12 @@ class DbBackupConfigure(models.Model): |
|
|
|
port=self.sftp_port) |
|
|
|
sftp = client.open_sftp() |
|
|
|
sftp.close() |
|
|
|
except Exception as e: |
|
|
|
raise UserError(_("SFTP Exception: %s", e)) |
|
|
|
except Exception: |
|
|
|
raise UserError( |
|
|
|
_("It seems there was an issue with the connection, " |
|
|
|
"possibly due to incorrect information provided. " |
|
|
|
"Please double-check all the information you provided " |
|
|
|
"for the connection to ensure it is correct.")) |
|
|
|
finally: |
|
|
|
client.close() |
|
|
|
elif self.backup_destination == 'ftp': |
|
|
@ -125,19 +600,13 @@ class DbBackupConfigure(models.Model): |
|
|
|
ftp_server.connect(self.ftp_host, int(self.ftp_port)) |
|
|
|
ftp_server.login(self.ftp_user, self.ftp_password) |
|
|
|
ftp_server.quit() |
|
|
|
except Exception as e: |
|
|
|
raise UserError(_("FTP Exception: %s", e)) |
|
|
|
title = _("Connection Test Succeeded!") |
|
|
|
message = _("Everything seems properly set up!") |
|
|
|
return { |
|
|
|
'type': 'ir.actions.client', |
|
|
|
'tag': 'display_notification', |
|
|
|
'params': { |
|
|
|
'title': title, |
|
|
|
'message': message, |
|
|
|
'sticky': False, |
|
|
|
} |
|
|
|
} |
|
|
|
except Exception: |
|
|
|
raise UserError( |
|
|
|
_("It seems there was an issue with the connection, " |
|
|
|
"possibly due to incorrect information provided. " |
|
|
|
"Please double-check all the information you provided " |
|
|
|
"for the connection to ensure it is correct.")) |
|
|
|
self.write({"success_test": True}) |
|
|
|
|
|
|
|
def _schedule_auto_backup(self): |
|
|
|
"""Function for generating and storing backup |
|
|
@ -269,15 +738,17 @@ class DbBackupConfigure(models.Model): |
|
|
|
client.close() |
|
|
|
# Google Drive backup |
|
|
|
elif rec.backup_destination == 'google_drive': |
|
|
|
try: |
|
|
|
if rec.gdrive_token_validity <= fields.Datetime.now(): |
|
|
|
rec.generate_gdrive_refresh_token() |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
try: |
|
|
|
access_token = self.env[ |
|
|
|
'google.drive.config'].sudo().get_access_token() |
|
|
|
headers = {"Authorization": "Bearer %s" % access_token} |
|
|
|
headers = { |
|
|
|
"Authorization": "Bearer %s" % rec.gdrive_access_token} |
|
|
|
para = { |
|
|
|
"name": backup_filename, |
|
|
|
"parents": [rec.google_drive_folder], |
|
|
@ -288,41 +759,294 @@ class DbBackupConfigure(models.Model): |
|
|
|
'file': open(temp.name, "rb") |
|
|
|
} |
|
|
|
requests.post( |
|
|
|
"https://www.googleapis.com/upload/drive/v3/files?" |
|
|
|
"uploadType=multipart", |
|
|
|
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart", |
|
|
|
headers=headers, |
|
|
|
files=files) |
|
|
|
files=files |
|
|
|
) |
|
|
|
if rec.auto_remove: |
|
|
|
query = "parents = '%s'" % rec.google_drive_folder |
|
|
|
query = "parents = '%s'" % rec.google_drive_folder_key |
|
|
|
files_req = requests.get( |
|
|
|
"https://www.googleapis.com/drive/v3/files?q=%s" |
|
|
|
% query, |
|
|
|
"https://www.googleapis.com/drive/v3/files?q=%s" % query, |
|
|
|
headers=headers) |
|
|
|
files = files_req.json()['files'] |
|
|
|
for file in files: |
|
|
|
file_date_req = requests.get( |
|
|
|
"https://www.googleapis.com/drive/v3/files/" |
|
|
|
"%s?fields=createdTime" % |
|
|
|
file['id'], |
|
|
|
headers=headers) |
|
|
|
create_time = file_date_req.json()['createdTime'][ |
|
|
|
"https://www.googleapis.com/drive/v3/files/%s?fields=createdTime" % |
|
|
|
file['id'], headers=headers) |
|
|
|
create_time = file_date_req.json()[ |
|
|
|
'createdTime'][ |
|
|
|
:19].replace('T', ' ') |
|
|
|
diff_days = ( |
|
|
|
datetime.datetime.now() - datetime. |
|
|
|
datetime.strptime(create_time, |
|
|
|
'%Y-%m-%d ' |
|
|
|
'%H:%M:%S')).days |
|
|
|
fields.datetime.now() - fields.datetime.strptime( |
|
|
|
create_time, '%Y-%m-%d %H:%M:%S')).days |
|
|
|
if diff_days >= rec.days_to_remove: |
|
|
|
requests.delete( |
|
|
|
"https://www.googleapis.com/drive/v3/" |
|
|
|
"files/%s" % |
|
|
|
file['id'], |
|
|
|
headers=headers) |
|
|
|
"https://www.googleapis.com/drive/v3/files/%s" % |
|
|
|
file['id'], headers=headers) |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_success.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
except Exception as e: |
|
|
|
rec.generated_exception = e |
|
|
|
_logger.info('Google Drive Exception: %s', e) |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_failed.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
except Exception: |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_failed.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
raise ValidationError( |
|
|
|
'Please check the credentials before activation') |
|
|
|
else: |
|
|
|
raise ValidationError('Please check connection') |
|
|
|
# Dropbox backup |
|
|
|
elif rec.backup_destination == 'dropbox': |
|
|
|
try: |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
try: |
|
|
|
dbx = dropbox.Dropbox( |
|
|
|
app_key=rec.dropbox_client_key, |
|
|
|
app_secret=rec.dropbox_client_secret, |
|
|
|
oauth2_refresh_token=rec.dropbox_refresh_token) |
|
|
|
dropbox_destination = (rec.dropbox_folder + '/' + |
|
|
|
backup_filename) |
|
|
|
dbx.files_upload(temp.read(), dropbox_destination) |
|
|
|
if rec.auto_remove: |
|
|
|
files = dbx.files_list_folder( |
|
|
|
rec.dropbox_folder) |
|
|
|
file_entries = files.entries |
|
|
|
expired_files = list(filter( |
|
|
|
lambda fl: (fields.datetime.now() - |
|
|
|
fl.client_modified).days >= |
|
|
|
rec.days_to_remove, |
|
|
|
file_entries)) |
|
|
|
for file in expired_files: |
|
|
|
dbx.files_delete_v2(file.path_display) |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_success.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
except Exception as error: |
|
|
|
rec.generated_exception = error |
|
|
|
_logger.info('Dropbox Exception: %s', error) |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_failed.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
except Exception: |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_failed.send_mail(rec.id, force_send=True) |
|
|
|
raise ValidationError( |
|
|
|
'Please check the credentials before activation') |
|
|
|
else: |
|
|
|
raise ValidationError('Please check connection') |
|
|
|
# Onedrive Backup |
|
|
|
elif rec.backup_destination == 'onedrive': |
|
|
|
try: |
|
|
|
if rec.onedrive_token_validity <= fields.Datetime.now(): |
|
|
|
rec.generate_onedrive_refresh_token() |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
headers = { |
|
|
|
'Authorization': 'Bearer %s' % rec.onedrive_access_token, |
|
|
|
'Content-Type': 'application/json'} |
|
|
|
upload_session_url = MICROSOFT_GRAPH_END_POINT + "/v1.0/me/drive/items/%s:/%s:/createUploadSession" % ( |
|
|
|
rec.onedrive_folder_key, backup_filename) |
|
|
|
try: |
|
|
|
upload_session = requests.post(upload_session_url, |
|
|
|
headers=headers) |
|
|
|
upload_url = upload_session.json().get('uploadUrl') |
|
|
|
requests.put(upload_url, data=temp.read()) |
|
|
|
if rec.auto_remove: |
|
|
|
list_url = MICROSOFT_GRAPH_END_POINT + "/v1.0/me/drive/items/%s/children" % rec.onedrive_folder_key |
|
|
|
response = requests.get(list_url, headers=headers) |
|
|
|
files = response.json().get('value') |
|
|
|
for file in files: |
|
|
|
create_time = file['createdDateTime'][ |
|
|
|
:19].replace( |
|
|
|
'T', |
|
|
|
' ') |
|
|
|
diff_days = ( |
|
|
|
fields.datetime.now() - fields.datetime.strptime( |
|
|
|
create_time, '%Y-%m-%d %H:%M:%S')).days |
|
|
|
if diff_days >= rec.days_to_remove: |
|
|
|
delete_url = MICROSOFT_GRAPH_END_POINT + "/v1.0/me/drive/items/%s" % \ |
|
|
|
file['id'] |
|
|
|
requests.delete(delete_url, headers=headers) |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_success.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
except Exception as error: |
|
|
|
rec.generated_exception = error |
|
|
|
_logger.info('Onedrive Exception: %s', error) |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_failed.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
except Exception: |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_failed.send_mail(rec.id, force_send=True) |
|
|
|
raise ValidationError( |
|
|
|
'Please check the credentials before activation') |
|
|
|
else: |
|
|
|
raise ValidationError('Please check connection') |
|
|
|
# NextCloud Backup |
|
|
|
elif rec.backup_destination == 'next_cloud': |
|
|
|
try: |
|
|
|
if rec.domain and rec.next_cloud_password and \ |
|
|
|
rec.next_cloud_user_name: |
|
|
|
try: |
|
|
|
# Connect to NextCloud using the provided username |
|
|
|
# and password |
|
|
|
ncx = NextCloud(rec.domain, |
|
|
|
auth=HTTPBasicAuth( |
|
|
|
rec.next_cloud_user_name, |
|
|
|
rec.next_cloud_password)) |
|
|
|
# Connect to NextCloud again to perform additional |
|
|
|
# operations |
|
|
|
nc = nextcloud_client.Client(rec.domain) |
|
|
|
nc.login(rec.next_cloud_user_name, |
|
|
|
rec.next_cloud_password) |
|
|
|
# Get the folder name from the NextCloud folder ID |
|
|
|
folder_name = rec.nextcloud_folder_key |
|
|
|
# If auto_remove is enabled, remove backup files |
|
|
|
# older than specified days |
|
|
|
if rec.auto_remove: |
|
|
|
folder_path = "/" + folder_name |
|
|
|
for item in nc.list(folder_path): |
|
|
|
backup_file_name = item.path.split("/")[-1] |
|
|
|
backup_date_str = \ |
|
|
|
backup_file_name.split("_")[ |
|
|
|
2] |
|
|
|
backup_date = fields.datetime.strptime( |
|
|
|
backup_date_str, '%Y-%m-%d').date() |
|
|
|
if (fields.date.today() - backup_date).days \ |
|
|
|
>= rec.days_to_remove: |
|
|
|
nc.delete(item.path) |
|
|
|
# If notify_user is enabled, send a success email |
|
|
|
# notification |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_success.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
except Exception as error: |
|
|
|
rec.generated_exception = error |
|
|
|
_logger.info('NextCloud Exception: %s', error) |
|
|
|
if rec.notify_user: |
|
|
|
# If an exception occurs, send a failed email |
|
|
|
# notification |
|
|
|
mail_template_failed.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
# Get the list of folders in the root directory of NextCloud |
|
|
|
data = ncx.list_folders('/').__dict__ |
|
|
|
folders = [ |
|
|
|
[file_name['href'].split('/')[-2], |
|
|
|
file_name['file_id']] |
|
|
|
for file_name in data['data'] if |
|
|
|
file_name['href'].endswith('/')] |
|
|
|
# If the folder name is not found in the list of folders, |
|
|
|
# create the folder |
|
|
|
if folder_name not in [file[0] for file in folders]: |
|
|
|
nc.mkdir(folder_name) |
|
|
|
# Dump the database to a temporary file |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
backup_file_path = temp.name |
|
|
|
remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |
|
|
|
f"{backup_time}.{rec.backup_format}" |
|
|
|
nc.put_file(remote_file_path, backup_file_path) |
|
|
|
else: |
|
|
|
# Dump the database to a temporary file |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
backup_file_path = temp.name |
|
|
|
remote_file_path = f"/{folder_name}/{rec.db_name}_" \ |
|
|
|
f"{backup_time}.{rec.backup_format}" |
|
|
|
nc.put_file(remote_file_path, backup_file_path) |
|
|
|
except Exception: |
|
|
|
raise ValidationError('Please check connection') |
|
|
|
# Amazon S3 Backup |
|
|
|
elif rec.backup_destination == 'amazon_s3': |
|
|
|
if rec.aws_access_key and rec.aws_secret_access_key: |
|
|
|
try: |
|
|
|
# Create a boto3 client for Amazon S3 with provided |
|
|
|
# access key id and secret access key |
|
|
|
bo3 = boto3.client( |
|
|
|
's3', |
|
|
|
aws_access_key_id=rec.aws_access_key, |
|
|
|
aws_secret_access_key=rec.aws_secret_access_key) |
|
|
|
# If auto_remove is enabled, remove the backups that |
|
|
|
# are older than specified days from the S3 bucket |
|
|
|
if rec.auto_remove: |
|
|
|
folder_path = rec.aws_folder_name |
|
|
|
response = bo3.list_objects( |
|
|
|
Bucket=rec.bucket_file_name, |
|
|
|
Prefix=folder_path) |
|
|
|
today = fields.date.today() |
|
|
|
for file in response['Contents']: |
|
|
|
file_path = file['Key'] |
|
|
|
last_modified = file['LastModified'] |
|
|
|
date = last_modified.date() |
|
|
|
age_in_days = (today - date).days |
|
|
|
if age_in_days >= rec.days_to_remove: |
|
|
|
bo3.delete_object( |
|
|
|
Bucket=rec.bucket_file_name, |
|
|
|
Key=file_path) |
|
|
|
# Create a boto3 resource for Amazon S3 with provided |
|
|
|
# access key id and secret access key |
|
|
|
s3 = boto3.resource( |
|
|
|
's3', |
|
|
|
aws_access_key_id=rec.aws_access_key, |
|
|
|
aws_secret_access_key=rec.aws_secret_access_key) |
|
|
|
# Create a folder in the specified bucket, if it |
|
|
|
# doesn't already exist |
|
|
|
s3.Object(rec.bucket_file_name, |
|
|
|
rec.aws_folder_name + '/').put() |
|
|
|
bucket = s3.Bucket(rec.bucket_file_name) |
|
|
|
# Get all the prefixes in the bucket |
|
|
|
prefixes = set() |
|
|
|
for obj in bucket.objects.all(): |
|
|
|
key = obj.key |
|
|
|
if key.endswith('/'): |
|
|
|
prefix = key[:-1] # Remove the trailing slash |
|
|
|
prefixes.add(prefix) |
|
|
|
# If the specified folder is present in the bucket, |
|
|
|
# take a backup of the database and upload it to the |
|
|
|
# S3 bucket |
|
|
|
if rec.aws_folder_name in prefixes: |
|
|
|
temp = tempfile.NamedTemporaryFile( |
|
|
|
suffix='.%s' % rec.backup_format) |
|
|
|
with open(temp.name, "wb+") as tmp: |
|
|
|
odoo.service.db.dump_db(rec.db_name, tmp, |
|
|
|
rec.backup_format) |
|
|
|
backup_file_path = temp.name |
|
|
|
remote_file_path = f"{rec.aws_folder_name}/{rec.db_name}_" \ |
|
|
|
f"{backup_time}.{rec.backup_format}" |
|
|
|
s3.Object(rec.bucket_file_name, |
|
|
|
remote_file_path).upload_file( |
|
|
|
backup_file_path) |
|
|
|
# If notify_user is enabled, send email to the |
|
|
|
# user notifying them about the successful backup |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_success.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|
except Exception as error: |
|
|
|
# If any error occurs, set the 'generated_exception' |
|
|
|
# field to the error message and log the error |
|
|
|
rec.generated_exception = error |
|
|
|
_logger.info('Amazon S3 Exception: %s', error) |
|
|
|
# If notify_user is enabled, send email to the user |
|
|
|
# notifying them about the failed backup |
|
|
|
if rec.notify_user: |
|
|
|
mail_template_failed.send_mail(rec.id, |
|
|
|
force_send=True) |
|
|
|