示例#1
0
文件: conftest.py 项目: zhengqz/awx
def credentialtype_external():
    external_type_inputs = {
        'fields': [{
            'id': 'url',
            'label': 'Server URL',
            'type': 'string',
            'help_text': 'The server url.'
        }, {
            'id': 'token',
            'label': 'Token',
            'type': 'string',
            'secret': True,
            'help_text': 'An access token for the server.'
        }],
        'metadata': [{
            'id': 'key',
            'label': 'Key',
            'type': 'string'
        }, {
            'id': 'version',
            'label': 'Version',
            'type': 'string'
        }],
        'required': ['url', 'token', 'key'],
    }
    external_type = CredentialType(kind='external',
                                   managed_by_tower=True,
                                   name='External Service',
                                   inputs=external_type_inputs)
    external_type.save()
    return external_type
示例#2
0
def test_update_credential_type_success(get, patch, delete, admin):
    _type = CredentialType(kind='cloud')
    _type.save()

    url = reverse('api:credential_type_detail', kwargs={'pk': _type.pk})
    response = patch(url, {'name': 'Some Other Name'}, admin)
    assert response.status_code == 200

    assert get(url, admin).data.get('name') == 'Some Other Name'
    assert delete(url, admin).status_code == 204
示例#3
0
def test_update_credential_type_in_use_xfail(patch, delete, admin):
    _type = CredentialType(kind='cloud', inputs={'fields': []})
    _type.save()
    Credential(credential_type=_type, name='My Custom Cred').save()

    url = reverse('api:credential_type_detail', kwargs={'pk': _type.pk})
    response = patch(url, {'name': 'Some Other Name'}, admin)
    assert response.status_code == 200

    url = reverse('api:credential_type_detail', kwargs={'pk': _type.pk})
    response = patch(url, {'inputs': {}}, admin)
    assert response.status_code == 403

    assert delete(url, admin).status_code == 403
def test_update_credential_type_in_use_xfail(patch, delete, admin):
    _type = CredentialType(kind='cloud', inputs={'fields': []})
    _type.save()
    Credential(credential_type=_type, name='My Custom Cred').save()

    url = reverse('api:credential_type_detail', kwargs={'pk': _type.pk})
    patch(url, {'name': 'Some Other Name'}, admin, expect=200)

    url = reverse('api:credential_type_detail', kwargs={'pk': _type.pk})
    response = patch(url, {'inputs': {}}, admin, expect=403)
    assert response.data['detail'] == 'Modifications to inputs are not allowed for credential types that are in use'

    response = delete(url, admin, expect=403)
    assert response.data['detail'] == 'Credential types that are in use cannot be deleted'
示例#5
0
def credentialtype_external():
    external_type_inputs = {
        'fields': [
            {'id': 'url', 'label': 'Server URL', 'type': 'string', 'help_text': 'The server url.'},
            {'id': 'token', 'label': 'Token', 'type': 'string', 'secret': True, 'help_text': 'An access token for the server.'},
        ],
        'metadata': [{'id': 'key', 'label': 'Key', 'type': 'string'}, {'id': 'version', 'label': 'Version', 'type': 'string'}],
        'required': ['url', 'token', 'key'],
    }

    class MockPlugin(object):
        def backend(self, **kwargs):
            return 'secret'

    with mock.patch('awx.main.models.credential.CredentialType.plugin', new_callable=PropertyMock) as mock_plugin:
        mock_plugin.return_value = MockPlugin()
        external_type = CredentialType(kind='external', managed_by_tower=True, name='External Service', inputs=external_type_inputs)
        external_type.save()
        yield external_type
