pax_global_header 0000666 0000000 0000000 00000000064 12416044674 0014522 g ustar 00root root 0000000 0000000 52 comment=aaa45a30797e484ef36ae39e950b03eb0df4d59e django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/ 0000775 0000000 0000000 00000000000 12416044674 0023656 5 ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/.gitignore 0000664 0000000 0000000 00000000067 12416044674 0025651 0 ustar 00root root 0000000 0000000 *.py[coa] /django_sshkey.egg-info /testproject/test.db django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/LICENSE 0000664 0000000 0000000 00000002726 12416044674 0024672 0 ustar 00root root 0000000 0000000 Copyright (c) 2014, Clemson University All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. * Neither the name of the {organization} nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/MANIFEST.in 0000664 0000000 0000000 00000000565 12416044674 0025422 0 ustar 00root root 0000000 0000000 include LICENSE include README.rst include README.upgrading.rst include RELEASE-NOTES.rst include lookup.py include lookup.sh include django-sshkey-lookup include django-sshkey-lookup-all include django-sshkey-lookup-by-username include django-sshkey-lookup-by-fingerprint recursive-include django_sshkey/migrations *.py recursive-include django_sshkey/templates.example * django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/README 0000777 0000000 0000000 00000000000 12416044674 0026220 2README.rst ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/README.rst 0000664 0000000 0000000 00000021253 12416044674 0025350 0 ustar 00root root 0000000 0000000 ============= django-sshkey ============= django-sshkey allows you to associate multiple SSH public keys with Django user accounts. It provides views to list, add, edit, and delete keys, each of which is intended for end-user consumption. It also provides a lookup view and corresponding lookup commands that are suitable for use with the ``AuthorizedKeysCommand`` feature in OpenSSH_ 6.2 and above. The Django app ============== To use django-sshkey in your Django project, simply add ``django_sshkey`` to ``INSTALLED_APPS`` in ``settings.py``, map the URLs into your project, and provide templates for the views (example templates are provided in the source). In order to associate an incoming public key with a user you must define ``SSHKEY_AUTHORIZED_KEYS_OPTIONS`` in your project's ``settings.py``. This should be a string containing options accepted by sshd, with ``{username}`` being replaced with the username of the user associated with the incoming public key. django-sshkey can also help you keep track of when a key was last used. ``SSHKEY_AUTHORIZED_KEYS_OPTIONS`` also replaces ``{key_id}`` with the key's id. The command that is run can then notify django-sshkey that the key was used by issuing a HTTP POST to the lookup URL, placing the key_id in the request body. For instance:: SSHKEY_AUTHORIZED_KEYS_OPTIONS = 'command="my-command {username} {key_id}",no-pty' in settings.py will cause keys produced by the below commands to look similar to:: command="my-command fred 15",no-pty ssh-rsa AAAAB3NzaC1yc2E... sshd would then verify the key is correct and run ``my-command``. ``my-command`` would then know that this is fred and that he is using key 15, and could tell django-sshkey to update the last_used field of that key by running the equivalent of this command:: curl -d 15 http://localhost:8000/sshkey/lookup Your URL may vary depending upon your configuration. URL Configuration ----------------- This text assumes that your project's ``urls.py`` maps ``django_sshkey.urls`` into the URL namespace as follows:: import django_sshkey.urls urlpatterns = patterns('', ... url('^sshkey/', include(django_sshkey.urls)), ... ) You will need to adjust your URLs in the examples below if you use a different mapping. .. WARNING:: The ``/sshkey/lookup`` URL can expose all public keys that have been uploaded to your site. Although they are public keys, it is probably a good idea to limit what systems can access this URL via your web server's configuration. Most of the lookup methods below require access to this URL, and only the systems that need to run the lookup commands should have access to it. Settings -------- ``SSHKEY_AUTHORIZED_KEYS_OPTIONS`` String, optional. Defines the SSH options that will be prepended to each public key. ``{username}`` will be replaced by the username; ``{key_id}`` will be replaced by the key's id. New in version 2.3. ``SSHKEY_ALLOW_EDIT`` Boolean, defaults to ``False``. Whether or not editing keys is allowed. Note that no email will be sent in any case when a key is edited, hence the reason that editing keys is disabled by default. New in version 2.3. ``SSHKEY_EMAIL_ADD_KEY`` Boolean, defaults to ``True``. Whether or not an email should be sent to the user when a new key is added to their account. New in version 2.3. ``SSHKEY_EMAIL_ADD_KEY_SUBJECT`` String, defaults to ``"A new key was added to your account"``. The subject of the email that gets sent out when a new key is added. New in version 2.3. ``SSHKEY_FROM_EMAIL`` String, defaults to ``DEFAULT_FROM_EMAIL``. New in version 2.3. ``SSHKEY_SEND_HTML_EMAIL`` Boolean, defaults to ``False``. Whether or not multipart HTML emails should be sent. New in version 2.3. Templates --------- Example templates are available in the ``templates.example`` directory. ``sshkey/userkey_list.html`` Used when listing a user's keys. ``sshkey/userkey_detail.html`` Used when adding or editing a user's keys. ``sshkey/add_key.txt`` The plain text body of the email sent when a new key is added. New in version 2.3. ``sshkey/add_key.html`` The HTML body of the email sent when a new key is added. New in version 2.3. Tying OpenSSH to django-sshkey ============================== There are multiple methods of connecting OpenSSH to django-sshkey. All of the methods listed here require the use of the ``AuthorizedKeysCommand`` directive in ``sshd_config`` present in OpenSSH 6.2 and above. Please note that the command that is referenced by this directive and its ancestor directories must be owned by root and writable only by owner. Unless otherwise stated, all of the methods below use the ``SSHKEY_LOOKUP_URL`` environment variable to determine the URL of the ``/sshkey/lookup`` URL. If this environment variable is not defined then it will default to ``http://localhost:8000/sshkey/lookup``. If this environment variable is defined in the sshd process then it will be inherited by the ``AuthorizedKeysCommand``. Additionally, all of the methods below use either ``curl`` (preferred) or ``wget``. Some commands also use ``ssh-keygen``. These commands must be present in ``PATH``. If you would prefer not to use these external commands then there are variants of the lookup commands implemented purely in Python. However, they are *much* slower. To use the variants, replace ``lookup`` with ``pylookup``. For example, use ``django-sshkey-pylookup-all`` instead of ``django-sshkey-lookup-all``. Using ``django-sshkey-lookup`` ------------------------------ :: Usage: django-sshkey-lookup -a URL django-sshkey-lookup -u URL USERNAME django-sshkey-lookup -f URL FINGERPRINT django-sshkey-lookup URL [USERNAME] This program has different modes of operation: ``-a`` Print all public keys. ``-u`` Print all public keys owned by the specified user. ``-f`` Print all public keys matching the specified fingerprint. Default Compatibility mode. If the username parameter is given then print all public keys owned by the specified user; otherwise perform the same functionality as ``django-sshkey-lookup-by-fingerprint`` (see below). All modes expect that the lookup URL be specified as the first non-option parameter. This command is compatible with the old script ``lookup.sh`` but was renamed to have a less ambiguous name when installed system-wide. A symlink is left in its place for backwards compatibility. Using ``django-sshkey-lookup-all`` ---------------------------------- ``Usage: django-sshkey-lookup-all`` This program prints all SSH public keys that are defined on your site. sshd will have to scan through all of them to find the first match, so with many keys this method will be slow. However, it does not require a patched OpenSSH server. This program: * can be used directly with ``AuthorizedKeysCommand`` (the username parameter is ignored). * does not require a patched OpenSSH server. * does not scale well to a large number of user keys. Using ``django-sshkey-lookup-by-username`` ------------------------------------------ ``Usage: django-sshkey-lookup-by-username USERNAME`` This program prints all SSH public keys that are associated with the specified user. This program: * can be used directly with ``AuthorizedKeysCommand``. * does not require a patched OpenSSH server. * is ideal if each Django user corresponds to a system user account. Using ``django-sshkey-lookup-by-fingerprint`` --------------------------------------------- ``Usage: django-sshkey-lookup-by-fingerprint`` This program prints all SSH public keys that match the given fingerprint. The fingerprint is determined by the first of the following that is found: 1. The ``SSH_KEY_FINGERPRINT`` environment variable, which should contain the MD5 fingerprint of the key (this is the second field generated by ``ssh-keygen -l``). 2. The ``SSH_KEY`` environment variable, which should contain the key in standard openssh format (the same format as ``~/.ssh/id_rsa.pub``), is sent to ``ssh-keygen -l`` to determine the fingerprint. 3. The key in standard openssh format is read from standard input and is sent to ``ssh-keygen -l`` to determine the fingerprint. This program: * can be used directly with ``AuthorizedKeysCommand`` (the username parameter is ignored). * requires a patched OpenSSH server; compatible patches can be found at one of the following locations: - openssh-akcenv_ (this is the preferred patch) - openssh-stdinkey_ * is ideal if you want all Django users to access SSH via a shared system user account and be identified by their SSH public key. .. _OpenSSH: http://www.openssh.com/ .. _openssh-akcenv: https://github.com/ScottDuckworth/openssh-akcenv .. _openssh-stdinkey: https://github.com/ScottDuckworth/openssh-stdinkey django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/README.upgrading.rst 0000664 0000000 0000000 00000005126 12416044674 0027330 0 ustar 00root root 0000000 0000000 Upgrading and Downgrading ========================= django-sshkey is equipped with South_ migrations. This makes changes to the database schema in upgrades or downgrades a simple process. Migrations will only be present on minor version changes. To use South migrations, you must have the south app in your project's ``INSTALLED_APPS``. The following table maps django-sshkey version to migration labels: +---------+---------------+-------+------------------------------------------+ | Version | App Name | Label | Notes | +=========+===============+=======+==========================================+ | 1.0 | sshkey | 0001 | Migrations were not present in 1.0.x | +---------+---------------+-------+------------------------------------------+ | 1.1 | sshkey | 0002 | | +---------+---------------+-------+------------------------------------------+ | 2.0-2.2 | django_sshkey | 0001 | See Upgrading from 1.1.x to 2.x below | +---------+---------------+-------+------------------------------------------+ | 2.3 | django_sshkey | 0002 | | +---------+---------------+-------+------------------------------------------+ To upgrade, install the new version of django-sshkey and then migrate your project to its corresponding label from the table above using the following command:: python manage.py migrate APP_NAME LABEL To downgrade, perform the migration down to the label of the desired version before installing the older django-sshkey. Upgrading from 1.1.x to 2.x --------------------------- django-sshkey 2.x renames the sshkey app to django_sshkey. However, the database table names are not changed. To upgrade, all references to the sshkey module must be changed to django_sshkey. This includes all instances of ``import sshkey`` or ``from sshkey import ...`` and all references to sshkey in URL patterns, views, or templates, as well as updating ``INSTALLED_APPS`` in ``settings.py``. Once you have made those changes you will need to fake the initial migration for django_sshkey:: python manage.py migrate --fake django_sshkey 0001_initial This completes the upgrade process. The only thing that remains is the two existing migration records in the ``south_migrationhistory`` table from the now nonexistent sshkey app. These records do not cause any problems, but they can be removed at your discrection using the following SQL statement on your database:: DELETE FROM south_migrationhistory WHERE app_name="sshkey"; .. _South: http://south.aeracode.org/ django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/RELEASE-NOTES.rst 0000664 0000000 0000000 00000004216 12416044674 0026361 0 ustar 00root root 0000000 0000000 =============================== Release Notes for django-sshkey =============================== 2.3.2 (2014-09-15) ------------------ * Bug fix #1: entering whitespace for key field results in IndexError * Make example templates work with Django 1.4+ 2.3.1 (2014-07-29) ------------------ * Add support for Django 1.6 * Add missing dependency for pyasn1 (introduced in 2.3.0) * Add release notes (this file) 2.3.0 (2014-07-07) ------------------ * Schema change (label 0002): add last_used timestamp * Provide {key_id} in template for SSHKEY_AUTHORIZED_KEYS_OPTIONS so that last_used timestamp may be updated * Add support for RFC4716 and PEM public keys for import and export * django-sshkey-lookup can now use any method to lookup keys: all, by username, by fingerprint, or compatibility mode * Add ability to send email to user when a key is added to their account * Add the following settings * SSHKEY_ALLOW_EDIT * SSHKEY_EMAIL_ADD_KEY * SSHKEY_EMAIL_ADD_KEY_SUBJECT * SSHKEY_FROM_EMAIL * SSHKEY_SEND_HTML_EMAIL * Remove setting SSHKEY_AUTHORIZED_KEYS_COMMAND (deprecated since 1.0.0) * Fix up example templates 2.2.0 (2014-03-26) ------------------ * Change license to BSD 3-clause * Basic compatability with Django > 1.3 * OpenSSH patch removed, refer to their separate projects * Remove deprecated sshkey_authorized_keys_command management command * Add the following lookup commands * django-sshkey-lookup-all * django-sshkey-lookup-by-fingerprint * django-sshkey-lookup-by-username 2.1.0 (2014-01-22) ------------------ * lookup.sh and lookup.py deprecated in favor of django-sshkey-lookup and django-sshkey-pylookup, respectively * Install scripts using setuptools 2.0.1 (2013-09-30) ------------------ * Add missing __init__.py 2.0.0 (2013-09-30) ------------------ * Rename sshkey to django_sshkey 1.1.1 (2013-09-03) ------------------ * Include management and migrations directories in setuptools 1.1.0 (2013-08-28) ------------------ * Schema change (label 0001): add created and last_modified timestamps 1.0.1 (2013-08-28) ------------------ * Add copyright info 1.0.0 (2013-08-28) ------------------ First release django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django-sshkey-lookup 0000775 0000000 0000000 00000005051 12416044674 0027662 0 ustar 00root root 0000000 0000000 #!/bin/sh # Copyright (c) 2014, Clemson University # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the {organization} nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. usage() { echo "Usage: $0 -a URL" echo " $0 -u URL USERNAME" echo " $0 -f URL FINGERPRINT" echo " $0 URL [USERNAME]" } mode=x while getopts ':hafu' opt; do case $opt in h) usage exit 0 ;; a) mode=a ;; f) mode=f ;; u) mode=u ;; [?]) exec 1>&2 echo "Invalid option: -$OPTARG" usage exit 1 ;; esac done shift $(($OPTIND-1)) if [ $# -eq 0 ]; then usage >&2 exit 1 fi url="$1" case $mode in a) query= ;; f) if [ $# -lt 2 ]; then usage >&2 exit 1 fi query="fingerprint=$2" ;; u) if [ $# -lt 2 ]; then usage >&2 exit 1 fi query="username=$2" ;; x) if [ $# -eq 1 ]; then SSHKEY_LOOKUP_URL="${url}" export SSHKEY_LOOKUP_URL exec `dirname $0`/django-sshkey-lookup-by-fingerprint else query="username=$2" fi ;; esac if type curl >/dev/null 2>&1; then exec curl -s -G "${url}" --data-urlencode "${query}" else exec wget -q -O - "${url}?${query}" fi django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django-sshkey-lookup-all 0000775 0000000 0000000 00000003256 12416044674 0030435 0 ustar 00root root 0000000 0000000 #!/bin/sh # Copyright (c) 2014, Clemson University # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the {organization} nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. url="${SSHKEY_LOOKUP_URL:-http://localhost:8000/sshkey/lookup}" if type curl >/dev/null 2>&1; then exec curl -s "$url" else exec wget -q -O - "$url" fi django-sshkey-lookup-by-fingerprint 0000775 0000000 0000000 00000004141 12416044674 0032537 0 ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e #!/bin/bash # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the {organization} nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. url="${SSHKEY_LOOKUP_URL:-http://localhost:8000/sshkey/lookup}" if [ "x$SSH_KEY_FINGERPRINT" != "x" ]; then fingerprint="$SSH_KEY_FINGERPRINT" else if [ "x$SSH_KEY" == "x" ] && ! read SSH_KEY; then echo "Error: cannot retrieve fingerprint from environment or stdin" >&2 exit 1 fi info="$(ssh-keygen -lf /dev/stdin <<< "$SSH_KEY")" if [ $? -ne 0 ]; then echo "Error: $info" >&2 exit 1 fi info=($info) fingerprint="${info[1]}" fi if type curl >/dev/null 2>&1; then exec curl -s -G "$url" --data-urlencode "fingerprint=${fingerprint}" else exec wget -q -O - "${url}?fingerprint=${fingerprint}" fi django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django-sshkey-lookup-by-username 0000775 0000000 0000000 00000003336 12416044674 0032113 0 ustar 00root root 0000000 0000000 #!/bin/sh # Copyright (c) 2014, Clemson University # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the {organization} nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. url="${SSHKEY_LOOKUP_URL:-http://localhost:8000/sshkey/lookup}" if type curl >/dev/null 2>&1; then exec curl -s -G "$url" --data-urlencode "username=$1" else exec wget -q -O - "${url}?username=$1" fi django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/ 0000775 0000000 0000000 00000000000 12416044674 0026506 5 ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/__init__.py 0000664 0000000 0000000 00000003036 12416044674 0030621 0 ustar 00root root 0000000 0000000 # Copyright (c) 2014, Clemson University # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the {organization} nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. __version__ = '2.3.2' django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/admin.py 0000664 0000000 0000000 00000005410 12416044674 0030150 0 ustar 00root root 0000000 0000000 # Copyright (c) 2014, Clemson University # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the {organization} nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from django.contrib import admin from django.core.urlresolvers import reverse from django_sshkey.models import UserKey, Key class KeyAdmin(admin.ModelAdmin): list_display = [ '__unicode__', 'created', 'last_modified', 'last_used', ] readonly_fields = [ 'fingerprint', 'created', 'last_modified', 'last_used', ] search_fields = [ 'fingerprint', ] class ApplicationKeyAdmin(KeyAdmin): list_display = [ '__unicode__', 'basekey', 'created', 'last_modified', 'last_used', ] search_fields = [] # would be quite slow to search on fingerprint readonly_fields = [ 'created', 'last_modified', 'last_used', 'basekey_link', ] def basekey_link(self, obj): url = reverse('admin:django_sshkey_key_change', args=(obj.basekey.id,)) return '%s' % (url, obj.basekey) basekey_link.allow_tags = True class NamedKeyAdmin(ApplicationKeyAdmin): search_fields = [ 'name', ] class UserKeyAdmin(NamedKeyAdmin): list_display = [ '__unicode__', 'user', 'basekey', 'created', 'last_modified', 'last_used', ] search_fields = [ 'name', 'user__username', ] admin.site.register(Key, KeyAdmin) admin.site.register(UserKey, UserKeyAdmin) django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/defaults.py 0000664 0000000 0000000 00000003635 12416044674 0030676 0 ustar 00root root 0000000 0000000 # Copyright (c) 2014, Clemson University # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the {organization} nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from django_sshkey.models import UserKey from django_sshkey import settings def userkey_formatter(key): userkey = UserKey.objects.select_related('user').get(basekey=key) if settings.SSHKEY_AUTHORIZED_KEYS_OPTIONS: options = settings.SSHKEY_AUTHORIZED_KEYS_OPTIONS.format( username=userkey.user.username, key_id=key.id, ) + ' ' else: options = '' return options + key.key django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/forms.py 0000664 0000000 0000000 00000005755 12416044674 0030222 0 ustar 00root root 0000000 0000000 # Copyright (c) 2014, Clemson University # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the {organization} nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from django import forms from django.core.exceptions import ValidationError from django_sshkey.models import Key, UserKey from django_sshkey.util import pubkey_parse class ApplicationKeyForm(forms.ModelForm): key = forms.CharField( max_length=2000, required=True, widget=forms.Textarea(attrs={ 'cols': 72, 'rows': 15, 'placeholder': "Paste in the contents of your public key file here", }) ) def clean_key(self): opts = self._meta key = self.cleaned_data['key'] key = opts.model.base(key=key) key.full_clean() return key def save(self, commit=True): instance = super(ApplicationKeyForm, self).save(commit=False) if commit: basekey = self.cleaned_data['key'] basekey.save() instance.basekey = basekey instance.save() return instance class NamedKeyForm(ApplicationKeyForm): def clean(self): cleaned_data = super(NamedKeyForm, self).clean() if 'key' in cleaned_data and not cleaned_data.get('name'): pubkey = pubkey_parse(cleaned_data['key'].key) if not pubkey.comment: raise ValidationError('Name or key comment required') cleaned_data['name'] = pubkey.comment return cleaned_data class UserKeyForm(NamedKeyForm): class Meta: model = UserKey fields = ['name', 'key'] exclude = ['basekey'] widgets = { 'name': forms.TextInput(attrs={ 'size': 50, 'placeholder': "username@hostname, or leave blank to use key comment", }) } django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/migrations/ 0000775 0000000 0000000 00000000000 12416044674 0030662 5 ustar 00root root 0000000 0000000 0001_initial.py 0000664 0000000 0000000 00000012640 12416044674 0033251 0 ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/migrations # encoding: utf-8 import datetime from south.db import db from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): def forwards(self, orm): # Adding model 'UserKey' db.create_table('sshkey_userkey', ( ('id', self.gf('django.db.models.fields.AutoField')(primary_key=True)), ('user', self.gf('django.db.models.fields.related.ForeignKey')(to=orm['auth.User'])), ('name', self.gf('django.db.models.fields.CharField')(max_length=50, blank=True)), ('key', self.gf('django.db.models.fields.TextField')(max_length=2000)), ('fingerprint', self.gf('django.db.models.fields.CharField')(db_index=True, max_length=47, blank=True)), ('created', self.gf('django.db.models.fields.DateTimeField')(auto_now_add=True, null=True, blank=True)), ('last_modified', self.gf('django.db.models.fields.DateTimeField')(auto_now=True, null=True, blank=True)), )) db.send_create_signal('django_sshkey', ['UserKey']) # Adding unique constraint on 'UserKey', fields ['user', 'name'] db.create_unique('sshkey_userkey', ['user_id', 'name']) def backwards(self, orm): # Removing unique constraint on 'UserKey', fields ['user', 'name'] db.delete_unique('sshkey_userkey', ['user_id', 'name']) # Deleting model 'UserKey' db.delete_table('sshkey_userkey') models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) }, 'auth.permission': { 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) }, 'auth.user': { 'Meta': {'object_name': 'User'}, 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, 'contenttypes.contenttype': { 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, 'django_sshkey.userkey': { 'Meta': {'unique_together': "[('user', 'name')]", 'object_name': 'UserKey', 'db_table': "'sshkey_userkey'"}, 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), 'fingerprint': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '47', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'key': ('django.db.models.fields.TextField', [], {'max_length': '2000'}), 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) } } complete_apps = ['django_sshkey'] 0002_auto__add_field_userkey_last_used.py 0000664 0000000 0000000 00000011117 12416044674 0040513 0 ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/migrations # encoding: utf-8 import datetime from south.db import db from south.v2 import SchemaMigration from django.db import models class Migration(SchemaMigration): def forwards(self, orm): # Adding field 'UserKey.last_used' db.add_column('sshkey_userkey', 'last_used', self.gf('django.db.models.fields.DateTimeField')(null=True), keep_default=False) def backwards(self, orm): # Deleting field 'UserKey.last_used' db.delete_column('sshkey_userkey', 'last_used') models = { 'auth.group': { 'Meta': {'object_name': 'Group'}, 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '80'}), 'permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}) }, 'auth.permission': { 'Meta': {'ordering': "('content_type__app_label', 'content_type__model', 'codename')", 'unique_together': "(('content_type', 'codename'),)", 'object_name': 'Permission'}, 'codename': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'content_type': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['contenttypes.ContentType']"}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50'}) }, 'auth.user': { 'Meta': {'object_name': 'User'}, 'date_joined': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'email': ('django.db.models.fields.EmailField', [], {'max_length': '75', 'blank': 'True'}), 'first_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'groups': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Group']", 'symmetrical': 'False', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'is_active': ('django.db.models.fields.BooleanField', [], {'default': 'True'}), 'is_staff': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'is_superuser': ('django.db.models.fields.BooleanField', [], {'default': 'False'}), 'last_login': ('django.db.models.fields.DateTimeField', [], {'default': 'datetime.datetime.now'}), 'last_name': ('django.db.models.fields.CharField', [], {'max_length': '30', 'blank': 'True'}), 'password': ('django.db.models.fields.CharField', [], {'max_length': '128'}), 'user_permissions': ('django.db.models.fields.related.ManyToManyField', [], {'to': "orm['auth.Permission']", 'symmetrical': 'False', 'blank': 'True'}), 'username': ('django.db.models.fields.CharField', [], {'unique': 'True', 'max_length': '30'}) }, 'contenttypes.contenttype': { 'Meta': {'ordering': "('name',)", 'unique_together': "(('app_label', 'model'),)", 'object_name': 'ContentType', 'db_table': "'django_content_type'"}, 'app_label': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'model': ('django.db.models.fields.CharField', [], {'max_length': '100'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '100'}) }, 'django_sshkey.userkey': { 'Meta': {'unique_together': "[('user', 'name')]", 'object_name': 'UserKey', 'db_table': "'sshkey_userkey'"}, 'created': ('django.db.models.fields.DateTimeField', [], {'auto_now_add': 'True', 'null': 'True', 'blank': 'True'}), 'fingerprint': ('django.db.models.fields.CharField', [], {'db_index': 'True', 'max_length': '47', 'blank': 'True'}), 'id': ('django.db.models.fields.AutoField', [], {'primary_key': 'True'}), 'key': ('django.db.models.fields.TextField', [], {'max_length': '2000'}), 'last_modified': ('django.db.models.fields.DateTimeField', [], {'auto_now': 'True', 'null': 'True', 'blank': 'True'}), 'last_used': ('django.db.models.fields.DateTimeField', [], {'null': 'True'}), 'name': ('django.db.models.fields.CharField', [], {'max_length': '50', 'blank': 'True'}), 'user': ('django.db.models.fields.related.ForeignKey', [], {'to': "orm['auth.User']"}) } } complete_apps = ['django_sshkey'] __init__.py 0000664 0000000 0000000 00000000000 12416044674 0032702 0 ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/migrations django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/models.py 0000664 0000000 0000000 00000015666 12416044674 0030361 0 ustar 00root root 0000000 0000000 # Copyright (c) 2014, Clemson University # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the {organization} nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from django.db import models from django.contrib.auth.models import User from django.contrib.contenttypes.models import ContentType from django.core.exceptions import ValidationError from django.db.models.signals import pre_save, post_delete from django.dispatch import receiver try: from django.utils.timezone import now except ImportError: import datetime now = datetime.datetime.now from django_sshkey.util import PublicKeyParseError, pubkey_parse from django_sshkey import settings class Key(models.Model): key = models.TextField(max_length=2000) fingerprint = models.CharField(max_length=47, blank=True, db_index=True) created = models.DateTimeField(auto_now_add=True, null=True) content_type = models.ForeignKey(ContentType) last_modified = models.DateTimeField(null=True) last_used = models.DateTimeField(null=True) class Meta: db_table = 'sshkey_key' def __unicode__(self): if self.fingerprint: return self.fingerprint else: return self.key[:20] + u'...' def clean_fields(self, exclude=None): if not exclude or 'key' not in exclude: self.key = self.key.strip() if not self.key: raise ValidationError({'key': ["This field is required."]}) def clean(self): self.key = self.key.strip() if not self.key: return try: pubkey = pubkey_parse(self.key) except PublicKeyParseError as e: raise ValidationError(str(e)) self.key = pubkey.format_openssh() self.fingerprint = pubkey.fingerprint() def validate_unique(self, exclude=None): if self.pk is None: objects = type(self).objects else: objects = type(self).objects.exclude(pk=self.pk) if exclude is None or 'key' not in exclude: try: other = objects.get(fingerprint=self.fingerprint, key=self.key) message = 'That key is already on file.' raise ValidationError({'key': [message]}) except type(self).DoesNotExist: pass def export(self, format='RFC4716'): pubkey = pubkey_parse(self.key) f = format.upper() if f == 'RFC4716': return pubkey.format_rfc4716() if f == 'PEM': return pubkey.format_pem() raise ValueError("Invalid format") def save(self, *args, **kwargs): if kwargs.pop('update_last_modified', True): self.last_modified = now() super(Key, self).save(*args, **kwargs) def touch(self): self.last_used = now() self.save(update_last_modified=False) class ApplicationKey(models.Model): basekey = models.ForeignKey(Key) class Meta: abstract = True @classmethod def base(cls, **kw): if not 'content_type' in kw: kw['content_type'] = ContentType.objects.get_for_model(cls) return Key(**kw) def __unicode__(self): try: return unicode(self.basekey) except Key.DoesNotExist: return u'(no basekey)' @property def key(self): return self.basekey.key @property def fingerprint(self): return self.basekey.fingerprint @property def created(self): return self.basekey.created @property def last_modified(self): return self.basekey.last_modified @property def last_used(self): return self.basekey.last_used class NamedKey(ApplicationKey): name = models.CharField(max_length=50, blank=True) class Meta: abstract = True def __unicode__(self): return unicode(self.name) def clean(self): if not self.name: try: pubkey = pubkey_parse(self.key) if not pubkey.comment: raise ValidationError('Name or key comment required') self.name = pubkey.comment except Key.DoesNotExist: pass class UserKey(NamedKey): user = models.ForeignKey(User, db_index=True) class Meta: db_table = 'sshkey_userkey' unique_together = [ ('user', 'name'), ] def validate_unique(self, exclude=None): cls = type(self) if not self.pk is None: qs = cls.objects.exclude(pk=pk) else: qs = cls.objects.all() if exclude is None or 'name' not in exclude: if qs.filter(user=self.user, name=self.name).count(): message = 'You already have a key with that name' raise ValidationError({'name': [message]}) if exclude is None or 'basekey' not in exclude: if qs.filter(basekey=self.basekey): message = 'Cannot associate key with two users.' raise ValidationError({'basekey': [message]}) @receiver(post_delete, sender=UserKey) def delete_user_key(sender, instance, **kwargs): instance.basekey.delete() @receiver(pre_save, sender=UserKey) def send_email_add_key(sender, instance, **kwargs): if not settings.SSHKEY_EMAIL_ADD_KEY or instance.pk: return from django.template.loader import render_to_string from django.core.mail import EmailMultiAlternatives from django.core.urlresolvers import reverse context_dict = { 'key': instance, 'subject': settings.SSHKEY_EMAIL_ADD_KEY_SUBJECT, } request = getattr(instance, 'request', None) if request: context_dict['request'] = request context_dict['userkey_list_uri'] = request.build_absolute_uri(reverse('django_sshkey.views.userkey_list')) text_content = render_to_string('sshkey/add_key.txt', context_dict) msg = EmailMultiAlternatives( settings.SSHKEY_EMAIL_ADD_KEY_SUBJECT, text_content, settings.SSHKEY_FROM_EMAIL, [instance.user.email], ) if settings.SSHKEY_SEND_HTML_EMAIL: html_content = render_to_string('sshkey/add_key.html', context_dict) msg.attach_alternative(html_content, 'text/html') msg.send() django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/settings.py 0000664 0000000 0000000 00000004706 12416044674 0030727 0 ustar 00root root 0000000 0000000 # Copyright (c) 2014, Clemson University # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # * Neither the name of the {organization} nor the names of its # contributors may be used to endorse or promote products derived from # this software without specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. from django.conf import settings SSHKEY_AUTHORIZED_KEYS_OPTIONS = getattr(settings, 'SSHKEY_AUTHORIZED_KEYS_OPTIONS', None) SSHKEY_ALLOW_EDIT = getattr(settings, 'SSHKEY_ALLOW_EDIT', False) SSHKEY_EMAIL_ADD_KEY = getattr(settings, 'SSHKEY_EMAIL_ADD_KEY', True) SSHKEY_EMAIL_ADD_KEY_SUBJECT = getattr(settings, 'SSHKEY_EMAIL_ADD_KEY_SUBJECT', "A new public key was added to your account" ) SSHKEY_FROM_EMAIL = getattr(settings, 'SSHKEY_FROM_EMAIL', settings.DEFAULT_FROM_EMAIL) SSHKEY_SEND_HTML_EMAIL = getattr(settings, 'SSHKEY_SEND_HTML_EMAIL', False) SSHKEY_KEY_FORMATTERS = getattr(settings, 'SSHKEY_KEY_FORMATTERS', {}) SSHKEY_KEY_FORMATTERS.setdefault('django_sshkey.userkey', 'django_sshkey.defaults.userkey_formatter') def get_formatter(key): module_name, object_name = SSHKEY_KEY_FORMATTERS[key].rsplit('.', 1) obj = __import__(module_name) for p in module_name.split('.')[1:]: obj = getattr(obj, p) return getattr(obj, object_name) django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/templates.example/ 0000775 0000000 0000000 00000000000 12416044674 0032136 5 ustar 00root root 0000000 0000000 sshkey/ 0000775 0000000 0000000 00000000000 12416044674 0033365 5 ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/templates.example add_key.html 0000664 0000000 0000000 00000001103 12416044674 0035646 0 ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/templates.example/sshkey
{{ key.user.first_name }},
The following SSH public key was added to your account {% if request.META.SERVER_NAME %} on {{ request.META.SERVER_NAME }} {% endif %} {% if request.META.REMOTE_ADDR %} from {{ request.META.REMOTE_ADDR }}{% if request.META.REMOTE_HOST %} ({{ request.META.REMOTE_HOST }}){% endif %}{% endif %}:
Name: {{ key.name }}
Fingerprint: {{ key.fingerprint }}
If you believe this key was added in error then you should click here and delete the key.
add_key.txt 0000664 0000000 0000000 00000000745 12416044674 0035534 0 ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/templates.example/sshkey {{ key.user.first_name }}, The following SSH public key was added to your account{% if request.META.SERVER_NAME %} on {{ request.META.SERVER_NAME }}{% endif %}{% if request.META.REMOTE_ADDR %} from {{ request.META.REMOTE_ADDR }}{% if request.META.REMOTE_HOST %} ({{ request.META.REMOTE_HOST }}){% endif %}{% endif %}: Name: {{ key.name }} Fingerprint: {{ key.fingerprint }} If you believe this key was added in error then you should go to {{ userkey_list_uri }} and delete the key. userkey_detail.html 0000664 0000000 0000000 00000000445 12416044674 0037267 0 ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/templates.example/sshkey {% if action == 'add' %}{{ error_message }}
{% endif %} userkey_list.html 0000664 0000000 0000000 00000002050 12416044674 0036772 0 ustar 00root root 0000000 0000000 django-sshkey-feature-key-aaa45a30797e484ef36ae39e950b03eb0df4d59e/django_sshkey/templates.example/sshkey {% load url from future %}Key | Fingerprint | Created | {% if allow_edit %}Last Modified | {% endif %}Last Used | |
---|---|---|---|---|---|
{{ userkey.name }} | {{ userkey.fingerprint }} | {{ userkey.created|default:"unknown" }} | {% if allow_edit %}{{ userkey.last_modified|default:"unknown" }} | {% endif %}{{ userkey.last_used|default:"never" }} | {% if allow_edit %} Edit {% endif %} Delete |