示例#1
0
def revisions_handler(revision_id, diff_id=None):
    landoapi = LandoAPIClient(
        landoapi_url=current_app.config['LANDO_API_URL'],
        phabricator_api_token=get_phabricator_api_token(),
        auth0_access_token=session.get('access_token'))

    # Loads the submitted form if POST or creates a new one if GET
    form = RevisionForm()
    errors = []

    # Submit the landing request if this is a POST
    if form.is_submitted():
        if not is_user_authenticated():
            errors.append('You must be logged in to land a revision.')
        elif form.validate():
            try:
                # Returns True or raises a LandingSubmissionError
                if landoapi.post_landings(revision_id, form.diff_id.data,
                                          form.confirmation_token.data):
                    redirect_url = (
                        '/revisions/{revision_id}/{diff_id}/'.format(
                            revision_id=revision_id, diff_id=diff_id))
                    return redirect(redirect_url)
            except LandingSubmissionError as e:
                errors.append(e.error)
        else:
            for field, field_errors in form.errors.items():
                for error in field_errors:
                    errors.append(error)

    # If this is a GET or the POST fails, load data to display revision page.
    revision = landoapi.get_revision(revision_id, diff_id)
    diff_id = diff_id or revision['diff']['id']
    landing_statuses = landoapi.get_landings(revision_id)
    dryrun_result = {}
    if is_user_authenticated():
        dryrun_result = landoapi.post_landings_dryrun(revision_id, diff_id)
        form.confirmation_token.data = dryrun_result.get('confirmation_token')

    form.diff_id.data = diff_id

    return render_template('revision/revision.html',
                           revision=revision,
                           landing_statuses=landing_statuses,
                           form=form,
                           warnings=dryrun_result.get('warnings', []),
                           blockers=dryrun_result.get('blockers', []),
                           errors=errors)
示例#2
0
def revisions_handler(revision_id, diff_id=None):
    if not is_user_authenticated():
        handler = _revisions_handler
    else:
        handler = _revisions_handler_with_auth

    return handler(revision_id, diff_id=diff_id)
示例#3
0
    def wrapped(*args, **kwargs):
        if not is_user_authenticated():
            handler = no_auth_f
        else:
            handler = auth_f

        return handler(*args, **kwargs)
示例#4
0
def home():
    enable_transplant_ui = current_app.config.get("ENABLE_EMBEDDED_TRANSPLANT_UI")
    if not (enable_transplant_ui and is_user_authenticated()):
        # Return a static HTML page for users that are not logged in.
        return render_template("home.html")

    # Render the landing queue.
    return render_template("queue/queue.html")
示例#5
0
def sec_approval_request_handler():
    if not current_app.config.get("ENABLE_SEC_APPROVAL"):
        abort(404)

    if not is_user_authenticated():
        errors = make_form_error(
            'You must be logged in to request sec-approval'
        )
        return jsonify(errors=errors), 401

    token = get_phabricator_api_token()
    if not token:
        # The user has not set their API Token. Lando API will return an
        # error if it is missing.
        errors = make_form_error(
            'You must set your Phabricator API token in the Lando User '
            'Settings to request sec-approval'
        )
        return jsonify(errors=errors), 400

    api = LandoAPI(
        current_app.config['LANDO_API_URL'],
        auth0_access_token=session.get('access_token'),
        phabricator_api_token=token,
    )

    form = SecApprovalRequestForm()

    if not form.validate():
        return jsonify(errors=form.errors), 400
    else:
        logger.info(
            "sec-approval requested",
            extra={"revision_id": form.revision_id.data}
        )

        # NOTE: We let errors in the upstream service get turned into
        # exceptions and bubble up to the application's default exception
        # handler. It will generate a HTTP 500 for the UI to handle.
        api.request(
            "POST",
            "requestSecApproval",
            require_auth0=True,
            json={
                "revision_id": form.revision_id.data,
                "sanitized_message": form.new_message.data,
            }
        )

    return jsonify({})
示例#6
0
def settings():
    if not is_user_authenticated():
        # Accessing it unauthenticated from UI is protected by CSP
        return jsonify(
            dict(success=False, errors=dict(form_errors=["User is not authenticated"]))
        )

    form = UserSettingsForm()
    if not form.validate_on_submit():
        return jsonify(dict(success=False, errors=form.errors))

    payload = dict(success=True)
    response = manage_phab_api_token_cookie(form, payload)
    return response
