Add some login machinery.
This commit is contained in:
parent
366907b08d
commit
e107d79612
10 changed files with 172 additions and 11 deletions
57
api.py
Normal file
57
api.py
Normal file
|
@ -0,0 +1,57 @@
|
|||
import logging
|
||||
|
||||
from flask import request, make_response, jsonify, abort
|
||||
from flask.ext.login import login_required, current_user
|
||||
from functools import wraps
|
||||
|
||||
import model
|
||||
|
||||
from app import app
|
||||
from util import parse_repository_name
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@app.route('/api/')
|
||||
def welcome():
|
||||
return make_response('welcome', 200)
|
||||
|
||||
|
||||
@app.route('/api/repository/', methods=['POST'])
|
||||
@login_required
|
||||
def create_repo_api():
|
||||
pass
|
||||
|
||||
|
||||
@app.route('/api/repository/', methods=['GET'])
|
||||
@login_required
|
||||
def list_repos_api():
|
||||
def repo_view(repo_perm):
|
||||
return {
|
||||
'namespace': repo_perm.repository.namespace,
|
||||
'name': repo_perm.repository.name,
|
||||
'role': repo_perm.role.name,
|
||||
}
|
||||
|
||||
repos = [repo_view(repo)
|
||||
for repo in model.get_user_repositories(current_user)]
|
||||
response = {
|
||||
'repositories': repos
|
||||
}
|
||||
|
||||
return jsonify(response)
|
||||
|
||||
|
||||
@app.route('/api/repository/<path:repository>', methods=['PUT'])
|
||||
@login_required
|
||||
@parse_repository_name
|
||||
def update_repo_api(namespace, repository):
|
||||
pass
|
||||
|
||||
|
||||
@app.route('/api/repository/<path:repository>', methods=['GET'])
|
||||
@login_required
|
||||
@parse_repository_name
|
||||
def get_repo_api(namespace, repository):
|
||||
pass
|
47
app.py
47
app.py
|
@ -1,10 +1,55 @@
|
|||
from flask import Flask, make_response, jsonify
|
||||
import logging
|
||||
|
||||
from flask import (Flask, make_response, request, abort, render_template,
|
||||
redirect, url_for)
|
||||
from flask.ext.principal import Principal
|
||||
from flask.ext.login import login_user, LoginManager
|
||||
|
||||
import model
|
||||
|
||||
app = Flask(__name__)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
Principal(app, use_sessions=False)
|
||||
|
||||
app.secret_key = '1cb18882-6d12-440d-a4cc-b7430fb5f884'
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = 'signin'
|
||||
|
||||
|
||||
@login_manager.user_loader
|
||||
def load_user(username):
|
||||
return model.get_user(username)
|
||||
|
||||
|
||||
@app.route('/', methods=['GET'])
|
||||
def index():
|
||||
return render_template('index.html')
|
||||
|
||||
|
||||
@app.route('/signin', methods=['POST'])
|
||||
def signin():
|
||||
username = request.form['username']
|
||||
password = request.form['password']
|
||||
|
||||
#TODO Allow email login
|
||||
verified = model.verify_user(username, password)
|
||||
if verified:
|
||||
logger.debug('Successfully signed in as: %s' % username)
|
||||
|
||||
login_user(verified)
|
||||
return redirect(request.args.get('next') or url_for('index'))
|
||||
|
||||
abort(403)
|
||||
|
||||
|
||||
@app.route('/signin', methods=['GET'])
|
||||
def render_signin_page():
|
||||
return render_template('signin.html')
|
||||
|
||||
|
||||
@app.route('/_ping')
|
||||
@app.route('/v1/_ping')
|
||||
def ping():
|
||||
|
|
12
database.py
12
database.py
|
@ -20,6 +20,18 @@ class User(BaseModel):
|
|||
email = CharField(unique=True)
|
||||
verified = BooleanField(default=False)
|
||||
|
||||
def is_active(self):
|
||||
return self.verified
|
||||
|
||||
def is_authenticated(self):
|
||||
return True
|
||||
|
||||
def is_anonymous(self):
|
||||
return False
|
||||
|
||||
def get_id(self):
|
||||
return unicode(self.username)
|
||||
|
||||
|
||||
class Visibility(BaseModel):
|
||||
name = CharField()
|
||||
|
|
11
index.py
11
index.py
|
@ -8,26 +8,19 @@ from functools import wraps
|
|||
|
||||
from app import app
|
||||
from auth import process_auth, get_authenticated_user, get_validated_token
|
||||
from util import parse_namespace_repository
|
||||
from util import parse_namespace_repository, parse_repository_name
|
||||
from permissions import (ModifyRepositoryPermission, ReadRepositoryPermission,
|
||||
UserPermission)
|
||||
|
||||
import model
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
REGISTRY_SERVER = 'localhost:5003'
|
||||
|
||||
|
||||
def parse_repository_name(f):
|
||||
@wraps(f)
|
||||
def wrapper(repository, *args, **kwargs):
|
||||
(namespace, repository) = parse_namespace_repository(repository)
|
||||
return f(namespace, repository, *args, **kwargs)
|
||||
return wrapper
|
||||
|
||||
|
||||
def generate_headers(access):
|
||||
def add_headers(f):
|
||||
@wraps(f)
|
||||
|
|
15
model.py
15
model.py
|
@ -15,6 +15,13 @@ def create_user(username, password, email):
|
|||
return new_user
|
||||
|
||||
|
||||
def get_user(username):
|
||||
try:
|
||||
return User.get(User.username == username)
|
||||
except User.DoesNotExist:
|
||||
return None
|
||||
|
||||
|
||||
def verify_user(username, password):
|
||||
try:
|
||||
fetched = User.get(User.username == username)
|
||||
|
@ -61,6 +68,14 @@ def get_repository(namespace, name):
|
|||
return None
|
||||
|
||||
|
||||
def get_user_repositories(user):
|
||||
select = RepositoryPermission.select(RepositoryPermission, Repository, Role)
|
||||
with_user = select.join(User).where(User.username == user.username)
|
||||
with_role = with_user.switch(RepositoryPermission).join(Role)
|
||||
with_repo = with_role.switch(RepositoryPermission).join(Repository)
|
||||
return with_repo
|
||||
|
||||
|
||||
def create_repository(namespace, name, owner):
|
||||
private = Visibility.get(name='private')
|
||||
repo = Repository.create(namespace=namespace, name=name,
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
peewee
|
||||
flask
|
||||
py-bcrypt
|
||||
Flask-Principal
|
||||
Flask-Principal
|
||||
Flask-Login
|
15
templates/index.html
Normal file
15
templates/index.html
Normal file
|
@ -0,0 +1,15 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Quay - Private Docker Repository</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<!-- Latest compiled and minified CSS -->
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
|
||||
|
||||
<!-- Optional theme -->
|
||||
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap-theme.min.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World</h1>
|
||||
</body>
|
||||
</html>
|
12
templates/signin.html
Normal file
12
templates/signin.html
Normal file
|
@ -0,0 +1,12 @@
|
|||
<html>
|
||||
<head>
|
||||
<title>Quay Sign In</title>
|
||||
</head>
|
||||
<body>
|
||||
<form method="post">
|
||||
Username: <input type="text" name="username"><br>
|
||||
Password: <input type="password" name="password"><br>
|
||||
<input type="submit" value="Sign In">
|
||||
</form>
|
||||
</body>
|
||||
</html>
|
10
util.py
10
util.py
|
@ -1,5 +1,7 @@
|
|||
import urllib
|
||||
|
||||
from functools import wraps
|
||||
|
||||
|
||||
def parse_namespace_repository(repository):
|
||||
parts = repository.rstrip('/').split('/', 1)
|
||||
|
@ -10,3 +12,11 @@ def parse_namespace_repository(repository):
|
|||
(namespace, repository) = parts
|
||||
repository = urllib.quote_plus(repository)
|
||||
return (namespace, repository)
|
||||
|
||||
|
||||
def parse_repository_name(f):
|
||||
@wraps(f)
|
||||
def wrapper(repository, *args, **kwargs):
|
||||
(namespace, repository) = parse_namespace_repository(repository)
|
||||
return f(namespace, repository, *args, **kwargs)
|
||||
return wrapper
|
||||
|
|
1
wsgi.py
1
wsgi.py
|
@ -3,6 +3,7 @@ import logging
|
|||
from app import app
|
||||
|
||||
import index
|
||||
import api
|
||||
|
||||
if __name__ == '__main__':
|
||||
FORMAT = '%(asctime)-15s - %(levelname)s - %(pathname)s - ' + \
|
||||
|
|
Reference in a new issue