Update the Google Photos integration to limit scope to Home Assistant created content (#126398)

This commit is contained in:
Allen Porter 2024-09-21 10:56:13 -07:00 committed by GitHub
parent 556deb4f77
commit 505fb3738f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 15 additions and 37 deletions

View File

@ -6,12 +6,9 @@ OAUTH2_AUTHORIZE = "https://accounts.google.com/o/oauth2/v2/auth"
OAUTH2_TOKEN = "https://oauth2.googleapis.com/token"
UPLOAD_SCOPE = "https://www.googleapis.com/auth/photoslibrary.appendonly"
READ_SCOPES = [
"https://www.googleapis.com/auth/photoslibrary.readonly",
"https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata",
]
READ_SCOPE = "https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
OAUTH2_SCOPES = [
*READ_SCOPES,
READ_SCOPE,
UPLOAD_SCOPE,
"https://www.googleapis.com/auth/userinfo.profile",
]

View File

@ -19,11 +19,10 @@ from homeassistant.components.media_source import (
from homeassistant.core import HomeAssistant
from . import GooglePhotosConfigEntry
from .const import DOMAIN, READ_SCOPES
from .const import DOMAIN, READ_SCOPE
_LOGGER = logging.getLogger(__name__)
MAX_RECENT_PHOTOS = 100
MEDIA_ITEMS_PAGE_SIZE = 100
ALBUM_PAGE_SIZE = 50
@ -38,16 +37,12 @@ class SpecialAlbumDetails:
path: str
title: str
list_args: dict[str, Any]
max_photos: int | None
class SpecialAlbum(Enum):
"""Special Album types."""
RECENT = SpecialAlbumDetails("recent", "Recent Photos", {}, MAX_RECENT_PHOTOS)
FAVORITE = SpecialAlbumDetails(
"favorites", "Favorite Photos", {"favorites": True}, None
)
UPLOADED = SpecialAlbumDetails("uploaded", "Uploaded", {})
@classmethod
def of(cls, path: str) -> Self | None:
@ -247,12 +242,6 @@ class GooglePhotosMediaSource(MediaSource):
**list_args, page_size=MEDIA_ITEMS_PAGE_SIZE
):
media_items.extend(media_item_result.media_items)
if (
special_album
and (max_photos := special_album.value.max_photos)
and len(media_items) > max_photos
):
break
except GooglePhotosApiError as err:
raise BrowseError(f"Error listing media items: {err}") from err
@ -270,7 +259,7 @@ class GooglePhotosMediaSource(MediaSource):
entries = []
for entry in self.hass.config_entries.async_loaded_entries(DOMAIN):
scopes = entry.data["token"]["scope"].split(" ")
if any(scope in scopes for scope in READ_SCOPES):
if READ_SCOPE in scopes:
entries.append(entry)
return entries

View File

@ -92,8 +92,7 @@ async def test_full_flow(
f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
"&redirect_uri=https://example.com/auth/external/callback"
f"&state={state}"
"&scope=https://www.googleapis.com/auth/photoslibrary.readonly"
"+https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
"&scope=https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
"+https://www.googleapis.com/auth/photoslibrary.appendonly"
"+https://www.googleapis.com/auth/userinfo.profile"
"&access_type=offline&prompt=consent"
@ -121,8 +120,7 @@ async def test_full_flow(
"refresh_token": FAKE_REFRESH_TOKEN,
"type": "Bearer",
"scope": (
"https://www.googleapis.com/auth/photoslibrary.readonly"
" https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
"https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
" https://www.googleapis.com/auth/photoslibrary.appendonly"
" https://www.googleapis.com/auth/userinfo.profile"
),
@ -163,8 +161,7 @@ async def test_api_not_enabled(
f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
"&redirect_uri=https://example.com/auth/external/callback"
f"&state={state}"
"&scope=https://www.googleapis.com/auth/photoslibrary.readonly"
"+https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
"&scope=https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
"+https://www.googleapis.com/auth/photoslibrary.appendonly"
"+https://www.googleapis.com/auth/userinfo.profile"
"&access_type=offline&prompt=consent"
@ -203,8 +200,7 @@ async def test_general_exception(
f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
"&redirect_uri=https://example.com/auth/external/callback"
f"&state={state}"
"&scope=https://www.googleapis.com/auth/photoslibrary.readonly"
"+https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
"&scope=https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
"+https://www.googleapis.com/auth/photoslibrary.appendonly"
"+https://www.googleapis.com/auth/userinfo.profile"
"&access_type=offline&prompt=consent"
@ -288,8 +284,7 @@ async def test_reauth(
f"{OAUTH2_AUTHORIZE}?response_type=code&client_id={CLIENT_ID}"
"&redirect_uri=https://example.com/auth/external/callback"
f"&state={state}"
"&scope=https://www.googleapis.com/auth/photoslibrary.readonly"
"+https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
"&scope=https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
"+https://www.googleapis.com/auth/photoslibrary.appendonly"
"+https://www.googleapis.com/auth/userinfo.profile"
"&access_type=offline&prompt=consent"
@ -321,8 +316,7 @@ async def test_reauth(
"refresh_token": FAKE_REFRESH_TOKEN,
"type": "Bearer",
"scope": (
"https://www.googleapis.com/auth/photoslibrary.readonly"
" https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
"https://www.googleapis.com/auth/photoslibrary.readonly.appcreateddata"
" https://www.googleapis.com/auth/photoslibrary.appendonly"
" https://www.googleapis.com/auth/userinfo.profile"
),

View File

@ -66,8 +66,7 @@ async def test_no_read_scopes(
@pytest.mark.parametrize(
("album_path", "expected_album_title"),
[
(f"{CONFIG_ENTRY_ID}/a/recent", "Recent Photos"),
(f"{CONFIG_ENTRY_ID}/a/favorites", "Favorite Photos"),
(f"{CONFIG_ENTRY_ID}/a/uploaded", "Uploaded Photos"),
(f"{CONFIG_ENTRY_ID}/a/album-media-id-1", "Album title"),
],
)
@ -109,8 +108,7 @@ async def test_browse_albums(
assert browse.identifier == CONFIG_ENTRY_ID
assert browse.title == "Account Name"
assert [(child.identifier, child.title) for child in browse.children] == [
(f"{CONFIG_ENTRY_ID}/a/recent", "Recent Photos"),
(f"{CONFIG_ENTRY_ID}/a/favorites", "Favorite Photos"),
(f"{CONFIG_ENTRY_ID}/a/uploaded", "Uploaded"),
(f"{CONFIG_ENTRY_ID}/a/album-media-id-1", "Album title"),
]

View File

@ -11,7 +11,7 @@ from google_photos_library_api.model import (
)
import pytest
from homeassistant.components.google_photos.const import DOMAIN, READ_SCOPES
from homeassistant.components.google_photos.const import DOMAIN, READ_SCOPE
from homeassistant.config_entries import ConfigEntryState
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
@ -225,7 +225,7 @@ async def test_upload_service_fails_create(
@pytest.mark.parametrize(
("scopes"),
[
READ_SCOPES,
[READ_SCOPE],
],
)
async def test_upload_service_no_scope(