Changeset 11813


Ignore:
Timestamp:
Jul 27, 2012, 10:48:39 PM (11 years ago)
Author:
Steffen Hoffmann
Message:

CryptoPlugin: Implement some functionality now, starting with OpenPGP, refs #10030 and #10080.

Location:
cryptoplugin/trunk
Files:
5 added
8 edited

Legend:

Unmodified
Added
Removed
  • cryptoplugin/trunk/COPYING

    r11807 r11813  
    2727OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
    2828IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     29
     30----
     31
     32For OpenPGP operation this code relies on python-gnupg, a wrapper for the
     33'gpg' command. So it's disclaimer follows here for completeness:
     34
     35Portions of this module are derived from A.M. Kuchling's well-designed
     36GPG.py, using Richard Jones' updated version 1.3, which can be found
     37in the pycrypto CVS repository on Sourceforge:
     38
     39http://pycrypto.cvs.sourceforge.net/viewvc/pycrypto/gpg/GPG.py
     40
     41This module is *not* forward-compatible with amk's; some of the
     42old interface has changed.  For instance, since I've added decrypt
     43functionality, I elected to initialize with a 'gnupghome' argument
     44instead of 'keyring', so that gpg can find both the public and secret
     45keyrings.  I've also altered some of the returned objects in order for
     46the caller to not have to know as much about the internals of the
     47result classes.
     48
     49While the rest of ISconf is released under the GPL, I am releasing
     50this single file under the same terms that A.M. Kuchling used for
     51pycrypto.
     52
     53Steve Traugott, stevegt@terraluna.org
     54Thu Jun 23 21:27:20 PDT 2005
     55
     56This version of the module has been modified from Steve Traugott's version
     57(see http://trac.t7a.org/isconf/browser/trunk/lib/python/isconf/GPG.py) by
     58Vinay Sajip to make use of the subprocess module (Steve's version uses
     59os.fork() and so does not work on Windows). Renamed to gnupg.py to avoid
     60confusion with the previous versions.
     61
     62Modifications Copyright (C) 2008-2012 Vinay Sajip. All rights reserved.
     63
     64----
     65
     66Finally, the licensing terms of pycrypto are reproduced here for reference:
     67
     68===================================================================
     69Distribute and use freely; there are no restrictions on further
     70dissemination and usage except those imposed by the laws of your
     71country of residence.  This software is provided "as is" without
     72warranty of fitness for use or suitability for any purpose, express
     73or implied. Use at your own risk or not at all.
     74===================================================================
     75
     76Incorporating the code into commercial products is permitted; you do
     77not have to make source available or contribute your changes back
     78(though that would be nice).
     79
     80--amk                                                             (www.amk.ca)
  • cryptoplugin/trunk/crypto/admin.py

    r11811 r11813  
    77# you should have received as part of this distribution.
    88
     9import os
     10
     11from datetime import datetime
     12
    913from trac.admin import IAdminPanelProvider
     14from trac.config import Option
    1015from trac.core import implements
    1116from trac.perm import IPermissionRequestor
     17from trac.util.datefmt import to_timestamp, utc
     18from trac.web.chrome import add_notice, add_stylesheet, add_warning
    1219
    13 from crypto.api import _, dgettext
     20from crypto.api import CryptoBase, _, dgettext
     21from crypto.compat import exception_to_unicode
    1422from crypto.web_ui import CommonTemplateProvider
    1523
     
    2230    # IPermissionRequestor method
    2331    def get_permission_actions(self):
    24         actions = ['CRYPTO_ADMIN']
     32        action = ['CRYPTO_DELETE']
     33        actions = [('CRYPTO_ADMIN', action), action[0]]
    2534        return actions
    2635
     
    3241
    3342    def render_admin_panel(self, req, cat, page, path_info):
     43        if req.method == 'POST':
     44            defaults = self.config.defaults().get('crypto')
     45            ## Read admin form values.
     46            # GnuPG settings
     47            gpg_binary = req.args.get('gpg_binary')
     48            gpg_home = req.args.get('gpg_home')
     49            priv_key = req.args.get('priv_key')
     50            # Key generation presets
     51            expire_date = req.args.get('expire_date')
     52            key_length = req.args.get('key_length')
     53            key_type = req.args.get('key_type')
     54            subkey_length = req.args.get('subkey_length')
     55            subkey_type = req.args.get('subkey_type')
     56            # Checkbox return value requires special parsing.
     57            allow_usermod = bool(req.args.get('allow_usermod'))
     58
     59            # Overwrite deleted values with defaults.
     60            if not gpg_binary and defaults:
     61                gpg_binary = defaults['gpg_binary']
     62            self.config.set('crypto', 'gpg_binary', gpg_binary)
     63            if not gpg_home and defaults:
     64                gpg_home = defaults['gpg_home']
     65            self.config.set('crypto', 'gpg_home', gpg_home)
     66            if not priv_key and defaults:
     67                priv_key = defaults['private_key']
     68            self.config.set('crypto', 'gpg_private_key', priv_key)
     69
     70            self.config.set('crypto', 'gpg_keygen_allow_usermod',
     71                            allow_usermod)
     72            if not expire_date and defaults:
     73                expire_date = defaults['gpg_keygen_expire_date']
     74            self.config.set('crypto', 'gpg_keygen_expire_date', expire_date)
     75            if not key_length and defaults:
     76                key_length = defaults['gpg_keygen_key_length']
     77            self.config.set('crypto', 'gpg_keygen_key_length', key_length)
     78            if not key_type and defaults:
     79                key_type = defaults['gpg_keygen_key_type']
     80            self.config.set('crypto', 'gpg_keygen_key_type', key_type)
     81            if not subkey_length and defaults:
     82                subkey_length = defaults['gpg_keygen_subkey_length']
     83            self.config.set('crypto', 'gpg_keygen_subkey_length',
     84                            subkey_length)
     85            if not subkey_type and defaults:
     86                subkey_type = defaults['gpg_keygen_subkey_type']
     87            self.config.set('crypto', 'gpg_keygen_subkey_type', subkey_type)
     88
     89            # Save effective new configuration.
     90            _save_config(self.config, req, self.log)
     91            req.redirect(req.href.admin(cat, page))
     92
    3493        # Get current configuration.
    35         if req.method == 'POST':
    36             # Save configuration changes.
    37             test = 'replace_with_real_code'
    38         # Display current configuration.
    39         data = {'_dgettext': dgettext}
     94        gpg = {
     95            'binary': self.config.get('crypto', 'gpg_binary'),
     96            'home': self.config.get('crypto', 'gpg_home')
     97        }
     98        keygen = {
     99            'allow_usermod': self.config.getbool('crypto',
     100                                                 'gpg_keygen_allow_usermod'),
     101            'expire_date': self.config.get('crypto', 'gpg_keygen_expire_date'),
     102            'key_length': self.config.getint('crypto',
     103                                             'gpg_keygen_key_length'),
     104            'key_type': self.config.get('crypto', 'gpg_keygen_key_type'),
     105            'subkey_length': self.config.getint('crypto',
     106                                                'gpg_keygen_subkey_length'),
     107            'subkey_type': self.config.get('crypto', 'gpg_keygen_subkey_type')
     108        }
     109        now_ts = to_timestamp(datetime.now(utc))
     110        priv_key = self.config.get('crypto', 'gpg_private_key')
     111        priv_keys = [dict(id='', label=_("(Select private key)"))] + [
     112            dict(id=key['keyid'],
     113                 label=' - '.join([key['keyid'], key.get('uids')[1]]),
     114                 disabled=key.get('expires') and \
     115                          int(key.get('expires')) < now_ts,
     116                 selected=key['keyid'] == priv_key)
     117            for key in CryptoBase(self.env).keys(private=True)
     118        ]
     119           
     120        data = {
     121            '_dgettext': dgettext,
     122            'env_dir': os.path.abspath(self.env.path),
     123            'gpg': gpg,
     124            'keygen': keygen,
     125            'priv_keys': priv_keys
     126        }
     127        add_stylesheet(req, 'crypto/crypto.css')
    40128        return 'admin_crypto.html', data
     129
     130
     131def _save_config(config, req, log):
     132    """Try to save the config, and display either a success notice or a
     133    failure warning (code copied verbatim from Trac core).
     134    """
     135    try:
     136        config.save()
     137        add_notice(req, _("Your changes have been saved."))
     138    except Exception, e:
     139        log.error('Error writing to trac.ini: %s', exception_to_unicode(e))
     140        add_warning(req, _("""Error writing to trac.ini, make sure it is
     141                           writable by the web server. Your changes have not
     142                           been saved."""))
  • cryptoplugin/trunk/crypto/api.py

    r11811 r11813  
    99from pkg_resources import resource_filename
    1010
    11 from trac.core import Component
     11from trac.core import Component, ExtensionPoint, Interface, implements
    1212
    1313# Import standard i18n methods.
     
    4141
    4242
     43class IKeyVault(Interface):
     44    """Defines common key store operations."""
     45
     46    def keys(self, private=False, id_only=False):
     47        """Returns the list of all available keys."""
     48
     49    def has_key(self, key_id, private=False):
     50        """Returns whether a key with the given ID is available or not."""
     51
     52    def get_key(self, key_id, private=False):
     53        """Returns a distinct key as dictionary of key properties."""
     54
     55    def create_key(self, **kwargs):
     56        """Generate a new key with specified properties."""
     57
     58    def delete_key(self, key_id, perm):
     59        """Delete the key specified by ID."""
     60
     61
     62class ICipherModule(Interface):
     63    """Defines common key operations."""
     64
     65
     66class Credential(Component):
     67
     68    abstract = True
     69
     70
     71class GenericFactory(Component):
     72    """Provides common key store operations."""
     73
     74    abstract = True
     75    implements(IKeyVault)
     76    name = 'generic'
     77
     78    def keys(self, private=False, id_only=False):
     79        """Returns the list of all available keys."""
     80        return []
     81
     82    def has_key(self, key_id, private=False):
     83        """Returns whether a key with the given ID is available or not."""
     84        if key_id in self.keys(private=private, id_only=True):
     85            return True
     86        return False
     87
     88    def get_key(self, key_id, private=False):
     89        """Returns a distinct key as dictionary of key properties."""
     90        for key in self.keys(private=private):
     91            if key_id == key['keyid']:
     92                return key
     93        return {}
     94
     95    def create_key(self, **kwargs):
     96        """Generate a new key with specified properties."""
     97        return {}
     98
     99    def delete_key(self, key_id, perm):
     100        """Delete the key specified by ID."""
     101        return False
     102
     103
    43104class CryptoBase(Component):
    44105    """Cryptography foundation for Trac."""
     106
     107    _key_stores = ExtensionPoint(IKeyVault)
    45108
    46109    def __init__(self):
     
    48111        locale_dir = resource_filename(__name__, 'locale')
    49112        add_domain(self.env.path, locale_dir)
     113
     114    def keys(self, private=False, id_only=False):
     115        """Returns the list of all available keys."""
     116        for store in self._key_stores:
     117            for key in store.keys(private=private, id_only=id_only):
     118                yield key
     119
     120    def has_key(self, key_id, private=False):
     121        """Returns whether a key with the given ID is available or not."""
     122        if key_id in self.keys(private=private, id_only=True):
     123            return True
     124        return False
  • cryptoplugin/trunk/crypto/templates/admin_crypto.html

    r11811 r11813  
    1717  <body>
    1818    <h2>Cryptography: Configuration</h2>
     19    <form class="mod" id="modcrypto" method="post" action="">
     20      <fieldset>
     21        <legend>OpenPGP: GnuPG</legend>
     22        <div class="field">
     23          <label>Binary:<br />
     24            <input type="text" id="gpg_binary" name="gpg_binary" size="48"
     25                   value="$gpg.binary"/>
     26          </label>
     27          <p class="help">
     28            GnuPG executable name, optionally including full path. For usual
     29            installations that location is auto-detected by retaining the
     30            default value <code>gpg</code>.
     31          </p>
     32        </div>
     33        <div class="field">
     34          <label>Directory:<br />
     35            <input type="text" id="gpg_home" name="gpg_home" size="48"
     36                   value="$gpg.home"/>
     37          </label>
     38          <p class="help" i18n:msg="dir">
     39            This directory contains keyrings as well as other support files.
     40            You could direct to an existing GnuPG home directory, or to a
     41            new path so that it will be created upon next environment reload
     42            and will be populated with new files including empty keyrings.
     43            If you specify a relative path, that directory will reside inside
     44            the project environment (<code>$env_dir</code>).
     45          </p>
     46        </div>
     47        <div class="field">
     48          <label>Server Key:<br />
     49            <select id="priv_key" name="priv_key">
     50              <option py:for="key in priv_keys" value="${key.id}"
     51                      selected="${key.selected or None}"
     52                      disabled="${key.disabled or None}">${key.label}</option>
     53            </select>
     54          </label>
     55          <p class="help">
     56            The selected key is used for signing auto-generated content. Such
     57            unattended operation currently requires a blank private key
     58            password. Directory and keyring file access must be narrowed down
     59            carefully to limit exposure of this sensitive information.
     60          </p>
     61        </div>
     62
     63      <fieldset>
     64        <legend>OpenPGP Key Generation Presets</legend>
     65        <table>
     66          <tr>
     67            <th class="col1">
     68              <label for="key_type">Key Type:</label>
     69            </th>
     70            <td class="col1">
     71              <input type="text" id="key_type" name="key_type"
     72                     value="$keygen.key_type"/>
     73            </td>
     74            <th class="col2">
     75              <label for="key_length">Key Length:</label>
     76            </th>
     77            <td class="col2">
     78              <input type="text" id="key_length" name="key_length"
     79                     value="$keygen.key_length"/>
     80            </td>
     81          </tr>
     82          <tr>
     83            <th class="col1">
     84              <label for="subkey_type">Subkey Type:</label>
     85            </th>
     86            <td class="col1">
     87              <input type="text" id="subkey_type" name="subkey_type"
     88                     value="$keygen.subkey_type"/>
     89            </td>
     90            <th class="col2">
     91              <label for="subkey_length">Subkey Length:</label>
     92            </th>
     93            <td class="col2">
     94              <input type="text" id="subkey_length" name="subkey_length"
     95                     value="$keygen.subkey_length"/>
     96            </td>
     97          </tr>
     98          <tr>
     99            <th class="col1">
     100              <label for="expire_date">Expiration Date:</label>
     101            </th>
     102            <td class="col1">
     103              <input type="text" id="expire_date" name="expire_date"
     104                     value="$keygen.expire_date"/>
     105            </td>
     106            <th class="col2">
     107              <input type="checkbox" id="allow_usermod" name="allow_usermod"
     108                     checked="${keygen.allow_usermod and 'checked' or None}"/>
     109            </th>
     110            <td class="col2">
     111              <label for="allow_usermod">
     112                Allow users to overwrite key generation presets.
     113              </label>
     114            </td>
     115          </tr>
     116        </table>
     117      </fieldset>
     118        <div class="buttons">
     119          <input type="submit" value="${_('Apply changes')}"/>
     120        </div>
     121      </fieldset>
     122    </form>
    19123  </body>
    20124</html>
  • cryptoplugin/trunk/crypto/tests/admin.py

    r11810 r11813  
    3232    def test_available_actions(self):
    3333        self.failIf('CRYPTO_ADMIN' not in self.perm.get_actions())
     34        self.failIf('CRYPTO_DELETE' not in self.perm.get_actions())
    3435
    3536    def test_available_actions_no_perms(self):
    3637        self.perm.grant_permission('admin', 'authenticated')
    3738        self.assertFalse(self.perm.check_permission('CRYPTO_ADMIN', 'admin'))
     39        self.assertFalse(self.perm.check_permission('CRYPTO_DELETE', 'admin'))
     40
     41    def test_available_actions_delete_only(self):
     42        self.perm.grant_permission('admin', 'CRYPTO_DELETE')
     43        self.assertFalse(self.perm.check_permission('CRYPTO_ADMIN', 'admin'))
     44        self.assertTrue(self.perm.check_permission('CRYPTO_DELETE', 'admin'))
    3845
    3946    def test_available_actions_full_perms(self):
    4047        self.perm.grant_permission('admin', 'TRAC_ADMIN')
    4148        self.assertTrue(self.perm.check_permission('CRYPTO_ADMIN', 'admin'))
     49        self.assertTrue(self.perm.check_permission('CRYPTO_DELETE', 'admin'))
    4250
    4351
  • cryptoplugin/trunk/crypto/tests/web_ui.py

    r11812 r11813  
    2323        self.env.path = tempfile.mkdtemp()
    2424
    25         # CommonTemplateProvider is rather abstract, test it using a subclass.
     25        # CommonTemplateProvider is abstract, test it using a subclass.
    2626        self.crypto_up = UserCryptoPreferences(self.env)
    2727
  • cryptoplugin/trunk/crypto/web_ui.py

    r11812 r11813  
    2121    implements(ITemplateProvider)
    2222
     23    abstract = True
     24
    2325    # ITemplateProvider methods
    2426
    2527    def get_htdocs_dirs(self):
    26         return []
     28        return [('crypto', resource_filename(__name__, 'htdocs'))]
    2729
    2830    def get_templates_dirs(self):
    29         return [resource_filename('crypto', 'templates')]
     31        return [resource_filename(__name__, 'templates')]
    3032
    3133class UserCryptoPreferences(CommonTemplateProvider):
  • cryptoplugin/trunk/setup.py

    r11811 r11813  
    5353            'crypto.api = crypto.api',
    5454            'crypto.admin = crypto.admin',
     55            'crypto.openpgp = crypto.openpgp',
    5556            'crypto.web_ui = crypto.web_ui'
    5657        ]
Note: See TracChangeset for help on using the changeset viewer.