Adding Functional Tests and Supporting Fixes for Global Preferred CAs

This commit containers the following changes to fix the global
preferred certificate authority operations.

- Add functional tests for global preferred CA operations
- Change cas/CA_ID/unset-global-preferred to cas/unset-global-preferred
- Allow all content types for set-global-preferred and unset-global-preferred
- Add create_or_update_from_project_id() for PreferredCertificateAuthority
  table and refactor
- Make reserved global project id an external project id
- Use hard delete for PreferredCertificateAuthority entities

Change-Id: I56fbdb0246f3159444778cd37d0080f771eb00f6
Partially-implements: blueprint add-cas
APIImpact:
This commit is contained in:
Dave McCowan 2015-09-19 01:25:12 -04:00
parent b06abac781
commit 78abb2e99b
11 changed files with 194 additions and 75 deletions

View File

@ -76,9 +76,8 @@ class CertificateAuthorityController(controllers.ACLMixin):
route_table = {
'add-to-project': self.add_to_project,
'remove-from-project': self.remove_from_project,
'set-preferred': self.set_preferred,
'set-global-preferred': self.set_global_preferred,
'unset-global-preferred': self.unset_global_preferred,
'set-preferred': self.set_preferred,
}
if name in route_table:
return route_table[name]
@ -228,17 +227,11 @@ class CertificateAuthorityController(controllers.ACLMixin):
if not project_ca:
_requested_preferred_ca_not_a_project_ca()
preferred_ca = self.preferred_ca_repo.get_project_entities(
project_model.id)
if preferred_ca is not None:
self.preferred_ca_repo.update_preferred_ca(project_model.id,
self.ca)
else:
preferred_ca = models.PreferredCertificateAuthority(
project_model.id, self.ca.id)
self.preferred_ca_repo.create_from(preferred_ca)
self.preferred_ca_repo.create_or_update_by_project_id(
project_model.id, self.ca.id)
@pecan.expose()
@utils.allow_all_content_types
@controllers.handle_exceptions(u._('Set global preferred CA'))
@controllers.enforce_rbac('certificate_authority:set_global_preferred')
def set_global_preferred(self, external_project_id):
@ -246,31 +239,9 @@ class CertificateAuthorityController(controllers.ACLMixin):
pecan.abort(405)
LOG.debug("== Set global preferred CA %s", self.ca.id)
pref_ca = self.preferred_ca_repo.get_global_preferred_ca()
if pref_ca is None:
global_preferred_ca = models.PreferredCertificateAuthority(
self.preferred_ca_repo.PREFERRED_PROJECT_ID,
self.ca.id)
self.preferred_ca_repo.create_from(global_preferred_ca)
else:
self.preferred_ca_repo.update_global_preferred_ca(self.ca)
@pecan.expose()
@controllers.handle_exceptions(u._('Unset global preferred CA'))
@controllers.enforce_rbac('certificate_authority:unset_global_preferred')
def unset_global_preferred(self, external_project_id):
if pecan.request.method != 'POST':
pecan.abort(405)
LOG.debug("== Unsetting global preferred CA")
self._remove_global_preferred_ca(external_project_id)
def _remove_global_preferred_ca(self, external_project_id):
global_preferred_ca = self.preferred_ca_repo.get_project_entities(
self.preferred_ca_repo.PREFERRED_PROJECT_ID)
if global_preferred_ca:
self.preferred_ca_repo.delete_entity_by_id(
global_preferred_ca[0].id,
external_project_id)
project = res.get_or_create_global_preferred_project()
self.preferred_ca_repo.create_or_update_by_project_id(
project.id, self.ca.id)
@index.when(method='DELETE')
@utils.allow_all_content_types
@ -299,7 +270,8 @@ class CertificateAuthoritiesController(controllers.ACLMixin):
route_table = {
'all': self.get_all,
'global-preferred': self.get_global_preferred,
'preferred': self.preferred
'preferred': self.preferred,
'unset-global-preferred': self.unset_global_preferred,
}
if name in route_table:
return route_table[name]
@ -437,13 +409,30 @@ class CertificateAuthoritiesController(controllers.ACLMixin):
def get_global_preferred(self, external_project_id, **kw):
LOG.debug('Start certificate_authorities get_global_preferred CA')
pref_ca = self.preferred_ca_repo.get_global_preferred_ca()
pref_ca = cert_resources.get_global_preferred_ca()
if not pref_ca:
pecan.abort(404, u._("No global preferred CA defined"))
ca = self.ca_repo.get(entity_id=pref_ca.ca_id)
return ca.to_dict_fields()
@pecan.expose()
@utils.allow_all_content_types
@controllers.handle_exceptions(u._('Unset global preferred CA'))
@controllers.enforce_rbac('certificate_authorities:unset_global_preferred')
def unset_global_preferred(self, external_project_id):
if pecan.request.method != 'POST':
pecan.abort(405)
LOG.debug("== Unsetting global preferred CA")
self._remove_global_preferred_ca(external_project_id)
def _remove_global_preferred_ca(self, external_project_id):
global_preferred_ca = cert_resources.get_global_preferred_ca()
if global_preferred_ca:
self.preferred_ca_repo.delete_entity_by_id(
global_preferred_ca.id,
external_project_id)
@pecan.expose(generic=True, template='json')
@utils.allow_all_content_types
@controllers.handle_exceptions(u._('Retrieve project preferred CA'))

