Add API usage tests

This commit is contained in:
Joseph Schorr 2016-04-05 15:27:45 -04:00 committed by Jimmy Zelinskie
parent 11ff3e9b59
commit fb1dca4e94
4 changed files with 194 additions and 20 deletions

View file

@ -123,7 +123,7 @@ def approve_service_key(kid, approver, approval_type, notes=''):
delete_all_notifications_by_path_prefix('/service_key_approval/{0}'.format(kid)) delete_all_notifications_by_path_prefix('/service_key_approval/{0}'.format(kid))
_gc_expired(key.service) _gc_expired(key.service)
return key
def _list_service_keys_query(kid=None, service=None, approved_only=False): def _list_service_keys_query(kid=None, service=None, approved_only=False):
query = ServiceKey.select().join(ServiceKeyApproval, JOIN_LEFT_OUTER) query = ServiceKey.select().join(ServiceKeyApproval, JOIN_LEFT_OUTER)

View file

@ -5,7 +5,6 @@ import logging
import os import os
import string import string
from calendar import timegm
from datetime import datetime from datetime import datetime
from hashlib import sha256 from hashlib import sha256
from random import SystemRandom from random import SystemRandom
@ -600,8 +599,8 @@ class SuperUserServiceKeyManagement(ApiResource):
'auto_approved': True, 'auto_approved': True,
} }
log_action('service_key_create', user.username, key_log_metadata) log_action('service_key_create', None, key_log_metadata)
log_action('service_key_approve', user.username, key_log_metadata) log_action('service_key_approve', None, key_log_metadata)
return jsonify({ return jsonify({
'kid': kid, 'kid': kid,
@ -616,7 +615,7 @@ class SuperUserServiceKeyManagement(ApiResource):
@resource('/v1/superuser/keys/<kid>') @resource('/v1/superuser/keys/<kid>')
@path_param('kid', 'The unique identifier for a service key') @path_param('kid', 'The unique identifier for a service key')
@show_if(features.SUPER_USERS) @show_if(features.SUPER_USERS)
class SuperUserServiceKeyUpdater(ApiResource): class SuperUserServiceKey(ApiResource):
""" Resource for managing service keys. """ """ Resource for managing service keys. """
schemas = { schemas = {
'PutServiceKey': { 'PutServiceKey': {
@ -640,6 +639,19 @@ class SuperUserServiceKeyUpdater(ApiResource):
}, },
} }
@verify_not_prod
@nickname('getServiceKey')
@require_scope(scopes.SUPERUSER)
def get(self, kid):
if SuperUserPermission().can():
try:
key = model.service_keys.get_service_key(kid)
return jsonify(key_view(key))
except model.service_keys.ServiceKeyDoesNotExist:
abort(404)
abort(403)
@require_fresh_login @require_fresh_login
@verify_not_prod @verify_not_prod
@nickname('updateServiceKey') @nickname('updateServiceKey')
@ -675,13 +687,13 @@ class SuperUserServiceKeyUpdater(ApiResource):
'expiration_date': expiration_date, 'expiration_date': expiration_date,
}) })
log_action('service_key_extend', user.username, key_log_metadata) log_action('service_key_extend', None, key_log_metadata)
model.service_keys.set_key_expiration(kid, expiration_date) model.service_keys.set_key_expiration(kid, expiration_date)
if 'name' in body or 'metadata' in body: if 'name' in body or 'metadata' in body:
model.service_keys.update_service_key(kid, body.get('name'), body.get('metadata')) model.service_keys.update_service_key(kid, body.get('name'), body.get('metadata'))
log_action('service_key_modify', user.username, key_log_metadata) log_action('service_key_modify', None, key_log_metadata)
return jsonify(key_view(model.service_keys.get_service_key(kid))) return jsonify(key_view(model.service_keys.get_service_key(kid)))
@ -693,7 +705,10 @@ class SuperUserServiceKeyUpdater(ApiResource):
@require_scope(scopes.SUPERUSER) @require_scope(scopes.SUPERUSER)
def delete(self, kid): def delete(self, kid):
if SuperUserPermission().can(): if SuperUserPermission().can():
key = model.service_keys.delete_service_key(kid) try:
key = model.service_keys.delete_service_key(kid)
except model.service_keys.ServiceKeyDoesNotExist:
abort(404)
key_log_metadata = { key_log_metadata = {
'kid': kid, 'kid': kid,
@ -703,9 +718,8 @@ class SuperUserServiceKeyUpdater(ApiResource):
'expiration_date': key.expiration_date, 'expiration_date': key.expiration_date,
} }
user = get_authenticated_user() log_action('service_key_delete', None, key_log_metadata)
log_action('service_key_delete', user.username, key_log_metadata) return make_response('', 204)
return make_response('', 201)
abort(403) abort(403)
@ -734,13 +748,24 @@ class SuperUserServiceKeyApproval(ApiResource):
@verify_not_prod @verify_not_prod
@nickname('approveServiceKey') @nickname('approveServiceKey')
@require_scope(scopes.SUPERUSER) @require_scope(scopes.SUPERUSER)
def put(self, kid): @validate_json_request('ApproveServiceKey')
def post(self, kid):
if SuperUserPermission().can(): if SuperUserPermission().can():
notes = request.get_json().get('notes', '') notes = request.get_json().get('notes', '')
approver = get_authenticated_user() approver = get_authenticated_user()
try: try:
model.service_keys.approve_service_key(kid, approver, ServiceKeyApprovalType.SUPERUSER, key = model.service_keys.approve_service_key(kid, approver, ServiceKeyApprovalType.SUPERUSER,
notes=notes) notes=notes)
# Log the approval of the service key.
key_log_metadata = {
'kid': kid,
'service': key.service,
'name': key.name,
'expiration_date': key.expiration_date,
}
log_action('service_key_approve', None, key_log_metadata)
except model.ServiceKeyDoesNotExist: except model.ServiceKeyDoesNotExist:
abort(404) abort(404)
except model.ServiceKeyAlreadyApproved: except model.ServiceKeyAlreadyApproved:

