-
Notifications
You must be signed in to change notification settings - Fork 8
Add deploy keys #44
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Add deploy keys #44
Changes from all commits
fde7255
6622f1e
527bb3b
5eae3d6
accdf3a
f8cbf6b
3e19b74
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,18 +1,19 @@ | ||||||||||||
| import json | ||||||||||||
| import traceback | ||||||||||||
| import datetime | ||||||||||||
| import uuid | ||||||||||||
|
|
||||||||||||
| from algoliasearch import algoliasearch | ||||||||||||
| from flask import Blueprint, jsonify, abort, request | ||||||||||||
| from flask_cors import CORS | ||||||||||||
|
|
||||||||||||
| from sqlalchemy.exc import IntegrityError | ||||||||||||
| from sqlalchemy.orm.exc import NoResultFound | ||||||||||||
| from sqlalchemy.orm.exc import NoResultFound, MultipleResultsFound | ||||||||||||
| from werkzeug.exceptions import BadRequest | ||||||||||||
| from sqlalchemy.exc import DataError | ||||||||||||
| from zipfile import BadZipFile | ||||||||||||
|
|
||||||||||||
| from .utils import authed_request, demand_authed_request, get_uid, id_generator, validate_new_app_fields, is_valid_category, is_valid_appinfo, is_valid_platform, clone_asset_collection_without_images, is_valid_image_file, is_valid_image_size, get_max_image_dimensions, generate_image_url, is_users_developer_id, user_is_wizard, newAppValidationException, algolia_app, first_version_is_newer | ||||||||||||
| from .utils import authed_request, demand_authed_request, get_uid, id_generator, validate_new_app_fields, is_valid_category, is_valid_appinfo, is_valid_platform, clone_asset_collection_without_images, is_valid_image_file, is_valid_image_size, get_max_image_dimensions, generate_image_url, is_users_developer_id, user_is_wizard, newAppValidationException, algolia_app, first_version_is_newer, is_valid_deploy_key_for_app | ||||||||||||
| from .models import Category, db, App, Developer, Release, CompanionApp, Binary, AssetCollection, LockerEntry, UserLike | ||||||||||||
| from .pbw import PBW, release_from_pbw | ||||||||||||
| from .s3 import upload_pbw, upload_asset | ||||||||||||
|
|
@@ -673,7 +674,7 @@ def wizard_update_app(app_id): | |||||||||||
| else: | ||||||||||||
| return jsonify(error="Invalid POST body. Provide one or more fields to update", e="body.invalid"), 400 | ||||||||||||
|
|
||||||||||||
|
|
||||||||||||
| @devportal_api.route('/wizard/app/<app_id>', methods=['DELETE']) | ||||||||||||
| def wizard_delete_app(app_id): | ||||||||||||
| if not user_is_wizard(): | ||||||||||||
|
|
@@ -726,6 +727,106 @@ def wizard_get_s3_assets(app_id): | |||||||||||
|
|
||||||||||||
| return jsonify(images = images, pbws = pbws) | ||||||||||||
|
|
||||||||||||
| @devportal_api.route("/deploykey", methods=['POST']) | ||||||||||||
| def deploy_key(): | ||||||||||||
| try: | ||||||||||||
| req = request.json | ||||||||||||
| except BadRequest as e: | ||||||||||||
| return jsonify(error="Invalid POST body. Expected JSON", e="body.invalid"), 400 | ||||||||||||
| if req is None: | ||||||||||||
| return jsonify(error="Invalid POST body. Expected JSON", e="body.invalid"), 400 | ||||||||||||
|
|
||||||||||||
| if not "operation" in req: | ||||||||||||
| return jsonify(error="Missing required field: operation", e="missing.field.operation"), 400 | ||||||||||||
|
|
||||||||||||
| if req["operation"] == "regenerate": | ||||||||||||
|
|
||||||||||||
| result = demand_authed_request('GET', f"{config['REBBLE_AUTH_URL']}/api/v1/me/pebble/appstore") | ||||||||||||
| me = result.json() | ||||||||||||
|
|
||||||||||||
| try: | ||||||||||||
| developer = Developer.query.filter_by(id=me['id']).one() | ||||||||||||
| except NoResultFound: | ||||||||||||
| return jsonify(error="No developer account associated with user", e="setup.required"), 400 | ||||||||||||
|
|
||||||||||||
| new_deploy_key = str(uuid.uuid4()) | ||||||||||||
| developer.deploy_key = new_deploy_key | ||||||||||||
| developer.deploy_key_last_used = None | ||||||||||||
| db.session.commit() | ||||||||||||
|
|
||||||||||||
| return jsonify(new_key=new_deploy_key) | ||||||||||||
|
|
||||||||||||
| else: | ||||||||||||
| return jsonify(error="Unknown operation requested", e="operation.invalid"), 400 | ||||||||||||
|
|
||||||||||||
| @devportal_api.route('/deploy', methods=['POST']) | ||||||||||||
| def submit_new_release_via_deploy(): | ||||||||||||
| # Todo: Merge this with the publish release endpoint | ||||||||||||
|
|
||||||||||||
| if not request.headers.get("x-deploy-key"): | ||||||||||||
| return jsonify(error="No X-Deploy-Key header found", e="permission.denied"), 401 | ||||||||||||
|
|
||||||||||||
| data = dict(request.form) | ||||||||||||
|
|
||||||||||||
| if "pbw" not in request.files: | ||||||||||||
| return jsonify(error="Missing file: pbw", e="pbw.missing"), 400 | ||||||||||||
|
|
||||||||||||
| if "release_notes" not in data: | ||||||||||||
| return jsonify(error="Missing field: release_notes", e="release_notes.missing"), 400 | ||||||||||||
|
|
||||||||||||
| pbw_file = request.files['pbw'].read() | ||||||||||||
|
|
||||||||||||
| try: | ||||||||||||
| pbw = PBW(pbw_file, 'aplite') | ||||||||||||
| with pbw.zip.open('appinfo.json') as f: | ||||||||||||
| appinfo = json.load(f) | ||||||||||||
| except BadZipFile as e: | ||||||||||||
| return jsonify(error=f"Your pbw file is invalid or corrupted", e="invalid.pbw"), 400 | ||||||||||||
| except KeyError as e: | ||||||||||||
| return jsonify(error=f"Your pbw file is invalid or corrupted", e="invalid.pbw"), 400 | ||||||||||||
|
|
||||||||||||
| appinfo_valid, appinfo_valid_reason = is_valid_appinfo(appinfo) | ||||||||||||
| if not appinfo_valid: | ||||||||||||
| return jsonify(error=f"The appinfo.json in your pbw file has the following error: {appinfo_valid_reason}", e="invalid.appinfocontent"), 400 | ||||||||||||
|
|
||||||||||||
| uuid = appinfo['uuid'] | ||||||||||||
| version = appinfo['versionLabel'] | ||||||||||||
|
|
||||||||||||
| try: | ||||||||||||
| app = App.query.filter(App.app_uuid == uuid).one() | ||||||||||||
| except NoResultFound: | ||||||||||||
| return jsonify(error="Unknown app. To submit a new app to the appstore for the first time, please use dev-portal.rebble.io", e="app.notfound"), 400 | ||||||||||||
| except MultipleResultsFound: | ||||||||||||
| return jsonify(error="You cannot use deploy keys with this app. You must submit a release manually through dev-portal.rebble.io", e="app.noteligible"), 400 | ||||||||||||
|
|
||||||||||||
| # Check we own the app | ||||||||||||
| if not is_valid_deploy_key_for_app(request.headers.get("x-deploy-key"), app): | ||||||||||||
| return jsonify(error="You do not have permission to modify that app", e="permission.denied"), 403 | ||||||||||||
|
|
||||||||||||
| # Update last used time | ||||||||||||
| dev = Developer.query.filter_by(id=app.developer_id).one() | ||||||||||||
| dev.deploy_key_last_used = datetime.datetime.now(datetime.timezone.utc) | ||||||||||||
|
Comment on lines
+806
to
+808
|
||||||||||||
| # Update last used time | |
| dev = Developer.query.filter_by(id=app.developer_id).one() | |
| dev.deploy_key_last_used = datetime.datetime.now(datetime.timezone.utc) | |
| # Prepare developer object for later update | |
| dev = Developer.query.filter_by(id=app.developer_id).one() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will take a look
Willow-Systems marked this conversation as resolved.
Show resolved
Hide resolved
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| """Add deploy key col | ||
|
|
||
| Revision ID: e56d904098e5 | ||
| Revises: c4e0470dc040 | ||
| Create Date: 2025-07-31 23:32:55.499315 | ||
|
|
||
| """ | ||
| from alembic import op | ||
| import sqlalchemy as sa | ||
|
|
||
|
|
||
| # revision identifiers, used by Alembic. | ||
| revision = 'e56d904098e5' | ||
| down_revision = 'c4e0470dc040' | ||
| branch_labels = None | ||
| depends_on = None | ||
|
|
||
|
|
||
| def upgrade(): | ||
| op.add_column('developers', sa.Column('deploy_key', sa.String(), nullable=True)) | ||
| op.add_column('developers', sa.Column('deploy_key_last_used', sa.DateTime(), nullable=True)) | ||
|
|
||
| def downgrade(): | ||
| op.drop_column('developers', 'deploy_key') | ||
| op.drop_column('developers', 'deploy_key_last_used') |
Uh oh!
There was an error while loading. Please reload this page.