Esempio n. 1
0
def get_manage_members(store_id):
    try:
        members = admin_api.get_store_members(flask.session, store_id)

        for item in members:
            if item["email"] == flask.session["publisher"]["email"]:
                item["current_user"] = True

    except StoreApiResponseErrorList as api_response_error_list:
        return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    return jsonify(members)
Esempio n. 2
0
def get_models(store_id):
    try:
        stores = admin_api.get_stores(flask.session)
        store = admin_api.get_store(flask.session, store_id)
    except StoreApiResponseErrorList as api_response_error_list:
        return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    return flask.render_template(
        "admin/models.html",
        stores=stores,
        store=store,
        models=[],
    )
def get_release_history_json(snap_name):
    page = flask.request.args.get("page", default=1, type=int)

    try:
        release_history = api.snap_release_history(flask.session, snap_name,
                                                   page)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return flask.jsonify(api_response_error_list.errors), 400
    except ApiError as api_error:
        return _handle_error(api_error)

    return flask.jsonify(release_history)
Esempio n. 4
0
def post_settings(store_id):
    settings = {}
    settings["private"] = json.loads(flask.request.form.get("private"))
    settings["manual-review-policy"] = flask.request.form.get(
        "manual-review-policy")

    res = {}

    try:
        admin_api.change_store_settings(flask.session, store_id, settings)
        res["msg"] = "Changes saved"
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    return jsonify({"success": True})
def get_snap_revision_json(snap_name, revision):
    """
    Return JSON object from the publisher API
    """
    try:
        revision = publisher_api.get_snap_revision(flask.session, snap_name,
                                                   revision)
    except StoreApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return flask.jsonify(api_response_error_list.errors), 400
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    return flask.jsonify(revision)
Esempio n. 6
0
def get_settings(snap_name):
    try:
        snap_details = api.get_snap_info(snap_name, flask.session)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_error(api_error)

    if "whitelist_country_codes" in snap_details:
        whitelist_country_codes = (
            snap_details["whitelist_country_codes"]
            if len(snap_details["whitelist_country_codes"]) > 0 else [])
    else:
        whitelist_country_codes = []

    if "blacklist_country_codes" in snap_details:
        blacklist_country_codes = (
            snap_details["blacklist_country_codes"]
            if len(snap_details["blacklist_country_codes"]) > 0 else [])
    else:
        blacklist_country_codes = []

    countries = []
    for country in pycountry.countries:
        countries.append({"key": country.alpha_2, "name": country.name})

    context = {
        "snap_name": snap_details["snap_name"],
        "snap_title": snap_details["title"],
        "snap_id": snap_details["snap_id"],
        "publisher_name": snap_details["publisher"]["display-name"],
        "license": license,
        "private": snap_details["private"],
        "unlisted": snap_details["unlisted"],
        "countries": countries,
        "whitelist_country_codes": whitelist_country_codes,
        "blacklist_country_codes": blacklist_country_codes,
        "price": snap_details["price"],
        "store": snap_details["store"],
        "keywords": snap_details["keywords"],
        "status": snap_details["status"],
    }

    return flask.render_template("publisher/settings.html", **context)
Esempio n. 7
0
def get_stores():
    """
    In this view we get all the stores the user is an admin or we show a 403
    """
    try:
        stores = admin_api.get_stores(flask.session)
    except StoreApiResponseErrorList as api_response_error_list:
        return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    if not stores:
        flask.abort(403)

    # We redirect to the first store snap list
    return flask.redirect(
        flask.url_for(".get_settings", store_id=stores[0]["id"]))
def post_release(snap_name):
    data = flask.request.json

    if not data:
        return flask.jsonify({}), 400

    try:
        response = api.post_snap_release(flask.session, snap_name, data)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return flask.jsonify(api_response_error_list.errors), 400
    except ApiError as api_error:
        return _handle_error(api_error)

    return flask.jsonify(response)