View file

@ -49,7 +49,7 @@ from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserMana
SuperUserSendRecoveryEmail, ChangeLog, SuperUserSendRecoveryEmail, ChangeLog,
SuperUserOrganizationManagement, SuperUserOrganizationList, SuperUserOrganizationManagement, SuperUserOrganizationList,
SuperUserAggregateLogs, SuperUserServiceKeyManagement, SuperUserAggregateLogs, SuperUserServiceKeyManagement,
SuperUserServiceKeyUpdater, SuperUserServiceKeyApproval) SuperUserServiceKey, SuperUserServiceKeyApproval)
from endpoints.api.secscan import RepositoryImageSecurity from endpoints.api.secscan import RepositoryImageSecurity
@ -3912,6 +3912,25 @@ class TestSuperUserSendRecoveryEmail(ApiTestCase):
self._run_test('POST', 404, 'devtable', None) self._run_test('POST', 404, 'devtable', None)
class TestSuperUserServiceKeyApproval(ApiTestCase):
def setUp(self):
ApiTestCase.setUp(self)
self._set_url(SuperUserServiceKeyApproval, kid=1234)
def test_post_anonymous(self):
self._run_test('POST', 401, None, {})
def test_post_freshuser(self):
self._run_test('POST', 403, 'freshuser', {})
def test_post_reader(self):
self._run_test('POST', 403, 'reader', {})
def test_post_devtable(self):
self._run_test('POST', 404, 'devtable', {})
class TestSuperUserServiceKeyManagement(ApiTestCase): class TestSuperUserServiceKeyManagement(ApiTestCase):
def setUp(self): def setUp(self):
ApiTestCase.setUp(self) ApiTestCase.setUp(self)
@ -3942,10 +3961,22 @@ class TestSuperUserServiceKeyManagement(ApiTestCase):
self._run_test('POST', 200, 'devtable', dict(service='someservice', expiration=None)) self._run_test('POST', 200, 'devtable', dict(service='someservice', expiration=None))
class TestSuperUserServiceKeyUpdater(ApiTestCase): class TestSuperUserServiceKey(ApiTestCase):
def setUp(self): def setUp(self):
ApiTestCase.setUp(self) ApiTestCase.setUp(self)
self._set_url(SuperUserServiceKeyUpdater, kid=1234) self._set_url(SuperUserServiceKey, kid=1234)
def test_get_anonymous(self):
self._run_test('GET', 403, None, None)
def test_get_freshuser(self):
self._run_test('GET', 403, 'freshuser', None)
def test_get_reader(self):
self._run_test('GET', 403, 'reader', None)
def test_get_devtable(self):
self._run_test('GET', 404, 'devtable', None)
def test_delete_anonymous(self): def test_delete_anonymous(self):
self._run_test('DELETE', 401, None, None) self._run_test('DELETE', 401, None, None)
@ -3957,7 +3988,7 @@ class TestSuperUserServiceKeyUpdater(ApiTestCase):
self._run_test('DELETE', 403, 'reader', None) self._run_test('DELETE', 403, 'reader', None)
def test_delete_devtable(self): def test_delete_devtable(self):
self._run_test('DELETE', 400, 'devtable', None) self._run_test('DELETE', 404, 'devtable', None)
def test_put_anonymous(self): def test_put_anonymous(self):
self._run_test('PUT', 401, None, {}) self._run_test('PUT', 401, None, {})

