-
Notifications
You must be signed in to change notification settings - Fork 74
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* initial version * update requirements.txt * draft 2 * retrieve scope from config * prepare config * save persistent token cahe * save token cache * auth flow, token cache * minor flake8 fixes * clean up comments * flake8, refactor * Update publish_docs_to_wiki.yml * move logic into api.py * formatting * Revert "Update publish_docs_to_wiki.yml" This reverts commit 19a9835. * flake8 * add required module to workflow * remove sharepoint-onedrive SDK
- Loading branch information
1 parent
8b793ca
commit 0b4c1dd
Showing
11 changed files
with
526 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,170 @@ | ||
import logging | ||
import pickle | ||
import json | ||
import paho.mqtt.publish as publish | ||
import msal | ||
import base64 | ||
|
||
from msal import PublicClientApplication | ||
from helpermodules.messaging import MessageType | ||
from modules.backup_clouds.onedrive.config import OneDriveBackupCloud, OneDriveBackupCloudConfiguration | ||
|
||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
def encode_str_base64(string: str) -> str: | ||
string_bytes = string.encode("ascii") | ||
string_base64_bytes = base64.b64encode(string_bytes) | ||
string_base64_string = string_base64_bytes.decode("ascii") | ||
return string_base64_string | ||
|
||
|
||
def save_tokencache(config: OneDriveBackupCloudConfiguration, cache: str) -> None: | ||
# encode cache to base64 and save to config | ||
log.debug("saving updated tokencache to config") | ||
config.persistent_tokencache = encode_str_base64(cache) | ||
|
||
# construct full configuartion object for cloud backup | ||
backupcloud = OneDriveBackupCloud() | ||
backupcloud.configuration = config | ||
backupcloud_to_mqtt = json.dumps(backupcloud.__dict__, default=lambda o: o.__dict__) | ||
log.debug("Config to MQTT:" + str(backupcloud_to_mqtt)) | ||
|
||
publish.single("openWB/set/system/backup_cloud/config", backupcloud_to_mqtt, retain=True, hostname="localhost") | ||
|
||
|
||
def get_tokens(config: OneDriveBackupCloudConfiguration) -> dict: | ||
result = None | ||
cache = msal.SerializableTokenCache() | ||
|
||
if config.persistent_tokencache: | ||
cache.deserialize(base64.b64decode(config.persistent_tokencache)) | ||
else: | ||
raise Exception("No tokencache found, please re-configure and re-authorize access Cloud backup settings.") | ||
|
||
# Create a public client application with msal | ||
log.debug("creating MSAL public client application") | ||
app = msal.PublicClientApplication(client_id=config.clientID, authority=config.authority, token_cache=cache) | ||
|
||
log.debug("getting accounts") | ||
accounts = app.get_accounts() | ||
if accounts: | ||
chosen = accounts[0] # assume that we only will have a single account in cache | ||
log.debug("selected account " + str(chosen["username"])) | ||
# Now let's try to find a token in cache for this account | ||
result = app.acquire_token_silent(scopes=config.scope, account=chosen) | ||
else: | ||
raise Exception("No matching account found,please re-configure and re-authorize access Cloud backup settings.") | ||
|
||
log.debug("done acquring tokens") | ||
if not result: # We have no token for this account, so the end user shall sign-in | ||
raise Exception("No token found, please re-configure and re-authorize access Cloud backup settings.") | ||
|
||
if "access_token" in result: | ||
log.debug("access token retrieved") | ||
save_tokencache(config=config, cache=cache.serialize()) | ||
else: | ||
# Print the error | ||
raise Exception("Error retrieving access token", result.get("error"), result.get("error_description")) | ||
return result | ||
|
||
|
||
def generateMSALAuthCode(cloudbackup: OneDriveBackupCloud) -> dict: | ||
""" startet den Authentifizierungsprozess für MSAL (Microsoft Authentication Library) für Onedrive Backup | ||
und speichert den AuthCode in der Konfiguration""" | ||
result = dict( | ||
message="", | ||
MessageType=MessageType.SUCCESS | ||
) | ||
|
||
if cloudbackup is None: | ||
result["message"] = """Es ist keine Backup-Cloud konfiguriert. | ||
Bitte Konfiguration speichern und erneut versuchen.<br />""" | ||
result["MessageType"] = MessageType.WARNING | ||
return result | ||
|
||
# Create a public client application with msal | ||
app = PublicClientApplication( | ||
client_id=cloudbackup.configuration.clientID, | ||
authority=cloudbackup.configuration.authority | ||
) | ||
|
||
# create device flow to obtain auth code | ||
flow = app.initiate_device_flow(cloudbackup.configuration.scope) | ||
if "user_code" not in flow: | ||
raise Exception( | ||
"Fail to create device flow. Err: %s" % json.dumps(flow, indent=4)) | ||
|
||
flow["expires_at"] = 0 # Mark it as expired immediately to prevent | ||
pickleString = str(pickle.dumps(flow), encoding='latin1') | ||
|
||
cloudbackup.configuration.flow = str(pickleString) | ||
cloudbackup.configuration.authcode = flow["user_code"] | ||
cloudbackup.configuration.authurl = flow["verification_uri"] | ||
cloudbackupconfig_to_mqtt = json.dumps(cloudbackup.__dict__, default=lambda o: o.__dict__) | ||
|
||
publish.single( | ||
"openWB/set/system/backup_cloud/config", cloudbackupconfig_to_mqtt, retain=True, hostname="localhost" | ||
) | ||
|
||
result["message"] = """Authorisierung gestartet, bitte den Link öffen, Code eingeben, | ||
und Zugang authorisieren. Anschließend Zugangsberechtigung abrufen.""" | ||
result["MessageType"] = MessageType.SUCCESS | ||
|
||
return result | ||
|
||
|
||
def retrieveMSALTokens(cloudbackup: OneDriveBackupCloud) -> dict: | ||
result = dict( | ||
message="", | ||
MessageType=MessageType.SUCCESS | ||
) | ||
if cloudbackup is None: | ||
result["message"] = """Es ist keine Backup-Cloud konfiguriert. | ||
Bitte Konfiguration speichern und erneut versuchen.<br />""" | ||
result["MessageType"] = MessageType.WARNING | ||
return result | ||
|
||
# Create a public client application with msal | ||
tokens = None | ||
cache = msal.SerializableTokenCache() | ||
app = PublicClientApplication(client_id=cloudbackup.configuration.clientID, | ||
authority=cloudbackup.configuration.authority, token_cache=cache) | ||
|
||
f = cloudbackup.configuration.flow | ||
if f is None: | ||
result["message"] = """Es ist wurde kein Auth-Code erstellt. | ||
Bitte zunächst Auth-Code erstellen und den Authorisierungsprozess beenden.<br />""" | ||
result["MessageType"] = MessageType.WARNING | ||
return result | ||
flow = pickle.loads(bytes(f, encoding='latin1')) | ||
|
||
tokens = app.acquire_token_by_device_flow(flow) | ||
# https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.acquire_token_by_device_flow | ||
# https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-device-code | ||
# Check if the token was obtained successfully | ||
if "access_token" in tokens: | ||
log.debug("retrieved access token") | ||
|
||
# Tokens retrieved, remove auth codes as they are single use only. | ||
cloudbackup.configuration.flow = None | ||
cloudbackup.configuration.authcode = None | ||
cloudbackup.configuration.authurl = None | ||
|
||
# save tokens | ||
save_tokencache(config=cloudbackup.configuration, cache=cache.serialize()) | ||
result["message"] = """Zugangsberechtigung erfolgreich abgerufen.""" | ||
result["MessageType"] = MessageType.SUCCESS | ||
return result | ||
|
||
else: | ||
result["message"] = """"Es konnten keine Tokens abgerufen werden: | ||
%s <br> %s""" % (tokens.get("error"), tokens.get("error_description")) | ||
result["MessageType"] = MessageType.WARNING | ||
'''pub_user_message(payload, connection_id, | ||
"Es konnten keine Tokens abgerufen werden: %s <br> %s" | ||
% (result.get("error"), result.get("error_description")), MessageType.WARNING | ||
) | ||
''' | ||
return result |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
#!/usr/bin/env python3 | ||
import logging | ||
import os | ||
import pathlib | ||
|
||
from modules.backup_clouds.onedrive.msdrive.onedrive import OneDrive | ||
from modules.backup_clouds.onedrive.api import get_tokens | ||
from modules.backup_clouds.onedrive.config import OneDriveBackupCloud, OneDriveBackupCloudConfiguration | ||
from modules.common.abstract_device import DeviceDescriptor | ||
from modules.common.configurable_backup_cloud import ConfigurableBackupCloud | ||
|
||
|
||
log = logging.getLogger(__name__) | ||
|
||
|
||
def upload_backup(config: OneDriveBackupCloudConfiguration, backup_filename: str, backup_file: bytes) -> None: | ||
# upload a single file to onedrive useing credentials from OneDriveBackupCloudConfiguration | ||
# https://docs.microsoft.com/en-us/onedrive/developer/rest-api/api/driveitem_put_content?view=odsp-graph-online | ||
tokens = get_tokens(config) # type: ignore | ||
log.debug("token object retrieved, access_token: %s", tokens.__len__) | ||
log.debug("instantiate OneDrive connection") | ||
onedrive = OneDrive(access_token=tokens["access_token"]) | ||
|
||
localbackup = os.path.join(pathlib.Path().resolve(), 'data', 'backup', backup_filename) | ||
remote_filename = backup_filename.replace(':', '-') # file won't upload when name contains ':' | ||
|
||
if not config.backuppath.endswith("/"): | ||
log.debug("fixing missing ending slash in backuppath: " + config.backuppath) | ||
config.backuppath = config.backuppath + "/" | ||
|
||
log.debug("uploading file %s to OneDrive", backup_filename) | ||
onedrive.upload_item(item_path=(config.backuppath+remote_filename), file_path=localbackup, | ||
conflict_behavior="replace") | ||
|
||
|
||
def create_backup_cloud(config: OneDriveBackupCloud): | ||
def updater(backup_filename: str, backup_file: bytes): | ||
upload_backup(config.configuration, backup_filename, backup_file) | ||
return ConfigurableBackupCloud(config=config, component_updater=updater) | ||
|
||
|
||
device_descriptor = DeviceDescriptor(configuration_factory=OneDriveBackupCloud) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
from typing import Optional | ||
|
||
|
||
class OneDriveBackupCloudConfiguration: | ||
def __init__(self, backuppath: str = "/openWB/Backup/", | ||
persistent_tokencache: Optional[str] = None, | ||
authurl: Optional[str] = None, | ||
authcode: Optional[str] = None, | ||
scope: Optional[list] = ["https://graph.microsoft.com/Files.ReadWrite"], | ||
authority: Optional[str] = "https://login.microsoftonline.com/consumers/", | ||
clientID: Optional[str] = "e529d8d2-3b0f-4ae4-b2ba-2d9a2bba55b2", | ||
flow: Optional[str] = None) -> None: | ||
self.backuppath = backuppath | ||
self.persistent_tokencache = persistent_tokencache | ||
self.authurl = authurl | ||
self.authcode = authcode | ||
self.scope = scope | ||
self.authority = authority | ||
self.clientID = clientID | ||
self.flow = flow | ||
|
||
|
||
class OneDriveBackupCloud: | ||
def __init__(self, | ||
name: str = "OneDrive", | ||
type: str = "onedrive", | ||
configuration: OneDriveBackupCloudConfiguration = None) -> None: | ||
self.name = name | ||
self.type = type | ||
self.configuration = configuration or OneDriveBackupCloudConfiguration() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
BASE_GRAPH_URL = "https://graph.microsoft.com/v1.0" | ||
SIMPLE_UPLOAD_MAX_SIZE = 4000000 # 4MB | ||
CHUNK_UPLOAD_MAX_SIZE = 3276800 # ~3MB must be divisible by 327680 bytes |
Oops, something went wrong.