Esempio n. 9
0
def get_manage_members(store_id):
    try:
        stores = admin_api.get_stores(flask.session)
        store = admin_api.get_store(flask.session, store_id)
        members = admin_api.get_store_members(flask.session, store_id)
        invites = admin_api.get_store_invites(flask.session, store_id)
    except StoreApiResponseErrorList as api_response_error_list:
        return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    return flask.render_template(
        "admin/manage_members.html",
        stores=stores,
        store=store,
        members=members,
        invites=invites,
    )
Esempio n. 10
0
def post_update_invites(store_id):
    invites = json.loads(flask.request.form.get("invites"))

    try:
        admin_api.update_store_invites(flask.session, store_id, invites)
        flask.flash("Changes saved", "positive")
    except StoreApiResponseErrorList as api_response_error_list:
        msgs = [
            f"{error.get('message', 'An error occurred')}"
            for error in api_response_error_list.errors
        ]

        for msg in msgs:
            flask.flash(msg, "negative")
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    return flask.redirect(flask.url_for(".get_invites", store_id=store_id))
Esempio n. 11
0
def get_invites(store_id):
    try:
        stores = admin_api.get_stores(flask.session)
        store = admin_api.get_store(flask.session, store_id)
        invites = admin_api.get_store_invites(flask.session, store_id)
    except StoreApiResponseErrorList as api_response_error_list:
        return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    pending_invites = []
    expired_invites = []
    revoked_invites = []

    for invite in invites:
        if invite["status"] == "Pending":
            pending_invites.append(invite)

        if invite["status"] == "Expired":
            expired_invites.append(invite)

        if invite["status"] == "Revoked":
            revoked_invites.append(invite)

    sorted_pending_invites = sorted(
        pending_invites, key=lambda item: item["expiration-date"]
    )

    sorted_expired_invites = sorted(
        expired_invites, key=lambda item: item["expiration-date"]
    )

    sorted_revoked_invites = sorted(
        revoked_invites, key=lambda item: item["expiration-date"]
    )

    return flask.render_template(
        "admin/invites.html",
        stores=stores,
        store=store,
        pending_invites=sorted_pending_invites,
        expired_invites=sorted_expired_invites,
        revoked_invites=sorted_revoked_invites,
    )
Esempio n. 12
0
def get_account_snaps():
    try:
        account_info = publisher_api.get_account(flask.session)
    except StoreApiResponseErrorList as api_response_error_list:
        return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    user_snaps, registered_snaps = logic.get_snaps_account_info(account_info)

    flask_user = flask.session["publisher"]

    context = {
        "snaps": user_snaps,
        "current_user": flask_user["nickname"],
        "registered_snaps": registered_snaps,
    }

    return flask.render_template("publisher/account-snaps.html", **context)
Esempio n. 13
0
def get_snap_build(snap_name, build_id):
    try:
        details = publisher_api.get_snap_info(snap_name, flask.session)
    except StoreApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    context = {
        "snap_id": details["snap_id"],
        "snap_name": details["snap_name"],
        "snap_title": details["title"],
        "snap_build": {},
    }

    # Get build by snap name and build_id
    lp_build = launchpad.get_snap_build(details["snap_name"], build_id)

    if lp_build:
        status = map_build_and_upload_states(
            lp_build["buildstate"], lp_build["store_upload_status"]
        )
        context["snap_build"] = {
            "id": lp_build["self_link"].split("/")[-1],
            "arch_tag": lp_build["arch_tag"],
            "datebuilt": lp_build["datebuilt"],
            "duration": lp_build["duration"],
            "logs": lp_build["build_log_url"],
            "revision_id": lp_build["revision_id"],
            "status": status,
            "title": lp_build["title"],
        }

        if context["snap_build"]["logs"]:
            context["raw_logs"] = launchpad.get_snap_build_log(
                details["snap_name"], build_id
            )

    return flask.render_template("publisher/build.html", **context)
Esempio n. 14
0
def get_validate_repo(snap_name):
    try:
        details = api.get_snap_info(snap_name, flask.session)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_error(api_error)

    owner, repo = flask.request.args.get("repo").split("/")

    return flask.jsonify(
        validate_repo(
            flask.session.get("github_auth_secret"),
            details["snap_name"],
            owner,
            repo,
        ))
