def get_fields(self):
     fields = super(SettingSingletonSerializer, self).get_fields()
     try:
         category_slug = self.context['view'].kwargs.get('category_slug', 'all')
     except (KeyError, AttributeError):
         category_slug = ''
     for key in settings_registry.get_registered_settings(category_slug=category_slug):
         if self.instance and not hasattr(self.instance, key):
             continue
         extra_kwargs = {}
         # Make LICENSE read-only here; LICENSE is only updated via /api/v2/config/
         if key == 'LICENSE':
             extra_kwargs['read_only'] = True
         field = settings_registry.get_setting_field(key, mixin_class=SettingFieldMixin, for_user=bool(category_slug == 'user'), **extra_kwargs)
         fields[key] = field
     return fields
Exemple #2
0
    def perform_destroy(self, instance):
        settings_change_list = []
        for setting in self.get_queryset().exclude(key='LICENSE'):
            if settings_registry.get_setting_field(setting.key).read_only:
                continue
            setting.delete()
            settings_change_list.append(setting.key)
        if settings_change_list and 'migrate_to_database_settings' not in sys.argv:
            handle_setting_changes.delay(settings_change_list)

        # When TOWER_URL_BASE is deleted from the API, reset it to the hostname
        # used to make the request as a default.
        if hasattr(instance, 'TOWER_URL_BASE'):
            url = '{}://{}'.format(self.request.scheme,
                                   self.request.get_host())
            if settings.TOWER_URL_BASE != url:
                settings.TOWER_URL_BASE = url
Exemple #3
0
    def _discover_settings(self, registered_settings):
        if self.verbosity >= 1:
            self.stdout.write(
                self.style.HEADING(
                    'Discovering settings to be migrated and commented:'))

        # Determine which settings need to be commented/migrated.
        to_migrate = collections.OrderedDict()
        to_comment = collections.OrderedDict()
        patterns = self._get_settings_file_patterns()

        for name in registered_settings:
            comment_error, migrate_error = None, None
            files_to_comment = []
            try:
                files_to_comment = self._check_if_needs_comment(patterns, name)
            except Exception as e:
                comment_error = 'Error commenting {0}: {1!r}'.format(name, e)
                if not self.skip_errors:
                    raise CommandError(comment_error)
            if files_to_comment:
                to_comment[name] = files_to_comment
            migrate_value = empty
            if files_to_comment:
                migrate_value = self._check_if_needs_migration(name)
                if migrate_value is not empty:
                    field = settings_registry.get_setting_field(name)
                    assert not field.read_only
                    try:
                        data = field.to_representation(migrate_value)
                        setting_value = field.run_validation(data)
                        db_value = field.to_representation(setting_value)
                        to_migrate[name] = db_value
                    except Exception as e:
                        to_comment.pop(name)
                        migrate_error = 'Unable to assign value {0!r} to setting "{1}: {2!s}".'.format(
                            migrate_value, name, e)
                        if not self.skip_errors:
                            raise CommandError(migrate_error)
            self._display_tbd(name, files_to_comment, migrate_value,
                              comment_error, migrate_error)
        if self.verbosity == 1 and not to_migrate and not to_comment:
            self.stdout.write('  No settings found to migrate or comment!')
        return (to_migrate, to_comment)
