def _webhook(record_id):
     record_id = UUID(record_id)
     hook = GitHubCommitToBuild._GitHubCommitToBuildRecord.query.filter(
         GitHubCommitToBuild._GitHubCommitToBuildRecord.id ==
         record_id).one_or_none()
     if not hook:
         return "Unknown hook " + str(record_id), 404
     valid = Validation(request)
     commit = valid.require("head_commit")
     repo = valid.require("repository")
     ref = valid.require("ref")
     if not valid.ok:
         return "Got request, but it has no commits"
     return submit_github_build(
         "commits",
         hook,
         repo,
         commit,
         env={
             "GITHUB_DELIVERY": request.headers.get("X-GitHub-Delivery"),
             "GITHUB_EVENT": request.headers.get("X-GitHub-Event"),
             "GITHUB_REF": ref,
             "GITHUB_REPO": repo["full_name"],
         },
         secrets=hook.secrets)
Exemple #2
0
 def _webhook(record_id):
     record_id = UUID(record_id)
     hook = GitHubPRToBuild._GitHubPRToBuildRecord.query.filter(
         GitHubPRToBuild._GitHubPRToBuildRecord.id == record_id).first()
     if not hook:
         return "Unknown hook " + str(record_id), 404
     valid = Validation(request)
     pr = valid.require("pull_request")
     action = valid.require("action")
     if not valid.ok:
         return "Got request, but it has no commits"
     if action not in ["opened", "synchronize"]:
         return "Got update, but there are no new commits"
     head = pr["head"]
     base = pr["base"]
     base_repo = base["repo"]
     head_repo = head["repo"]
     auth = GitHubAuthorization.query.filter(
         GitHubAuthorization.user_id == hook.user_id).first()
     if not auth:
         return ("You have not authorized us to access your GitHub account",
                 401)
     return submit_build(hook,
                         head_repo,
                         head,
                         base_repo,
                         secrets=False,
                         extras={
                             "automerge": hook.automerge,
                             "pr": pr["number"]
                         })
Exemple #3
0
def oauth_register_POST():
    valid = Validation(request)

    client_name = valid.require("client-name")
    redirect_uri = valid.require("redirect-uri")

    valid.expect(not redirect_uri or valid_url(redirect_uri),
            "Must be a valid HTTP or HTTPS URI", field="redirect-uri")

    if not valid.ok:
        return render_template("oauth-register.html",
                client_name=client_name,
                redirect_uri=redirect_uri,
                valid=valid)

    client = OAuthClient(current_user, client_name, redirect_uri)
    secret = client.gen_client_secret()
    session["client_id"] = client.client_id
    session["client_secret"] = secret
    session["client_event"] = "registered"
    db.session.add(client)
    audit_log("register oauth client",
            "Registered OAuth client {}".format(client.client_id))
    db.session.commit()
    return redirect("/oauth/registered")
    def _webhook(record_id):
        record_id = UUID(record_id)
        hook = GitLabCommitToBuild._GitLabCommitToBuildRecord.query.filter(
                GitLabCommitToBuild._GitLabCommitToBuildRecord.id == record_id
            ).one_or_none()
        if not hook:
            return "Unknown hook " + str(record_id), 404
        auth = GitLabAuthorization.query.filter(
                GitLabAuthorization.user_id == hook.user_id,
                GitLabAuthorization.upstream == hook.upstream,
            ).first()
        if not auth:
            return "Invalid authorization for this hook"
        gitlab = Gitlab(f"https://{hook.upstream}",
                oauth_token=auth.oauth_token)

        valid = Validation(request)
        commit = valid.require("after")
        ref = valid.require("ref")
        if not valid.ok:
            return "Unexpected hook payload"

        try:
            project = gitlab.projects.get(hook.repo_id)
            commit = project.commits.get(commit)
        except:
            return "Unable to fetch commit information"

        urls = submit_gitlab_build("commits", auth, hook, project, commit, env={
            "GITLAB_REF": ref,
        })
        if isinstance(urls, str):
            return urls
        return "Submitted:\n\n" + "\n".join([f"{n}: {u}" for n, u in urls])