Esempio n. 15
0
def post_build(snap_name):
    try:
        details = api.get_snap_info(snap_name, flask.session)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_error(api_error)

    if launchpad.is_snap_building(details["snap_name"]):
        launchpad.cancel_snap_builds(details["snap_name"])

    launchpad.build_snap(details["snap_name"])

    flask.flash("Build triggered", "positive")

    return flask.redirect(
        flask.url_for(".get_snap_builds", snap_name=snap_name))
Esempio n. 16
0
def post_register_name_dispute():
    try:
        snap_name = flask.request.form.get("snap-name", "")
        claim_comment = flask.request.form.get("claim-comment", "")
        api.post_register_name_dispute(flask.session, bleach.clean(snap_name),
                                       bleach.clean(claim_comment))
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code in [400, 409]:
            return flask.render_template(
                "publisher/register-name-dispute.html",
                snap_name=snap_name,
                errors=api_response_error_list.errors,
            )
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_error(api_error)

    return flask.render_template(
        "publisher/register-name-dispute-success.html", snap_name=snap_name)
Esempio n. 17
0
def get_register_name():
    try:
        user = publisher_api.get_account(flask.session)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    available_stores = logic.filter_available_stores(user["stores"])

    snap_name = flask.request.args.get("snap_name", default="", type=str)
    store = flask.request.args.get("store", default="", type=str)

    conflict_str = flask.request.args.get("conflict",
                                          default="False",
                                          type=str)
    conflict = conflict_str == "True"

    already_owned_str = flask.request.args.get("already_owned",
                                               default="False",
                                               type=str)
    already_owned = already_owned_str == "True"

    reserved_str = flask.request.args.get("reserved",
                                          default="False",
                                          type=str)
    reserved = reserved_str == "True"

    is_private_str = flask.request.args.get("is_private",
                                            default="False",
                                            type=str)
    is_private = is_private_str == "True"

    context = {
        "snap_name": snap_name,
        "is_private": is_private,
        "conflict": conflict,
        "already_owned": already_owned,
        "reserved": reserved,
        "store": store,
        "available_stores": available_stores,
    }
    return flask.render_template("publisher/register-snap.html", **context)
Esempio n. 18
0
def post_settings(store_id):
    settings = {}
    settings["private"] = not flask.request.form.get("is_public")
    settings["manual-review-policy"] = flask.request.form.get(
        "manual-review-policy")

    try:
        admin_api.change_store_settings(flask.session, store_id, settings)
        flask.flash("Changes saved", "positive")
    except StoreApiResponseErrorList as api_response_error_list:
        msgs = [
            f"{error.get('message', 'An error occurred')}"
            for error in api_response_error_list.errors
        ]

        for msg in msgs:
            flask.flash(msg, "negative")
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    return flask.redirect(flask.url_for(".get_settings", store_id=store_id))
Esempio n. 19
0
def login_callback():
    code = flask.request.args["code"]
    state = flask.request.args["state"]

    flask.session.pop("macaroon_root", None)
    flask.session.pop("macaroon_discharge", None)

    # Avoid CSRF attacks
    validate_csrf(state)

    discharged_token = candid.discharge_token(code)
    candid_macaroon = candid.discharge_macaroon(
        flask.session["publisher-macaroon"], discharged_token)

    # Store bakery authentication
    flask.session["macaroons"] = candid.get_serialized_bakery_macaroon(
        flask.session["publisher-macaroon"], candid_macaroon)

    try:
        publisher = publisher_api.whoami(flask.session)
    except StoreApiResponseErrorList as api_response_error_list:
        return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    flask.session["publisher"] = {
        "account_id": publisher["account"]["id"],
        "nickname": publisher["account"]["username"],
        "fullname": publisher["account"]["name"],
        "image": None,
        "email": publisher["account"]["email"],
    }

    return flask.redirect(
        flask.session.pop(
            "next_url",
            flask.url_for("publisher_snaps.get_account_snaps"),
        ),
        302,
    )