示例#7
0
def oidc_error(error=None, error_description=None):
    """Handles authentication errors returned by Auth0.

    When something goes wrong with authentication, Auth0 redirects to our
    provided redirect uri (simply /redirect_uri when using flask_pyoidc) with
    the above two query parameters: error and error_description.
    We hook into this using the @oidc.error_view decorator so we can handle
    recoverable errors.

    The most common error is with refreshing the user's session automatically,
    officially called "Silent Authentication" (see
    https://auth0.com/docs/api-auth/tutorials/silent-authentication).

    With silent authentication enabled, flask_pyoidc passes 'prompt=none'
    when it requests authentication. If the user's greater Single Sign On
    session is still active, then the user is logged in seamlessly and their
    lando-ui session is refreshed. When the user's greater Single Sign On
    session is expired, Auth0 explicitly raises a 'login_required' error and
    provides that at our redirect_uri. Auth0 requires that the login is
    requested _without_ the 'prompt=none' option. By clearing the current
    session, flask_pyoidc knows to request login with a prompt.

    In general, when something goes wrong, we logout the user so that they can
    try again from a fresh state. If the user wasn't logged in to begin with,
    then we display an error message and log it.
    """
    if (
            is_user_authenticated() or
            error in ('login_required', 'interaction_required')
       ):
        # last_local_referrer is guaranteed to not be a signin/signout route.
        redirect_url = session.get('last_local_referrer') or '/'
        session.clear()
        response = make_response(redirect(redirect_url))
        response.delete_cookie('phabricator-api-token')
        return response
    else:
        logger.error(
            'authentication error',
            extra={
                'error': error,
                'error_description': error_description
            }
        )  # yapf: disable
        raise UIError(
            title='Authentication Error: {}'.format(error),
            message=error_description
        )
示例#8
0
def update_landing_job(landing_job_id):
    if not is_user_authenticated():
        errors = make_form_error(
            "You must be logged in to update a landing job.")
        return jsonify(errors=errors), 401

    token = get_phabricator_api_token()
    api = LandoAPI(
        current_app.config["LANDO_API_URL"],
        auth0_access_token=session.get("access_token"),
        phabricator_api_token=token,
    )

    try:
        data = api.request(
            "PUT",
            f"landing_jobs/{landing_job_id}",
            require_auth0=True,
            json=request.get_json(),
        )
    except LandoAPIError as e:
        return e.response, e.response["status"]
    return data
def is_user_authenticated():
    return helpers.is_user_authenticated()
示例#10
0
def revision(revision_id):
    api = LandoAPI(current_app.config['LANDO_API_URL'],
                   auth0_access_token=session.get('access_token'),
                   phabricator_api_token=get_phabricator_api_token())

    form = TransplantRequestForm()
    errors = []
    if form.is_submitted():
        if not is_user_authenticated():
            errors.append('You must be logged in to request a landing')

        elif not form.validate():
            for _, field_errors in form.errors.items():
                errors.extend(field_errors)

        else:
            try:
                api.request('POST',
                            'transplants',
                            require_auth0=True,
                            json={
                                'landing_path':
                                json.loads(form.landing_path.data),
                                'confirmation_token':
                                form.confirmation_token.data,
                            })
                # We don't actually need any of the data from the
                # the submission. As long as an exception wasn't
                # raised we're successful.
                return redirect(
                    url_for('revisions.revision', revision_id=revision_id))
            except LandoAPIError as e:
                if not e.detail:
                    raise

                errors.append(e.detail)

    # Request the entire stack.
    try:
        stack = api.request('GET', 'stacks/D{}'.format(revision_id))
    except LandoAPIError as e:
        if e.status_code == 404:
            raise RevisionNotFound(revision_id)
        else:
            raise

    # Build a mapping from phid to revision and identify
    # the data for the revision used to load this page.
    revision = None
    revisions = {}
    for r in stack['revisions']:
        revisions[r['phid']] = r
        if r['id'] == 'D{}'.format(revision_id):
            revision = r['phid']

    # Request all previous transplants for the stack.
    transplants = api.request(
        'GET',
        'transplants',
        params={'stack_revision_id': 'D{}'.format(revision_id)})

    # TODO: support displaying the full DAG, and landing *past* the
    # current revision.
    #
    # The revision may appear in many `landable_paths`` if it has
    # multiple children, or any of its landable descendents have
    # multiple children. That being said, there should only be a
    # single unique path up to this revision, so find the first
    # it appears in. The revisions up to the target one in this
    # path form the landable series.
    series = None
    for p in stack['landable_paths']:
        try:
            series = p[:p.index(revision) + 1]
            break
        except ValueError:
            pass

    dryrun = None
    if series and is_user_authenticated():
        landing_path = [{
            'revision_id': revisions[phid]['id'],
            'diff_id': revisions[phid]['diff']['id'],
        } for phid in series]
        form.landing_path.data = json.dumps(landing_path)

        dryrun = api.request('POST',
                             'transplants/dryrun',
                             require_auth0=True,
                             json={'landing_path': landing_path})
        form.confirmation_token.data = dryrun.get('confirmation_token')

        series = list(reversed(series))

    return render_template(
        'stack/stack.html',
        revision_id='D{}'.format(revision_id),
        series=series,
        dryrun=dryrun,
        stack=stack,
        transplants=transplants,
        revisions=revisions,
        revision_phid=revision,
        errors=errors,
        form=form,
    )
