Updated to newer Python syntax where possible
[zeroinstall/zeroinstall-afb.git] / zeroinstall / injector / trust.py
blob776d6c8520868aad158662e964612a185c1f0d9c
1 """
2 Records who we trust to sign feeds.
4 Trust is divided up into domains, so that it is possible to trust a key
5 in some cases and not others.
7 @var trust_db: Singleton trust database instance.
8 """
10 # Copyright (C) 2009, Thomas Leonard
11 # See the README file for details, or visit http://0install.net.
13 from zeroinstall import _
14 import os
16 from zeroinstall.support import basedir
17 from .namespaces import config_site, config_prog, XMLNS_TRUST
19 class TrustDB(object):
20 """A database of trusted keys.
21 @ivar keys: maps trusted key fingerprints to a set of domains for which where it is trusted
22 @type keys: {str: set(str)}
23 @ivar watchers: callbacks invoked by L{notify}
24 @see: L{trust_db} - the singleton instance of this class"""
25 __slots__ = ['keys', 'watchers']
27 def __init__(self):
28 self.keys = None
29 self.watchers = []
31 def is_trusted(self, fingerprint, domain = None):
32 self.ensure_uptodate()
34 domains = self.keys.get(fingerprint, None)
35 if not domains: return False # Unknown key
37 if domain is None:
38 return True # Deprecated
40 return domain in domains or '*' in domains
42 def get_trust_domains(self, fingerprint):
43 """Return the set of domains in which this key is trusted.
44 If the list includes '*' then the key is trusted everywhere.
45 @since: 0.27
46 """
47 self.ensure_uptodate()
48 return self.keys.get(fingerprint, set())
50 def get_keys_for_domain(self, domain):
51 """Return the set of keys trusted for this domain.
52 @since: 0.27"""
53 self.ensure_uptodate()
54 return set([fp for fp in self.keys
55 if domain in self.keys[fp]])
57 def trust_key(self, fingerprint, domain = '*'):
58 """Add key to the list of trusted fingerprints.
59 @param fingerprint: base 16 fingerprint without any spaces
60 @type fingerprint: str
61 @param domain: domain in which key is to be trusted
62 @type domain: str
63 @note: call L{notify} after trusting one or more new keys"""
64 if self.is_trusted(fingerprint, domain): return
66 int(fingerprint, 16) # Ensure fingerprint is valid
68 if fingerprint not in self.keys:
69 self.keys[fingerprint] = set()
71 #if domain == '*':
72 # warn("Calling trust_key() without a domain is deprecated")
74 self.keys[fingerprint].add(domain)
75 self.save()
77 def untrust_key(self, key, domain = '*'):
78 self.ensure_uptodate()
79 self.keys[key].remove(domain)
81 if not self.keys[key]:
82 # No more domains for this key
83 del self.keys[key]
85 self.save()
87 def save(self):
88 from xml.dom import minidom
89 import tempfile
91 doc = minidom.Document()
92 root = doc.createElementNS(XMLNS_TRUST, 'trusted-keys')
93 root.setAttribute('xmlns', XMLNS_TRUST)
94 doc.appendChild(root)
96 for fingerprint in self.keys:
97 keyelem = doc.createElementNS(XMLNS_TRUST, 'key')
98 root.appendChild(keyelem)
99 keyelem.setAttribute('fingerprint', fingerprint)
100 for domain in self.keys[fingerprint]:
101 domainelem = doc.createElementNS(XMLNS_TRUST, 'domain')
102 domainelem.setAttribute('value', domain)
103 keyelem.appendChild(domainelem)
105 d = basedir.save_config_path(config_site, config_prog)
106 fd, tmpname = tempfile.mkstemp(dir = d, prefix = 'trust-')
107 tmp = os.fdopen(fd, 'wb')
108 doc.writexml(tmp, indent = "", addindent = " ", newl = "\n")
109 tmp.close()
111 os.rename(tmpname, os.path.join(d, 'trustdb.xml'))
113 def notify(self):
114 """Call all watcher callbacks.
115 This should be called after trusting or untrusting one or more new keys.
116 @since: 0.25"""
117 for w in self.watchers: w()
119 def ensure_uptodate(self):
120 from xml.dom import minidom
122 # This is a bit inefficient... (could cache things)
123 self.keys = {}
125 trust = basedir.load_first_config(config_site, config_prog, 'trustdb.xml')
126 if trust:
127 keys = minidom.parse(trust).documentElement
128 for key in keys.getElementsByTagNameNS(XMLNS_TRUST, 'key'):
129 domains = set()
130 self.keys[key.getAttribute('fingerprint')] = domains
131 for domain in key.getElementsByTagNameNS(XMLNS_TRUST, 'domain'):
132 domains.add(domain.getAttribute('value'))
133 else:
134 # Convert old database to XML format
135 trust = basedir.load_first_config(config_site, config_prog, 'trust')
136 if trust:
137 #print "Loading trust from", trust_db
138 for key in file(trust).read().split('\n'):
139 if key:
140 self.keys[key] = set(['*'])
141 else:
142 # No trust database found.
143 # Trust Thomas Leonard's key for 0install.net by default.
144 # Avoids distracting confirmation box on first run when we check
145 # for updates to the GUI.
146 self.keys['92429807C9853C0744A68B9AAE07828059A53CC1'] = set(['0install.net'])
148 def domain_from_url(url):
149 """Extract the trust domain for a URL.
150 @param url: the feed's URL
151 @type url: str
152 @return: the trust domain
153 @rtype: str
154 @since: 0.27
155 @raise SafeException: the URL can't be parsed"""
156 import urlparse
157 from zeroinstall import SafeException
158 if os.path.isabs(url):
159 raise SafeException(_("Can't get domain from a local path: '%s'") % url)
160 domain = urlparse.urlparse(url)[1]
161 if domain and domain != '*':
162 return domain
163 raise SafeException(_("Can't extract domain from URL '%s'") % url)
165 trust_db = TrustDB()