Esempio n. 20
0
def post_disconnect_repo(snap_name):
    try:
        details = publisher_api.get_snap_info(snap_name, flask.session)
    except StoreApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except StoreApiError as api_error:
        return _handle_error(api_error)

    lp_snap = launchpad.get_snap_by_store_name(snap_name)
    launchpad.delete_snap(details["snap_name"])

    # Try to remove the GitHub webhook if possible
    if flask.session.get("github_auth_secret"):
        github = GitHub(flask.session.get("github_auth_secret"))

        try:
            gh_owner, gh_repo = lp_snap["git_repository_url"][19:].split("/")

            old_hook = github.get_hook_by_url(
                gh_owner,
                gh_repo,
                f"{GITHUB_WEBHOOK_HOST_URL}{snap_name}/webhook/notify",
            )

            if old_hook:
                github.remove_hook(
                    gh_owner,
                    gh_repo,
                    old_hook["id"],
                )
        except HTTPError:
            pass

    return flask.redirect(
        flask.url_for(".get_snap_builds", snap_name=snap_name)
    )
Esempio n. 21
0
def update_invite_status(store_id):
    invites = json.loads(flask.request.form.get("invites"))

    res = {}

    try:
        admin_api.update_store_invites(flask.session, store_id, invites)
        res["msg"] = "Changes saved"
    except StoreApiResponseErrorList as api_response_error_list:
        msgs = [
            f"{error.get('message', 'An error occurred')}"
            for error in api_response_error_list.errors
        ]

        msgs = list(dict.fromkeys(msgs))

        for msg in msgs:
            flask.flash(msg, "negative")
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    return jsonify(res)
Esempio n. 22
0
def post_manage_members(store_id):
    members = json.loads(flask.request.form.get("members"))

    res = {}

    try:
        admin_api.update_store_members(flask.session, store_id, members)
        res["msg"] = "Changes saved"
    except StoreApiResponseErrorList as api_response_error_list:

        codes = [error.get("code") for error in api_response_error_list.errors]

        msgs = [
            f"{error.get('message', 'An error occurred')}"
            for error in api_response_error_list.errors
        ]

        for code in codes:
            account_id = ""

            if code == "store-users-no-match":
                if account_id:
                    res["msg"] = code
                else:
                    res["msg"] = "invite"

            elif code == "store-users-multiple-matches":
                res["msg"] = code
            else:
                for msg in msgs:
                    flask.flash(msg, "negative")

    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    return jsonify(res)
Esempio n. 23
0
def get_store_snaps(store_id):
    try:
        stores = admin_api.get_stores(flask.session)
        store = admin_api.get_store(flask.session, store_id)
        snaps = admin_api.get_store_snaps(flask.session, store_id)

        # list of all deduped store IDs that are not current store
        other_store_ids = list(dict.fromkeys([d["store"] for d in snaps]))
        other_stores = list(
            filter(lambda id: id != store["id"], other_store_ids))

        # store data for each store ID
        other_stores_data = []
        for other_store_id in other_stores:
            if other_store_id == "ubuntu":
                other_stores_data.append({
                    "id": "ubuntu",
                    "name": "Global store"
                })
            else:
                store_data = admin_api.get_store(flask.session, other_store_id)
                other_stores_data.append(store_data)

    except StoreApiResponseErrorList as api_response_error_list:
        return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    return flask.render_template(
        "admin/snaps.html",
        stores=stores,
        store=store,
        store_json=json.dumps(store),
        snaps=json.dumps(snaps),
        other_stores_data=json.dumps(other_stores_data),
    )
Esempio n. 24
0
def get_snap_builds_json(snap_name):
    try:
        details = api.get_snap_info(snap_name, flask.session)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_error(api_error)

    context = {"snap_builds": []}

    start = flask.request.args.get("start", 0, type=int)
    size = flask.request.args.get("size", 15, type=int)
    build_slice = slice(start, start + size)

    # Get built snap in launchpad with this store name
    lp_snap = launchpad.get_snap_by_store_name(details["snap_name"])

    if lp_snap:
        context.update(get_builds(lp_snap, build_slice))

    return flask.jsonify(context)
