diff --git a/.gitignore b/.gitignore index 063cc147..2da48923 100644 --- a/.gitignore +++ b/.gitignore @@ -31,6 +31,8 @@ pip-log.txt .tox nosetests.xml karma-test-results.xml +coverage.xml +package-lock.json # Translations *.mo diff --git a/Dockerfile b/Dockerfile index da8949f9..4207b072 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,7 +6,7 @@ WORKDIR /srv/confidant RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ curl ca-certificates \ - && /usr/bin/curl -sL --fail https://deb.nodesource.com/setup_8.x | bash - + && /usr/bin/curl -sL --fail https://deb.nodesource.com/setup_12.x | bash - RUN apt-get update \ && DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ # For frontend @@ -19,9 +19,6 @@ RUN apt-get update \ COPY package.json /srv/confidant/ -RUN npm install grunt-cli && \ - npm install - COPY piptools_requirements*.txt requirements*.txt /srv/confidant/ ENV PATH=/venv/bin:$PATH @@ -32,10 +29,13 @@ RUN virtualenv /venv -ppython3 && \ COPY .jshintrc Gruntfile.js /srv/confidant/ COPY confidant/public /srv/confidant/confidant/public -RUN node_modules/grunt-cli/bin/grunt build - COPY . /srv/confidant EXPOSE 80 +# added this to test the saml stuff +RUN groupadd confidantTestGroup +RUN useradd confidant-user +RUN usermod -a -G confidantTestGroup confidant-user + CMD ["gunicorn", "confidant.wsgi:app", "--workers=2", "-k", "gevent", "--access-logfile=-", "--error-logfile=-"] diff --git a/Makefile b/Makefile index aec983f6..e7b335c1 100644 --- a/Makefile +++ b/Makefile @@ -11,6 +11,8 @@ down: docker-compose down docker_build: clean + npm install grunt-cli && npm install + node_modules/grunt-cli/bin/grunt build docker build -t lyft/confidant . docker_test: docker_build docker_test_unit docker_test_integration docker_test_frontend down @@ -42,7 +44,7 @@ test_unit: clean pytest --strict --junitxml=build/unit.xml --cov=confidant --cov-report=html --cov-report=xml --cov-report=term --no-cov-on-fail tests/unit test_frontend: - node_modules/grunt-cli/bin/grunt test + ./node_modules/grunt-cli/bin/grunt test .PHONY: compile_deps # freeze requirements.in to requirements3.txt compile_deps: diff --git a/confidant/authnz/__init__.py b/confidant/authnz/__init__.py index 12b7893d..30ef6edf 100644 --- a/confidant/authnz/__init__.py +++ b/confidant/authnz/__init__.py @@ -15,12 +15,13 @@ ) from confidant.authnz import userauth +import grp + _VALIDATOR = None logger = logging.getLogger(__name__) user_mod = userauth.init_user_auth_class() - def _get_validator(): global _VALIDATOR if _VALIDATOR is None: @@ -58,7 +59,6 @@ def user_is_user_type(user_type): return True return False - def user_is_service(service): if not settings.USE_AUTH: return True @@ -66,6 +66,20 @@ def user_is_service(service): return True return False +# TODO: ASK STRIPE ABOUT THIS! +# IF GET_LOGGED_IN_USER IS AN EMAIL, WE CHECK THE GROUP MEMBERSHIP OF THE EMAIL PREFIX! +def user_in_groups(groupnames): + for groupname in groupnames: + try: + group = grp.getgrnam(groupname) + except KeyError: + continue + user = get_logged_in_user() + if '@' in user: + user = user.split('@')[0] + if user in group[3]: + return True + return False def service_in_account(account): # We only scope to account, if an account is specified. diff --git a/confidant/authnz/errors.py b/confidant/authnz/errors.py index 357951f7..45713979 100644 --- a/confidant/authnz/errors.py +++ b/confidant/authnz/errors.py @@ -1,6 +1,5 @@ # authentication / authorization related error classes - class UserUnknownError(Exception): pass diff --git a/confidant/authnz/rbac.py b/confidant/authnz/rbac.py index 707844c8..f78014f6 100644 --- a/confidant/authnz/rbac.py +++ b/confidant/authnz/rbac.py @@ -2,7 +2,7 @@ from confidant import authnz from confidant.services import certificatemanager - +from confidant.models.credential import Credential def default_acl(*args, **kwargs): """ Default ACLs for confidant: Allow access to all resource types @@ -25,6 +25,15 @@ def default_acl(*args, **kwargs): action = kwargs.get('action') resource_id = kwargs.get('resource_id') resource_kwargs = kwargs.get('kwargs') + if resource_type == 'credential' and resource_id is not None: + try: + cred = Credential.get(resource_id) + except: + return False + if len(cred.groups) and not authnz.user_in_groups(cred.groups): + if 'error_message_handler' in kwargs: + kwargs['error_message_handler']("Not a member of any of the following groups: {}".format(cred.groups)) + return False if authnz.user_is_user_type('user'): if resource_type == 'certificate': return False @@ -63,7 +72,6 @@ def default_acl(*args, **kwargs): # This should never happen, but paranoia wins out return False - def no_acl(*args, **kwargs): """ Stub function that always returns true This function is set by settings.py by the variable ACL_MODULE diff --git a/confidant/models/blind_credential.py b/confidant/models/blind_credential.py index 5d832293..64f04011 100644 --- a/confidant/models/blind_credential.py +++ b/confidant/models/blind_credential.py @@ -6,7 +6,8 @@ NumberAttribute, BooleanAttribute, UTCDateTimeAttribute, - JSONAttribute + JSONAttribute, + ListAttribute ) from pynamodb.indexes import GlobalSecondaryIndex, AllProjection @@ -51,6 +52,7 @@ class Meta: modified_date = UTCDateTimeAttribute(default=datetime.now) modified_by = UnicodeAttribute() documentation = UnicodeAttribute(null=True) + groups = ListAttribute(default=list) def equals(self, other_cred): if self.name != other_cred.name: @@ -71,4 +73,6 @@ def equals(self, other_cred): return False if self.documentation != other_cred.documentation: return False + if set(self.groups) != set(other_cred.groups): + return False return True diff --git a/confidant/models/credential.py b/confidant/models/credential.py index 6db38c3b..7854934c 100644 --- a/confidant/models/credential.py +++ b/confidant/models/credential.py @@ -56,6 +56,8 @@ class CredentialBase(Model): tags = ListAttribute(default=list) last_decrypted_date = UTCDateTimeAttribute(null=True) last_rotation_date = UTCDateTimeAttribute(null=True) + # if a user is not in one of the groups, cannot interact w credential + groups = ListAttribute(default=list) class Credential(CredentialBase): @@ -82,6 +84,8 @@ def equals(self, other_cred): return False if set(self.tags) != set(other_cred.tags): return False + if set(self.groups) != set(other_cred.groups): + return False return True def diff(self, other_cred): @@ -131,6 +135,11 @@ def diff(self, other_cred): 'added': new.modified_date, 'removed': old.modified_date, } + if set(old.groups) != set(new.groups): + diff['groups'] = { + 'added': list(set(new.groups) - set(old.groups)), + 'removed': list(set(old.groups) - set(new.groups)), + } return diff def _diff_dict(self, old, new): @@ -228,6 +237,7 @@ def from_archive_credential(cls, archive_credential): tags=archive_credential.tags, last_decrypted_date=archive_credential.last_decrypted_date, last_rotation_date=archive_credential.last_rotation_date, + groups=archive_credential.groups, ) @@ -261,4 +271,5 @@ def from_credential(cls, credential): tags=credential.tags, last_decrypted_date=credential.last_decrypted_date, last_rotation_date=credential.last_rotation_date, + groups=credential.groups, ) diff --git a/confidant/public/modules/history/controllers/CredentialHistoryCtrl.js b/confidant/public/modules/history/controllers/CredentialHistoryCtrl.js index db589881..e8004b1e 100644 --- a/confidant/public/modules/history/controllers/CredentialHistoryCtrl.js +++ b/confidant/public/modules/history/controllers/CredentialHistoryCtrl.js @@ -54,12 +54,24 @@ $scope.noDiff = true; } }, function(res) { - $scope.getError = res.data.error; + if (res.status === 500) { + $scope.getError = 'Unexpected server error.'; + $log.error(res); + } else { + $scope.getError = res.data.error; + } }); } if ($scope.currentRevision === $scope.credentialRevision) { $scope.isCurrentRevision = true; } + }, function(res) { + if (res.status === 500) { + $scope.getError = 'Unexpected server error.'; + $log.error(res); + } else { + $scope.getError = res.data.error; + } }); $scope.shouldDisplayList = function(value) { diff --git a/confidant/public/modules/resources/controllers/CredentialDetailsCtrl.js b/confidant/public/modules/resources/controllers/CredentialDetailsCtrl.js index e9d3223c..04886e5d 100644 --- a/confidant/public/modules/resources/controllers/CredentialDetailsCtrl.js +++ b/confidant/public/modules/resources/controllers/CredentialDetailsCtrl.js @@ -85,11 +85,18 @@ if ($scope.credentialId) { CredentialServices.get({'id': $scope.credentialId}).$promise.then(function(credentialServices) { $scope.credentialServices = credentialServices.services; - }); - - Credential.get({'id': $scope.credentialId, 'metadata_only': true}).$promise.then(function(credential) { - $scope.shown = false; - populateCredential(credential); + Credential.get({'id': $scope.credentialId, 'metadata_only': true}).$promise.then(function(credential) { + $scope.shown = false; + populateCredential(credential); + }, function(res) { + if (res.status === 500) { + $scope.getError = 'Unexpected server error.'; + $log.error(res); + } else { + $scope.getError = res.data.error; + } + deferred.reject(); + }); }, function(res) { if (res.status === 500) { $scope.getError = 'Unexpected server error.'; @@ -97,7 +104,6 @@ } else { $scope.getError = res.data.error; } - deferred.reject(); }); } else { // A new credential is being created @@ -106,7 +112,8 @@ enabled: true, credentialPairs: [{'key': '', 'value': ''}], mungedMetadata: [], - mungedTags: [] + mungedTags: [], + groups: [], }; credentialCopy = angular.copy($scope.credential); $scope.shown = true; @@ -221,6 +228,7 @@ _credential.name = $scope.credential.name; _credential.enabled = $scope.credential.enabled; _credential.documentation = $scope.credential.documentation; + _credential.groups = []; _credential.credential_pairs = {}; _credential.metadata = {}; _credential.tags = []; @@ -253,6 +261,14 @@ } _credential.metadata[metadataItem.key] = metadataItem.value; } + // Remove whitespace from group names + var unmungedGroups = $scope.credential.groups.split(','); + for (i = 0; i < unmungedGroups.length; i++) { + var grp = unmungedGroups[i].trim(); + if (grp.length > 0) { + _credential.groups.push(grp); + } + } for (i = $scope.credential.mungedTags.length; i--;) { var tagItem = $scope.credential.mungedTags[i]; if (tagItem.isDeleted) { diff --git a/confidant/public/modules/resources/views/credential-details.html b/confidant/public/modules/resources/views/credential-details.html index 36125dfc..639d63ac 100644 --- a/confidant/public/modules/resources/views/credential-details.html +++ b/confidant/public/modules/resources/views/credential-details.html @@ -93,6 +93,10 @@ +
+ + {{ credential.groups.join(",") || 'Not set' }} +
diff --git a/confidant/routes/certificates.py b/confidant/routes/certificates.py index a5d839f0..bb88972b 100644 --- a/confidant/routes/certificates.py +++ b/confidant/routes/certificates.py @@ -248,7 +248,6 @@ def list_cas(): GET /v1/cas **Example response**: - .. sourcecode:: http HTTP/1.1 200 OK @@ -310,7 +309,6 @@ def get_ca(ca): :type ca: str **Example response**: - .. sourcecode:: http HTTP/1.1 200 OK diff --git a/confidant/routes/credentials.py b/confidant/routes/credentials.py index 5aa3ed8e..14fd7801 100644 --- a/confidant/routes/credentials.py +++ b/confidant/routes/credentials.py @@ -76,7 +76,8 @@ def get_credential_list(): "documentation": "Example documentation", "modified_date": "2019-12-16T23:16:11.413299+00:00", "modified_by": "rlane@example.com", - "permissions": {} + "permissions": {}, + "groups": [] }, ... ], @@ -87,10 +88,16 @@ def get_credential_list(): :statuscode 200: Success :statuscode 403: Client does not have permissions to list credentials. """ - if not acl_module_check(resource_type='credential', action='list'): + sub_error_msg = "" + def set_sub_error(x): + nonlocal sub_error_msg + sub_error_msg = x + if not acl_module_check(resource_type='credential', action='list', error_message_handler=set_sub_error): msg = "{} does not have access to list credentials".format( authnz.get_logged_in_user() ) + if sub_error_msg: + msg = "{} : {}".format(msg, sub_error_msg) error_msg = {'error': msg} return jsonify(error_msg), 403 @@ -144,6 +151,7 @@ def get_credential(id): "documentation": "Example documentation", "modified_date": "2019-12-16T23:16:11.413299+00:00", "modified_by": "rlane@example.com", + "groups": ["security-group"], "permissions": { "metadata": true, "get": true, @@ -159,13 +167,20 @@ def get_credential(id): """ metadata_only = misc.get_boolean(request.args.get('metadata_only')) + sub_error_msg = "" + def set_sub_error(x): + nonlocal sub_error_msg + sub_error_msg = x if not acl_module_check(resource_type='credential', action='metadata', - resource_id=id): + resource_id=id, + error_message_handler=set_sub_error): msg = "{} does not have access to credential {}".format( authnz.get_logged_in_user(), id ) + if sub_error_msg: + msg = "{} : {}".format(msg, sub_error_msg) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 @@ -302,13 +317,20 @@ def diff_credential(id, old_revision, new_revision): :statuscode 404: The provided credential ID or one of the provided revisions does not exist. """ + sub_error_msg = "" + def set_sub_error(x): + nonlocal sub_error_msg + sub_error_msg = x if not acl_module_check(resource_type='credential', action='metadata', - resource_id=id): + resource_id=id, + error_message_handler=set_sub_error): msg = "{} does not have access to diff credential {}".format( authnz.get_logged_in_user(), id ) + if sub_error_msg: + msg = "{} : {}".format(msg, sub_error_msg) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 @@ -386,13 +408,20 @@ def get_archive_credential_revisions(id): metadata for the provided credential ID. :statuscode 404: The provided credential ID does not exist. """ + sub_error_msg = "" + def set_sub_error(x): + nonlocal sub_error_msg + sub_error_msg = x if not acl_module_check(resource_type='credential', action='metadata', - resource_id=id): + resource_id=id, + error_message_handler=set_sub_error): msg = "{} does not have access to credential {} revisions".format( authnz.get_logged_in_user(), id ) + if sub_error_msg: + msg = "{} : {}".format(msg, sub_error_msg) error_msg = {'error': msg} return jsonify(error_msg), 403 @@ -465,10 +494,16 @@ def get_archive_credential_list(): :statuscode 200: Success :statuscode 403: Client does not have permissions to list credentials """ - if not acl_module_check(resource_type='credential', action='list'): + sub_error_msg = "" + def set_sub_error(x): + nonlocal sub_error_msg + sub_error_msg = x + if not acl_module_check(resource_type='credential', action='list', error_message_handler=set_sub_error): msg = "{} does not have access to list credentials".format( authnz.get_logged_in_user() ) + if sub_error_msg: + msg = "{} : {}".format(msg, sub_error_msg) error_msg = {'error': msg} return jsonify(error_msg), 403 @@ -560,10 +595,16 @@ def create_credential(): correct format, or a required field was not provided. :statuscode 403: Client does not have access to create credentials. ''' - if not acl_module_check(resource_type='credential', action='create'): + sub_error_msg = "" + def set_sub_error(x): + nonlocal sub_error_msg + sub_error_msg = x + if not acl_module_check(resource_type='credential', action='create', error_message_handler=set_sub_error): msg = "{} does not have access to create credentials".format( authnz.get_logged_in_user() ) + if sub_error_msg: + msg = "{} : {}".format(msg, sub_error_msg) error_msg = {'error': msg} return jsonify(error_msg), 403 @@ -611,6 +652,7 @@ def create_credential(): documentation=data.get('documentation'), tags=data.get('tags', []), last_rotation_date=last_rotation_date, + groups=data.get('groups', None), ).save(id__null=True) # Make this the current revision cred = Credential( @@ -627,6 +669,7 @@ def create_credential(): documentation=data.get('documentation'), tags=data.get('tags', []), last_rotation_date=last_rotation_date, + groups=data.get('groups', []) ) cred.save() permissions = { @@ -674,11 +717,18 @@ def get_credential_dependencies(id): :statuscode 403: Client does not have permissions to get metadata for the provided credential. """ + sub_error_msg = "" + def set_sub_error(x): + nonlocal sub_error_msg + sub_error_msg = x if not acl_module_check(resource_type='credential', action='metadata', - resource_id=id): + resource_id=id, + error_message_handler=set_sub_error): msg = "{} does not have access to get dependencies for credential {}" msg = msg.format(authnz.get_logged_in_user(), id) + if sub_error_msg: + msg = "{} : {}".format(msg, sub_error_msg) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 @@ -753,13 +803,20 @@ def update_credential(id): :statuscode 403: Client does not have access to update the provided credential ID. ''' + sub_error_msg = "" + def set_sub_error(x): + nonlocal sub_error_msg + sub_error_msg = x if not acl_module_check(resource_type='credential', action='update', - resource_id=id): + resource_id=id, + error_message_handler=set_sub_error): msg = "{} does not have access to update credential {}".format( authnz.get_logged_in_user(), id ) + if sub_error_msg: + msg = "{} : {}".format(msg, sub_error_msg) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 @@ -783,6 +840,7 @@ def update_credential(id): 'metadata': data.get('metadata', _cred.metadata), 'documentation': data.get('documentation', _cred.documentation), 'tags': data.get('tags', _cred.tags), + 'groups': data.get('groups', _cred.groups), } # Enforce documentation, EXCEPT if we are restoring an old revision if (not update['documentation'] and @@ -850,6 +908,7 @@ def update_credential(id): documentation=update['documentation'], tags=update['tags'], last_rotation_date=update['last_rotation_date'], + groups=update['groups'], ).save(id__null=True) except PutError as e: logger.error(e) @@ -869,6 +928,7 @@ def update_credential(id): documentation=update['documentation'], tags=update['tags'], last_rotation_date=update['last_rotation_date'], + groups=update['groups'], ) cred.save() except PutError as e: @@ -951,13 +1011,20 @@ def revert_credential_to_revision(id, to_revision): :statuscode 403: Client does not have access to revert the provided credential ID. ''' + sub_error_msg = "" + def set_sub_error(x): + nonlocal sub_error_msg + sub_error_msg = x if not acl_module_check(resource_type='credential', action='revert', - resource_id=id): + resource_id=id, + error_message_handler=set_sub_error): msg = "{} does not have access to revert credential {}".format( authnz.get_logged_in_user(), id ) + if sub_error_msg: + msg = "{} : {}".format(msg, sub_error_msg) error_msg = {'error': msg, 'reference': id} return jsonify(error_msg), 403 @@ -1024,6 +1091,7 @@ def revert_credential_to_revision(id, to_revision): documentation=revert_credential.documentation, tags=revert_credential.tags, last_rotation_date=revert_credential.last_rotation_date, + groups=revert_credential.groups, ).save(id__null=True) except PutError as e: logger.error(e) @@ -1043,6 +1111,7 @@ def revert_credential_to_revision(id, to_revision): documentation=revert_credential.documentation, tags=revert_credential.tags, last_rotation_date=revert_credential.last_rotation_date, + groups=revert_credential.groups, ) cred.save() except PutError as e: diff --git a/confidant/schema/credentials.py b/confidant/schema/credentials.py index a045152d..c0e2f8ed 100644 --- a/confidant/schema/credentials.py +++ b/confidant/schema/credentials.py @@ -22,6 +22,7 @@ class CredentialResponse(object): tags = attr.ib(default=list) last_rotation_date = attr.ib(default=None) next_rotation_date = attr.ib(default=None) + groups = attr.ib(default=list) @classmethod def from_credential( @@ -42,6 +43,7 @@ def from_credential( tags=credential.tags, last_rotation_date=credential.last_rotation_date, next_rotation_date=credential.next_rotation_date, + groups=credential.groups, ) if include_credential_keys: ret.credential_keys = credential.credential_keys @@ -70,6 +72,7 @@ class Meta: tags = fields.List(fields.Str()) last_rotation_date = fields.DateTime() next_rotation_date = fields.DateTime() + groups = fields.List(fields.Str()) @attr.s diff --git a/docs/root/acls.md b/docs/root/acls.md index 5a51af13..b326c957 100644 --- a/docs/root/acls.md +++ b/docs/root/acls.md @@ -1,5 +1,4 @@ # Access Controls (ACLs) - ## Design The design for managing fine-grained ACLs in confidant is relatively simple. Hookpoints are called whenever a resource type will be accessed by an end-user; these hookpoints look like: diff --git a/package.json b/package.json index 02e79545..9e78c0a0 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "angular-xeditable": "~0.1.9", "bootstrap": "3.4.1", "es5-shim": "~4.1.13", + "grunt-cli": "^1.3.2", "json3": "~3.3.2", "lodash": "~4.17.15", "spin.js": "~2.3.2" diff --git a/pytest.ini b/pytest.ini index 971ddc0a..b763b7e2 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,6 +1,6 @@ [pytest] -addopts = -p no:warnings --no-print-logs --strict +addopts = -p no:warnings --no-print-logs --strict -vvv env = SESSION_SECRET=secret DYNAMODB_CREATE_TABLE=False diff --git a/tests/unit/confidant/authnz/authnz_test.py b/tests/unit/confidant/authnz/authnz_test.py index 8cf35614..bbb2adc5 100644 --- a/tests/unit/confidant/authnz/authnz_test.py +++ b/tests/unit/confidant/authnz/authnz_test.py @@ -97,6 +97,14 @@ def test_user_is_service(mocker): assert authnz.user_is_service('notconfidant-unitttest') is False +def test_user_in_groups(mocker): + my_group = ('my_group', '', 1337, ['user1']) + mocker.patch('grp.getgrnam', return_value=my_group) + mocker.patch('confidant.authnz.get_logged_in_user', return_value='user1') + assert authnz.user_in_groups(['my_group']) + mocker.patch('confidant.authnz.get_logged_in_user', return_value='user2') + assert not authnz.user_in_groups(['my_group']) + def test_service_in_account(mocker): # If we aren't scoping, this should pass assert authnz.service_in_account(None) is True diff --git a/tests/unit/confidant/authnz/rbac_test.py b/tests/unit/confidant/authnz/rbac_test.py index c16c6602..64a02985 100644 --- a/tests/unit/confidant/authnz/rbac_test.py +++ b/tests/unit/confidant/authnz/rbac_test.py @@ -1,6 +1,8 @@ from confidant.app import create_app from confidant.authnz import rbac +from confidant.models.credential import Credential +from datetime import datetime def test_default_acl(mocker): mocker.patch('confidant.settings.USE_AUTH', True) @@ -113,7 +115,33 @@ def test_default_acl(mocker): # Test for bad user type g_mock.user_type = 'badtype' assert rbac.default_acl(resource_type='service', action='get') is False - + # Test groups + g_mock.user_type = 'user' + g_mock.username = 'test-user' + mocker.patch('confidant.authnz.user_in_groups', lambda _: False) + credential = Credential( + id='1234', + revision=1, + data_type='credential', + enabled=True, + name='Test credential', + credential_pairs='akjlaklkaj==', + data_key='slkjlksfjklsdjf==', + cipher_version=2, + metadata={}, + modified_date=datetime.now(), + modified_by='test@example.com', + documentation='', + last_rotation_date=datetime(2020, 1, 1), + groups=['testgroup'] + ) + mocker.patch( + 'confidant.routes.credentials.Credential.get', + return_value=credential, + ) + assert rbac.default_acl(resource_type='credential', resource_id='cred', action='get') is False + mocker.patch('confidant.authnz.user_in_groups', lambda _: True) + assert rbac.default_acl(resource_type='credential', resource_id='cred', action='get') is True def test_no_acl(): app = create_app() diff --git a/tests/unit/confidant/models/credential_test.py b/tests/unit/confidant/models/credential_test.py index 2e19d6da..0b51d82f 100644 --- a/tests/unit/confidant/models/credential_test.py +++ b/tests/unit/confidant/models/credential_test.py @@ -15,6 +15,7 @@ def test_equals(mocker): documentation='', metadata={}, tags=['ADMIN_PRIV'], + groups=['testgroup'], ) cred2 = Credential( name='test', @@ -22,6 +23,7 @@ def test_equals(mocker): documentation='', metadata={}, tags=['ADMIN_PRIV'], + groups=['testgroup'], ) assert cred1.equals(cred2) is True @@ -69,6 +71,29 @@ def test_not_equals_different_tags(mocker): assert cred1.equals(cred2) is False +def test_not_equals_different_groups(mocker): + decrypted_pairs_mock = mocker.patch( + 'confidant.models.credential.Credential.decrypted_credential_pairs' + ) + decrypted_pairs_mock.return_value = {'test': 'me'} + cred1 = Credential( + name='test', + enabled=True, + documentation='', + metadata={}, + tags=['ADMIN_PRIV'], + groups=['g1'], + ) + cred2 = Credential( + name='test', + enabled=True, + documentation='', + metadata={}, + tags=['ADMIN_PRIV'], + groups=['g2'], + ) + assert cred1.equals(cred2) is False + def test_diff(mocker): mocker.patch( 'confidant.models.credential.Credential' @@ -87,6 +112,7 @@ def test_diff(mocker): modified_by=modified_by, modified_date=modified_date_old, tags=['FINANCIALLY_SENSITIVE', 'IMPORTANT'], + groups=['g1'], ) new = Credential( name='test2', @@ -97,6 +123,7 @@ def test_diff(mocker): modified_by=modified_by, modified_date=modified_date_new, tags=['ADMIN_PRIV', 'IMPORTANT'], + groups=['g2'], ) # TODO: figure out how to test decrypted_credential_pairs. Mocking # it is turning out to be difficult. @@ -125,6 +152,10 @@ def test_diff(mocker): 'removed': ['FINANCIALLY_SENSITIVE'], 'added': ['ADMIN_PRIV'], }, + 'groups': { + 'removed': ['g1'], + 'added': ['g2'], + }, } assert old.diff(new) == expectedDiff diff --git a/tests/unit/confidant/routes/certificates_test.py b/tests/unit/confidant/routes/certificates_test.py index 094c9db4..17e4788b 100644 --- a/tests/unit/confidant/routes/certificates_test.py +++ b/tests/unit/confidant/routes/certificates_test.py @@ -8,6 +8,8 @@ def test_get_certificate(mocker): app = create_app() mocker.patch('confidant.settings.USE_AUTH', False) + # making sure that group settings for credentials don't affect certs + mocker.patch('confidant.authnz.user_in_groups', lambda _: False) mocker.patch( 'confidant.authnz.get_logged_in_user', return_value='badservice', diff --git a/tests/unit/confidant/routes/credentials_test.py b/tests/unit/confidant/routes/credentials_test.py index f0e85395..744c198d 100644 --- a/tests/unit/confidant/routes/credentials_test.py +++ b/tests/unit/confidant/routes/credentials_test.py @@ -24,6 +24,7 @@ def credential(mocker): modified_by='test@example.com', documentation='', last_rotation_date=datetime(2020, 1, 1), + groups=['testgroup'] ) @@ -43,6 +44,7 @@ def archive_credential(mocker): modified_by='test@example.com', documentation='', tags=['OLD TAG'], + groups=['archivegroup'] ) @@ -62,6 +64,7 @@ def credential_list(mocker): modified_date=datetime.now(), modified_by='test@example.com', documentation='', + groups=['g1'], ), Credential( id='5678', @@ -76,6 +79,7 @@ def credential_list(mocker): modified_date=datetime.now(), modified_by='test@example.com', documentation='', + groups=['g2'], ), ] return credentials @@ -128,7 +132,7 @@ def test_get_credential(mocker, credential): ret = app.test_client().get('/v1/credentials/1234', follow_redirects=False) assert ret.status_code == 403 - def acl_module_check(resource_type, action, resource_id): + def acl_module_check(resource_type, action, resource_id, **kwargs): if action == 'metadata': if resource_id == '5678': return False @@ -381,6 +385,7 @@ def test_create_credential(mocker, credential): 'credential_pairs': {'key': 'value'}, 'name': 'shiny new key', 'tags': ['ADMIN_PRIV', 'MY_SPECIAL_TAG'], + 'groups': ['g1'] }), ) json_data = json.loads(ret.data) diff --git a/tests/unit/confidant/routes/identity_test.py b/tests/unit/confidant/routes/identity_test.py index 7655e91e..359134a6 100644 --- a/tests/unit/confidant/routes/identity_test.py +++ b/tests/unit/confidant/routes/identity_test.py @@ -1,6 +1,6 @@ from confidant.authnz import UserUnknownError from confidant.app import create_app - +from confidant import settings def test_get_user_info(mocker): mocker.patch('confidant.settings.USE_AUTH', False) @@ -25,7 +25,6 @@ def test_get_user_info_no_user(mocker): assert ret.status_code == 200 assert ret.json == {'email': None} - def test_get_client_config(mocker): def acl_module_check(resource_type, action): if resource_type == 'credential': @@ -59,7 +58,7 @@ def acl_module_check(resource_type, action): 'xsrf_cookie_name': 'CSRF_TOKEN', 'maintenance_mode': True, 'history_page_limit': 50, - 'defined_tags': [], + 'defined_tags': set(['ROTATION_EXCLUDED', 'FINANCIALLY_SENSITIVE']), 'permissions': { 'credentials': { 'list': True, @@ -79,5 +78,7 @@ def acl_module_check(resource_type, action): app = create_app() ret = app.test_client().get('/v1/client_config', follow_redirects=False) + ret_json = dict(ret.json) + ret_json['generated']['defined_tags'] = set(ret_json['generated']['defined_tags']) assert ret.status_code == 200 assert ret.json == expected