示例#6
0
def forwards(apps, schema_editor):
    InventoryUpdate = apps.get_model('main', 'InventoryUpdate')
    InventorySource = apps.get_model('main', 'InventorySource')

    r = InventoryUpdate.objects.filter(source='tower').update(
        source='controller')
    if r:
        logger.warning(f'Renamed {r} tower inventory updates to controller')
    InventorySource.objects.filter(source='tower').update(source='controller')
    if r:
        logger.warning(f'Renamed {r} tower inventory sources to controller')

    CredentialType = apps.get_model('main', 'CredentialType')

    tower_type = CredentialType.objects.filter(managed_by_tower=True,
                                               namespace='tower').first()
    if tower_type is not None:
        controller_type = CredentialType.objects.filter(managed_by_tower=True,
                                                        namespace='controller',
                                                        kind='cloud').first()
        if controller_type:
            # this gets created by prior migrations in upgrade scenarios
            controller_type.delete()

        registry_type = ManagedCredentialType.registry.get('controller')
        if not registry_type:
            raise RuntimeError(
                'Excpected to find controller credential, this may need to be edited in the future!'
            )
        logger.warning(
            'Renaming the Ansible Tower credential type for existing install')
        tower_type.name = registry_type.name  # sensitive to translations
        tower_type.namespace = 'controller'  # if not done, will error setup_tower_managed_defaults
        tower_type.save(update_fields=['name', 'namespace'])

    ModernCredentialType.setup_tower_managed_defaults(apps)