Esempio n. 25
0
def publisher_snap_metrics(snap_name):
    """
    A view to display the snap metrics page for specific snaps.

    This queries the snapcraft API (api.snapcraft.io) and passes
    some of the data through to the publisher/metrics.html template,
    with appropriate sanitation.
    """
    try:
        details = publisher_api.get_snap_info(snap_name, flask.session)
    except StoreApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    metric_requested = logic.extract_metrics_period(
        flask.request.args.get("period", default="30d", type=str))

    installed_base_metric = logic.verify_base_metrics(
        flask.request.args.get("active-devices", default="version", type=str))

    installed_base = logic.get_installed_based_metric(installed_base_metric)
    metrics_query_json = metrics_helper.build_metrics_json(
        snap_id=details["snap_id"],
        installed_base=installed_base,
        metric_period=metric_requested["int"],
        metric_bucket=metric_requested["bucket"],
    )

    try:
        metrics_response = publisher_api.get_publisher_metrics(
            flask.session, json=metrics_query_json)
    except StoreApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    try:
        latest_day_period = logic.extract_metrics_period("1d")
        latest_installed_base = logic.get_installed_based_metric("version")
        latest_day_query_json = metrics_helper.build_metrics_json(
            snap_id=details["snap_id"],
            installed_base=latest_installed_base,
            metric_period=latest_day_period["int"],
            metric_bucket=latest_day_period["bucket"],
        )
        latest_day_response = publisher_api.get_publisher_metrics(
            flask.session, json=latest_day_query_json)
    except StoreApiResponseErrorList as api_response_error_list:
        return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    active_metrics = metrics_helper.find_metric(metrics_response["metrics"],
                                                installed_base)

    series = active_metrics["series"]

    if installed_base_metric == "os":
        capitalized_series = active_metrics["series"]
        for item in capitalized_series:
            item["name"] = metrics._capitalize_os_name(item["name"])
        series = capitalized_series

    active_devices = metrics.ActiveDevices(
        name=active_metrics["metric_name"],
        series=series,
        buckets=active_metrics["buckets"],
        status=active_metrics["status"],
    )

    latest_active = 0

    if active_devices:
        latest_active = active_devices.get_number_latest_active_devices()

    if latest_day_response:
        latest_active_metrics = metrics_helper.find_metric(
            latest_day_response["metrics"], latest_installed_base)
        if latest_active_metrics:
            latest_active_devices = metrics.ActiveDevices(
                name=latest_active_metrics["metric_name"],
                series=latest_active_metrics["series"],
                buckets=latest_active_metrics["buckets"],
                status=latest_active_metrics["status"],
            )
            latest_active = (
                latest_active_devices.get_number_latest_active_devices())

    country_metric = metrics_helper.find_metric(
        metrics_response["metrics"], "weekly_installed_base_by_country")
    country_devices = metrics.CountryDevices(
        name=country_metric["metric_name"],
        series=country_metric["series"],
        buckets=country_metric["buckets"],
        status=country_metric["status"],
        private=True,
    )

    territories_total = 0
    if country_devices:
        territories_total = country_devices.get_number_territories()

    nodata = not any([country_devices, active_devices])

    annotations = {"name": "annotations", "series": [], "buckets": []}

    default_track = (details.get("default-track")
                     if details.get("default-track") else "latest")

    for category in details["categories"]["items"]:
        date = category["since"].split("T")[0]
        new_date = logic.convert_date(category["since"])

        if date not in annotations["buckets"]:
            annotations["buckets"].append(date)

        index_of_date = annotations["buckets"].index(date)

        single_series = {
            "values": [0] * (len(annotations)),
            "name": category["name"],
            "display_name": category["name"].capitalize().replace("-", " "),
            "display_date": new_date,
            "date": date,
        }

        single_series["values"][index_of_date] = 1

        annotations["series"].append(single_series)

    annotations["series"] = sorted(annotations["series"],
                                   key=lambda k: k["date"])

    context = {
        # Data direct from details API
        "snap_name": snap_name,
        "snap_title": details["title"],
        "publisher_name": details["publisher"]["display-name"],
        "metric_period": metric_requested["period"],
        "active_device_metric": installed_base_metric,
        "default_track": default_track,
        "private": details["private"],
        # Metrics data
        "nodata": nodata,
        "latest_active_devices": latest_active,
        "active_devices": dict(active_devices),
        "territories_total": territories_total,
        "territories": country_devices.country_data,
        "active_devices_annotations": annotations,
        # Context info
        "is_linux": "Linux" in flask.request.headers["User-Agent"],
    }

    return flask.render_template("publisher/metrics.html", **context)
