Add services and SSH keys.
authorRadek Czajka <rczajka@rczajka.pl>
Sat, 30 Mar 2019 21:49:27 +0000 (22:49 +0100)
committerRadek Czajka <rczajka@rczajka.pl>
Sat, 30 Mar 2019 21:49:27 +0000 (22:49 +0100)
32 files changed:
src/accounts/admin.py [deleted file]
src/accounts/migrations/0003_auto_20190330_1311.py [new file with mode: 0644]
src/accounts/models.py
src/accounts/templates/account/base.html
src/cas/settings.py
src/cas/static/css/main.css
src/cas/urls.py
src/services/__init__.py [new file with mode: 0644]
src/services/admin.py [new file with mode: 0644]
src/services/apps.py [new file with mode: 0644]
src/services/migrations/0001_initial.py [new file with mode: 0644]
src/services/migrations/__init__.py [new file with mode: 0644]
src/services/models.py [new file with mode: 0644]
src/services/templates/services/ssh_authorized_keys.txt [new file with mode: 0644]
src/services/templatetags/__init__.py [new file with mode: 0644]
src/services/templatetags/services.py [new file with mode: 0644]
src/services/tests.py [new file with mode: 0644]
src/services/urls.py [new file with mode: 0644]
src/services/views.py [new file with mode: 0644]
src/ssh_keys/__init__.py [new file with mode: 0644]
src/ssh_keys/admin.py [new file with mode: 0644]
src/ssh_keys/apps.py [new file with mode: 0644]
src/ssh_keys/forms.py [new file with mode: 0644]
src/ssh_keys/migrations/0001_initial.py [new file with mode: 0644]
src/ssh_keys/migrations/__init__.py [new file with mode: 0644]
src/ssh_keys/models.py [new file with mode: 0644]
src/ssh_keys/templates/ssh_keys/base.html [new file with mode: 0644]
src/ssh_keys/templates/ssh_keys/sshkey_add.html [new file with mode: 0644]
src/ssh_keys/templates/ssh_keys/sshkey_confirm_delete.html [new file with mode: 0644]
src/ssh_keys/templates/ssh_keys/sshkey_list.html [new file with mode: 0644]
src/ssh_keys/urls.py [new file with mode: 0644]
src/ssh_keys/views.py [new file with mode: 0644]

diff --git a/src/accounts/admin.py b/src/accounts/admin.py
deleted file mode 100644 (file)
index f4af527..0000000
+++ /dev/null
@@ -1,4 +0,0 @@
-from django.contrib import admin
-from .models import Service
-
-admin.site.register(Service)
diff --git a/src/accounts/migrations/0003_auto_20190330_1311.py b/src/accounts/migrations/0003_auto_20190330_1311.py
new file mode 100644 (file)
index 0000000..1270244
--- /dev/null
@@ -0,0 +1,38 @@
+# Generated by Django 2.1.7 on 2019-03-30 13:11
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('accounts', '0002_servicegroup_serviceuser'),
+    ]
+
+    operations = [
+        migrations.RemoveField(
+            model_name='servicegroup',
+            name='group',
+        ),
+        migrations.RemoveField(
+            model_name='servicegroup',
+            name='service',
+        ),
+        migrations.RemoveField(
+            model_name='serviceuser',
+            name='service',
+        ),
+        migrations.RemoveField(
+            model_name='serviceuser',
+            name='user',
+        ),
+        migrations.DeleteModel(
+            name='Service',
+        ),
+        migrations.DeleteModel(
+            name='ServiceGroup',
+        ),
+        migrations.DeleteModel(
+            name='ServiceUser',
+        ),
+    ]
index b0bd496..b0be8d4 100644 (file)
@@ -1,28 +1,6 @@
-from django.conf import settings
-from django.db import models
 from cas_provider.signals import cas_collect_custom_attributes
 
 
