"""
A dialog box for confirming GPG keys.
"""

# Copyright (C) 2009, Thomas Leonard
# -*- coding: utf-8 -*-
# See the README file for details, or visit http://0install.net.
from zeroinstall import _, translation

import gtk
from zeroinstall.injector.model import SafeException
from zeroinstall.injector import gpg, trust
from zeroinstall.support import tasks
from zeroinstall.gtkui import help_box, gtkutils

def frame(page, title, content, expand = False):
	frame = gtk.Frame()
	label = gtk.Label()
	label.set_markup('<b>%s</b>' % title)
	frame.set_label_widget(label)
	frame.set_shadow_type(gtk.SHADOW_NONE)
	if type(content) in (str, unicode):
		content = gtk.Label(content)
		content.set_alignment(0, 0.5)
		content.set_selectable(True)
	frame.add(content)
	if hasattr(content, 'set_padding'):
		content.set_padding(8, 4)
	else:
		content.set_border_width(8)
	page.pack_start(frame, expand, True, 0)

def pretty_fp(fp):
	s = fp[0:4]
	for x in range(4, len(fp), 4):
		s += ' ' + fp[x:x + 4]
	return s

def left(text):
	label = gtk.Label(text)
	label.set_alignment(0, 0.5)
	label.set_selectable(True)
	return label

def make_hints_area(closed, key_info_fetcher):
	def text(parent):
		text = ""
		for node in parent.childNodes:
			if node.nodeType == node.TEXT_NODE:
				text = text + node.data
		return text

	hints = gtk.VBox(False, 4)

	shown = set()
	def add_hints():
		infos = set(key_info_fetcher.info) - shown
		for info in infos:
			hints.add(make_hint(info.getAttribute("vote"), text(info)))
			shown.add(info)

		if not(key_info_fetcher.blocker or shown):
			hints.add(make_hint("bad", _('Warning: Nothing known about this key!')))

	if key_info_fetcher.blocker:
		status = left(key_info_fetcher.status)
		hints.add(status)

		@tasks.async
		def update_when_ready():
			while key_info_fetcher.blocker:
				yield key_info_fetcher.blocker, closed
				if closed.happened:
					# The dialog box was closed. Stop updating.
					return
				add_hints()
			status.destroy()
		update_when_ready()
	else:
		add_hints()

	hints.show()
	return hints

def make_hint(vote, hint_text):
	hint_icon = gtk.Image()
	if vote == "good":
		hint_icon.set_from_stock(gtk.STOCK_YES, gtk.ICON_SIZE_BUTTON)
	else:
		hint_icon.set_from_stock(gtk.STOCK_DIALOG_WARNING, gtk.ICON_SIZE_BUTTON)
	hint = left(hint_text)
	hint.set_line_wrap(True)
	hint_hbox = gtk.HBox(False, 4)
	hint_hbox.pack_start(hint_icon, False, True, 0)
	hint_hbox.pack_start(hint, True, True, 0)
	hint_icon.set_alignment(0, 0)
	hint_hbox.show_all()
	return hint_hbox

