mirror of
https://github.com/flectar/mail.git
synced 2026-11-11 17:57:54 +00:00
168 lines
8.3 KiB
Python
168 lines
8.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Validate Android OAuth configuration and the actual build-script environment."""
|
|
import hashlib
|
|
import importlib.util
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
import unittest
|
|
|
|
sys.dont_write_bytecode = True
|
|
SCRIPTS = Path(__file__).resolve().parent
|
|
spec = importlib.util.spec_from_file_location("oauth", SCRIPTS / "verify-android-oauth.py")
|
|
oauth = importlib.util.module_from_spec(spec)
|
|
spec.loader.exec_module(oauth)
|
|
GOOGLE = "FLECTAR_GOOGLE_ANDROID_CLIENT_ID"
|
|
MICROSOFT = "FLECTAR_MICROSOFT_ANDROID_CLIENT_ID"
|
|
REDIRECT = "FLECTAR_MICROSOFT_ANDROID_REDIRECT_URI"
|
|
VALID = {
|
|
GOOGLE: "123456789-fixture.apps.googleusercontent.com",
|
|
MICROSOFT: "abcdef01-2345-4678-9abc-def012345678",
|
|
}
|
|
|
|
|
|
class AndroidOAuthTests(unittest.TestCase):
|
|
def test_optional_providers_and_production_requirements(self):
|
|
for values in ({}, {GOOGLE: "", MICROSOFT: " "}, {GOOGLE: VALID[GOOGLE]}, {MICROSOFT: VALID[MICROSOFT]}):
|
|
oauth.verify(values)
|
|
with self.assertRaises(ValueError):
|
|
oauth.verify(values, require_all=True)
|
|
oauth.verify(VALID, require_all=True)
|
|
oauth.verify({GOOGLE: VALID[GOOGLE], MICROSOFT: " " + VALID[MICROSOFT].upper() + " "})
|
|
|
|
def test_rejects_placeholders_and_malformed_ids_without_echoing_values(self):
|
|
for key, value in (
|
|
(MICROSOFT, "00000000-0000-0000-0000-000000000000"),
|
|
(MICROSOFT, "------------------------------------"),
|
|
(MICROSOFT, "a" * 36),
|
|
(MICROSOFT, VALID[MICROSOFT] + "?unexpected=value"),
|
|
(MICROSOFT, "invalid-app-id"),
|
|
(GOOGLE, "test.apps.googleusercontent.com"),
|
|
(GOOGLE, "TEST.apps.googleusercontent.com"),
|
|
(GOOGLE, "wrong-suffix"),
|
|
):
|
|
with self.subTest(key=key, value=value):
|
|
with self.assertRaises(ValueError) as error:
|
|
oauth.verify({key: value})
|
|
self.assertIn(key, str(error.exception))
|
|
self.assertNotIn(value, str(error.exception))
|
|
|
|
def run_build(self, script, configuration, ci_variable=None, preview=False):
|
|
# Run the real shell entry points. Replace Cargo with a recorder that
|
|
# stops before compilation; no SDK downloads, signing or network calls.
|
|
with tempfile.TemporaryDirectory() as directory:
|
|
root = Path(directory)
|
|
(root / "scripts").mkdir()
|
|
for name in (script, "verify-android-oauth.py", "verify-android-keystore.sh", "android-version-code.py"):
|
|
shutil.copyfile(SCRIPTS / name, root / "scripts" / name)
|
|
# Exercise the real CI signing guard with a fake public certificate,
|
|
# without depending on release secrets or a JDK.
|
|
certificate = b"OAuth test public certificate"
|
|
android = root / "platform/android"
|
|
android.mkdir(parents=True)
|
|
(android / "app-signing-certificate.sha256").write_text(
|
|
hashlib.sha256(certificate).hexdigest() + "\n"
|
|
)
|
|
sdk = root / "sdk"
|
|
sdk.mkdir()
|
|
toolchain = root / "ndk/toolchains/llvm/prebuilt/fixture/bin"
|
|
toolchain.mkdir(parents=True)
|
|
binaries = root / "bin"
|
|
binaries.mkdir()
|
|
for path in (toolchain / "aarch64-linux-android26-clang", binaries / "gradle"):
|
|
path.write_text("#!/bin/sh\nexit 99\n")
|
|
path.chmod(0o755)
|
|
keytool = binaries / "keytool"
|
|
keytool.write_text(
|
|
"#!/usr/bin/env python3\nimport pathlib, sys\n"
|
|
"assert sys.argv[1] in ('-exportcert', '-certreq')\n"
|
|
"assert sys.argv[2] == '-keystore'\n"
|
|
"assert sys.argv[4:8] == ['-alias', 'fixture', '-storepass:env', 'FLECTAR_ANDROID_KEYSTORE_PASSWORD']\n"
|
|
"assert pathlib.Path(sys.argv[3]).is_file()\n"
|
|
f"sys.stdout.buffer.write({certificate!r})\n"
|
|
)
|
|
keytool.chmod(0o755)
|
|
capture = root / "cargo-environment.json"
|
|
cargo = binaries / "cargo"
|
|
cargo.write_text(
|
|
"#!/usr/bin/env python3\nimport os,json,pathlib\n"
|
|
"pathlib.Path(os.environ['OAUTH_CAPTURE']).write_text(json.dumps({"
|
|
"key:os.environ.get(key) for key in " + repr((GOOGLE, MICROSOFT, REDIRECT)) + "}))\n"
|
|
"raise SystemExit(73)\n"
|
|
)
|
|
cargo.chmod(0o755)
|
|
keystore = root / "test.keystore"
|
|
keystore.touch()
|
|
env = {key: value for key, value in os.environ.items() if not key.startswith("FLECTAR_")}
|
|
env.update({
|
|
# Select the build mode explicitly instead of inheriting the
|
|
# test runner's CI environment.
|
|
"CI": "",
|
|
"GITHUB_ACTIONS": "",
|
|
"PATH": str(binaries) + os.pathsep + env.get("PATH", ""),
|
|
"ANDROID_HOME": str(sdk),
|
|
"ANDROID_NDK_ROOT": str(root / "ndk"),
|
|
"GRADLE_CMD": str(binaries / "gradle"),
|
|
"XDG_DATA_HOME": str(root / "data"),
|
|
"FLECTAR_ANDROID_KEYSTORE": str(keystore),
|
|
"FLECTAR_ANDROID_KEYSTORE_PASSWORD": "fixture",
|
|
"FLECTAR_ANDROID_KEY_ALIAS": "fixture",
|
|
"FLECTAR_ANDROID_KEY_PASSWORD": "fixture",
|
|
"FLECTAR_ANDROID_VERSION_NAME": "0.1.0-alpha.8" if preview else "0.1.0",
|
|
"OAUTH_CAPTURE": str(capture),
|
|
**configuration,
|
|
})
|
|
if ci_variable:
|
|
env[ci_variable] = "true"
|
|
command = ["bash", str(root / "scripts" / script)]
|
|
if preview:
|
|
command.append("--preview")
|
|
result = subprocess.run(command,
|
|
env=env, capture_output=True, text=True, timeout=15)
|
|
recorded = json.loads(capture.read_text()) if capture.exists() else None
|
|
return result, recorded
|
|
|
|
def test_preview_never_injects_placeholder_defaults(self):
|
|
for ci_variable in (None, "CI", "GITHUB_ACTIONS"):
|
|
for values in ({}, {GOOGLE: "", MICROSOFT: "", REDIRECT: ""}, VALID):
|
|
with self.subTest(ci_variable=ci_variable, configuration=values):
|
|
result, recorded = self.run_build("build-android-release.sh", values, ci_variable, preview=True)
|
|
self.assertEqual(result.returncode, 73, result.stdout + result.stderr)
|
|
self.assertEqual(recorded, {key: values.get(key) for key in (GOOGLE, MICROSOFT, REDIRECT)})
|
|
|
|
def test_preview_rejects_invalid_ids_before_compilation(self):
|
|
for ci_variable in (None, "CI", "GITHUB_ACTIONS"):
|
|
for values in ({MICROSOFT: "00000000-0000-0000-0000-000000000000"}, {GOOGLE: "test.apps.googleusercontent.com"}):
|
|
with self.subTest(ci_variable=ci_variable, configuration=values):
|
|
result, recorded = self.run_build("build-android-release.sh", values, ci_variable, preview=True)
|
|
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
|
self.assertIsNone(recorded)
|
|
self.assertIn("not a valid", result.stderr)
|
|
|
|
def test_production_requires_real_ids_before_compilation(self):
|
|
values = {**VALID, REDIRECT: "msauth://com.flectar.mail/fixture-signature"}
|
|
result, recorded = self.run_build("build-android-release.sh", values)
|
|
self.assertEqual(result.returncode, 73, result.stdout + result.stderr)
|
|
self.assertEqual(recorded, values)
|
|
for invalid in ("", "00000000-0000-0000-0000-000000000000", "a" * 36):
|
|
result, recorded = self.run_build("build-android-release.sh", {**values, MICROSOFT: invalid})
|
|
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
|
self.assertIsNone(recorded)
|
|
|
|
def test_stable_cannot_bypass_provider_checks_or_override_version_order(self):
|
|
for values in (
|
|
{"FLECTAR_ANDROID_VERSION_NAME": "0.1.0"},
|
|
{"FLECTAR_ANDROID_VERSION_CODE": "1"},
|
|
):
|
|
result, recorded = self.run_build("build-android-release.sh", values, preview=True)
|
|
self.assertEqual(result.returncode, 1, result.stdout + result.stderr)
|
|
self.assertIsNone(recorded)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|