Esempio n. 26
0
def get_listing_snap(snap_name):
    try:
        snap_details = api.get_snap_info(snap_name, flask.session)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_error(api_error)

    details_metrics_enabled = snap_details["public_metrics_enabled"]
    details_blacklist = snap_details["public_metrics_blacklist"]

    is_on_stable = logic.is_snap_on_stable(snap_details["channel_maps_list"])

    # Filter icon & screenshot urls from the media set.
    icon_urls, screenshot_urls, banner_urls = logic.categorise_media(
        snap_details["media"])

    licenses = []
    for license in helpers.get_licenses():
        licenses.append({"key": license["licenseId"], "name": license["name"]})

    license = snap_details["license"]
    license_type = "custom"

    if " AND " not in license.upper() and " WITH " not in license.upper():
        license_type = "simple"

    referrer = None

    if flask.request.args.get("from"):
        referrer = flask.request.args.get("from")

    try:
        categories_results = store_api.get_categories()
    except StoreApiError:
        categories_results = []

    categories = sorted(
        get_categories(categories_results),
        key=lambda category: category["slug"],
    )

    snap_categories = logic.replace_reserved_categories_key(
        snap_details["categories"])

    snap_categories = logic.filter_categories(snap_categories)

    snap_categories["categories"] = [
        category["name"] for category in snap_categories["categories"]
    ]

    filename = "publisher/content/listing_tour.yaml"
    tour_steps = helpers.get_yaml(filename, typ="rt")

    context = {
        "snap_id": snap_details["snap_id"],
        "snap_name": snap_details["snap_name"],
        "snap_title": snap_details["title"],
        "snap_categories": snap_categories,
        "summary": snap_details["summary"],
        "description": snap_details["description"],
        "icon_url": icon_urls[0] if icon_urls else None,
        "publisher_name": snap_details["publisher"]["display-name"],
        "username": snap_details["publisher"]["username"],
        "screenshot_urls": screenshot_urls,
        "banner_urls": banner_urls,
        "contact": snap_details["contact"],
        "private": snap_details["private"],
        "website": snap_details["website"] or "",
        "public_metrics_enabled": details_metrics_enabled,
        "public_metrics_blacklist": details_blacklist,
        "license": license,
        "license_type": license_type,
        "licenses": licenses,
        "video_urls": snap_details["video_urls"],
        "is_on_stable": is_on_stable,
        "from": referrer,
        "categories": categories,
        "tour_steps": tour_steps,
        "status": snap_details["status"],
    }

    return flask.render_template("publisher/listing.html", **context)
Esempio n. 27
0
def get_snap_builds(snap_name):
    try:
        details = api.get_snap_info(snap_name, flask.session)

        # API call to make users without needed permissions refresh the session
        # Users needs package_upload_request permission to use this feature
        api.get_package_upload_macaroon(session=flask.session,
                                        snap_name=snap_name,
                                        channels=["edge"])
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_error(api_error)

    context = {
        "snap_id": details["snap_id"],
        "snap_name": details["snap_name"],
        "snap_title": details["title"],
        "snap_builds_enabled": False,
        "snap_builds": [],
        "total_builds": 0,
    }

    # Get built snap in launchpad with this store name
    lp_snap = launchpad.get_snap_by_store_name(details["snap_name"])

    if lp_snap:
        # In this case we can use the GitHub user account or
        # the Snapcraft GitHub user to check the snapcraft.yaml
        github = GitHub(
            flask.session.get("github_auth_secret",
                              GITHUB_SNAPCRAFT_USER_TOKEN))

        # Git repository without GitHub hostname
        context["github_repository"] = lp_snap["git_repository_url"][19:]
        github_owner, github_repo = context["github_repository"].split("/")

        context["yaml_file_exists"] = github.get_snapcraft_yaml_location(
            github_owner, github_repo)

        if not context["yaml_file_exists"]:
            flask.flash("This repository doesn't contain a snapcraft.yaml",
                        "negative")
        context.update(get_builds(lp_snap, slice(0, BUILDS_PER_PAGE)))

        context["snap_builds_enabled"] = bool(context["snap_builds"])
    else:
        github = GitHub(flask.session.get("github_auth_secret"))

        try:
            context["github_user"] = github.get_user()
        except Unauthorized:
            context["github_user"] = None

        if context["github_user"]:
            context["github_orgs"] = github.get_orgs()

    return flask.render_template("publisher/builds.html", **context)
