/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar
5971.1.36 by Jonathan Riddell
update copyright
1
# Copyright (C) 2005, 2011 Canonical Ltd
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
2
#   Authors: Robert Collins <robert.collins@canonical.com>
3
#
4
# This program is free software; you can redistribute it and/or modify
5
# it under the terms of the GNU General Public License as published by
6
# the Free Software Foundation; either version 2 of the License, or
7
# (at your option) any later version.
8
#
9
# This program is distributed in the hope that it will be useful,
10
# but WITHOUT ANY WARRANTY; without even the implied warranty of
11
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
# GNU General Public License for more details.
13
#
14
# You should have received a copy of the GNU General Public License
15
# along with this program; if not, write to the Free Software
4183.7.1 by Sabin Iacob
update FSF mailing address
16
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
17
18
"""GPG signing and checking logic."""
19
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
20
import os
21
import sys
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
22
from StringIO import StringIO
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
23
24
from bzrlib.lazy_import import lazy_import
25
lazy_import(globals(), """
1442.1.58 by Robert Collins
gpg signing of content
26
import errno
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
27
import subprocess
28
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
29
from bzrlib import (
30
    errors,
31
    trace,
1551.8.11 by Aaron Bentley
Clear terminal before signing
32
    ui,
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
33
    )
6092.2.3 by Jonathan Riddell
improve formatting
34
from bzrlib.i18n import (
35
    gettext, 
36
    ngettext,
37
    )
1996.3.1 by John Arbash Meinel
Demandloading builtins.py drops our load time from 350ms to 291ms
38
""")
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
39
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
40
#verification results
5971.1.1 by Jonathan Riddell
add a verify command
41
SIGNATURE_VALID = 0
42
SIGNATURE_KEY_MISSING = 1
43
SIGNATURE_NOT_VALID = 2
44
SIGNATURE_NOT_SIGNED = 3
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
45
SIGNATURE_EXPIRED = 4
5971.1.1 by Jonathan Riddell
add a verify command
46
47
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
48
class DisabledGPGStrategy(object):
49
    """A GPG Strategy that makes everything fail."""
50
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
51
    @staticmethod
52
    def verify_signatures_available():
53
        return True
54
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
55
    def __init__(self, ignored):
56
        """Real strategies take a configuration."""
57
58
    def sign(self, content):
59
        raise errors.SigningFailed('Signing is disabled.')
60
5971.1.31 by Jonathan Riddell
and update tests
61
    def verify(self, content, testament):