Exemple #4
0
    def post(self, request, *args, **kwargs):
        defaults = dict()
        for key in settings_registry.get_registered_settings(
                category_slug='logging'):
            try:
                defaults[key] = settings_registry.get_setting_field(
                    key).get_default()
            except serializers.SkipField:
                defaults[key] = None
        obj = type('Settings', (object, ), defaults)()
        serializer = self.get_serializer(obj, data=request.data)
        serializer.is_valid(raise_exception=True)
        # Special validation specific to logging test.
        errors = {}
        for key in ['LOG_AGGREGATOR_TYPE', 'LOG_AGGREGATOR_HOST']:
            if not request.data.get(key, ''):
                errors[key] = 'This field is required.'
        if errors:
            raise ValidationError(errors)

        if request.data.get('LOG_AGGREGATOR_PASSWORD',
                            '').startswith('$encrypted$'):
            serializer.validated_data['LOG_AGGREGATOR_PASSWORD'] = getattr(
                settings, 'LOG_AGGREGATOR_PASSWORD', '')

        try:

            class MockSettings:
                pass

            mock_settings = MockSettings()
            for k, v in serializer.validated_data.items():
                setattr(mock_settings, k, v)
            mock_settings.LOG_AGGREGATOR_LEVEL = 'DEBUG'
            if mock_settings.LOG_AGGREGATOR_PROTOCOL.upper() == 'UDP':
                UDPHandler.perform_test(mock_settings)
                return Response(status=status.HTTP_201_CREATED)
            else:
                BaseHTTPSHandler.perform_test(mock_settings)
        except LoggingConnectivityException as e:
            return Response({'error': str(e)},
                            status=status.HTTP_500_INTERNAL_SERVER_ERROR)
        return Response(status=status.HTTP_200_OK)
Exemple #5
0
 def _check_if_needs_migration(self, setting):
     # Check whether the current value differs from the default.
     default_value = settings.DEFAULTS_SNAPSHOT.get(setting, empty)
     if default_value is empty and setting != 'LICENSE':
         field = settings_registry.get_setting_field(setting,
                                                     read_only=True)
         try:
             default_value = field.get_default()
         except SkipField:
             pass
     current_value = getattr(settings, setting, empty)
     if setting == 'CUSTOM_LOGIN_INFO' and current_value in {empty, ''}:
         local_settings_file = self._get_local_settings_file()
         try:
             if os.path.exists(local_settings_file):
                 local_settings = json.load(open(local_settings_file))
                 current_value = local_settings.get('custom_login_info', '')
         except Exception as e:
             if not self.skip_errors:
                 raise CommandError(
                     'Error reading custom login info from {0}: {1!r}'.
                     format(local_settings_file, e))
     if setting == 'CUSTOM_LOGO' and current_value in {empty, ''}:
         custom_logo_file = self._get_custom_logo_file()
         try:
             if os.path.exists(custom_logo_file):
                 custom_logo_data = open(custom_logo_file).read()
                 if custom_logo_data:
                     current_value = 'data:image/png;base64,{}'.format(
                         base64.b64encode(custom_logo_data))
                 else:
                     current_value = ''
         except Exception as e:
             if not self.skip_errors:
                 raise CommandError(
                     'Error reading custom logo from {0}: {1!r}'.format(
                         custom_logo_file, e))
     if current_value != default_value:
         if current_value is empty:
             current_value = None
         return current_value
     return empty
Exemple #6
0
 def get_object(self):
     settings_qs = self.get_queryset()
     registered_settings = settings_registry.get_registered_settings(
         category_slug=self.category_slug, )
     all_settings = {}
     for setting in settings_qs:
         all_settings[setting.key] = setting.value
     for key in registered_settings:
         if key in all_settings or self.category_slug == 'changed':
             continue
         try:
             field = settings_registry.get_setting_field(
                 key, for_user=bool(self.category_slug == 'user'))
             all_settings[key] = field.get_default()
         except serializers.SkipField:
             all_settings[key] = None
     all_settings[
         'user'] = self.request.user if self.category_slug == 'user' else None
     obj = type('Settings', (object, ), all_settings)()
     self.check_object_permissions(self.request, obj)
     return obj
Exemple #7
0
 def get_object(self):
     settings_qs = self.get_queryset()
     registered_settings = settings_registry.get_registered_settings(
         category_slug=self.category_slug,
         features_enabled=get_licensed_features(),
         slugs_to_ignore=VERSION_SPECIFIC_CATEGORIES_TO_EXCLUDE[
             get_request_version(self.request)])
     all_settings = {}
     for setting in settings_qs:
         all_settings[setting.key] = setting.value
     for key in registered_settings:
         if key in all_settings or self.category_slug == 'changed':
             continue
         try:
             field = settings_registry.get_setting_field(
                 key, for_user=bool(self.category_slug == 'user'))
             all_settings[key] = field.get_default()
         except serializers.SkipField:
             all_settings[key] = None
     all_settings[
         'user'] = self.request.user if self.category_slug == 'user' else None
     obj = type('Settings', (object, ), all_settings)()
     self.check_object_permissions(self.request, obj)
     return obj
