diff --git a/auto_database_backup/__init__.py b/auto_database_backup/__init__.py index a4a979205..117da7d8f 100644 --- a/auto_database_backup/__init__.py +++ b/auto_database_backup/__init__.py @@ -20,3 +20,4 @@ from . import models from . import wizard +from . import controllers diff --git a/auto_database_backup/__manifest__.py b/auto_database_backup/__manifest__.py index 12293d2b3..ebab59d6c 100644 --- a/auto_database_backup/__manifest__.py +++ b/auto_database_backup/__manifest__.py @@ -22,8 +22,8 @@ { 'name': "Automatic Database Backup To Local Server, Remote Server, Google Drive And Dropbox", - 'version': '15.0.2.0.1', - 'summary': """Generate automatic backup of databases and store to local, google drive, dropbox or remote server""", + 'version': '15.0.3.0.1', + 'summary': """Generate automatic backup of databases and store to local, google drive, dropbox, onedrive or remote server""", 'description': """This module has been developed for creating database backups automatically and store it to the different locations.""", 'author': "Cybrosys Techno Solutions", @@ -36,8 +36,9 @@ 'security/ir.model.access.csv', 'data/data.xml', 'views/db_backup_configure_views.xml', - 'wizard/dropbox_authcode_wizard_views.xml' + 'wizard/dropbox_authcode_wizard_views.xml', ], + 'external_dependencies': {'python': ['dropbox']}, 'license': 'LGPL-3', 'images': ['static/description/banner.gif'], 'installable': True, diff --git a/auto_database_backup/controllers/__init__.py b/auto_database_backup/controllers/__init__.py new file mode 100644 index 000000000..8c5dc606f --- /dev/null +++ b/auto_database_backup/controllers/__init__.py @@ -0,0 +1,21 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2022-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . + +from . import main diff --git a/auto_database_backup/controllers/main.py b/auto_database_backup/controllers/main.py new file mode 100644 index 000000000..b9adf3cef --- /dev/null +++ b/auto_database_backup/controllers/main.py @@ -0,0 +1,35 @@ +# -*- coding: utf-8 -*- +############################################################################# +# +# Cybrosys Technologies Pvt. Ltd. +# +# Copyright (C) 2022-TODAY Cybrosys Technologies() +# Author: Cybrosys Techno Solutions() +# +# You can modify it under the terms of the GNU LESSER +# GENERAL PUBLIC LICENSE (LGPL v3), Version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU LESSER GENERAL PUBLIC LICENSE (LGPL v3) for more details. +# +# You should have received a copy of the GNU LESSER GENERAL PUBLIC LICENSE +# (LGPL v3) along with this program. +# If not, see . + +import json + +from odoo import http +from odoo.http import request + + +class OnedriveAuth(http.Controller): + + @http.route('/onedrive/authentication', type='http', auth="public") + def oauth2callback(self, **kw): + state = json.loads(kw['state']) + backup_config = request.env['db.backup.configure'].sudo().browse(state.get('backup_config_id')) + backup_config.get_onedrive_tokens(kw.get('code')) + url_return = state.get('url_return') + return request.redirect(url_return) diff --git a/auto_database_backup/data/data.xml b/auto_database_backup/data/data.xml index 2787ddd11..c7151d219 100644 --- a/auto_database_backup/data/data.xml +++ b/auto_database_backup/data/data.xml @@ -51,6 +51,9 @@ Dropbox + + Onedrive + .