class TrustBox(gtk.Dialog):
	"""Display a dialog box asking the user to confirm that one of the
	keys is trusted for this domain.
	"""
	parent = None
	closed = None

	def __init__(self, pending, valid_sigs, parent):
		"""@since: 0.42"""
		assert valid_sigs

		gtk.Dialog.__init__(self)
		self.set_has_separator(False)
		self.set_position(gtk.WIN_POS_CENTER)
		self.set_transient_for(parent)

		self.closed = tasks.Blocker(_("confirming keys with user"))

		domain = trust.domain_from_url(pending.url)
		assert domain

		def destroy(box):
			self.closed.trigger()

		self.connect('destroy', destroy)

		self.set_title(_('Confirm trust'))

		vbox = gtk.VBox(False, 4)
		vbox.set_border_width(4)
		self.vbox.pack_start(vbox, True, True, 0)

		notebook = gtk.Notebook()

		if len(valid_sigs) == 1:
			notebook.set_show_tabs(False)

		label = left(_('Checking: %s') % pending.url)
		label.set_padding(4, 4)
		vbox.pack_start(label, False, True, 0)

		currently_trusted_keys = trust.trust_db.get_keys_for_domain(domain)
		if currently_trusted_keys:
			keys = [gpg.load_key(fingerprint) for fingerprint in currently_trusted_keys]
			descriptions = [_("%(key_name)s\n(fingerprint: %(key_fingerprint)s)") % {'key_name': key.name, 'key_fingerprint': pretty_fp(key.fingerprint)}
					for key in keys]
		else:
			descriptions = [_('None')]
		frame(vbox, _('Keys already approved for "%s"') % domain, '\n'.join(descriptions))

		label = left(translation.ngettext('This key signed the feed:', 'These keys signed the feed:', len(valid_sigs)))

		label.set_padding(4, 4)
		vbox.pack_start(label, False, True, 0)

		vbox.pack_start(notebook, True, True, 0)

		self.add_button(gtk.STOCK_HELP, gtk.RESPONSE_HELP)
		self.add_button(gtk.STOCK_CANCEL, gtk.RESPONSE_CANCEL)
		self.add_button(gtk.STOCK_ADD, gtk.RESPONSE_OK)
		self.set_default_response(gtk.RESPONSE_OK)

		trust_checkbox = {}	# Sig -> CheckButton
		def ok_sensitive():
			trust_any = False
			for toggle in trust_checkbox.values():
				if toggle.get_active():
					trust_any = True
					break
			self.set_response_sensitive(gtk.RESPONSE_OK, trust_any)

		first = True
		for sig in valid_sigs:
			if hasattr(sig, 'get_details'):
				name = '<unknown>'
				details = sig.get_details()
				for item in details:
					if item[0] == 'uid' and len(item) > 9:
						name = item[9]
						break
			else:
				name = None
			page = gtk.VBox(False, 4)
			page.set_border_width(8)

			frame(page, _('Fingerprint'), pretty_fp(sig.fingerprint))

			if name is not None:
				frame(page, _('Claimed identity'), name)

			frame(page, _('Unreliable hints database says'), make_hints_area(self.closed, valid_sigs[sig]))

			already_trusted = trust.trust_db.get_trust_domains(sig.fingerprint)
			if already_trusted:
				frame(page, _('You already trust this key for these domains'),
					'\n'.join(already_trusted))

			trust_checkbox[sig] = gtk.CheckButton(_('_Trust this key'))
			page.pack_start(trust_checkbox[sig], False, True, 0)
			trust_checkbox[sig].connect('toggled', lambda t: ok_sensitive())

			notebook.append_page(page, gtk.Label(name or 'Signature'))

			if first:
				trust_checkbox[sig].set_active(True)
				first = False

		ok_sensitive()
		self.vbox.show_all()

		if len(valid_sigs) == 1:
			for box in trust_checkbox.values():
				box.hide()

		def response(box, resp):
			if resp == gtk.RESPONSE_HELP:
				trust_help.display()
				return
			if resp == gtk.RESPONSE_OK:
				to_trust = [sig for sig in trust_checkbox if trust_checkbox[sig].get_active()]

				if not self._confirm_unknown_keys(to_trust, valid_sigs):
					return

				self.trust_keys(to_trust, domain)
			self.destroy()
		self.connect('response', response)

	def trust_keys(self, agreed_sigs, domain):
		assert domain
		try:
			for sig in agreed_sigs:
				trust.trust_db.trust_key(sig.fingerprint, domain)

			trust.trust_db.notify()
		except Exception as ex:
			gtkutils.show_message_box(self, str(ex), gtk.MESSAGE_ERROR)
			if not isinstance(ex, SafeException):
				raise

	def _confirm_unknown_keys(self, to_trust, valid_sigs):
		"""Check the key-info server's results for these keys. If we don't know any of them,
		ask for extra confirmation from the user.
		@param to_trust: the signatures the user wants to trust
		@return: True to continue"""

		def is_unknown(sig):
			for note in valid_sigs[sig].info:
				if note.getAttribute("vote") == "good":
					return False
			return True
		unknown = [sig for sig in to_trust if is_unknown(sig)]

		if unknown:
			if len(unknown) == 1:
				msg = _('WARNING: you are confirming a key which was not known to the key server. Are you sure?')
			else:
				msg = _('WARNING: you are confirming keys which were not known to the key server. Are you sure?')

			box = gtk.MessageDialog(self,
						gtk.DIALOG_DESTROY_WITH_PARENT,
						gtk.MESSAGE_QUESTION, gtk.BUTTONS_OK_CANCEL,
						msg)
			box.set_position(gtk.WIN_POS_CENTER)
			response = box.run()
			box.destroy()
			return response == gtk.RESPONSE_OK

		return True

trust_help = help_box.HelpBox(_("Trust Help"),
(_('Overview'), '\n' +
_("""When you run a program, it typically has access to all your files and can generally do \
anything that you're allowed to do (delete files, send emails, etc). So it's important \
to make sure that you don't run anything malicious.""")),

(_('Digital signatures'), '\n' +
_("""Each software author creates a 'key-pair'; a 'public key' and a 'private key'. Without going \
into the maths, only something encrypted with the private key will decrypt with the public key.

So, when a programmer releases some software, they encrypt it with their private key (which no-one \
else has). When you download it, the injector checks that it decrypts using their public key, thus \
proving that it came from them and hasn't been tampered with.""")),

(_('Trust'), '\n' +
_("""After the injector has checked that the software hasn't been modified since it was signed with \
the private key, you still have the following problems:

1. Does the public key you have really belong to the author?
2. Even if the software really did come from that person, do you trust them?""")),

(_('Key fingerprints'), '\n' +
_("""To confirm (1), you should compare the public key you have with the genuine one. To make this \
easier, the injector displays a 'fingerprint' for the key. Look in mailing list postings or some \
other source to check that the fingerprint is right (a different key will have a different \
fingerprint).

You're trying to protect against the situation where an attacker breaks into a web site \
and puts up malicious software, signed with the attacker's private key, and puts up the \
attacker's public key too. If you've downloaded this software before, you \
should be suspicious that you're being asked to confirm another key!""")),

(_('Reputation'), '\n' +
_("""In general, most problems seem to come from malicous and otherwise-unknown people \
replacing software with modified versions, or creating new programs intended only to \
cause damage. So, check your programs are signed by a key with a good reputation!""")))