示例#11
0
def revision(revision_id):
    api = LandoAPI(
        current_app.config["LANDO_API_URL"],
        auth0_access_token=session.get("access_token"),
        phabricator_api_token=get_phabricator_api_token(),
    )

    form = TransplantRequestForm()
    sec_approval_form = SecApprovalRequestForm()

    errors = []
    if form.is_submitted():
        if not is_user_authenticated():
            errors.append("You must be logged in to request a landing")

        elif not form.validate():
            for _, field_errors in form.errors.items():
                errors.extend(field_errors)

        else:
            try:
                api.request(
                    "POST",
                    "transplants",
                    require_auth0=True,
                    json={
                        "landing_path": json.loads(form.landing_path.data),
                        "confirmation_token": form.confirmation_token.data,
                    },
                )
                # We don't actually need any of the data from the
                # the submission. As long as an exception wasn't
                # raised we're successful.
                return redirect(url_for("revisions.revision", revision_id=revision_id))
            except LandoAPIError as e:
                if not e.detail:
                    raise

                errors.append(e.detail)

    # Request the entire stack.
    try:
        stack = api.request("GET", "stacks/D{}".format(revision_id))
    except LandoAPIError as e:
        if e.status_code == 404:
            raise RevisionNotFound(revision_id)
        else:
            raise

    # Build a mapping from phid to revision and identify
    # the data for the revision used to load this page.
    revision = None
    revisions = {}
    for r in stack["revisions"]:
        revisions[r["phid"]] = r
        if r["id"] == "D{}".format(revision_id):
            revision = r["phid"]

    # Build a mapping from phid to repository.
    repositories = {}
    for r in stack["repositories"]:
        repositories[r["phid"]] = r

    # Request all previous transplants for the stack.
    transplants = api.request(
        "GET", "transplants", params={"stack_revision_id": "D{}".format(revision_id)}
    )

    # The revision may appear in many `landable_paths`` if it has
    # multiple children, or any of its landable descendents have
    # multiple children. That being said, there should only be a
    # single unique path up to this revision, so find the first
    # it appears in. The revisions up to the target one in this
    # path form the landable series.
    #
    # We also form a set of all the revisions that are landable
    # so we can present selection for what to land.
    series = None
    landable = set()
    for p in stack["landable_paths"]:
        for phid in p:
            landable.add(phid)

        try:
            series = p[: p.index(revision) + 1]
        except ValueError:
            pass

    dryrun = None
    target_repo = None
    if series and is_user_authenticated():
        landing_path = [
            {
                "revision_id": revisions[phid]["id"],
                "diff_id": revisions[phid]["diff"]["id"],
            }
            for phid in series
        ]
        form.landing_path.data = json.dumps(landing_path)

        dryrun = api.request(
            "POST",
            "transplants/dryrun",
            require_auth0=True,
            json={"landing_path": landing_path},
        )
        form.confirmation_token.data = dryrun.get("confirmation_token")

        series = list(reversed(series))
        target_repo = repositories.get(revisions[series[0]]["repo_phid"])

    phids = set(revisions.keys())
    edges = set(Edge(child=e[0], parent=e[1]) for e in stack["edges"])
    order = sort_stack_topological(
        phids, edges, key=lambda x: int(revisions[x]["id"][1:])
    )
    drawing_width, drawing_rows = draw_stack_graph(phids, edges, order)

    annotate_sec_approval_workflow_info(revisions)

    # Are we showing the "sec-approval request submitted" dialog?
    # If we are then fill in its values.
    submitted_revision = request.args.get("show_approval_success")
    submitted_rev_url = None
    if submitted_revision:
        for rev in revisions.values():
            if rev["id"] == submitted_revision:
                submitted_rev_url = rev["url"]
                break

    return render_template(
        "stack/stack.html",
        revision_id="D{}".format(revision_id),
        series=series,
        landable=landable,
        dryrun=dryrun,
        stack=stack,
        rows=list(zip(reversed(order), reversed(drawing_rows))),
        drawing_width=drawing_width,
        transplants=transplants,
        revisions=revisions,
        revision_phid=revision,
        sec_approval_form=sec_approval_form,
        submitted_rev_url=submitted_rev_url,
        target_repo=target_repo,
        errors=errors,
        form=form,
    )
