Compare commits
	
		
			16 Commits
		
	
	
		
			version/0.
			...
			version/0.
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 250b6691d4 | |||
| e3b02a6e78 | |||
| e94ef34d8f | |||
| 49e945307a | |||
| edfe0e5450 | |||
| 06b65a7882 | |||
| ff9bc8aa70 | |||
| 28da67abe6 | |||
| 39d9fe9bf0 | |||
| 750117b0fd | |||
| 983462f80d | |||
| 4ae31d409b | |||
| 98b414f3e2 | |||
| a0d42092e3 | |||
| f2569b6424 | |||
| 9d344d887c | 
| @ -1,5 +1,5 @@ | |||||||
| [bumpversion] | [bumpversion] | ||||||
| current_version = 0.0.8-alpha | current_version = 0.0.10-alpha | ||||||
| tag = True | tag = True | ||||||
| commit = True | commit = True | ||||||
| parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) | parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-(?P<release>.*) | ||||||
|  | |||||||
| @ -52,9 +52,9 @@ package-docker: | |||||||
|     name: gcr.io/kaniko-project/executor:debug |     name: gcr.io/kaniko-project/executor:debug | ||||||
|     entrypoint: [""] |     entrypoint: [""] | ||||||
|   before_script: |   before_script: | ||||||
|     - echo "{\"auths\":{\"https://docker.$NEXUS_URL/\":{\"username\":\"$NEXUS_USER\",\"password\":\"$NEXUS_PASS\"}}}" > /kaniko/.docker/config.json |     - echo "{\"auths\":{\"docker.$NEXUS_URL\":{\"auth\":\"$NEXUS_AUTH\"}}}" > /kaniko/.docker/config.json | ||||||
|   script: |   script: | ||||||
|     - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.pkg.beryju.org/passbook:latest --destination docker.pkg.beryju.org/passbook:0.0.8-alpha |     - /kaniko/executor --context $CI_PROJECT_DIR --dockerfile $CI_PROJECT_DIR/Dockerfile --destination docker.pkg.beryju.org/passbook:latest --destination docker.pkg.beryju.org/passbook:0.0.10-alpha | ||||||
|   stage: build |   stage: build | ||||||
|   only: |   only: | ||||||
|     - tags |     - tags | ||||||
| @ -65,7 +65,7 @@ package-helm: | |||||||
|     - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash |     - curl https://raw.githubusercontent.com/helm/helm/master/scripts/get | bash | ||||||
|     - helm init --client-only |     - helm init --client-only | ||||||
|     - helm package helm/passbook |     - helm package helm/passbook | ||||||
|     - ./manage.py nexus_upload --method put --url $NEXUS_URL --user $NEXUS_USER --password $NEXUS_PASS --repo helm *.tgz |     - ./manage.py nexus_upload --method put --url $NEXUS_URL --auth $NEXUS_AUTH --repo helm *.tgz | ||||||
|   only: |   only: | ||||||
|     - tags |     - tags | ||||||
|     - /^version/.*$/ |     - /^version/.*$/ | ||||||
|  | |||||||
| @ -1,6 +1,6 @@ | |||||||
| apiVersion: v1 | apiVersion: v1 | ||||||
| appVersion: "0.0.8-alpha" | appVersion: "0.0.10-alpha" | ||||||
| description: A Helm chart for passbook. | description: A Helm chart for passbook. | ||||||
| name: passbook | name: passbook | ||||||
| version: 1.0.0 | version: "0.0.10-alpha" | ||||||
| icon: https://passbook.beryju.org/images/logo.png | icon: https://passbook.beryju.org/images/logo.png | ||||||
|  | |||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook""" | """passbook""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook admin""" | """passbook admin""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
| @ -137,5 +137,43 @@ | |||||||
|             </div> |             </div> | ||||||
|         </div> |         </div> | ||||||
|     </div> |     </div> | ||||||
|  |     <div class="col-xs-6 col-sm-2 col-md-2"> | ||||||
|  |         <div class="card-pf card-pf-accented card-pf-aggregate-status"> | ||||||
|  |             <h2 class="card-pf-title"> | ||||||
|  |                 <a href="#"> | ||||||
|  |                     <span class="pficon-bundle"></span> | ||||||
|  |                     <span class="card-pf-aggregate-status-count"></span> {% trans 'Version' %} | ||||||
|  |                 </a> | ||||||
|  |             </h2> | ||||||
|  |             <div class="card-pf-body"> | ||||||
|  |                 <p class="card-pf-aggregate-status-notifications"> | ||||||
|  |                     <span class="card-pf-aggregate-status-notification"> | ||||||
|  |                         <a href="#"> | ||||||
|  |                             {{ version }} | ||||||
|  |                         </a> | ||||||
|  |                     </span> | ||||||
|  |                 </p> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
|  |     <div class="col-xs-6 col-sm-2 col-md-2"> | ||||||
|  |         <div class="card-pf card-pf-accented card-pf-aggregate-status"> | ||||||
|  |             <h2 class="card-pf-title"> | ||||||
|  |                 <a href="#"> | ||||||
|  |                     <span class="pficon-server"></span> | ||||||
|  |                     <span class="card-pf-aggregate-status-count"></span> {% trans 'Worker(s)' %} | ||||||
|  |                 </a> | ||||||
|  |             </h2> | ||||||
|  |             <div class="card-pf-body"> | ||||||
|  |                 <p class="card-pf-aggregate-status-notifications"> | ||||||
|  |                     <span class="card-pf-aggregate-status-notification"> | ||||||
|  |                         <a href="#"> | ||||||
|  |                             <span class="pficon pficon-ok"></span>{{ worker_count }} | ||||||
|  |                         </a> | ||||||
|  |                     </span> | ||||||
|  |                 </p> | ||||||
|  |             </div> | ||||||
|  |         </div> | ||||||
|  |     </div> | ||||||
| </div> | </div> | ||||||
| {% endblock %} | {% endblock %} | ||||||
|  | |||||||
| @ -31,6 +31,8 @@ | |||||||
|                         href="{% url 'passbook_admin:user-update' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a> |                         href="{% url 'passbook_admin:user-update' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Edit' %}</a> | ||||||
|                     <a class="btn btn-default btn-sm" |                     <a class="btn btn-default btn-sm" | ||||||
|                         href="{% url 'passbook_admin:user-delete' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a> |                         href="{% url 'passbook_admin:user-delete' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Delete' %}</a> | ||||||
|  |                     <a class="btn btn-default btn-sm" | ||||||
|  |                         href="{% url 'passbook_admin:user-password-reset' pk=user.pk %}?back={{ request.get_full_path }}">{% trans 'Reset Password' %}</a> | ||||||
|                 </td> |                 </td> | ||||||
|             </tr> |             </tr> | ||||||
|             {% endfor %} |             {% endfor %} | ||||||
|  | |||||||
| @ -56,6 +56,8 @@ urlpatterns = [ | |||||||
|          users.UserUpdateView.as_view(), name='user-update'), |          users.UserUpdateView.as_view(), name='user-update'), | ||||||
|     path('users/<int:pk>/delete/', |     path('users/<int:pk>/delete/', | ||||||
|          users.UserDeleteView.as_view(), name='user-delete'), |          users.UserDeleteView.as_view(), name='user-delete'), | ||||||
|  |     path('users/<int:pk>/reset/', | ||||||
|  |          users.UserPasswordResetView.as_view(), name='user-password-reset'), | ||||||
|     # Audit Log |     # Audit Log | ||||||
|     path('audit/', audit.AuditEntryListView.as_view(), name='audit-log'), |     path('audit/', audit.AuditEntryListView.as_view(), name='audit-log'), | ||||||
|     # Groups |     # Groups | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| """passbook Application administration""" | """passbook Application administration""" | ||||||
|  | from django.contrib import messages | ||||||
| from django.contrib.messages.views import SuccessMessageMixin | from django.contrib.messages.views import SuccessMessageMixin | ||||||
| from django.urls import reverse_lazy | from django.urls import reverse_lazy | ||||||
| from django.utils.translation import ugettext as _ | from django.utils.translation import ugettext as _ | ||||||
| @ -45,5 +46,10 @@ class ApplicationDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView) | |||||||
|  |  | ||||||
|     model = Application |     model = Application | ||||||
|  |  | ||||||
|  |     template_name = 'generic/delete.html' | ||||||
|     success_url = reverse_lazy('passbook_admin:applications') |     success_url = reverse_lazy('passbook_admin:applications') | ||||||
|     success_message = _('Successfully updated Application') |     success_message = _('Successfully deleted Application') | ||||||
|  |  | ||||||
|  |     def delete(self, request, *args, **kwargs): | ||||||
|  |         messages.success(self.request, self.success_message) | ||||||
|  |         return super().delete(request, *args, **kwargs) | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| """passbook Factor administration""" | """passbook Factor administration""" | ||||||
|  | from django.contrib import messages | ||||||
| from django.contrib.messages.views import SuccessMessageMixin | from django.contrib.messages.views import SuccessMessageMixin | ||||||
| from django.http import Http404 | from django.http import Http404 | ||||||
| from django.urls import reverse_lazy | from django.urls import reverse_lazy | ||||||
| @ -73,7 +74,11 @@ class FactorDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView): | |||||||
|     model = Factor |     model = Factor | ||||||
|     template_name = 'generic/delete.html' |     template_name = 'generic/delete.html' | ||||||
|     success_url = reverse_lazy('passbook_admin:factors') |     success_url = reverse_lazy('passbook_admin:factors') | ||||||
|     success_message = _('Successfully updated Factor') |     success_message = _('Successfully deleted Factor') | ||||||
|  |  | ||||||
|     def get_object(self, queryset=None): |     def get_object(self, queryset=None): | ||||||
|         return Factor.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first() |         return Factor.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first() | ||||||
|  |  | ||||||
|  |     def delete(self, request, *args, **kwargs): | ||||||
|  |         messages.success(self.request, self.success_message) | ||||||
|  |         return super().delete(request, *args, **kwargs) | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| """passbook Invitation administration""" | """passbook Invitation administration""" | ||||||
|  | from django.contrib import messages | ||||||
| from django.contrib.messages.views import SuccessMessageMixin | from django.contrib.messages.views import SuccessMessageMixin | ||||||
| from django.http import HttpResponseRedirect | from django.http import HttpResponseRedirect | ||||||
| from django.urls import reverse_lazy | from django.urls import reverse_lazy | ||||||
| @ -42,4 +43,8 @@ class InvitationDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView): | |||||||
|     model = Invitation |     model = Invitation | ||||||
|     template_name = 'generic/delete.html' |     template_name = 'generic/delete.html' | ||||||
|     success_url = reverse_lazy('passbook_admin:invitations') |     success_url = reverse_lazy('passbook_admin:invitations') | ||||||
|     success_message = _('Successfully updated Invitation') |     success_message = _('Successfully deleted Invitation') | ||||||
|  |  | ||||||
|  |     def delete(self, request, *args, **kwargs): | ||||||
|  |         messages.success(self.request, self.success_message) | ||||||
|  |         return super().delete(request, *args, **kwargs) | ||||||
|  | |||||||
| @ -2,6 +2,8 @@ | |||||||
| from django.views.generic import TemplateView | from django.views.generic import TemplateView | ||||||
|  |  | ||||||
| from passbook.admin.mixins import AdminRequiredMixin | from passbook.admin.mixins import AdminRequiredMixin | ||||||
|  | from passbook.core import __version__ | ||||||
|  | from passbook.core.celery import CELERY_APP | ||||||
| from passbook.core.models import (Application, Factor, Invitation, Policy, | from passbook.core.models import (Application, Factor, Invitation, Policy, | ||||||
|                                   Provider, Source, User) |                                   Provider, Source, User) | ||||||
|  |  | ||||||
| @ -19,4 +21,6 @@ class AdministrationOverviewView(AdminRequiredMixin, TemplateView): | |||||||
|         kwargs['source_count'] = len(Source.objects.all()) |         kwargs['source_count'] = len(Source.objects.all()) | ||||||
|         kwargs['factor_count'] = len(Factor.objects.all()) |         kwargs['factor_count'] = len(Factor.objects.all()) | ||||||
|         kwargs['invitation_count'] = len(Invitation.objects.all()) |         kwargs['invitation_count'] = len(Invitation.objects.all()) | ||||||
|  |         kwargs['version'] = __version__ | ||||||
|  |         kwargs['worker_count'] = len(CELERY_APP.control.ping(timeout=0.5)) | ||||||
|         return super().get_context_data(**kwargs) |         return super().get_context_data(**kwargs) | ||||||
|  | |||||||
| @ -68,11 +68,15 @@ class PolicyDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView): | |||||||
|     model = Policy |     model = Policy | ||||||
|     template_name = 'generic/delete.html' |     template_name = 'generic/delete.html' | ||||||
|     success_url = reverse_lazy('passbook_admin:policies') |     success_url = reverse_lazy('passbook_admin:policies') | ||||||
|     success_message = _('Successfully updated Policy') |     success_message = _('Successfully deleted Policy') | ||||||
|  |  | ||||||
|     def get_object(self, queryset=None): |     def get_object(self, queryset=None): | ||||||
|         return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first() |         return Policy.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first() | ||||||
|  |  | ||||||
|  |     def delete(self, request, *args, **kwargs): | ||||||
|  |         messages.success(self.request, self.success_message) | ||||||
|  |         return super().delete(request, *args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
| class PolicyTestView(AdminRequiredMixin, DetailView, FormView): | class PolicyTestView(AdminRequiredMixin, DetailView, FormView): | ||||||
|     """View to test policy(s)""" |     """View to test policy(s)""" | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| """passbook Provider administration""" | """passbook Provider administration""" | ||||||
|  | from django.contrib import messages | ||||||
| from django.contrib.messages.views import SuccessMessageMixin | from django.contrib.messages.views import SuccessMessageMixin | ||||||
| from django.http import Http404 | from django.http import Http404 | ||||||
| from django.urls import reverse_lazy | from django.urls import reverse_lazy | ||||||
| @ -64,7 +65,11 @@ class ProviderDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView): | |||||||
|     model = Provider |     model = Provider | ||||||
|     template_name = 'generic/delete.html' |     template_name = 'generic/delete.html' | ||||||
|     success_url = reverse_lazy('passbook_admin:providers') |     success_url = reverse_lazy('passbook_admin:providers') | ||||||
|     success_message = _('Successfully updated Provider') |     success_message = _('Successfully deleted Provider') | ||||||
|  |  | ||||||
|     def get_object(self, queryset=None): |     def get_object(self, queryset=None): | ||||||
|         return Provider.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first() |         return Provider.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first() | ||||||
|  |  | ||||||
|  |     def delete(self, request, *args, **kwargs): | ||||||
|  |         messages.success(self.request, self.success_message) | ||||||
|  |         return super().delete(request, *args, **kwargs) | ||||||
|  | |||||||
| @ -1,4 +1,5 @@ | |||||||
| """passbook Source administration""" | """passbook Source administration""" | ||||||
|  | from django.contrib import messages | ||||||
| from django.contrib.messages.views import SuccessMessageMixin | from django.contrib.messages.views import SuccessMessageMixin | ||||||
| from django.http import Http404 | from django.http import Http404 | ||||||
| from django.urls import reverse_lazy | from django.urls import reverse_lazy | ||||||
| @ -66,9 +67,13 @@ class SourceDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView): | |||||||
|     """Delete source""" |     """Delete source""" | ||||||
|  |  | ||||||
|     model = Source |     model = Source | ||||||
|  |     template_name = 'generic/delete.html' | ||||||
|     success_url = reverse_lazy('passbook_admin:sources') |     success_url = reverse_lazy('passbook_admin:sources') | ||||||
|     success_message = _('Successfully updated Source') |     success_message = _('Successfully deleted Source') | ||||||
|  |  | ||||||
|     def get_object(self, queryset=None): |     def get_object(self, queryset=None): | ||||||
|         return Source.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first() |         return Source.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first() | ||||||
|  |  | ||||||
|  |     def delete(self, request, *args, **kwargs): | ||||||
|  |         messages.success(self.request, self.success_message) | ||||||
|  |         return super().delete(request, *args, **kwargs) | ||||||
|  | |||||||
| @ -1,12 +1,15 @@ | |||||||
| """passbook User administration""" | """passbook User administration""" | ||||||
|  | from django.contrib import messages | ||||||
| from django.contrib.messages.views import SuccessMessageMixin | from django.contrib.messages.views import SuccessMessageMixin | ||||||
| from django.urls import reverse_lazy | from django.shortcuts import get_object_or_404, redirect | ||||||
|  | from django.urls import reverse, reverse_lazy | ||||||
| from django.utils.translation import ugettext as _ | from django.utils.translation import ugettext as _ | ||||||
|  | from django.views import View | ||||||
| from django.views.generic import DeleteView, ListView, UpdateView | from django.views.generic import DeleteView, ListView, UpdateView | ||||||
|  |  | ||||||
| from passbook.admin.mixins import AdminRequiredMixin | from passbook.admin.mixins import AdminRequiredMixin | ||||||
| from passbook.core.forms.users import UserDetailForm | from passbook.core.forms.users import UserDetailForm | ||||||
| from passbook.core.models import User | from passbook.core.models import Nonce, User | ||||||
|  |  | ||||||
|  |  | ||||||
| class UserListView(AdminRequiredMixin, ListView): | class UserListView(AdminRequiredMixin, ListView): | ||||||
| @ -31,6 +34,24 @@ class UserDeleteView(SuccessMessageMixin, AdminRequiredMixin, DeleteView): | |||||||
|     """Delete user""" |     """Delete user""" | ||||||
|  |  | ||||||
|     model = User |     model = User | ||||||
|  |     template_name = 'generic/delete.html' | ||||||
|     success_url = reverse_lazy('passbook_admin:users') |     success_url = reverse_lazy('passbook_admin:users') | ||||||
|     success_message = _('Successfully updated User') |     success_message = _('Successfully deleted User') | ||||||
|  |  | ||||||
|  |     def delete(self, request, *args, **kwargs): | ||||||
|  |         messages.success(self.request, self.success_message) | ||||||
|  |         return super().delete(request, *args, **kwargs) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class UserPasswordResetView(AdminRequiredMixin, View): | ||||||
|  |     """Get Password reset link for user""" | ||||||
|  |  | ||||||
|  |     # pylint: disable=invalid-name | ||||||
|  |     def get(self, request, pk): | ||||||
|  |         """Create nonce for user and return link""" | ||||||
|  |         user = get_object_or_404(User, pk=pk) | ||||||
|  |         nonce = Nonce.objects.create(user=user) | ||||||
|  |         link = request.build_absolute_uri(reverse( | ||||||
|  |             'passbook_core:auth-password-reset', kwargs={'nonce': nonce.uuid})) | ||||||
|  |         messages.success(request, _('Password reset link: <pre>%(link)s</pre>' % {'link': link})) | ||||||
|  |         return redirect('passbook_admin:users') | ||||||
|  | |||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook api""" | """passbook api""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook audit Header""" | """passbook audit Header""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
| @ -51,6 +51,9 @@ class AuditEntry(UUIDModel): | |||||||
|     def create(action, request, **kwargs): |     def create(action, request, **kwargs): | ||||||
|         """Create AuditEntry from arguments""" |         """Create AuditEntry from arguments""" | ||||||
|         client_ip, _ = get_client_ip(request) |         client_ip, _ = get_client_ip(request) | ||||||
|  |         if not hasattr(request, 'user'): | ||||||
|  |             user = None | ||||||
|  |         else: | ||||||
|             user = request.user |             user = request.user | ||||||
|         if isinstance(user, AnonymousUser): |         if isinstance(user, AnonymousUser): | ||||||
|             user = kwargs.get('user', None) |             user = kwargs.get('user', None) | ||||||
| @ -60,7 +63,7 @@ class AuditEntry(UUIDModel): | |||||||
|             # User 255.255.255.255 as fallback if IP cannot be determined |             # User 255.255.255.255 as fallback if IP cannot be determined | ||||||
|             request_ip=client_ip or '255.255.255.255', |             request_ip=client_ip or '255.255.255.255', | ||||||
|             context=kwargs) |             context=kwargs) | ||||||
|         LOGGER.debug("Logged %s from %s (%s)", action, request.user, client_ip) |         LOGGER.debug("Logged %s from %s (%s)", action, user, client_ip) | ||||||
|         return entry |         return entry | ||||||
|  |  | ||||||
|     def save(self, *args, **kwargs): |     def save(self, *args, **kwargs): | ||||||
|  | |||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook captcha_factor Header""" | """passbook captcha_factor Header""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook core""" | """passbook core""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
| @ -12,6 +12,7 @@ from django.views.generic import FormView | |||||||
| from passbook.core.auth.factor import AuthenticationFactor | from passbook.core.auth.factor import AuthenticationFactor | ||||||
| from passbook.core.auth.view import AuthenticationView | from passbook.core.auth.view import AuthenticationView | ||||||
| from passbook.core.forms.authentication import PasswordFactorForm | from passbook.core.forms.authentication import PasswordFactorForm | ||||||
|  | from passbook.core.models import Nonce | ||||||
| from passbook.lib.config import CONFIG | from passbook.lib.config import CONFIG | ||||||
|  |  | ||||||
| LOGGER = getLogger(__name__) | LOGGER = getLogger(__name__) | ||||||
| @ -29,7 +30,8 @@ class PasswordFactor(FormView, AuthenticationFactor): | |||||||
|  |  | ||||||
|     def get(self, request, *args, **kwargs): |     def get(self, request, *args, **kwargs): | ||||||
|         if 'password-forgotten' in request.GET: |         if 'password-forgotten' in request.GET: | ||||||
|             # TODO: Save nonce key in database for password reset |             nonce = Nonce.objects.create(user=self.pending_user) | ||||||
|  |             LOGGER.debug("DEBUG %s", str(nonce.uuid)) | ||||||
|             # TODO: Send email to user |             # TODO: Send email to user | ||||||
|             self.authenticator.cleanup() |             self.authenticator.cleanup() | ||||||
|             messages.success(request, _('Check your E-Mails for a password reset link.')) |             messages.success(request, _('Check your E-Mails for a password reset link.')) | ||||||
|  | |||||||
| @ -8,6 +8,7 @@ from django.utils.translation import gettext_lazy as _ | |||||||
|  |  | ||||||
| from passbook.core.models import User | from passbook.core.models import User | ||||||
| from passbook.lib.config import CONFIG | from passbook.lib.config import CONFIG | ||||||
|  | from passbook.lib.utils.ui import human_list | ||||||
|  |  | ||||||
| LOGGER = getLogger(__name__) | LOGGER = getLogger(__name__) | ||||||
|  |  | ||||||
| @ -15,13 +16,16 @@ class LoginForm(forms.Form): | |||||||
|     """Allow users to login""" |     """Allow users to login""" | ||||||
|  |  | ||||||
|     title = _('Log in to your account') |     title = _('Log in to your account') | ||||||
|     uid_field = forms.CharField(widget=forms.TextInput(attrs={'placeholder': _('UID')})) |     uid_field = forms.CharField() | ||||||
|     remember_me = forms.BooleanField(required=False) |     remember_me = forms.BooleanField(required=False) | ||||||
|  |  | ||||||
|     def __init__(self, *args, **kwargs): |     def __init__(self, *args, **kwargs): | ||||||
|         super().__init__(*args, **kwargs) |         super().__init__(*args, **kwargs) | ||||||
|         if CONFIG.y('passbook.uid_fields') == ['email']: |         if CONFIG.y('passbook.uid_fields') == ['e-mail']: | ||||||
|             self.fields['uid_field'] = forms.EmailField() |             self.fields['uid_field'] = forms.EmailField() | ||||||
|  |         self.fields['uid_field'].widget.attrs = { | ||||||
|  |             'placeholder': _(human_list([x.title() for x in CONFIG.y('passbook.uid_fields')])) | ||||||
|  |         } | ||||||
|  |  | ||||||
|     def clean_uid_field(self): |     def clean_uid_field(self): | ||||||
|         """Validate uid_field after EmailValidator if 'email' is the only selected uid_fields""" |         """Validate uid_field after EmailValidator if 'email' is the only selected uid_fields""" | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ class InvitationForm(forms.ModelForm): | |||||||
|     class Meta: |     class Meta: | ||||||
|  |  | ||||||
|         model = Invitation |         model = Invitation | ||||||
|         fields = ['expires', 'fixed_username', 'fixed_email'] |         fields = ['expires', 'fixed_username', 'fixed_email', 'needs_confirmation'] | ||||||
|         labels = { |         labels = { | ||||||
|             'fixed_username': "Force user's username (optional)", |             'fixed_username': "Force user's username (optional)", | ||||||
|             'fixed_email': "Force user's email (optional)", |             'fixed_email': "Force user's email (optional)", | ||||||
|  | |||||||
| @ -1,5 +1,5 @@ | |||||||
| """passbook nexus_upload management command""" | """passbook nexus_upload management command""" | ||||||
| from getpass import getpass | from base64 import b64decode | ||||||
|  |  | ||||||
| import requests | import requests | ||||||
| from django.core.management.base import BaseCommand | from django.core.management.base import BaseCommand | ||||||
| @ -24,9 +24,9 @@ class Command(BaseCommand): | |||||||
|             help='Nexus root URL', |             help='Nexus root URL', | ||||||
|             required=True) |             required=True) | ||||||
|         parser.add_argument( |         parser.add_argument( | ||||||
|             '--user', |             '--auth', | ||||||
|             action='store', |             action='store', | ||||||
|             help='Username to use for Nexus upload', |             help='base64-encoded string of username:password', | ||||||
|             required=True) |             required=True) | ||||||
|         parser.add_argument( |         parser.add_argument( | ||||||
|             '--method', |             '--method', | ||||||
| @ -37,29 +37,21 @@ class Command(BaseCommand): | |||||||
|             help=('Method used for uploading files to nexus. ' |             help=('Method used for uploading files to nexus. ' | ||||||
|                   'Apt repositories use post, Helm uses put.'), |                   'Apt repositories use post, Helm uses put.'), | ||||||
|             required=True) |             required=True) | ||||||
|         parser.add_argument( |  | ||||||
|             '--password', |  | ||||||
|             action='store', |  | ||||||
|             help=("Password to use for Nexus upload. " |  | ||||||
|                   "If parameter not given, we'll interactively ask")) |  | ||||||
|         # Positional arguments |         # Positional arguments | ||||||
|         parser.add_argument('file', nargs='+', type=str) |         parser.add_argument('file', nargs='+', type=str) | ||||||
|  |  | ||||||
|     def handle(self, *args, **options): |     def handle(self, *args, **options): | ||||||
|         """Upload debian package to nexus repository""" |         """Upload debian package to nexus repository""" | ||||||
|         if options.get('password') is None: |         auth = tuple(b64decode(options.get('auth')).decode('utf-8').split(':', 1)) | ||||||
|             options['password'] = getpass() |  | ||||||
|         responses = {} |         responses = {} | ||||||
|         url = 'https://%(url)s/repository/%(repo)s//' % options |         url = 'https://%(url)s/repository/%(repo)s/' % options | ||||||
|         method = options.get('method') |         method = options.get('method') | ||||||
|         exit_code = 0 |         exit_code = 0 | ||||||
|         for file in options.get('file'): |         for file in options.get('file'): | ||||||
|             if method == 'post': |             if method == 'post': | ||||||
|                 responses[file] = requests.post(url, data=open(file, mode='rb'), |                 responses[file] = requests.post(url, data=open(file, mode='rb'), auth=auth) | ||||||
|                                                 auth=(options.get('user'), options.get('password'))) |  | ||||||
|             else: |             else: | ||||||
|                 responses[file] = requests.put(url+file, data=open(file, mode='rb'), |                 responses[file] = requests.put(url+file, data=open(file, mode='rb'), auth=auth) | ||||||
|                                                auth=(options.get('user'), options.get('password'))) |  | ||||||
|         self.stdout.write('Upload results:\n') |         self.stdout.write('Upload results:\n') | ||||||
|         sep = '-' * 60 |         sep = '-' * 60 | ||||||
|         self.stdout.write('%s\n' % sep) |         self.stdout.write('%s\n' % sep) | ||||||
|  | |||||||
							
								
								
									
										31
									
								
								passbook/core/migrations/0012_nonce.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										31
									
								
								passbook/core/migrations/0012_nonce.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,31 @@ | |||||||