5971.1.33 by Jonathan Riddell
rename errors.VerifyFailed to errors.SignatureVerificationFailed
62
        raise errors.SignatureVerificationFailed('Signature verification is \
63
disabled.')
5971.1.6 by Jonathan Riddell
fix methods for dummy gpg strategies
64
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
65
    def set_acceptable_keys(self, command_line_input):
5971.1.14 by Jonathan Riddell
add test for set_acceptable_keys, accept non-trusted keys if specified as acceptable, import dummy key in tests so it works outside my machine
66
        pass
67
1442.1.62 by Robert Collins
Allow creation of testaments from uncommitted data, and use that to get signatures before committing revisions.
68
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
69
class LoopbackGPGStrategy(object):
5971.1.85 by Jonathan Riddell
use unicode strings for UI
70
    """A GPG Strategy that acts like 'cat' - data is just passed through.
5971.1.86 by Jonathan Riddell
doc string formatting
71
    Used in tests.
72
    """
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
73
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
74
    @staticmethod
75
    def verify_signatures_available():
76
        return True
77
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
78
    def __init__(self, ignored):
79
        """Real strategies take a configuration."""
80
81
    def sign(self, content):
1551.12.15 by Aaron Bentley
add header/trailer to fake clearsigned texts
82
        return ("-----BEGIN PSEUDO-SIGNED CONTENT-----\n" + content +
1551.12.52 by Aaron Bentley
speling fix
83
                "-----END PSEUDO-SIGNED CONTENT-----\n")
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
84
5971.1.31 by Jonathan Riddell
and update tests
85
    def verify(self, content, testament):
5971.1.22 by Jonathan Riddell
fix tests
86
        return SIGNATURE_VALID, None
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
87
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
88
    def set_acceptable_keys(self, command_line_input):
89
        if command_line_input is not None:
90
            patterns = command_line_input.split(",")
91
            self.acceptable_keys = []
92
            for pattern in patterns:
93
                if pattern == "unknown":
94
                    pass
95
                else:
96
                    self.acceptable_keys.append(pattern)
5971.1.14 by Jonathan Riddell
add test for set_acceptable_keys, accept non-trusted keys if specified as acceptable, import dummy key in tests so it works outside my machine
97
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
98
    def do_verifications(self, revisions, repository):
99
        count = {SIGNATURE_VALID: 0,
100
                 SIGNATURE_KEY_MISSING: 0,
101
                 SIGNATURE_NOT_VALID: 0,
6043.2.10 by Jonathan Riddell
fix test test_verify_commits_acceptable_key
102
                 SIGNATURE_NOT_SIGNED: 0,
103
                 SIGNATURE_EXPIRED: 0}
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
104
        result = []
105
        all_verifiable = True
106
        for rev_id in revisions:
107
            verification_result, uid =\
6257.3.1 by Jelmer Vernooij
Support verifying remote signatures.
108
                repository.verify_revision_signature(rev_id,self)
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
109
            result.append([rev_id, verification_result, uid])
110
            count[verification_result] += 1
111
            if verification_result != SIGNATURE_VALID:
112
                all_verifiable = False
113
        return (count, result, all_verifiable)
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
114
115
    def valid_commits_message(self, count):
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
116
        return gettext(u"{0} commits with valid signatures").format(
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
117
                                        count[SIGNATURE_VALID])            
118
119
    def unknown_key_message(self, count):
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
120
        return ngettext(u"{0} commit with unknown key",
5971.1.85 by Jonathan Riddell
use unicode strings for UI
121
                             u"{0} commits with unknown keys",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
122
                             count[SIGNATURE_KEY_MISSING]).format(
123
                                        count[SIGNATURE_KEY_MISSING])
124
125
    def commit_not_valid_message(self, count):
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
126
        return ngettext(u"{0} commit not valid",
5971.1.85 by Jonathan Riddell
use unicode strings for UI
127
                             u"{0} commits not valid",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
128
                             count[SIGNATURE_NOT_VALID]).format(
129
                                            count[SIGNATURE_NOT_VALID])
130
131
    def commit_not_signed_message(self, count):
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
132
        return ngettext(u"{0} commit not signed",
5971.1.85 by Jonathan Riddell
use unicode strings for UI
133
                             u"{0} commits not signed",
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
134
                             count[SIGNATURE_NOT_SIGNED]).format(
135
                                        count[SIGNATURE_NOT_SIGNED])
136
6043.3.3 by Jonathan Riddell
add expired_commit_message() to LoopbackGPGStrategy
137
    def expired_commit_message(self, count):
6092.2.7 by Jonathan Riddell
i18n.gettext() -> gettext()
138
        return ngettext(u"{0} commit with key now expired",
139
                        u"{0} commits with key now expired",
140
                        count[SIGNATURE_EXPIRED]).format(
6043.3.3 by Jonathan Riddell
add expired_commit_message() to LoopbackGPGStrategy
141
                                        count[SIGNATURE_EXPIRED])
142
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
143
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
144
def _set_gpg_tty():
145
    tty = os.environ.get('TTY')
146
    if tty is not None:
147
        os.environ['GPG_TTY'] = tty
1912.3.2 by John Arbash Meinel
Adding some logging, because on my machine TTY is not exported by default.
148
        trace.mutter('setting GPG_TTY=%s', tty)
149
    else:
150
        # This is not quite worthy of a warning, because some people
151
        # don't need GPG_TTY to be set. But it is worthy of a big mark
152
        # in ~/.bzr.log, so that people can debug it if it happens to them
153
        trace.mutter('** Env var TTY empty, cannot set GPG_TTY.'
154
                     '  Is TTY exported?')
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
155
156
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
157
class GPGStrategy(object):
158
    """GPG Signing and checking facilities."""
3943.8.1 by Marius Kruger
remove all trailing whitespace from bzr source
159
5971.1.11 by Jonathan Riddell
add set_acceptable_keys() so user can specify which gpg keys can be used for verification
160
    acceptable_keys = None
161
6351.3.9 by Vincent Ladeuil
Move __init__ at the beginning of the class since that's the current idiom.
162
    def __init__(self, config_stack):
163
        self._config_stack = config_stack
164
        try:
165
            import gpgme
166
            self.context = gpgme.Context()
167
        except ImportError, error:
168
            pass # can't use verify()
169
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
170
    @staticmethod
171
    def verify_signatures_available():
5971.1.82 by Jonathan Riddell
method doc
172
        """
5971.1.86 by Jonathan Riddell
doc string formatting
173
        check if this strategy can verify signatures
174
5971.1.82 by Jonathan Riddell
method doc
175
        :return: boolean if this strategy can verify signatures
176
        """
5971.1.60 by Jonathan Riddell
move checking for gpgme availability into gpg.py
177
        try:
178
            import gpgme
179
            return True
180
        except ImportError, error:
181
            return False
182
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
183
    def _command_line(self):
6351.3.2 by Jelmer Vernooij
Convert some gpg options to config stacks.
184
        return [self._config_stack.get('gpg_signing_command'), '--clearsign',
185
                '-u', self._config_stack.get('gpg_signing_key')]
1442.1.57 by Robert Collins
check that we get the right command line from the default gpg strategy.
186
1442.1.58 by Robert Collins
gpg signing of content
187
    def sign(self, content):
2273.1.1 by John Arbash Meinel
``GPGStrategy.sign()`` will now raise ``BzrBadParameterUnicode`` if
188
        if isinstance(content, unicode):
189
            raise errors.BzrBadParameterUnicode('content')
1551.8.11 by Aaron Bentley
Clear terminal before signing
190
        ui.ui_factory.clear_term()
1963.1.8 by John Arbash Meinel
Don't use preexec_fn on win32
191
192
        preexec_fn = _set_gpg_tty
193
        if sys.platform == 'win32':
194
            # Win32 doesn't support preexec_fn, but wouldn't support TTY anyway.
195
            preexec_fn = None
1442.1.58 by Robert Collins
gpg signing of content
196
        try:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
197
            process = subprocess.Popen(self._command_line(),
198
                                       stdin=subprocess.PIPE,
1912.3.1 by John Arbash Meinel
updating gpg.py to set GPG_TTY in the environment.
199
                                       stdout=subprocess.PIPE,
1963.1.8 by John Arbash Meinel
Don't use preexec_fn on win32
200
                                       preexec_fn=preexec_fn)
1442.1.58 by Robert Collins
gpg signing of content
201
            try:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
202
                result = process.communicate(content)[0]
1442.1.58 by Robert Collins
gpg signing of content
203
                if process.returncode is None:
204
                    process.wait()
205
                if process.returncode != 0:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
206
                    raise errors.SigningFailed(self._command_line())
1442.1.58 by Robert Collins
gpg signing of content
207
                return result
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
208
            except OSError, e:
1442.1.58 by Robert Collins
gpg signing of content
209
                if e.errno == errno.EPIPE:
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
210
                    raise errors.SigningFailed(self._command_line())
1442.1.59 by Robert Collins
Add re-sign command to generate a digital signature on a single revision.
211
                else:
212
                    raise
1442.1.58 by Robert Collins
gpg signing of content
213
        except ValueError:
214
            # bad subprocess parameters, should never happen.
215
            raise
216
        except OSError, e:
217
            if e.errno == errno.ENOENT:
218
                # gpg is not installed
1185.78.4 by John Arbash Meinel
Reverting gpg changes, should not be mainline, see gpg_uses_tempfile plugin.
219
                raise errors.SigningFailed(self._command_line())
1442.1.58 by Robert Collins
gpg signing of content
220
            else:
221
                raise
5971.1.1 by Jonathan Riddell
add a verify command
222
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
223
    def verify(self, content, testament):
5971.1.7 by Jonathan Riddell
add method docs
224
        """Check content has a valid signature.
225
        
226
        :param content: the commit signature
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
227
        :param testament: the valid testament string for the commit
5971.1.7 by Jonathan Riddell
add method docs
228
        
5971.1.18 by Jonathan Riddell
add email to verbose output
229
        :return: SIGNATURE_VALID or a failed SIGNATURE_ value, key uid if valid
5971.1.7 by Jonathan Riddell
add method docs
230
        """
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
231
        try:
232
            import gpgme
5971.1.41 by Jonathan Riddell
fix calling GpgmeNotInstalled
233
        except ImportError, error:
234
            raise errors.GpgmeNotInstalled(error)
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
235
5971.1.1 by Jonathan Riddell
add a verify command
236
        signature = StringIO(content)
5971.1.4 by Jonathan Riddell
tidy up repository and gpg.py
237
        plain_output = StringIO()
5971.1.1 by Jonathan Riddell
add a verify command
238
        
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
239
        try:
5971.1.61 by Jonathan Riddell
make gpgme context global to class
240
            result = self.context.verify(signature, None, plain_output)
5971.1.5 by Jonathan Riddell
catch errors from gpgme, implement verify in dummy gpg strategies
241
        except gpgme.GpgmeError,error:
5971.1.33 by Jonathan Riddell
rename errors.VerifyFailed to errors.SignatureVerificationFailed
242
            raise errors.SignatureVerificationFailed(error[2])
5971.1.1 by Jonathan Riddell
add a verify command
243
6043.2.15 by Jonathan Riddell
turn comments into sentences
244
        # No result if input is invalid.
245
        # test_verify_invalid()
5971.1.9 by Jonathan Riddell
add some tests
246
        if len(result) == 0:
5971.1.17 by Jonathan Riddell
add verbose option
247
            return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
248
        # User has specified a list of acceptable keys, check our result is in it.
249
        # test_verify_unacceptable_key()
5971.1.13 by Jonathan Riddell
return missing if not in acceptable keys
250
        fingerprint = result[0].fpr
251
        if self.acceptable_keys is not None:
6043.2.4 by Jonathan Riddell
add test for valid_but_unacceptable_key, document tests in verify method
252
            if not fingerprint in self.acceptable_keys:                
5971.1.27 by Jonathan Riddell
verbose info for unknown keys
253
                return SIGNATURE_KEY_MISSING, fingerprint[-8:]
6043.2.15 by Jonathan Riddell
turn comments into sentences
254
        # Check the signature actually matches the testament.
255
        # test_verify_bad_testament()
5971.1.30 by Jonathan Riddell
check the testament actually matches the commit when validating
256
        if testament != plain_output.getvalue():
6043.2.4 by Jonathan Riddell
add test for valid_but_unacceptable_key, document tests in verify method
257
            return SIGNATURE_NOT_VALID, None 
6043.2.15 by Jonathan Riddell
turn comments into sentences
258
        # Yay gpgme set the valid bit.
259
        # Can't write a test for this one as you can't set a key to be
260
        # trusted using gpgme.
5971.1.1 by Jonathan Riddell
add a verify command
261
        if result[0].summary & gpgme.SIGSUM_VALID:
5971.1.61 by Jonathan Riddell
make gpgme context global to class
262
            key = self.context.get_key(fingerprint)
5971.1.17 by Jonathan Riddell
add verbose option
263
            name = key.uids[0].name
5971.1.18 by Jonathan Riddell
add email to verbose output
264
            email = key.uids[0].email
265
            return SIGNATURE_VALID, name + " <" + email + ">"
6043.2.15 by Jonathan Riddell
turn comments into sentences
266
        # Sigsum_red indicates a problem, unfortunatly I have not been able
267
        # to write any tests which actually set this.
5971.1.1 by Jonathan Riddell
add a verify command
268
        if result[0].summary & gpgme.SIGSUM_RED:
5971.1.17 by Jonathan Riddell
add verbose option
269
            return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
270
        # GPG does not know this key.
271
        # test_verify_unknown_key()
5971.1.1 by Jonathan Riddell
add a verify command
272
        if result[0].summary & gpgme.SIGSUM_KEY_MISSING:
5971.1.27 by Jonathan Riddell
verbose info for unknown keys
273
            return SIGNATURE_KEY_MISSING, fingerprint[-8:]
6043.2.15 by Jonathan Riddell
turn comments into sentences
274
        # Summary isn't set if sig is valid but key is untrusted
275
        # but if user has explicity set the key as acceptable we can validate it.
5971.1.14 by Jonathan Riddell
add test for set_acceptable_keys, accept non-trusted keys if specified as acceptable, import dummy key in tests so it works outside my machine
276
        if result[0].summary == 0 and self.acceptable_keys is not None:
277
            if fingerprint in self.acceptable_keys:
6043.2.15 by Jonathan Riddell
turn comments into sentences
278
                # test_verify_untrusted_but_accepted()
6043.2.4 by Jonathan Riddell
add test for valid_but_unacceptable_key, document tests in verify method
279
                return SIGNATURE_VALID, None 
6043.2.15 by Jonathan Riddell
turn comments into sentences
280
        # test_verify_valid_but_untrusted()
6043.2.7 by Jonathan Riddell
some reordering of verification, improve names of tests
281
        if result[0].summary == 0 and self.acceptable_keys is None:
282
            return SIGNATURE_NOT_VALID, None
6043.2.3 by Jonathan Riddell
comment gpgme verify usage so it can be verified by someone who knows what they're talking about, also accept signatures from keys which were valid but have since expired
283
        if result[0].summary & gpgme.SIGSUM_KEY_EXPIRED:
6043.2.8 by Jonathan Riddell
add test for unknown key
284
            expires = self.context.get_key(result[0].fpr).subkeys[0].expires
6043.2.3 by Jonathan Riddell
comment gpgme verify usage so it can be verified by someone who knows what they're talking about, also accept signatures from keys which were valid but have since expired
285
            if expires > result[0].timestamp:
6043.2.15 by Jonathan Riddell
turn comments into sentences
286
                # The expired key was not expired at time of signing.
287
                # test_verify_expired_but_valid()
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
288
                return SIGNATURE_EXPIRED, fingerprint[-8:]
6043.2.3 by Jonathan Riddell
comment gpgme verify usage so it can be verified by someone who knows what they're talking about, also accept signatures from keys which were valid but have since expired
289
            else:
6043.2.15 by Jonathan Riddell
turn comments into sentences
290
                # I can't work out how to create a test where the signature
291
                # was expired at the time of signing.
6043.2.3 by Jonathan Riddell
comment gpgme verify usage so it can be verified by someone who knows what they're talking about, also accept signatures from keys which were valid but have since expired
292
                return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
293
        # A signature from a revoked key gets this.
294
        # test_verify_revoked_signature()
6043.2.5 by Jonathan Riddell
catch a revoked key and add test for it
295
        if result[0].summary & gpgme.SIGSUM_SYS_ERROR:
296
            return SIGNATURE_NOT_VALID, None
6043.2.15 by Jonathan Riddell
turn comments into sentences
297
        # Other error types such as revoked keys should (I think) be caught by
298
        # SIGSUM_RED so anything else means something is buggy.
5971.1.42 by Jonathan Riddell
fix string formatting
299
        raise errors.SignatureVerificationFailed("Unknown GnuPG key "\
300
                                                 "verification result")
5971.1.11 by Jonathan Riddell
add set_acceptable_keys() so user can specify which gpg keys can be used for verification
301
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
302
    def set_acceptable_keys(self, command_line_input):
6351.3.2 by Jelmer Vernooij
Convert some gpg options to config stacks.
303
        """Set the acceptable keys for verifying with this GPGStrategy.
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
304
        
305
        :param command_line_input: comma separated list of patterns from
306
                                command line
307
        :return: nothing
308
        """
309
        key_patterns = None
6351.3.2 by Jelmer Vernooij
Convert some gpg options to config stacks.
310
        acceptable_keys_config = self._config_stack.get('acceptable_keys')
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
311
        try:
312
            if isinstance(acceptable_keys_config, unicode):
313
                acceptable_keys_config = str(acceptable_keys_config)
314
        except UnicodeEncodeError:
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
315
            #gpg Context.keylist(pattern) does not like unicode
6138.3.8 by Jonathan Riddell
more error gettext()ing
316
            raise errors.BzrCommandError(gettext('Only ASCII permitted in option names'))
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
317
318
        if acceptable_keys_config is not None:
319
            key_patterns = acceptable_keys_config
320
        if command_line_input is not None: #command line overrides config
321
            key_patterns = command_line_input
322
        if key_patterns is not None:
323
            patterns = key_patterns.split(",")
324
325
            self.acceptable_keys = []
326
            for pattern in patterns:
327
                result = self.context.keylist(pattern)
328
                found_key = False
329
                for key in result:
330
                    found_key = True
331
                    self.acceptable_keys.append(key.subkeys[0].fpr)
332
                    trace.mutter("Added acceptable key: " + key.subkeys[0].fpr)
333
                if not found_key:
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
334
                    trace.note(gettext(
6123.1.15 by Jelmer Vernooij
fix format string
335
                            "No GnuPG key results for pattern: {0}"
5971.1.69 by Jonathan Riddell
move some code from cmd_verify to gpg.set_acceptable_keys
336
                                ).format(pattern))
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
337
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
338
    def do_verifications(self, revisions, repository,
339
                            process_events_callback=None):
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
340
        """do verifications on a set of revisions
341
        
342
        :param revisions: list of revision ids to verify
343
        :param repository: repository object
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
344
        :param process_events_callback: method to call for GUI frontends that
345
                                                want to keep their UI refreshed
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
346
        
347
        :return: count dictionary of results of each type,
348
                 result list for each revision,
349
                 boolean True if all results are verified successfully
350
        """
351
        count = {SIGNATURE_VALID: 0,
352
                 SIGNATURE_KEY_MISSING: 0,
353
                 SIGNATURE_NOT_VALID: 0,
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
354
                 SIGNATURE_NOT_SIGNED: 0,
355
                 SIGNATURE_EXPIRED: 0}
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
356
        result = []
357
        all_verifiable = True
358
        for rev_id in revisions:
359
            verification_result, uid =\
6257.3.1 by Jelmer Vernooij
Support verifying remote signatures.
360
                repository.verify_revision_signature(rev_id, self)
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
361
            result.append([rev_id, verification_result, uid])
362
            count[verification_result] += 1
363
            if verification_result != SIGNATURE_VALID:
364
                all_verifiable = False
5971.1.83 by Jonathan Riddell
add a process_events_callback when verifying revisions so qbzr can keep the UI responsive
365
            if process_events_callback is not None:
366
                process_events_callback()
5971.1.70 by Jonathan Riddell
move code which does verifications of revisions from cmd_verify_signatures to gpg.do_verifications
367
        return (count, result, all_verifiable)
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
368
369
    def verbose_valid_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
370
        """takes a verify result and returns list of signed commits strings"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
371
        signers = {}
372
        for rev_id, validity, uid in result:
373
            if validity == SIGNATURE_VALID:
374
                signers.setdefault(uid, 0)
375
                signers[uid] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
376
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
377
        for uid, number in signers.items():
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
378
             result.append( ngettext(u"{0} signed {1} commit", 
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
379
                             u"{0} signed {1} commits",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
380
                             number).format(uid, number) )
381
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
382
383
384
    def verbose_not_valid_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
385
        """takes a verify result and returns list of not valid commit info"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
386
        signers = {}
387
        for rev_id, validity, empty in result:
388
            if validity == SIGNATURE_NOT_VALID:
389
                revision = repo.get_revision(rev_id)
390
                authors = ', '.join(revision.get_apparent_authors())
391
                signers.setdefault(authors, 0)
392
                signers[authors] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
393
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
394
        for authors, number in signers.items():
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
395
            result.append( ngettext(u"{0} commit by author {1}", 
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
396
                                 u"{0} commits by author {1}",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
397
                                 number).format(number, authors) )
398
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
399
400
    def verbose_not_signed_message(self, result, repo):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
401
        """takes a verify result and returns list of not signed commit info"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
402
        signers = {}
403
        for rev_id, validity, empty in result:
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
404
            if validity == SIGNATURE_NOT_SIGNED:
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
405
                revision = repo.get_revision(rev_id)
406
                authors = ', '.join(revision.get_apparent_authors())
407
                signers.setdefault(authors, 0)
408
                signers[authors] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
409
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
410
        for authors, number in signers.items():
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
411
            result.append( ngettext(u"{0} commit by author {1}", 
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
412
                                 u"{0} commits by author {1}",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
413
                                 number).format(number, authors) )
414
        return result
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
415
416
    def verbose_missing_key_message(self, result):
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
417
        """takes a verify result and returns list of missing key info"""
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
418
        signers = {}
419
        for rev_id, validity, fingerprint in result:
420
            if validity == SIGNATURE_KEY_MISSING:
421
                signers.setdefault(fingerprint, 0)
422
                signers[fingerprint] += 1
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
423
        result = []
5971.1.71 by Jonathan Riddell
move some message code into gpg.py
424
        for fingerprint, number in signers.items():
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
425
            result.append( ngettext(u"Unknown key {0} signed {1} commit", 
5971.1.81 by Jonathan Riddell
signature messages need to handle unicode names
426
                                 u"Unknown key {0} signed {1} commits",
5971.1.75 by Jonathan Riddell
fix verbose messages, now return a list
427
                                 number).format(fingerprint, number) )
428
        return result
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
429
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
430
    def verbose_expired_key_message(self, result, repo):
431
        """takes a verify result and returns list of expired key info"""
432
        signers = {}
433
        fingerprint_to_authors = {}
434
        for rev_id, validity, fingerprint in result:
435
            if validity == SIGNATURE_EXPIRED:
436
                revision = repo.get_revision(rev_id)
437
                authors = ', '.join(revision.get_apparent_authors())
438
                signers.setdefault(fingerprint, 0)
439
                signers[fingerprint] += 1
440
                fingerprint_to_authors[fingerprint] = authors
441
        result = []
442
        for fingerprint, number in signers.items():
6092.2.7 by Jonathan Riddell
i18n.gettext() -> gettext()
443
            result.append(ngettext(u"{0} commit by author {1} with "\
444
                                    "key {2} now expired", 
445
                                   u"{0} commits by author {1} with key {2} now "\
446
                                    "expired",
447
                                    number).format(number,
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
448
                            fingerprint_to_authors[fingerprint], fingerprint) )
449
        return result
450
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
451
    def valid_commits_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
452
        """returns message for number of commits"""
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
453
        return gettext(u"{0} commits with valid signatures").format(
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
454
                                        count[SIGNATURE_VALID])
455
456
    def unknown_key_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
457
        """returns message for number of commits"""
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
458
        return ngettext(u"{0} commit with unknown key",
459
                        u"{0} commits with unknown keys",
460
                        count[SIGNATURE_KEY_MISSING]).format(
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
461
                                        count[SIGNATURE_KEY_MISSING])
462
463
    def commit_not_valid_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
464
        """returns message for number of commits"""
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
465
        return ngettext(u"{0} commit not valid",
466
                        u"{0} commits not valid",
467
                        count[SIGNATURE_NOT_VALID]).format(
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
468
                                            count[SIGNATURE_NOT_VALID])
469
470
    def commit_not_signed_message(self, count):
5971.1.73 by Jonathan Riddell
document methods
471
        """returns message for number of commits"""
6092.2.1 by Jonathan Riddell
Use gettext.NullTranslations in i18n to allow use of i18n even when translations are not turned on
472
        return ngettext(u"{0} commit not signed",
473
                        u"{0} commits not signed",
474
                        count[SIGNATURE_NOT_SIGNED]).format(
5971.1.72 by Jonathan Riddell
move all messages into gpg.py so they can be reused by other UIs
475
                                        count[SIGNATURE_NOT_SIGNED])
6043.3.1 by Jonathan Riddell
Report commits signed with expired keys in "verify-signatures".
476
477
    def expired_commit_message(self, count):
478
        """returns message for number of commits"""
6092.2.7 by Jonathan Riddell
i18n.gettext() -> gettext()
479
        return ngettext(u"{0} commit with key now expired",
480
                        u"{0} commits with key now expired",
481
                        count[SIGNATURE_EXPIRED]).format(
482
                                    count[SIGNATURE_EXPIRED])