示例#7
0
文件: filters.py 项目: ziozzang/awx
    def filter_queryset(self, request, queryset, view):
        try:
            # Apply filters specified via query_params. Each entry in the lists
            # below is (negate, field, value).
            and_filters = []
            or_filters = []
            chain_filters = []
            role_filters = []
            search_filters = {}
            # Can only have two values: 'AND', 'OR'
            # If 'AND' is used, an iterm must satisfy all condition to show up in the results.
            # If 'OR' is used, an item just need to satisfy one condition to appear in results.
            search_filter_relation = 'OR'
            for key, values in request.query_params.lists():
                if key in self.RESERVED_NAMES:
                    continue

                # HACK: Make job event filtering by host name mostly work even
                # when not capturing job event hosts M2M.
                if queryset.model._meta.object_name == 'JobEvent' and key.startswith(
                        'hosts__name'):
                    key = key.replace('hosts__name', 'or__host__name')
                    or_filters.append((False, 'host__name__isnull', True))

                # Custom __int filter suffix (internal use only).
                q_int = False
                if key.endswith('__int'):
                    key = key[:-5]
                    q_int = True

                # RBAC filtering
                if key == 'role_level':
                    role_filters.append(values[0])
                    continue

                # Search across related objects.
                if key.endswith('__search'):
                    if values and ',' in values[0]:
                        search_filter_relation = 'AND'
                        values = reduce(lambda list1, list2: list1 + list2,
                                        [i.split(',') for i in values])
                    for value in values:
                        search_value, new_keys = self.value_to_python(
                            queryset.model, key, force_text(value))
                        assert isinstance(new_keys, list)
                        search_filters[search_value] = new_keys
                    continue

                # Custom chain__ and or__ filters, mutually exclusive (both can
                # precede not__).
                q_chain = False
                q_or = False
                if key.startswith('chain__'):
                    key = key[7:]
                    q_chain = True
                elif key.startswith('or__'):
                    key = key[4:]
                    q_or = True

                # Custom not__ filter prefix.
                q_not = False
                if key.startswith('not__'):
                    key = key[5:]
                    q_not = True

                # Make legacy v1 Job/Template fields work for backwards compatability
                # TODO: remove after API v1 deprecation period
                if queryset.model._meta.object_name in (
                        'JobTemplate', 'Job') and key in (
                            'credential', 'vault_credential',
                            'cloud_credential', 'network_credential'
                        ) or queryset.model._meta.object_name in (
                            'InventorySource',
                            'InventoryUpdate') and key == 'credential':
                    key = 'credentials'

                # Make legacy v1 Credential fields work for backwards compatability
                # TODO: remove after API v1 deprecation period
                #
                # convert v1 `Credential.kind` queries to `Credential.credential_type__pk`
                if queryset.model._meta.object_name == 'Credential' and key == 'kind':
                    key = key.replace('kind', 'credential_type')

                    if 'ssh' in values:
                        # In 3.2, SSH and Vault became separate credential types, but in the v1 API,
                        # they're both still "kind=ssh"
                        # under the hood, convert `/api/v1/credentials/?kind=ssh` to
                        # `/api/v1/credentials/?or__credential_type=<ssh_pk>&or__credential_type=<vault_pk>`
                        values = set(values)
                        values.add('vault')
                        values = list(values)
                        q_or = True

                    for i, kind in enumerate(values):
                        if kind == 'vault':
                            type_ = CredentialType.objects.get(kind=kind)
                        else:
                            type_ = CredentialType.from_v1_kind(kind)
                        if type_ is None:
                            raise ParseError(
                                _('cannot filter on kind %s') % kind)
                        values[i] = type_.pk

                # Convert value(s) to python and add to the appropriate list.
                for value in values:
                    if q_int:
                        value = int(value)
                    value, new_key = self.value_to_python(
                        queryset.model, key, value)
                    if q_chain:
                        chain_filters.append((q_not, new_key, value))
                    elif q_or:
                        or_filters.append((q_not, new_key, value))
                    else:
                        and_filters.append((q_not, new_key, value))

            # Now build Q objects for database query filter.
            if and_filters or or_filters or chain_filters or role_filters or search_filters:
                args = []
                for n, k, v in and_filters:
                    if n:
                        args.append(~Q(**{k: v}))
                    else:
                        args.append(Q(**{k: v}))
                for role_name in role_filters:
                    if not hasattr(queryset.model, 'accessible_pk_qs'):
                        raise ParseError(
                            _('Cannot apply role_level filter to this list because its model '
                              'does not use roles for access control.'))
                    args.append(
                        Q(pk__in=queryset.model.accessible_pk_qs(
                            request.user, role_name)))
                if or_filters:
                    q = Q()
                    for n, k, v in or_filters:
                        if n:
                            q |= ~Q(**{k: v})
                        else:
                            q |= Q(**{k: v})
                    args.append(q)
                if search_filters and search_filter_relation == 'OR':
                    q = Q()
                    for term, constrains in search_filters.items():
                        for constrain in constrains:
                            q |= Q(**{constrain: term})
                    args.append(q)
                elif search_filters and search_filter_relation == 'AND':
                    for term, constrains in search_filters.items():
                        q_chain = Q()
                        for constrain in constrains:
                            q_chain |= Q(**{constrain: term})
                        queryset = queryset.filter(q_chain)
                for n, k, v in chain_filters:
                    if n:
                        q = ~Q(**{k: v})
                    else:
                        q = Q(**{k: v})
                    queryset = queryset.filter(q)
                queryset = queryset.filter(*args).distinct()
            return queryset
        except (FieldError, FieldDoesNotExist, ValueError, TypeError) as e:
            raise ParseError(e.args[0])
        except ValidationError as e:
            raise ParseError(json.dumps(e.messages, ensure_ascii=False))