Esempio n. 28
0
def post_snap_builds(snap_name):
    try:
        details = api.get_snap_info(snap_name, flask.session)
    except ApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except ApiError as api_error:
        return _handle_error(api_error)

    # Don't allow changes from Admins that are no contributors
    account_snaps = api.get_account_snaps(flask.session)

    if snap_name not in account_snaps:
        flask.flash("You do not have permissions to modify this Snap",
                    "negative")
        return flask.redirect(
            flask.url_for(".get_snap_builds", snap_name=snap_name))

    redirect_url = flask.url_for(".get_snap_builds", snap_name=snap_name)

    # Get built snap in launchpad with this store name
    github = GitHub(flask.session.get("github_auth_secret"))
    owner, repo = flask.request.form.get("github_repository").split("/")

    if not github.check_permissions_over_repo(owner, repo):
        flask.flash(
            "Your GitHub account doesn't have permissions in the repository",
            "negative",
        )
        return flask.redirect(redirect_url)

    repo_validation = validate_repo(flask.session.get("github_auth_secret"),
                                    snap_name, owner, repo)

    if not repo_validation["success"]:
        flask.flash(repo_validation["error"]["message"], "negative")
        return flask.redirect(redirect_url)

    lp_snap = launchpad.get_snap_by_store_name(details["snap_name"])
    git_url = f"https://github.com/{owner}/{repo}"

    if not lp_snap:
        lp_snap_name = md5(git_url.encode("UTF-8")).hexdigest()

        try:
            repo_exist = launchpad.get_snap(lp_snap_name)
        except HTTPError as e:
            if e.response.status_code == 404:
                repo_exist = False
            else:
                raise e

        if repo_exist:
            # The user registered the repo in BSI but didn't register a name
            # We can remove it and continue with the normal process
            if not repo_exist["store_name"]:
                # This conditional should be removed when issue 2657 is solved
                launchpad._request(path=repo_exist["self_link"][32:],
                                   method="DELETE")
            else:
                flask.flash(
                    "The specified repository is being used by another snap:"
                    f" {repo_exist['store_name']}",
                    "negative",
                )
                return flask.redirect(redirect_url)

        macaroon = api.get_package_upload_macaroon(session=flask.session,
                                                   snap_name=snap_name,
                                                   channels=["edge"
                                                             ])["macaroon"]

        launchpad.create_snap(snap_name, git_url, macaroon)

        flask.flash("The GitHub repository was linked correctly.", "positive")

        # Create webhook in the repo, it should also trigger the first build
        github_hook_url = f"https://snapcraft.io/{snap_name}/webhook/notify"

        try:
            hook = github.get_hook_by_url(owner, repo, github_hook_url)

            # We create the webhook if doesn't exist already in this repo
            if not hook:
                github.create_hook(owner, repo, github_hook_url)
        except HTTPError:
            flask.flash(
                "The GitHub Webhook could not be created. "
                "Please trigger a new build manually.",
                "caution",
            )

    elif lp_snap["git_repository_url"] != git_url:
        # In the future, create a new record, delete the old one
        raise AttributeError(
            f"Snap {snap_name} already has a build repository associated")

    return flask.redirect(redirect_url)