@@ -72,6 +75,9 @@ Dropbox + + + Onedrive
@@ -138,6 +144,9 @@
Dropbox + + + Onedrive
diff --git a/auto_database_backup/doc/RELEASE_NOTES.md b/auto_database_backup/doc/RELEASE_NOTES.md index d17f5d9fb..c91697f2e 100644 --- a/auto_database_backup/doc/RELEASE_NOTES.md +++ b/auto_database_backup/doc/RELEASE_NOTES.md @@ -6,7 +6,12 @@ - Initial commit for auto_database_backup #### 14.06.2022 -#### Version 15.0.1.0.1 +#### Version 15.0.2.0.1 #### ADD - Dropbox integration added. Backup can be stored in to dropbox. +#### 20.08.2022 +#### Version 15.0.3.0.1 +#### ADD +- Onedrive integration added. Backup can be stored in to onedrive. + diff --git a/auto_database_backup/models/db_backup_configure.py b/auto_database_backup/models/db_backup_configure.py index b3a0f3a27..cacf3dfff 100644 --- a/auto_database_backup/models/db_backup_configure.py +++ b/auto_database_backup/models/db_backup_configure.py @@ -24,9 +24,13 @@ from odoo import models, fields, api, _ from odoo.exceptions import UserError, ValidationError import odoo from odoo.service import db +from odoo.http import request import dropbox +from werkzeug import urls +from datetime import timedelta + import datetime import os import paramiko @@ -39,6 +43,9 @@ import logging _logger = logging.getLogger(__name__) +ONEDRIVE_SCOPE = ['offline_access openid Files.ReadWrite.All'] +MICROSOFT_GRAPH_END_POINT = "https://graph.microsoft.com" + class AutoDatabaseBackup(models.Model): _name = 'db.backup.configure' @@ -56,7 +63,8 @@ class AutoDatabaseBackup(models.Model): ('google_drive', 'Google Drive'), ('ftp', 'FTP'), ('sftp', 'SFTP'), - ('dropbox', 'Dropbox') + ('dropbox', 'Dropbox'), + ('onedrive', 'Onedrive') ], string='Backup Destination') backup_path = fields.Char(string='Backup Path', help='Local storage directory path') sftp_host = fields.Char(string='SFTP Host') @@ -84,6 +92,22 @@ class AutoDatabaseBackup(models.Model): 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') + onedrive_client_id = fields.Char(string='Onedrive Client ID', copy=False) + onedrive_client_secret = fields.Char(string='Onedrive Client Secret', compy=False) + onedrive_access_token = fields.Char(string='Onedrive Access Token', copy=False) + onedrive_refresh_token = fields.Char(string='Onedrive Refresh Token', copy=False) + onedrive_token_validity = fields.Datetime(string='Onedrive Token Validity', copy=False) + onedrive_folder_id = fields.Char(string='Folder ID') + is_onedrive_token_generated = fields.Boolean(string='onedrive Tokens Generated', + compute='_compute_is_onedrive_token_generated', copy=False) + + @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): @@ -95,7 +119,7 @@ class AutoDatabaseBackup(models.Model): def action_get_dropbox_auth_code(self): """ - Open a wizard to setup dropbox Authorization code + Open a wizard to set up dropbox Authorization code """ return { 'type': 'ir.actions.act_window', @@ -105,6 +129,92 @@ class AutoDatabaseBackup(models.Model): 'target': 'new', } + def action_get_onedrive_auth_code(self): + """ + Generate onedrive authorization code + """ + AUTHORITY = 'https://login.microsoftonline.com/common/oauth2/v2.0/authorize' + action = self.env["ir.actions.act_window"].sudo()._for_xml_id("auto_database_backup.action_db_backup_configure") + 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['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_id, + '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 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_id, + '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_id, + '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, + }) + except requests.HTTPError as error: + _logger.exception("Bad microsoft onedrive request : %s !", error.response.content) + raise error + def get_dropbox_auth_url(self): """ Return dropbox authorization url @@ -330,3 +440,33 @@ class AutoDatabaseBackup(models.Model): _logger.info('Dropbox Exception: %s', error) if rec.notify_user: mail_template_failed.send_mail(rec.id, force_send=True) + # Onedrive Backup + elif rec.backup_destination == 'onedrive': + 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_id, 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_id + 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 = (datetime.datetime.now() - datetime.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) diff --git a/auto_database_backup/static/description/assets/icons/onedrive.png b/auto_database_backup/static/description/assets/icons/onedrive.png new file mode 100644 index 000000000..8a779bcc2 Binary files /dev/null and b/auto_database_backup/static/description/assets/icons/onedrive.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/dropbox1.png b/auto_database_backup/static/description/assets/screenshots/dropbox1.png new file mode 100644 index 000000000..b06cd219f Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/dropbox1.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/dropbox2.png b/auto_database_backup/static/description/assets/screenshots/dropbox2.png new file mode 100644 index 000000000..2544b8cd8 Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/dropbox2.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/dropbox3.png b/auto_database_backup/static/description/assets/screenshots/dropbox3.png new file mode 100644 index 000000000..1ac146ad6 Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/dropbox3.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/dropbox4.png b/auto_database_backup/static/description/assets/screenshots/dropbox4.png new file mode 100644 index 000000000..d392e50de Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/dropbox4.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/onedrive1.png b/auto_database_backup/static/description/assets/screenshots/onedrive1.png new file mode 100644 index 000000000..6e5e16728 Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/onedrive1.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/onedrive2.png b/auto_database_backup/static/description/assets/screenshots/onedrive2.png new file mode 100644 index 000000000..155d97204 Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/onedrive2.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/onedrive3.png b/auto_database_backup/static/description/assets/screenshots/onedrive3.png new file mode 100644 index 000000000..f4e564206 Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/onedrive3.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/onedrive4.png b/auto_database_backup/static/description/assets/screenshots/onedrive4.png new file mode 100644 index 000000000..4ae8a4c0f Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/onedrive4.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/onedrive5.png b/auto_database_backup/static/description/assets/screenshots/onedrive5.png new file mode 100644 index 000000000..44123fcd5 Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/onedrive5.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/onedrive6.png b/auto_database_backup/static/description/assets/screenshots/onedrive6.png new file mode 100644 index 000000000..c078e3b22 Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/onedrive6.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/onedrive7.png b/auto_database_backup/static/description/assets/screenshots/onedrive7.png new file mode 100644 index 000000000..3e771fcc7 Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/onedrive7.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/onedrive8.png b/auto_database_backup/static/description/assets/screenshots/onedrive8.png new file mode 100644 index 000000000..8db9d4bce Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/onedrive8.png differ diff --git a/auto_database_backup/static/description/assets/screenshots/onedrive9.png b/auto_database_backup/static/description/assets/screenshots/onedrive9.png new file mode 100644 index 000000000..33e1a2edb Binary files /dev/null and b/auto_database_backup/static/description/assets/screenshots/onedrive9.png differ diff --git a/auto_database_backup/static/description/banner.gif b/auto_database_backup/static/description/banner.gif index 57bfa2a33..591c8b18d 100644 Binary files a/auto_database_backup/static/description/banner.gif and b/auto_database_backup/static/description/banner.gif differ diff --git a/auto_database_backup/static/description/index.html b/auto_database_backup/static/description/index.html index 1803f2346..1946dc5c6 100644 --- a/auto_database_backup/static/description/index.html +++ b/auto_database_backup/static/description/index.html @@ -7,7 +7,7 @@ Automatic Database Backup