Exemple #5
0
def tracker_labels_POST(owner, name):
    tracker, access = get_tracker(owner, name)
    is_owner = current_user.id == tracker.owner_id
    if not tracker:
        abort(404)
    if not is_owner:
        abort(403)

    valid = Validation(request)
    label_name = valid.require("name")
    label_color = valid.require("color")
    if not valid.ok:
        return render_template("tracker-labels.html",
                               tracker=tracker,
                               access=access,
                               is_owner=is_owner,
                               **valid.kwargs), 400

    valid.expect(2 < len(label_name) < 50,
                 "Must be between 2 and 50 characters",
                 field="name")
    valid.expect(color.valid_hex_color_code(label_color),
                 "Invalid hex color code",
                 field="color")
    if not valid.ok:
        return render_template("tracker-labels.html",
                               tracker=tracker,
                               access=access,
                               is_owner=is_owner,
                               **valid.kwargs), 400

    existing_label = (Label.query.filter(
        Label.tracker_id == tracker.id).filter(
            Label.name == label_name)).first()
    valid.expect(not existing_label,
                 "A label with this name already exists",
                 field="name")
    if not valid.ok:
        return render_template("tracker-labels.html",
                               tracker=tracker,
                               access=access,
                               is_owner=is_owner,
                               **valid.kwargs), 400

    # Determine a foreground color to use
    label_color_rgb = color.color_from_hex(label_color)
    text_color_rgb = color.get_text_color(label_color_rgb)
    text_color = color.color_to_hex(text_color_rgb)

    label = Label()
    label.tracker_id = tracker.id
    label.name = label_name
    label.color = label_color
    label.text_color = text_color
    db.session.add(label)
    db.session.commit()

    return redirect(url_for(".tracker_labels_GET", owner=owner, name=name))
Exemple #6
0
def oauth_token_POST(token):
    valid = Validation(request)
    client_id = valid.require("client_id")
    client_secret = valid.require("client_secret")
    revocation_url = valid.require("revocation_url")
    if not valid.ok:
        return valid.response

    client = (OAuthClient.query.filter(
        OAuthClient.client_id == client_id)).one_or_none()
    if not client:
        return {"errors": [{"reason": "404 not found"}]}, 404

    client_secret_hash = hashlib.sha512(client_secret.encode()).hexdigest()
    valid.expect(client_secret_hash == client.client_secret_hash,
                 "Invalid client secret")
    if not valid.ok:
        return valid.response

    h = hashlib.sha512(token.encode()).hexdigest()
    oauth_token = (OAuthToken.query.filter(
        OAuthToken.token_hash == h)).one_or_none()
    valid.expect(
        oauth_token is not None and oauth_token.expires > datetime.utcnow(),
        "Invalid or expired OAuth token")
    if not valid.ok:
        return valid.response

    rev = RevocationUrl.query\
            .filter(RevocationUrl.token_id == oauth_token.id)\
            .filter(RevocationUrl.client_id == client.id).first()
    if not rev:
        rev = RevocationUrl(client, oauth_token, revocation_url)
        db.session.add(rev)
    else:
        rev.url = revocation_url
    db.session.commit()

    if oauth_token._scopes == "*":
        return {"expires": oauth_token.expires, "scopes": "*"}

    scopes = [
        str(s) for s in oauth_token.scopes
        if (s.client_id and s.client_id == client.client_id)
        or s == OAuthScope("profile:read")
    ]
    valid.expect(any(scopes), "Invalid or expired OAuth token")
    if not valid.ok:
        return valid.response

    scopes = ",".join(scopes)
    # TODO: Celery task to notify of revocation

    return {
        "expires": oauth_token.expires,
        "scopes": ",".join(str(s) for s in oauth_token.scopes)
    }
Exemple #7
0
def oauth_exchange_POST():
    valid = Validation(request)
    client_id = valid.require('client_id')
    client_secret = valid.require('client_secret')
    exchange = valid.require('exchange')
    if not valid.ok:
        return valid.response

    client = (OAuthClient.query.filter(
        OAuthClient.client_id == client_id)).one_or_none()
    valid.expect(client, 'Unknown client ID')
    if not valid.ok:
        return valid.response

    client_secret_hash = hashlib.sha512(client_secret.encode()).hexdigest()
    valid.expect(client_secret_hash == client.client_secret_hash,
                 'Invalid client secret')
    if not valid.ok:
        return valid.response

    stash = redis.get(exchange)
    valid.expect(stash, 'Exchange token expired')
    if not valid.ok:
        return valid.response

    stash = json.loads(stash.decode())
    redis.delete(exchange)

    user = stash.get('user_id')
    scopes = stash.get('scopes')
    user = User.query.filter(User.id == user).first()
    valid.expect(
        user, "Unknown user ID stored for "
        "this exchange token (this isn't supposed to happen")
    if not valid.ok:
        return valid.response

    previous = (OAuthToken.query.filter(OAuthToken.user_id == user.id).filter(
        OAuthToken.client_id == client.id)).one_or_none()
    if not previous:
        oauth_token = OAuthToken(user, client)
    else:
        oauth_token = previous
        previous.expires = datetime.utcnow() + timedelta(days=365)

    oauth_token.scopes = [OAuthScope(s) for s in scopes.split(",")]
    token = oauth_token.gen_token()
    if not client.preauthorized:
        audit_log("oauth token issued",
                  "issued oauth token {} to client {}".format(
                      oauth_token.token_partial, client.client_id),
                  user=user)
    if not previous:
        db.session.add(oauth_token)
    db.session.commit()

    return {"token": token, "expires": oauth_token.expires}