View file

@ -6,12 +6,15 @@ import logging
import re import re
import json as py_json import json as py_json
from calendar import timegm
from StringIO import StringIO from StringIO import StringIO
from urllib import urlencode from urllib import urlencode
from urlparse import urlparse, urlunparse, parse_qs from urlparse import urlparse, urlunparse, parse_qs
from playhouse.test_utils import assert_query_count, _QueryLogHandler from playhouse.test_utils import assert_query_count, _QueryLogHandler
from httmock import urlmatch, HTTMock from httmock import urlmatch, HTTMock
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.backends import default_backend
from endpoints.api import api_bp, api from endpoints.api import api_bp, api
from endpoints.building import PreparedBuild from endpoints.building import PreparedBuild
@ -20,7 +23,7 @@ from app import app, config_provider
from buildtrigger.basehandler import BuildTriggerHandler from buildtrigger.basehandler import BuildTriggerHandler
from initdb import setup_database_for_testing, finished_database_for_testing from initdb import setup_database_for_testing, finished_database_for_testing
from data import database, model from data import database, model
from data.database import RepositoryActionCount from data.database import RepositoryActionCount, LogEntry, LogEntryKind
from endpoints.api.team import TeamMember, TeamMemberList, TeamMemberInvite, OrganizationTeam from endpoints.api.team import TeamMember, TeamMemberList, TeamMemberInvite, OrganizationTeam
from endpoints.api.tag import RepositoryTagImages, RepositoryTag, RevertTag, ListRepositoryTags from endpoints.api.tag import RepositoryTagImages, RepositoryTag, RevertTag, ListRepositoryTags
@ -53,7 +56,9 @@ from endpoints.api.organization import (OrganizationList, OrganizationMember,
from endpoints.api.repository import RepositoryList, RepositoryVisibility, Repository from endpoints.api.repository import RepositoryList, RepositoryVisibility, Repository
from endpoints.api.permission import (RepositoryUserPermission, RepositoryTeamPermission, from endpoints.api.permission import (RepositoryUserPermission, RepositoryTeamPermission,
RepositoryTeamPermissionList, RepositoryUserPermissionList) RepositoryTeamPermissionList, RepositoryUserPermissionList)
from endpoints.api.superuser import SuperUserLogs, SuperUserList, SuperUserManagement from endpoints.api.superuser import (SuperUserLogs, SuperUserList, SuperUserManagement,
SuperUserServiceKeyManagement, SuperUserServiceKey,
SuperUserServiceKeyApproval)
from endpoints.api.secscan import RepositoryImageSecurity from endpoints.api.secscan import RepositoryImageSecurity
from endpoints.api.suconfig import (SuperUserRegistryStatus, SuperUserConfig, SuperUserConfigFile, from endpoints.api.suconfig import (SuperUserRegistryStatus, SuperUserConfig, SuperUserConfigFile,
SuperUserCreateInitialSuperUser) SuperUserCreateInitialSuperUser)
@ -3554,6 +3559,119 @@ class TestRepositoryImageSecurity(ApiTestCase):
self.assertEquals(1, response['data']['Layer']['IndexedByVersion']) self.assertEquals(1, response['data']['Layer']['IndexedByVersion'])
class TestSuperUserKeyManagement(ApiTestCase):
def test_get_update_keys(self):
self.login(ADMIN_ACCESS_USER)
json = self.getJsonResponse(SuperUserServiceKeyManagement)
self.assertEquals(3, len(json['keys']))
key = json['keys'][0]
self.assertTrue('name' in key)
self.assertTrue('service' in key)
self.assertTrue('kid' in key)
self.assertTrue('created_date' in key)
self.assertTrue('expiration_date' in key)
self.assertTrue('jwk' in key)
self.assertTrue('approval' in key)
self.assertTrue('metadata' in key)
# Update the key's name.
self.putJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']),
data=dict(name='somenewname'))
# Ensure the key's name has been changed.
json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']))
self.assertEquals('somenewname', json['name'])
# Ensure a log was added for the modification.
kind = LogEntryKind.get(LogEntryKind.name == 'service_key_modify')
self.assertEquals(1, model.log.LogEntry.select().where(LogEntry.kind == kind).count())
# Update the key's metadata.
self.putJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']),
data=dict(metadata=dict(foo='bar')))
# Ensure the key's metadata has been changed.
json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']))
self.assertEquals('bar', json['metadata']['foo'])
# Ensure a log was added for the modification.
kind = LogEntryKind.get(LogEntryKind.name == 'service_key_modify')
self.assertEquals(2, model.log.LogEntry.select().where(LogEntry.kind == kind).count())
# Change the key's expiration.
self.putJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']),
data=dict(expiration=None))
# Ensure the key's expiration has been changed.
json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=key['kid']))
self.assertIsNone(json['expiration_date'])
# Ensure a log was added for the modification.
kind = LogEntryKind.get(LogEntryKind.name == 'service_key_extend')
self.assertEquals(1, model.log.LogEntry.select().where(LogEntry.kind == kind).count())
# Delete the key.
self.deleteResponse(SuperUserServiceKey, params=dict(kid=key['kid']))
# Ensure the key no longer exists.
self.getResponse(SuperUserServiceKey, params=dict(kid=key['kid']), expected_code=404)
json = self.getJsonResponse(SuperUserServiceKeyManagement)
self.assertEquals(2, len(json['keys']))
# Ensure a log was added for the deletion.
kind = LogEntryKind.get(LogEntryKind.name == 'service_key_delete')
self.assertEquals(1, model.log.LogEntry.select().where(LogEntry.kind == kind).count())
def test_create_key(self):
self.login(ADMIN_ACCESS_USER)
kind = LogEntryKind.get(LogEntryKind.name == 'service_key_create')
existing_log_count = model.log.LogEntry.select().where(LogEntry.kind == kind).count()
new_key = {
'service': 'coolservice',
'name': 'mynewkey',
'metadata': dict(foo='baz'),
'notes': 'whazzup!?',
'expiration': timegm((datetime.datetime.now() + datetime.timedelta(days=1)).utctimetuple()),
}
# Create the key.
json = self.postJsonResponse(SuperUserServiceKeyManagement, data=new_key)
self.assertEquals('mynewkey', json['name'])
self.assertTrue('kid' in json)
self.assertTrue('public_key' in json)
self.assertTrue('private_key' in json)
# Verify the private key is a valid PEM.
serialization.load_pem_private_key(json['private_key'].encode('utf-8'), None, default_backend())
# Verify the key.
kid = json['kid']
json = self.getJsonResponse(SuperUserServiceKey, params=dict(kid=kid))
self.assertEquals('mynewkey', json['name'])
self.assertEquals('coolservice', json['service'])
self.assertEquals('baz', json['metadata']['foo'])
self.assertEquals(kid, json['kid'])
self.assertIsNotNone(json['approval'])
self.assertEquals('ServiceKeyApprovalType.SUPERUSER', json['approval']['approval_type'])
self.assertEquals(ADMIN_ACCESS_USER, json['approval']['approver']['username'])
self.assertEquals('whazzup!?', json['approval']['notes'])
# Ensure that there are logs for the creation and auto-approval.
kind = LogEntryKind.get(LogEntryKind.name == 'service_key_create')
self.assertEquals(existing_log_count + 1, model.log.LogEntry.select().where(LogEntry.kind == kind).count())
kind = LogEntryKind.get(LogEntryKind.name == 'service_key_approve')
self.assertEquals(existing_log_count + 1, model.log.LogEntry.select().where(LogEntry.kind == kind).count())
class TestSuperUserManagement(ApiTestCase): class TestSuperUserManagement(ApiTestCase):
def test_get_user(self): def test_get_user(self):
self.login(ADMIN_ACCESS_USER) self.login(ADMIN_ACCESS_USER)