~edwargix/git.sr.ht

ec7faa1bf2e7ba3d648f3d6dadd59232b35e832b — Drew DeVault 8 years ago 18d22cd
Refactor access controls and add repo edit
A gitsrht/access.py => gitsrht/access.py +39 -0
@@ 0,0 1,39 @@
from enum import IntFlag
from flask_login import current_user
from gitsrht.types import User, Repository, RepoVisibility

class UserAccess(IntFlag):
    none = 0
    read = 1
    write = 2
    manage = 4

def get_repo(owner_name, repo_name):
    if owner_name[0] == "~":
        user = User.query.filter(User.username == owner_name[1:]).first()
        if user:
            repo = Repository.query.filter(Repository.owner_id == user.id)\
                .filter(Repository.name == repo_name).first()
        else:
            repo = None
        return user, repo
    else:
        # TODO: organizations
        return None, None

def get_access(repo):
    # TODO: ACLs
    if not repo:
        return UserAccess.none
    if not current_user:
        if repo.visibility == RepoVisibility.public or \
                repo.visibility == RepoVisibility.unlisted:
            return UserAccess.read
    if repo.owner_id == current_user.id:
        return UserAccess.read | UserAccess.write | UserAccess.manage
    if repo.visibility == RepoVisibility.private:
        return UserAccess.none
    return UserAccess.read

def has_access(repo, access):
    return access in get_access(repo)

M gitsrht/blueprints/manage.py => gitsrht/blueprints/manage.py +28 -7
@@ 5,6 5,7 @@ from srht.database import db
from srht.validation import Validation
from gitsrht.types import Repository, RepoVisibility
from gitsrht.decorators import loginrequired
from gitsrht.access import get_repo, has_access, UserAccess
import shutil
import subprocess
import os


@@ 13,15 14,13 @@ import re
manage = Blueprint('manage', __name__)
repos_path = cfg("cgit", "repos")

@manage.route("/manage")
@manage.route("/create")
@loginrequired
def index():
    another = request.args.get("another")
    repos = Repository.query.filter(Repository.owner_id == current_user.id)\
            .order_by(Repository.updated.desc()).all()
    return render_template("manage.html", repos=repos, another=another)
    return render_template("create.html", another=another)

@manage.route("/manage/create", methods=["POST"])
@manage.route("/create", methods=["POST"])
@loginrequired
def create():
    valid = Validation(request)