Exemple #8
0
    def finalize_response(self, request, response, *args, **kwargs):
        """
        Log warning for 400 requests.  Add header with elapsed time.
        """
        #
        # If the URL was rewritten, and we get a 404, we should entirely
        # replace the view in the request context with an ApiErrorView()
        # Without this change, there will be subtle differences in the BrowseableAPIRenderer
        #
        # These differences could provide contextual clues which would allow
        # anonymous users to determine if usernames were valid or not
        # (e.g., if an anonymous user visited `/api/v2/users/valid/`, and got a 404,
        # but also saw that the page heading said "User Detail", they might notice
        # that's a difference in behavior from a request to `/api/v2/users/not-valid/`, which
        # would show a page header of "Not Found").  Changing the view here
        # guarantees that the rendered response will look exactly like the response
        # when you visit a URL that has no matching URL paths in `awx.api.urls`.
        #
        if response.status_code == 404 and 'awx.named_url_rewritten' in request.environ:
            self.headers.pop('Allow', None)
            response = super(APIView,
                             self).finalize_response(request, response, *args,
                                                     **kwargs)
            view = ApiErrorView()
            setattr(view, 'request', request)
            response.renderer_context['view'] = view
            return response

        if response.status_code >= 400:
            msg_data = {
                'status_code': response.status_code,
                'user_name': request.user,
                'url_path': request.path,
                'remote_addr': request.META.get('REMOTE_ADDR', None),
            }

            if type(response.data) is dict:
                msg_data['error'] = response.data.get('error',
                                                      response.status_text)
            elif type(response.data) is list:
                msg_data['error'] = ", ".join(
                    list(
                        map(lambda x: x.get('error', response.status_text),
                            response.data)))
            else:
                msg_data['error'] = response.status_text

            try:
                status_msg = getattr(
                    settings, 'API_400_ERROR_LOG_FORMAT').format(**msg_data)
            except Exception as e:
                if getattr(settings, 'API_400_ERROR_LOG_FORMAT', None):
                    logger.error(
                        "Unable to format API_400_ERROR_LOG_FORMAT setting, defaulting log message: {}"
                        .format(e))
                status_msg = settings_registry.get_setting_field(
                    'API_400_ERROR_LOG_FORMAT').get_default().format(
                        **msg_data)

            if hasattr(self, '__init_request_error__'):
                response = self.handle_exception(self.__init_request_error__)
            if response.status_code == 401:
                response.data['detail'] += _(
                    ' To establish a login session, visit') + ' /api/login/.'
                logger.info(status_msg)
            else:
                logger.warning(status_msg)

        response = super(APIView,
                         self).finalize_response(request, response, *args,
                                                 **kwargs)
        time_started = getattr(self, 'time_started', None)
        response['X-API-Product-Version'] = get_awx_version()
        response['X-API-Product-Name'] = server_product_name()

        response['X-API-Node'] = settings.CLUSTER_HOST_ID
        if time_started:
            time_elapsed = time.time() - self.time_started
            response['X-API-Time'] = '%0.3fs' % time_elapsed
        if getattr(settings, 'SQL_DEBUG', False):
            queries_before = getattr(self, 'queries_before', 0)
            q_times = [
                float(q['time']) for q in connection.queries[queries_before:]
            ]
            response['X-API-Query-Count'] = len(q_times)
            response['X-API-Query-Time'] = '%0.3fs' % sum(q_times)

        if getattr(self, 'deprecated', False):
            response[
                'Warning'] = '299 awx "This resource has been deprecated and will be removed in a future release."'  # noqa

        return response
