Fix login with robot to quay-appr

This commit is contained in:
Antoine Legrand 2017-04-13 14:25:47 +02:00
parent f5ab03070c
commit 578f87f94c
6 changed files with 63 additions and 52 deletions

View file

@ -8,10 +8,10 @@ from cnr.exception import raise_package_not_found, raise_channel_not_found
from six import add_metaclass
from app import storage
from app import storage, authentication
from data import model, oci_model
from data.database import Tag, Manifest, MediaType, Blob, Repository, Channel
from util.names import parse_robot_username
class BlobDescriptor(namedtuple('Blob', ['mediaType', 'size', 'digest', 'urls'])):
""" BlobDescriptor describes a blob with its mediatype, size and digest.
@ -427,6 +427,16 @@ class OCIAppModel(AppRegistryDataInterface):
return ChannelView(current=channel.linked_tag.name,
name=channel.name)
def get_user(self, username, password):
err_msg = None
if parse_robot_username(username) is not None:
try:
user = model.user.verify_robot(username, password)
except model.InvalidRobotException as exc:
return (None, exc.message)
else:
user, err_msg = authentication.verify_and_link_user(username, password)
return (user, err_msg)
def _strip_sha256_header(digest):
if digest.startswith('sha256:'):

View file

@ -8,7 +8,7 @@ from cnr.models.package_base import PackageBase, manifest_media_type
from app import storage
from data.interfaces.appr import oci_app_model
from data.oci_model import blob # TODO these calls should be through oci_app_model
from data.oci_model import blob # TODO these calls should be through oci_app_model
class Blob(BlobBase):
@ -83,6 +83,15 @@ class Channel(ChannelBase):
oci_app_model.delete_channel(self.package, self.name)
class User(object):
""" User in CNR models """
@classmethod
def get_user(cls, username, password):
""" Returns True if user creds is valid """
return oci_app_model.get_user(username, password)
class Package(PackageBase):
""" CNR Package model implemented against the Quay data model. """

View file

@ -1,23 +1,21 @@
import logging
from base64 import b64encode
import cnr
from cnr.api.impl import registry as cnr_registry
from cnr.api.registry import repo_name, _pull
from cnr.exception import (CnrException, InvalidUsage, InvalidParams, InvalidRelease,
UnableToLockResource, UnauthorizedAccess, Unsupported, ChannelNotFound, Forbidden,
PackageAlreadyExists, PackageNotFound, PackageReleaseNotFound)
from flask import request, jsonify
from cnr.api.registry import _pull, repo_name
from cnr.exception import (
ChannelNotFound, CnrException, Forbidden, InvalidParams, InvalidRelease, InvalidUsage,
PackageAlreadyExists, PackageNotFound, PackageReleaseNotFound, UnableToLockResource,
UnauthorizedAccess, Unsupported)
from flask import jsonify, request
from app import authentication
from auth.auth_context import get_authenticated_user
from auth.decorators import process_auth
from auth.permissions import CreateRepositoryPermission, ModifyRepositoryPermission
from endpoints.appr import appr_bp, require_app_repo_read, require_app_repo_write
from auth.permissions import (CreateRepositoryPermission, ModifyRepositoryPermission)
from endpoints.appr import (appr_bp, require_app_repo_read, require_app_repo_write)
from endpoints.appr.cnr_backend import Blob, Channel, Package, User
from endpoints.appr.decorators import disallow_for_image_repository
from endpoints.appr.cnr_backend import Package, Channel, Blob
from endpoints.decorators import anon_allowed, anon_protect
from util.names import REPOSITORY_NAME_REGEX, TAG_REGEX
@ -58,7 +56,7 @@ def login():
if not username or not password:
raise InvalidUsage('Missing username or password')
user, err = authentication.verify_credentials(username, password)
user, err = User.get_user(username, password)
if err is not None:
raise UnauthorizedAccess(err)
@ -185,7 +183,7 @@ def push(namespace, package_name):
if not REPOSITORY_NAME_REGEX.match(package_name):
logger.debug('Found invalid repository name CNR push: %s', reponame)
raise InvalidUsage()
raise InvalidUsage('invalid repository name: %s' % reponame)
values = request.get_json(force=True, silent=True) or {}
private = values.get('visibility', 'private')
@ -258,19 +256,24 @@ def show_channel(namespace, package_name, channel_name):
@require_app_repo_write
@anon_protect
def add_channel_release(namespace, package_name, channel_name, release):
if not TAG_REGEX.match(channel_name):
logger.debug('Found invalid channel name CNR add channel release: %s', channel_name)
raise InvalidUsage()
if not TAG_REGEX.match(release):
logger.debug('Found invalid release name CNR add channel release: %s', release)
raise InvalidUsage()
_check_channel_name(channel_name, release)
reponame = repo_name(namespace, package_name)
result = cnr_registry.add_channel_release(reponame, channel_name, release, channel_class=Channel,
package_class=Package)
return jsonify(result)
def _check_channel_name(channel_name, release=None):
if not TAG_REGEX.match(channel_name):
logger.debug('Found invalid channel name CNR add channel release: %s', channel_name)
raise InvalidUsage("Found invalid channelname %s" % release,
{'name': channel_name,
"release": release})
if release is not None and not TAG_REGEX.match(release):
logger.debug('Found invalid release name CNR add channel release: %s', release)
raise InvalidUsage("Found invalid channel release name %s" % release,
{'name': channel_name,
"release": release})
@appr_bp.route(
"/api/v1/packages/<string:namespace>/<string:package_name>/channels/<string:channel_name>/<string:release>",
@ -281,14 +284,7 @@ def add_channel_release(namespace, package_name, channel_name, release):
@require_app_repo_write
@anon_protect
def delete_channel_release(namespace, package_name, channel_name, release):
if not TAG_REGEX.match(channel_name):
logger.debug('Found invalid channel name CNR delete channel release: %s', channel_name)
raise InvalidUsage()
if not TAG_REGEX.match(release):
logger.debug('Found invalid release name CNR delete channel release: %s', release)
raise InvalidUsage()
_check_channel_name(channel_name, release)
reponame = repo_name(namespace, package_name)
result = cnr_registry.delete_channel_release(reponame, channel_name, release,
channel_class=Channel, package_class=Package)
@ -304,10 +300,7 @@ def delete_channel_release(namespace, package_name, channel_name, release):
@require_app_repo_write
@anon_protect
def delete_channel(namespace, package_name, channel_name):
if not TAG_REGEX.match(channel_name):
logger.debug('Found invalid channel name CNR delete channel: %s', channel_name)
raise InvalidUsage()
_check_channel_name(channel_name)
reponame = repo_name(namespace, package_name)
result = cnr_registry.delete_channel(reponame, channel_name, channel_class=Channel)
return jsonify(result)

View file

@ -215,7 +215,7 @@ class TestQuayModels(CnrTestModels):
b2db = oci_blob.get_blob(p2.digest)
assert b2db.id == bdb.id
def test_push_same_blob(self, db_with_data1):
def test_force_push_different_blob(self, db_with_data1):
p = db_with_data1.Package.get("titi/rocketchat", "2.0.1", 'kpm')
assert p.package == "titi/rocketchat"
assert p.release == "2.0.1"

View file

@ -1,29 +1,28 @@
import json
from flask import url_for
import pytest
from data import model
from endpoints.appr.registry import appr_bp
from test.fixtures import app, appconfig, database_uri, init_db_path, sqlitedb_file
def test_invalid_login(app, client):
app.register_blueprint(appr_bp, url_prefix='/cnr')
@pytest.mark.parametrize('login_data, expected_code', [
({"username": "devtable", "password": "password"}, 200),
({"username": "devtable", "password": "badpass"}, 401),
({"username": "devtable+dtrobot", "password": "badpass"}, 401),
({"username": "devtable+dtrobot2", "password": None}, 200),
])
def test_login(login_data, expected_code, app, client):
if "+" in login_data['username'] and login_data['password'] is None:
username, robotname = login_data['username'].split("+")
_, login_data['password'] = model.user.create_robot(robotname, model.user.get_user(username))
app.register_blueprint(appr_bp, url_prefix='/cnr')
url = url_for('appr.login')
headers = {'Content-Type': 'application/json'}
data = {'user': {'username': 'foo', 'password': 'bar'}}
data = {'user': login_data}
rv = client.open(url, method='POST', data=json.dumps(data), headers=headers)
assert rv.status_code == 401
def test_valid_login(app, client):
app.register_blueprint(appr_bp, url_prefix='/cnr')
url = url_for('appr.login')
headers = {'Content-Type': 'application/json'}
data = {'user': {'username': 'devtable', 'password': 'password'}}
rv = client.open(url, method='POST', data=json.dumps(data), headers=headers)
assert rv.status_code == 200
assert rv.status_code == expected_code

Binary file not shown.