@@ 39,7 38,7 @@ def create():
    another = valid.optional("another")

    if not valid.ok:
        return render_template("manage.html",
        return render_template("create.html",
                valid=valid,
                repos=repos,
                repo_name=repo_name,


@@ 64,6 63,28 @@ def create():
    shutil.copy(hook_src, os.path.join(repo.path, "hooks", "update"))

    if another == "on":
        return redirect("/manage?another")
        return redirect("/create?another")
    else:
        return redirect("/~{}/{}".format(current_user.username, repo_name))

@manage.route("/<owner_name>/<repo_name>/settings/info")
def settings_info(owner_name, repo_name):
    owner, repo = get_repo(owner_name, repo_name)
    if not has_access(repo, UserAccess.read):
        abort(404)
    if not has_access(repo, UserAccess.manage):
        abort(403)
    return render_template("settings_info.html", owner=owner, repo=repo)

@manage.route("/<owner_name>/<repo_name>/settings/info", methods=["POST"])
def settings_info_POST(owner_name, repo_name):
    owner, repo = get_repo(owner_name, repo_name)
    if not has_access(repo, UserAccess.read):
        abort(404)
    if not has_access(repo, UserAccess.manage):
        abort(403)
    valid = Validation(request)
    desc = valid.optional("description", default=repo.description)
    repo.description = desc
    db.session.commit()
    return redirect("/{}/{}/settings/info".format(owner_name, repo_name))

M gitsrht/blueprints/public.py => gitsrht/blueprints/public.py +26 -21
@@ 3,6 3,7 @@ from flask_login import current_user
import requests
from srht.config import cfg
from gitsrht.types import User, Repository, RepoVisibility
from gitsrht.access import UserAccess, has_access, get_repo

public = Blueprint('cgit', __name__)



@@ 30,48 31,52 @@ def check_repo(user, repo):
    if _repo.visibility == RepoVisibility.private:
        if not current_user or current_user.id != _repo.owner_id:
            abort(404)
    return _repo
    return u, _repo

@public.route("/~<user>/<repo>", defaults={ "cgit_path": "" })
@public.route("/~<user>/<repo>/", defaults={ "cgit_path": "" })
@public.route("/~<user>/<repo>/<path:cgit_path>")
def cgit_passthrough(user, repo, cgit_path):
    check_repo(user, repo)
@public.route("/<owner_name>/<repo_name>", defaults={ "cgit_path": "" })
@public.route("/<owner_name>/<repo_name>/", defaults={ "cgit_path": "" })
@public.route("/<owner_name>/<repo_name>/<path:cgit_path>")
def cgit_passthrough(owner_name, repo_name, cgit_path):
    owner, repo = get_repo(owner_name, repo_name)
    if not has_access(repo, UserAccess.read):
        abort(404)
    r = requests.get("{}/{}".format(upstream, request.full_path))
    if r.status_code != 200:
        abort(r.status_code)
    base = cfg("network", "git").replace("http://", "").replace("https://", "")
    clone_urls = [
        ("ssh://git@{}/~{}/{}", "git@{}/~{}/{}"),
        ("https://{}/~{}/{}",),
        ("git://{}/~{}/{}",)
        ("ssh://git@{}/{}/{}", "git@{}/{}/{}"),
        ("https://{}/{}/{}",),
        ("git://{}/{}/{}",)
    ]
    if "Repository seems to be empty" in r.text:
        clone_urls = clone_urls[:2]
    clone_text = "<tr><td colspan='3'>" +\
        "<a rel='vcs-git' href='__CLONE_URL__' title='~{}/{} Git repository'>__CLONE_URL__</a>".format(user, repo) +\
        "</td></tr>"
        "<a rel='vcs-git' href='__CLONE_URL__' title='{}/{} Git repository'>__CLONE_URL__</a>".format(
                owner_name, repo_name) + "</td></tr>"
    if not clone_text in r.text:
        clone_text = clone_text.replace(" colspan='3'", "")
    text = r.text.replace(
        clone_text,
        " ".join(["<tr><td colspan='3'><a href='{}'>{}</a></td></tr>".format(
            url[0].format(base, user, repo), url[-1].format(base, user, repo))
            url[0].format(base, owner_name, repo_name),
            url[-1].format(base, owner_name, repo_name))
            for url in clone_urls])
    )
    if "Repository seems to be empty" in r.text:
        text = text.replace("<th class='left'>Clone</th>", "<th class='left'>Push</th>")
    return render_template("cgit.html",
            cgit_html=text,
            owner_name="~" + user,
            repo_name=repo)
            cgit_html=text, owner=owner, repo=repo,
            has_access=has_access, UserAccess=UserAccess)

@public.route("/~<user>/<repo>/patch", defaults={"path": None})
@public.route("/~<user>/<repo>/patch/<path:path>")
@public.route("/~<user>/<repo>/plain/<path:path>")
@public.route("/~<user>/<repo>/snapshot/<path:path>")
def cgit_plain(user, repo, path):
    check_repo(user, repo)
@public.route("/<owner_name>/<repo_name>/patch", defaults={"path": None})
@public.route("/<owner_name>/<repo_name>/patch/<path:path>")
@public.route("/<owner_name>/<repo_name>/plain/<path:path>")
@public.route("/<owner_name>/<repo_name>/snapshot/<path:path>")
def cgit_plain(owner_name, repo_name, path):
    owner, repo = get_repo(owner_name, repo_name)
    if not has_access(repo, UserAccess.read):
        abort(404)
    r = requests.get("{}/{}".format(upstream, request.full_path), stream=True)
    return Response(stream_with_context(r.iter_content()), content_type=r.headers['content-type'])


M gitsrht/templates/cgit.html => gitsrht/templates/cgit.html +6 -1
@@ 4,7 4,12 @@
  <div class="row">
    <div class="col-md-12">
      <h2>
        <a href="/{{ owner_name }}">{{ owner_name }}</a>/{{ repo_name }}
        <a href="/{{ owner.canonical_name }}">{{ owner.canonical_name }}</a>/{{ repo.name }}
        <small class="repo-nav">
        {% if has_access(repo, UserAccess.manage) %}
          <a href="/{{ owner.canonical_name }}/{{ repo.name }}/settings/info">Settings</a>
        {% endif %}
        </small>
      </h2>
    </div>
  </div>

