add simple authz tests

pull/7/head
Max Erenberg 1 year ago
parent 26fd8f6f68
commit 490abb302c
  1. 1
      .gitignore
  2. 3
      ceo_common/krb5/utils.py
  3. 20
      tests/ceod/api/test_members.py
  4. 55
      tests/conftest_ceod_api.py

1
.gitignore vendored

@ -1,4 +1,5 @@
__pycache__/
*.pyc
/venv/
.vscode/
/cred

@ -93,7 +93,8 @@ def get_fwd_tgt(server: str, cache_name: Union[str, None] = None) -> bytes:
e.g. 'ceod/phosphoric-acid.csclub.uwaterloo.ca'.
If `cache_name` is None, the default cache will be used; otherwise,
the cache with that name will be used.
the cache with that name will be used. Must have the format
'TYPE:residual'.
"""
if cache_name is None:
cache_function = get_krb5_cc_default

@ -80,7 +80,7 @@ def test_api_next_uid(cfg, client, create_user_result):
def test_api_get_user(cfg, client, create_user_result):
old_data = create_user_result
old_data = create_user_result.copy()
uid = old_data['uid']
del old_data['password']
@ -145,7 +145,7 @@ def test_api_patch_user(client, create_user_result):
def test_api_renew_user(cfg, client, create_user_result, ldap_conn):
data = create_user_result
data = create_user_result.copy()
uid = data['uid']
old_terms = data['terms']
old_non_member_terms = data.get('non_member_terms', [])
@ -188,3 +188,19 @@ def test_api_reset_password(client, create_user_result):
status, data = client.post(f'/api/members/{uid}/pwreset')
assert status == 200
assert data['password'] == 'krb5'
def test_authz_check(client, create_user_result):
# non-staff members may not create users
status, data = client.post('/api/members', json={
'uid': 'test_1', 'cn': 'Test One', 'terms': ['s2021'],
}, principal='regular1')
assert status == 403
# non-syscom members may not see forwarding addresses
old_data = create_user_result.copy()
uid = old_data['uid']
del old_data['password']
del old_data['forwarding_addresses']
_, data = client.get(f'/api/members/{uid}', principal='regular1')
assert data == old_data

@ -1,4 +1,6 @@
from base64 import b64encode
import contextlib
import os
import json
import socket
import subprocess
@ -29,18 +31,28 @@ class CeodTestClient:
self.syscom_principal = 'ctdalek'
# this is only used for the HTTPSNEGOAuth
self.base_url = f'http://{socket.getfqdn()}'
# keep a list of all of the principals for which we acquired a TGT
self.principals = []
# for each principal for which we acquired a TGT, map their
# username to a file (ccache) storing their TGT
self.principal_ccaches = {}
# this is where we'll store the credentials for each principal
self.ccache = 'DIR:' + cache_dir
self.cache_dir = cache_dir
@contextlib.contextmanager
def krb5ccname_env(self, principal):
"""Temporarily change KRB5CCNAME to the ccache of the principal."""
old_krb5ccname = os.environ['KRB5CCNAME']
os.environ['KRB5CCNAME'] = self.principal_ccaches[principal]
try:
yield
finally:
os.environ['KRB5CCNAME'] = old_krb5ccname
def get_auth(self, principal):
"""Acquire a HTTPSPNEGOAuth instance for the principal."""
name = gssapi.Name(principal)
creds = gssapi.Credentials(
name=name,
usage='initiate',
store={'ccache': self.ccache},
)
# the 'store' arg doesn't seem to work for DIR ccaches
with self.krb5ccname_env(principal):
creds = gssapi.Credentials(name=name, usage='initiate')
auth = HTTPSPNEGOAuth(
opportunistic_auth=True,
target_name='ceod',
@ -48,23 +60,30 @@ class CeodTestClient:
)
return auth
def kinit(self, principal):
"""Acquire an initial TGT for the principal."""
# For some reason, kinit with the '-c' option deletes the other
# credentials in the cache collection, so we need to override the
# env variable
subprocess.run(
['kinit', principal],
text=True, input='krb5', check=True, stdout=subprocess.DEVNULL,
env={'KRB5CCNAME': self.principal_ccaches[principal]})
def get_headers(self, principal):
if principal not in self.principals:
# Acquire the initial TGT
subprocess.run(
['kinit', '-c', self.ccache, principal],
text=True, input='krb5',
check=True, stdout=subprocess.DEVNULL)
self.principals.append(principal)
if principal not in self.principal_ccaches:
_, filename = tempfile.mkstemp(dir=self.cache_dir)
self.principal_ccaches[principal] = filename
self.kinit(principal)
# Get the Authorization header (SPNEGO).
# The method doesn't matter here because we just need to extract
# the header using req.prepare().
req = Request('GET', self.base_url, auth=self.get_auth(principal))
headers = list(req.prepare().headers.items())
# Get the X-KRB5-CRED header (forwarded TGT).
cred = b64encode(
get_fwd_tgt('ceod/' + socket.getfqdn(), self.ccache)
).decode()
cred = b64encode(get_fwd_tgt(
'ceod/' + socket.getfqdn(), self.principal_ccaches[principal]
)).decode()
headers.append(('X-KRB5-CRED', cred))
return headers

Loading…
Cancel
Save