3 Commits

Author SHA1 Message Date
  alfred 5edefd86e7 Refactor for starting with authorization methods 1 month ago
  alfred 92190825f0 Basic CRUD for authentication methods 1 month ago
  alfred d3a19790c6 Update project 2 months ago

+ 6
- 0
apps/authentication/__init__.py View File

@@ -0,0 +1,6 @@
1
+from apps.authentication import authenticators
2
+
3
+auth_methods = {
4
+    'passwordless': authenticators.PasswordlessAuthenticator,
5
+    'user_password': authenticators.UserPasswordAuthenticator
6
+}

+ 18
- 0
apps/authentication/authenticators.py View File

@@ -0,0 +1,18 @@
1
+from apps.authentication import models, forms, mixins
2
+from apps.base.classes import DJaWThObject
3
+
4
+
5
+class Authenticator(DJaWThObject):
6
+    pass
7
+
8
+
9
+class PasswordlessAuthenticator(mixins.PasswordlessMixin, Authenticator):
10
+    alias = 'Passwordless'
11
+    model = models.PasswordlessModel
12
+    template = 'authentication/passwordless.html'
13
+    help = 'authentication/passwordless-help.html'
14
+    form = forms.PasswordlessForm
15
+
16
+
17
+class UserPasswordAuthenticator(Authenticator):
18
+    alias = 'User - Password'

+ 4
- 3
apps/authentication/forms.py View File

@@ -1,5 +1,6 @@
1
-from django import forms
1
+from apps.base.forms import DataclassForm
2
+from .models import PasswordlessModel
2 3
 
3 4
 
4
-class PasswordlessForm(forms.Form):
5
-    pass
5
+class PasswordlessForm(DataclassForm):
6
+    dataclass = PasswordlessModel

+ 1
- 0
apps/authentication/mixins/__init__.py View File

@@ -0,0 +1 @@
1
+from .passwordless import PasswordlessMixin

+ 2
- 0
apps/authentication/mixins/passwordless.py View File

@@ -0,0 +1,2 @@
1
+class PasswordlessMixin:
2
+    pass

+ 5
- 42
apps/authentication/models.py View File

@@ -1,45 +1,8 @@
1
-from dataclasses import dataclass
2
-from apps.authentication import forms
1
+from dataclasses import dataclass, field
3 2
 
4 3
 
5 4
 @dataclass
6
-class Passwordless:
7
-    one_use_access: bool
8
-    web_hook_base: str
9
-    access_parameter: str
10
-
11
-
12
-class Authenticator:
13
-    alias = ''
14
-    model = None
15
-    default = None
16
-    template = ''
17
-    form = None
18
-
19
-    def __init__(self, **kwargs):
20
-        self.data = self.model(**kwargs) if kwargs else self.default
21
-
22
-    def __str__(self):
23
-        return self.alias
24
-
25
-
26
-class PasswordlessAuthenticator(Authenticator):
27
-    alias = 'Passwordless'
28
-    model = Passwordless
29
-    template = 'authentication/passwordless.html'
30
-    form = forms.PasswordlessForm
31
-    default = Passwordless(
32
-        one_use_access=True,
33
-        web_hook_base='https://example.com/auth',
34
-        access_parameter='access'
35
-    )
36
-
37
-
38
-class UserPasswordAuthenticator(Authenticator):
39
-    alias = 'User - Password'
40
-
41
-
42
-auth_methods = {
43
-    'passwordless': PasswordlessAuthenticator,
44
-    'user_password': UserPasswordAuthenticator
45
-}
5
+class PasswordlessModel:
6
+    one_use_access: bool = field(default=True, metadata={'label': 'One use access'})
7
+    web_hook_base: str = field(default='https://example.com/authenticate', metadata={'label': 'Webhook'})
8
+    access_parameter: str = field(default='access', metadata={'label': 'Access parameter name'})

+ 5
- 0
apps/authentication/templates/authentication/passwordless-help.html View File

@@ -0,0 +1,5 @@
1
+<ul class="enumeration">
2
+    <li><span class="emphasize">One use access</span>: Sets if the access key should be reset after its use.</li>
3
+    <li><span class="emphasize">Webhook base</span>: Sets which URL to call for telling your platform to get the access token.</li>
4
+    <li><span class="emphasize">Access parameter</span>: Sets the parameter name for the access key.</li>
5
+</ul>