Exemple #8
0
def jobs_POST():
    valid = Validation(request)
    _manifest = valid.require("manifest", cls=str)
    max_len = Job.manifest.prop.columns[0].type.length
    valid.expect(not _manifest or len(_manifest) < max_len,
                 "Manifest must be less than {} bytes".format(max_len),
                 field="manifest")
    note = valid.optional("note", cls=str)
    read = valid.optional("access:read", ["*"], list)
    write = valid.optional("access:write", [current_token.user.username], list)
    secrets = valid.optional("secrets", cls=bool, default=True)
    tags = valid.optional("tags", [], list)
    valid.expect(
        all(re.match(r"^[A-Za-z0-9_.-]+$", tag) for tag in tags),
        "Invalid tag name, tags must use lowercase alphanumeric characters, underscores, dashes, or dots",
        field="tags")
    triggers = valid.optional("triggers", list(), list)
    execute = valid.optional("execute", True, bool)
    if not valid.ok:
        return valid.response
    try:
        manifest = Manifest(yaml.safe_load(_manifest))
    except Exception as ex:
        valid.error(str(ex))
        return valid.response
    # TODO: access controls
    job = Job(current_token.user, _manifest)
    job.note = note
    if tags:
        job.tags = "/".join(tags)
    job.secrets = secrets
    db.session.add(job)
    db.session.flush()
    for task in manifest.tasks:
        t = Task(job, task.name)
        db.session.add(t)
        db.session.flush()  # assigns IDs for ordering purposes
    for index, trigger in enumerate(triggers):
        _valid = Validation(trigger)
        action = _valid.require("action", TriggerType)
        condition = _valid.require("condition", TriggerCondition)
        if not _valid.ok:
            _valid.copy(valid, "triggers[{}]".format(index))
            return valid.response
        # TODO: Validate details based on trigger type
        t = Trigger(job)
        t.trigger_type = action
        t.condition = condition
        t.details = json.dumps(trigger)
        db.session.add(t)
    if execute:
        queue_build(job, manifest)  # commits the session
    else:
        db.session.commit()
    return {"id": job.id}
Exemple #9
0
def billing_initial_POST():
    valid = Validation(request)
    amount = valid.require("amount")
    amount = int(amount)
    plan = valid.require("plan")
    valid.expect(not amount or amount > 0, "Expected amount >0")
    if not valid.ok:
        return "Invalid form submission", 400
    current_user.payment_cents = amount
    db.session.commit()
    if current_user.stripe_customer:
        return redirect(url_for("billing.billing_GET"))
    return redirect(url_for("billing.new_payment_GET"))
 def _webhook(record_id):
     record_id = UUID(record_id)
     hook = GitHubCommitToBuild._GitHubCommitToBuildRecord.query.filter(
             GitHubCommitToBuild._GitHubCommitToBuildRecord.id == record_id
         ).one_or_none()
     if not hook:
         return "Unknown hook " + str(record_id), 404
     valid = Validation(request)
     commit = valid.require("head_commit")
     repo = valid.require("repository")
     if not valid.ok:
         return "Got request, but it has no commits"
     return submit_build(hook, repo, commit, secrets=hook.secrets)
Exemple #11
0
def ticket_edit_POST(owner, name, ticket_id):
    tracker, _ = get_tracker(owner, name)
    if not tracker:
        abort(404)
    ticket, access = get_ticket(tracker, ticket_id)
    if not ticket:
        abort(404)
    if not TicketAccess.edit in access:
        abort(401)

    valid = Validation(request)
    title = valid.require("title", friendly_name="Title")
    desc = valid.optional("description")

    valid.expect(not title or 3 <= len(title) <= 2048,
                 "Title must be between 3 and 2048 characters.",
                 field="title")
    valid.expect(not desc or len(desc) < 16384,
                 "Description must be no more than 16384 characters.",
                 field="description")

    if not valid.ok:
        return render_template("edit_ticket.html",
                               tracker=tracker,
                               ticket=ticket,
                               **valid.kwargs)

    ticket.title = title
    ticket.description = desc
    db.session.commit()

    return redirect(ticket_url(ticket))
