+
+ The main goal of this application is to lower the initial barrier of using SCAP.
+diff --git a/scap_workbench.desktop b/org.open_scap.scap_workbench.desktop
+similarity index 67%
+rename from scap_workbench.desktop
+rename to org.open_scap.scap_workbench.desktop
+index 5b4bb899..0d568b1d 100644
+--- a/scap_workbench.desktop
++++ b/org.open_scap.scap_workbench.desktop
+@@ -1,11 +1,11 @@
+ [Desktop Entry]
++Version=1.0
+ Type=Application
+ Name=SCAP Workbench
+ GenericName=SCAP Scanner and Profile Editor
+-Comment=GUI tool that allows scanning both local and remote computers using SCAP content of your choice.
++Comment=GUI tool that allows scanning both local and remote computers using SCAP content of your choice
+ TryExec=scap-workbench
+ Exec=scap-workbench
+ Icon=scap-workbench
+-Categories=System;
+-Keywords=scap;
+-X-Desktop-File-Install-Version=0.3
++Categories=System;Security;
++Keywords=SCAP;
diff --git a/SOURCES/scap-workbench-1.2.2-generate-result-based-remediation-from-tailored-profile.patch b/SOURCES/scap-workbench-1.2.2-generate-result-based-remediation-from-tailored-profile.patch
new file mode 100644
index 0000000..23c1453
--- /dev/null
+++ b/SOURCES/scap-workbench-1.2.2-generate-result-based-remediation-from-tailored-profile.patch
@@ -0,0 +1,519 @@
+From 69e988df963cb184062814e75c737fe080f303df Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?=
+Date: Tue, 25 Feb 2020 10:50:46 +0100
+Subject: [PATCH] Generate result-based remediation from tailored profile
+
+Users can generate remediation script from scan results
+of a tailored profile.
+
+Unfortunately, the current design of SCAP Workbench doesn't allow
+a clear way of doing this. The scan is run in a separated oscap
+process. SCAP Workbench doesn't have access to oscap internal
+xccdf_session which creates the ARF. It can't obtain the Tailoring
+component reference ID.
+
+Instead, we will save the tailoring document to a temporary file
+and use the temporary file when generating the remediation.
+
+Resolves: RHBZ#1640715
+---
+ include/RemediationRoleSaver.h | 19 +++++++++-------
+ include/ResultViewer.h | 2 ++
+ src/RemediationRoleSaver.cpp | 40 ++++++++++++++++++++++------------
+ src/ResultViewer.cpp | 9 +++++---
+ 4 files changed, 45 insertions(+), 25 deletions(-)
+
+diff --git a/include/RemediationRoleSaver.h b/include/RemediationRoleSaver.h
+index 1681b901..dfeea0c9 100644
+--- a/include/RemediationRoleSaver.h
++++ b/include/RemediationRoleSaver.h
+@@ -98,33 +98,35 @@ class PuppetProfileRemediationSaver : public ProfileBasedRemediationSaver
+ class ResultBasedProcessRemediationSaver : public RemediationSaverBase
+ {
+ public:
+- ResultBasedProcessRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents,
++ ResultBasedProcessRemediationSaver(
++ QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath,
+ const QString& saveMessage, const QString& filetypeExtension, const QString& filetypeTemplate, const QString& fixType);
+
+ private:
+ virtual void saveToFile(const QString& filename);
+ SpacelessQTemporaryFile mArfFile;
++ QString tailoring;
+ };
+
+
+ class BashResultRemediationSaver : public ResultBasedProcessRemediationSaver
+ {
+ public:
+- BashResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents);
++ BashResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath);
+ };
+
+
+ class AnsibleResultRemediationSaver : public ResultBasedProcessRemediationSaver
+ {
+ public:
+- AnsibleResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents);
++ AnsibleResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath);
+ };
+
+
+ class PuppetResultRemediationSaver : public ResultBasedProcessRemediationSaver
+ {
+ public:
+- PuppetResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents);
++ PuppetResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath);
+ };
+
+ #else // i.e. SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION is defined
+@@ -133,33 +135,34 @@ class PuppetResultRemediationSaver : public ResultBasedProcessRemediationSaver
+ class ResultBasedLibraryRemediationSaver : public RemediationSaverBase
+ {
+ public:
+- ResultBasedLibraryRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents,
++ ResultBasedLibraryRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath,
+ const QString& saveMessage, const QString& filetypeExtension, const QString& filetypeTemplate, const QString& fixType);
+
+ private:
+ virtual void saveToFile(const QString& filename);
+ SpacelessQTemporaryFile mArfFile;
++ QString tailoring;
+ };
+
+
+ class BashResultRemediationSaver : public ResultBasedLibraryRemediationSaver
+ {
+ public:
+- BashResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents);
++ BashResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath);
+ };
+
+
+ class AnsibleResultRemediationSaver : public ResultBasedLibraryRemediationSaver
+ {
+ public:
+- AnsibleResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents);
++ AnsibleResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath);
+ };
+
+
+ class PuppetResultRemediationSaver : public ResultBasedLibraryRemediationSaver
+ {
+ public:
+- PuppetResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents);
++ PuppetResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath);
+ };
+
+ #endif // SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION
+diff --git a/include/ResultViewer.h b/include/ResultViewer.h
+index a6da89da..2ec8d576 100644
+--- a/include/ResultViewer.h
++++ b/include/ResultViewer.h
+@@ -99,6 +99,8 @@ class ResultViewer : public QWidget
+ /// If user requests to open the file via desktop services
+ SpacelessQTemporaryFile* mReportFile;
+ QByteArray mARF;
++
++ QString tailoringFilePath;
+ };
+
+ #endif
+diff --git a/src/RemediationRoleSaver.cpp b/src/RemediationRoleSaver.cpp
+index 900a221d..28389dbb 100644
+--- a/src/RemediationRoleSaver.cpp
++++ b/src/RemediationRoleSaver.cpp
+@@ -164,7 +164,8 @@ PuppetProfileRemediationSaver::PuppetProfileRemediationSaver(QWidget* parentWind
+ {}
+
+ #ifndef SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION
+-ResultBasedProcessRemediationSaver::ResultBasedProcessRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents,
++ResultBasedProcessRemediationSaver::ResultBasedProcessRemediationSaver(
++ QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath,
+ const QString& saveMessage, const QString& filetypeExtension, const QString& filetypeTemplate, const QString& fixType):
+ RemediationSaverBase(parentWindow, saveMessage, filetypeExtension, filetypeTemplate, fixType)
+ {
+@@ -172,6 +173,7 @@ ResultBasedProcessRemediationSaver::ResultBasedProcessRemediationSaver(QWidget*
+ mArfFile.open();
+ mArfFile.write(arfContents);
+ mArfFile.close();
++ tailoring = tailoringFilePath;
+ }
+
+ void ResultBasedProcessRemediationSaver::saveToFile(const QString& filename)
+@@ -191,6 +193,11 @@ void ResultBasedProcessRemediationSaver::saveToFile(const QString& filename)
+ args.append("--result-id");
+ args.append("");
+
++ if (!tailoring.isNull()) {
++ args.append("--tailoring-file");
++ args.append(tailoring.toUtf8().constData());
++ }
++
+ args.append(mArfFile.fileName());
+
+ // Launching a process and going through its output is something we do already in OscapScannerLocal::evaluate()
+@@ -222,23 +229,24 @@ void ResultBasedProcessRemediationSaver::saveToFile(const QString& filename)
+ }
+ }
+
+-BashResultRemediationSaver::BashResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents):
+- ResultBasedProcessRemediationSaver(parentWindow, arfContents,
++BashResultRemediationSaver::BashResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath):
++ ResultBasedProcessRemediationSaver(parentWindow, arfContents, tailoringFilePath,
+ bashSaveMessage, bashFiletypeExtension, bashFiletypeTemplate, bashFixTemplate)
+ {}
+
+-AnsibleResultRemediationSaver::AnsibleResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents):
+- ResultBasedProcessRemediationSaver(parentWindow, arfContents,
++AnsibleResultRemediationSaver::AnsibleResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath):
++ ResultBasedProcessRemediationSaver(parentWindow, arfContents, tailoringFilePath,
+ ansibleSaveMessage, ansibleFiletypeExtension, ansibleFiletypeTemplate, ansibleFixType)
+ {}
+
+-PuppetResultRemediationSaver::PuppetResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents):
+- ResultBasedProcessRemediationSaver(parentWindow, arfContents,
++PuppetResultRemediationSaver::PuppetResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath):
++ ResultBasedProcessRemediationSaver(parentWindow, arfContents, tailoringFilePath,
+ puppetSaveMessage, puppetFiletypeExtension, puppetFiletypeTemplate, puppetFixType)
+ {}
+
+ #else // i.e. SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION is defined
+-ResultBasedLibraryRemediationSaver::ResultBasedLibraryRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents,
++ResultBasedLibraryRemediationSaver::ResultBasedLibraryRemediationSaver(
++ QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath,
+ const QString& saveMessage, const QString& filetypeExtension, const QString& filetypeTemplate, const QString& fixType):
+ RemediationSaverBase(parentWindow, saveMessage, filetypeExtension, filetypeTemplate, fixType)
+ {
+@@ -246,6 +254,7 @@ ResultBasedLibraryRemediationSaver::ResultBasedLibraryRemediationSaver(QWidget*
+ mArfFile.open();
+ mArfFile.write(arfContents);
+ mArfFile.close();
++ tailoring = tailoringFilePath;
+ }
+
+ void ResultBasedLibraryRemediationSaver::saveToFile(const QString& filename)
+@@ -282,6 +291,9 @@ void ResultBasedLibraryRemediationSaver::saveToFile(const QString& filename)
+
+ if (session == NULL)
+ throw std::runtime_error("Couldn't get XCCDF session from the report source");
++ if (!tailoring.isNull()) {
++ xccdf_session_set_user_tailoring_file(session, tailoring.toUtf8().constData());
++ }
+
+ xccdf_session_set_loading_flags(session, XCCDF_SESSION_LOAD_XCCDF);
+ if (xccdf_session_load(session) != 0)
+@@ -316,18 +328,18 @@ void ResultBasedLibraryRemediationSaver::saveToFile(const QString& filename)
+ }
+ }
+
+-BashResultRemediationSaver::BashResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents):
+- ResultBasedLibraryRemediationSaver(parentWindow, arfContents,
++BashResultRemediationSaver::BashResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath):
++ ResultBasedLibraryRemediationSaver(parentWindow, arfContents, tailoringFilePath,
+ bashSaveMessage, bashFiletypeExtension, bashFiletypeTemplate, bashFixTemplate)
+ {}
+
+-AnsibleResultRemediationSaver::AnsibleResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents):
+- ResultBasedLibraryRemediationSaver(parentWindow, arfContents,
++AnsibleResultRemediationSaver::AnsibleResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath):
++ ResultBasedLibraryRemediationSaver(parentWindow, arfContents, tailoringFilePath,
+ ansibleSaveMessage, ansibleFiletypeExtension, ansibleFiletypeTemplate, ansibleFixType)
+ {}
+
+-PuppetResultRemediationSaver::PuppetResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents):
+- ResultBasedLibraryRemediationSaver(parentWindow, arfContents,
++PuppetResultRemediationSaver::PuppetResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath):
++ ResultBasedLibraryRemediationSaver(parentWindow, arfContents, tailoringFilePath,
+ puppetSaveMessage, puppetFiletypeExtension, puppetFiletypeTemplate, puppetFixType)
+ {}
+
+diff --git a/src/ResultViewer.cpp b/src/ResultViewer.cpp
+index 1e730e99..c5444746 100644
+--- a/src/ResultViewer.cpp
++++ b/src/ResultViewer.cpp
+@@ -114,6 +114,9 @@ void ResultViewer::loadContent(Scanner* scanner)
+ if (mInputBaseName.endsWith("-xccdf"))
+ mInputBaseName.chop(QString("-xccdf").length());
+ }
++ if (session->isSelectedProfileTailoring()) {
++ tailoringFilePath = session->getTailoringFilePath();
++ }
+
+ mReport.clear();
+ scanner->getReport(mReport);
+@@ -173,19 +176,19 @@ void ResultViewer::openReport()
+
+ void ResultViewer::generateBashRemediationRole()
+ {
+- BashResultRemediationSaver remediation(this, mARF);
++ BashResultRemediationSaver remediation(this, mARF, tailoringFilePath);
+ remediation.selectFilenameAndSaveRole();
+ }
+
+ void ResultViewer::generateAnsibleRemediationRole()
+ {
+- AnsibleResultRemediationSaver remediation(this, mARF);
++ AnsibleResultRemediationSaver remediation(this, mARF, tailoringFilePath);
+ remediation.selectFilenameAndSaveRole();
+ }
+
+ void ResultViewer::generatePuppetRemediationRole()
+ {
+- PuppetResultRemediationSaver remediation(this, mARF);
++ PuppetResultRemediationSaver remediation(this, mARF, tailoringFilePath);
+ remediation.selectFilenameAndSaveRole();
+ }
+
+From e97539b824202e91c9cd551316836935046e53ca Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?=
+Date: Mon, 2 Mar 2020 14:14:49 +0100
+Subject: [PATCH] Use only library calls to generate remediation
+
+Removes CMake option
+SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION
+and removes the code that is used when this option is not set.
+That means the remediations will be generated using libopenscap
+library calls. The removed code executed "oscap" command to
+do the same thing.
+---
+ CMakeLists.txt | 8 +---
+ include/Config.h.in | 1 -
+ include/RemediationRoleSaver.h | 41 ----------------
+ src/RemediationRoleSaver.cpp | 87 ----------------------------------
+ 4 files changed, 2 insertions(+), 135 deletions(-)
+
+diff --git a/CMakeLists.txt b/CMakeLists.txt
+index 060df785..8d16d3c5 100644
+--- a/CMakeLists.txt
++++ b/CMakeLists.txt
+@@ -49,12 +49,8 @@ endif()
+ # Local scanning tools
+ option(SCAP_WORKBENCH_LOCAL_SCAN_ENABLED "If enabled, scanning of local machine is possible from workbench. Else the option is disabled in the GUI." TRUE)
+
+-option(SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION "If enabled, result-based remediation roles will be generated by calls to the libopenscap library (instead of being generated by the oscap subprocess). Requires openscap>=1.2.16" FALSE)
+-
+-if (SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION)
+- if(${OPENSCAP_VERSION_MAJOR} LESS 2 AND ${OPENSCAP_VERSION_MINOR} LESS 3 AND ${OPENSCAP_VERSION_PATCH} LESS 16) # i.e. oscap<1.2.16
+- message(FATAL_ERROR "Library-powered generation of result-based remediation roles is supported only if you have oscap>=1.2.16, whereas you have oscap==${OPENSCAP_VERSION}")
+- endif()
++if(${OPENSCAP_VERSION_MAJOR} LESS 2 AND ${OPENSCAP_VERSION_MINOR} LESS 3 AND ${OPENSCAP_VERSION_PATCH} LESS 16) # i.e. oscap<1.2.16
++ message(FATAL_ERROR "Library-powered generation of result-based remediation roles is supported only if you have oscap>=1.2.16, whereas you have oscap==${OPENSCAP_VERSION}")
+ endif()
+
+ find_program(NICE_EXECUTABLE NAMES nice) # fully optional, local scan still available when missing
+diff --git a/include/Config.h.in b/include/Config.h.in
+index 21b3f373..a9bc718f 100644
+--- a/include/Config.h.in
++++ b/include/Config.h.in
+@@ -40,7 +40,6 @@
+ #define SCAP_WORKBENCH_LOCAL_PKEXEC_OSCAP_PATH "@CMAKE_INSTALL_FULL_LIBEXECDIR@/scap-workbench-pkexec-oscap.sh"
+ #define SCAP_WORKBENCH_LOCAL_RPM_EXTRACT_PATH "@CMAKE_INSTALL_FULL_LIBEXECDIR@/scap-workbench-rpm-extract.sh"
+ #define SCAP_WORKBENCH_REMOTE_OSCAP_PATH "oscap"
+-#cmakedefine SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION
+ #cmakedefine SCAP_WORKBENCH_LOCAL_SSH_FOUND
+ #define SCAP_WORKBENCH_LOCAL_SSH_PATH "@SSH_EXECUTABLE@"
+ #cmakedefine SCAP_WORKBENCH_LOCAL_SETSID_FOUND
+diff --git a/include/RemediationRoleSaver.h b/include/RemediationRoleSaver.h
+index dfeea0c9..95938db8 100644
+--- a/include/RemediationRoleSaver.h
++++ b/include/RemediationRoleSaver.h
+@@ -93,45 +93,6 @@ class PuppetProfileRemediationSaver : public ProfileBasedRemediationSaver
+ };
+
+
+-#ifndef SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION
+-/// Base for all result-based remediation generators that uses oscap process
+-class ResultBasedProcessRemediationSaver : public RemediationSaverBase
+-{
+- public:
+- ResultBasedProcessRemediationSaver(
+- QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath,
+- const QString& saveMessage, const QString& filetypeExtension, const QString& filetypeTemplate, const QString& fixType);
+-
+- private:
+- virtual void saveToFile(const QString& filename);
+- SpacelessQTemporaryFile mArfFile;
+- QString tailoring;
+-};
+-
+-
+-class BashResultRemediationSaver : public ResultBasedProcessRemediationSaver
+-{
+- public:
+- BashResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath);
+-};
+-
+-
+-class AnsibleResultRemediationSaver : public ResultBasedProcessRemediationSaver
+-{
+- public:
+- AnsibleResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath);
+-};
+-
+-
+-class PuppetResultRemediationSaver : public ResultBasedProcessRemediationSaver
+-{
+- public:
+- PuppetResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath);
+-};
+-
+-#else // i.e. SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION is defined
+-
+-/// Base for all result-based remediation generators that uses the openscap library
+ class ResultBasedLibraryRemediationSaver : public RemediationSaverBase
+ {
+ public:
+@@ -165,7 +126,5 @@ class PuppetResultRemediationSaver : public ResultBasedLibraryRemediationSaver
+ PuppetResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath);
+ };
+
+-#endif // SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION
+-
+
+ #endif // SCAP_WORKBENCH_REMEDIATION_ROLE_SAVER_H_
+diff --git a/src/RemediationRoleSaver.cpp b/src/RemediationRoleSaver.cpp
+index 28389dbb..a9866738 100644
+--- a/src/RemediationRoleSaver.cpp
++++ b/src/RemediationRoleSaver.cpp
+@@ -35,11 +35,7 @@ extern "C"
+ #include
+ #include
+ #include
+-#ifdef SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION
+- // vvv This include is used only for library-based generation of result-base remediation roles
+- // vvv and it requires (relatively recent) openscap 1.2.16
+ #include
+-#endif
+ }
+
+
+@@ -163,88 +159,6 @@ PuppetProfileRemediationSaver::PuppetProfileRemediationSaver(QWidget* parentWind
+ puppetSaveMessage, puppetFiletypeExtension, puppetFiletypeTemplate, puppetFixType)
+ {}
+
+-#ifndef SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION
+-ResultBasedProcessRemediationSaver::ResultBasedProcessRemediationSaver(
+- QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath,
+- const QString& saveMessage, const QString& filetypeExtension, const QString& filetypeTemplate, const QString& fixType):
+- RemediationSaverBase(parentWindow, saveMessage, filetypeExtension, filetypeTemplate, fixType)
+-{
+- mArfFile.setAutoRemove(true);
+- mArfFile.open();
+- mArfFile.write(arfContents);
+- mArfFile.close();
+- tailoring = tailoringFilePath;
+-}
+-
+-void ResultBasedProcessRemediationSaver::saveToFile(const QString& filename)
+-{
+- QStringList args;
+- args.append("xccdf");
+- args.append("generate");
+- args.append("fix");
+-
+- args.append("--template");
+- args.append(mTemplateString);
+- args.append("--output");
+- args.append(filename);
+-
+- // vvv This will work, if there is only one result ID in the ARF file, it will be picked no matter what the argument value is.
+- // However, ommitting --result-id "" won't work.
+- args.append("--result-id");
+- args.append("");
+-
+- if (!tailoring.isNull()) {
+- args.append("--tailoring-file");
+- args.append(tailoring.toUtf8().constData());
+- }
+-
+- args.append(mArfFile.fileName());
+-
+- // Launching a process and going through its output is something we do already in OscapScannerLocal::evaluate()
+- // This is a lightweight launch though.
+- QProcess process(mParentWindow);
+-
+- SpacelessQTemporaryDir workingDir;
+- process.setWorkingDirectory(workingDir.path());
+- QString program(SCAP_WORKBENCH_LOCAL_OSCAP_PATH);
+-
+- process.start(program, args);
+- process.waitForStarted();
+-
+- const unsigned int remediationGenerationTimeout = 10000;
+-
+- const int process_finished_on_time = process.waitForFinished(remediationGenerationTimeout);
+-
+- if (!process_finished_on_time)
+- {
+- QString message = QObject::tr("The process that was supposed to generate remediations didn't finish on time (i.e. within %1 secs), so it was terminated.").arg(remediationGenerationTimeout / 1000);
+- process.kill();
+- throw std::runtime_error(message.toUtf8().constData());
+- }
+-
+- if (process.exitCode() != 0)
+- {
+- QString completeErrorMessage(QObject::tr("Exit code of 'oscap' was %1: %2"));
+- throw std::runtime_error(completeErrorMessage.arg(process.exitCode()).arg(QString(process.readAllStandardError())).toUtf8().constData());
+- }
+-}
+-
+-BashResultRemediationSaver::BashResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath):
+- ResultBasedProcessRemediationSaver(parentWindow, arfContents, tailoringFilePath,
+- bashSaveMessage, bashFiletypeExtension, bashFiletypeTemplate, bashFixTemplate)
+-{}
+-
+-AnsibleResultRemediationSaver::AnsibleResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath):
+- ResultBasedProcessRemediationSaver(parentWindow, arfContents, tailoringFilePath,
+- ansibleSaveMessage, ansibleFiletypeExtension, ansibleFiletypeTemplate, ansibleFixType)
+-{}
+-
+-PuppetResultRemediationSaver::PuppetResultRemediationSaver(QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath):
+- ResultBasedProcessRemediationSaver(parentWindow, arfContents, tailoringFilePath,
+- puppetSaveMessage, puppetFiletypeExtension, puppetFiletypeTemplate, puppetFixType)
+-{}
+-
+-#else // i.e. SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION is defined
+ ResultBasedLibraryRemediationSaver::ResultBasedLibraryRemediationSaver(
+ QWidget* parentWindow, const QByteArray& arfContents, const QString& tailoringFilePath,
+ const QString& saveMessage, const QString& filetypeExtension, const QString& filetypeTemplate, const QString& fixType):
+@@ -343,4 +257,3 @@ PuppetResultRemediationSaver::PuppetResultRemediationSaver(QWidget* parentWindow
+ puppetSaveMessage, puppetFiletypeExtension, puppetFiletypeTemplate, puppetFixType)
+ {}
+
+-#endif // SCAP_WORKBENCH_USE_LIBRARY_FOR_RESULT_BASED_REMEDIATION_ROLES_GENERATION
+From 550fc786d2cdec391544cd7bc3a33325ba545803 Mon Sep 17 00:00:00 2001
+From: =?UTF-8?q?Jan=20=C4=8Cern=C3=BD?=
+Date: Tue, 3 Mar 2020 09:36:20 +0100
+Subject: [PATCH] Remove known issue
+
+This issue has been fixed by 69e988df963cb184062814e75c737fe080f303df.
+---
+ doc/user_manual.adoc | 14 --------------
+ 1 file changed, 14 deletions(-)
+
+diff --git a/doc/user_manual.adoc b/doc/user_manual.adoc
+index fa47d4fc..48933dd9 100644
+--- a/doc/user_manual.adoc
++++ b/doc/user_manual.adoc
+@@ -524,20 +524,6 @@ Both while opening the files and when scanning. This option is discouraged and
+ should only be used by content creators and/or people who really know what they
+ are doing.
+
+-== Known issues
+-
+-=== Result-based remediations of tailored profiles
+-
+-Saving remediation roles to the disk may not work for a customized profile. Specifically, it won't work if you add additional rules to it.
+-If this limitation affects you, follow these steps:
+-
+-Remark: You will need to use the oscap command-line utility, which is bundled together with scap-workbench.
+-
+-1. Save the scan results
+-2. Save your profile customization to a file using the "File->Save customization only" option.
+-3. Run this command: oscap xccdf generate fix --output --result-id '' --tailoring-file .
+-Refer to oscap xccdf generate fix -h if you want other than Bash output.
+-
+ == Where to Get Help?
+
+ You ask for help with the application using
diff --git a/SOURCES/scap-workbench-1.2.2-refactor_messages-PR_271.patch b/SOURCES/scap-workbench-1.2.2-refactor_messages-PR_271.patch
new file mode 100644
index 0000000..ff9e7f5
--- /dev/null
+++ b/SOURCES/scap-workbench-1.2.2-refactor_messages-PR_271.patch
@@ -0,0 +1,196 @@
+From 7786cd8b020ab3aa4a9720e6fa8f60285486a48b Mon Sep 17 00:00:00 2001
+From: Matej Tyc
+Date: Wed, 27 Jan 2021 14:28:57 +0100
+Subject: [PATCH] Refactored handling of scanner messages.
+
+- Better detection of errors and warnings using leading E: and W: that
+ oscap emits.
+- Possibility to expand the message filtering using inheritance.
+---
+ include/OscapScannerBase.h | 15 +++++
+ src/OscapScannerBase.cpp | 132 ++++++++++++++++++++++++++++++++-----
+ 2 files changed, 130 insertions(+), 17 deletions(-)
+
+diff --git a/include/OscapScannerBase.h b/include/OscapScannerBase.h
+index 00f67699..f82379be 100644
+--- a/include/OscapScannerBase.h
++++ b/include/OscapScannerBase.h
+@@ -77,6 +77,21 @@ class OscapScannerBase : public Scanner
+
+ ReadingState mReadingState;
+
++ enum MessageType
++ {
++ MSG_INFO, MSG_WARNING, MSG_ERROR, MSG_UNKNOWN
++ };
++
++ virtual void filterStdErr(QString& errorText);
++ void emitMessage(MessageType kind, QString& message);
++ virtual void selectWarning(MessageType& kind, const QString& message);
++ virtual void processWarning(QString& message);
++ virtual void selectInfo(MessageType& kind, const QString& message);
++ virtual void processInfo(QString& message);
++ virtual void selectError(MessageType& kind, const QString& message);
++ virtual void processError(QString& message);
++ virtual void processUnknown(QString& message);
++
+ /// We keep filling this buffer until we reach : or \n
+ QString mReadBuffer;
+
+diff --git a/src/OscapScannerBase.cpp b/src/OscapScannerBase.cpp
+index bdb02e74..0ec4575e 100644
+--- a/src/OscapScannerBase.cpp
++++ b/src/OscapScannerBase.cpp
+@@ -418,6 +418,117 @@ void OscapScannerBase::readStdOut(QProcess& process)
+ while (tryToReadStdOutChar(process));
+ }
+
++
++void OscapScannerBase::emitMessage(MessageType kind, QString& message)
++{
++ QString rawMessage = QObject::tr(message.toUtf8().constData());
++ switch (kind)
++ {
++ case MSG_INFO:
++ {
++ emit infoMessage(message);
++ break;
++ }
++ case MSG_WARNING:
++ {
++ emit warningMessage(message);
++ break;
++ }
++ default:
++ emit errorMessage(message);
++ }
++}
++
++
++void OscapScannerBase::selectWarning(MessageType& kind, const QString& message)
++{
++ if (message.contains("WARNING: "))
++ {
++ kind = MSG_WARNING;
++ }
++ if (message.contains(QRegExp("^W:\\s")))
++ {
++ kind = MSG_WARNING;
++ }
++}
++
++
++void OscapScannerBase::processWarning(QString& message)
++{
++ message = guiFriendlyMessage(message);
++}
++
++
++void OscapScannerBase::selectInfo(MessageType& kind, const QString& message)
++{
++ if (message.contains(QRegExp("^Downloading: .+ \\.{3} \\w+\\n")))
++ {
++ kind = MSG_INFO;
++ }
++}
++
++
++void OscapScannerBase::processInfo(QString& message)
++{
++ (void)message; // suppress the unused arg warning
++}
++
++
++void OscapScannerBase::selectError(MessageType& kind, const QString& message)
++{
++ if (message.contains(QRegExp("^E:\\s")))
++ {
++ kind = MSG_ERROR;
++ }
++}
++
++
++void OscapScannerBase::processError(QString& message)
++{
++ message.remove(QRegExp("Error:\\s*"));
++ message.remove(QRegExp("^E:\\s*"));
++ message.remove(QRegExp("\\n"));
++}
++
++
++void OscapScannerBase::processUnknown(QString& message)
++{
++ message = QString("The 'oscap' process has written the following content to stderr:\n%1").arg(message);
++}
++
++
++void OscapScannerBase::filterStdErr(QString& errorText)
++{
++ MessageType type = MSG_UNKNOWN;
++ // let detection of a more severe type of message (error) overrule a benign one (info)
++ selectInfo(type, errorText);
++ selectWarning(type, errorText);
++ selectError(type, errorText);
++ switch(type)
++ {
++ case MSG_INFO:
++ {
++ processInfo(errorText);
++ break;
++ }
++ case MSG_WARNING:
++ {
++ processWarning(errorText);
++ break;
++ }
++ case MSG_ERROR:
++ {
++ processError(errorText);
++ break;
++ }
++ default:
++ {
++ processUnknown(errorText);
++ }
++ }
++ emitMessage(type, errorText);
++}
++
+ void OscapScannerBase::watchStdErr(QProcess& process)
+ {
+ process.setReadChannel(QProcess::StandardError);
+@@ -433,21 +544,7 @@ void OscapScannerBase::watchStdErr(QProcess& process)
+
+ if (!stdErrOutput.isEmpty())
+ {
+- if (stdErrOutput.contains("WARNING: "))
+- {
+- QString guiMessage = guiFriendlyMessage(stdErrOutput);
+- emit warningMessage(QObject::tr(guiMessage.toUtf8().constData()));
+- }
+- // Openscap >= 1.2.11 (60fb9f0c98eee) sends this message through stderr
+- else if (stdErrOutput.contains(QRegExp("^Downloading: .+ \\.{3} \\w+\\n")))
+- {
+- emit infoMessage(stdErrOutput);
+- }
+- else
+- {
+- emit errorMessage(QObject::tr("The 'oscap' process has written the following content to stderr:\n"
+- "%1").arg(stdErrOutput));
+- }
++ filterStdErr(stdErrOutput);
+ }
+
+ }
+@@ -458,8 +555,9 @@ QString OscapScannerBase::guiFriendlyMessage(const QString& cliMessage)
+ {
+ QString guiMessage = cliMessage;
+
+- // Remove "WARNING:" prefix and trailing \n
+- guiMessage.remove(QRegExp("(WARNING: )|\n"));
++ guiMessage.remove(QRegExp("WARNING:\\s*"));
++ guiMessage.remove(QRegExp("^W:\\s*"));
++ guiMessage.remove(QRegExp("\\n"));
+
+ if (cliMessage.contains("--fetch-remote-resources"))
+ guiMessage = QString("Remote resources might be necessary for this profile to work properly. Please select \"Fetch remote resources\" for complete scan");
diff --git a/SOURCES/scap-workbench-1.2.2-remote_sudo-PR_270.patch b/SOURCES/scap-workbench-1.2.2-remote_sudo-PR_270.patch
new file mode 100644
index 0000000..3b4c198
--- /dev/null
+++ b/SOURCES/scap-workbench-1.2.2-remote_sudo-PR_270.patch
@@ -0,0 +1,560 @@
+From 0e48a7161be7fbabc02ba05407131be2595e9b6d Mon Sep 17 00:00:00 2001
+From: Matej Tyc
+Date: Tue, 1 Dec 2020 18:46:20 +0100
+Subject: [PATCH 1/4] Implement possibility to scan by sudoers.
+
+- The remote scanning dialog got a "user is sudoer" checkbox.
+- Dry run can make use of sudo in connection with oscap-ssh.
+- The scanning procedure uses sudo invocation as part of the ssh command.
+---
+ include/OscapScannerRemoteSsh.h | 2 ++
+ include/RemoteMachineComboBox.h | 2 ++
+ src/MainWindow.cpp | 4 ++++
+ src/OscapScannerRemoteSsh.cpp | 28 +++++++++++++++++++------
+ src/RemoteMachineComboBox.cpp | 7 +++++++
+ ui/RemoteMachineComboBox.ui | 36 +++++++++++++++++++++++++++++----
+ 6 files changed, 69 insertions(+), 10 deletions(-)
+
+diff --git a/include/OscapScannerRemoteSsh.h b/include/OscapScannerRemoteSsh.h
+index d2aa7013..69eedfe4 100644
+--- a/include/OscapScannerRemoteSsh.h
++++ b/include/OscapScannerRemoteSsh.h
+@@ -36,6 +36,7 @@ class OscapScannerRemoteSsh : public OscapScannerBase
+ OscapScannerRemoteSsh();
+ virtual ~OscapScannerRemoteSsh();
+
++ void setUserIsSudoer(bool userIsSudoer);
+ virtual void setTarget(const QString& target);
+ virtual void setSession(ScanningSession* session);
+
+@@ -57,6 +58,7 @@ class OscapScannerRemoteSsh : public OscapScannerBase
+ void removeRemoteDirectory(const QString& path, const QString& desc);
+
+ SshConnection mSshConnection;
++ bool mUserIsSudoer;
+ };
+
+ #endif
+diff --git a/include/RemoteMachineComboBox.h b/include/RemoteMachineComboBox.h
+index 41a9643c..3b338127 100644
+--- a/include/RemoteMachineComboBox.h
++++ b/include/RemoteMachineComboBox.h
+@@ -44,6 +44,7 @@ class RemoteMachineComboBox : public QWidget
+
+ void setRecentMachineCount(unsigned int count);
+ unsigned int getRecentMachineCount() const;
++ bool userIsSudoer() const;
+
+ public slots:
+ void notifyTargetUsed(const QString& target);
+@@ -65,6 +66,7 @@ class RemoteMachineComboBox : public QWidget
+
+ QStringList mRecentTargets;
+ QComboBox* mRecentComboBox;
++ QCheckBox* mUserIsSudoer;
+ };
+
+ #endif
+diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
+index c9a0937b..236cfde1 100644
+--- a/src/MainWindow.cpp
++++ b/src/MainWindow.cpp
+@@ -678,6 +678,7 @@ void MainWindow::scanAsync(ScannerMode scannerMode)
+ // In the OscapScannerRemoteSsh class the port will be parsed out again...
+ const QString target = mUI.localMachineRadioButton->isChecked() ?
+ "localhost" : mUI.remoteMachineDetails->getTarget();
++ const bool userIsSudoer = mUI.remoteMachineDetails->userIsSudoer();
+
+ bool fetchRemoteResources = mUI.fetchRemoteResourcesCheckbox->isChecked();
+ try
+@@ -689,7 +690,10 @@ void MainWindow::scanAsync(ScannerMode scannerMode)
+ if (target == "localhost")
+ mScanner = new OscapScannerLocal();
+ else
++ {
+ mScanner = new OscapScannerRemoteSsh();
++ ((OscapScannerRemoteSsh *)mScanner)->setUserIsSudoer(userIsSudoer);
++ }
+
+ mScanner->setTarget(target);
+
+diff --git a/src/OscapScannerRemoteSsh.cpp b/src/OscapScannerRemoteSsh.cpp
+index dcfd6d5f..d20faf59 100644
+--- a/src/OscapScannerRemoteSsh.cpp
++++ b/src/OscapScannerRemoteSsh.cpp
+@@ -37,7 +37,8 @@ extern "C"
+
+ OscapScannerRemoteSsh::OscapScannerRemoteSsh():
+ OscapScannerBase(),
+- mSshConnection(this)
++ mSshConnection(this),
++ mUserIsSudoer(false)
+ {
+ mSshConnection.setCancelRequestSource(&mCancelRequested);
+ }
+@@ -87,6 +88,11 @@ void OscapScannerRemoteSsh::setTarget(const QString& target)
+ mSshConnection.setPort(port);
+ }
+
++void OscapScannerRemoteSsh::setUserIsSudoer(bool userIsSudoer)
++{
++ mUserIsSudoer = userIsSudoer;
++}
++
+ void OscapScannerRemoteSsh::setSession(ScanningSession* session)
+ {
+ OscapScannerBase::setSession(session);
+@@ -99,6 +105,10 @@ void OscapScannerRemoteSsh::setSession(ScanningSession* session)
+ QStringList OscapScannerRemoteSsh::getCommandLineArgs() const
+ {
+ QStringList args("oscap-ssh");
++ if (mUserIsSudoer)
++ {
++ args.append("--sudo");
++ }
+ args.append(mSshConnection.getTarget());
+ args.append(QString::number(mSshConnection.getPort()));
+
+@@ -235,19 +245,19 @@ void OscapScannerRemoteSsh::evaluate()
+
+ if (mScannerMode == SM_OFFLINE_REMEDIATION)
+ {
+- args = buildOfflineRemediationArgs(inputFile,
++ args.append(buildOfflineRemediationArgs(inputFile,
+ resultFile,
+ reportFile,
+- arfFile);
++ arfFile));
+ }
+ else
+ {
+- args = buildEvaluationArgs(inputFile,
++ args.append(buildEvaluationArgs(inputFile,
+ tailoringFile,
+ resultFile,
+ reportFile,
+ arfFile,
+- mScannerMode == SM_SCAN_ONLINE_REMEDIATION);
++ mScannerMode == SM_SCAN_ONLINE_REMEDIATION));
+ }
+
+ const QString sshCmd = args.join(" ");
+@@ -255,8 +265,14 @@ void OscapScannerRemoteSsh::evaluate()
+ emit infoMessage(QObject::tr("Starting the remote process..."));
+
+ QProcess process(this);
++ QString sudo;
++ if (mUserIsSudoer)
++ {
++ // tell sudo not to bother to read password from the terminal
++ sudo = " sudo -n";
++ }
+
+- process.start(SCAP_WORKBENCH_LOCAL_SSH_PATH, baseArgs + QStringList(QString("cd '%1'; " SCAP_WORKBENCH_REMOTE_OSCAP_PATH " %2").arg(workingDir).arg(sshCmd)));
++ process.start(SCAP_WORKBENCH_LOCAL_SSH_PATH, baseArgs + QStringList(QString("cd '%1';" "%2 " SCAP_WORKBENCH_REMOTE_OSCAP_PATH " %3").arg(workingDir).arg(sudo).arg(sshCmd)));
+ process.waitForStarted();
+
+ if (process.state() != QProcess::Running)
+diff --git a/src/RemoteMachineComboBox.cpp b/src/RemoteMachineComboBox.cpp
+index 46d1b7d1..7b402344 100644
+--- a/src/RemoteMachineComboBox.cpp
++++ b/src/RemoteMachineComboBox.cpp
+@@ -41,6 +41,8 @@ RemoteMachineComboBox::RemoteMachineComboBox(QWidget* parent):
+ this, SLOT(updateHostPort(int))
+ );
+
++ mUserIsSudoer = mUI.userIsSudoer;
++
+ setRecentMachineCount(5);
+ syncFromQSettings();
+
+@@ -51,6 +53,11 @@ RemoteMachineComboBox::~RemoteMachineComboBox()
+ delete mQSettings;
+ }
+
++bool RemoteMachineComboBox::userIsSudoer() const
++{
++ return mUserIsSudoer->isChecked();
++}
++
+ QString RemoteMachineComboBox::getTarget() const
+ {
+ return QString("%1:%2").arg(mUI.host->text()).arg(mUI.port->value());
+diff --git a/ui/RemoteMachineComboBox.ui b/ui/RemoteMachineComboBox.ui
+index 780d06ce..f9e9665c 100644
+--- a/ui/RemoteMachineComboBox.ui
++++ b/ui/RemoteMachineComboBox.ui
+@@ -6,15 +6,24 @@
+
+ 0
+ 0
+- 553
+- 29
++ 609
++ 42
+
+
+
+ RemoteMachineComboBox
+
+
+-
++
++ 0
++
++
++ 0
++
++
++ 0
++
++
+ 0
+
+ -
+@@ -73,8 +82,17 @@
+
+ -
+
++
++ true
++
++
++
++ 0
++ 0
++
++
+
+- QAbstractSpinBox::UpDownArrows
++ QAbstractSpinBox::NoButtons
+
+
+ 1
+@@ -87,6 +105,16 @@
+
+
+
++ -
++
++
++ Check if the remote user doesn't have root privileges, but they can perform administrative tasks using paswordless sudo.
++
++
++ user is sudoer
++
++
++
+ -
+
+
+
+From 1fd9bc807f1c76452c0803436efd311000d7470b Mon Sep 17 00:00:00 2001
+From: Matej Tyc
+Date: Wed, 27 Jan 2021 14:33:04 +0100
+Subject: [PATCH 2/4] Updated message handling of sudo-related issues.
+
+---
+ include/OscapScannerRemoteSsh.h | 5 +++++
+ src/OscapScannerRemoteSsh.cpp | 23 +++++++++++++++++++++++
+ 2 files changed, 28 insertions(+)
+
+diff --git a/include/OscapScannerRemoteSsh.h b/include/OscapScannerRemoteSsh.h
+index 69eedfe4..280a69da 100644
+--- a/include/OscapScannerRemoteSsh.h
++++ b/include/OscapScannerRemoteSsh.h
+@@ -43,6 +43,11 @@ class OscapScannerRemoteSsh : public OscapScannerBase
+ virtual QStringList getCommandLineArgs() const;
+ virtual void evaluate();
+
++ protected:
++
++ virtual void selectError(MessageType& kind, const QString& message);
++ virtual void processError(QString& message);
++
+ private:
+ void ensureConnected();
+
+diff --git a/src/OscapScannerRemoteSsh.cpp b/src/OscapScannerRemoteSsh.cpp
+index d20faf59..69b51373 100644
+--- a/src/OscapScannerRemoteSsh.cpp
++++ b/src/OscapScannerRemoteSsh.cpp
+@@ -344,6 +344,29 @@ void OscapScannerRemoteSsh::evaluate()
+ signalCompletion(mCancelRequested);
+ }
+
++void OscapScannerRemoteSsh::selectError(MessageType& kind, const QString& message)
++{
++ OscapScannerBase::selectError(kind, message);
++ if (mUserIsSudoer)
++ {
++ if (message.contains(QRegExp("^sudo:")))
++ {
++ kind = MSG_ERROR;
++ }
++ }
++
++}
++
++void OscapScannerRemoteSsh::processError(QString& message)
++{
++ OscapScannerBase::processError(message);
++ if (mUserIsSudoer && message.contains(QRegExp("^sudo:")))
++ {
++ message.replace(QRegExp("^sudo:"), "Error invoking sudo on the host:");
++ message += ".\nOnly passwordless sudo setup on the remote host is supported by scap-workbench.";
++ }
++}
++
+ void OscapScannerRemoteSsh::ensureConnected()
+ {
+ if (mSshConnection.isConnected())
+
+From 57097b3b9d6f85caa96ab2940c29e94f16382252 Mon Sep 17 00:00:00 2001
+From: Matej Tyc
+Date: Thu, 28 Jan 2021 17:12:08 +0100
+Subject: [PATCH 3/4] Added documentation about setting up passwordless sudo.
+
+---
+ doc/user_manual.adoc | 11 +++++++++++
+ src/OscapScannerRemoteSsh.cpp | 2 ++
+ 2 files changed, 13 insertions(+)
+
+diff --git a/doc/user_manual.adoc b/doc/user_manual.adoc
+index 29ebd919..2c8501a0 100644
+--- a/doc/user_manual.adoc
++++ b/doc/user_manual.adoc
+@@ -363,6 +363,17 @@ files are not supported yet!
+ .Selecting a remote machine for scanning
+ image::scanning_remote_machine.png[align="center"]
+
++The remote user doesn't have to be a superuser - you can setup the remote
++`/etc/sudoers` file (using `visudo`) to enable the paswordless sudo for that particular user,
++and you check the "user is sudoer" checkbox.
++
++For example, if the scanning user is `oscap-user`, that would involve putting
++
++ oscap-user ALL=(root) NOPASSWD: /usr/bin/oscap xccdf eval *
++
++user specification into the `sudoers` file, or into a separate file
++that is included by `sudoers` s.a. `/etc/sudoers.d/99-oscap-user`.
++
+ === Enable Online Remediation (optional)
+
+ ****
+diff --git a/src/OscapScannerRemoteSsh.cpp b/src/OscapScannerRemoteSsh.cpp
+index 69b51373..7fa38b2e 100644
+--- a/src/OscapScannerRemoteSsh.cpp
++++ b/src/OscapScannerRemoteSsh.cpp
+@@ -364,6 +364,8 @@ void OscapScannerRemoteSsh::processError(QString& message)
+ {
+ message.replace(QRegExp("^sudo:"), "Error invoking sudo on the host:");
+ message += ".\nOnly passwordless sudo setup on the remote host is supported by scap-workbench.";
++ message += " \nTo configure a non-privileged user oscap-user to run only the oscap binary as root, "
++ "add this User Specification to your sudoers file: oscap-user ALL=(root) NOPASSWD: /usr/bin/oscap xccdf eval *";
+ }
+ }
+
+
+From e8daecc80ad54e95de764728f0cbe4863a67be0d Mon Sep 17 00:00:00 2001
+From: Matej Tyc
+Date: Thu, 28 Jan 2021 17:45:09 +0100
+Subject: [PATCH 4/4] Added suport for the sudoers checkbox to history.
+
+Recent remote scans now encode the sudo mode into the "target" that is
+stored in the recent remote hosts list.
+---
+ include/OscapScannerRemoteSsh.h | 3 ++-
+ include/RemoteMachineComboBox.h | 2 +-
+ src/MainWindow.cpp | 5 ++++-
+ src/OscapScannerRemoteSsh.cpp | 30 +++++++++++++++++++++++-------
+ src/RemoteMachineComboBox.cpp | 17 +++++++++++------
+ 5 files changed, 41 insertions(+), 16 deletions(-)
+
+diff --git a/include/OscapScannerRemoteSsh.h b/include/OscapScannerRemoteSsh.h
+index 280a69da..50214b80 100644
+--- a/include/OscapScannerRemoteSsh.h
++++ b/include/OscapScannerRemoteSsh.h
+@@ -31,11 +31,12 @@ class OscapScannerRemoteSsh : public OscapScannerBase
+ Q_OBJECT
+
+ public:
+- static void splitTarget(const QString& in, QString& target, unsigned short& port);
++ static void splitTarget(const QString& in, QString& target, unsigned short& port, bool& userIsSudoer);
+
+ OscapScannerRemoteSsh();
+ virtual ~OscapScannerRemoteSsh();
+
++ bool getUserIsSudoer() const;
+ void setUserIsSudoer(bool userIsSudoer);
+ virtual void setTarget(const QString& target);
+ virtual void setSession(ScanningSession* session);
+diff --git a/include/RemoteMachineComboBox.h b/include/RemoteMachineComboBox.h
+index 3b338127..c2d946c9 100644
+--- a/include/RemoteMachineComboBox.h
++++ b/include/RemoteMachineComboBox.h
+@@ -47,7 +47,7 @@ class RemoteMachineComboBox : public QWidget
+ bool userIsSudoer() const;
+
+ public slots:
+- void notifyTargetUsed(const QString& target);
++ void notifyTargetUsed(const QString& target, bool userIsSudoer);
+ void clearHistory();
+
+ protected slots:
+diff --git a/src/MainWindow.cpp b/src/MainWindow.cpp
+index 236cfde1..496e1724 100644
+--- a/src/MainWindow.cpp
++++ b/src/MainWindow.cpp
+@@ -763,7 +763,10 @@ void MainWindow::scanAsync(ScannerMode scannerMode)
+ );
+
+ if (target != "localhost")
+- mUI.remoteMachineDetails->notifyTargetUsed(mScanner->getTarget());
++ {
++ bool userIsSudoer = ((OscapScannerRemoteSsh *)mScanner)->getUserIsSudoer();
++ mUI.remoteMachineDetails->notifyTargetUsed(mScanner->getTarget(), userIsSudoer);
++ }
+
+ mScanThread->start();
+ }
+diff --git a/src/OscapScannerRemoteSsh.cpp b/src/OscapScannerRemoteSsh.cpp
+index 7fa38b2e..b1c4426f 100644
+--- a/src/OscapScannerRemoteSsh.cpp
++++ b/src/OscapScannerRemoteSsh.cpp
+@@ -46,7 +46,7 @@ OscapScannerRemoteSsh::OscapScannerRemoteSsh():
+ OscapScannerRemoteSsh::~OscapScannerRemoteSsh()
+ {}
+
+-void OscapScannerRemoteSsh::splitTarget(const QString& in, QString& target, unsigned short& port)
++void OscapScannerRemoteSsh::splitTarget(const QString& in, QString& target, unsigned short& port, bool& userIsSudoer)
+ {
+ // NB: We dodge a bullet here because the editor will always pass a port
+ // as the last component. A lot of checking and parsing does not need
+@@ -56,10 +56,19 @@ void OscapScannerRemoteSsh::splitTarget(const QString& in, QString& target, unsi
+ // being there and always being the last component.
+
+ // FIXME: Ideally, this should split from the right side and stop after one split
+- QStringList split = in.split(':');
++ userIsSudoer = false;
++ QStringList sudoerSplit = in.split(' ');
++ if (sudoerSplit.size() > 1)
++ {
++ if (sudoerSplit.at(1) == "sudo")
++ {
++ userIsSudoer = true;
++ }
++ }
++ QStringList hostPortSplit = sudoerSplit.at(0).split(':');
+
+- const QString portString = split.back();
+- split.removeLast();
++ const QString portString = hostPortSplit.back();
++ hostPortSplit.removeLast();
+
+ {
+ bool status = false;
+@@ -69,25 +78,32 @@ void OscapScannerRemoteSsh::splitTarget(const QString& in, QString& target, unsi
+ port = status ? portCandidate : 22;
+ }
+
+- target = split.join(":");
++ target = hostPortSplit.join(":");
+ }
+
+ void OscapScannerRemoteSsh::setTarget(const QString& target)
+ {
+- OscapScannerBase::setTarget(target);
++ QStringList sudoerSplit = target.split(' ');
++ OscapScannerBase::setTarget(sudoerSplit.at(0));
+
+ if (mSshConnection.isConnected())
+ mSshConnection.disconnect();
+
+ QString cleanTarget;
+ unsigned short port;
++ bool userIsSudoer;
+
+- splitTarget(target, cleanTarget, port);
++ splitTarget(target, cleanTarget, port, userIsSudoer);
+
+ mSshConnection.setTarget(cleanTarget);
+ mSshConnection.setPort(port);
+ }
+
++bool OscapScannerRemoteSsh::getUserIsSudoer() const
++{
++ return mUserIsSudoer;
++}
++
+ void OscapScannerRemoteSsh::setUserIsSudoer(bool userIsSudoer)
+ {
+ mUserIsSudoer = userIsSudoer;
+diff --git a/src/RemoteMachineComboBox.cpp b/src/RemoteMachineComboBox.cpp
+index 7b402344..127bdac7 100644
+--- a/src/RemoteMachineComboBox.cpp
++++ b/src/RemoteMachineComboBox.cpp
+@@ -30,7 +30,7 @@ RemoteMachineComboBox::RemoteMachineComboBox(QWidget* parent):
+
+ #if (QT_VERSION >= QT_VERSION_CHECK(4, 7, 0))
+ // placeholder text is only supported in Qt 4.7 onwards
+- mUI.host->setPlaceholderText(QObject::tr("username@hostname"));
++ mUI.host->setPlaceholderText(QObject::tr("username@hostname [sudo]"));
+ #endif
+
+ mQSettings = new QSettings(this);
+@@ -77,11 +77,12 @@ unsigned int RemoteMachineComboBox::getRecentMachineCount() const
+ return mRecentTargets.size();
+ }
+
+-void RemoteMachineComboBox::notifyTargetUsed(const QString& target)
++void RemoteMachineComboBox::notifyTargetUsed(const QString& target, bool userIsSudoer)
+ {
+ QString host;
+ unsigned short port;
+- OscapScannerRemoteSsh::splitTarget(target, host, port);
++ bool placeholder;
++ OscapScannerRemoteSsh::splitTarget(target, host, port, placeholder);
+
+ // skip invalid suggestions
+ if (host.isEmpty() || port == 0)
+@@ -90,7 +91,8 @@ void RemoteMachineComboBox::notifyTargetUsed(const QString& target)
+ const unsigned int machineCount = getRecentMachineCount();
+
+ // this moves target to the beginning of the list if it was in the list already
+- mRecentTargets.prepend(target);
++ QString targetWithSudo = target + (userIsSudoer ? " sudo" : "");
++ mRecentTargets.prepend(targetWithSudo);
+ mRecentTargets.removeDuplicates();
+
+ setRecentMachineCount(machineCount);
+@@ -106,6 +108,7 @@ void RemoteMachineComboBox::clearHistory()
+ {
+ mUI.host->setText("");
+ mUI.port->setValue(22);
++ mUI.userIsSudoer->setChecked(false);
+
+ const unsigned int machineCount = getRecentMachineCount();
+ mRecentTargets.clear();
+@@ -167,6 +170,7 @@ void RemoteMachineComboBox::updateHostPort(int index)
+ {
+ mUI.host->setText("");
+ mUI.port->setValue(22);
++ mUI.userIsSudoer->setChecked(false);
+ return;
+ }
+
+@@ -179,10 +183,11 @@ void RemoteMachineComboBox::updateHostPort(int index)
+
+ QString host;
+ unsigned short port;
++ bool userIsSudoer;
+
+- OscapScannerRemoteSsh::splitTarget(target, host, port);
++ OscapScannerRemoteSsh::splitTarget(target, host, port, userIsSudoer);
+
+ mUI.host->setText(host);
+ mUI.port->setValue(port);
+-
++ mUI.userIsSudoer->setChecked(userIsSudoer);
+ }
diff --git a/SOURCES/scap-workbench-1.2.2-ui_dimensions.patch b/SOURCES/scap-workbench-1.2.2-ui_dimensions.patch
new file mode 100644
index 0000000..586e653
--- /dev/null
+++ b/SOURCES/scap-workbench-1.2.2-ui_dimensions.patch
@@ -0,0 +1,87 @@
+diff --git a/ui/SSGIntegrationDialog.ui b/ui/SSGIntegrationDialog.ui
+index 2e613740..519773f1 100644
+--- a/ui/SSGIntegrationDialog.ui
++++ b/ui/SSGIntegrationDialog.ui
+@@ -7,7 +7,7 @@
+ 0
+ 0
+ 889
+- 288
++ 330
+
+
+
+@@ -28,6 +28,9 @@
+
+
-
+
++
++ 12
++
+
-
+
+
+@@ -52,15 +55,19 @@
+
+ -
+
++
++ QLayout::SetMinimumSize
++
+
+ 0
+
+
-
+
+-
+-
+- 11
+-
++
++
++ 0
++ 0
++
+
+
+ <html><head/><body><p>SCAP Security Guide was found installed on this machine.</p><p>The content provided by SCAP Security Guide allows you to quickly scan your machine according to well stablished security baselines.</p><p>Also, these guides are a good starting point if you'd like to customize a policy or profile for your own needs.</p><p>Select one of the default guides to load, or select Other SCAP Content option to load your own content.</p></body></html>
+@@ -111,10 +118,13 @@
+
+ Qt::Vertical
+
++
++ QSizePolicy::MinimumExpanding
++
+
+
+ 20
+- 40
++ 20
+
+
+
+@@ -157,24 +167,6 @@
+
+
+
+- -
+-
+-
+-
+- 0
+- 0
+-
+-
+-
+-
+- 6
+-
+-
+- 0
+-
+-
+-
+-
+
+
+
diff --git a/SPECS/scap-workbench.spec b/SPECS/scap-workbench.spec
index c27687f..573a7f4 100644
--- a/SPECS/scap-workbench.spec
+++ b/SPECS/scap-workbench.spec
@@ -2,7 +2,7 @@
Name: scap-workbench
Version: 1.2.1
-Release: 11%{?dist}
+Release: 13%{?dist}
Summary: Scanning, tailoring, editing and validation tool for SCAP content
License: GPLv3+
@@ -12,6 +12,12 @@ Patch1: scap-workbench-1.2.2-fix-qt-deprecated-pr-259.patch
Patch2: scap-workbench-1.2.2-replace-obsolete-Qstring-SkipEmptyParts-pr-266.patch
Patch3: %{name}-gcc11.patch
Patch4: scap-workbench-1.2.2-no-rpath-pr-285.patch
+Patch5: scap-workbench-1.2.2-fix-appdata-pr-288.diff
+Patch6: scap-workbench-1.2.2-fix-appdata-pr-295.patch
+Patch7: scap-workbench-1.2.2-generate-result-based-remediation-from-tailored-profile.patch
+Patch8: scap-workbench-1.2.2-ui_dimensions.patch
+Patch9: scap-workbench-1.2.2-refactor_messages-PR_271.patch
+Patch10: scap-workbench-1.2.2-remote_sudo-PR_270.patch
BuildRequires: cmake >= 2.6
BuildRequires: qt5-qtbase-devel >= 5.0.0
@@ -52,7 +58,7 @@ content. The tool is based on OpenSCAP library.
%files
%{_bindir}/scap-workbench
-%{_datadir}/applications/scap-workbench.desktop
+%{_datadir}/applications/org.open_scap.scap_workbench.desktop
%{_datadir}/scap-workbench/*.png
%{_datadir}/scap-workbench/translations/*
%{_libexecdir}/scap-workbench-oscap.sh
@@ -60,14 +66,20 @@ content. The tool is based on OpenSCAP library.
%{_libexecdir}/scap-workbench-rpm-extract.sh
%{_datadir}/polkit-1/actions/scap-workbench-oscap.policy
%{_datadir}/pixmaps/scap-workbench.png
-%{_datadir}/pixmaps/scap-workbench.svg
-%{_datadir}/appdata/scap-workbench.appdata.xml
+%{_datadir}/icons/hicolor/scalable/apps/scap-workbench.svg
+%{_datadir}/metainfo/org.open_scap.scap_workbench.appdata.xml
%doc %{_mandir}/man8/scap-workbench.8.gz
%doc %{_pkgdocdir}/user_manual.html
%doc %{_pkgdocdir}/COPYING
%doc %{_pkgdocdir}/README.md
%changelog
+* Wed Dec 01 2021 Matej Tyc - 1.2.1-13
+- Ported 1.2.2 patches that we are present in el8 packages (rhbz#2029381)
+
+* Mon Nov 08 2021 Evgenii Kolesnikov - 1.2.1-12
+- Fix appdata (rhbz#2021212)
+
* Tue Aug 10 2021 Mohan Boddu - 1.2.1-11
- Rebuilt for IMA sigs, glibc 2.34, aarch64 flags
Related: rhbz#1991688