pax_global_header 0000666 0000000 0000000 00000000064 12354713423 0014516 g ustar 00root root 0000000 0000000 52 comment=35336d9e7e2c61f71a928fc2046b3df9a47b4330
django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/ 0000775 0000000 0000000 00000000000 12354713423 0026434 5 ustar 00root root 0000000 0000000 .gitignore 0000664 0000000 0000000 00000000067 12354713423 0030350 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 *.py[coa]
/django_sshkey.egg-info
/testproject/test.db
LICENSE 0000664 0000000 0000000 00000002726 12354713423 0027371 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 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.
MANIFEST.in 0000664 0000000 0000000 00000000533 12354713423 0030114 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 include LICENSE
include README.rst
include README.upgrading.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 *
README 0000777 0000000 0000000 00000000000 12354713423 0030717 2README.rst ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 README.rst 0000664 0000000 0000000 00000014265 12354713423 0030054 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 =============
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.
For instance::
SSHKEY_AUTHORIZED_KEYS_OPTIONS = 'command="my-command {username}",no-pty'
in settings.py will cause keys produced by the below commands to look similar
to::
command="my-command fred",no-pty ssh-rsa AAAAB3NzaC1yc2E...
assuming the key ``AAAAB3NzaC1yc2E...`` is owned by fred.
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.
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-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.
Using ``django-sshkey-lookup``
------------------------------
``Usage: django-sshkey-lookup URL [USERNAME]``
This program is a wrapper around the previous two commands. The first
parameter is placed in the ``SSHKEY_LOOKUP_URL`` environment variable. If the
second parameter is present then ``django-sshkey-lookup-by-username`` is
executed; otherwise ``django-sshkey-lookup-by-fingerprint`` is executed.
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.
.. _OpenSSH: http://www.openssh.com/
.. _openssh-akcenv: https://github.com/ScottDuckworth/openssh-akcenv
.. _openssh-stdinkey: https://github.com/ScottDuckworth/openssh-stdinkey
README.upgrading.rst 0000664 0000000 0000000 00000004670 12354713423 0032032 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 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+ | django_sshkey | 0001 | See Upgrading from 1.1.x to 2.x below |
+---------+---------------+-------+------------------------------------------+
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-lookup 0000775 0000000 0000000 00000003432 12354713423 0032362 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 #!/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.
if [ $# -eq 0 ]; then
echo "Usage: $0 URL [USERNAME]" >&2
exit 1
fi
SSHKEY_LOOKUP_URL="$1"
export SSHKEY_LOOKUP_URL
if [ $# -eq 1 ]; then
exec `dirname $0`/django-sshkey-lookup-by-fingerprint
else
exec `dirname $0`/django-sshkey-lookup-by-username "$2"
fi
django-sshkey-lookup-all 0000775 0000000 0000000 00000003256 12354713423 0033134 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 #!/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 12354713423 0035315 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 #!/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-lookup-by-username 0000775 0000000 0000000 00000003336 12354713423 0034612 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 #!/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/ 0000775 0000000 0000000 00000000000 12354713423 0031205 5 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 __init__.py 0000664 0000000 0000000 00000003036 12354713423 0033320 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey # 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.2.0'
admin.py 0000664 0000000 0000000 00000003635 12354713423 0032656 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey # 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_sshkey.models import UserKey
class UserKeyAdmin(admin.ModelAdmin):
list_display = [
'__unicode__',
'user',
'name',
'fingerprint',
'created',
'last_modified',
]
search_fields = [
'user__username',
]
readonly_fields = [
'fingerprint',
'created',
'last_modified',
]
admin.site.register(UserKey, UserKeyAdmin)
forms.py 0000664 0000000 0000000 00000003256 12354713423 0032713 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey # 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_sshkey.models import UserKey
class UserKeyForm(forms.ModelForm):
class Meta:
model = UserKey
fields = ['name', 'key']
migrations/ 0000775 0000000 0000000 00000000000 12354713423 0033361 5 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey 0001_initial.py 0000664 0000000 0000000 00000012640 12354713423 0036027 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/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']
__init__.py 0000664 0000000 0000000 00000000000 12354713423 0035460 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey/migrations models.py 0000664 0000000 0000000 00000007373 12354713423 0033054 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey # 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.core.exceptions import ValidationError
from django_sshkey.util import PublicKeyParseError, pubkey_parse
class UserKey(models.Model):
user = models.ForeignKey(User, db_index=True)
name = models.CharField(max_length=50, blank=True)
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)
last_modified = models.DateTimeField(auto_now=True, null=True)
class Meta:
db_table = 'sshkey_userkey'
unique_together = [
('user', 'name'),
]
def __unicode__(self):
return unicode(self.user) + u': ' + self.name
def clean_fields(self, exclude=None):
if not exclude or 'key' not in exclude:
self.key = self.key.strip()
def clean(self):
try:
pubkey = pubkey_parse(self.key)
except PublicKeyParseError as e:
raise ValidationError(str(e))
self.key = pubkey.format_openssh()
self.fingerprint = pubkey.fingerprint()
if not self.name:
if not pubkey.comment:
raise ValidationError('Name or key comment required')
self.name = pubkey.comment
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 'name' not in exclude:
if objects.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 'key' not in exclude:
try:
other = objects.get(fingerprint=self.fingerprint, key=self.key)
if self.user == other.user:
message = 'You already have that key on file (%s)' % other.name
else:
message = 'Somebody else already has that key 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")
settings.py 0000664 0000000 0000000 00000004057 12354713423 0033425 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey # 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_AUTHORIZED_KEYS_COMMAND = getattr(settings, 'SSHKEY_AUTHORIZED_KEYS_COMMAND', None)
if SSHKEY_AUTHORIZED_KEYS_COMMAND is not None:
import warnings
with warnings.catch_warnings():
import warnings
warnings.simplefilter('default', DeprecationWarning)
warnings.warn(
'SSHKEY_AUTHORIZED_KEYS_COMMAND has been deprecated; '
'use SSHKEY_AUTHORIZED_KEYS_OPTIONS instead.',
DeprecationWarning)
templates.example/ 0000775 0000000 0000000 00000000000 12354713423 0034635 5 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey sshkey/ 0000775 0000000 0000000 00000000000 12354713423 0036143 5 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey/templates.example userkey_detail.html 0000664 0000000 0000000 00000000445 12354713423 0042045 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey/templates.example/sshkey {% if action == 'add' %}
Add SSH Key
{% else %}
Edit SSH Key
{% endif %}
{% if error_message %}
{{ error_message }}
{% endif %}
userkey_list.html 0000664 0000000 0000000 00000001141 12354713423 0041550 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey/templates.example/sshkey My Keys
Add Key
Key |
Fingerprint |
Created |
Last Modified |
|
{% for userkey in userkey_list %}
{{ userkey.name }} |
{{ userkey.fingerprint }} |
{{ userkey.created }} |
{{ userkey.last_modified }} |
Edit |
Delete |
{% endfor %}
tests.py 0000664 0000000 0000000 00000033770 12354713423 0032733 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey # 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.test import TestCase
from django.test.client import Client
from django.contrib.auth.models import User
from django.core.exceptions import ValidationError
from django.core.urlresolvers import reverse
from django_sshkey.models import UserKey
from django_sshkey import settings
import os
import shutil
import subprocess
import tempfile
def ssh_keygen(type=None, passphrase='', comment=None, file=None):
cmd = ['ssh-keygen', '-q']
if type is not None:
cmd += ['-t', type]
if passphrase is not None:
cmd += ['-N', passphrase]
if comment is not None:
cmd += ['-C', comment]
if file is not None:
cmd += ['-f', file]
subprocess.check_call(cmd)
def ssh_key_export(input_file, output_file, format='RFC4716'):
cmd = ['ssh-keygen', '-e', '-m', format, '-f', input_file]
with open(output_file, 'wb') as f:
subprocess.check_call(cmd, stdout=f)
def ssh_fingerprint(pubkey_path):
cmd = ['ssh-keygen', '-lf', pubkey_path]
p = subprocess.Popen(cmd, stdout=subprocess.PIPE)
stdout, stderr = p.communicate()
fingerprint = stdout.split(None, 2)[1]
return fingerprint
class BaseTestCase(TestCase):
@classmethod
def setUpClass(cls):
cls.key_dir = tempfile.mkdtemp(prefix='sshkey-test.')
@classmethod
def tearDownClass(cls):
if cls.key_dir:
shutil.rmtree(cls.key_dir)
cls.key_dir = None
class UserKeyCreationTestCase(BaseTestCase):
@classmethod
def setUpClass(cls):
super(UserKeyCreationTestCase, cls).setUpClass()
cls.user1 = User.objects.create(username='user1')
cls.user2 = User.objects.create(username='user2')
# key1 has a comment
cls.key1_path = os.path.join(cls.key_dir, 'key1')
ssh_keygen(comment='comment', file=cls.key1_path)
# key2 does not have a comment
cls.key2_path = os.path.join(cls.key_dir, 'key2')
ssh_keygen(comment='', file=cls.key2_path)
@classmethod
def tearDownClass(cls):
User.objects.all().delete()
super(UserKeyCreationTestCase, cls).tearDownClass()
def tearDown(self):
UserKey.objects.all().delete()
def test_with_name_with_comment(self):
key = UserKey(
user = self.user1,
name = 'name',
key = open(self.key1_path+'.pub').read(),
)
key.full_clean()
key.save()
self.assertEqual(key.name, 'name')
def test_with_name_without_comment(self):
key = UserKey(
user = self.user1,
name = 'name',
key = open(self.key2_path+'.pub').read(),
)
key.full_clean()
key.save()
self.assertEqual(key.name, 'name')
def test_without_name_with_comment(self):
key = UserKey(
user = self.user1,
key = open(self.key1_path+'.pub').read(),
)
key.full_clean()
key.save()
self.assertEqual(key.name, 'comment')
def test_without_name_without_comment_fails(self):
key = UserKey(
user = self.user1,
key = open(self.key2_path+'.pub').read(),
)
self.assertRaises(ValidationError, key.full_clean)
def test_private_key_fails(self):
key = UserKey(
user = self.user1,
name = 'name',
key = open(self.key1_path).read(),
)
self.assertRaises(ValidationError, key.full_clean)
def test_invalid_key_fails(self):
key = UserKey(
user = self.user1,
name = 'name',
key = 'ssh-rsa invalid',
)
self.assertRaises(ValidationError, key.full_clean)
def test_key_with_options_fails(self):
key = UserKey(
user = self.user1,
name = 'name',
key = 'command="foobar" ' + open(self.key1_path+'.pub').read(),
)
self.assertRaises(ValidationError, key.full_clean)
def test_multiple_keys_fails(self):
key = UserKey(
user = self.user1,
name = 'name',
key = open(self.key1_path+'.pub').read() \
+ open(self.key2_path+'.pub').read(),
)
self.assertRaises(ValidationError, key.full_clean)
def test_fingerprint(self):
fingerprint = ssh_fingerprint(self.key1_path+'.pub')
key = UserKey(
user = self.user1,
name = 'name',
key = open(self.key1_path+'.pub').read(),
)
key.full_clean()
key.save()
self.assertEqual(key.fingerprint, fingerprint)
def test_same_name_same_user(self):
key1 = UserKey(
user = self.user1,
name = 'name',
key = open(self.key1_path+'.pub').read(),
)
key1.full_clean()
key1.save()
key2 = UserKey(
user = self.user1,
name = 'name',
key = open(self.key2_path+'.pub').read(),
)
self.assertRaises(ValidationError, key2.full_clean)
def test_same_name_different_user(self):
key1 = UserKey(
user = self.user1,
name = 'name',
key = open(self.key1_path+'.pub').read(),
)
key1.full_clean()
key1.save()
key2 = UserKey(
user = self.user2,
name = 'name',
key = open(self.key2_path+'.pub').read(),
)
key2.full_clean()
key2.save()
def test_same_key_same_user(self):
key1 = UserKey(
user = self.user1,
name = 'name1',
key = open(self.key1_path+'.pub').read(),
)
key1.full_clean()
key1.save()
key2 = UserKey(
user = self.user1,
name = 'name2',
key = open(self.key1_path+'.pub').read(),
)
self.assertRaises(ValidationError, key2.full_clean)
def test_same_key_different_user(self):
key1 = UserKey(
user = self.user1,
name = 'name1',
key = open(self.key1_path+'.pub').read(),
)
key1.full_clean()
key1.save()
key2 = UserKey(
user = self.user2,
name = 'name2',
key = open(self.key1_path+'.pub').read(),
)
self.assertRaises(ValidationError, key2.full_clean)
class RFC4716TestCase(BaseTestCase):
@classmethod
def setUpClass(cls):
super(RFC4716TestCase, cls).setUpClass()
cls.user1 = User.objects.create(username='user1')
# key1 has a comment
cls.key1_path = os.path.join(cls.key_dir, 'key1')
cls.key1_rfc4716_path = os.path.join(cls.key_dir, 'key1.rfc4716')
ssh_keygen(comment='comment', file=cls.key1_path)
ssh_key_export(cls.key1_path, cls.key1_rfc4716_path, 'RFC4716')
# key2 does not have a comment
cls.key2_path = os.path.join(cls.key_dir, 'key2')
cls.key2_rfc4716_path = os.path.join(cls.key_dir, 'key2.rfc4716')
ssh_keygen(comment='', file=cls.key2_path)
ssh_key_export(cls.key2_path, cls.key2_rfc4716_path, 'RFC4716')
@classmethod
def tearDownClass(cls):
User.objects.all().delete()
super(RFC4716TestCase, cls).tearDownClass()
def tearDown(self):
UserKey.objects.all().delete()
def test_with_comment(self):
key = UserKey(
user = self.user1,
name = 'name',
key = open(self.key1_rfc4716_path).read(),
)
key.full_clean()
key.save()
self.assertEqual(key.key.split()[:2], open(self.key1_path+'.pub').read().split()[:2])
def test_without_comment(self):
key = UserKey(
user = self.user1,
name = 'name',
key = open(self.key2_rfc4716_path).read(),
)
key.full_clean()
key.save()
self.assertEqual(key.key.split()[:2], open(self.key2_path+'.pub').read().split()[:2])
class PemTestCase(BaseTestCase):
@classmethod
def setUpClass(cls):
super(PemTestCase, cls).setUpClass()
cls.user1 = User.objects.create(username='user1')
cls.key1_path = os.path.join(cls.key_dir, 'key1')
cls.key1_pem_path = os.path.join(cls.key_dir, 'key1.pem')
ssh_keygen(comment='', file=cls.key1_path)
ssh_key_export(cls.key1_path, cls.key1_pem_path, 'PEM')
@classmethod
def tearDownClass(cls):
User.objects.all().delete()
super(PemTestCase, cls).tearDownClass()
def tearDown(self):
UserKey.objects.all().delete()
def test(self):
key = UserKey(
user = self.user1,
name = 'name',
key = open(self.key1_pem_path).read(),
)
key.full_clean()
key.save()
self.assertEqual(key.key.split()[:2], open(self.key1_path+'.pub').read().split()[:2])
class UserKeyLookupTestCase(BaseTestCase):
@classmethod
def setUpClass(cls):
super(UserKeyLookupTestCase, cls).setUpClass()
cls.original_options = settings.SSHKEY_AUTHORIZED_KEYS_OPTIONS
settings.SSHKEY_AUTHORIZED_KEYS_OPTIONS = 'command="{username}"'
cls.user1 = User.objects.create(username='user1')
cls.user2 = User.objects.create(username='user2')
cls.key1_path = os.path.join(cls.key_dir, 'key1')
ssh_keygen(file=cls.key1_path)
key1 = UserKey(
user = cls.user1,
name = 'key1',
key = open(cls.key1_path+'.pub').read(),
)
key1.full_clean()
key1.save()
cls.key2_path = os.path.join(cls.key_dir, 'key2')
ssh_keygen(file=cls.key2_path)
key2 = UserKey(
user = cls.user1,
name = 'key2',
key = open(cls.key2_path+'.pub').read(),
)
key2.full_clean()
key2.save()
cls.key3_path = os.path.join(cls.key_dir, 'key3')
ssh_keygen(file=cls.key3_path)
key3 = UserKey(
user = cls.user2,
name = 'key3',
key = open(cls.key3_path+'.pub').read(),
)
key3.full_clean()
key3.save()
cls.key4_path = os.path.join(cls.key_dir, 'key4')
ssh_keygen(file=cls.key4_path)
@classmethod
def tearDownClass(cls):
settings.SSHKEY_AUTHORIZED_KEYS_OPTIONS = cls.original_options
User.objects.all().delete()
super(UserKeyLookupTestCase, cls).tearDownClass()
def test_lookup_all(self):
client = Client()
url = reverse('django_sshkey.views.lookup')
response = client.get(url)
self.assertEqual(response.status_code, 200)
self.assertIn('Content-Type', response)
self.assertEqual(response['Content-Type'], 'text/plain')
actual_content = set(response.content.strip().split('\n'))
correct_content = set((
'command="user1" ' + open(self.key1_path + '.pub').read().strip(),
'command="user1" ' + open(self.key2_path + '.pub').read().strip(),
'command="user2" ' + open(self.key3_path + '.pub').read().strip(),
))
self.assertEqual(actual_content, correct_content)
def test_lookup_by_fingerprint(self):
client = Client()
url = reverse('django_sshkey.views.lookup')
fingerprint = ssh_fingerprint(self.key1_path+'.pub')
response = client.get(url, {'fingerprint': fingerprint})
self.assertEqual(response.status_code, 200)
self.assertIn('Content-Type', response)
self.assertEqual(response['Content-Type'], 'text/plain')
username = self.user1.username
actual_content = set(response.content.strip().split('\n'))
correct_content = set((
'command="user1" ' + open(self.key1_path + '.pub').read().strip(),
))
self.assertEqual(actual_content, correct_content)
def test_lookup_by_username_single_result(self):
client = Client()
url = reverse('django_sshkey.views.lookup')
username = self.user2.username
response = client.get(url, {'username': username})
self.assertEqual(response.status_code, 200)
self.assertIn('Content-Type', response)
self.assertEqual(response['Content-Type'], 'text/plain')
body = open(self.key1_path + '.pub').read().strip()
actual_content = set(response.content.strip().split('\n'))
correct_content = set((
'command="user2" ' + open(self.key3_path + '.pub').read().strip(),
))
self.assertEqual(actual_content, correct_content)
def test_lookup_by_username_multiple_results(self):
client = Client()
url = reverse('django_sshkey.views.lookup')
username = self.user1.username
response = client.get(url, {'username': username})
self.assertEqual(response.status_code, 200)
self.assertIn('Content-Type', response)
self.assertEqual(response['Content-Type'], 'text/plain')
body = open(self.key1_path + '.pub').read().strip()
actual_content = set(response.content.strip().split('\n'))
correct_content = set((
'command="user1" ' + open(self.key1_path + '.pub').read().strip(),
'command="user1" ' + open(self.key2_path + '.pub').read().strip(),
))
self.assertEqual(actual_content, correct_content)
def test_lookup_nonexist_fingerprint(self):
client = Client()
url = reverse('django_sshkey.views.lookup')
fingerprint = ssh_fingerprint(self.key4_path+'.pub')
response = client.get(url, {'fingerprint': fingerprint})
self.assertEqual(response.status_code, 200)
self.assertIn('Content-Type', response)
self.assertEqual(response['Content-Type'], 'text/plain')
self.assertEqual(response.content, '')
def test_lookup_nonexist_username(self):
client = Client()
url = reverse('django_sshkey.views.lookup')
response = client.get(url, {'username': 'batman'})
self.assertEqual(response.status_code, 200)
self.assertIn('Content-Type', response)
self.assertEqual(response['Content-Type'], 'text/plain')
self.assertEqual(response.content, '')
urls.py 0000664 0000000 0000000 00000003554 12354713423 0032553 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey # 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.
try:
from django.conf.urls.defaults import patterns, url
except ImportError:
from django.conf.urls import patterns, url
urlpatterns = patterns('django_sshkey.views',
url(r'^lookup$', 'lookup'),
url(r'^$', 'userkey_list'),
url(r'^add$', 'userkey_add'),
url(r'^(?P\d+)$', 'userkey_edit'),
url(r'^(?P\d+)/delete$', 'userkey_delete'),
)
util.py 0000664 0000000 0000000 00000020207 12354713423 0032535 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey # 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.
import base64
import binascii
import struct
SSHKEY_LOOKUP_URL_DEFAULT = 'http://localhost:8000/sshkey/lookup'
def wrap(text, width, wrap_end=None):
n = 0
t = ''
if wrap_end is None:
while n < len(text):
m = n + width
t += text[n:m]
if len(text) <= m:
return t
t += '\n'
n = m
else:
while n < len(text):
m = n + width
if len(text) <= m:
return t + text[n:m]
m -= len(wrap_end)
t += text[n:m] + wrap_end + '\n'
n = m
return t
def bytes2int(b):
h = binascii.hexlify(b)
return int(h, 16)
def int2bytes(i):
h = '%x' % i
if len(h) & 1:
h = '0' + h
return bytearray.fromhex(h)
class PublicKeyParseError(Exception):
def __init__(self, text):
self.text = text
def __str__(self):
return "Unrecognized public key format"
class PublicKey(object):
def __init__(self, b64key, comment=None):
self.b64key = b64key
self.comment = comment
keydata = base64.b64decode(b64key.encode('ascii'))
self.keydata = keydata
self.parts = []
while keydata:
dlen = struct.unpack('>I', keydata[:4])[0]
data, keydata = keydata[4:4+dlen], keydata[4+dlen:]
self.parts.append(data)
self.algorithm = self.parts[0]
def fingerprint(self):
import hashlib
fp = hashlib.md5(self.keydata).hexdigest()
return ':'.join(a+b for a,b in zip(fp[::2], fp[1::2]))
def format_openssh(self):
out = self.algorithm + ' ' + self.b64key
if self.comment:
out += ' ' + self.comment
return out
def format_rfc4716(self):
out = '---- BEGIN SSH2 PUBLIC KEY ----\n'
if self.comment:
comment = 'Comment: "%s"' % self.comment
out += wrap(comment, 72, '\\') + '\n'
out += wrap(self.b64key, 72) + '\n'
out += '---- END SSH2 PUBLIC KEY ----'
return out
def format_pem(self):
if self.algorithm != 'ssh-rsa' and len(self.parts) == 3:
raise TypeError("key is not a valid RSA key")
from pyasn1.codec.der import encoder as der_encoder
from pyasn1.type import univ
e = bytes2int(self.parts[1])
n = bytes2int(self.parts[2])
pkcs1_seq = univ.Sequence()
pkcs1_seq.setComponentByPosition(0, univ.Integer(n))
pkcs1_seq.setComponentByPosition(1, univ.Integer(e))
der = der_encoder.encode(pkcs1_seq)
out = (
'-----BEGIN RSA PUBLIC KEY-----\n' +
wrap(base64.b64encode(der), 64) + '\n' +
'-----END RSA PUBLIC KEY-----'
)
return out
def pubkey_parse_openssh(text):
fields = text.split(None, 2)
if len(fields) < 2:
raise PublicKeyParseError(text)
try:
if len(fields) == 2:
key = PublicKey(fields[1])
else:
key = PublicKey(fields[1], fields[2])
except TypeError:
raise PublicKeyParseError(text)
if fields[0] != key.algorithm:
raise PublicKeyParseError(text)
return key
def pubkey_parse_rfc4716(text):
lines = text.splitlines()
if not (
lines[0] == '---- BEGIN SSH2 PUBLIC KEY ----'
and lines[-1] == '---- END SSH2 PUBLIC KEY ----'
):
raise PublicKeyParseError(text)
lines = lines[1:-1]
b64key = ''
headers = {}
while lines:
line = lines.pop(0)
if ':' in line:
while line[-1] == '\\':
line = line[:-1] + lines.pop(0)
k,v = line.split(':', 1)
headers[k.lower()] = v.lstrip()
else:
b64key += line
comment = headers.get('comment')
if comment and comment[0] in ('"', "'") and comment[0] == comment[-1]:
comment = comment[1:-1]
try:
return PublicKey(b64key, comment)
except TypeError:
raise PublicKeyParseError(text)
def pubkey_parse_pem(text):
from pyasn1.codec.der import decoder as der_decoder
lines = text.splitlines()
if not (
lines[0] == '-----BEGIN RSA PUBLIC KEY-----'
and lines[-1] == '-----END RSA PUBLIC KEY-----'
):
raise PublicKeyParseError(text)
der = base64.b64decode(''.join(lines[1:-1]).encode('ascii'))
pkcs1_seq = der_decoder.decode(der)
n_val = pkcs1_seq[0][0]
e_val = pkcs1_seq[0][1]
n = int2bytes(n_val)
e = int2bytes(e_val)
if n[0] & 0x80: n = b'\x00' + n
if e[0] & 0x80: e = b'\x00' + e
algorithm = 'ssh-rsa'.encode('ascii')
keydata = (
struct.pack('>I', len(algorithm)) +
algorithm +
struct.pack('>I', len(e)) +
e +
struct.pack('>I', len(n)) +
n
)
b64key = base64.b64encode(keydata).decode('ascii')
return PublicKey(b64key)
def pubkey_parse(text):
lines = text.splitlines()
if len(lines) == 1:
return pubkey_parse_openssh(text)
if lines[0] == '---- BEGIN SSH2 PUBLIC KEY ----':
return pubkey_parse_rfc4716(text)
if lines[0] == '-----BEGIN RSA PUBLIC KEY-----':
return pubkey_parse_pem(text)
raise PublicKeyParseError(text)
def lookup_all(url):
import urllib
response = urllib.urlopen(url)
return response.readlines()
def lookup_by_username(url, username):
import urllib
url += '?' + urllib.urlencode({'username': username})
response = urllib.urlopen(url)
return response.readlines()
def lookup_by_fingerprint(url, fingerprint):
import urllib
url += '?' + urllib.urlencode({'fingerprint': fingerprint})
response = urllib.urlopen(url)
return response.readlines()
def lookup_all_main():
import sys
from os import getenv
url = getenv('SSHKEY_LOOKUP_URL', SSHKEY_LOOKUP_URL_DEFAULT)
for key in lookup_all(url):
sys.stdout.write(key)
def lookup_by_username_main():
import sys
from os import getenv
if len(sys.argv) < 2:
sys.stderr.write('Usage: %s USERNAME\n' % sys.argv[0])
sys.exit(1)
username = sys.argv[1]
url = getenv('SSHKEY_LOOKUP_URL', SSHKEY_LOOKUP_URL_DEFAULT)
for key in lookup_by_username(url, username):
sys.stdout.write(key)
def lookup_by_fingerprint_main():
import sys
from os import getenv
fingerprint = getenv('SSH_KEY_FINGERPRINT')
if fingerprint is None:
key = getenv('SSH_KEY')
if key is None:
key = sys.stdin.readline()
if not key:
sys.stderr.write(
"Error: cannot retrieve fingerprint from environment or stdin\n"
)
sys.exit(1)
try:
pubkey = pubkey_parse(key)
except PublicKeyParseError as e:
sys.stderr.write("Error: " + str(e))
sys.exit(1)
fingerprint = pubkey.fingerprint()
url = getenv('SSHKEY_LOOKUP_URL', SSHKEY_LOOKUP_URL_DEFAULT)
for key in lookup_by_fingerprint(url, fingerprint):
sys.stdout.write(key)
def lookup_main():
import sys
from os import environ
if len(sys.argv) < 2:
sys.stderr.write('Usage: %s URL [USERNAME]\n' % sys.argv[0])
sys.exit(1)
url = sys.argv[1]
if len(sys.argv) == 2:
environ['SSHKEY_LOOKUP_URL'] = url
lookup_by_fingerprint_main()
else:
username = sys.argv[2]
for key in lookup_by_username(url, username):
sys.stdout.write(key)
views.py 0000664 0000000 0000000 00000012354 12354713423 0032721 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/django_sshkey # 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.http import HttpResponse, HttpResponseRedirect
from django.views.decorators.http import require_http_methods, require_GET
from django.shortcuts import get_object_or_404, render_to_response
from django.template import RequestContext
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.core.exceptions import PermissionDenied
from django.core.urlresolvers import reverse
from django.utils.http import is_safe_url
from django_sshkey import settings
from django_sshkey.models import UserKey
from django_sshkey.forms import UserKeyForm
@require_GET
def lookup(request):
try:
fingerprint = request.GET['fingerprint']
keys = UserKey.objects.filter(fingerprint=fingerprint)
except KeyError:
try:
username = request.GET['username']
keys = UserKey.objects.filter(user__username=username)
except KeyError:
keys = UserKey.objects.iterator()
response = ''
for key in keys:
if settings.SSHKEY_AUTHORIZED_KEYS_OPTIONS:
options = settings.SSHKEY_AUTHORIZED_KEYS_OPTIONS.format(
username=key.user.username) + ' '
elif settings.SSHKEY_AUTHORIZED_KEYS_COMMAND:
options = 'command="%s" ' % (
settings.SSHKEY_AUTHORIZED_KEYS_COMMAND
.format(username=key.user.username)
.replace('"', r'\"')
)
else:
options = ''
response += options + key.key + '\n'
return HttpResponse(response, mimetype='text/plain')
@login_required
@require_GET
def userkey_list(request):
userkey_list = UserKey.objects.filter(user=request.user)
return render_to_response(
'sshkey/userkey_list.html',
{ 'userkey_list': userkey_list },
context_instance = RequestContext(request),
)
@login_required
@require_http_methods(['GET', 'POST'])
def userkey_add(request):
if request.method == 'POST':
userkey = UserKey(user=request.user)
form = UserKeyForm(request.POST, instance=userkey)
if form.is_valid():
form.save()
default_redirect = reverse('django_sshkey.views.userkey_list')
url = request.GET.get('next', default_redirect)
if not is_safe_url(url=url, host=request.get_host()):
url = default_redirect
message = 'SSH key %s was saved.' % userkey.name
messages.success(request, message, fail_silently=True)
return HttpResponseRedirect(url)
else:
form = UserKeyForm()
return render_to_response(
'sshkey/userkey_detail.html',
{ 'form': form, 'action': 'add' },
context_instance = RequestContext(request),
)
@login_required
@require_http_methods(['GET', 'POST'])
def userkey_edit(request, pk):
userkey = get_object_or_404(UserKey, pk=pk)
if userkey.user != request.user:
raise PermissionDenied
if request.method == 'POST':
form = UserKeyForm(request.POST, instance=userkey)
if form.is_valid():
form.save()
default_redirect = reverse('django_sshkey.views.userkey_list')
url = request.GET.get('next', default_redirect)
if not is_safe_url(url=url, host=request.get_host()):
url = default_redirect
message = 'SSH key %s was saved.' % userkey.name
messages.success(request, message, fail_silently=True)
return HttpResponseRedirect(url)
else:
form = UserKeyForm(instance=userkey)
return render_to_response(
'sshkey/userkey_detail.html',
{ 'form': form, 'action': 'edit' },
context_instance = RequestContext(request),
)
@login_required
@require_GET
def userkey_delete(request, pk):
userkey = get_object_or_404(UserKey, pk=pk)
if userkey.user != request.user:
raise PermissionDenied
userkey.delete()
message = 'SSH key %s was deleted.' % userkey.name
messages.success(request, message, fail_silently=True)
return HttpResponseRedirect(reverse('django_sshkey.views.userkey_list'))
lookup.py 0000775 0000000 0000000 00000003321 12354713423 0030242 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 #!/usr/bin/env python
# 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.
import warnings
import django_sshkey.util
warnings.warn("lookup.py is deprecated; use django-sshkey-pylookup",
DeprecationWarning)
django_sshkey.util.lookup_main()
lookup.sh 0000777 0000000 0000000 00000000000 12354713423 0034215 2django-sshkey-lookup ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 maintainer.txt 0000664 0000000 0000000 00000000503 12354713423 0031243 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 RELEASE INSTRUCTIONS
ensure unit tests pass
git flow release start X.Y.Z
bump __version__ in django_sshkey/__init__.py
git add django_sshkey/__init__.py
git commit -m 'bump version'
python setup.py sdist
git flow release finish -m 'version X.Y.Z' X.Y.Z
git push origin master X.Y.Z
publish dist/django-sshkey-X.Y.Z.tar.gz
setup.py 0000664 0000000 0000000 00000006135 12354713423 0030074 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 # 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.
import os
from setuptools import setup
README = open(os.path.join(os.path.dirname(__file__), 'README.rst')).read()
# allow setup.py to be run from any path
os.chdir(os.path.normpath(os.path.join(os.path.abspath(__file__), os.pardir)))
exec(open('django_sshkey/__init__.py').read())
setup(
name='django-sshkey',
version=__version__,
packages=['django_sshkey'],
include_package_data=True,
license='BSD',
description='Associates multiple SSH public keys with Django user accounts.',
long_description=README,
url='https://bitbucket.org/ClemsonSoCUnix/django-sshkey',
author='Scott Duckworth',
author_email='sduckwo@clemson.edu',
classifiers=[
'Environment :: Web Environment',
'Framework :: Django',
'Intended Audience :: Developers',
'License :: OSI Approved :: BSD License',
'Operating System :: OS Independent',
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
],
scripts=[
'django-sshkey-lookup',
'django-sshkey-lookup-all',
'django-sshkey-lookup-by-username',
'django-sshkey-lookup-by-fingerprint',
],
entry_points={
'console_scripts': [
'django-sshkey-pylookup = django_sshkey.util:lookup_main',
'django-sshkey-pylookup-all = django_sshkey.util:lookup_all_main',
'django-sshkey-pylookup-by-username = django_sshkey.util:lookup_by_username_main',
'django-sshkey-pylookup-by-fingerprint = django_sshkey.util:lookup_by_fingerprint_main',
],
},
)
testproject/ 0000775 0000000 0000000 00000000000 12354713423 0030723 5 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330 __init__.py 0000664 0000000 0000000 00000000000 12354713423 0033022 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/testproject django_sshkey 0000777 0000000 0000000 00000000000 12354713423 0036457 2../django_sshkey ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/testproject manage.py 0000775 0000000 0000000 00000000767 12354713423 0032542 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/testproject #!/usr/bin/env python
from django.core.management import execute_manager
import imp
try:
imp.find_module('settings') # Assumed to be in the same directory.
except ImportError:
import sys
sys.stderr.write("Error: Can't find the file 'settings.py' in the directory containing %r. It appears you've customized things.\nYou'll have to run django-admin.py, passing it your settings module.\n" % __file__)
sys.exit(1)
import settings
if __name__ == "__main__":
execute_manager(settings)
settings.py 0000664 0000000 0000000 00000012070 12354713423 0033135 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/testproject # Django settings for testproject project.
DEBUG = True
TEMPLATE_DEBUG = DEBUG
ADMINS = (
# ('Your Name', 'your_email@example.com'),
)
MANAGERS = ADMINS
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
'NAME': 'test.db', # Or path to database file if using sqlite3.
'USER': '', # Not used with sqlite3.
'PASSWORD': '', # Not used with sqlite3.
'HOST': '', # Set to empty string for localhost. Not used with sqlite3.
'PORT': '', # Set to empty string for default. Not used with sqlite3.
}
}
# Hosts/domain names that are valid for this site; required if DEBUG is False
# See https://docs.djangoproject.com/en/{{ docs_version }}/ref/settings/#allowed-hosts
ALLOWED_HOSTS = []
# Local time zone for this installation. Choices can be found here:
# http://en.wikipedia.org/wiki/List_of_tz_zones_by_name
# although not all choices may be available on all operating systems.
# On Unix systems, a value of None will cause Django to use the same
# timezone as the operating system.
# If running in a Windows environment this must be set to the same as your
# system time zone.
TIME_ZONE = 'America/New_York'
# Language code for this installation. All choices can be found here:
# http://www.i18nguy.com/unicode/language-identifiers.html
LANGUAGE_CODE = 'en-us'
SITE_ID = 1
# If you set this to False, Django will make some optimizations so as not
# to load the internationalization machinery.
USE_I18N = True
# If you set this to False, Django will not format dates, numbers and
# calendars according to the current locale
USE_L10N = True
# Absolute filesystem path to the directory that will hold user-uploaded files.
# Example: "/home/media/media.lawrence.com/media/"
MEDIA_ROOT = ''
# URL that handles the media served from MEDIA_ROOT. Make sure to use a
# trailing slash.
# Examples: "http://media.lawrence.com/media/", "http://example.com/media/"
MEDIA_URL = ''
# Absolute path to the directory static files should be collected to.
# Don't put anything in this directory yourself; store your static files
# in apps' "static/" subdirectories and in STATICFILES_DIRS.
# Example: "/home/media/media.lawrence.com/static/"
STATIC_ROOT = ''
# URL prefix for static files.
# Example: "http://media.lawrence.com/static/"
STATIC_URL = '/static/'
# URL prefix for admin static files -- CSS, JavaScript and images.
# Make sure to use a trailing slash.
# Examples: "http://foo.com/static/admin/", "/static/admin/".
ADMIN_MEDIA_PREFIX = '/static/admin/'
# Additional locations of static files
STATICFILES_DIRS = (
# Put strings here, like "/home/html/static" or "C:/www/django/static".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
)
# List of finder classes that know how to find static files in
# various locations.
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.FileSystemFinder',
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
# 'django.contrib.staticfiles.finders.DefaultStorageFinder',
)
# Make this unique, and don't share it with anybody.
SECRET_KEY = '^t!$t$&%g!dzo!2ig40v1tgk95$ed^i&h7w(ydg&adkdkc8wvn'
# List of callables that know how to import templates from various sources.
TEMPLATE_LOADERS = (
'django.template.loaders.filesystem.Loader',
'django.template.loaders.app_directories.Loader',
# 'django.template.loaders.eggs.Loader',
)
MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
)
ROOT_URLCONF = 'testproject.urls'
TEMPLATE_DIRS = (
# Put strings here, like "/home/html/django_templates" or "C:/www/django/templates".
# Always use forward slashes, even on Windows.
# Don't forget to use absolute paths, not relative paths.
'templates',
)
INSTALLED_APPS = (
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.sites',
'django.contrib.messages',
'django.contrib.staticfiles',
'django.contrib.admin',
'django.contrib.admindocs',
'south',
'django_sshkey',
)
# A sample logging configuration. The only tangible logging
# performed by this configuration is to send an email to
# the site admins on every HTTP 500 error.
# See http://docs.djangoproject.com/en/dev/topics/logging for
# more details on how to customize your logging configuration.
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'handlers': {
'mail_admins': {
'level': 'ERROR',
'class': 'django.utils.log.AdminEmailHandler'
}
},
'loggers': {
'django.request': {
'handlers': ['mail_admins'],
'level': 'ERROR',
'propagate': True,
},
}
}
templates 0000777 0000000 0000000 00000000000 12354713423 0041255 2../django_sshkey/templates.example ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/testproject urls.py 0000664 0000000 0000000 00000000453 12354713423 0032264 0 ustar 00root root 0000000 0000000 django-sshkey-35336d9e7e2c61f71a928fc2046b3df9a47b4330-35336d9e7e2c61f71a928fc2046b3df9a47b4330/testproject from django.conf.urls.defaults import patterns, include, url
from django.contrib import admin
admin.autodiscover()
urlpatterns = patterns('',
url(r'^', include('django_sshkey.urls')),
url(r'^admin/doc/', include('django.contrib.admindocs.urls')),
url(r'^admin/', include(admin.site.urls)),
)