|  | # Generated by Django 2.1.7 on 2019-02-25 19:12 | ||||||
|  |  | ||||||
|  | import uuid | ||||||
|  |  | ||||||
|  | import django.db.models.deletion | ||||||
|  | from django.conf import settings | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  | import passbook.core.models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('passbook_core', '0011_auto_20190225_1438'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.CreateModel( | ||||||
|  |             name='Nonce', | ||||||
|  |             fields=[ | ||||||
|  |                 ('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), | ||||||
|  |                 ('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)), | ||||||
|  |                 ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), | ||||||
|  |             ], | ||||||
|  |             options={ | ||||||
|  |                 'verbose_name': 'Nonce', | ||||||
|  |                 'verbose_name_plural': 'Nonces', | ||||||
|  |             }, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @ -0,0 +1,18 @@ | |||||||
|  | # Generated by Django 2.1.7 on 2019-02-25 19:57 | ||||||
|  |  | ||||||
|  | from django.db import migrations, models | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('passbook_core', '0012_nonce'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AddField( | ||||||
|  |             model_name='invitation', | ||||||
|  |             name='needs_confirmation', | ||||||
|  |             field=models.BooleanField(default=True), | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
							
								
								
									
										25
									
								
								passbook/core/migrations/0014_auto_20190226_0850.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								passbook/core/migrations/0014_auto_20190226_0850.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | # Generated by Django 2.1.7 on 2019-02-26 08:50 | ||||||
|  |  | ||||||
|  | from django.db import migrations | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def create_initial_factor(apps, schema_editor): | ||||||
|  |     """Create initial PasswordFactor if none exists""" | ||||||
|  |     PasswordFactor = apps.get_model("passbook_core", "PasswordFactor") | ||||||
|  |     if not PasswordFactor.objects.exists(): | ||||||
|  |         PasswordFactor.objects.create( | ||||||
|  |             name='password', | ||||||
|  |             slug='password', | ||||||
|  |             order=0, | ||||||
|  |             backends=[] | ||||||
|  |         ) | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('passbook_core', '0013_invitation_needs_confirmation'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.RunPython(create_initial_factor) | ||||||
|  |     ] | ||||||
| @ -1,5 +1,6 @@ | |||||||
| """passbook core models""" | """passbook core models""" | ||||||
| import re | import re | ||||||
|  | from datetime import timedelta | ||||||
| from logging import getLogger | from logging import getLogger | ||||||
| from random import SystemRandom | from random import SystemRandom | ||||||
| from time import sleep | from time import sleep | ||||||
| @ -18,6 +19,11 @@ from passbook.lib.models import CreatedUpdatedModel, UUIDModel | |||||||
|  |  | ||||||
| LOGGER = getLogger(__name__) | LOGGER = getLogger(__name__) | ||||||
|  |  | ||||||
|  |  | ||||||
|  | def default_nonce_duration(): | ||||||
|  |     """Default duration a Nonce is valid""" | ||||||
|  |     return now() + timedelta(hours=4) | ||||||
|  |  | ||||||
| class Group(UUIDModel): | class Group(UUIDModel): | ||||||
|     """Custom Group model which supports a basic hierarchy""" |     """Custom Group model which supports a basic hierarchy""" | ||||||
|  |  | ||||||
| @ -386,6 +392,7 @@ class Invitation(UUIDModel): | |||||||
|     expires = models.DateTimeField(default=None, blank=True, null=True) |     expires = models.DateTimeField(default=None, blank=True, null=True) | ||||||
|     fixed_username = models.TextField(blank=True, default=None) |     fixed_username = models.TextField(blank=True, default=None) | ||||||
|     fixed_email = models.TextField(blank=True, default=None) |     fixed_email = models.TextField(blank=True, default=None) | ||||||
|  |     needs_confirmation = models.BooleanField(default=True) | ||||||
|  |  | ||||||
|     @property |     @property | ||||||
|     def link(self): |     def link(self): | ||||||
| @ -399,3 +406,17 @@ class Invitation(UUIDModel): | |||||||
|  |  | ||||||
|         verbose_name = _('Invitation') |         verbose_name = _('Invitation') | ||||||
|         verbose_name_plural = _('Invitations') |         verbose_name_plural = _('Invitations') | ||||||
|  |  | ||||||
|  | class Nonce(UUIDModel): | ||||||
|  |     """One-time link for password resets/signup-confirmations""" | ||||||
|  |  | ||||||
|  |     expires = models.DateTimeField(default=default_nonce_duration) | ||||||
|  |     user = models.ForeignKey('User', on_delete=models.CASCADE) | ||||||
|  |  | ||||||
|  |     def __str__(self): | ||||||
|  |         return "Nonce %s (expires=%s)" % (self.uuid.hex, self.expires) | ||||||
|  |  | ||||||
|  |     class Meta: | ||||||
|  |  | ||||||
|  |         verbose_name = _('Nonce') | ||||||
|  |         verbose_name_plural = _('Nonces') | ||||||
|  | |||||||
| @ -74,6 +74,7 @@ INSTALLED_APPS = [ | |||||||
|     'passbook.otp.apps.PassbookOTPConfig', |     'passbook.otp.apps.PassbookOTPConfig', | ||||||
|     'passbook.captcha_factor.apps.PassbookCaptchaFactorConfig', |     'passbook.captcha_factor.apps.PassbookCaptchaFactorConfig', | ||||||
|     'passbook.hibp_policy.apps.PassbookHIBPConfig', |     'passbook.hibp_policy.apps.PassbookHIBPConfig', | ||||||
|  |     'passbook.pretend.apps.PassbookPretendConfig', | ||||||
| ] | ] | ||||||
|  |  | ||||||
| # Message Tag fix for bootstrap CSS Classes | # Message Tag fix for bootstrap CSS Classes | ||||||
|  | |||||||
| @ -6,12 +6,16 @@ | |||||||
| {% block content %} | {% block content %} | ||||||
| <div class="container"> | <div class="container"> | ||||||
|     {% block above_form %} |     {% block above_form %} | ||||||
|     <h1>{% trans 'Delete' %}</h1> |     <h1>{% blocktrans with object_type=object|fieldtype|title %}Delete {{ object_type }}{% endblocktrans %}</h1> | ||||||
|     {% endblock %} |     {% endblock %} | ||||||
|     <div class=""> |     <div class=""> | ||||||
|         <form method="post" class="form-horizontal"> |         <form method="post" class="form-horizontal"> | ||||||
|             {% csrf_token %} |             {% csrf_token %} | ||||||
|       <p>Are you sure you want to delete "{{ object }}"?</p> |             <p> | ||||||
|  |                 {% blocktrans with object_type=object|fieldtype|title name=object %} | ||||||
|  |                 Are you sure you want to delete {{ object_type }} "{{ object }}"? | ||||||
|  |                 {% endblocktrans %} | ||||||
|  |             </p> | ||||||
|             <a href="{% back %}" class="btn btn-default">{% trans 'Back' %}</a> |             <a href="{% back %}" class="btn btn-default">{% trans 'Back' %}</a> | ||||||
|             <input type="submit" class="btn btn-danger" value="{% trans 'Delete' %}" /> |             <input type="submit" class="btn btn-danger" value="{% trans 'Delete' %}" /> | ||||||
|         </form> |         </form> | ||||||
|  | |||||||
| @ -29,7 +29,7 @@ | |||||||
| <div class="login-pf-page"> | <div class="login-pf-page"> | ||||||
|     <div class="container-fluid"> |     <div class="container-fluid"> | ||||||
|         <div class="row"> |         <div class="row"> | ||||||
|             <div class="col-sm-6 col-sm-offset-3 col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4"> |             <div class="col-sm-12 col-md-8 col-md-offset-2 col-lg-4 col-lg-offset-4"> | ||||||
|                 <header class="login-pf-page-header"> |                 <header class="login-pf-page-header"> | ||||||
|                     <img class="login-pf-brand" style="max-height: 10rem;" src="{% static 'img/logo.svg' %}" |                     <img class="login-pf-brand" style="max-height: 10rem;" src="{% static 'img/logo.svg' %}" | ||||||
|                         alt="passbook logo" /> |                         alt="passbook logo" /> | ||||||
|  | |||||||
| @ -18,7 +18,6 @@ | |||||||
| <header class="login-pf-header"> | <header class="login-pf-header"> | ||||||
|   <h1>{% trans title %}</h1> |   <h1>{% trans title %}</h1> | ||||||
| </header> | </header> | ||||||
| {% include 'partials/messages.html' %} |  | ||||||
| <form method="POST"> | <form method="POST"> | ||||||
|   {% csrf_token %} |   {% csrf_token %} | ||||||
|   {% include 'partials/form_login.html' %} |   {% include 'partials/form_login.html' %} | ||||||
|  | |||||||
| @ -1,10 +0,0 @@ | |||||||
| """passbook core login test""" |  | ||||||
|  |  | ||||||
| from django.test import TestCase |  | ||||||
|  |  | ||||||
|  |  | ||||||
| class LoginTest(TestCase): |  | ||||||
|     """Test login""" |  | ||||||
|  |  | ||||||
|     def test(self): |  | ||||||
|         """Stub test""" |  | ||||||
							
								
								
									
										147
									
								
								passbook/core/tests/test_views_authentication.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								passbook/core/tests/test_views_authentication.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,147 @@ | |||||||
|  | """passbook Core Account Test""" | ||||||
|  |  | ||||||
|  | from django.test import TestCase | ||||||
|  | from django.urls import reverse | ||||||
|  |  | ||||||
|  | from passbook.core.forms.authentication import LoginForm, SignUpForm | ||||||
|  | from passbook.core.models import User | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestAuthenticationViews(TestCase): | ||||||
|  |     """passbook Core Account Test""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         super().setUp() | ||||||
|  |         self.sign_up_data = { | ||||||
|  |             'first_name': 'Test', | ||||||
|  |             'last_name': 'User', | ||||||
|  |             'username': 'beryjuorg', | ||||||
|  |             'email': 'unittest@passbook.beryju.org', | ||||||
|  |             'password': 'B3ryju0rg!', | ||||||
|  |             'password_repeat': 'B3ryju0rg!', | ||||||
|  |         } | ||||||
|  |         self.login_data = { | ||||||
|  |             'uid_field': 'unittest@example.com', | ||||||
|  |         } | ||||||
|  |         self.user = User.objects.create_superuser( | ||||||
|  |             username='unittest user', | ||||||
|  |             email='unittest@example.com', | ||||||
|  |             password='test123') | ||||||
|  |  | ||||||
|  |     def test_sign_up_view(self): | ||||||
|  |         """Test account.sign_up view (Anonymous)""" | ||||||
|  |         self.client.logout() | ||||||
|  |         response = self.client.get(reverse('passbook_core:auth-sign-up')) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |  | ||||||
|  |     def test_login_view(self): | ||||||
|  |         """Test account.login view (Anonymous)""" | ||||||
|  |         self.client.logout() | ||||||
|  |         response = self.client.get(reverse('passbook_core:auth-login')) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
|  |         # test login with post | ||||||
|  |         form = LoginForm(self.login_data) | ||||||
|  |         self.assertTrue(form.is_valid()) | ||||||
|  |  | ||||||
|  |         response = self.client.post(reverse('passbook_core:auth-login'), data=form.cleaned_data) | ||||||
|  |         self.assertEqual(response.status_code, 302) | ||||||
|  |  | ||||||
|  |     def test_logout_view(self): | ||||||
|  |         """Test account.logout view""" | ||||||
|  |         self.client.force_login(self.user) | ||||||
|  |         response = self.client.get(reverse('passbook_core:auth-logout')) | ||||||
|  |         self.assertEqual(response.status_code, 302) | ||||||
|  |  | ||||||
|  |     def test_sign_up_view_auth(self): | ||||||
|  |         """Test account.sign_up view (Authenticated)""" | ||||||
|  |         self.client.force_login(self.user) | ||||||
|  |         response = self.client.get(reverse('passbook_core:auth-logout')) | ||||||
|  |         self.assertEqual(response.status_code, 302) | ||||||
|  |  | ||||||
|  |     def test_login_view_auth(self): | ||||||
|  |         """Test account.login view (Authenticated)""" | ||||||
|  |         self.client.force_login(self.user) | ||||||
|  |         response = self.client.get(reverse('passbook_core:auth-login')) | ||||||
|  |         self.assertEqual(response.status_code, 302) | ||||||
|  |  | ||||||
|  |     def test_login_view_post(self): | ||||||
|  |         """Test account.login view POST (Anonymous)""" | ||||||
|  |         login_response = self.client.post(reverse('passbook_core:auth-login'), data=self.login_data) | ||||||
|  |         self.assertEqual(login_response.status_code, 302) | ||||||
|  |         self.assertEqual(login_response.url, reverse('passbook_core:auth-process')) | ||||||
|  |  | ||||||
|  |     def test_sign_up_view_post(self): | ||||||
|  |         """Test account.sign_up view POST (Anonymous)""" | ||||||
|  |         form = SignUpForm(self.sign_up_data) | ||||||
|  |         self.assertTrue(form.is_valid()) | ||||||
|  |  | ||||||
|  |         response = self.client.post(reverse('passbook_core:auth-sign-up'), data=form.cleaned_data) | ||||||
|  |         self.assertEqual(response.status_code, 302) | ||||||
|  |  | ||||||
|  |     # def test_reset_password_init_view(self): | ||||||
|  |     #     """Test account.reset_password_init view POST (Anonymous)""" | ||||||
|  |     #     form = SignUpForm(self.sign_up_data) | ||||||
|  |     #     self.assertTrue(form.is_valid()) | ||||||
|  |  | ||||||
|  |     #     res = test_request(accounts.SignUpView.as_view(), | ||||||
|  |     #                        method='POST', | ||||||
|  |     #                        req_kwargs=form.cleaned_data) | ||||||
|  |     #     self.assertEqual(res.status_code, 302) | ||||||
|  |  | ||||||
|  |     #     res = test_request(accounts.PasswordResetInitView.as_view()) | ||||||
|  |     #     self.assertEqual(res.status_code, 200) | ||||||
|  |  | ||||||
|  |     # def test_resend_confirmation(self): | ||||||
|  |     #     """Test AccountController.resend_confirmation""" | ||||||
|  |     #     form = SignUpForm(self.sign_up_data) | ||||||
|  |     #     self.assertTrue(form.is_valid()) | ||||||
|  |  | ||||||
|  |     #     res = test_request(accounts.SignUpView.as_view(), | ||||||
|  |     #                        method='POST', | ||||||
|  |     #                        req_kwargs=form.cleaned_data) | ||||||
|  |     #     self.assertEqual(res.status_code, 302) | ||||||
|  |     #     user = User.objects.get(email=self.sign_up_data['email']) | ||||||
|  |     #     # Invalidate all other links for this user | ||||||
|  |     #     old_acs = AccountConfirmation.objects.filter( | ||||||
|  |     #         user=user) | ||||||
|  |     #     for old_ac in old_acs: | ||||||
|  |     #         old_ac.confirmed = True | ||||||
|  |     #         old_ac.save() | ||||||
|  |     #     # Create Account Confirmation UUID | ||||||
|  |     #     new_ac = AccountConfirmation.objects.create(user=user) | ||||||
|  |     #     self.assertFalse(new_ac.is_expired) | ||||||
|  |     #     on_user_confirm_resend.send( | ||||||
|  |     #         sender=None, | ||||||
|  |     #         user=user, | ||||||
|  |     #         request=None) | ||||||
|  |  | ||||||
|  |     # def test_reset_passowrd(self): | ||||||
|  |     #     """Test reset password POST""" | ||||||
|  |     #     # Signup user first | ||||||
|  |     #     sign_up_form = SignUpForm(self.sign_up_data) | ||||||
|  |     #     self.assertTrue(sign_up_form.is_valid()) | ||||||
|  |  | ||||||
|  |     #     sign_up_res = test_request(accounts.SignUpView.as_view(), | ||||||
|  |     #                               method='POST', | ||||||
|  |     #                               req_kwargs=sign_up_form.cleaned_data) | ||||||
|  |     #     self.assertEqual(sign_up_res.status_code, 302) | ||||||
|  |  | ||||||
|  |     #     user = User.objects.get(email=self.sign_up_data['email']) | ||||||
|  |     #     # Invalidate all other links for this user | ||||||
|  |     #     old_acs = AccountConfirmation.objects.filter( | ||||||
|  |     #         user=user) | ||||||
|  |     #     for old_ac in old_acs: | ||||||
|  |     #         old_ac.confirmed = True | ||||||
|  |     #         old_ac.save() | ||||||
|  |     #     # Create Account Confirmation UUID | ||||||
|  |     #     new_ac = AccountConfirmation.objects.create(user=user) | ||||||
|  |     #     self.assertFalse(new_ac.is_expired) | ||||||
|  |     #     uuid = AccountConfirmation.objects.filter(user=user).first().pk | ||||||
|  |     #     reset_res = test_request(accounts.PasswordResetFinishView.as_view(), | ||||||
|  |     #                              method='POST', | ||||||
|  |     #                              user=user, | ||||||
|  |     #                              url_kwargs={'uuid': uuid}, | ||||||
|  |     #                              req_kwargs=self.change_data) | ||||||
|  |  | ||||||
|  |     #     self.assertEqual(reset_res.status_code, 302) | ||||||
|  |     #     self.assertEqual(reset_res.url, reverse('common-index')) | ||||||
							
								
								
									
										21
									
								
								passbook/core/tests/test_views_overview.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										21
									
								
								passbook/core/tests/test_views_overview.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,21 @@ | |||||||
|  | """passbook user view tests""" | ||||||
|  | from django.shortcuts import reverse | ||||||
|  | from django.test import TestCase | ||||||
|  |  | ||||||
|  | from passbook.core.models import User | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestOverviewViews(TestCase): | ||||||
|  |     """Test Overview Views""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         super().setUp() | ||||||
|  |         self.user = User.objects.create_superuser( | ||||||
|  |             username='unittest user', | ||||||
|  |             email='unittest@example.com', | ||||||
|  |             password='test123') | ||||||
|  |         self.client.force_login(self.user) | ||||||
|  |  | ||||||
|  |     def test_overview(self): | ||||||
|  |         """Test UserSettingsView""" | ||||||
|  |         self.assertEqual(self.client.get(reverse('passbook_core:overview')).status_code, 200) | ||||||
							
								
								
									
										43
									
								
								passbook/core/tests/test_views_user.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										43
									
								
								passbook/core/tests/test_views_user.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,43 @@ | |||||||
|  | """passbook user view tests""" | ||||||
|  | from django.shortcuts import reverse | ||||||
|  | from django.test import TestCase | ||||||
|  |  | ||||||
|  | from passbook.core.forms.users import PasswordChangeForm | ||||||
|  | from passbook.core.models import User | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestUserViews(TestCase): | ||||||
|  |     """Test User Views""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         super().setUp() | ||||||
|  |         self.user = User.objects.create_superuser( | ||||||
|  |             username='unittest user', | ||||||
|  |             email='unittest@example.com', | ||||||
|  |             password='test123') | ||||||
|  |         self.client.force_login(self.user) | ||||||
|  |  | ||||||
|  |     def test_user_settings(self): | ||||||
|  |         """Test UserSettingsView""" | ||||||
|  |         self.assertEqual(self.client.get(reverse('passbook_core:user-settings')).status_code, 200) | ||||||
|  |  | ||||||
|  |     def test_user_delete(self): | ||||||
|  |         """Test UserDeleteView""" | ||||||
|  |         self.assertEqual(self.client.post(reverse('passbook_core:user-delete')).status_code, 302) | ||||||
|  |         self.assertEqual(User.objects.filter(username='unittest user').exists(), False) | ||||||
|  |         self.setUp() | ||||||
|  |  | ||||||
|  |     def test_user_change_password(self): | ||||||
|  |         """Test UserChangePasswordView""" | ||||||
|  |         form_data = { | ||||||
|  |             'password': 'test2', | ||||||
|  |             'password_repeat': 'test2' | ||||||
|  |         } | ||||||
|  |         form = PasswordChangeForm(data=form_data) | ||||||
|  |         self.assertTrue(form.is_valid()) | ||||||
|  |         self.assertEqual(self.client.get( | ||||||
|  |             reverse('passbook_core:user-change-password')).status_code, 200) | ||||||
|  |         self.assertEqual(self.client.post( | ||||||
|  |             reverse('passbook_core:user-change-password'), data=form_data).status_code, 302) | ||||||
|  |         self.user.refresh_from_db() | ||||||
|  |         self.assertTrue(self.user.check_password('test2')) | ||||||
							
								
								
									
										25
									
								
								passbook/core/tests/test_views_utils.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								passbook/core/tests/test_views_utils.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,25 @@ | |||||||
|  | """passbook util view tests""" | ||||||
|  |  | ||||||
|  | from django.test import RequestFactory, TestCase | ||||||
|  |  | ||||||
|  | from passbook.core.views.utils import LoadingView, PermissionDeniedView | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class TestUtilViews(TestCase): | ||||||
|  |     """Test Utility Views""" | ||||||
|  |  | ||||||
|  |     def setUp(self): | ||||||
|  |         self.factory = RequestFactory() | ||||||
|  |  | ||||||
|  |     def test_loading_view(self): | ||||||
|  |         """Test loading view""" | ||||||
|  |         request = self.factory.get('something') | ||||||
|  |         response = LoadingView.as_view(target_url='somestring')(request) | ||||||
|  |         response.render() | ||||||
|  |         self.assertIn('somestring', response.content.decode('utf-8')) | ||||||
|  |  | ||||||
|  |     def test_permission_denied_view(self): | ||||||
|  |         """Test PermissionDeniedView""" | ||||||
|  |         request = self.factory.get('something') | ||||||
|  |         response = PermissionDeniedView.as_view()(request) | ||||||
|  |         self.assertEqual(response.status_code, 200) | ||||||
| @ -19,13 +19,17 @@ core_urls = [ | |||||||
|     path('auth/login/', authentication.LoginView.as_view(), name='auth-login'), |     path('auth/login/', authentication.LoginView.as_view(), name='auth-login'), | ||||||
|     path('auth/logout/', authentication.LogoutView.as_view(), name='auth-logout'), |     path('auth/logout/', authentication.LogoutView.as_view(), name='auth-logout'), | ||||||
|     path('auth/sign_up/', authentication.SignUpView.as_view(), name='auth-sign-up'), |     path('auth/sign_up/', authentication.SignUpView.as_view(), name='auth-sign-up'), | ||||||
|  |     path('auth/sign_up/<uuid:nonce>/confirm/', authentication.SignUpConfirmView.as_view(), | ||||||
|  |          name='auth-sign-up-confirm'), | ||||||
|     path('auth/process/denied/', view.FactorPermissionDeniedView.as_view(), name='auth-denied'), |     path('auth/process/denied/', view.FactorPermissionDeniedView.as_view(), name='auth-denied'), | ||||||
|  |     path('auth/password/reset/<uuid:nonce>/', authentication.PasswordResetView.as_view(), | ||||||
|  |          name='auth-password-reset'), | ||||||
|     path('auth/process/', view.AuthenticationView.as_view(), name='auth-process'), |     path('auth/process/', view.AuthenticationView.as_view(), name='auth-process'), | ||||||
|     path('auth/process/<slug:factor>/', view.AuthenticationView.as_view(), name='auth-process'), |     path('auth/process/<slug:factor>/', view.AuthenticationView.as_view(), name='auth-process'), | ||||||
|     # User views |     # User views | ||||||
|     path('user/', user.UserSettingsView.as_view(), name='user-settings'), |     path('_/user/', user.UserSettingsView.as_view(), name='user-settings'), | ||||||
|     path('user/delete/', user.UserDeleteView.as_view(), name='user-delete'), |     path('_/user/delete/', user.UserDeleteView.as_view(), name='user-delete'), | ||||||
|     path('user/change_password/', user.UserChangePasswordView.as_view(), |     path('_/user/change_password/', user.UserChangePasswordView.as_view(), | ||||||
|          name='user-change-password'), |          name='user-change-password'), | ||||||
|     # Overview |     # Overview | ||||||
|     path('', overview.OverviewView.as_view(), name='overview'), |     path('', overview.OverviewView.as_view(), name='overview'), | ||||||
|  | |||||||
| @ -1,7 +1,8 @@ | |||||||
| """passbook access helper classes""" | """passbook access helper classes""" | ||||||
| from logging import getLogger | from logging import getLogger | ||||||
|  |  | ||||||
| from django.http import Http404 | from django.contrib import messages | ||||||
|  | from django.utils.translation import gettext as _ | ||||||
|  |  | ||||||
| from passbook.core.models import Application | from passbook.core.models import Application | ||||||
|  |  | ||||||
| @ -11,14 +12,18 @@ class AccessMixin: | |||||||
|     """Mixin class for usage in Authorization views. |     """Mixin class for usage in Authorization views. | ||||||
|     Provider functions to check application access, etc""" |     Provider functions to check application access, etc""" | ||||||
|  |  | ||||||
|  |     # request is set by view but since this Mixin has no base class | ||||||
|  |     request = None | ||||||
|  |  | ||||||
|     def provider_to_application(self, provider): |     def provider_to_application(self, provider): | ||||||
|         """Lookup application assigned to provider, throw error if no application assigned""" |         """Lookup application assigned to provider, throw error if no application assigned""" | ||||||
|         try: |         try: | ||||||
|             return provider.application |             return provider.application | ||||||
|         except Application.DoesNotExist as exc: |         except Application.DoesNotExist as exc: | ||||||
|             # TODO: Log that no provider has no application assigned |             messages.error(self.request, _('Provider "%(name)s" has no application assigned' % { | ||||||
|             LOGGER.warning('Provider "%s" has no application assigned...', provider) |                 'name': provider | ||||||
|             raise Http404 from exc |                 })) | ||||||
|  |             raise exc | ||||||
|  |  | ||||||
|     def user_has_access(self, application, user): |     def user_has_access(self, application, user): | ||||||
|         """Check if user has access to application.""" |         """Check if user has access to application.""" | ||||||
|  | |||||||
| @ -3,17 +3,17 @@ from logging import getLogger | |||||||
| from typing import Dict | from typing import Dict | ||||||
|  |  | ||||||
| from django.contrib import messages | from django.contrib import messages | ||||||
| from django.contrib.auth import logout | from django.contrib.auth import login, logout | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin | from django.contrib.auth.mixins import LoginRequiredMixin, UserPassesTestMixin | ||||||
| from django.http import HttpRequest, HttpResponse | from django.http import HttpRequest, HttpResponse | ||||||
| from django.shortcuts import redirect, reverse | from django.shortcuts import get_object_or_404, redirect, reverse | ||||||
| from django.utils.translation import ugettext as _ | from django.utils.translation import ugettext as _ | ||||||
| from django.views import View | from django.views import View | ||||||
| from django.views.generic import FormView | from django.views.generic import FormView | ||||||
|  |  | ||||||
| from passbook.core.auth.view import AuthenticationView | from passbook.core.auth.view import AuthenticationView | ||||||
| from passbook.core.forms.authentication import LoginForm, SignUpForm | from passbook.core.forms.authentication import LoginForm, SignUpForm | ||||||
| from passbook.core.models import Invitation, Source, User | from passbook.core.models import Invitation, Nonce, Source, User | ||||||
| from passbook.core.signals import invitation_used, user_signed_up | from passbook.core.signals import invitation_used, user_signed_up | ||||||
| from passbook.lib.config import CONFIG | from passbook.lib.config import CONFIG | ||||||
|  |  | ||||||
| @ -52,6 +52,9 @@ class LoginView(UserPassesTestMixin, FormView): | |||||||
|     def get_user(self, uid_value) -> User: |     def get_user(self, uid_value) -> User: | ||||||
|         """Find user instance. Returns None if no user was found.""" |         """Find user instance. Returns None if no user was found.""" | ||||||
|         for search_field in CONFIG.y('passbook.uid_fields'): |         for search_field in CONFIG.y('passbook.uid_fields'): | ||||||
|  |             # Workaround for E-Mail -> email | ||||||
|  |             if search_field == 'e-mail': | ||||||
|  |                 search_field = 'email' | ||||||
|             users = User.objects.filter(**{search_field: uid_value}) |             users = User.objects.filter(**{search_field: uid_value}) | ||||||
|             if users.exists(): |             if users.exists(): | ||||||
|                 LOGGER.debug("Found user %s with uid_field %s", users.first(), search_field) |                 LOGGER.debug("Found user %s with uid_field %s", users.first(), search_field) | ||||||
| @ -136,6 +139,15 @@ class SignUpView(UserPassesTestMixin, FormView): | |||||||
|     def form_valid(self, form: SignUpForm) -> HttpResponse: |     def form_valid(self, form: SignUpForm) -> HttpResponse: | ||||||
|         """Create user""" |         """Create user""" | ||||||
|         self._user = SignUpView.create_user(form.cleaned_data, self.request) |         self._user = SignUpView.create_user(form.cleaned_data, self.request) | ||||||
|  |         needs_confirmation = True | ||||||
|  |         if self._invitation and not self._invitation.needs_confirmation: | ||||||
|  |             needs_confirmation = False | ||||||
|  |         if needs_confirmation: | ||||||
|  |             nonce = Nonce.objects.create(user=self._user) | ||||||
|  |             LOGGER.debug(str(nonce.uuid)) | ||||||
|  |             # TODO: Send E-Mail to user | ||||||
|  |             self._user.is_active = False | ||||||
|  |             self._user.save() | ||||||
|         self.consume_invitation() |         self.consume_invitation() | ||||||
|         messages.success(self.request, _("Successfully signed up!")) |         messages.success(self.request, _("Successfully signed up!")) | ||||||
|         LOGGER.debug("Successfully signed up %s", |         LOGGER.debug("Successfully signed up %s", | ||||||
| @ -182,8 +194,35 @@ class SignUpView(UserPassesTestMixin, FormView): | |||||||
|             sender=SignUpView, |             sender=SignUpView, | ||||||
|             user=new_user, |             user=new_user, | ||||||
|             request=request) |             request=request) | ||||||
|         # TODO: Implement Verification, via email or others |  | ||||||
|         # if needs_confirmation: |  | ||||||
|         #     Create Account Confirmation UUID |  | ||||||
|         #     AccountConfirmation.objects.create(user=new_user) |  | ||||||
|         return new_user |         return new_user | ||||||
|  |  | ||||||
|  | class SignUpConfirmView(View): | ||||||
|  |     """Confirm registration from Nonce""" | ||||||
|  |  | ||||||
|  |     def get(self, request, nonce): | ||||||
|  |         """Verify UUID and activate user""" | ||||||
|  |         nonce = get_object_or_404(Nonce, uuid=nonce) | ||||||
|  |         nonce.user.is_active = True | ||||||
|  |         nonce.user.save() | ||||||
|  |         # Workaround: hardcoded reference to ModelBackend, needs testing | ||||||
|  |         nonce.user.backend = 'django.contrib.auth.backends.ModelBackend' | ||||||
|  |         login(request, nonce.user) | ||||||
|  |         nonce.delete() | ||||||
|  |         messages.success(request, _('Successfully confirmed registration.')) | ||||||
|  |         return redirect('passbook_core:overview') | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PasswordResetView(View): | ||||||
|  |     """Temporarily authenticate User and allow them to reset their password""" | ||||||
|  |  | ||||||
|  |     def get(self, request, nonce): | ||||||
|  |         """Authenticate user with nonce and redirect to password change view""" | ||||||
|  |         # 3. (Optional) Trap user in password change view | ||||||
|  |         nonce = get_object_or_404(Nonce, uuid=nonce) | ||||||
|  |         # Workaround: hardcoded reference to ModelBackend, needs testing | ||||||
|  |         nonce.user.backend = 'django.contrib.auth.backends.ModelBackend' | ||||||
|  |         login(request, nonce.user) | ||||||
|  |         nonce.delete() | ||||||
|  |         messages.success(request, _(('Temporarily authenticated with Nonce, ' | ||||||
|  |                                      'please change your password'))) | ||||||
|  |         return redirect('passbook_core:user-change-password') | ||||||
|  | |||||||
| @ -11,6 +11,7 @@ from passbook.lib.config import CONFIG | |||||||
|  |  | ||||||
| class UserSettingsView(UpdateView): | class UserSettingsView(UpdateView): | ||||||
|     """Update User settings""" |     """Update User settings""" | ||||||
|  |  | ||||||
|     template_name = 'user/settings.html' |     template_name = 'user/settings.html' | ||||||
|     form_class = UserDetailForm |     form_class = UserDetailForm | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										17
									
								
								passbook/hibp_policy/migrations/0002_auto_20190225_1912.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								passbook/hibp_policy/migrations/0002_auto_20190225_1912.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | |||||||
|  | # Generated by Django 2.1.7 on 2019-02-25 19:12 | ||||||
|  |  | ||||||
|  | from django.db import migrations | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class Migration(migrations.Migration): | ||||||
|  |  | ||||||
|  |     dependencies = [ | ||||||
|  |         ('passbook_hibp_policy', '0001_initial'), | ||||||
|  |     ] | ||||||
|  |  | ||||||
|  |     operations = [ | ||||||
|  |         migrations.AlterModelOptions( | ||||||
|  |             name='haveibeenpwendpolicy', | ||||||
|  |             options={'verbose_name': 'have i been pwned Policy', 'verbose_name_plural': 'have i been pwned Policies'}, | ||||||
|  |         ), | ||||||
|  |     ] | ||||||
| @ -1,2 +1,2 @@ | |||||||
| """Passbook ldap app Header""" | """Passbook ldap app Header""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook lib""" | """passbook lib""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
							
								
								
									
										7
									
								
								passbook/lib/utils/ui.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								passbook/lib/utils/ui.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | |||||||
|  | """passbook UI utils""" | ||||||
|  |  | ||||||
|  | def human_list(_list) -> str: | ||||||
|  |     """Convert a list of items into 'a, b or c'""" | ||||||
|  |     last_item = _list.pop() | ||||||
|  |     result = ', '.join(_list) | ||||||
|  |     return '%s or %s' % (result, last_item) | ||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook oauth_client Header""" | """passbook oauth_client Header""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook oauth_provider Header""" | """passbook oauth_provider Header""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
| @ -22,6 +22,8 @@ OAUTH2_PROVIDER = { | |||||||
|     'SCOPES': { |     'SCOPES': { | ||||||
|         'openid:userinfo': 'Access OpenID Userinfo', |         'openid:userinfo': 'Access OpenID Userinfo', | ||||||
|         # 'write': 'Write scope', |         # 'write': 'Write scope', | ||||||
|         # 'groups': 'Access to your groups' |         # 'groups': 'Access to your groups', | ||||||
|  |         'user:email': 'GitHub Compatibility: User E-Mail', | ||||||
|  |         'read:org': 'GitHub Compatibility: User Groups', | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  | |||||||
| @ -11,7 +11,6 @@ | |||||||
| <header class="login-pf-header"> | <header class="login-pf-header"> | ||||||
|   <h1>{% trans 'Authorize Application' %}</h1> |   <h1>{% trans 'Authorize Application' %}</h1> | ||||||
| </header> | </header> | ||||||
| {% include 'partials/messages.html' %} |  | ||||||
| <form method="POST"> | <form method="POST"> | ||||||
|     {% csrf_token %} |     {% csrf_token %} | ||||||
|     {% if not error %} |     {% if not error %} | ||||||
|  | |||||||
| @ -1,6 +1,7 @@ | |||||||
| """passbook oauth_provider urls""" | """passbook oauth_provider urls""" | ||||||
|  |  | ||||||
| from django.urls import include, path | from django.urls import path | ||||||
|  | from oauth2_provider import views | ||||||
|  |  | ||||||
| from passbook.oauth_provider.views import oauth2 | from passbook.oauth_provider.views import oauth2 | ||||||
|  |  | ||||||
| @ -13,5 +14,8 @@ urlpatterns = [ | |||||||
|     path('authorize/permission_denied/', oauth2.OAuthPermissionDenied.as_view(), |     path('authorize/permission_denied/', oauth2.OAuthPermissionDenied.as_view(), | ||||||
|          name='oauth2-permission-denied'), |          name='oauth2-permission-denied'), | ||||||
|     # OAuth API |     # OAuth API | ||||||
|     path('', include('oauth2_provider.urls', namespace='oauth2_provider')), |     path("authorize/", views.AuthorizationView.as_view(), name="authorize"), | ||||||
|  |     path("token/", views.TokenView.as_view(), name="token"), | ||||||
|  |     path("revoke_token/", views.RevokeTokenView.as_view(), name="revoke-token"), | ||||||
|  |     path("introspect/", views.IntrospectTokenView.as_view(), name="introspect"), | ||||||
| ] | ] | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ from django.utils.translation import ugettext as _ | |||||||
| from oauth2_provider.views.base import AuthorizationView | from oauth2_provider.views.base import AuthorizationView | ||||||
|  |  | ||||||
| from passbook.audit.models import AuditEntry | from passbook.audit.models import AuditEntry | ||||||
|  | from passbook.core.models import Application | ||||||
| from passbook.core.views.access import AccessMixin | from passbook.core.views.access import AccessMixin | ||||||
| from passbook.core.views.utils import LoadingView, PermissionDeniedView | from passbook.core.views.utils import LoadingView, PermissionDeniedView | ||||||
| from passbook.oauth_provider.models import OAuth2Provider | from passbook.oauth_provider.models import OAuth2Provider | ||||||
| @ -38,14 +39,17 @@ class PassbookAuthorizationView(AccessMixin, AuthorizationView): | |||||||
|         # Get client_id to get provider, so we can update skip_authorization field |         # Get client_id to get provider, so we can update skip_authorization field | ||||||
|         client_id = request.GET.get('client_id') |         client_id = request.GET.get('client_id') | ||||||
|         provider = get_object_or_404(OAuth2Provider, client_id=client_id) |         provider = get_object_or_404(OAuth2Provider, client_id=client_id) | ||||||
|  |         try: | ||||||
|             application = self.provider_to_application(provider) |             application = self.provider_to_application(provider) | ||||||
|  |         except Application.DoesNotExist: | ||||||
|  |             return redirect('passbook_oauth_provider:oauth2-permission-denied') | ||||||
|         # Update field here so oauth-toolkit does work for us |         # Update field here so oauth-toolkit does work for us | ||||||
|         provider.skip_authorization = application.skip_authorization |         provider.skip_authorization = application.skip_authorization | ||||||
|         provider.save() |         provider.save() | ||||||
|         self._application = application |         self._application = application | ||||||
|         # Check permissions |         # Check permissions | ||||||
|         if not self.user_has_access(self._application, request.user): |         if not self.user_has_access(self._application, request.user): | ||||||
|             return redirect(reverse('passbook_oauth_provider:oauth2-permission-denied')) |             return redirect('passbook_oauth_provider:oauth2-permission-denied') | ||||||
|         actual_response = super().dispatch(request, *args, **kwargs) |         actual_response = super().dispatch(request, *args, **kwargs) | ||||||
|         if actual_response.status_code == 400: |         if actual_response.status_code == 400: | ||||||
|             LOGGER.debug(request.GET.get('redirect_uri')) |             LOGGER.debug(request.GET.get('redirect_uri')) | ||||||
|  | |||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook otp Header""" | """passbook otp Header""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
| @ -6,7 +6,7 @@ from logging import getLogger | |||||||
| from django.contrib import messages | from django.contrib import messages | ||||||
| from django.contrib.auth.mixins import LoginRequiredMixin | from django.contrib.auth.mixins import LoginRequiredMixin | ||||||
| from django.http import Http404, HttpRequest, HttpResponse | from django.http import Http404, HttpRequest, HttpResponse | ||||||
| from django.shortcuts import redirect | from django.shortcuts import get_object_or_404, redirect | ||||||
| from django.urls import reverse | from django.urls import reverse | ||||||
| from django.utils.translation import ugettext as _ | from django.utils.translation import ugettext as _ | ||||||
| from django.views import View | from django.views import View | ||||||
| @ -41,28 +41,27 @@ class UserSettingsView(LoginRequiredMixin, TemplateView): | |||||||
|         kwargs['state'] = totp_devices.exists() and static.exists() |         kwargs['state'] = totp_devices.exists() and static.exists() | ||||||
|         return kwargs |         return kwargs | ||||||
|  |  | ||||||
| class DisableView(LoginRequiredMixin, TemplateView): | class DisableView(LoginRequiredMixin, View): | ||||||
|     """Disable TOTP for user""" |     """Disable TOTP for user""" | ||||||
|     # TODO: Use Django DeleteView with custom delete? |  | ||||||
|     # def |  | ||||||
|     # # Delete all the devices for user |  | ||||||
|     # static = get_object_or_404(StaticDevice, user=request.user, confirmed=True) |  | ||||||
|     # static_tokens = StaticToken.objects.filter(device=static).order_by('token') |  | ||||||
|     # totp = TOTPDevice.objects.filter(user=request.user, confirmed=True) |  | ||||||
|     # static.delete() |  | ||||||
|     # totp.delete() |  | ||||||
|     # for token in static_tokens: |  | ||||||
|     #     token.delete() |  | ||||||
|     # messages.success(request, 'Successfully disabled TOTP') |  | ||||||
|     # # Create event with email notification |  | ||||||
|     # # Event.create( |  | ||||||
|     # #     user=request.user, |  | ||||||
|     # #     message=_('You disabled TOTP.'), |  | ||||||
|     # #     current=True, |  | ||||||
|     # #     request=request, |  | ||||||
|     # #     send_notification=True) |  | ||||||
|     # return redirect(reverse('passbook_core:overview')) |  | ||||||
|  |  | ||||||
|  |     def get(self, request, *args, **kwargs): | ||||||
|  |         """Delete all the devices for user""" | ||||||
|  |         static = get_object_or_404(StaticDevice, user=request.user, confirmed=True) | ||||||
|  |         static_tokens = StaticToken.objects.filter(device=static).order_by('token') | ||||||
|  |         totp = TOTPDevice.objects.filter(user=request.user, confirmed=True) | ||||||
|  |         static.delete() | ||||||
|  |         totp.delete() | ||||||
|  |         for token in static_tokens: | ||||||
|  |             token.delete() | ||||||
|  |         messages.success(request, 'Successfully disabled OTP') | ||||||
|  |         # Create event with email notification | ||||||
|  |         # Event.create( | ||||||
|  |         #     user=request.user, | ||||||
|  |         #     message=_('You disabled TOTP.'), | ||||||
|  |         #     current=True, | ||||||
|  |         #     request=request, | ||||||
|  |         #     send_notification=True) | ||||||
|  |         return redirect(reverse('passbook_otp:otp-user-settings')) | ||||||
|  |  | ||||||
| class EnableView(LoginRequiredMixin, FormView): | class EnableView(LoginRequiredMixin, FormView): | ||||||
|     """View to set up OTP""" |     """View to set up OTP""" | ||||||
|  | |||||||
							
								
								
									
										0
									
								
								passbook/pretend/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								passbook/pretend/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										11
									
								
								passbook/pretend/apps.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								passbook/pretend/apps.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,11 @@ | |||||||
|  | """passbook pretend config""" | ||||||
|  | from django.apps import AppConfig | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class PassbookPretendConfig(AppConfig): | ||||||
|  |     """passbook pretend config""" | ||||||
|  |  | ||||||
|  |     name = 'passbook.pretend' | ||||||
|  |     label = 'passbook_pretend' | ||||||
|  |     verbose_name = 'passbook Pretender' | ||||||
|  |     mountpoint = '' | ||||||
							
								
								
									
										16
									
								
								passbook/pretend/urls.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								passbook/pretend/urls.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,16 @@ | |||||||
|  | """passbook pretend urls""" | ||||||
|  | from django.urls import include, path | ||||||
|  | from oauth2_provider.views import TokenView | ||||||
|  |  | ||||||
|  | from passbook.oauth_provider.views.oauth2 import PassbookAuthorizationView | ||||||
|  | from passbook.pretend.views.github import GitHubUserView | ||||||
|  |  | ||||||
|  | github_urlpatterns = [ | ||||||
|  |     path('login/oauth/authorize', PassbookAuthorizationView.as_view(), name='github-authorize'), | ||||||
|  |     path('login/oauth/access_token', TokenView.as_view(), name='github-access-token'), | ||||||
|  |     path('user', GitHubUserView.as_view(), name='github-user'), | ||||||
|  | ] | ||||||
|  |  | ||||||
|  | urlpatterns = [ | ||||||
|  |     path('', include(github_urlpatterns)) | ||||||
|  | ] | ||||||
							
								
								
									
										0
									
								
								passbook/pretend/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								passbook/pretend/views/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										55
									
								
								passbook/pretend/views/github.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								passbook/pretend/views/github.py
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,55 @@ | |||||||
|  | """passbook pretend GitHub Views""" | ||||||
|  | from django.http import JsonResponse | ||||||
|  | from django.views import View | ||||||
|  |  | ||||||
|  |  | ||||||
|  | class GitHubUserView(View): | ||||||
|  |     """Emulate GitHub's /user API Endpoint""" | ||||||
|  |  | ||||||
|  |     def get(self, request): | ||||||
|  |         """Emulate GitHub's /user API Endpoint""" | ||||||
|  |         return JsonResponse({ | ||||||
|  |             "login": request.user.username, | ||||||
|  |             "id": request.user.pk, | ||||||
|  |             "node_id": "", | ||||||
|  |             "avatar_url": "", | ||||||
|  |             "gravatar_id": "", | ||||||
|  |             "url": "", | ||||||
|  |             "html_url": "", | ||||||
|  |             "followers_url": "", | ||||||
|  |             "following_url": "", | ||||||
|  |             "gists_url": "", | ||||||
|  |             "starred_url": "", | ||||||
|  |             "subscriptions_url": "", | ||||||
|  |             "organizations_url": "", | ||||||
|  |             "repos_url": "", | ||||||
|  |             "events_url": "", | ||||||
|  |             "received_events_url": "", | ||||||
|  |             "type": "User", | ||||||
|  |             "site_admin": False, | ||||||
|  |             "name": "%s %s" % (request.user.first_name, request.user.last_name), | ||||||
|  |             "company": "", | ||||||
|  |             "blog": "", | ||||||
|  |             "location": "", | ||||||
|  |             "email": request.user.email, | ||||||
|  |             "hireable": False, | ||||||
|  |             "bio": "", | ||||||
|  |             "public_repos": 0, | ||||||
|  |             "public_gists": 0, | ||||||
|  |             "followers": 0, | ||||||
|  |             "following": 0, | ||||||
|  |             "created_at": request.user.date_joined, | ||||||
|  |             "updated_at": request.user.date_joined, | ||||||
|  |             "private_gists": 0, | ||||||
|  |             "total_private_repos": 0, | ||||||
|  |             "owned_private_repos": 0, | ||||||
|  |             "disk_usage": 0, | ||||||
|  |             "collaborators": 0, | ||||||
|  |             "two_factor_authentication": True, | ||||||
|  |             "plan": { | ||||||
|  |                 "name": "None", | ||||||
|  |                 "space": 0, | ||||||
|  |                 "private_repos": 0, | ||||||
|  |                 "collaborators": 0 | ||||||
|  |             } | ||||||
|  |         }) | ||||||
| @ -1,2 +1,2 @@ | |||||||
| """passbook saml_idp Header""" | """passbook saml_idp Header""" | ||||||
| __version__ = '0.0.8-alpha' | __version__ = '0.0.10-alpha' | ||||||
|  | |||||||
| @ -11,7 +11,6 @@ | |||||||
| <header class="login-pf-header"> | <header class="login-pf-header"> | ||||||
|   <h1>{% trans 'Authorize Application' %}</h1> |   <h1>{% trans 'Authorize Application' %}</h1> | ||||||
| </header> | </header> | ||||||
| {% include 'partials/messages.html' %} |  | ||||||
| <form method="POST" action="{{ acs_url }}">> | <form method="POST" action="{{ acs_url }}">> | ||||||
|   {% csrf_token %} |   {% csrf_token %} | ||||||
|   <input type="hidden" name="ACSUrl" value="{{ acs_url }}"> |   <input type="hidden" name="ACSUrl" value="{{ acs_url }}"> | ||||||
|  | |||||||
		Reference in New Issue
	
	Block a user
	