Exemple #12
0
 def _configure_POST(github):
     valid = Validation(request)
     repo = valid.require("repo")
     if not valid.ok:
         return "quit yo hackin bullshit"
     repo = github.get_repo(repo)
     if not repo:
         return "quit yo hackin bullshit"
     task = Task()
     task.name = "{}::github_pr_to_build".format(repo.full_name)
     task.user_id = current_user.id
     task._taskdef = "github_pr_to_build"
     db.session.add(task)
     db.session.flush()
     record = GitHubPRToBuild._GitHubPRToBuildRecord()
     record.id = uuid4()
     record.user_id = current_user.id
     record.task_id = task.id
     record.github_webhook_id = -1
     record.repo = repo.full_name
     db.session.add(record)
     db.session.flush()
     hook = repo.create_hook("web", {
         "url":
         _root +
         url_for("github_pr_to_build._webhook", record_id=record.id),
         "content_type":
         "json",
     }, ["pull_request"],
                             active=True)
     record.github_webhook_id = hook.id
     db.session.commit()
     return redirect(url_for("html.edit_task", task_id=task.id))
Exemple #13
0
def forgot_POST():
    valid = Validation(request)
    email = valid.require("email", friendly_name="Email")
    if not valid.ok:
        return render_template("forgot.html", **valid.kwargs)
    user = User.query.filter(User.email == email).first()
    valid.expect(user, "No account found with this email address.")
    if not valid.ok:
        return render_template("forgot.html", **valid.kwargs)
    factors = (UserAuthFactor.query.filter(
        UserAuthFactor.user_id == user.id)).all()
    valid.expect(
        not any(f for f in factors
                if f.factor_type in [FactorType.totp, FactorType.u2f]),
        "This account has two-factor authentication enabled, contact support.")
    if not valid.ok:
        return render_template("forgot.html", **valid.kwargs)
    rh = user.gen_reset_hash()
    db.session.commit()
    send_email(
        'reset_pw',
        user.email,
        'Reset your password on {}'.format(site_name),
        headers={
            "From":
            f"{cfg('sr.ht', 'owner-name')} <*****@*****.**>",
            "To":
            "{} <{}>".format(user.username, user.email),
            "Reply-To":
            f"{cfg('sr.ht', 'owner-name')} <{cfg('sr.ht', 'owner-email')}>",
        },
        user=user)
    audit_log("password reset requested", user=user)
    return render_template("forgot.html", done=True)
Exemple #14
0
def submit_POST():
    valid = Validation(request)
    _manifest = valid.require("manifest", friendly_name="Manifest")
    max_len = Job.manifest.prop.columns[0].type.length
    note = valid.optional("note", default="Submitted on the web")
    valid.expect(not _manifest or len(_manifest) < max_len,
                 "Manifest must be less than {} bytes".format(max_len),
                 field="manifest")
    if not valid.ok:
        return render_template("submit.html", **valid.kwargs)
    try:
        manifest = Manifest(yaml.safe_load(_manifest))
    except Exception as ex:
        valid.error(str(ex), field="manifest")
        return render_template("submit.html", **valid.kwargs)
    job = Job(current_user, _manifest)
    job.note = note
    db.session.add(job)
    db.session.flush()
    for task in manifest.tasks:
        t = Task(job, task.name)
        db.session.add(t)
        db.session.flush()  # assigns IDs for ordering purposes
    queue_build(job, manifest)  # commits the session
    return redirect("/~" + current_user.username + "/job/" + str(job.id))
Exemple #15
0
def ssh_keys_POST():
    user = User.query.get(current_user.id)
    valid = Validation(request)

    ssh_key = valid.require("ssh-key")
    if valid.ok:
        try:
            parsed_key = ssh.SSHKey(ssh_key)
            valid.expect(parsed_key.bits, "This is not a valid SSH key",
                         "ssh-key")
        except:
            valid.error("This is not a valid SSH key", "ssh-key")
    if valid.ok:
        fingerprint = parsed_key.hash_md5()[4:]
        valid.expect(SSHKey.query\
            .filter(SSHKey.fingerprint == fingerprint) \
            .count() == 0, "We already have this SSH key on file.", "ssh-key")

    if not valid.ok:
        return render_template("keys.html",
                               current_user=user,
                               ssh_key=ssh_key,
                               valid=valid)

    key = SSHKey(user, ssh_key, fingerprint, parsed_key.comment)
    db.session.add(key)
    audit_log("ssh key added", 'Added SSH key {}'.format(fingerprint))
    db.session.commit()
    return redirect("/keys")
 def _webhook(record_id):
     record_id = UUID(record_id)
     hook = GitHubPRToBuild._GitHubPRToBuildRecord.query.filter(
         GitHubPRToBuild._GitHubPRToBuildRecord.id == record_id).first()
     if not hook:
         return "Unknown hook " + str(record_id), 404
     valid = Validation(request)
     pr = valid.require("pull_request")
     action = valid.require("action")
     if not valid.ok:
         return "Got request, but it has no commits"
     if action not in ["opened", "synchronize"]:
         return "Got update, but there are no new commits"
     head = pr["head"]
     base = pr["base"]
     base_repo = base["repo"]
     head_repo = head["repo"]
     auth = GitHubAuthorization.query.filter(
         GitHubAuthorization.user_id == hook.user_id).first()
     if not auth:
         return ("You have not authorized us to access your GitHub account",
                 401)
     secrets = hook.secrets
     if not base_repo["private"]:
         secrets = False
     return submit_github_build(
         "pulls",
         hook,
         head_repo,
         head,
         base_repo,
         secrets=secrets,
         extras={
             "automerge": hook.automerge,
             "pr": pr["number"]
         },
         env={
             "GITHUB_DELIVERY": request.headers.get("X-GitHub-Delivery"),
             "GITHUB_EVENT": request.headers.get("X-GitHub-Event"),
             "GITHUB_PR_NUMBER": str(pr["number"]),
             "GITHUB_PR_TITLE": pr["title"],
             "GITHUB_BASE_REPO": base_repo["full_name"],
             "GITHUB_HEAD_REPO": head_repo["full_name"],
         })
