Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/asfquart/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def requirements_to_iter(args: typing.Any):
args = [args]
# Test that each requirement is an allowed one (belongs to the Requirements class)
for req in args:
if not callable(req) or req != getattr(Requirements, req.__name__, None):
if not callable(req) or not issubclass(req.__self__, Requirements):
raise TypeError(
f"Authentication requirement {req} is not valid. Must belong to the asfquart.auth.Requirements class."
)
Expand Down
91 changes: 73 additions & 18 deletions tests/auth.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,47 @@
#!/usr/bin/env python3

import time
import re

import pytest
import quart

import asfquart.auth
from asfquart.auth import Requirements as R

class MyR(R):
"""Test auth methods in a Requirements subclass"""

E_ALWAYS_FALSE = "Always False"

@classmethod
def true(cls, _session):
return True, ""

@classmethod
def false(cls, _session):
return False, cls.E_ALWAYS_FALSE

class LoneR():
"""Test auth methods in an independent class"""

E_ALWAYS_FALSE = "Always False"

@classmethod
def true(cls, _session):
return True, ""

@classmethod
def false(cls, _session):
return False, cls.E_ALWAYS_FALSE

def _string_to_re(s):
"""convert arbitrary string to fullmatch regex"""
return re.escape(s) + '$'

def _string_to_re(s):
"""convert arbitrary string to fullmatch regex"""
return re.escape(s) + '$'

@pytest.mark.auth
async def test_auth_basics():
Expand All @@ -20,10 +54,8 @@ async def requires_session():

# Test with no session, should fail
quart.session = {}
try:
with pytest.raises(asfquart.auth.AuthenticationFailed, match=_string_to_re(R.E_NOT_LOGGED_IN)):
await requires_session()
except asfquart.auth.AuthenticationFailed as e:
assert e.message is R.E_NOT_LOGGED_IN

# Test with session, should work.
quart.session = {app.app_id: {"uts": time.time(), "foo": "bar"}}
Expand Down Expand Up @@ -53,17 +85,13 @@ async def requires_mfa():

# Test MFA with no session, should fail exactly like auth_required
quart.session = {}
try:
with pytest.raises(asfquart.auth.AuthenticationFailed, match=_string_to_re(R.E_NOT_LOGGED_IN)):
await requires_mfa()
except asfquart.auth.AuthenticationFailed as e:
assert e.message is R.E_NOT_LOGGED_IN

# Test with session without MFA, should fail.
quart.session = {app.app_id: {"uts": time.time(), "foo": "bar"}}
try:
with pytest.raises(asfquart.auth.AuthenticationFailed, match=_string_to_re(R.E_NO_MFA)):
await requires_mfa()
except asfquart.auth.AuthenticationFailed as e:
assert e.message is R.E_NO_MFA

# Test with session with MFA, should work.
quart.session = {app.app_id: {"uts": time.time(), "foo": "bar", "mfa": True}}
Expand Down Expand Up @@ -95,27 +123,21 @@ async def test_member_or_chair_auth():

# Test role with no session, should fail exactly like auth_required
quart.session = {}
try:
with pytest.raises(asfquart.auth.AuthenticationFailed, match=_string_to_re(R.E_NOT_LOGGED_IN)):
await test_committer_auth()
except asfquart.auth.AuthenticationFailed as e:
assert e.message is R.E_NOT_LOGGED_IN

# Test with session , should work
quart.session = {app.app_id: {"uts": time.time(), "foo": "bar"}}
await test_committer_auth()

# Test with a role we don't have, should fail
try:
with pytest.raises(asfquart.auth.AuthenticationFailed, match=_string_to_re(R.E_NOT_MEMBER)):
await test_member_auth()
except asfquart.auth.AuthenticationFailed as e:
assert e.message is R.E_NOT_MEMBER

# Test with for both member and chair, while only being member. should pass on member check, fail on chair
quart.session = {app.app_id: {"uts": time.time(), "foo": "bar", "isMember": True}}
try:
with pytest.raises(asfquart.auth.AuthenticationFailed, match=_string_to_re(R.E_NOT_CHAIR)):
await test_member_and_chair_auth()
except asfquart.auth.AuthenticationFailed as e:
assert e.message is R.E_NOT_CHAIR

# Test for either member of chair, should work as we have chair (but not member)
quart.session = {app.app_id: {"uts": time.time(), "foo": "bar", "isChair": True}}
Expand All @@ -124,3 +146,36 @@ async def test_member_or_chair_auth():
# Test for both member and chair, when we are both. should work.
quart.session = {app.app_id: {"uts": time.time(), "foo": "bar", "isMember": True, "isChair": True}}
await test_member_and_chair_auth()

@pytest.mark.auth
async def test_extended_auth():
"""Extended auth tests"""

@asfquart.auth.require(MyR.true)
async def test_true():
pass

@asfquart.auth.require(MyR.false)
async def test_false():
pass

# Should always work
await test_true()

with pytest.raises(asfquart.auth.AuthenticationFailed, match=_string_to_re(MyR.E_ALWAYS_FALSE)):
await test_false()

@pytest.mark.auth
async def test_lone_auth():
"""Extended auth tests using independent class"""

# cannot use independent class as a decorator
with pytest.raises(TypeError):
@asfquart.auth.require(LoneR.true)
async def test_true():
pass

with pytest.raises(TypeError):
@asfquart.auth.require(LoneR.false)
async def test_false():
pass