+ 19
- 0
apps/authentication/templates/authentication/passwordless.html View File

@@ -0,0 +1,19 @@
1
+<div class="row">
2
+    <div class="col s12" style="display: flex;">
3
+    <label>One use access: </label>
4
+        {{ form.one_use_access }}
5
+    </div>
6
+</div>
7
+
8
+<div class="row">
9
+    <div class="col s9">
10
+        <label>Webhook base:</label>
11
+        {{ form.web_hook_base }}
12
+    </div>
13
+    <div class="col s3">
14
+        <label>Access parameter:</label>
15
+        <div style="display: flex; flex-direction: row;">
16
+            <div class="character">?</div><div>{{ form.access_parameter }}</div><div class="character">=ACCESS_KEY</div>
17
+        </div>
18
+    </div>
19
+</div>

+ 5
- 0
apps/authorization/__init__.py View File

@@ -0,0 +1,5 @@
1
+from apps.authorization import authorizers as auth
2
+
3
+authorization_methods = {
4
+    'jwt': auth.JwtAuthorizer
5
+}

+ 5
- 0
apps/authorization/apps.py View File

@@ -0,0 +1,5 @@
1
+from django.apps import AppConfig
2
+
3
+
4
+class AuthorizationConfig(AppConfig):
5
+    name = 'authorization'

+ 9
- 0
apps/authorization/authorizers.py View File

@@ -0,0 +1,9 @@
1
+from apps.base.classes import DJaWThObject
2
+
3
+
4
+class Authorizer(DJaWThObject):
5
+    pass
6
+
7
+
8
+class JwtAuthorizer(Authorizer):
9
+    pass

+ 0
- 0
apps/authorization/forms.py View File


+ 0
- 0
apps/authorization/models.py View File


+ 18
- 0
apps/base/classes.py View File

@@ -0,0 +1,18 @@
1
+from dataclasses import asdict
2
+
3
+
4
+class DJaWThObject:
5
+    alias = ''
6
+    model = None
7
+    template = ''
8
+    help = ''
9
+    form = None
10
+
11
+    def __init__(self, **kwargs):
12
+        self.data = self.model(**kwargs)
13
+
14
+    def get_dict(self):
15
+        return asdict(self.data)
16
+
17
+    def __str__(self):
18
+        return self.alias

+ 43
- 0
apps/base/forms.py View File

@@ -0,0 +1,43 @@
1
+from django import forms
2
+import dataclasses
3
+
4
+
5
+field_types = {
6
+    str: {
7
+        'field': forms.CharField,
8
+        'parameters': {}
9
+    },
10
+    bool: {
11
+        'field': forms.BooleanField,
12
+        'parameters': {
13
+            'required': False,
14
+            'widget': forms.CheckboxInput(
15
+                attrs={'style': 'opacity: 1; pointer-events: auto; position: inherit; margin-left: 10px;'}
16
+            )
17
+        }
18
+    }
19
+}
20
+
21
+
22
+class DataclassForm(forms.BaseForm):
23
+    dataclass = None
24
+    instance = None
25
+
26
+    def __init__(self, *args, **kwargs):
27
+        self.base_fields = {}
28
+        self.generate_fields()
29
+        super().__init__(*args, **kwargs)
30
+
31
+    def generate_fields(self):
32
+        dataclass = self.dataclass
33
+        for field in dataclasses.fields(dataclass):
34
+            if hasattr(self, field.name) and issubclass(type(getattr(self, field.name)), forms.Field):
35
+                self.base_fields[field.name] = getattr(self, field.name)
36
+                continue
37
+            f = field_types.get(field.type)
38
+            field_cls = f.get('field')
39
+            parameters = {
40
+                **f.get('parameters', {}),
41
+                **field.metadata
42
+            }
43
+            self.base_fields[field.name] = field_cls(**parameters)

+ 28
- 5
apps/base/static/styles/scss/stylesheet.scss View File

@@ -6,7 +6,6 @@
6 6
 
7 7
 body {
8 8
     background-color: $primary-background-color;
9
-    min-height: 100vh;
10 9
 }
11 10
 