Exemple #17
0
def new_payment_POST():
    valid = Validation(request)
    term = valid.require("term")
    token = valid.require("stripe-token")
    if not valid.ok:
        return "Invalid form submission", 400
    if not current_user.stripe_customer:
        new_customer = True
        try:
            customer = stripe.Customer.create(
                    description="~" + current_user.username,
                    email=current_user.email,
                    card=token)
            current_user.stripe_customer = customer.id
            current_user.payment_due = datetime.utcnow() + timedelta(seconds=-1)
        except stripe.error.CardError as e:
            details = e.json_body["error"]["message"]
            return render_template("new-payment.html",
                    amount=current_user.payment_cents, error=details)
    else:
        new_customer = False
        try:
            customer = stripe.Customer.retrieve(current_user.stripe_customer)
            source = customer.sources.create(source=token)
            customer.default_source = source.stripe_id
            customer.save()
        except stripe.error.CardError as e:
            details = e.json_body["error"]["message"]
            return render_template("new-payment.html",
                    amount=current_user.payment_cents, error=details)
    audit_log("billing", "New payment method handed")
    current_user.payment_interval = PaymentInterval(term)
    success, details = charge_user(current_user)
    if not success:
        return render_template("new-payment.html",
                amount=current_user.payment_cents, error=details)
    db.session.commit()
    if new_customer:
        return redirect(onboarding_redirect)
    session["message"] = "Your payment method was updated."
    return redirect(url_for("billing.billing_GET"))
Exemple #18
0
def security_totp_enable_POST():
    valid = Validation(request)

    secret = valid.require("secret")
    code = valid.require("code")
    
    if not valid.ok:
        return render_template("totp-enable.html",
            qrcode=totp_get_qrcode(secret),
            otpauth_uri=otpauth_uri(secret),
            secret=secret, valid=valid), 400
    code = code.replace(" ", "")
    try:
        code = int(code)
    except:
        valid.error(
                "This TOTP code is invalid (expected a number)", field="code")
    if not valid.ok:
        return render_template("totp-enable.html",
            qrcode=totp_get_qrcode(secret),
            otpauth_uri=otpauth_uri(secret),
            secret=secret, valid=valid), 400

    valid.expect(totp(secret, code),
            "The code you entered is incorrect.", field="code")

    if not valid.ok:
        return render_template("totp-enable.html",
            qrcode=totp_get_qrcode(secret),
            otpauth_uri=otpauth_uri(secret),
            secret=secret, valid=valid), 400

    factor = UserAuthFactor(current_user, FactorType.totp)
    factor.secret = secret.encode('utf-8')
    db.session.add(factor)
    audit_log("enabled two factor auth", 'Enabled TOTP')
    db.session.commit()
    metrics.meta_totp_enabled.inc()
    return redirect("/security")
Exemple #19
0
def totp_challenge_POST():
    user_id = session.get('authorized_user')
    factors = session.get('extra_factors')
    return_to = session.get('return_to') or '/'
    if not user_id or not factors:
        return redirect("/login")
    valid = Validation(request)

    code = valid.require('code')

    if not valid.ok:
        return render_template("totp-challenge.html",
                               return_to=return_to,
                               valid=valid)
    code = code.replace(" ", "")
    try:
        code = int(code)
    except:
        valid.error("This TOTP code is invalid (expected a number)",
                    field="code")
    if not valid.ok:
        return render_template("totp-challenge.html",
                               return_to=return_to,
                               valid=valid)

    factor = UserAuthFactor.query.get(factors[0])
    secret = factor.secret.decode('utf-8')

    valid.expect(totp(secret, code),
                 'The code you entered is incorrect.',
                 field='code')

    if not valid.ok:
        return render_template("totp-challenge.html",
                               valid=valid,
                               return_to=return_to)

    factors = factors[1:]
    if len(factors) != 0:
        return get_challenge(factors[0])

    del session['authorized_user']
    del session['extra_factors']
    del session['return_to']

    user = User.query.get(user_id)
    login_user(user, remember=True)
    audit_log("logged in")
    db.session.commit()
    metrics.meta_logins_success.inc()
    return redirect(return_to)