-class Service(models.Model):
-    ordering = models.IntegerField()
-    name = models.CharField(max_length=255)
-    url = models.URLField()
-    image = models.ImageField(upload_to='accounts/service/')
-
-    class Meta:
-        ordering = ('ordering', )
-
-
-class ServiceUser(models.Model):
-    service = models.ForeignKey(Service, on_delete=models.CASCADE)
-    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
-
-
-class ServiceGroup(models.Model):
-    service = models.ForeignKey(Service, on_delete=models.CASCADE)
-    group = models.ForeignKey('auth.Group', on_delete=models.CASCADE)
-
-
 def user_attributes(sender, user, **kwargs):
     return {
         'firstname': user.first_name,
index c15aaad..18aa501 100644 (file)
@@ -1,5 +1,6 @@
 {% extends "base.html" %}
 {% load gravatar i18n %}
+{% load use_ssh from services %}
 
 {% block content %}
   <aside class="menu">
       <p><a {% if menu == 'profile' %}class="active"{% endif %} href="{% url 'accounts_profile' %}">{% trans "Your profile" %}</a></p>
       <p><a {% if menu == 'password' %}class="active"{% endif %} href="{% url 'password_change' %}">{% trans "Password change" %}</a></p>
       <!--p><a {% if menu == 'email' %}class="active"{% endif %} href="">{% trans "E-mail" %}</a></p-->
-      <!--p><a {% if menu == 'ssh' %}class="active"{% endif %} href="">{% trans "SSH keys" %}</a></p-->
+      {% use_ssh as use_ssh %}
+      {% if use_ssh %}
+        <p><a {% if menu == 'ssh' %}class="active"{% endif %} href="{% url 'ssh_keys' %}">{% trans "SSH keys" %}</a></p>
+      {% endif %}
     {% endblock %}
     <p><a href="{% url 'cas_logout' %}">{% trans "Logout" %}</a></p>
   </aside>
index 62725c7..d660614 100644 (file)
@@ -84,6 +84,8 @@ LOCALE_PATHS = (
 
 INSTALLED_APPS = (
     'accounts',
+    'services',
+    'ssh_keys',
 
     'cas_provider',
     'django_gravatar',
index be4aa1c..3894e43 100644 (file)
@@ -31,7 +31,7 @@ a:hover {
 }
 
 
-form label, form input, form button {
+label, input, button, textarea {
     display: block;
     width: 100%;
 }
index 297b82b..abe01e8 100644 (file)
@@ -16,6 +16,8 @@ urlpatterns = [
     path('admin/', admin.site.urls),
 
     path('accounts/', include('accounts.urls')),
+    path('services/', include('services.urls')),
+    path('ssh/', include('ssh_keys.urls')),
     path('auth/', include('django.contrib.auth.urls')),
 ]
 
diff --git a/src/services/__init__.py b/src/services/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/services/admin.py b/src/services/admin.py
new file mode 100644 (file)
index 0000000..ae83a8f
--- /dev/null
@@ -0,0 +1,18 @@
+from django.contrib import admin
+from . import models
+
+
+class HookInline(admin.TabularInline):
+    model = models.Hook
+
+
+class ServiceAdmin(admin.ModelAdmin):
+    filter_horizontal = ['groups', 'users']
+    inlines = [
+            HookInline,
+        ]
+
+
+admin.site.register(models.Service, ServiceAdmin)
+
+
diff --git a/src/services/apps.py b/src/services/apps.py
new file mode 100644 (file)
index 0000000..e40a94f
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class ServicesConfig(AppConfig):
+    name = 'services'
diff --git a/src/services/migrations/0001_initial.py b/src/services/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..ce32d07
--- /dev/null
@@ -0,0 +1,45 @@
+# Generated by Django 2.1.7 on 2019-03-30 13:31
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+        ('auth', '0009_alter_user_last_name_max_length'),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='Hook',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('email', models.CharField(max_length=255)),
+            ],
+        ),
+        migrations.CreateModel(
+            name='Service',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('name', models.CharField(max_length=255)),
+                ('url', models.URLField(blank=True)),
+                ('key', models.CharField(blank=True, max_length=255)),
+                ('uses_ssh', models.BooleanField(default=False)),
+                ('groups', models.ManyToManyField(blank=True, to='auth.Group')),
+                ('users', models.ManyToManyField(blank=True, to=settings.AUTH_USER_MODEL)),
+            ],
+            options={
+                'ordering': ('name',),
+            },
+        ),
+        migrations.AddField(
+            model_name='hook',
+            name='service',
+            field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='services.Service'),
+        ),
+    ]
diff --git a/src/services/migrations/__init__.py b/src/services/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/services/models.py b/src/services/models.py
new file mode 100644 (file)
index 0000000..6e945ce
--- /dev/null
@@ -0,0 +1,33 @@
+from django.conf import settings
+from django.contrib.auth.models import User
+from django.db import models
+
+
+class Service(models.Model):
+    name = models.CharField(max_length=255)
+    url = models.URLField(blank=True)
+    key = models.CharField(max_length=255, blank=True)
+    uses_ssh = models.BooleanField(default=False)
+    groups = models.ManyToManyField('auth.Group', blank=True)
+    users = models.ManyToManyField(settings.AUTH_USER_MODEL, blank=True)
+
+    class Meta:
+        ordering = ('name', )
+
+    def __str__(self):
+        return self.name
+
+    def all_users(self):
+        return User.objects.filter(
+                models.Q(service=self) |
+                models.Q(groups__service=self)
+            ).distinct()
+
+    @classmethod
+    def for_user(cls, user):
+        return cls.objects.filter(models.Q(users=user) | models.Q(groups__user=user))
+
+
+class Hook(models.Model):
+    service = models.ForeignKey(Service, on_delete=models.CASCADE)
+    email = models.CharField(max_length=255)
diff --git a/src/services/templates/services/ssh_authorized_keys.txt b/src/services/templates/services/ssh_authorized_keys.txt
new file mode 100644 (file)
index 0000000..835c2e5
--- /dev/null
@@ -0,0 +1,4 @@
+{% for user in service.all_users %}# {{ user }}
+{% for key in user.sshkey_set.all %}{{ key.key }}
+{% endfor %}
+{% endfor %}
diff --git a/src/services/templatetags/__init__.py b/src/services/templatetags/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/services/templatetags/services.py b/src/services/templatetags/services.py
new file mode 100644 (file)
index 0000000..959e0f8
--- /dev/null
@@ -0,0 +1,12 @@
+from django.template import Library
+from ..models import Service
+
+
+register = Library()
+
+
+@register.simple_tag(takes_context=True)
+def use_ssh(context):
+    user = context['request'].user
+    if user.is_anonymous: return True
+    return Service.for_user(user).filter(uses_ssh=True).exists()
diff --git a/src/services/tests.py b/src/services/tests.py
new file mode 100644 (file)
index 0000000..7ce503c
--- /dev/null
@@ -0,0 +1,3 @@
+from django.test import TestCase
+
+# Create your tests here.
diff --git a/src/services/urls.py b/src/services/urls.py
new file mode 100644 (file)
index 0000000..fc34dcc
--- /dev/null
@@ -0,0 +1,7 @@
+from django.urls import path
+from . import views
+
+
+urlpatterns = [
+    path('<int:pk>/ssh/authorized_keys', views.SshAuthorizedKeysView.as_view()),
+]
diff --git a/src/services/views.py b/src/services/views.py
new file mode 100644 (file)
index 0000000..f599592
--- /dev/null
@@ -0,0 +1,15 @@
+from django.views.generic import DetailView
+from .models import Service
+
+
+class SshAuthorizedKeysView(DetailView):
+    model = Service
+    template_name = 'services/ssh_authorized_keys.txt'
+    content_type = 'text/plain'
+
+    def get_object(self):
+        obj = super().get_object()
+        if self.request.GET.get('key') != obj.key:
+            obj = None
+        return obj
+
diff --git a/src/ssh_keys/__init__.py b/src/ssh_keys/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/ssh_keys/admin.py b/src/ssh_keys/admin.py
new file mode 100644 (file)
index 0000000..b907416
--- /dev/null
@@ -0,0 +1,5 @@
+from django.contrib import admin
+from .models import SSHKey
+
+
+admin.site.register(SSHKey)
diff --git a/src/ssh_keys/apps.py b/src/ssh_keys/apps.py
new file mode 100644 (file)
index 0000000..6663b50
--- /dev/null
@@ -0,0 +1,5 @@
+from django.apps import AppConfig
+
+
+class SshKeysConfig(AppConfig):
+    name = 'ssh_keys'
diff --git a/src/ssh_keys/forms.py b/src/ssh_keys/forms.py
new file mode 100644 (file)
index 0000000..f3c0f7c
--- /dev/null
@@ -0,0 +1,6 @@
+from django import forms
+from .models import SSHKey
+
+
+class SSHKeyForm(forms.ModelForm):
+    model = SSHKey
diff --git a/src/ssh_keys/migrations/0001_initial.py b/src/ssh_keys/migrations/0001_initial.py
new file mode 100644 (file)
index 0000000..61ca8ba
--- /dev/null
@@ -0,0 +1,26 @@
+# Generated by Django 2.1.7 on 2019-03-30 13:11
+
+from django.conf import settings
+from django.db import migrations, models
+import django.db.models.deletion
+
+
+class Migration(migrations.Migration):
+
+    initial = True
+
+    dependencies = [
+        migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+    ]
+
+    operations = [
+        migrations.CreateModel(
+            name='SSHKey',
+            fields=[
+                ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
+                ('key', models.TextField()),
+                ('created_at', models.DateTimeField(auto_now_add=True)),
+                ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
+            ],
+        ),
+    ]
diff --git a/src/ssh_keys/migrations/__init__.py b/src/ssh_keys/migrations/__init__.py
new file mode 100644 (file)
index 0000000..e69de29
diff --git a/src/ssh_keys/models.py b/src/ssh_keys/models.py
new file mode 100644 (file)
index 0000000..960a5ec
--- /dev/null
@@ -0,0 +1,11 @@
+from django.conf import settings
+from django.db import models
+
+
+class SSHKey(models.Model):
+    user = models.ForeignKey(settings.AUTH_USER_MODEL, on_delete=models.CASCADE)
+    key = models.TextField()
+    created_at = models.DateTimeField(auto_now_add=True)
+
+    class Meta:
+        ordering = ['created_at']
diff --git a/src/ssh_keys/templates/ssh_keys/base.html b/src/ssh_keys/templates/ssh_keys/base.html
new file mode 100644 (file)
index 0000000..2e21241
--- /dev/null
@@ -0,0 +1,8 @@
+{% extends "account/base.html" %}
+
+
+{% block menu %}
+  {% with menu='ssh' %}
+    {{ block.super }}
+  {% endwith %}
+{% endblock %}
diff --git a/src/ssh_keys/templates/ssh_keys/sshkey_add.html b/src/ssh_keys/templates/ssh_keys/sshkey_add.html
new file mode 100644 (file)
index 0000000..1b67d03
--- /dev/null
@@ -0,0 +1,15 @@
+{% extends 'ssh_keys/base.html' %}
+{% load i18n %}
+
+
+{% block accounts-content %}
+<h1>{% trans "Add SSH key" %}</h1>
+
+<form method='post'>
+  {% csrf_token %}
+  {{ form.as_p }}
+  <button type='submit'>
+    {% trans "Add" %}
+  </button>
+</form>
+{% endblock %}
diff --git a/src/ssh_keys/templates/ssh_keys/sshkey_confirm_delete.html b/src/ssh_keys/templates/ssh_keys/sshkey_confirm_delete.html
new file mode 100644 (file)
index 0000000..7f3d109
--- /dev/null
@@ -0,0 +1,20 @@
+{% extends "ssh_keys/base.html" %}
+{% load i18n %}
+
+
+{% block accounts-content %}
+<h1>{% trans "Confirm deleting an SSH key" %}</h1>
+
+<p>Are you sure you want to delete this key?</p>
+
+<form method="post">
+  {% csrf_token %}
+  <p>
+    <code class="key">{{ object.key }}</code><br>
+    {% trans "Added at" %} {{ object.created_at }}.
+    </p>
+  <button>
+    {% trans "Delete" %}
+  </button>
+</form>
+{% endblock %}
diff --git a/src/ssh_keys/templates/ssh_keys/sshkey_list.html b/src/ssh_keys/templates/ssh_keys/sshkey_list.html
new file mode 100644 (file)
index 0000000..a4f5155
--- /dev/null
@@ -0,0 +1,30 @@
+{% extends "account/base.html" %}
+{% load i18n %}
+
+
+{% block menu %}
+  {% with menu="ssh" %}
+    {{ block.super }}
+  {% endwith %}
+{% endblock %}
+
+
+{% block accounts-content %}
+  <h1>{% trans "SSH keys" %}</h1>
+
+  {% for key in object_list %}
+<p>
+  <code style='word-wrap:break-word'>{{ key.key }}</code><br>
+  {% trans "Added at" %} {{ key.created_at }}<br>
+  <a href="{% url 'ssh_keys_delete' key.id %}">
+    {% trans "Delete" %}
+  </a>
+</p>
+<hr>
+  {% endfor %}
+
+  <a href="{% url 'ssh_keys_add' %}">
+    {% trans "Add" %}
+  </a>
+
+{% endblock %}
diff --git a/src/ssh_keys/urls.py b/src/ssh_keys/urls.py
new file mode 100644 (file)
index 0000000..deabb5c
--- /dev/null
@@ -0,0 +1,10 @@
+from django.urls import path
+from . import views
+
+
+urlpatterns = [
+    path('', views.SSHKeysView.as_view(), name='ssh_keys'),
+    path('<int:pk>/delete/', views.DeleteSSHKeyView.as_view(), name='ssh_keys_delete'),
+    path('add/', views.AddSSHKeyView.as_view(), name='ssh_keys_add'),
+
+]
diff --git a/src/ssh_keys/views.py b/src/ssh_keys/views.py
new file mode 100644 (file)
index 0000000..684dcf6
--- /dev/null
@@ -0,0 +1,27 @@
+from django.contrib.auth.mixins import LoginRequiredMixin
+from django.views.generic import ListView, CreateView, DeleteView
+from .models import SSHKey
+
+
+class SSHKeysView(LoginRequiredMixin, ListView):
+    def get_queryset(self):
+        return SSHKey.objects.filter(user=self.request.user)
+
+
+class AddSSHKeyView(LoginRequiredMixin, CreateView):
+    fields = ['key']
+    model = SSHKey
+    success_url = '/ssh/'
+    template_name = 'ssh_keys/sshkey_add.html'
+
+    def form_valid(self, form):
+        form.instance.user = self.request.user
+        return super().form_valid(form)
+
+    
+class DeleteSSHKeyView(LoginRequiredMixin, DeleteView):
+    success_url = '/ssh/'
+
+    def get_queryset(self):
+        return SSHKey.objects.filter(user=self.request.user)
+