Exemple #9
0
    def _migrate_settings(self, registered_settings):
        patterns = self._get_settings_file_patterns()

        # Determine which settings need to be commented/migrated.
        if self.verbosity >= 1:
            self.stdout.write(
                self.style.HEADING(
                    'Discovering settings to be migrated and commented:'))
        to_migrate = collections.OrderedDict()
        to_comment = collections.OrderedDict()
        for name in registered_settings:
            comment_error, migrate_error = None, None
            files_to_comment = []
            try:
                files_to_comment = self._check_if_needs_comment(patterns, name)
            except Exception as e:
                comment_error = 'Error commenting {0}: {1!r}'.format(name, e)
                if not self.skip_errors:
                    raise CommandError(comment_error)
            if files_to_comment:
                to_comment[name] = files_to_comment
            migrate_value = empty
            if files_to_comment:
                migrate_value = self._check_if_needs_migration(name)
                if migrate_value is not empty:
                    field = settings_registry.get_setting_field(name)
                    assert not field.read_only
                    try:
                        data = field.to_representation(migrate_value)
                        setting_value = field.run_validation(data)
                        db_value = field.to_representation(setting_value)
                        to_migrate[name] = db_value
                    except Exception as e:
                        to_comment.pop(name)
                        migrate_error = 'Unable to assign value {0!r} to setting "{1}: {2!s}".'.format(
                            migrate_value, name, e)
                        if not self.skip_errors:
                            raise CommandError(migrate_error)
            self._display_tbd(name, files_to_comment, migrate_value,
                              comment_error, migrate_error)
        if self.verbosity == 1 and not to_migrate and not to_comment:
            self.stdout.write('  No settings found to migrate or comment!')

        # Now migrate those settings to the database.
        if self.verbosity >= 1:
            if self.dry_run:
                self.stdout.write(
                    self.style.HEADING(
                        'Migrating settings to database (dry-run):'))
            else:
                self.stdout.write(
                    self.style.HEADING('Migrating settings to database:'))
            if not to_migrate:
                self.stdout.write('  No settings to migrate!')
        for name, db_value in to_migrate.items():
            display_value = json.dumps(db_value, indent=4)
            setting = Setting.objects.filter(
                key=name, user__isnull=True).order_by('pk').first()
            action = 'No Change'
            if not setting:
                action = 'Migrated'
                if not self.dry_run:
                    Setting.objects.create(key=name, user=None, value=db_value)
            elif setting.value != db_value or type(
                    setting.value) != type(db_value):
                action = 'Updated'
                if not self.dry_run:
                    setting.value = db_value
                    setting.save(update_fields=['value'])
            self._display_migrate(name, action, display_value)

        # Now comment settings in settings files.
        if self.verbosity >= 1:
            if bool(self.dry_run or self.no_comment):
                self.stdout.write(
                    self.style.HEADING(
                        'Commenting settings in files (dry-run):'))
            else:
                self.stdout.write(
                    self.style.HEADING('Commenting settings in files:'))
            if not to_comment:
                self.stdout.write('  No settings to comment!')
        if to_comment:
            to_comment_patterns = []
            license_file_to_comment = None
            local_settings_file_to_comment = None
            custom_logo_file_to_comment = None
            for files_to_comment in to_comment.values():
                for file_to_comment in files_to_comment:
                    if file_to_comment == self._get_license_file():
                        license_file_to_comment = file_to_comment
                    elif file_to_comment == self._get_local_settings_file():
                        local_settings_file_to_comment = file_to_comment
                    elif file_to_comment == self._get_custom_logo_file():
                        custom_logo_file_to_comment = file_to_comment
                    elif file_to_comment not in to_comment_patterns:
                        to_comment_patterns.append(file_to_comment)
            # Run once in dry-run mode to catch any errors from updating the files.
            diffs = comment_assignments(to_comment_patterns,
                                        to_comment.keys(),
                                        dry_run=True,
                                        backup_suffix=self.backup_suffix)
            # Then, if really updating, run again.
            if not self.dry_run and not self.no_comment:
                diffs = comment_assignments(to_comment_patterns,
                                            to_comment.keys(),
                                            dry_run=False,
                                            backup_suffix=self.backup_suffix)
                if license_file_to_comment:
                    diffs.extend(self._comment_license_file(dry_run=False))
                if local_settings_file_to_comment:
                    diffs.extend(
                        self._comment_local_settings_file(dry_run=False))
                if custom_logo_file_to_comment:
                    diffs.extend(self._comment_custom_logo_file(dry_run=False))
            self._display_comment(diffs)