Skip to content

Commit a84dedd

Browse files
agrenottshcheklein
andauthored
Partial compatibility with Google security update 2021 (#350)
* Partial compatibility with Google security update 2021 Google Drive added a new "resourceKey" attribute required to access documents shared by links. This resourceKey must be passed through HTTP header, aside with the document ID. resourceKey can be retrieved from a previous list operation on containing folder. Partial implementation; only for basic methods: GetContentFile, GetContentIOBuffer and FetchMetadata. * lint --------- Co-authored-by: Ivan Shcheklein <[email protected]>
1 parent 66cf7bc commit a84dedd

File tree

2 files changed

+202
-9
lines changed

2 files changed

+202
-9
lines changed

pydrive2/files.py

+24-9
Original file line numberDiff line numberDiff line change
@@ -453,16 +453,14 @@ def FetchMetadata(self, fields=None, fetch_all=False):
453453

454454
if file_id:
455455
try:
456-
metadata = (
457-
self.auth.service.files()
458-
.get(
459-
fileId=file_id,
460-
fields=fields,
461-
# Teamdrive support
462-
supportsAllDrives=True,
463-
)
464-
.execute(http=self.http)
456+
request = self.auth.service.files().get(
457+
fileId=file_id,
458+
fields=fields,
459+
# Teamdrive support
460+
supportsAllDrives=True,
465461
)
462+
request = self._AddResourceKeyHeaders(request)
463+
metadata = request.execute(http=self.http)
466464
except errors.HttpError as error:
467465
raise ApiRequestError(error)
468466
else:
@@ -687,6 +685,23 @@ def _WrapRequest(self, request):
687685
"""
688686
if self.http:
689687
request.http = self.http
688+
request = self._AddResourceKeyHeaders(request)
689+
return request
690+
691+
def _AddResourceKeyHeaders(self, request):
692+
"""Add resourceKey headers to request if file is secured with resourceKey and
693+
its available (from a list for example).
694+
695+
:param request: request to add headers to.
696+
:type request: googleapiclient.http.HttpRequest
697+
"""
698+
file_id = self.metadata.get("id") or self.get("id")
699+
if file_id:
700+
resourceKey = self.get("resourceKey")
701+
if resourceKey:
702+
request.headers[
703+
"X-Goog-Drive-Resource-Keys"
704+
] = f"{file_id}/{resourceKey}"
690705
return request
691706

692707
@LoadAuth

pydrive2/test/test_file.py

+178
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
import filecmp
2+
import json
23
import os
34
import unittest
5+
from unittest.mock import MagicMock
6+
import httplib2
47
import pytest
58
import sys
69
from io import BytesIO
@@ -25,6 +28,59 @@
2528
)
2629

2730

31+
def auth_with_resource_key_mock() -> GoogleAuth:
32+
"""
33+
Create GoogleAuth with mocked inner httplib2.Http simulating need
34+
for resourceKey header.
35+
"""
36+
http_mock = MagicMock()
37+
38+
def resource_key_request(
39+
uri,
40+
method="GET",
41+
body=None,
42+
headers=None,
43+
redirections=1,
44+
connection_type=None,
45+
):
46+
"""httplib2.Http.request mock."""
47+
# If "media" is not requested, it means we expect metadata
48+
fetch_meta_data = "alt=media" not in uri
49+
if (
50+
headers
51+
and "X-Goog-Drive-Resource-Keys" in headers
52+
and headers["X-Goog-Drive-Resource-Keys"]
53+
== "0BxphPoRgwhnodHNjS3JESnFNS1E/0-vjzOveuin3fnf4LUlfsD3A"
54+
):
55+
if fetch_meta_data:
56+
# Fake meta data query response
57+
content = json.dumps({"title": "N48E012.zip"}).encode()
58+
else:
59+
# Fake file content query response
60+
content = b"some content"
61+
return (
62+
httplib2.Response(
63+
{"status": "200", "content-length": str(len(content))}
64+
),
65+
content,
66+
)
67+
# Simulate 404 response for file not found; body must be valid error JSON
68+
return (
69+
httplib2.Response({"status": "404"}),
70+
json.dumps({"error": {"code": 404}}).encode(),
71+
)
72+
73+
http_mock.request.side_effect = resource_key_request
74+
ga = GoogleAuth(
75+
settings_file_path(
76+
"default.yaml", os.path.join(os.path.dirname(__file__), "")
77+
)
78+
)
79+
ga.thread_local.http = http_mock
80+
ga.ServiceAuth()
81+
return ga
82+
83+
2884
class GoogleDriveFileTest(unittest.TestCase):
2985
"""Tests basic file operations of files.GoogleDriveFile.
3086
Upload and download of contents and metadata, and thread-safety checks.
@@ -356,6 +412,128 @@ def test_Files_Get_Content_Buffer(self):
356412

357413
self.DeleteUploadedFiles(drive, [file1["id"]])
358414

415+
def test_Files_Get_Content_Buffer_resourceKey_missing(self):
416+
"""404 expected for file secured with resourceKey when not provided."""
417+
418+
ga = auth_with_resource_key_mock()
419+
420+
drive = GoogleDrive(ga)
421+
file1 = drive.CreateFile(
422+
{
423+
"id": "0BxphPoRgwhnodHNjS3JESnFNS1E",
424+
}
425+
)
426+
with self.assertRaisesRegex(
427+
ApiRequestError, "HttpError 404 when requesting"
428+
):
429+
pydrive_retry(file1.GetContentIOBuffer)
430+
431+
def test_Files_Get_Content_Buffer_resourceKey(self):
432+
"""End to end scenario with real file."""
433+
ga = auth_with_resource_key_mock()
434+
drive = GoogleDrive(ga)
435+
file1 = drive.CreateFile(
436+
{
437+
"id": "0BxphPoRgwhnodHNjS3JESnFNS1E",
438+
"resourceKey": "0-vjzOveuin3fnf4LUlfsD3A",
439+
}
440+
)
441+
442+
buffer1 = pydrive_retry(file1.GetContentIOBuffer)
443+
444+
self.assertEqual(len(buffer1), 12)
445+
446+
@pytest.mark.manual
447+
def test_Files_Get_Content_Buffer_resourceKey_missing_real(self):
448+
"""
449+
404 expected for file secured with resourceKey when not provided.
450+
End to end scenario with real public file.
451+
"""
452+
drive = GoogleDrive(self.ga)
453+
file1 = drive.CreateFile(
454+
{
455+
"id": "0BxphPoRgwhnodHNjS3JESnFNS1E",
456+
}
457+
)
458+
with self.assertRaisesRegex(
459+
ApiRequestError, "HttpError 404 when requesting"
460+
):
461+
pydrive_retry(file1.GetContentIOBuffer)
462+
463+
@pytest.mark.manual
464+
def test_Files_Get_Content_Buffer_resourceKey_real(self):
465+
"""End to end scenario with real public file."""
466+
drive = GoogleDrive(self.ga)
467+
file1 = drive.CreateFile(
468+
{
469+
"id": "0BxphPoRgwhnodHNjS3JESnFNS1E",
470+
"resourceKey": "0-vjzOveuin3fnf4LUlfsD3A",
471+
}
472+
)
473+
474+
buffer1 = pydrive_retry(file1.GetContentIOBuffer)
475+
476+
self.assertEqual(len(buffer1), 6128902)
477+
478+
def test_Files_Get_Content_File_resourceKey_missing(self):
479+
"""404 expected for file secured with resourceKey when not provided."""
480+
ga = auth_with_resource_key_mock()
481+
drive = GoogleDrive(ga)
482+
file1 = drive.CreateFile(
483+
{
484+
"id": "0BxphPoRgwhnodHNjS3JESnFNS1E",
485+
}
486+
)
487+
fileOut = self.getTempFile()
488+
with self.assertRaisesRegex(
489+
ApiRequestError, "HttpError 404 when requesting"
490+
):
491+
pydrive_retry(file1.GetContentFile, fileOut)
492+
493+
def test_Files_Get_Content_File_resourceKey(self):
494+
ga = auth_with_resource_key_mock()
495+
drive = GoogleDrive(ga)
496+
file1 = drive.CreateFile(
497+
{
498+
"id": "0BxphPoRgwhnodHNjS3JESnFNS1E",
499+
"resourceKey": "0-vjzOveuin3fnf4LUlfsD3A",
500+
}
501+
)
502+
503+
fileOut = self.getTempFile()
504+
pydrive_retry(file1.GetContentFile, fileOut)
505+
506+
with open(fileOut, "rb") as f:
507+
self.assertEqual(len(f.read()), 12)
508+
509+
def test_Files_Fetch_Metadata_resourceKey_missing(self):
510+
"""404 expected for file secured with resourceKey when not provided."""
511+
ga = auth_with_resource_key_mock()
512+
drive = GoogleDrive(ga)
513+
file1 = drive.CreateFile(
514+
{
515+
"id": "0BxphPoRgwhnodHNjS3JESnFNS1E",
516+
}
517+
)
518+
with self.assertRaisesRegex(
519+
ApiRequestError, "HttpError 404 when requesting"
520+
):
521+
pydrive_retry(file1.FetchMetadata)
522+
523+
def test_Files_Fetch_Metadata_resourceKey(self):
524+
ga = auth_with_resource_key_mock()
525+
drive = GoogleDrive(ga)
526+
file1 = drive.CreateFile(
527+
{
528+
"id": "0BxphPoRgwhnodHNjS3JESnFNS1E",
529+
"resourceKey": "0-vjzOveuin3fnf4LUlfsD3A",
530+
}
531+
)
532+
533+
pydrive_retry(file1.FetchMetadata)
534+
535+
self.assertEqual(file1.metadata["title"], "N48E012.zip")
536+
359537
def test_Upload_Download_Empty_File(self):
360538
filename = os.path.join(self.tmpdir, str(time()))
361539
create_file(filename, "")

0 commit comments

Comments
 (0)