diff --git a/music/auth/auth.py b/music/auth/auth.py index ce34fe1..d63627e 100644 --- a/music/auth/auth.py +++ b/music/auth/auth.py @@ -4,6 +4,7 @@ from music.model.user import User from music.model.config import Config from music.auth.jwt_keys import generate_key from music.api.decorators import no_cache +from music.cloud import SPOT_CLIENT_URI, SPOT_SECRET_URI from urllib.parse import urlencode, urlunparse import datetime @@ -11,11 +12,14 @@ from datetime import timedelta from numbers import Number import logging from base64 import b64encode + +from google.cloud import secretmanager import requests blueprint = Blueprint('authapi', __name__) logger = logging.getLogger(__name__) +secret_client = secretmanager.SecretManagerServiceClient() @blueprint.route('/login', methods=['GET', 'POST']) @@ -194,10 +198,10 @@ def auth(): if 'username' in session: - config = Config.collection.get("config/music-tools") + spot_client = secret_client.access_secret_version(request={"name": SPOT_CLIENT_URI}) params = urlencode( { - 'client_id': config.spotify_client_id, + 'client_id': spot_client.payload.data.decode("UTF-8"), 'response_type': 'code', 'scope': 'playlist-modify-public playlist-modify-private playlist-read-private ' 'user-read-playback-state user-modify-playback-state user-library-read', @@ -221,10 +225,11 @@ def token(): flash('authorization failed') return redirect('app_route') else: - config = Config.collection.get("config/music-tools") + spot_client = secret_client.access_secret_version(request={"name": SPOT_CLIENT_URI}) + spot_secret = secret_client.access_secret_version(request={"name": SPOT_SECRET_URI}) idsecret = b64encode( - bytes(config.spotify_client_id + ':' + config.spotify_client_secret, "utf-8") + bytes(spot_client.payload.data.decode("UTF-8") + ':' + spot_secret.payload.data.decode("UTF-8"), "utf-8") ).decode("ascii") headers = {'Authorization': 'Basic %s' % idsecret} diff --git a/music/auth/jwt_keys.py b/music/auth/jwt_keys.py index 9c7782e..7d9c9d6 100644 --- a/music/auth/jwt_keys.py +++ b/music/auth/jwt_keys.py @@ -1,17 +1,15 @@ from datetime import timedelta, datetime, timezone import jwt +from music.cloud import JWT_SECRET_URI from music.model.user import User -from music.model.config import Config + +from google.cloud import secretmanager + +secret_client = secretmanager.SecretManagerServiceClient() def get_jwt_secret_key() -> str: - - config = Config.collection.get("config/music-tools") - - if config.jwt_secret_key is None or len(config.jwt_secret_key) == 0: - raise KeyError("no jwt secret key found") - - return config.jwt_secret_key + return secret_client.access_secret_version(request={"name": JWT_SECRET_URI}).payload.data.decode("UTF-8") def generate_key(user: User, timeout: datetime | timedelta = timedelta(minutes=60)) -> str: diff --git a/music/cloud/__init__.py b/music/cloud/__init__.py index a79e984..3980655 100644 --- a/music/cloud/__init__.py +++ b/music/cloud/__init__.py @@ -10,6 +10,12 @@ from .tasks import run_user_playlist_task logger = logging.getLogger(__name__) +SPOT_CLIENT_URI = "projects/sarsooxyz/secrets/spotify-client/versions/latest" +SPOT_SECRET_URI = "projects/sarsooxyz/secrets/spotify-secret/versions/latest" +LASTFM_CLIENT_URI = "projects/sarsooxyz/secrets/lastfm-client/versions/latest" +JWT_SECRET_URI = "projects/sarsooxyz/secrets/jwt-secret/versions/latest" +COOKIE_SECRET_URI = "projects/sarsooxyz/secrets/cookie-secret/versions/latest" + def queue_run_user_playlist(username: str, playlist_name: str): config = Config.collection.get("config/music-tools") diff --git a/music/db/database.py b/music/db/database.py index dc8dd2d..79c1c01 100644 --- a/music/db/database.py +++ b/music/db/database.py @@ -7,9 +7,13 @@ from spotframework.net.network import Network as SpotifyNetwork, SpotifyNetworkE from spotframework.net.user import NetworkUser from fmframework.net.network import Network as FmNetwork from music.model.user import User -from music.model.config import Config + +from music.cloud import SPOT_CLIENT_URI, SPOT_SECRET_URI, LASTFM_CLIENT_URI + +from google.cloud import secretmanager logger = logging.getLogger(__name__) +secret_client = secretmanager.SecretManagerServiceClient() def refresh_token_database_callback(user: User) -> None: @@ -50,10 +54,11 @@ def get_authed_spotify_network(user: User) -> Optional[SpotifyNetwork]: if user is not None: if user.spotify_linked: - config = Config.collection.get("config/music-tools") + spot_client = secret_client.access_secret_version(request={"name": SPOT_CLIENT_URI}) + spot_secret = secret_client.access_secret_version(request={"name": SPOT_SECRET_URI}) - user_obj = DatabaseUser(client_id=config.spotify_client_id, - client_secret=config.spotify_client_secret, + user_obj = DatabaseUser(client_id=spot_client.payload.data.decode("UTF-8"), + client_secret=spot_secret.payload.data.decode("UTF-8"), refresh_token=user.refresh_token, user_id=user.username, access_token=user.access_token) @@ -92,8 +97,9 @@ def get_authed_lastfm_network(user: User) -> Optional[FmNetwork]: if user is not None: if user.lastfm_username: - config = Config.collection.get("config/music-tools") - return FmNetwork(username=user.lastfm_username, api_key=config.last_fm_client_id) + lastfm_client = secret_client.access_secret_version(request={"name": LASTFM_CLIENT_URI}) + + return FmNetwork(username=user.lastfm_username, api_key=lastfm_client.payload.data.decode("UTF-8")) else: logger.error(f'{user.username} has no last.fm username') else: diff --git a/music/model/config.py b/music/model/config.py index f7d08d9..0a75d57 100644 --- a/music/model/config.py +++ b/music/model/config.py @@ -11,14 +11,8 @@ class Config(Model): """Set correct path in Firestore """ - spotify_client_id = TextField() - spotify_client_secret = TextField() - last_fm_client_id = TextField() - playlist_cloud_operating_mode = TextField() # task, function """Determines whether playlist and tag update operations are done by Cloud Tasks or Functions """ - secret_key = TextField() - jwt_secret_key = TextField() jwt_max_length = NumberField() jwt_default_length = NumberField() diff --git a/music/music.py b/music/music.py index 1aae82e..11333d2 100644 --- a/music/music.py +++ b/music/music.py @@ -1,4 +1,5 @@ from flask import Flask, render_template, redirect, session, flash, url_for +from google.cloud import secretmanager import logging import os @@ -6,9 +7,10 @@ import os from music.auth import auth_blueprint from music.api import api_blueprint, player_blueprint, fm_blueprint, \ spotfm_blueprint, spotify_blueprint, admin_blueprint, tag_blueprint -from music.model.config import Config +from music.cloud import COOKIE_SECRET_URI logger = logging.getLogger(__name__) +secret_client = secretmanager.SecretManagerServiceClient() def create_app(): @@ -20,11 +22,7 @@ def create_app(): app = Flask(__name__, static_folder=os.path.join(os.path.dirname(__file__), '..', 'build'), template_folder="templates") - config = Config.collection.get("config/music-tools") - if config is not None: - app.secret_key = config.secret_key - else: - logger.error('no config returned, skipping secret key') + app.secret_key = secret_client.access_secret_version(request={"name": COOKIE_SECRET_URI}).payload.data.decode("UTF-8") app.register_blueprint(auth_blueprint, url_prefix='/auth') app.register_blueprint(api_blueprint, url_prefix='/api') diff --git a/poetry.lock b/poetry.lock index 14046f5..bb920a1 100644 --- a/poetry.lock +++ b/poetry.lock @@ -291,6 +291,23 @@ protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4 [package.extras] libcst = ["libcst (>=0.3.10)"] +[[package]] +name = "google-cloud-secret-manager" +version = "2.12.6" +description = "Secret Manager API API client library" +category = "main" +optional = false +python-versions = ">=3.7" + +[package.dependencies] +google-api-core = {version = ">=1.32.0,<2.0.0 || >=2.8.0,<3.0.0dev", extras = ["grpc"]} +grpc-google-iam-v1 = ">=0.12.4,<1.0.0dev" +proto-plus = ">=1.22.0,<2.0.0dev" +protobuf = ">=3.19.5,<3.20.0 || >3.20.0,<3.20.1 || >3.20.1,<4.21.0 || >4.21.0,<4.21.1 || >4.21.1,<4.21.2 || >4.21.2,<4.21.3 || >4.21.3,<4.21.4 || >4.21.4,<4.21.5 || >4.21.5,<5.0.0dev" + +[package.extras] +libcst = ["libcst (>=0.2.5)"] + [[package]] name = "google-cloud-tasks" version = "2.10.4" @@ -835,7 +852,7 @@ python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" [metadata] lock-version = "1.1" python-versions = "~3.10" -content-hash = "fef135789689e4ab5a67d8e37ce0da3ca5b772a500a3ce9ac69ac5b2876b5175" +content-hash = "ab592da5f6b73bd51841928689b332bdd994e188398b85a1d5e48cc402c89dac" [metadata.files] alabaster = [ @@ -923,6 +940,10 @@ google-cloud-pubsub = [ {file = "google-cloud-pubsub-2.13.10.tar.gz", hash = "sha256:3e746a1a97c96bdc63fc5499529ed7262f1464f6de8c8bae6238c12200919ac0"}, {file = "google_cloud_pubsub-2.13.10-py2.py3-none-any.whl", hash = "sha256:1a29795265ddb2eff23b4e4c2c4594a56f5a861827fe1cc72c53de42a3a13def"}, ] +google-cloud-secret-manager = [ + {file = "google-cloud-secret-manager-2.12.6.tar.gz", hash = "sha256:7aaba2997f43f8c3b35903efc54c389a802f39853ba81e81e1190f093420ecf6"}, + {file = "google_cloud_secret_manager-2.12.6-py2.py3-none-any.whl", hash = "sha256:585e2ccd05038526b39eeabfe6932a7398341a76770ee0dd797c8581313e5212"}, +] google-cloud-tasks = [ {file = "google-cloud-tasks-2.10.4.tar.gz", hash = "sha256:650f4f43be725841103727ea3bc13227ae9ebaa2f99dadaad53888831cfc1d70"}, {file = "google_cloud_tasks-2.10.4-py2.py3-none-any.whl", hash = "sha256:d5bc498eff25b145bd0a2d67ee466814dc9baf259a5ee8c4187aef267f4d83d4"}, diff --git a/pyproject.toml b/pyproject.toml index ad7f4bb..6cb77cb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -19,6 +19,7 @@ Flask = "^2.1.3" google-cloud-firestore = "<=2.3.4" google-cloud-logging = "^3.2.1" google-cloud-pubsub = "^2.13.4" +google-cloud-secret-manager = "^2.12.6" google-cloud-tasks = "^2.10.0" requests = "^2.28.1" PyJWT = "^2.4.0"