View File

@ -23,6 +23,12 @@ from barbican.model import repositories
LOG = utils.getLogger(__name__)
GLOBAL_PREFERRED_PROJECT_ID = "GLOBAL_PREFERRED"
def get_or_create_global_preferred_project():
return get_or_create_project(GLOBAL_PREFERRED_PROJECT_ID)
def get_or_create_project(project_id):
"""Returns project with matching project_id.

View File

@ -982,7 +982,7 @@ class ProjectCertificateAuthority(BASE, SoftDeleteMixIn, ModelBase):
'ca_id': self.ca_id}
class PreferredCertificateAuthority(BASE, SoftDeleteMixIn, ModelBase):
class PreferredCertificateAuthority(BASE, ModelBase):
"""Stores preferred CAs for any project.
Admins can define a set of CAs available for issuance requests for

View File

@ -1629,9 +1629,11 @@ class ProjectCertificateAuthorityRepo(BaseRepo):
class PreferredCertificateAuthorityRepo(BaseRepo):
"""Repository for the PreferredCertificateAuthority entity."""
"""Repository for the PreferredCertificateAuthority entity.
PREFERRED_PROJECT_ID = "0"
PreferredCertificateAuthority entries are not soft delete. So there is no
need to have deleted=False filter in queries.
"""
def get_by_create_date(self, offset_arg=None, limit_arg=None,
project_id=None, ca_id=None,
@ -1648,7 +1650,6 @@ class PreferredCertificateAuthorityRepo(BaseRepo):
query = session.query(models.PreferredCertificateAuthority)
query = query.order_by(models.PreferredCertificateAuthority.created_at)
query = query.filter_by(deleted=False)
if project_id:
query = query.filter(
@ -1672,22 +1673,26 @@ class PreferredCertificateAuthorityRepo(BaseRepo):
return entities, offset, limit, total
def get_global_preferred_ca(self):
pref_cas = self.get_project_entities(self.PREFERRED_PROJECT_ID)
if len(pref_cas) > 0:
return pref_cas[0]
return None
def create_or_update_by_project_id(self, project_id, ca_id, session=None):
"""Create or update preferred CA for a project by project_id.
def update_global_preferred_ca(self, new_ca):
self.update_preferred_ca(self.PREFERRED_PROJECT_ID, new_ca)
def update_preferred_ca(self, project_id, new_ca):
session = self.get_session()
query = session.query(models.PreferredCertificateAuthority).filter_by(
project_id=project_id)
entity = query.one()
entity.ca_id = new_ca.id
entity.save()
:param project_id: ID of project whose preferred CA will be saved
:param ca_id: ID of preferred CA
:param session: SQLAlchemy session object.
:return: None
"""
session = self.get_session(session)
query = session.query(models.PreferredCertificateAuthority)
query = query.filter_by(project_id=project_id)
try:
entity = query.one()
except sa_orm.exc.NoResultFound:
self.create_from(
models.PreferredCertificateAuthority(project_id, ca_id),
session=session)
else:
entity.ca_id = ca_id
entity.save(session)
def _do_entity_name(self):
"""Sub-class hook: return entity name, such as for debugging."""
@ -1709,7 +1714,7 @@ class PreferredCertificateAuthorityRepo(BaseRepo):
:param session: existing db session reference.
"""
return session.query(models.PreferredCertificateAuthority).filter_by(
project_id=project_id).filter_by(deleted=False)
project_id=project_id)
class SecretACLRepo(BaseRepo):

View File

@ -18,6 +18,7 @@ from OpenSSL import crypto
from barbican.common import exception as excep
from barbican.common import hrefs
from barbican.common import resources as res
import barbican.common.utils as utils
from barbican.model import models
from barbican.model import repositories as repos
@ -315,6 +316,16 @@ def modify_certificate_request(order_model, updated_meta):
raise NotImplementedError # pragma: no cover
def get_global_preferred_ca():
project = res.get_or_create_global_preferred_project()
preferred_ca_repository = repos.get_preferred_ca_repository()
cas = preferred_ca_repository.get_project_entities(project.id)
if not cas:
return None
else:
return cas[0]
def _get_ca_id(order_meta, project_id):
ca_id = order_meta.get(cert.CA_ID)
if ca_id:
@ -326,7 +337,7 @@ def _get_ca_id(order_meta, project_id):
if total > 0:
return cas[0].ca_id
global_ca = preferred_ca_repository.get_global_preferred_ca()
global_ca = get_global_preferred_ca()
if global_ca:
return global_ca.ca_id

View File

@ -352,23 +352,17 @@ class WhenTestingCAsResource(utils.BarbicanAPIBaseTestCase):
def test_should_unset_global_preferred(self):
self.create_cas()
resp = self.app.post('/cas/{0}/unset-global-preferred'.format(
self.global_preferred_ca.id))
resp = self.app.post(
'/cas/unset-global-preferred')
self.assertEqual(204, resp.status_int)
def test_should_unset_global_preferred_not_post(self):
self.create_cas()
resp = self.app.get(
'/cas/{0}/unset-global-preferred'.format(self.selected_ca_id),
'/cas/unset-global-preferred',
expect_errors=True)
self.assertEqual(405, resp.status_int)
def test_should_raise_unset_global_preferred_ca_not_found(self):
resp = self.app.post(
'/cas/bogus_ca/unset-global-preferred',
expect_errors=True)
self.assertEqual(404, resp.status_int)
def test_should_get_projects(self):
self.create_cas()
resp = self.app.get(
@ -456,6 +450,7 @@ class WhenTestingCAsResource(utils.BarbicanAPIBaseTestCase):
def create_cas(self, set_project_cas=True):
self.project = res.get_or_create_project(self.project_id)
self.global_project = res.get_or_create_global_preferred_project()
project_repo.save(self.project)
self.project_ca_ids = []
@ -505,7 +500,7 @@ class WhenTestingCAsResource(utils.BarbicanAPIBaseTestCase):
if ca_id == 1:
# set global preferred ca
pref_ca = models.PreferredCertificateAuthority(
preferred_ca_repo.PREFERRED_PROJECT_ID,
self.global_project.id,
ca.id)
preferred_ca_repo.create_from(pref_ca)
preferred_ca_repo.save(pref_ca)

View File

@ -13,6 +13,7 @@
import datetime
from barbican.common import exception
from barbican.common import resources as res
from barbican.model import models
from barbican.model import repositories
from barbican.tests import database_utils
@ -334,6 +335,8 @@ class WhenTestingPreferredCARepo(database_utils.RepositoryTestCase):
'ca_signing_certificate': 'XXXXX-updated-XXXXX',
'intermediates': 'YYYYY'}
self.global_project = res.get_or_create_global_preferred_project()
def _add_ca(self, parsed_ca, session):
ca = self.ca_repo.create_from(models.CertificateAuthority(parsed_ca),
session=session)
@ -354,7 +357,7 @@ class WhenTestingPreferredCARepo(database_utils.RepositoryTestCase):
def _add_global_preferred_ca(self, ca_id, session):
preferred_ca = self.preferred_ca_repo.create_from(
models.PreferredCertificateAuthority(
self.preferred_ca_repo.PREFERRED_PROJECT_ID,
self.global_project.id,
ca_id),
session)
return preferred_ca
@ -460,13 +463,28 @@ class WhenTestingPreferredCARepo(database_utils.RepositoryTestCase):
session.commit()
pca = self.preferred_ca_repo.get_project_entities(
self.preferred_ca_repo.PREFERRED_PROJECT_ID,
self.global_project.id,
session)
self.assertEqual([ca.id], [s.ca_id for s in pca])
def test_should_update(self):
def test_should_create(self):
session = self.ca_repo.get_session()
ca = self._add_ca(self.parsed_ca, session)
project = self._add_project("project_1", session)
self.preferred_ca_repo.create_or_update_by_project_id(
project.id, ca.id)
session.commit()
self.ca_repo.update_entity(ca, self.parsed_modified_ca, session)
def test_should_update(self):
session = self.ca_repo.get_session()
ca1 = self._add_ca(self.parsed_ca, session)
ca2 = self._add_ca(self.parsed_ca2, session)
project = self._add_project("project_1", session)
self.preferred_ca_repo.create_or_update_by_project_id(
project.id, ca1.id)
session.commit()
self.preferred_ca_repo.create_or_update_by_project_id(
project.id, ca2.id)
session.commit()

View File

@ -55,6 +55,7 @@
"certificate_authorities:post": "rule:admin",
"certificate_authorities:get_preferred_ca": "rule:all_users",
"certificate_authorities:get_global_preferred_ca": "rule:service_admin",
"certificate_authorities:unset_global_preferred": "rule:service_admin",
"certificate_authority:delete": "rule:admin",
"certificate_authority:get": "rule:all_users",
"certificate_authority:get_cacert": "rule:all_users",
@ -64,7 +65,6 @@
"certificate_authority:remove_from_project": "rule:admin",
"certificate_authority:set_preferred": "rule:admin",
"certificate_authority:set_global_preferred": "rule:service_admin",
"certificate_authority:unset_global_preferred": "rule:service_admin",
"secret_acls:put_patch": "rule:secret_project_admin or rule:secret_project_creator",
"secret_acls:delete": "rule:secret_project_admin or rule:secret_project_creator",
"secret_acls:get": "rule:all_but_audit and rule:secret_project_match",

View File

@ -154,3 +154,24 @@ class CABehaviors(base_behaviors.BaseBehaviors):
response_model_type=ca_models.CAModel,
extra_headers=extra_headers, use_auth=use_auth,
user_name=user_name)
def set_global_preferred(self, ca_ref, headers=None,
use_auth=True, user_name=None):
resp = self.client.post(ca_ref + '/set-global-preferred',
extra_headers=headers, use_auth=use_auth,
user_name=user_name)
return resp
def unset_global_preferred(self, headers=None,
use_auth=True, user_name=None):
resp = self.client.post('cas/unset-global-preferred',
extra_headers=headers,
use_auth=use_auth, user_name=user_name)
return resp
def get_global_preferred(self, extra_headers=None,
use_auth=True, user_name=None):
return self.client.get('cas/global-preferred',
response_model_type=ca_models.CAModel,
extra_headers=extra_headers,
use_auth=use_auth, user_name=user_name)

View File

@ -34,6 +34,7 @@ CONF = config.get_config()
admin_a = CONF.rbac_users.admin_a
creator_a = CONF.rbac_users.creator_a
service_admin = CONF.identity.service_admin
order_simple_cmc_request_data = {
'type': 'certificate',
@ -321,3 +322,76 @@ class ProjectCATestCase(CATestCommon):
# before)
(resp, cas, final_total, _, __) = self.ca_behaviors.get_cas()
self.assertEqual(initial_total, final_total)
class GlobalPreferredCATestCase(CATestCommon):
def setUp(self):
super(GlobalPreferredCATestCase, self).setUp()
(_, self.cas, self.num_cas, _, _) = self.ca_behaviors.get_cas()
self.ca_ids = [hrefs.get_ca_id_from_ref(ref) for ref in self.cas]
def tearDown(self):
self.ca_behaviors.unset_global_preferred(user_name=service_admin)
super(CATestCommon, self).tearDown()
def test_global_preferred_no_project_admin_access(self):
resp = self.ca_behaviors.get_global_preferred()
self.assertEqual(403, resp.status_code)
resp = self.ca_behaviors.set_global_preferred(ca_ref=self.cas[1])
self.assertEqual(403, resp.status_code)
resp = self.ca_behaviors.unset_global_preferred()
self.assertEqual(403, resp.status_code)
def test_global_preferred_update(self):
if self.num_cas < 2:
self.skipTest("At least two CAs are required for this test")
resp = self.ca_behaviors.set_global_preferred(
ca_ref=self.cas[0], user_name=service_admin)
self.assertEqual(204, resp.status_code)
resp = self.ca_behaviors.get_global_preferred(user_name=service_admin)
self.assertEqual(200, resp.status_code)
self.assertEqual(self.ca_ids[0], resp.model.ca_id)
resp = self.ca_behaviors.set_global_preferred(
ca_ref=self.cas[1], user_name=service_admin)
self.assertEqual(204, resp.status_code)
resp = self.ca_behaviors.get_global_preferred(user_name=service_admin)
self.assertEqual(200, resp.status_code)
self.assertEqual(self.ca_ids[1], resp.model.ca_id)
def test_global_preferred_set_and_unset(self):
resp = self.ca_behaviors.unset_global_preferred(
user_name=service_admin)
self.assertEqual(204, resp.status_code)
resp = self.ca_behaviors.get_global_preferred(user_name=service_admin)
self.assertEqual(404, resp.status_code)
resp = self.ca_behaviors.set_global_preferred(
ca_ref=self.cas[0], user_name=service_admin)
self.assertEqual(204, resp.status_code)
resp = self.ca_behaviors.get_global_preferred(user_name=service_admin)
self.assertEqual(200, resp.status_code)
self.assertEqual(self.ca_ids[0], resp.model.ca_id)
resp = self.ca_behaviors.unset_global_preferred(
user_name=service_admin)
self.assertEqual(204, resp.status_code)
resp = self.ca_behaviors.get_global_preferred(user_name=service_admin)
self.assertEqual(404, resp.status_code)
@testtools.skip("Skip test until ca behaviors tracks project cas")
def test_global_preferred_affects_project_preferred(self):
if self.num_cas < 2:
self.skipTest("At least two CAs are required for this test")
resp = self.ca_behaviors.get_preferred(user_name=admin_a)
self.assertEqual(200, resp.status_code)
self.assertEqual(self.ca_ids[0], resp.model.ca_id)
resp = self.ca_behaviors.set_global_preferred(
ca_ref=self.cas[1], user_name=service_admin)
self.assertEqual(204, resp.status_code)
resp = self.ca_behaviors.get_preferred(user_name=admin_a)
self.assertEqual(200, resp.status_code)
self.assertEqual(self.ca_ids[1], resp.model.ca_id)

View File

@ -29,7 +29,7 @@ retval=$?
testr slowest
# run the tests in parallel
SKIP=^\(\?\!\.\*\(ProjectQuotasPagingTestCase\|QuotaEnforcementTestCase\|ListingCAsTestCase\|ProjectCATestCase\)\)
SKIP=^\(\?\!\.\*\(ProjectQuotasPagingTestCase\|QuotaEnforcementTestCase\|ListingCAsTestCase\|ProjectCATestCase\|GlobalPreferredCATestCase\)\)
testr init
testr run $SKIP --parallel --subunit | subunit-trace --no-failure-debug -f
retval=$?