Exemple #20
0
def create_POST():
    valid = Validation(request)
    name = valid.require("tracker_name", friendly_name="Name")
    desc = valid.optional("tracker_desc")
    if not valid.ok:
        return render_template("tracker-create.html", **valid.kwargs), 400

    valid.expect(2 < len(name) < 256,
                 "Must be between 2 and 256 characters",
                 field="tracker_name")
    valid.expect(not valid.ok or name[0] in string.ascii_lowercase,
                 "Must begin with a lowercase letter",
                 field="tracker_name")
    valid.expect(not valid.ok or name_re.match(name),
                 "Only lowercase alphanumeric characters or -.",
                 field="tracker_name")
    valid.expect(not desc or len(desc) < 4096,
                 "Must be less than 4096 characters",
                 field="tracker_desc")
    if not valid.ok:
        return render_template("tracker-create.html", **valid.kwargs), 400

    tracker = (Tracker.query.filter(
        Tracker.owner_id == current_user.id).filter(
            Tracker.name == name)).first()
    valid.expect(not tracker,
                 "A tracker by this name already exists",
                 field="tracker_name")
    if not valid.ok:
        return render_template("tracker-create.html", **valid.kwargs), 400

    tracker = Tracker()
    tracker.owner_id = current_user.id
    tracker.name = name
    tracker.description = desc
    db.session.add(tracker)
    db.session.flush()

    sub = TicketSubscription()
    sub.tracker_id = tracker.id
    sub.user_id = current_user.id
    db.session.add(sub)
    db.session.commit()

    if "create-configure" in valid:
        return redirect(
            url_for(".settings_details_GET",
                    owner=current_user.username,
                    name=name))

    return redirect(tracker_url(tracker))
Exemple #21
0
def delegate_scopes_POST():
    valid = Validation(request)
    desc = valid.require("description", cls=str)
    name = valid.require("name", cls=str)
    writable = valid.require("writable", cls=bool)
    valid.expect(not name or re.match(r"^[a-z_]+$", name),
                 "Lowercase characters and underscores only",
                 field="name")
    if not valid.ok:
        return valid.response
    scope = (DelegatedScope.query\
        .filter(DelegatedScope.client_id == g.oauth_client.id)
        .filter(DelegatedScope.name == name)).one_or_none()
    valid.expect(scope is None,
                 "A scope with this name already exists.",
                 field="name")
    if not valid.ok:
        return valid.response
    scope = DelegatedScope(g.oauth_client, name, desc)
    scope.write = writable
    db.session.add(scope)
    db.session.commit()
    return scope.to_dict()
Exemple #22
0
def ticket_add_label(owner, name, ticket_id):
    tracker, _ = get_tracker(owner, name)
    if not tracker:
        abort(404)
    ticket, access = get_ticket(tracker, ticket_id)
    if not ticket:
        abort(404)
    if not TicketAccess.edit in access:
        abort(401)

    valid = Validation(request)
    label_id = valid.require("label_id", friendly_name="A label")
    if not valid.ok:
        ctx = get_ticket_context(ticket, tracker, access)
        return render_template("ticket.html", **ctx, **valid.kwargs)

    valid.expect(re.match(r"^\d+$", label_id),
                 "Label ID must be numeric",
                 field="label_id")
    if not valid.ok:
        ctx = get_ticket_context(ticket, tracker, access)
        return render_template("ticket.html", **ctx, **valid.kwargs)

    label_id = int(request.form.get('label_id'))
    label = Label.query.filter(Label.id == label_id).first()
    if not label:
        abort(404)

    ticket_label = (TicketLabel.query.filter(
        TicketLabel.label_id == label.id).filter(
            TicketLabel.ticket_id == ticket.id)).first()

    if not ticket_label:
        ticket_label = TicketLabel()
        ticket_label.ticket_id = ticket.id
        ticket_label.label_id = label.id
        ticket_label.user_id = current_user.id

        event = Event()
        event.event_type = EventType.label_added
        event.user_id = current_user.id
        event.ticket_id = ticket.id
        event.label_id = label.id

        db.session.add(ticket_label)
        db.session.add(event)
        db.session.commit()

    return redirect(ticket_url(ticket))
