Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Onedrive_Backup #968

Merged
merged 23 commits into from
Aug 4, 2023
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
67c74af
initial version
MartinRinas Jun 21, 2023
5114adc
update requirements.txt
MartinRinas Jun 21, 2023
115517b
draft 2
MartinRinas Jun 21, 2023
085024d
retrieve scope from config
MartinRinas Jun 24, 2023
005199a
prepare config
MartinRinas Jun 24, 2023
616309d
save persistent token cahe
MartinRinas Jun 26, 2023
1994012
save token cache
MartinRinas Jun 26, 2023
605e975
auth flow, token cache
MartinRinas Jun 28, 2023
4d000ba
minor flake8 fixes
MartinRinas Jun 28, 2023
9f8740f
clean up comments
MartinRinas Jun 28, 2023
4d70337
flake8, refactor
MartinRinas Jun 28, 2023
19a9835
Update publish_docs_to_wiki.yml
MartinRinas Jun 28, 2023
26d65c4
move logic into api.py
MartinRinas Jun 29, 2023
ca20778
formatting
MartinRinas Jun 29, 2023
8a0d9b9
Merge branch 'master' of https://github.com/MartinRinas/core into One…
MartinRinas Jun 29, 2023
23a2d1f
Revert "Update publish_docs_to_wiki.yml"
MartinRinas Jun 29, 2023
d93a824
flake8
MartinRinas Jun 29, 2023
f225b95
add required module to workflow
MartinRinas Jun 29, 2023
4287788
Merge branch 'master' of https://github.com/openWB/core into Onedrive…
MartinRinas Jun 29, 2023
afbeef5
Merge branch 'master' of https://github.com/openWB/core into Onedrive…
MartinRinas Jul 2, 2023
a17f908
Merge branch 'master' of https://github.com/openWB/core into Onedrive…
MartinRinas Jul 7, 2023
c73bd60
Merge branch 'master' of https://github.com/openWB/core into Onedrive…
MartinRinas Jul 26, 2023
4bcbfc1
remove sharepoint-onedrive SDK
MartinRinas Aug 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions packages/helpermodules/command.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
import traceback
from pathlib import Path
import paho.mqtt.client as mqtt
import pickle
import io
from msal import PublicClientApplication
from control.chargepoint import chargepoint
from control.chargepoint.chargepoint_template import get_autolock_plan_default, get_chargepoint_template_default

Expand All @@ -24,6 +27,7 @@
from modules.chargepoints.internal_openwb.chargepoint_module import ChargepointModule
from modules.chargepoints.internal_openwb.config import InternalChargepointMode
from modules.common.component_type import ComponentType, special_to_general_type_mapping, type_to_topic_mapping
from modules.backup_clouds.onedrive.config import OneDriveBackupCloudConfiguration
import dataclass_utils

log = logging.getLogger(__name__)
Expand Down Expand Up @@ -687,6 +691,48 @@ def restoreBackup(self, connection_id: str, payload: dict) -> None:
f'Restore-Status: {result.returncode}<br />Meldung: {result.stdout.decode("utf-8")}',
MessageType.ERROR)

def requestMSALAuthCode(self, connection_id: str, payload: dict) -> None:
""" startet den Authentifizierungsprozess für MSAL (Microsoft Authentication Library) um Onedrive Backup zu ermöglichen
"""

# to-do: Konfiguration aus OneDrive modul importieren?
# Define the scope of access
scope = {"https://graph.microsoft.com/Files.ReadWrite"} # Replace with your desired scope

# Define the authority and the token endpoint for MSA/Live accounts
authority = "https://login.microsoftonline.com/consumers/"
clientID = "e529d8d2-3b0f-4ae4-b2ba-2d9a2bba55b2"

# Create a public client application with msal
app = PublicClientApplication(client_id=clientID, authority=authority)

flow = app.initiate_device_flow(scope)
MartinRinas marked this conversation as resolved.
Show resolved Hide resolved
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
pickleBuffer = io.BytesIO()
pickle.dump(flow, pickleBuffer)

