Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,12 @@ def main(client_secrets, scope, save, credentials):

config_path = os.path.dirname(credentials)
if config_path and not os.path.isdir(config_path):
os.makedirs(config_path)
os.makedirs(config_path, mode=0o700)

with open(credentials, "w") as outfile:
# The file holds the refresh token and client secret, so restrict it
# to the current user instead of relying on the process umask.
fd = os.open(credentials, os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
with os.fdopen(fd, "w") as outfile:
json.dump(creds_data, outfile)

click.echo("credentials saved: %s" % credentials)
Expand Down
31 changes: 31 additions & 0 deletions packages/google-auth-oauthlib/tests/unit/test_tool.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import io
import json
import os.path
import stat
import tempfile
from unittest import mock

Expand Down Expand Up @@ -131,3 +132,33 @@ def test_save_existing_dir(self, runner, local_server_mock):
assert not result.exception
assert "saved" in result.output
assert result.exit_code == 0

@pytest.mark.skipif(
os.name == "nt", reason="POSIX file permissions are not available on Windows"
)
def test_save_file_permissions(self, runner, local_server_mock):
credentials_tmpdir = tempfile.mkdtemp()
credentials_path = os.path.join(
credentials_tmpdir, "new-directory", "credentials.json"
)
result = runner.invoke(
cli.main,
[
"--client-secrets",
CLIENT_SECRETS_FILE,
"--scope",
"somescope",
"--credentials",
credentials_path,
"--save",
],
)
local_server_mock.assert_called_with(mock.ANY)
assert not result.exception
assert result.exit_code == 0
# The saved file holds the refresh token and client secret, so neither
# it nor the directory created for it may be group or world accessible.
file_mode = stat.S_IMODE(os.stat(credentials_path).st_mode)
dir_mode = stat.S_IMODE(os.stat(os.path.dirname(credentials_path)).st_mode)
assert file_mode & 0o077 == 0
assert dir_mode & 0o077 == 0
Comment on lines +140 to +164

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The temporary directory created with tempfile.mkdtemp() is never cleaned up, which leads to directory leaks in the test environment. Using tempfile.TemporaryDirectory as a context manager ensures that the directory and its contents are automatically cleaned up after the test completes, even if assertions fail.

        with tempfile.TemporaryDirectory() as credentials_tmpdir:
            credentials_path = os.path.join(
                credentials_tmpdir, "new-directory", "credentials.json"
            )
            result = runner.invoke(
                cli.main,
                [
                    "--client-secrets",
                    CLIENT_SECRETS_FILE,
                    "--scope",
                    "somescope",
                    "--credentials",
                    credentials_path,
                    "--save",
                ],
            )
            local_server_mock.assert_called_with(mock.ANY)
            assert not result.exception
            assert result.exit_code == 0
            # The saved file holds the refresh token and client secret, so neither
            # it nor the directory created for it may be group or world accessible.
            file_mode = stat.S_IMODE(os.stat(credentials_path).st_mode)
            dir_mode = stat.S_IMODE(os.stat(os.path.dirname(credentials_path)).st_mode)
            assert file_mode & 0o077 == 0
            assert dir_mode & 0o077 == 0

Loading