def _load(self): if not os.path.isfile(self._config.get(Setting.DATA_CACHE_FILE_PATH)): self._data = {KEY_SNPASHOTS: {}} else: with open(self._config.get(Setting.DATA_CACHE_FILE_PATH)) as f: self._data = json.load(f) # Check for an upgrade. if KEY_LAST_VERSION in self._data: self._last_version = Version.parse(self._data[KEY_LAST_VERSION]) if self.previousVersion != self.currentVersion: # add an upgrade marker if KEY_UPGRADES not in self._data: self._data[KEY_UPGRADES] = [] self._data[KEY_UPGRADES].append({ 'prev_version': str(self.previousVersion), 'new_version': str(self.currentVersion), 'date': self._time.now().isoformat() }) self._data[KEY_LAST_VERSION] = str(self.currentVersion) self.makeDirty() self.saveIfDirty() if self.previousVersion == Version.default( ) and self.currentVersion > Version.parse("0.103"): self._flags.add(UpgradeFlags.DONT_IGNORE_LEGACY_SNAPSHOTS)
async def authorize(self, request: Request): if 'redirectbacktoken' in request.query: version = Version.parse(request.query.get('version', "0")) token_url = request.query.get('redirectbacktoken') return_url = request.query.get('return', None) state = { 'v': str(version), 'token': token_url, 'return': return_url, 'bg': self.base_context(request).get('backgroundColor'), 'ac': self.base_context(request).get('accentColor'), } # Someone is trying to authenticate with the add-on, direct them to the google auth url raise HTTPSeeOther(await self.exchanger.getAuthorizationUrl( json.dumps(state))) elif 'state' in request.query and 'code' in request.query: state = json.loads(unquote(request.query.get('state'))) code = request.query.get('code') try: version = Version.parse(state["v"]) creds = (await self.exchanger.exchange(code)).serialize( include_secret=False) if version < NEW_AUTH_MINIMUM: # Redirect back to the addon, since this is the older addon url = URL(state['token']).with_query( {'creds': json.dumps(creds)}) raise HTTPSeeOther(url) serialized_creds = str( base64.b64encode(json.dumps(creds).encode("utf-8")), "utf-8") url = URL(state['token']).with_query({ 'creds': serialized_creds, 'host': state['return'] }) context = { **self.base_context(request), 'redirect_url': str(url), 'credentials_serialized': serialized_creds, } if 'bg' in state: context['backgroundColor'] = state['bg'] if 'ac' in state: context['accentColor'] = state['ac'] return aiohttp_jinja2.render_template("authorize.jinja2", request, context) except Exception as e: if isinstance(e, HTTPSeeOther): # expected, pass this thorugh raise self.logError(request, e) content = "The server encountered an error while processing this request: " + str( e) + "<br/>" content += "Please <a href='https://github.com/sabeechen/hassio-google-drive-backup/issues'>file an issue</a> on Home Assistant Google Backup's GitHub page so I'm aware of this problem or attempt authorizing with Google Drive again." return Response(status=500, body=content) else: raise HTTPBadRequest()
async def test_backup_supervisor_path(ha: HaSource, supervisor: SimulatedSupervisor, interceptor: RequestInterceptor): supervisor._super_version = Version(2021, 7) await ha.get() assert not interceptor.urlWasCalled(URL_MATCH_BACKUPS) assert interceptor.urlWasCalled(URL_MATCH_SNAPSHOT)
def _load(self): if not os.path.isfile(self._config.get(Setting.DATA_CACHE_FILE_PATH)): self._data = {NECESSARY_OLD_BACKUP_PLURAL_NAME: {}} else: with open(self._config.get(Setting.DATA_CACHE_FILE_PATH)) as f: self._data = json.load(f) # Check for an upgrade. if KEY_LAST_VERSION in self._data: self._last_version = Version.parse(self._data[KEY_LAST_VERSION]) if self.previousVersion != self.currentVersion: # add an upgrade marker if KEY_UPGRADES not in self._data: self._data[KEY_UPGRADES] = [] self._data[KEY_UPGRADES].append({ 'prev_version': str(self.previousVersion), 'new_version': str(self.currentVersion), 'date': self._time.now().isoformat() }) self._data[KEY_LAST_VERSION] = str(self.currentVersion) self.makeDirty() self.saveIfDirty()
def __init__(self, config: Config, ports: Ports, time: Time): self._config = config self._time = time self._ports = ports self._auth_token = "test_header" self._backups: Dict[str, Any] = {} self._backup_data: Dict[str, bytearray] = {} self._backup_lock = asyncio.Lock() self._backup_inner_lock = asyncio.Lock() self._entities = {} self._events = [] self._attributes = {} self._notification = None self._min_backup_size = 1024 * 1024 * 5 self._max_backup_size = 1024 * 1024 * 5 self._addon_slug = "self_slug" self._options = self.defaultOptions() self._username = "******" self._password = "******" self._addons = all_addons.copy() self._super_version = Version(2021, 8) self.installAddon(self._addon_slug, "Home Assistant Google drive Backup") self.installAddon("42", "The answer") self.installAddon("sgadg", "sdgsagsdgsggsd")
def __init__(self, config: Config, time: Time): self._config = config self._data = {} self._dirty = {} self._time = time self._last_version = Version.default() self._flags = set() self._load()
async def test_old_delete_path(ha: HaSource, supervisor: SimulatedSupervisor, interceptor: RequestInterceptor, time: FakeTime): supervisor._super_version = Version(2020, 8) await ha.get() backup: HABackup = await ha.create(CreateOptions(time.now(), "Test Name")) full = DummyBackup(backup.name(), backup.date(), backup.size(), backup.slug(), "dummy") full.addSource(backup) await ha.delete(full) assert interceptor.urlWasCalled("/snapshots/{0}/remove".format( backup.slug()))
async def picker(self, request: Request): version = Version.parse(request.query.get('version', "0")) bg = request.query.get('bg', self.config.get(Setting.BACKGROUND_COLOR)) ac = request.query.get('ac', self.config.get(Setting.ACCENT_COLOR)) return { **self.base_context(request), "client_id": self.config.get(Setting.DEFAULT_DRIVE_CLIENT_ID), "developer_key": self.config.get(Setting.DRIVE_PICKER_API_KEY), "app_id": self.config.get(Setting.DEFAULT_DRIVE_CLIENT_ID).split("-")[0], 'backgroundColor': bg, 'accentColor': ac, "do_redirect": str(version < NEW_AUTH_MINIMUM).lower() }
def getUpgradeTime(self, version: Version): for upgrade in self._data[KEY_UPGRADES]: if Version.parse(upgrade['new_version']) >= version: return self._time.parse(upgrade['date']) return self._time.now()
def currentVersion(self): return Version.parse(VERSION)
async def test_version_upgrades(time: Time, injector: Injector, config: Config) -> None: # Simluate upgrading from an un-tracked version assert not os.path.exists(config.get(Setting.DATA_CACHE_FILE_PATH)) cache = injector.get(DataCache) upgrade_time = time.now() assert cache.previousVersion == Version.default() assert cache.currentVersion == Version.parse(VERSION) assert cache.checkFlag(UpgradeFlags.DONT_IGNORE_LEGACY_SNAPSHOTS) assert os.path.exists(config.get(Setting.DATA_CACHE_FILE_PATH)) with open(config.get(Setting.DATA_CACHE_FILE_PATH)) as f: data = json.load(f) assert data["upgrades"] == [{ "prev_version": str(Version.default()), "new_version": VERSION, "date": upgrade_time.isoformat() }] # Reload the data cache, verify there is no upgrade. time.advance(days=1) cache = DataCache(config, time) assert cache.previousVersion == Version.parse(VERSION) assert cache.currentVersion == Version.parse(VERSION) assert not cache.checkFlag(UpgradeFlags.DONT_IGNORE_LEGACY_SNAPSHOTS) assert os.path.exists(config.get(Setting.DATA_CACHE_FILE_PATH)) with open(config.get(Setting.DATA_CACHE_FILE_PATH)) as f: data = json.load(f) assert data["upgrades"] == [{ "prev_version": str(Version.default()), "new_version": VERSION, "date": upgrade_time.isoformat() }] # simulate upgrading to a new version, verify an upgrade gets identified. upgrade_version = Version.parse("200") class UpgradeCache(DataCache): def __init__(self): super().__init__(config, time) @property def currentVersion(self): return upgrade_version cache = UpgradeCache() assert cache.previousVersion == Version.parse(VERSION) assert cache.currentVersion == upgrade_version assert os.path.exists(config.get(Setting.DATA_CACHE_FILE_PATH)) with open(config.get(Setting.DATA_CACHE_FILE_PATH)) as f: data = json.load(f) assert data["upgrades"] == [{ "prev_version": str(Version.default()), "new_version": VERSION, "date": upgrade_time.isoformat() }, { "prev_version": VERSION, "new_version": str(upgrade_version), "date": time.now().isoformat() }] next_upgrade_time = time.now() time.advance(days=1) # Verify version upgrade time queries work as expected assert cache.getUpgradeTime(Version.parse(VERSION)) == upgrade_time assert cache.getUpgradeTime(Version.default()) == upgrade_time assert cache.getUpgradeTime(upgrade_version) == next_upgrade_time # degenerate case, should never happen but a sensible value needs to be returned assert cache.getUpgradeTime(Version.parse("201")) == time.now()
import base64 from os.path import abspath, join from aiohttp.web import Application, json_response, Request, TCPSite, AppRunner, post, Response, static, get from aiohttp.client_exceptions import ClientResponseError, ClientConnectorError, ServerConnectionError, ServerDisconnectedError, ServerTimeoutError from aiohttp.web_exceptions import HTTPBadRequest, HTTPSeeOther from backup.creds import Exchanger from backup.config import Config, Setting, VERSION from backup.exceptions import GoogleCredentialsExpired, ensureKey, KnownError from injector import ClassAssistedBuilder, inject, singleton from .errorstore import ErrorStore from .cloudlogger import CloudLogger from yarl import URL from backup.config import Version from urllib.parse import unquote NEW_AUTH_MINIMUM = Version(0, 101, 3) @singleton class Server(): @inject def __init__(self, config: Config, exchanger_builder: ClassAssistedBuilder[Exchanger], logger: CloudLogger, error_store: ErrorStore): self.exchanger = exchanger_builder.build( client_id=config.get(Setting.DEFAULT_DRIVE_CLIENT_ID), client_secret=config.get(Setting.DEFAULT_DRIVE_CLIENT_SECRET), redirect=URL(config.get(Setting.AUTHORIZATION_HOST)).with_path("/drive/authorize")) self.logger = logger
from backup.logger import getLogger, getHistory, TraceLogger from backup.creds import Exchanger, MANUAL_CODE_REDIRECT_URI, Creds from backup.debugworker import DebugWorker from backup.drive import FolderFinder from backup.const import FOLDERS from .debug import Debug from yarl import URL logger = getLogger(__name__) # Used to Google's oauth verification SCOPE: str = 'https://www.googleapis.com/auth/drive.file' MIME_TEXT_HTML = "text/html" MIME_JSON = "application/json" VERSION_CREATION_TRACKING = Version(0, 104, 0) @singleton class UiServer(Trigger, Startable): @inject def __init__(self, debug: Debug, coord: Coordinator, ha_source: HaSource, harequests: HaRequests, time: Time, config: Config, global_info: GlobalInfo, estimator: Estimator, session: ClientSession, exchanger_builder: ClassAssistedBuilder[Exchanger], debug_worker: DebugWorker, folder_finder: FolderFinder, data_cache: DataCache, haupdater: HaUpdater): super().__init__() # Currently running server tasks self.runners = [] self.exchanger_builder = exchanger_builder self._coord = coord
def test_version(): assert Version(1, 2, 3) == Version(1, 2, 3) assert Version(1, 2, 3) >= Version(1, 2, 3) assert Version(1, 2, 3) <= Version(1, 2, 3) assert Version(1, 2, 3) > Version(1, 2) assert Version(1) < Version(2) assert Version(2) > Version(1) assert Version(1) != Version(2) assert Version(1, 2) > Version(1) assert Version(1) < Version(1, 2)
def test_broken_versions(): assert Version.parse("") == Version.default() assert Version.parse(".") == Version.default() assert Version.parse("empty") == Version.default() assert Version.parse("no.version.here") == Version.default()
def test_junk_strings(): assert Version.parse("1-.2.3.1") == Version(1, 2, 3, 1) assert Version.parse("ignore-1.2.3.1") == Version(1, 2, 3, 1) assert Version.parse( "1.2.ignore.this.text.3.and...andhere.too.1") == Version(1, 2, 3, 1)
def test_default(): assert Version.default() == Version.default() assert not Version.default() > Version.default() assert not Version.default() < Version.default() assert not Version.default() != Version.default() assert Version.default() >= Version.default() assert Version.default() <= Version.default()
def test_parse_staging(): assert Version.parse("1.0.staging.1") == Version(1, 0, 1) assert Version.parse("1.0.staging.1").staging assert Version.parse("1.0.staging.1") > Version(1.0) assert Version.parse("1.2.3") == Version(1, 2, 3)
def test_parse(): assert Version.parse("1.0") == Version(1, 0) assert Version.parse("1.2.3") == Version(1, 2, 3)