#Pub().pub(f'openWB/set/vehicle/template/ev_template/{new_id}', ev_template_default)
# Pub().pub("openWB/set/command/max_id/ev_template", new_id)
pub_user_message(
payload, connection_id,
'Authorisierung gestartet, bitte den Link öffen, Code eingeben, '
'und Zugang authorisieren. Anschließend Zugangsberechtigung abrufen.',
MessageType.SUCCESS)

def retrieveMSALTokens(self, connection_id: str, payload: dict) -> None:
""" holt die Tokens für MSAL (Microsoft Authentication Library) um Onedrive Backup zu ermöglichen
"""
# Pub().pub(f'openWB/set/vehicle/template/ev_template/{new_id}', ev_template_default)
# Pub().pub("openWB/set/command/max_id/ev_template", new_id)
pub_user_message(
payload, connection_id,
'Zugangsberechtigung erfolgreich abgerufen.',
MessageType.SUCCESS)


class ErrorHandlingContext:
def __init__(self, payload: dict, connection_id: str):
Expand Down
Empty file.
104 changes: 104 additions & 0 deletions packages/modules/backup_clouds/onedrive/backup_cloud.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
#!/usr/bin/env python3
import logging
import msal
import atexit
import os
import json
from msdrive import OneDrive
from modules.backup_clouds.onedrive.config import OneDriveBackupCloud, OneDriveBackupCloudConfiguration
from modules.common.abstract_device import DeviceDescriptor
from modules.common.configurable_backup_cloud import ConfigurableBackupCloud
import pathlib
import base64


log = logging.getLogger(__name__)


def get_tokens(config: OneDriveBackupCloudConfiguration) -> dict:
result = None
cache = msal.SerializableTokenCache()
MartinRinas marked this conversation as resolved.
Show resolved Hide resolved

''' # to-do: add write to config after update
if os.path.exists("/var/www/html/openWB/packages/modules/backup_clouds/onedrive/my_cache.bin"): # to do: read from config
log.debug("reading token cache from file")
cache.deserialize(open("/var/www/html/openWB/packages/modules/backup_clouds/onedrive/my_cache.bin", "r").read())
else:
log.debug("token cache not found")
atexit.register(lambda: open("my_cache.bin", "w").write(cache.serialize()) # to-do: write to config
if cache.has_state_changed else None
)'''

if config.persistent_tokencache:
cache.deserialize(base64.b64decode(config.persistent_tokencache))

# 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)

log.debug("done acquring tokens")
if not result: # We have no token for this account, so the end user shall sign-in
# to-do: stop execution if no authcode is provided, log error

flow = app.initiate_device_flow(config.scope)

if "user_code" not in flow:
raise ValueError(
"Fail to create device flow. Err: %s" % json.dumps(flow, indent=4))

log.debug(flow["message"]) # to-do: present to user, open in browser and ask to sign in

# Ideally you should wait here, in order to save some unnecessary polling
# input("Press Enter after signing in from another device to proceed, CTRL+C to abort.")

result = app.acquire_token_by_device_flow(flow) # By default it will block
# You can follow this instruction to shorten the block time
# https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.acquire_token_by_device_flow
# or you may even turn off the blocking behavior,
# and then keep calling acquire_token_by_device_flow(flow) in your own customized loop

# Check if the token was obtained successfully
if "access_token" in result:
# Print the access token
print(result["access_token"])
else:
# Print the error
print(result.get("error"), result.get("error_description"))
return result


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)
30 changes: 30 additions & 0 deletions packages/modules/backup_clouds/onedrive/config.py
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,
url: 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.url = url
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()
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,5 @@ pkce==1.0.3
# skodaconnect==1.3.4
evdev==1.5.0
#telnetlib3==2.0.2
msal==1.22.0
onedrive-sharepoint-python-sdk==0.0.2
MartinRinas marked this conversation as resolved.
Show resolved Hide resolved