discourse/spec/services/themes_spec.rb
David Taylor 68b4fe4cf8
SECURITY: Expand and improve SSRF Protections (#18815)
See https://github.com/discourse/discourse/security/advisories/GHSA-rcc5-28r3-23rr

Co-authored-by: OsamaSayegh <asooomaasoooma90@gmail.com>
Co-authored-by: Daniel Waterworth <me@danielwaterworth.com>
2022-11-01 16:33:17 +00:00

190 lines
6.4 KiB
Ruby

# frozen_string_literal: true
RSpec.describe ThemesInstallTask do
fab!(:admin) { Fabricate(:admin) }
before do
Discourse::Application.load_tasks
end
describe '.new' do
THEME_NAME = "awesome theme"
def about_json(love_color: "FAFAFA", tertiary_low_color: "FFFFFF", color_scheme_name: "Amazing", about_url: "https://www.site.com/about", component: false)
<<~JSON
{
"name": "#{THEME_NAME}",
"about_url": "#{about_url}",
"license_url": "https://www.site.com/license",
"theme_version": "1.0",
"minimum_discourse_version": "1.0.0",
"assets": {
"font": "assets/font.woff2"
},
"component": "#{component}",
"color_schemes": {
"#{color_scheme_name}": {
"love": "#{love_color}",
"tertiary-low": "#{tertiary_low_color}"
}
},
"modifiers": {
"serialize_topic_excerpts": true
}
}
JSON
end
let :scss_data do
"@font-face { font-family: magic; src: url($font)}; body {color: fuchsia;}"
end
let :theme_repo do
setup_git_repo(
"about.json" => about_json,
"desktop/desktop.scss" => scss_data,
"scss/oldpath.scss" => ".class2{color:blue}",
"stylesheets/file.scss" => ".class1{color:red}",
"stylesheets/empty.scss" => "",
"javascripts/discourse/controllers/test.js.es6" => "console.log('test');",
"common/header.html" => "I AM HEADER",
"common/random.html" => "I AM SILLY",
"common/embedded.scss" => "EMBED",
"assets/font.woff2" => "FAKE FONT",
"settings.yaml" => "boolean_setting: true",
"locales/en.yml" => "sometranslations"
)
end
let :theme_repo_url do
MockGitImporter.register("https://example.com/theme_repo.git", theme_repo)
end
let :component_repo do
setup_git_repo(
"about.json" => about_json(component: true),
"desktop/desktop.scss" => scss_data,
"scss/oldpath.scss" => ".class2{color:blue}",
"stylesheets/file.scss" => ".class1{color:red}",
"stylesheets/empty.scss" => "",
"javascripts/discourse/controllers/test.js.es6" => "console.log('test');",
"common/header.html" => "I AM HEADER",
"common/random.html" => "I AM SILLY",
"common/embedded.scss" => "EMBED",
"assets/font.woff2" => "FAKE FONT",
"settings.yaml" => "boolean_setting: true",
"locales/en.yml" => "sometranslations"
)
end
let :component_repo_url do
MockGitImporter.register("https://example.com/component_repo.git", component_repo)
end
after do
`rm -fr #{theme_repo}`
`rm -fr #{component_repo}`
end
it 'gracefully fails' do
ThemesInstallTask.install("nothing": "fail!")
expect(Theme.where(name: "fail!").exists?).to eq(false)
end
before do
FinalDestination.stubs(:resolve).with(theme_repo_url).returns(URI.parse(theme_repo_url))
FinalDestination.stubs(:resolve).with(component_repo_url).returns(URI.parse(component_repo_url))
end
around(:each) do |group|
MockGitImporter.with_mock do
group.run
end
end
describe "no options" do
it 'installs a theme' do
ThemesInstallTask.install("some_theme": theme_repo_url)
expect(Theme.where(name: THEME_NAME).exists?).to eq(true)
end
end
describe "with options" do
it 'installs a theme from only a url' do
ThemesInstallTask.install({ "some_theme": { "url": theme_repo_url } })
expect(Theme.where(name: THEME_NAME).exists?).to eq(true)
end
it 'does not set the theme to default if the key/value is not present' do
ThemesInstallTask.install({ "some_theme": { "url": theme_repo_url } })
theme = Theme.find_by(name: THEME_NAME)
expect(theme.default?).to eq(false)
end
it 'sets the theme to default if the key/value is true' do
ThemesInstallTask.install({ "some_theme": { "url": theme_repo_url, default: true } })
theme = Theme.find_by(name: THEME_NAME)
expect(theme.default?).to eq(true)
end
it 'installs theme components, but does not add them to themes' do
ThemesInstallTask.install({ "some_theme": { "url": component_repo_url } })
theme = Theme.find_by(name: THEME_NAME)
expect(theme.component).to eq(true)
end
it 'adds component to all themes if "add_to_all_themes" is true' do
ThemesInstallTask.install({ "some_theme": { "url": component_repo_url, add_to_all_themes: true } })
theme = Theme.find_by(name: THEME_NAME)
Theme.where(component: false).each do |parent_theme|
expect(ChildTheme.find_by(parent_theme_id: parent_theme.id, child_theme_id: theme.id).nil?).to eq(false)
end
end
it 'updates theme fields' do
ThemesInstallTask.install({ "some_theme": component_repo_url })
theme = Theme.find_by(name: THEME_NAME)
remote = theme.remote_theme
scss = "body { background-color: black; }"
expect(theme.theme_fields.find_by(name: 'scss', value: scss)).to be_nil
File.write("#{component_repo}/common/common.scss", scss)
`cd #{component_repo} && git add common/common.scss`
`cd #{component_repo} && git commit -am "update"`
remote.update_remote_version
expect(remote.commits_behind).to eq(1)
expect(remote.remote_version).to eq(`cd #{component_repo} && git rev-parse HEAD`.strip)
ThemesInstallTask.install({ "some_theme": component_repo_url })
expect(theme.theme_fields.find_by(name: 'scss', value: scss)).not_to be_nil
expect(remote.reload.commits_behind).to eq(0)
end
end
end
describe '#theme_exists?' do
it 'can use https or ssh and find the same repo' do
remote_theme = RemoteTheme.create!(
remote_url: "https://github.com/org/testtheme.git",
local_version: "a2ec030e551fc8d8579790e1954876fe769fe40a",
remote_version: "21122230dbfed804067849393c3332083ddd0c07",
commits_behind: 2
)
Fabricate(:theme, remote_theme: remote_theme, user: admin)
# https
installer = ThemesInstallTask.new({ "url": "https://github.com/org/testtheme" })
expect(installer.theme_exists?).to eq(true)
# ssh
installer = ThemesInstallTask.new({ "url": "git@github.com:org/testtheme.git" })
expect(installer.theme_exists?).to eq(true)
end
end
end