12 11
 header {
@@ -23,15 +22,13 @@ header {
23 22
 
24 23
 h2 {
25 24
     font-size: 2rem;
26
-    text-decoration: underline;
25
+    //text-decoration: underline;
27 26
     font-family: Rubik, serif;
28 27
     padding: 0;
29 28
     margin: 1.5rem 0;
30 29
 }
31 30
 
32 31
 footer {
33
-    position: fixed;
34
-    bottom: 0;
35 32
     width: 100%;
36 33
     background-color: $alternative-background-color;
37 34
     min-height: 20px;
@@ -80,7 +77,7 @@ select {
80 77
     padding: 25px;
81 78
     border-radius: 0 25px 25px 0;
82 79
 
83
-    table, thead, tbody, tr, th, td {
80
+    table, thead, tbody, tr, th, td, select {
84 81
         color: $primary-background-color;
85 82
     }
86 83
 
@@ -91,4 +88,30 @@ select {
91 88
         align-items: center;
92 89
         padding-left: 25px;
93 90
     }
91
+
92
+    .character {
93
+        display: flex;
94
+        color: $primary-background-color;
95
+        font-weight: bold;
96
+        padding: 0 10px;
97
+        align-items: center;
98
+    }
99
+}
100
+
101
+.after-content {
102
+    margin-top: 50px;
103
+
104
+    .emphasize {
105
+        font-weight: bold;
106
+    }
107
+
108
+    .enumeration {
109
+        list-style: circle;
110
+     
111
+        li {
112
+            list-style: circle !important;
113
+            margin: 10px 0 0 20px;
114
+            padding-left: 5px;
115
+        }
116
+    }
94 117
 }

+ 19
- 6
apps/base/static/styles/stylesheet.css View File

@@ -6455,8 +6455,7 @@ input[type=range]::-ms-thumb {
6455 6455
   color: #D8D9DB; }
6456 6456
 
6457 6457
 body {
6458
-  background-color: #1F2439;
6459
-  min-height: 100vh; }
6458
+  background-color: #1F2439; }
6460 6459
 
6461 6460
 header {
6462 6461
   background-color: #2E334A;
@@ -6470,14 +6469,11 @@ header {
6470 6469
 
6471 6470
 h2 {
6472 6471
   font-size: 2rem;
6473
-  text-decoration: underline;
6474 6472
   font-family: Rubik, serif;
6475 6473
   padding: 0;
6476 6474
   margin: 1.5rem 0; }
6477 6475
 
6478 6476
 footer {
6479
-  position: fixed;
6480
-  bottom: 0;
6481 6477
   width: 100%;
6482 6478
   background-color: #BA7C10;
6483 6479
   min-height: 20px; }
@@ -6513,7 +6509,7 @@ select {
6513 6509
   background-color: #EEE;
6514 6510
   padding: 25px;
6515 6511
   border-radius: 0 25px 25px 0; }
6516
-  .content table, .content thead, .content tbody, .content tr, .content th, .content td {
6512
+  .content table, .content thead, .content tbody, .content tr, .content th, .content td, .content select {
6517 6513
     color: #1F2439; }
6518 6514
   .content .no-projects {
6519 6515
     display: flex;
@@ -6521,5 +6517,22 @@ select {
6521 6517
     color: #333;
6522 6518
     align-items: center;
6523 6519
     padding-left: 25px; }
6520
+  .content .character {
6521
+    display: flex;
6522
+    color: #1F2439;
6523
+    font-weight: bold;
6524
+    padding: 0 10px;
6525
+    align-items: center; }
6526
+
6527
+.after-content {
6528
+  margin-top: 50px; }
6529
+  .after-content .emphasize {
6530
+    font-weight: bold; }
6531
+  .after-content .enumeration {
6532
+    list-style: circle; }
6533
+    .after-content .enumeration li {
6534
+      list-style: circle !important;
6535
+      margin: 10px 0 0 20px;
6536
+      padding-left: 5px; }
6524 6537
 
6525 6538
 /*# sourceMappingURL=stylesheet.css.map */

+ 5
- 1
apps/base/templates/base.html View File

@@ -29,11 +29,12 @@
29 29
         </div>
30 30
         {% if user.is_authenticated %}
31 31
         <div>
32
-            <a href="{% url 'logout' %}">Logout</a>
32
+            <a href="{% url 'home' %}">My projects</a> | <a href="{% url 'logout' %}">Logout</a>
33 33
         </div>
34 34
         {% endif %}
35 35
         {% endblock %}
36 36
     </header>
37
+    <div style="min-height: 100vh; margin-bottom: 50px;">
37 38
     {% block precontent %}{% endblock %}
38 39
     <div class="container">
39 40
         {% block container %}
@@ -42,9 +43,12 @@
42 43
             {% block content %}
43 44
             {% endblock %}
44 45
             </div>
46
+            {% block after-content %}
47
+            {% endblock %}
45 48
         {% endblock %}
46 49
     </div>
47 50
   {% endblock %}
51
+    </div>
48 52
   <footer>
49 53
   </footer>
50 54
   </body>

+ 7
- 1
apps/project/forms.py View File

@@ -1,6 +1,6 @@
1 1
 from django import forms
2 2
 from apps.project.models import Project
3
-from apps.authentication.models import auth_methods
3
+from apps.authentication import auth_methods
4 4
 
5 5
 
6 6
 class ConfirmProjectForm(forms.ModelForm):
@@ -10,3 +10,9 @@ class ConfirmProjectForm(forms.ModelForm):
10 10
         exclude = ['auth_data']
11 11
 
12 12
     auth_type = forms.ChoiceField(choices=[(key, auth.alias) for key, auth in auth_methods.items()])
13
+
14
+
15
+class EditProjectForm(forms.ModelForm):
16
+    class Meta:
17
+        model = Project
18
+        fields = ['name', 'secret']

+ 2
- 1
apps/project/models.py View File

@@ -1,6 +1,6 @@
1 1
 from django.db import models
2 2
 from apps.base import models as base_models
3
-from apps.authentication.models import auth_methods
3
+from apps.authentication import auth_methods
4 4
 from django.utils.text import slugify
5 5
 from django.contrib.postgres.fields import JSONField
6 6
 
@@ -59,4 +59,5 @@ class Credential(models.Model):
59 59
 
60 60
 class Access(models.Model):
61 61
     identity = models.ForeignKey(Identity, on_delete=models.CASCADE, related_name='history')
62
+    action = models.CharField(max_length=100, blank=True, null=True)
62 63
     date = models.DateTimeField(auto_now_add=True)

+ 9
- 1
apps/project/templates/projects/confirm_project.html View File

@@ -19,11 +19,19 @@
19 19
 <input type="hidden" name="secret" value="{{ form.secret.value }}" />
20 20
 <input type="hidden" name="auth_type" value="{{ form.auth_type.value }}">
21 21
 <div class="row">
22
-    Identifier: <input type="text" name="identifier" value="{{ form.identifier.value }}">
22
+    <label for="identifier">Identifier</label>
23
+    <input type="text" name="identifier" id="identifier" value="{{ form.identifier.value }}" placeholder="Slug to identify the project">
23 24
 </div>
24 25
 <div class="row">
25 26
     <input type="submit" class="btn" value="Confirm">
26 27
 </div>
27 28
 </form>
29
+{% endblock %}
28 30
 
31
+{% block after-content %}
32
+<div class="after-content">
33
+    <p>
34
+        This identifier will be part of the url for accessing to your project data inside the D-JaWTh platform.
35
+    </p>
36
+</div>
29 37
 {% endblock %}

+ 23
- 5
apps/project/templates/projects/create_project.html View File

@@ -8,14 +8,16 @@
8 8
 <form method="post" action="{% url 'confirm_project' %}">
9 9
 {% csrf_token %}
10 10
 <div class="row">
11
-    Name: <input type="text" name="name">
11
+    <label for="name">Name</label>
12
+    <input type="text" name="name" id="name" placeholder="Project name">
12 13
 </div>
13 14
 <div class="row">
14
-    Secret: <input type="text" name="secret">
15
+    <label for="secret">Secret</label>
16
+    <input type="text" name="secret" id="secret" placeholder="Secret word to encrypt interactions">
15 17
 </div>
16 18
 <div class="row">
17
-    Authentication type:
18
-    <select name="auth_type">
19
+    <label for="auth_type">Authentication type</label>
20
+    <select name="auth_type" id="auth_type">
19 21
         {% for key, value in auth_methods.items %}
20 22
         <option value="{{ key }}">{{ value }}</option>
21 23
         {% endfor %}
@@ -23,7 +25,23 @@
23 25
 </div>
24 26
 <div class="row">
25 27
     <input type="submit" class="btn" value="Create">
26
-<//div>
28
+</div>
27 29
 </form>
30
+{% endblock %}
28 31
 
32
+{% block after-content %}
33
+<div class="after-content">
34
+    <p>You need to give your project a name to identify it.</p>
35
+    <p>Also it's required to set a secret key. That will be used to encrypt communication between D-JaWTh and your platform.</p>
36
+    <p>The authentication type will allow you to assign a way to authorize your users to your platform:</p>
37
+    <ul class="enumeration">
38
+        <li>
39
+            <span class="emphasize">Passwordless</span>: An access key will be sent to the user email address when the login is required. With this
40
+            key your user can request the access token.
41
+        </li>
42
+        <li>
43
+            <span class="emphasize">User - login</span>: After your user gives the right username and password the access token will be provided.
44
+        </li>
45
+    </ul>
46
+</div>
29 47
 {% endblock %}

+ 19
- 0
apps/project/templates/projects/edit_authentication.html View File

@@ -0,0 +1,19 @@
1
+{% extends "base.html" %}
2
+
3
+{% block title %}
4
+<h2>Edit {{ object.name }} {{ auth.alias | lower }} authentication</h2>
5
+{% endblock %}
6
+
7
+{% block content %}
8
+<form method="post" action="{% url 'edit_authentication' identifier=object.identifier %}">
9
+{% csrf_token %}
10
+{% include auth.template %}
11
+<input type="submit" class="btn" value="Update">
12
+</form>
13
+{% endblock %}
14
+
15
+{% block after-content %}
16
+<div class="after-content">
17
+{% include auth.help %}
18
+</div>
19
+{% endblock %}

+ 9
- 0
apps/project/templates/projects/project.html View File

@@ -1,4 +1,13 @@
1 1
 {% extends "base.html" %}
2 2
 
3
+{% block title %}
4
+<h2>{{ object.name }}</h2>
5
+{% endblock %}
6
+
3 7
 {% block content %}
8
+<form method="post" action="{% url 'project' identifier=object.identifier %}">
9
+{% csrf_token %}
10
+{{ form  }}
11
+<input type="submit" class="btn" value="Update">
12
+</form>
4 13
 {% endblock %}

+ 2
- 2
apps/project/templates/projects/projects_list.html View File

@@ -22,8 +22,8 @@ No projects
22 22
     <tbody>
23 23
     {% for project in user.projects.all %}
24 24
     <tr>
25
-        <td><a href="#!" style="display: inline-block; font-weight: bold; width: 100%;">{{ project.name }}</a></td>
26
-        <td><a class="btn-flat">{{ project.authentication }}</a></td>
25
+        <td><a href="{% url 'project' identifier=project.identifier %}" style="display: inline-block; font-weight: bold; width: 100%;">{{ project.name }}</a></td>
26
+        <td><a href="{% url 'edit_authentication' identifier=project.identifier %}" class="btn-flat">{{ project.authentication }}</a></td>
27 27
         <td><a class="btn-flat">JWT</a></td>
28 28
     </tr>
29 29
     {% endfor %}

+ 30
- 9
apps/project/urls.py View File

@@ -1,26 +1,47 @@
1 1
 #!/usr/bin/env python
2 2
 # -*- coding: utf-8 -*-
3 3
 
4
-from django.urls import path
5
-from django.views.generic import TemplateView
4
+from django.urls import path, reverse_lazy
5
+from django.views.generic import TemplateView, UpdateView
6 6
 from django.contrib.auth.decorators import login_required
7
-from .views import ConfirmProjectView
8
-from apps.authentication.models import auth_methods
7
+from .views import ConfirmProjectView, UpdateAuthenticationView
8
+from .models import Project
9
+from apps.authentication import auth_methods
10
+from .forms import EditProjectForm, ConfirmProjectForm
9 11
 
10 12
 
11 13
 urlpatterns = [
12 14
     path('home', login_required(TemplateView.as_view(
13 15
         template_name='projects/projects_list.html'
14 16
     )), name='home'),
17
+
15 18
     path('new_project', login_required(TemplateView.as_view(
16 19
         template_name='projects/create_project.html',
17 20
         extra_context={
18 21
             'auth_methods': lambda: {key: value.alias for key, value in auth_methods.items()}
19 22
         }
20 23
     )), name='new_project'),
21
-    path('confirm_project', login_required(ConfirmProjectView.as_view()), name='confirm_project'),
22
-    # path('<project>/authentication/<slug>', login_required(UpdateView(
23
-    # )), name='edit_authentication'),
24
-    # path('edit_project/<slug>', login_required(UpdateView(
25
-    # )), name='project'),
24
+
25
+    path('confirm_project', login_required(ConfirmProjectView.as_view(
26
+        template_name='projects/confirm_project.html',
27
+        success_url='home',
28
+        form_class=ConfirmProjectForm
29
+    )), name='confirm_project'),
30
+
31
+    path('<identifier>/authentication', login_required(UpdateAuthenticationView.as_view(
32
+        model=Project,
33
+        slug_field='identifier',
34
+        slug_url_kwarg='identifier',
35
+        template_name='projects/edit_authentication.html',
36
+        success_url=reverse_lazy('home')
37
+    )), name='edit_authentication'),
38
+
39
+    path('<identifier>', login_required(UpdateView.as_view(
40
+        model=Project,
41
+        slug_field='identifier',
42
+        slug_url_kwarg='identifier',
43
+        form_class=EditProjectForm,
44
+        template_name='projects/project.html',
45
+        success_url=reverse_lazy('home')
46
+    )), name='project'),
26 47
 ]

+ 36
- 7
apps/project/views.py View File

@@ -1,13 +1,8 @@
1
-from django.views.generic import CreateView
2
-from django.http import QueryDict
3
-from .forms import ConfirmProjectForm
1
+from django.views.generic import CreateView, UpdateView
2
+from django.http import QueryDict, HttpResponseRedirect
4 3
 
5 4
 
6 5
 class ConfirmProjectView(CreateView):
7
-    template_name = 'projects/confirm_project.html'
8
-    success_url = 'home'
9
-    form_class = ConfirmProjectForm
10
-
11 6
     def get_form_kwargs(self):
12 7
         from .models import Project
13 8
         kwargs = super().get_form_kwargs()
@@ -30,3 +25,37 @@ class ConfirmProjectView(CreateView):
30 25
 
31 26
     def form_invalid(self, form):
32 27
         return super().form_invalid(form)
28
+
29
+
30
+class UpdateAuthenticationView(UpdateView):
31
+    authentication = None
32
+
33
+    def get_authentication(self):
34
+        if self.authentication is None:
35
+            project = self.get_object()
36
+            self.authentication = project.authentication
37
+        return self.authentication
38
+
39
+    def get_form_class(self):
40
+        return self.get_authentication().form
41
+
42
+    def get_form_kwargs(self):
43
+        sent_data = self.request.POST if self.request.method in ('POST', 'PUT') else None
44
+        return {
45
+            'data': sent_data if sent_data is not None else self.get_authentication().get_dict()
46
+        }
47
+
48
+    def form_invalid(self, form):
49
+        return super().form_invalid(form)
50
+
51
+    def form_valid(self, form):
52
+        project = self.get_object()
53
+        project.auth_data = form.cleaned_data
54
+        project.save()
55
+        return HttpResponseRedirect(self.get_success_url())
56
+
57
+    def get_context_data(self, **kwargs):
58
+        return {
59
+            **super().get_context_data(**kwargs),
60
+            'auth': self.get_authentication()
61
+        }

+ 1
- 1
djawth/settings/dev.py View File

@@ -34,7 +34,7 @@ ALLOWED_HOSTS = []
34 34
 INSTALLED_APPS = [
35 35
     'apps.base',
36 36
     'apps.project',
37
-    # 'apps.authentication',
37
+    'apps.authentication',
38 38
     'django.contrib.auth',
39 39
     'django.contrib.contenttypes',
40 40
     'django.contrib.sessions',

+ 1
- 0
requirements.txt View File

@@ -1,5 +1,6 @@
1 1
 asgiref==3.2.3
2 2
 Django==3.0.1
3 3
 psycopg2-binary==2.8.4
4
+PyJWT==1.7.1
4 5
 pytz==2019.3
5 6
 sqlparse==0.3.0

Loading…
Cancel
Save