Esempio n. 29
0
def get_update_gh_webhooks(snap_name):
    try:
        details = publisher_api.get_snap_info(snap_name, flask.session)
    except StoreApiResponseErrorList as api_response_error_list:
        if api_response_error_list.status_code == 404:
            return flask.abort(404, "No snap named {}".format(snap_name))
        else:
            return _handle_error_list(api_response_error_list.errors)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    lp_snap = launchpad.get_snap_by_store_name(details["snap_name"])

    if not lp_snap:
        flask.flash("This snap is not linked with a GitHub repository",
                    "negative")

        return flask.redirect(
            flask.url_for(".get_settings", snap_name=snap_name))

    github = GitHub(flask.session.get("github_auth_secret"))

    try:
        github.get_user()
    except Unauthorized:
        return flask.redirect(f"/github/auth?back={flask.request.path}")

    gh_link = lp_snap["git_repository_url"][19:]
    gh_owner, gh_repo = gh_link.split("/")

    try:
        # Remove old BSI webhook if present
        old_url = (
            f"https://build.snapcraft.io/{gh_owner}/{gh_repo}/webhook/notify")
        old_hook = github.get_hook_by_url(gh_owner, gh_repo, old_url)

        if old_hook:
            github.remove_hook(
                gh_owner,
                gh_repo,
                old_hook["id"],
            )

        # Remove current hook
        github_hook_url = (
            f"{GITHUB_WEBHOOK_HOST_URL}{snap_name}/webhook/notify")
        snapcraft_hook = github.get_hook_by_url(gh_owner, gh_repo,
                                                github_hook_url)

        if snapcraft_hook:
            github.remove_hook(
                gh_owner,
                gh_repo,
                snapcraft_hook["id"],
            )

        # Create webhook in the repo
        github.create_hook(gh_owner, gh_repo, github_hook_url)
    except HTTPError:
        flask.flash(
            "The GitHub Webhook could not be created. "
            "Please try again or check your permissions over the repository.",
            "caution",
        )
    else:
        flask.flash("The webhook has been created successfully", "positive")

    return flask.redirect(flask.url_for(".get_settings", snap_name=snap_name))
Esempio n. 30
0
def post_register_name():
    snap_name = flask.request.form.get("snap-name")

    if not snap_name:
        return flask.redirect(flask.url_for(".get_register_name"))

    is_private = flask.request.form.get("is_private") == "private"
    store = flask.request.form.get("store")
    registrant_comment = flask.request.form.get("registrant_comment")

    try:
        publisher_api.post_register_name(
            session=flask.session,
            snap_name=snap_name,
            is_private=is_private,
            store=store,
            registrant_comment=registrant_comment,
        )
    except StoreApiResponseErrorList as api_response_error_list:
        try:
            user = publisher_api.get_account(flask.session)
        except (StoreApiError, ApiError) as api_error:
            return _handle_error(api_error)

        available_stores = logic.filter_available_stores(user["stores"])

        if api_response_error_list.status_code == 409:
            for error in api_response_error_list.errors:
                if error["code"] == "already_claimed":
                    return flask.redirect(
                        flask.url_for("account.get_account_details"))
                elif error["code"] == "already_registered":
                    return flask.redirect(
                        flask.url_for(
                            ".get_register_name",
                            snap_name=snap_name,
                            is_private=is_private,
                            store=store,
                            conflict=True,
                        ))
                elif error["code"] == "already_owned":
                    return flask.redirect(
                        flask.url_for(
                            ".get_register_name",
                            snap_name=snap_name,
                            is_private=is_private,
                            store=store,
                            already_owned=True,
                        ))
                elif error["code"] == "reserved_name":
                    return flask.redirect(
                        flask.url_for(
                            ".get_register_name",
                            snap_name=snap_name,
                            is_private=is_private,
                            store=store,
                            reserved=True,
                        ))

        context = {
            "snap_name": snap_name,
            "is_private": is_private,
            "available_stores": available_stores,
            "errors": api_response_error_list.errors,
        }

        return flask.render_template("publisher/register-snap.html", **context)
    except (StoreApiError, ApiError) as api_error:
        return _handle_error(api_error)

    flask.flash("".join([
        snap_name,
        " registered.",
        ' <a href="https://docs.snapcraft.io/build-snaps/upload"',
        " ",
        ' target="blank">How to upload a Snap</a>',
    ]))

    return flask.redirect(flask.url_for("account.get_account"))