blob: d40d79b895ecc4a6eb5348240bd95901f746c9fe [file] [log] [blame]
// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/ui/views/certificate_selector.h"
#include <stddef.h> // For size_t.
#include <string>
#include <vector>
#include "base/command_line.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "build/build_config.h"
#include "chrome/browser/certificate_viewer.h"
#include "chrome/browser/ui/views/chrome_layout_provider.h"
#include "chrome/common/buildflags.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/grit/generated_resources.h"
#include "components/constrained_window/constrained_window_views.h"
#include "components/strings/grit/components_strings.h"
#include "components/web_modal/web_contents_modal_dialog_manager.h"
#include "content/public/browser/web_contents.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/table_model.h"
#include "ui/base/models/table_model_observer.h"
#include "ui/base/mojom/dialog_button.mojom.h"
#include "ui/base/mojom/ui_base_types.mojom-shared.h"
#include "ui/views/controls/button/md_text_button.h"
#include "ui/views/controls/scroll_view.h"
#include "ui/views/controls/table/table_view.h"
#include "ui/views/layout/box_layout.h"
#include "ui/views/widget/widget.h"
#if BUILDFLAG(IS_CHROMEOS)
#include "chrome/browser/ash/certificate_provider/certificate_provider_service.h"
#include "chrome/browser/ash/certificate_provider/certificate_provider_service_factory.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/browser/extension_registry_factory.h"
#endif
#if BUILDFLAG(ENABLE_GLIC)
#include "chrome/browser/glic/public/glic_enabling.h"
#include "chrome/browser/glic/public/glic_keyed_service.h"
#include "chrome/browser/glic/public/glic_keyed_service_factory.h"
#include "chrome/browser/glic/widget/glic_window_controller.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_finder.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#endif
#if BUILDFLAG(ENABLE_GLIC)
namespace {
// Checks that `contents` is for glic.
bool IsForGlic(content::WebContents* contents) {
content::WebContents* outer = contents->GetOutermostWebContents();
glic::GlicKeyedService* glic_service =
glic::GlicKeyedServiceFactory::GetGlicKeyedService(
outer->GetBrowserContext());
return glic_service && (glic_service->IsGlicWebUi(contents) ||
glic_service->IsGlicWebUi(outer));
}
// Combines IsForGlic with glic dev switch.
bool UseGlicDevFlow(content::WebContents* contents) {
return base::CommandLine::ForCurrentProcess()->HasSwitch(
::switches::kGlicDev) &&
IsForGlic(contents);
}
} // namespace
#endif
const int CertificateSelector::kTableViewWidth = 500;
const int CertificateSelector::kTableViewHeight = 150;
class CertificateSelector::CertificateTableModel : public ui::TableModel {
public:
// |identities| and |provider_names| must have the same size.
CertificateTableModel(const net::ClientCertIdentityList& identities,
const std::vector<std::string>& provider_names);
CertificateTableModel(const CertificateTableModel&) = delete;
CertificateTableModel& operator=(const CertificateTableModel&) = delete;
// ui::TableModel:
size_t RowCount() override;
std::u16string GetText(size_t index, int column_id) override;
void SetObserver(ui::TableModelObserver* observer) override;
private:
struct Row {
std::u16string subject;
std::u16string issuer;
std::u16string provider;
std::u16string serial;
};
std::vector<Row> rows_;
};
CertificateSelector::CertificateTableModel::CertificateTableModel(
const net::ClientCertIdentityList& identities,
const std::vector<std::string>& provider_names) {
DCHECK_EQ(identities.size(), provider_names.size());
for (size_t i = 0; i < identities.size(); i++) {
net::X509Certificate* cert = identities[i]->certificate();
Row row;
row.subject = base::UTF8ToUTF16(cert->subject().GetDisplayName());
row.issuer = base::UTF8ToUTF16(cert->issuer().GetDisplayName());
row.provider = base::UTF8ToUTF16(provider_names[i]);
if (cert->serial_number().size() < std::numeric_limits<size_t>::max() / 2) {
row.serial = base::UTF8ToUTF16(base::HexEncode(cert->serial_number()));
}
rows_.push_back(row);
}
}
size_t CertificateSelector::CertificateTableModel::RowCount() {
return rows_.size();
}
std::u16string CertificateSelector::CertificateTableModel::GetText(
size_t index,
int column_id) {
DCHECK_LT(index, rows_.size());
const Row& row = rows_[index];
switch (column_id) {
case IDS_CERT_SELECTOR_SUBJECT_COLUMN:
return row.subject;
case IDS_CERT_SELECTOR_ISSUER_COLUMN:
return row.issuer;
case IDS_CERT_SELECTOR_PROVIDER_COLUMN:
return row.provider;
case IDS_CERT_SELECTOR_SERIAL_COLUMN:
return row.serial;
default:
NOTREACHED();
}
}
void CertificateSelector::CertificateTableModel::SetObserver(
ui::TableModelObserver* observer) {}
CertificateSelector::CertificateSelector(net::ClientCertIdentityList identities,
content::WebContents* web_contents)
: web_contents_(web_contents) {
SetCanResize(true);
SetModalType(ui::mojom::ModalType::kChild);
CHECK(web_contents_);
view_cert_button_ = SetExtraView(std::make_unique<views::MdTextButton>(
base::BindRepeating(&CertificateSelector::ViewCertButtonPressed,
base::Unretained(this)),
l10n_util::GetStringUTF16(IDS_PAGE_INFO_CERT_INFO_BUTTON)));
set_margins(ChromeLayoutProvider::Get()->GetDialogInsetsForContentType(
views::DialogContentType::kText, views::DialogContentType::kControl));
// |provider_names| and |identities_| are parallel arrays.
// The entry at index |i| is the provider name for |identities_[i]|.
std::vector<std::string> provider_names;
#if BUILDFLAG(IS_CHROMEOS)
chromeos::CertificateProviderService* service =
chromeos::CertificateProviderServiceFactory::GetForBrowserContext(
web_contents->GetBrowserContext());
extensions::ExtensionRegistry* extension_registry =
extensions::ExtensionRegistryFactory::GetForBrowserContext(
web_contents->GetBrowserContext());
for (auto& identity : identities) {
std::string provider_name;
bool has_extension = false;
std::string extension_id;
if (service->LookUpCertificate(*identity->certificate(), &has_extension,
&extension_id)) {
if (!has_extension) {
// This certificate was provided by an extension but isn't provided by
// any extension currently. Don't expose it to the user.
continue;
}
const auto* extension = extension_registry->GetExtensionById(
extension_id, extensions::ExtensionRegistry::ENABLED);
if (!extension) {
// This extension was unloaded in the meantime. Don't show the
// certificate.
continue;
}
provider_name = extension->short_name();
show_provider_column_ = true;
} // Otherwise the certificate is provided by the platform.
identities_.push_back(std::move(identity));
provider_names.push_back(provider_name);
}
#else
provider_names.assign(identities.size(), std::string());
identities_ = std::move(identities);
#endif
model_ = std::make_unique<CertificateTableModel>(identities_, provider_names);
}
CertificateSelector::~CertificateSelector() {
table_->SetModel(nullptr);
}
// static
bool CertificateSelector::CanShow(content::WebContents* web_contents) {
#if BUILDFLAG(ENABLE_GLIC)
if (UseGlicDevFlow(web_contents)) {
return true;
}
#endif
content::WebContents* top_level_web_contents =
constrained_window::GetTopLevelWebContents(web_contents);
return web_modal::WebContentsModalDialogManager::FromWebContents(
top_level_web_contents) != nullptr;
}
void CertificateSelector::Show() {
#if BUILDFLAG(ENABLE_GLIC)
// In the event that glic is showing and glic-dev is enabled, always show the
// certificate picker on the glic window. This is not fully correct, but
// satisfies the main dev use case with minimal overhead.
if (UseGlicDevFlow(web_contents_)) {
Profile* profile =
Profile::FromBrowserContext(web_contents_->GetBrowserContext());
Browser* browser = chrome::FindLastActiveWithProfile(profile);
if (browser) {
SetModalType(ui::mojom::ModalType::kWindow);
constrained_window::CreateBrowserModalDialogViews(
this, browser->GetBrowserView().GetNativeWindow())
->Show();
} else {
LOG(ERROR) << "Dev error. Make sure there's a browser window of the "
"matching profile open.";
}
return;
}
#endif
constrained_window::ShowWebModalDialogViews(this, web_contents_);
// TODO(isandrk): A certificate that was previously provided by *both* the
// platform and an extension will get incorrectly filtered out if the
// extension stops providing it (both instances will be filtered out), hence
// the |identities_| array will be empty. Displaying a dialog with an empty
// list won't make much sense for the user, and also there are some CHECKs in
// the code that will fail when the list is empty and that's why an early exit
// is performed here. See crbug.com/641440 for more details.
if (identities_.empty()) {
GetWidget()->Close();
return;
}
// Select the first row automatically. This must be done after the dialog has
// been created.
table_->Select(0);
}
void CertificateSelector::InitWithText(
std::unique_ptr<views::View> text_label) {
ChromeLayoutProvider* provider = ChromeLayoutProvider::Get();
const int vertical_spacing =
provider->GetDistanceMetric(views::DISTANCE_RELATED_CONTROL_VERTICAL);
SetLayoutManager(std::make_unique<views::BoxLayout>(
views::BoxLayout::Orientation::kVertical, gfx::Insets(),
vertical_spacing));
AddChildView(std::move(text_label));
std::vector<ui::TableColumn> columns;
columns.emplace_back(IDS_CERT_SELECTOR_SUBJECT_COLUMN, ui::TableColumn::LEFT,
-1, 0.4f);
columns.emplace_back(IDS_CERT_SELECTOR_ISSUER_COLUMN, ui::TableColumn::LEFT,
-1, 0.2f);
if (show_provider_column_) {
columns.emplace_back(IDS_CERT_SELECTOR_PROVIDER_COLUMN,
ui::TableColumn::LEFT, -1, 0.4f);
}
columns.emplace_back(IDS_CERT_SELECTOR_SERIAL_COLUMN, ui::TableColumn::LEFT,
-1, 0.2f);
for (auto& column : columns) {
column.sortable = true;
}
auto table = std::make_unique<views::TableView>(model_.get(), columns,
views::TableType::kTextOnly,
true /* single_selection */);
table_ = table.get();
table->set_observer(this);
AddChildView(views::TableView::CreateScrollViewWithTable(std::move(table)))
->SetPreferredSize(gfx::Size(kTableViewWidth, kTableViewHeight));
}
ui::TableModel* CertificateSelector::table_model_for_testing() const {
return model_.get();
}
net::ClientCertIdentity* CertificateSelector::GetSelectedCert() const {
const std::optional<size_t> selected = table_->GetFirstSelectedRow();
if (!selected.has_value()) {
return nullptr;
}
DCHECK_LT(selected.value(), identities_.size());
return identities_[selected.value()].get();
}
bool CertificateSelector::Accept() {
const std::optional<size_t> selected = table_->GetFirstSelectedRow();
if (!selected.has_value()) {
return false;
}
DCHECK_LT(selected.value(), identities_.size());
AcceptCertificate(std::move(identities_[selected.value()]));
return true;
}
std::u16string CertificateSelector::GetWindowTitle() const {
return l10n_util::GetStringUTF16(IDS_CLIENT_CERT_DIALOG_TITLE);
}
bool CertificateSelector::IsDialogButtonEnabled(
ui::mojom::DialogButton button) const {
return button != ui::mojom::DialogButton::kOk || GetSelectedCert();
}
views::View* CertificateSelector::GetInitiallyFocusedView() {
DCHECK(table_);
return table_;
}
void CertificateSelector::ViewCertButtonPressed() {
net::ClientCertIdentity* const cert = GetSelectedCert();
if (!cert) {
return;
}
ShowCertificateViewerForClientAuth(web_contents_,
web_contents_->GetTopLevelNativeWindow(),
cert->certificate());
}
void CertificateSelector::OnSelectionChanged() {
GetOkButton()->SetEnabled(GetSelectedCert() != nullptr);
}
void CertificateSelector::OnDoubleClick() {
if (GetSelectedCert()) {
AcceptDialog();
}
}
BEGIN_METADATA(CertificateSelector)
END_METADATA