def revision(revision_id):
    api = LandoAPI(current_app.config['LANDO_API_URL'],
                   auth0_access_token=session.get('access_token'),
                   phabricator_api_token=get_phabricator_api_token())

    form = TransplantRequestForm()
    errors = []
    if form.is_submitted():
        if not is_user_authenticated():
            errors.append('You must be logged in to request a landing')

        elif not form.validate():
            for _, field_errors in form.errors.items():
                errors.extend(field_errors)

        else:
            try:
                api.request('POST',
                            'transplants',
                            require_auth0=True,
                            json={
                                'landing_path':
                                json.loads(form.landing_path.data),
                                'confirmation_token':
                                form.confirmation_token.data,
                            })
                # We don't actually need any of the data from the
                # the submission. As long as an exception wasn't
                # raised we're successful.
                return redirect(
                    url_for('revisions.revision', revision_id=revision_id))
            except LandoAPIError as e:
                if not e.detail:
                    raise

                errors.append(e.detail)

    # Request the entire stack.
    try:
        stack = api.request('GET', 'stacks/D{}'.format(revision_id))
    except LandoAPIError as e:
        if e.status_code == 404:
            raise RevisionNotFound(revision_id)
        else:
            raise

    # Build a mapping from phid to revision and identify
    # the data for the revision used to load this page.
    revision = None
    revisions = {}
    for r in stack['revisions']:
        revisions[r['phid']] = r
        if r['id'] == 'D{}'.format(revision_id):
            revision = r['phid']

    # Build a mapping from phid to repository.
    repositories = {}
    for r in stack['repositories']:
        repositories[r['phid']] = r

    # Request all previous transplants for the stack.
    transplants = api.request(
        'GET',
        'transplants',
        params={'stack_revision_id': 'D{}'.format(revision_id)})

    # The revision may appear in many `landable_paths`` if it has
    # multiple children, or any of its landable descendents have
    # multiple children. That being said, there should only be a
    # single unique path up to this revision, so find the first
    # it appears in. The revisions up to the target one in this
    # path form the landable series.
    #
    # We also form a set of all the revisions that are landable
    # so we can present selection for what to land.
    series = None
    landable = set()
    for p in stack['landable_paths']:
        for phid in p:
            landable.add(phid)

        try:
            series = p[:p.index(revision) + 1]
        except ValueError:
            pass

    dryrun = None
    target_repo = None
    if series and is_user_authenticated():
        landing_path = [{
            'revision_id': revisions[phid]['id'],
            'diff_id': revisions[phid]['diff']['id'],
        } for phid in series]
        form.landing_path.data = json.dumps(landing_path)

        dryrun = api.request('POST',
                             'transplants/dryrun',
                             require_auth0=True,
                             json={'landing_path': landing_path})
        form.confirmation_token.data = dryrun.get('confirmation_token')

        series = list(reversed(series))
        target_repo = repositories.get(revisions[series[0]]['repo_phid'])

    phids = set(revisions.keys())
    edges = set(Edge(child=e[0], parent=e[1]) for e in stack['edges'])
    order = sort_stack_topological(phids,
                                   edges,
                                   key=lambda x: int(revisions[x]['id'][1:]))
    drawing_width, drawing_rows = draw_stack_graph(phids, edges, order)

    return render_template(
        'stack/stack.html',
        revision_id='D{}'.format(revision_id),
        series=series,
        landable=landable,
        dryrun=dryrun,
        stack=stack,
        rows=list(zip(reversed(order), reversed(drawing_rows))),
        drawing_width=drawing_width,
        transplants=transplants,
        revisions=revisions,
        revision_phid=revision,
        target_repo=target_repo,
        errors=errors,
        form=form,
    )