Exemple #23
0
def pgp_keys_POST():
    user = User.query.get(current_user.id)
    valid = Validation(request)

    pgp_key = valid.require("pgp-key")
    valid.expect(
        not pgp_key or len(pgp_key) < 32768,
        Markup("Maximum encoded key length is 32768 bytes. "
               "Try <br /><code>gpg --armor --export-options export-minimal "
               "--export &lt;fingerprint&gt;</code><br /> to export a "
               "smaller key."),
        field="pgp-key")
    if valid.ok:
        try:
            key = pgpy.PGPKey()
            key.parse(pgp_key.replace('\r', '').encode('utf-8'))
        except:
            valid.error("This is not a valid PGP key", field="pgp-key")
        valid.expect(any(key.userids),
                     "This key has no user IDs",
                     field="pgp-key")
        try:
            prepare_email("test", user.email, "test", encrypt_key=pgp_key)
        except:
            valid.error(
                "We were unable to encrypt a test message with this key",
                field="pgp-key")
    if valid.ok:
        valid.expect(PGPKey.query\
            .filter(PGPKey.user_id == user.id) \
            .filter(PGPKey.key_id == key.fingerprint)\
            .count() == 0, "This is a duplicate key", field="pgp-key")
    if not valid.ok:
        return render_template("keys.html",
                               current_user=user,
                               pgp_key=pgp_key,
                               valid=valid)

    pgp = PGPKey(user, pgp_key, key.fingerprint, key.userids[0].email)
    db.session.add(pgp)
    audit_log("pgp key added", 'Added PGP key {}'.format(key.fingerprint))
    db.session.commit()
    return redirect("/keys")
Exemple #24
0
    def _webhook(record_id):
        record_id = UUID(record_id)
        hook = GitLabMRToBuild._GitLabMRToBuildRecord.query.filter(
            GitLabMRToBuild._GitLabMRToBuildRecord.id ==
            record_id).one_or_none()
        if not hook:
            return "Unknown hook " + str(record_id), 404
        auth = GitLabAuthorization.query.filter(
            GitLabAuthorization.user_id == hook.user_id,
            GitLabAuthorization.upstream == hook.upstream,
        ).first()
        if not auth:
            return "Invalid authorization for this hook"
        gitlab = Gitlab(f"https://{hook.upstream}",
                        oauth_token=auth.oauth_token)

        valid = Validation(request)
        object_attrs = valid.require("object_attributes")
        if not valid.ok:
            return "Unexpected hook payload"
        source = object_attrs["source"]
        last_commit = object_attrs["last_commit"]

        project = gitlab.projects.get(hook.repo_id)
        source = gitlab.projects.get(source["id"])
        commit = project.commits.get(last_commit["id"])
        merge_req = project.mergerequests.get(object_attrs["iid"])

        urls = submit_gitlab_build(
            "mrs", auth, hook, project, commit, source, {
                "GITLAB_MR_NUMBER": object_attrs["iid"],
                "GITLAB_MR_TITLE": object_attrs["title"],
                "GITLAB_BASE_REPO": project.attributes["name_with_namespace"],
                "GITLAB_HEAD_REPO": source.attributes["name_with_namespace"],
            })
        if isinstance(urls, str):
            return urls

        summary = "\n\nbuilds.sr.ht jobs:\n\n" + ("\n".join(
            [f"[{n}]({url}): :clock1: running" for n, u in urls]))
        merge_req.description += summary
        merge_req.save()
        return
Exemple #25
0
def settings_access_POST(owner_name, repo_name):
    owner, repo = check_access(owner_name, repo_name, UserAccess.manage)
    if isinstance(repo, BaseRedirectMixin):
        repo = repo.new_repo
    valid = Validation(request)
    user = valid.require("user", friendly_name="User")
    mode = valid.optional("access", cls=AccessMode, default=AccessMode.ro)
    if not valid.ok:
        return render_template("settings_access.html",
                               owner=owner,
                               repo=repo,
                               **valid.kwargs)
    # TODO: Group access
    if user[0] == "~":
        user = user[1:]
    User = current_app.User
    user = User.query.filter(User.username == user).first()
    valid.expect(
        user,
        "I don't know this user. Have they logged into this service before?",
        field="user")
    valid.expect(
        not user or user.id != current_user.id,
        "You can't adjust your own access controls. You always have full read/write access.",
        field="user")
    if not valid.ok:
        return render_template("settings_access.html",
                               owner=owner,
                               repo=repo,
                               **valid.kwargs)
    Access = current_app.Access
    grant = (Access.query.filter(Access.repo_id == repo.id,
                                 Access.user_id == user.id)).first()
    if not grant:
        grant = Access()
        grant.repo_id = repo.id
        grant.user_id = user.id
        db.session.add(grant)
    grant.mode = mode
    db.session.commit()
    return redirect("/{}/{}/settings/access".format(owner.canonical_name,
                                                    repo.name))
