1
0
mirror of https://github.com/quay/quay.git synced 2026-01-26 06:21:37 +03:00
Files
quay/endpoints/v2/test/test_v2auth.py
Ryan Wallace a06cc6fa43 chore: update all black versions to 24.4.2 and run make black (#4754)
* chore(pre-commit): match black version with requirements-dev

* run `make black` against repo

* ci: switch to black 24.4.2

* fix: py312

* fix: flake8 errors

* fix: flake8 conflicts

* chore: add git blame ignore revs file
2025-12-19 11:29:53 -06:00

415 lines
11 KiB
Python

import pytest
from flask import url_for
from app import app as original_app
from app import instance_keys
from data import model
from data.model.user import get_robot_and_metadata, get_user
from endpoints.test.shared import conduct_call, gen_basic_auth
from test.fixtures import *
from util.security.registry_jwt import CLAIM_TUF_ROOTS, decode_bearer_token
def get_robot_password(username):
parent_name, robot_shortname = username.split("+", 1)
parent = get_user(parent_name)
_, token, _ = get_robot_and_metadata(robot_shortname, parent)
return token
@pytest.mark.parametrize(
"scope, username, password, expected_code, expected_scopes, push_private, visibility, org_create",
[
# Invalid repository.
(
"repository:devtable/simple/foo/bar/baz:pull",
"devtable",
"password",
400 if not original_app.config["FEATURE_EXTENDED_REPOSITORY_NAMES"] else 200,
(
[]
if not original_app.config["FEATURE_EXTENDED_REPOSITORY_NAMES"]
else ["devtable/simple/foo/bar/baz:pull"]
),
True,
"private",
False,
),
# Invalid scopes.
("some_invalid_scope", "devtable", "password", 400, [], True, "private", False),
# Invalid credentials.
("repository:devtable/simple:pull", "devtable", "invalid", 401, [], True, "private", False),
# Valid credentials.
(
"repository:devtable/simple:pull",
"devtable",
"password",
200,
["devtable/simple:pull"],
True,
"private",
False,
),
(
"repository:devtable/simple:push",
"devtable",
"password",
200,
["devtable/simple:push"],
True,
"private",
False,
),
(
"repository:devtable/simple:pull,push",
"devtable",
"password",
200,
["devtable/simple:push,pull"],
True,
"private",
False,
),
(
"repository:devtable/simple:pull,push,*",
"devtable",
"password",
200,
["devtable/simple:push,pull,*"],
True,
"private",
False,
),
(
"repository:buynlarge/orgrepo:pull,push,*",
"devtable",
"password",
200,
["buynlarge/orgrepo:push,pull,*"],
True,
"private",
False,
),
("", "devtable", "password", 200, [], True, "private", False),
# No credentials, non-public repo.
(
"repository:devtable/simple:pull",
None,
None,
200,
["devtable/simple:"],
True,
"private",
False,
),
# No credentials, public repo.
(
"repository:public/publicrepo:pull",
None,
None,
200,
["public/publicrepo:pull"],
True,
"private",
False,
),
# Reader only.
(
"repository:buynlarge/orgrepo:pull,push,*",
"reader",
"password",
200,
["buynlarge/orgrepo:pull"],
True,
"private",
False,
),
# Unknown repository.
(
"repository:devtable/unknownrepo:pull,push",
"devtable",
"password",
200,
["devtable/unknownrepo:push,pull"],
True,
"private",
False,
),
# Unknown repository in another namespace.
(
"repository:somenamespace/unknownrepo:pull,push",
"devtable",
"password",
200,
["somenamespace/unknownrepo:"],
True,
"private",
False,
),
# Disabled namespace.
(
["repository:devtable/simple:pull,push", "repository:disabled/complex:pull"],
"devtable",
"password",
405,
[],
True,
"private",
False,
),
# Multiple scopes.
(
["repository:devtable/simple:pull,push", "repository:devtable/complex:pull"],
"devtable",
"password",
200,
["devtable/simple:push,pull", "devtable/complex:pull"],
True,
"private",
False,
),
# Multiple scopes - non-superuser without push access to one repo
(
["repository:buynlarge/orgrepo:pull,push", "repository:devtable/simple:pull,push"],
"reader",
"password",
200,
["buynlarge/orgrepo:pull", "devtable/simple:"],
True,
"private",
False,
),
# Multiple scopes - devtable is a superuser so gets full access to both repos
(
["repository:devtable/simple:pull,push", "repository:public/publicrepo:pull,push"],
"devtable",
"password",
200,
["devtable/simple:push,pull", "public/publicrepo:push,pull"],
True,
"private",
False,
),
(
["repository:devtable/simple:pull,push,*", "repository:public/publicrepo:pull,push,*"],
"devtable",
"password",
200,
["devtable/simple:push,pull,*", "public/publicrepo:push,pull,*"],
True,
"private",
False,
),
# Read Only State
(
"repository:devtable/readonly:pull,push,*",
"devtable",
"password",
200,
["devtable/readonly:pull"],
True,
"private",
False,
),
# Mirror State as a typical User
(
"repository:devtable/mirrored:pull,push,*",
"devtable",
"password",
200,
["devtable/mirrored:pull"],
True,
"private",
False,
),
# Mirror State as the robot User should have write access
(
"repository:devtable/mirrored:pull,push,*",
"devtable+dtrobot",
get_robot_password,
200,
["devtable/mirrored:push,pull"],
True,
"private",
False,
),
# Organization repository, org admin
(
"repository:buynlarge/orgrepo:pull,push,*",
"devtable",
"password",
200,
["buynlarge/orgrepo:push,pull,*"],
True,
"private",
False,
),
# Organization repository, org creator
(
"repository:buynlarge/orgrepo:pull,push,*",
"creator",
"password",
200,
["buynlarge/orgrepo:"],
True,
"private",
False,
),
# Organization repository, org reader
(
"repository:buynlarge/orgrepo:pull,push,*",
"reader",
"password",
200,
["buynlarge/orgrepo:pull"],
True,
"private",
False,
),
# Proxy organization repository, org creator, repository does not exist
(
"repository:proxyorg/idonotexist:pull",
"member",
"password",
200,
["proxyorg/idonotexist:pull"],
True,
"private",
False,
),
# Organization repository, freshuser
(
"repository:buynlarge/orgrepo:pull,push,*",
"freshuser",
"password",
200,
["buynlarge/orgrepo:"],
True,
"private",
False,
),
# Pushed repository created private
(
"repository:devtable/visibility:pull,push,*",
"devtable",
"password",
200,
["devtable/visibility:push,pull,*"],
True,
"private",
False,
),
# Pushed repository created public
(
"repository:devtable/visibility:pull,push,*",
"devtable",
"password",
200,
["devtable/visibility:push,pull,*"],
False,
"public",
False,
),
# Push to existing user namespace
(
"repository:devtable/visibility:pull,push,*",
"devtable",
"password",
200,
["devtable/visibility:push,pull,*"],
True,
"public",
True,
),
# Pushed namespace created
(
"repository:neworg/visibility:pull,push,*",
"devtable",
"password",
200,
["neworg/visibility:push,pull,*"],
False,
"public",
True,
),
# Pushed namespace bad name
(
"repository:NewOrg/visibility:pull,push,*",
"devtable",
"password",
405,
["NewOrg/visibility:push,pull,*"],
False,
"public",
True,
),
],
)
def test_generate_registry_jwt(
scope,
username,
password,
expected_code,
expected_scopes,
push_private,
visibility,
org_create,
app,
client,
):
params = {
"service": original_app.config["SERVER_HOSTNAME"],
"scope": scope,
}
if callable(password):
password = password(username)
headers = {}
if username and password:
headers["Authorization"] = gen_basic_auth(username, password)
original_app.config["CREATE_PRIVATE_REPO_ON_PUSH"] = push_private
original_app.config["CREATE_NAMESPACE_ON_PUSH"] = org_create
resp = conduct_call(
client,
"v2.generate_registry_jwt",
url_for,
"GET",
params,
{},
expected_code,
headers=headers,
)
if expected_code != 200:
return
token = resp.json["token"]
decoded = decode_bearer_token(token, instance_keys, original_app.config)
assert decoded["iss"] == "quay"
assert decoded["aud"] == original_app.config["SERVER_HOSTNAME"]
assert decoded["sub"] == username if username else "(anonymous)"
expected_access = []
for scope in expected_scopes:
name, actions_str = scope.split(":")
actions = actions_str.split(",") if actions_str else []
expected_access.append(
{
"type": "repository",
"name": name,
"actions": actions,
}
)
assert decoded["access"] == expected_access
assert len(decoded["context"][CLAIM_TUF_ROOTS]) == len(expected_scopes)
# Test visibility
if scope == "repository:devtable/visibility:pull,push,*":
assert (
model.repository.get_repository("devtable", "visibility").visibility.name == visibility
)