- Automatic Database Backup To Local Server, Remote Server, Google Drive And Dropbox + Automatic Database Backup To Local Server, Remote Server, Google Drive, Onedrive And Dropbox

@@ -91,7 +91,7 @@

This module helps to generate backups of your databases automatically on regular interval of times. - The generated backups can be stored into local storage, ftp server, sftp server or Google Drive. + The generated backups can be stored into local storage, ftp server, sftp server, dropbox, Google Drive or Onedrive. User can enable auto remove option to automatically delete old backups. User can enable email notification to be notified about the success and failure of the backup generation and storage. @@ -174,6 +174,22 @@ +

+
+
+ +
+
+

+ Store Backup to Onedrive

+

+ Generated backup can be stored to Onedrive. +

+
+
+
+
+
+

+ Store Backup to Dropbox

+

+ Select backup destination as Dropbox. Enter the App key and App secret. + you'll need to register a new app in the App + Console. + Select Dropbox API app and choose your app's permission (files.content.write and files.content.read permissions + required). +

+

+ Please install dropbox library (pip install dropbox). +

+ + +

+ Setup refresh token +

+ +

+ Get the authorization code and click confirm. +

+ +

+ Reset the refresh token if required +

+ +
+ +
+

+ Store Backup to Onedrive

+

+ Select backup destination as onedrive. Enter the App key and App secret. + you'll need to register a new app in the Microsoft Azure portal. + While registering the app for the Redirect URI restrictions, copy your Odoo database URI followed by /onedrive/authentication. Example:. +

+ + +

+ Copy the Client ID +

+ +

+ Generate Client Secret. +

+ + +

+ get onedrive folder ID, where need to store the backup files, +

+ +

+ Configure the backup +

+ +

+ Setu Tokens, it will be redirected to an authorization page. +

+ + + +

+ Reset the token if required +

+ +
+ +

+ + + + + + +
@@ -80,6 +87,24 @@
+
+
+ +
+
+ +
+