Exemple #26
0
def secret_delete_POST():
    valid = Validation(request)

    uuid = valid.require("uuid")
    if not uuid:
        abort(404)

    secret = Secret.query.filter(Secret.uuid == uuid).first()
    if not secret:
        abort(404)
    if secret.user_id != current_user.id:
        abort(401)

    name = secret.name
    db.session.delete(secret)
    db.session.commit()

    session["message"] = "Successfully removed secret {}{}.".format(
        uuid, " ({})".format(name) if name else "")
    return redirect("/secrets")
Exemple #27
0
def reset_POST(token):
    user = User.query.filter(User.reset_hash == token).first()
    if not user:
        abort(404)
    if user.reset_expiry < datetime.utcnow():
        abort(404)
    valid = Validation(request)
    password = valid.require("password", friendly_name="Password")
    if not valid.ok:
        return render_template("reset.html", valid=valid)
    valid.expect(8 <= len(password) <= 512,
                 "Password must be between 8 and 512 characters.", "password")
    if not valid.ok:
        return render_template("reset.html", valid=valid)
    user.password = bcrypt.hashpw(password.encode('utf-8'),
                                  salt=bcrypt.gensalt()).decode('utf-8')
    audit_log("password reset", user=user)
    db.session.commit()
    login_user(user, remember=True)
    metrics.meta_pw_resets.inc()
    return redirect("/")
Exemple #28
0
def privacy_POST():
    valid = Validation(request)

    key_id = valid.require("pgp-key")
    key_id = key_id if key_id != "null" else None
    key = None

    if key_id:
        key = PGPKey.query.get(int(key_id))
        valid.expect(key.user_id == current_user.id, "Invalid PGP key")

    if not valid.ok:
        return redirect("/privacy")

    user = User.query.get(current_user.id)
    user.pgp_key = key
    audit_log("changed pgp key",
            "Set default PGP key to {}".format(key.key_id if key else None))
    db.session.commit()

    return redirect("/privacy")
Exemple #29
0
def create_list_POST():
    valid = Validation(request)
    list_name = valid.require("list_name", friendly_name="Name")
    list_desc = valid.optional("list_desc")
    if not valid.ok:
        return render_template("create.html", **valid.kwargs)

    valid.expect(re.match(r'^[a-z._-][a-z0-9._-]*$', list_name),
                 "Name must match [a-z._-][a-z0-9._-]*",
                 field="list_name")
    existing = (List.query.filter(List.owner_id == current_user.id).filter(
        List.name.ilike(list_name)).first())
    valid.expect(not existing,
                 "This name is already in use.",
                 field="list_name")
    valid.expect(not list_desc or 16 < len(list_desc) < 2048,
                 "Description must be between 16 and 2048 characters.",
                 field="list_desc")
    if not valid.ok:
        return render_template("create.html", **valid.kwargs)

    ml = List()
    ml.owner_id = current_user.id
    ml.name = list_name
    ml.description = list_desc
    db.session.add(ml)
    db.session.commit()

    # Auto-subscribe the owner
    sub = Subscription()
    sub.user_id = current_user.id
    sub.list_id = ml.id
    sub.confirmed = True
    db.session.add(sub)
    db.session.commit()

    return redirect(
        url_for("archives.archive",
                owner_name=current_user.canonical_name,
                list_name=ml.name))
Exemple #30
0
    def configure_repo_POST(gitlab, upstream):
        valid = Validation(request)
        repo_id = valid.require("repo_id")
        if not valid.ok:
            return "quit yo hackin bullshit"
        project = gitlab.projects.get(int(repo_id))
        if not project:
            return "quit yo hackin bullshit"

        task = Task()
        task.name = "{}::gitlab_mr_to_build".format(
            project.attributes["name_with_namespace"])
        task.user_id = current_user.id
        task._taskdef = "gitlab_mr_to_build"
        db.session.add(task)
        db.session.flush()

        record = GitLabMRToBuild._GitLabMRToBuildRecord()
        record.id = uuid4()
        record.user_id = current_user.id
        record.task_id = task.id
        record.gitlab_webhook_id = -1
        record.repo_name = project.attributes['name_with_namespace']
        record.repo_id = project.id
        record.web_url = project.attributes['web_url']
        record.upstream = upstream
        db.session.add(record)
        db.session.flush()

        hook = project.hooks.create({
            "url":
            _root +
            url_for("gitlab_mr_to_build._webhook", record_id=record.id),
            "merge_requests_events":
            1,
        })
        record.gitlab_webhook_id = hook.id
        db.session.commit()

        return redirect(url_for("html.edit_task", task_id=task.id))