Skip to content

Commit

Permalink
feat(ruby): add digest callbacks
Browse files Browse the repository at this point in the history
  • Loading branch information
imobachgs committed Feb 19, 2025
1 parent 04c836c commit 8c316c6
Show file tree
Hide file tree
Showing 2 changed files with 275 additions and 0 deletions.
153 changes: 153 additions & 0 deletions service/lib/agama/software/callbacks/digest.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# frozen_string_literal: true

# Copyright (c) [2025] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require "yast"
require "agama/question"
require "agama/software/callbacks/base"

Yast.import "Pkg"

module Agama
module Software
module Callbacks
# Callbacks related to digest handling
class Digest < Base
# Constructor
#
# @param questions_client [Agama::DBus::Clients::Questions]
# @param logger [Logger]
def initialize(questions_client, logger)
textdomain "agama"
@questions_client = questions_client
@logger = logger || ::Logger.new($stdout)
end

def setup
Yast::Pkg.CallbackAcceptFileWithoutChecksum(
Yast::FunRef.new(
:accept_file_without_checksum, "boolean (string)"
)
)
Yast::Pkg.CallbackAcceptUnknownDigest(
Yast::FunRef.new(
:accept_unknown_digest, "boolean (string, string)"
)
)
Yast::Pkg.CallbackAcceptWrongDigest(
Yast::FunRef.new(
:accept_wrong_digest, "boolean (string, string, string)")
)
end

# Callback to accept a file without a checksum
#
# @param filename [String] File name
# @return [Boolean]
def accept_file_without_checksum(filename)
name = strip_download_prefix(filename)
message = format(
_(
"No checksum for the file %{file} was found in the repository. This means that " \
"although the file is part of the signed repository, the list of checksums " \
"does not mention this file. Use it anyway?"
), file: name
)

question = Agama::Question.new(
qclass: "software.digest.no_digest",
text: message,
options: [yes_label.to_sym, no_label.to_sym],
default_option: yes_label.to_sym
)
questions_client.ask(question) do |question_client|
question_client.answer == yes_label.to_sym
end
end

# Callback to accept an unknown digest
#
# @param filename [String] File name
# @param digest [String] expected checksum
# @return [Boolean]
def accept_unknown_digest(filename, digest)
name = strip_download_prefix(filename)
message = format(
_("The checksum of the file %{file} is \"%{digest}\" but the expected checksum is " \
"unknown. This means that the origin and integrity of the file cannot be verified. " \
"Use it anyway?"), file: name, digest: digest
)

question = Agama::Question.new(
qclass: "software.digest.unknown_digest",
text: message,
options: [yes_label.to_sym, no_label.to_sym],
default_option: yes_label.to_sym
)
questions_client.ask(question) do |question_client|
question_client.answer == yes_label.to_sym
end
end

# Callback to accept wrong digest
#
# @param filename [String] File name
# @param expected_digest [String] expected checksum
# @param found_digest [String] found checksum
# @return [Boolean]
def accept_wrong_digest(filename, expected_digest, found_digest)
name = strip_download_prefix(filename)
message = format(
_("The expected checksum of file %{file} is \"%{found}\" but it was expected to be "\
"\"%{expected}\". The file has changed by accident or by an attacker since the "\
"creater signed it. Use it anyway?"),
file: name, found: found_digest, expected: expected_digest
)

question = Agama::Question.new(
qclass: "software.digest.unknown_digest",
text: message,
options: [yes_label.to_sym, no_label.to_sym],
default_option: yes_label.to_sym
)
questions_client.ask(question) do |question_client|
question_client.answer == yes_label.to_sym
end
end

private

# @return [Agama::DBus::Clients::Questions]
attr_reader :questions_client

# @return [Logger]
attr_reader :logger

# helper to strip download path. It uses internal knowledge that download
# prefix ends in TmpDir.* zypp location
#
# From https://github.com/yast/yast-yast2/blob/master/library/packages/src/modules/SignatureCheckDialogs.rb#L836
def strip_download_prefix(path)
path.sub(/\A\/.*\/TmpDir\.[^\/]+\//, "")
end
end
end
end
end
122 changes: 122 additions & 0 deletions service/test/agama/software/callbacks/digest_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# frozen_string_literal: true

# Copyright (c) [2025] SUSE LLC
#
# All Rights Reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as published
# by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
# more details.
#
# You should have received a copy of the GNU General Public License along
# with this program; if not, contact SUSE LLC.
#
# To contact SUSE LLC about this file by physical or electronic mail, you may
# find current contact information at www.suse.com.

require_relative "../../../test_helper"
require "agama/software/callbacks/digest"
require "agama/dbus/clients/questions"
require "agama/dbus/clients/question"

describe Agama::Software::Callbacks::Digest do
subject { described_class.new(questions_client, logger) }

let(:questions_client) { instance_double(Agama::DBus::Clients::Questions) }
let(:question_client) { instance_double(Agama::DBus::Clients::Question) }

let(:logger) { Logger.new($stdout, level: :warn) }

before do
allow(questions_client).to receive(:ask).and_yield(question_client)
allow(question_client).to receive(:answer).and_return(answer)
end

describe "#accept_file_without_checksum" do
let(:answer) { subject.yes_label.to_sym }

it "registers a question informing of the error" do
expect(questions_client).to receive(:ask) do |q|
expect(q.text).to match("No checksum for the file repomd.xml")
end
subject.accept_file_without_checksum("repomd.xml")
end

context "when the user answers :Yes" do
let(:answer) { subject.yes_label.to_sym }

it "returns true" do
expect(subject.accept_file_without_checksum("repomd.xml")).to eq(true)
end
end

context "when the user answers :No" do
let(:answer) { subject.no_label.to_sym }

it "returns false" do
expect(subject.accept_file_without_checksum("repomd.xml")).to eq(false)
end
end
end

describe "#accept_unknown_digest" do
let(:answer) { subject.yes_label.to_sym }

it "registers a question informing of the error" do
expect(questions_client).to receive(:ask) do |q|
expect(q.text).to include("The checksum of the file repomd.xml is \"123456\"")
end
subject.accept_unknown_digest("repomd.xml", "123456")
end

context "when the user answers :Yes" do
let(:answer) { subject.yes_label.to_sym }

it "returns true" do
expect(subject.accept_unknown_digest("repomd.xml", "123456")).to eq(true)
end
end

context "when the user answers :No" do
let(:answer) { subject.no_label.to_sym }

it "returns false" do
expect(subject.accept_unknown_digest("repomd.xml", "123456")).to eq(false)
end
end
end

describe "#accept_wrong_digest" do
let(:answer) { subject.yes_label.to_sym }

it "registers a question informing of the error" do
expect(questions_client).to receive(:ask) do |q|
expect(q.text).to match(
/The expected checksum of file repomd.xml is "654321".*expected.*"123456"/
)
end
subject.accept_wrong_digest("repomd.xml", "123456", "654321")
end

context "when the user answers :Yes" do
let(:answer) { subject.yes_label.to_sym }

it "returns true" do
expect(subject.accept_wrong_digest("repomd.xml", "123456", "654321")).to eq(true)
end
end

context "when the user answers :No" do
let(:answer) { subject.no_label.to_sym }

it "returns false" do
expect(subject.accept_wrong_digest("repomd.xml", "123456", "654321")).to eq(false)
end
end
end
end

0 comments on commit 8c316c6

Please sign in to comment.