R gitsrht/templates/manage.html => gitsrht/templates/create.html +1 -26
@@ 4,7 4,7 @@
  <div class="row">
    <section class="col-md-6">
      <h3 id="create">Create new repository</h3>
      <form method="POST" action="/manage/create">
      <form method="POST" action="/create">
        <div class="form-group {{valid.cls("repo-name")}}">
          <label for="repo-name">Name</label>
          <input


@@ 80,31 80,6 @@
            {% endif %}> Create another?
        </label>
      </form>
      {% if len(repos) != 0 %}
      <h3>Manage repositories</h3>
      <dl>
        {% for repo in repos %}
          <dt>
            <a href="/~{{current_user.username}}/{{repo.name}}">
              ~{{current_user.username}}/{{repo.name}}
            </a>
          </dt>
          <dd>
            {% if not repo.description %}
            <em>This repository has no description</em>
            {% else %}
            {{ repo.description }}
            {% endif %}
            <br />
            <a href="#">Edit details</a>
            &mdash;
            <a href="#">Manage collaborators</a>
            &mdash;
            <a href="#">Delete repository</a>
          </dd>
        {% endfor %}
      </dl>
      {% endif %}
    </section>
  </div>
</div>

M gitsrht/templates/git.html => gitsrht/templates/git.html +1 -1
@@ 1,6 1,6 @@
{% extends "layout.html" %}
{% block login_nav %}
<a href="/manage">Manage repos</a>
<a href="/create">Create repository</a>
&mdash;
{% endblock %}
{% block body %} 

A gitsrht/templates/settings.html => gitsrht/templates/settings.html +22 -0
@@ 0,0 1,22 @@
{% extends "layout.html" %}
{% block body %} 
<div class="container">
  <div class="row">
    <div class="col-md-12">
      <h2>
        <a
          href="/{{ owner.canonical_name }}"
        >{{ owner.canonical_name }}</a>/{{ repo.name }}
      </h2>
    </div>
  </div>
</div>
<ul class="nav nav-tabs">
  {% block tabs %}
  {% include "tabs.html" %}
  {% endblock %}
</ul>
<div class="container">
  {% block content %}{% endblock %}
</div>
{% endblock %}

A gitsrht/templates/settings_info.html => gitsrht/templates/settings_info.html +32 -0
@@ 0,0 1,32 @@
{% extends "settings.html" %}
{% block content %}
<div class="row">
  <div class="col-md-6">
    <form method="POST">
      <div class="form-group">
        <label for="name">
          Repository name <span class="text-muted">(you can't edit this)</span>
        </label>
        <input
          type="text"
          class="form-control"
          id="name"
          value="{{repo.name}}"
          readonly />
      </div>
      <div class="form-group">
        <label for="description">
          Description
        </label>
        <input
          type="text"
          class="form-control"
          id="description"
          name="description"
          value="{{repo.description}}" />
      </div>
      <button type="submit" class="btn btn-default pull-right">Save</button>
    </form>
  </div>
</div>
{% endblock %}

A gitsrht/templates/tabs.html => gitsrht/templates/tabs.html +22 -0
@@ 0,0 1,22 @@
{% macro link(path, title) %}
<a
  class="nav-link {% if request.path.endswith(path) %}active{% endif %}"
  href="/{{ owner.canonical_name }}/{{ repo.name }}/{{ path }}"
>{{ title }}</a>
{% endmacro %}

<li class="nav-item">
  <a
    class="nav-link"
    href="/{{ owner.canonical_name }}/{{ repo.name }}"
  >« back</a>
</li>
<li class="nav-item">
  {{ link("/info", "info") }}
</li>
<li class="nav-item">
  {{ link("/access", "access") }}
</li>
<li class="nav-item">
  {{ link("/delete", "delete") }}
</li>

M gitsrht/types/user.py => gitsrht/types/user.py +4 -0
@@ 16,6 16,10 @@ class User(Base):
    def __repr__(self):
        return '<User {} {}>'.format(self.id, self.username)

    @property
    def canonical_name(self):
        return "~" + self.username

    def is_authenticated(self):
        return True
    def is_active(self):

M scss/main.scss => scss/main.scss +4 -0
@@ 7,6 7,10 @@
  }
}

.repo-nav {
  margin-left: 1rem;
}

div#cgit {
  padding: 0;
  font-size: inherit;