Let key-less PCs ask to enroll, and let an admin retry a trust-blocked PC
Found in the Windows VM re-test. - A PC installed without the shared key got 401 from POST /api/endpoints/enroll while the shared key was allowed, so no request was recorded and no admin could approve it. Enroll now never refuses: a valid shared key only decides auto-issue on a clean match; no key or an invalid key creates or updates the one pending request for that hostname and serial. An approved request still issues only to its own nonce. - A PC blocked with "server certificate not trusted by this PC" could never be queued again after trust was fixed, because the blocker comes from its last failed upgrade. Naming the PC in a wave now queues it when trust is its only blocker, and the response lists it under "retried"; "next n" still skips it. The Migration table lets such a PC be selected. - counts.pending on GET /enrollment counts pending requests, including ones for unknown hostnames; counts.pendingcomputers keeps the per-computer figure.
This commit is contained in:
@@ -130,11 +130,62 @@ def test_enroll_validates_the_nonce(client, db, app, keyheaders):
|
||||
assert response.status_code == 400, nonce
|
||||
|
||||
|
||||
def test_enroll_requires_the_shared_key_while_it_is_allowed(client, db, app,
|
||||
keyheaders):
|
||||
_computer(db, 'BAY-01')
|
||||
response = client.post(ENROLL_URL, json=_enroll_body())
|
||||
assert response.status_code in (401, 500)
|
||||
@pytest.mark.parametrize('headers', [
|
||||
{},
|
||||
{'X-API-Key': 'not-the-key'},
|
||||
])
|
||||
def test_enroll_without_a_valid_key_records_a_pending_request(
|
||||
client, db, app, keyheaders, auth_headers, caplog, headers):
|
||||
from plugins.endpoints.models import EndpointCredential, EndpointEnrollRequest
|
||||
|
||||
assetid = _computer(db, 'BAY-01')
|
||||
response = _enroll(client, headers)
|
||||
|
||||
assert response.status_code == 202, response.get_data(as_text=True)
|
||||
data = response.get_json()['data']
|
||||
assert data['status'] == 'pending'
|
||||
assert data['reason'] == 'awaiting approval'
|
||||
assert EndpointCredential.query.filter_by(assetid=assetid).first() is None
|
||||
assert ('invalid API key' in caplog.text) == bool(headers)
|
||||
|
||||
# Retrying updates the same row; it never piles up rows.
|
||||
assert _enroll(client, headers).status_code == 202
|
||||
assert EndpointEnrollRequest.query.count() == 1
|
||||
|
||||
listing = client.get('/api/endpoints/enrollment',
|
||||
headers=auth_headers).get_json()['data']
|
||||
assert [(r['requestid'], r['status']) for r in listing['requests']] == [
|
||||
(data['requestid'], 'pending')]
|
||||
assert listing['counts']['pending'] == 1
|
||||
|
||||
|
||||
def test_enroll_with_no_key_for_an_unknown_hostname_is_pending(
|
||||
client, db, app, keyheaders):
|
||||
response = _enroll(client, {}, hostname='NEW-PC')
|
||||
assert response.status_code == 202, response.get_data(as_text=True)
|
||||
assert response.get_json()['data']['reason'] == 'unknown hostname'
|
||||
|
||||
|
||||
def test_approved_request_issues_to_its_nonce_without_a_key(
|
||||
client, db, app, keyheaders, auth_headers):
|
||||
from plugins.endpoints.models import EndpointCredential
|
||||
|
||||
assetid = _computer(db, 'BAY-01')
|
||||
pending = _enroll(client, {})
|
||||
requestid = pending.get_json()['data']['requestid']
|
||||
approve = client.post(
|
||||
f'/api/endpoints/enrollment/requests/{requestid}/approve',
|
||||
json={'assetid': assetid}, headers=auth_headers)
|
||||
assert approve.status_code == 200, approve.get_data(as_text=True)
|
||||
|
||||
wrong = _enroll(client, {}, nonce=OTHER_NONCE)
|
||||
assert wrong.status_code == 403
|
||||
assert _code(wrong) == 'ENROLL_NONCE_MISMATCH'
|
||||
|
||||
issued = _enroll(client, {})
|
||||
assert issued.status_code == 201, issued.get_data(as_text=True)
|
||||
assert EndpointCredential.query.filter_by(assetid=assetid).one() \
|
||||
.enrolledvia == 'approved'
|
||||
|
||||
|
||||
def test_a_second_nonce_on_a_pending_request_stays_pending(client, db, app,
|
||||
@@ -638,7 +689,8 @@ def test_enrollment_listing_and_settings(client, db, app, keyheaders,
|
||||
body = response.get_json()
|
||||
data = body['data']
|
||||
assert data['settings'] == {'sharedkeyallowed': True, 'autoenroll': True}
|
||||
assert data['counts'] == {'enrolled': 1, 'notenrolled': 1, 'pending': 1}
|
||||
assert data['counts'] == {'enrolled': 1, 'notenrolled': 1,
|
||||
'pendingcomputers': 1, 'pending': 1}
|
||||
states = {row['hostname']: row['state'] for row in data['computers']}
|
||||
assert states == {'BAY-01': 'enrolled', 'BAY-02': 'notenrolled',
|
||||
'BAY-03': 'pending'}
|
||||
@@ -663,6 +715,23 @@ def test_enrollment_listing_and_settings(client, db, app, keyheaders,
|
||||
assert bad.status_code == 400
|
||||
|
||||
|
||||
def test_enrollment_pending_count_includes_unmatched_requests(
|
||||
client, db, app, keyheaders, auth_headers):
|
||||
_computer(db, 'BAY-01', serialnumber=None)
|
||||
_computer(db, 'BAY-02', serialnumber='SER-2')
|
||||
_enroll(client, keyheaders, hostname='BAY-01', serialnumber='X')
|
||||
_enroll(client, {}, hostname='NOT-IN-SHOPDB', serialnumber='Y')
|
||||
|
||||
data = client.get('/api/endpoints/enrollment',
|
||||
headers=auth_headers).get_json()['data']
|
||||
pendingrequests = [r for r in data['requests'] if r['status'] == 'pending']
|
||||
assert len(pendingrequests) == 2
|
||||
assert data['counts'] == {'enrolled': 0, 'notenrolled': 1,
|
||||
'pendingcomputers': 1, 'pending': 2}
|
||||
states = [row['state'] for row in data['computers']]
|
||||
assert states.count('pending') == data['counts']['pendingcomputers']
|
||||
|
||||
|
||||
def test_approve_refuses_an_enrolled_computer(client, db, app, keyheaders,
|
||||
auth_headers):
|
||||
assetid = _computer(db, 'BAY-01')
|
||||
|
||||
Reference in New Issue
Block a user