示例#8
0
    def filter_queryset(self, request, queryset, view):
        try:
            # Apply filters specified via query_params. Each entry in the lists
            # below is (negate, field, value).
            and_filters = []
            or_filters = []
            chain_filters = []
            role_filters = []
            search_filters = []
            for key, values in request.query_params.lists():
                if key in self.RESERVED_NAMES:
                    continue

                # HACK: Make job event filtering by host name mostly work even
                # when not capturing job event hosts M2M.
                if queryset.model._meta.object_name == 'JobEvent' and key.startswith(
                        'hosts__name'):
                    key = key.replace('hosts__name', 'or__host__name')
                    or_filters.append((False, 'host__name__isnull', True))

                # Custom __int filter suffix (internal use only).
                q_int = False
                if key.endswith('__int'):
                    key = key[:-5]
                    q_int = True

                # RBAC filtering
                if key == 'role_level':
                    role_filters.append(values[0])
                    continue

                # Search across related objects.
                if key.endswith('__search'):
                    for value in values:
                        for search_term in force_text(value).replace(
                                ',', ' ').split():
                            search_value, new_keys = self.value_to_python(
                                queryset.model, key, search_term)
                            assert isinstance(new_keys, list)
                            for new_key in new_keys:
                                search_filters.append((new_key, search_value))
                    continue

                # Custom chain__ and or__ filters, mutually exclusive (both can
                # precede not__).
                q_chain = False
                q_or = False
                if key.startswith('chain__'):
                    key = key[7:]
                    q_chain = True
                elif key.startswith('or__'):
                    key = key[4:]
                    q_or = True

                # Custom not__ filter prefix.
                q_not = False
                if key.startswith('not__'):
                    key = key[5:]
                    q_not = True

                # Make legacy v1 Job/Template fields work for backwards compatability
                # TODO: remove after API v1 deprecation period
                if queryset.model._meta.object_name in (
                        'JobTemplate',
                        'Job') and key in ('credential', 'vault_credential',
                                           'cloud_credential',
                                           'network_credential'):
                    key = 'credentials'

                # Make legacy v1 Credential fields work for backwards compatability
                # TODO: remove after API v1 deprecation period
                #
                # convert v1 `Credential.kind` queries to `Credential.credential_type__pk`
                if queryset.model._meta.object_name == 'Credential' and key == 'kind':
                    key = key.replace('kind', 'credential_type')

                    if 'ssh' in values:
                        # In 3.2, SSH and Vault became separate credential types, but in the v1 API,
                        # they're both still "kind=ssh"
                        # under the hood, convert `/api/v1/credentials/?kind=ssh` to
                        # `/api/v1/credentials/?or__credential_type=<ssh_pk>&or__credential_type=<vault_pk>`
                        values = set(values)
                        values.add('vault')
                        values = list(values)
                        q_or = True

                    for i, kind in enumerate(values):
                        if kind == 'vault':
                            type_ = CredentialType.objects.get(kind=kind)
                        else:
                            type_ = CredentialType.from_v1_kind(kind)
                        if type_ is None:
                            raise ParseError(
                                _('cannot filter on kind %s') % kind)
                        values[i] = type_.pk

                # Convert value(s) to python and add to the appropriate list.
                for value in values:
                    if q_int:
                        value = int(value)
                    value, new_key = self.value_to_python(
                        queryset.model, key, value)
                    if q_chain:
                        chain_filters.append((q_not, new_key, value))
                    elif q_or:
                        or_filters.append((q_not, new_key, value))
                    else:
                        and_filters.append((q_not, new_key, value))

            # Now build Q objects for database query filter.
            if and_filters or or_filters or chain_filters or role_filters or search_filters:
                args = []
                for n, k, v in and_filters:
                    if n:
                        args.append(~Q(**{k: v}))
                    else:
                        args.append(Q(**{k: v}))
                for role_name in role_filters:
                    args.append(
                        Q(pk__in=RoleAncestorEntry.objects.filter(
                            ancestor__in=request.user.roles.all(),
                            content_type_id=ContentType.objects.get_for_model(
                                queryset.model).id,
                            role_field=role_name).values_list(
                                'object_id').distinct()))
                if or_filters:
                    q = Q()
                    for n, k, v in or_filters:
                        if n:
                            q |= ~Q(**{k: v})
                        else:
                            q |= Q(**{k: v})
                    args.append(q)
                if search_filters:
                    q = Q()
                    for k, v in search_filters:
                        q |= Q(**{k: v})
                    args.append(q)
                for n, k, v in chain_filters:
                    if n:
                        q = ~Q(**{k: v})
                    else:
                        q = Q(**{k: v})
                    queryset = queryset.filter(q)
                queryset = queryset.filter(*args).distinct()
            return queryset
        except (FieldError, FieldDoesNotExist, ValueError, TypeError) as e:
            raise ParseError(e.args[0])
        except ValidationError as e:
            raise ParseError(json.dumps(e.messages, ensure_ascii=False))