all: implement black as code formatter

master
Jens Langhammer 2019-12-31 12:51:16 +01:00
parent 8eb3f0f708
commit 3bd1eadd51
298 changed files with 4825 additions and 3145 deletions

View File

@ -26,7 +26,7 @@ jobs:
run: pip install -U pip pipenv && pipenv install --dev run: pip install -U pip pipenv && pipenv install --dev
- name: Lint with pylint - name: Lint with pylint
run: pipenv run pylint passbook run: pipenv run pylint passbook
isort: black:
runs-on: [ubuntu-latest] runs-on: [ubuntu-latest]
steps: steps:
- uses: actions/checkout@v1 - uses: actions/checkout@v1
@ -41,8 +41,8 @@ jobs:
${{ runner.os }}-pipenv- ${{ runner.os }}-pipenv-
- name: Install dependencies - name: Install dependencies
run: pip install -U pip pipenv && pipenv install --dev run: pip install -U pip pipenv && pipenv install --dev
- name: Lint with isort - name: Lint with black
run: pipenv run isort -c run: pipenv run black --check passbook
prospector: prospector:
runs-on: [ubuntu-latest] runs-on: [ubuntu-latest]
steps: steps:

View File

@ -51,8 +51,11 @@ bumpversion = "*"
colorama = "*" colorama = "*"
coverage = "*" coverage = "*"
django-debug-toolbar = "*" django-debug-toolbar = "*"
isort = "*"
prospector = "*" prospector = "*"
pylint = "*" pylint = "*"
pylint-django = "*" pylint-django = "*"
unittest-xml-reporting = "*" unittest-xml-reporting = "*"
black = "*"
[pipenv]
allow_prereleases = true

126
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{ {
"_meta": { "_meta": {
"hash": { "hash": {
"sha256": "5d1d5f5f9664ce6ffb10e89d3780c9e04d4f8f372129baaf3293e44432a2f16d" "sha256": "138816efaba5be0b175cfd5b5e6a0b58e5ba551567f0efb441740344da3986d8"
}, },
"pipfile-spec": 6, "pipfile-spec": 6,
"requires": { "requires": {
@ -238,11 +238,11 @@
}, },
"django-prometheus": { "django-prometheus": {
"hashes": [ "hashes": [
"sha256:60f331788f9846891e9ea8d7ccd2928b1042e2e99c8d673f97e2b85f5bc20112", "sha256:f0657d4b887309086b71b55f6aa4a95f967b35fe115128b501f95422c423b12c",
"sha256:bb2d4f8acd681fa5787df77e7482391017f0090c70473bccd2aa7cad327800ad" "sha256:f645016ae5270ac2025a70788cd2bd636244a0c5705b323cc086994bf828181e"
], ],
"index": "pypi", "index": "pypi",
"version": "==1.1.0" "version": "==2.0.0.dev124"
}, },
"django-recaptcha": { "django-recaptcha": {
"hashes": [ "hashes": [
@ -685,20 +685,20 @@
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", "sha256:21a8e19e2007a4047ffabbd8f0ee32c0dabae3b7f4b6c645110ae53e7714b470",
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", "sha256:74ad685bfb065f4bdd36d24aa97092f04bcbb1179b5ffdd3d5f994023fb8c292",
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", "sha256:79c3ba1da22e61c2a71aaa382c57518ab492278c8974c40187b900b50f3e0282",
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", "sha256:94ad913ab3fd967d14ecffda8182d7d0e1f7dd919b352773c492ec51890d3224",
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", "sha256:998db501e3a627c3e5678d6505f0e182d1529545df289db036cdc717f35d8058",
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", "sha256:9b69d4645bff5820713e8912bc61c4277dc127a6f8c197b52b6436503c42600f",
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", "sha256:9da13b536533518343a04f3c6564782ec8a13c705310b26b4832d77fa4d92a47",
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", "sha256:a76159f13b47fb44fb2acac8fef798a1940dd31b4acec6f4560bd11b2d92d31b",
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", "sha256:a9e9175c1e47a089a2b45d9e2afc6aae1f1f725538c32eec761894a42ba1227f",
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", "sha256:ea51ce7b96646ecd3bb12c2702e570c2bd7dd4d9f146db7fa83c5008ede35f66",
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4" "sha256:ffbaaa05de60fc444eda3f6300d1af27d965b09b67f1fb4ebcc88dd0fb4ab1b4"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.2" "version": "==5.3b1"
}, },
"qrcode": { "qrcode": {
"hashes": [ "hashes": [
@ -858,6 +858,13 @@
} }
}, },
"develop": { "develop": {
"appdirs": {
"hashes": [
"sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92",
"sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"
],
"version": "==1.4.3"
},
"asgiref": { "asgiref": {
"hashes": [ "hashes": [
"sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0", "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0",
@ -872,6 +879,13 @@
], ],
"version": "==2.3.3" "version": "==2.3.3"
}, },
"attrs": {
"hashes": [
"sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c",
"sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"
],
"version": "==19.3.0"
},
"autopep8": { "autopep8": {
"hashes": [ "hashes": [
"sha256:4d8eec30cc81bc5617dbf1218201d770dc35629363547f17577c61683ccfb3ee" "sha256:4d8eec30cc81bc5617dbf1218201d770dc35629363547f17577c61683ccfb3ee"
@ -887,6 +901,14 @@
"index": "pypi", "index": "pypi",
"version": "==1.6.2" "version": "==1.6.2"
}, },
"black": {
"hashes": [
"sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b",
"sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"
],
"index": "pypi",
"version": "==19.10b0"
},
"bumpversion": { "bumpversion": {
"hashes": [ "hashes": [
"sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e", "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e",
@ -895,6 +917,13 @@
"index": "pypi", "index": "pypi",
"version": "==0.5.3" "version": "==0.5.3"
}, },
"click": {
"hashes": [
"sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",
"sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"
],
"version": "==7.0"
},
"colorama": { "colorama": {
"hashes": [ "hashes": [
"sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff", "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff",
@ -981,7 +1010,6 @@
"sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1", "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1",
"sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd" "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"
], ],
"index": "pypi",
"version": "==4.3.21" "version": "==4.3.21"
}, },
"lazy-object-proxy": { "lazy-object-proxy": {
@ -1017,6 +1045,13 @@
], ],
"version": "==0.6.1" "version": "==0.6.1"
}, },
"pathspec": {
"hashes": [
"sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424",
"sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"
],
"version": "==0.7.0"
},
"pbr": { "pbr": {
"hashes": [ "hashes": [
"sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b", "sha256:139d2625547dbfa5fb0b81daebb39601c478c21956dc57e2e07b74450a8c506b",
@ -1103,20 +1138,46 @@
}, },
"pyyaml": { "pyyaml": {
"hashes": [ "hashes": [
"sha256:0e7f69397d53155e55d10ff68fdfb2cf630a35e6daf65cf0bdeaf04f127c09dc", "sha256:21a8e19e2007a4047ffabbd8f0ee32c0dabae3b7f4b6c645110ae53e7714b470",
"sha256:2e9f0b7c5914367b0916c3c104a024bb68f269a486b9d04a2e8ac6f6597b7803", "sha256:74ad685bfb065f4bdd36d24aa97092f04bcbb1179b5ffdd3d5f994023fb8c292",
"sha256:35ace9b4147848cafac3db142795ee42deebe9d0dad885ce643928e88daebdcc", "sha256:79c3ba1da22e61c2a71aaa382c57518ab492278c8974c40187b900b50f3e0282",
"sha256:38a4f0d114101c58c0f3a88aeaa44d63efd588845c5a2df5290b73db8f246d15", "sha256:94ad913ab3fd967d14ecffda8182d7d0e1f7dd919b352773c492ec51890d3224",
"sha256:483eb6a33b671408c8529106df3707270bfacb2447bf8ad856a4b4f57f6e3075", "sha256:998db501e3a627c3e5678d6505f0e182d1529545df289db036cdc717f35d8058",
"sha256:4b6be5edb9f6bb73680f5bf4ee08ff25416d1400fbd4535fe0069b2994da07cd", "sha256:9b69d4645bff5820713e8912bc61c4277dc127a6f8c197b52b6436503c42600f",
"sha256:7f38e35c00e160db592091751d385cd7b3046d6d51f578b29943225178257b31", "sha256:9da13b536533518343a04f3c6564782ec8a13c705310b26b4832d77fa4d92a47",
"sha256:8100c896ecb361794d8bfdb9c11fce618c7cf83d624d73d5ab38aef3bc82d43f", "sha256:a76159f13b47fb44fb2acac8fef798a1940dd31b4acec6f4560bd11b2d92d31b",
"sha256:c0ee8eca2c582d29c3c2ec6e2c4f703d1b7f1fb10bc72317355a746057e7346c", "sha256:a9e9175c1e47a089a2b45d9e2afc6aae1f1f725538c32eec761894a42ba1227f",
"sha256:e4c015484ff0ff197564917b4b4246ca03f411b9bd7f16e02a2f586eb48b6d04", "sha256:ea51ce7b96646ecd3bb12c2702e570c2bd7dd4d9f146db7fa83c5008ede35f66",
"sha256:ebc4ed52dcc93eeebeae5cf5deb2ae4347b3a81c3fa12b0b8c976544829396a4" "sha256:ffbaaa05de60fc444eda3f6300d1af27d965b09b67f1fb4ebcc88dd0fb4ab1b4"
], ],
"index": "pypi", "index": "pypi",
"version": "==5.2" "version": "==5.3b1"
},
"regex": {
"hashes": [
"sha256:032fdcc03406e1a6485ec09b826eac78732943840c4b29e503b789716f051d8d",
"sha256:0e6cf1e747f383f52a0964452658c04300a9a01e8a89c55ea22813931b580aa8",
"sha256:106e25a841921d8259dcef2a42786caae35bc750fb996f830065b3dfaa67b77e",
"sha256:1768cf42a78a11dae63152685e7a1d90af7a8d71d2d4f6d2387edea53a9e0588",
"sha256:27d1bd20d334f50b7ef078eba0f0756a640fd25f5f1708d3b5bed18a5d6bced9",
"sha256:29b20f66f2e044aafba86ecf10a84e611b4667643c42baa004247f5dfef4f90b",
"sha256:4850c78b53acf664a6578bba0e9ebeaf2807bb476c14ec7e0f936f2015133cae",
"sha256:57eacd38a5ec40ed7b19a968a9d01c0d977bda55664210be713e750dd7b33540",
"sha256:724eb24b92fc5fdc1501a1b4df44a68b9c1dda171c8ef8736799e903fb100f63",
"sha256:77ae8d926f38700432807ba293d768ba9e7652df0cbe76df2843b12f80f68885",
"sha256:78b3712ec529b2a71731fbb10b907b54d9c53a17ca589b42a578bc1e9a2c82ea",
"sha256:7bbbdbada3078dc360d4692a9b28479f569db7fc7f304b668787afc9feb38ec8",
"sha256:8d9ef7f6c403e35e73b7fc3cde9f6decdc43b1cb2ff8d058c53b9084bfcb553e",
"sha256:a83049eb717ae828ced9cf607845929efcb086a001fc8af93ff15c50012a5716",
"sha256:adc35d38952e688535980ae2109cad3a109520033642e759f987cf47fe278aa1",
"sha256:c29a77ad4463f71a506515d9ec3a899ed026b4b015bf43245c919ff36275444b",
"sha256:cfd31b3300fefa5eecb2fe596c6dee1b91b3a05ece9d5cfd2631afebf6c6fadd",
"sha256:d3ee0b035816e0520fac928de31b6572106f0d75597f6fa3206969a02baba06f",
"sha256:d508875793efdf6bab3d47850df8f40d4040ae9928d9d80864c1768d6aeaf8e3",
"sha256:ef0b828a7e22e58e06a1cceddba7b4665c6af8afeb22a0d8083001330572c147",
"sha256:faad39fdbe2c2ccda9846cd21581063086330efafa47d87afea4073a08128656"
],
"version": "==2019.12.20"
}, },
"requirements-detector": { "requirements-detector": {
"hashes": [ "hashes": [
@ -1165,6 +1226,13 @@
], ],
"version": "==1.31.0" "version": "==1.31.0"
}, },
"toml": {
"hashes": [
"sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c",
"sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"
],
"version": "==0.10.0"
},
"typed-ast": { "typed-ast": {
"hashes": [ "hashes": [
"sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161", "sha256:1170afa46a3799e18b4c977777ce137bb53c7485379d9706af8a59f2ea1aa161",

View File

@ -1,2 +1,2 @@
"""passbook""" """passbook"""
__version__ = '0.7.5-beta' __version__ = "0.7.5-beta"

View File

@ -5,7 +5,7 @@ from django.apps import AppConfig
class PassbookAdminConfig(AppConfig): class PassbookAdminConfig(AppConfig):
"""passbook admin app config""" """passbook admin app config"""
name = 'passbook.admin' name = "passbook.admin"
label = 'passbook_admin' label = "passbook_admin"
mountpoint = 'administration/' mountpoint = "administration/"
verbose_name = 'passbook Admin' verbose_name = "passbook Admin"

View File

@ -16,7 +16,7 @@ class YAMLField(forms.CharField):
"""Django's JSON Field converted to YAML""" """Django's JSON Field converted to YAML"""
default_error_messages = { default_error_messages = {
'invalid': _("'%(value)s' value must be valid YAML."), "invalid": _("'%(value)s' value must be valid YAML."),
} }
widget = forms.Textarea widget = forms.Textarea
@ -31,9 +31,7 @@ class YAMLField(forms.CharField):
converted = yaml.safe_load(value) converted = yaml.safe_load(value)
except yaml.YAMLError: except yaml.YAMLError:
raise forms.ValidationError( raise forms.ValidationError(
self.error_messages['invalid'], self.error_messages["invalid"], code="invalid", params={"value": value},
code='invalid',
params={'value': value},
) )
if isinstance(converted, str): if isinstance(converted, str):
return YAMLString(converted) return YAMLString(converted)

View File

@ -9,29 +9,32 @@ class TagModelForm(forms.ModelForm):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
# Check if we have an instance, load tags otherwise use an empty dict # Check if we have an instance, load tags otherwise use an empty dict
instance = kwargs.get('instance', None) instance = kwargs.get("instance", None)
tags = instance.tags if instance else {} tags = instance.tags if instance else {}
# Make sure all predefined tags exist in tags, and set default if they don't # Make sure all predefined tags exist in tags, and set default if they don't
predefined_tags = self._meta.model().get_predefined_tags() # pylint: disable=no-member predefined_tags = (
self._meta.model().get_predefined_tags()
) # pylint: disable=no-member
for key, value in predefined_tags.items(): for key, value in predefined_tags.items():
if key not in tags: if key not in tags:
tags[key] = value tags[key] = value
# Format JSON # Format JSON
kwargs['initial']['tags'] = tags kwargs["initial"]["tags"] = tags
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
def clean_tags(self): def clean_tags(self):
"""Make sure all required tags are set""" """Make sure all required tags are set"""
if hasattr(self.instance, 'get_required_keys') and hasattr(self.instance, 'tags'): if hasattr(self.instance, "get_required_keys") and hasattr(
self.instance, "tags"
):
for key in self.instance.get_required_keys(): for key in self.instance.get_required_keys():
if key not in self.cleaned_data.get('tags'): if key not in self.cleaned_data.get("tags"):
raise forms.ValidationError("Tag %s missing." % key) raise forms.ValidationError("Tag %s missing." % key)
return self.cleaned_data.get('tags') return self.cleaned_data.get("tags")
# pylint: disable=too-few-public-methods # pylint: disable=too-few-public-methods
class TagModelFormMeta: class TagModelFormMeta:
"""Base Meta class that uses the YAMLField""" """Base Meta class that uses the YAMLField"""
field_classes = { field_classes = {"tags": YAMLField}
'tags': YAMLField
}

View File

@ -1,7 +1,7 @@
"""passbook core source form fields""" """passbook core source form fields"""
# from django import forms # from django import forms
SOURCE_FORM_FIELDS = ['name', 'slug', 'enabled', 'policies'] SOURCE_FORM_FIELDS = ["name", "slug", "enabled", "policies"]
SOURCE_SERIALIZER_FIELDS = ['pk', 'name', 'slug', 'enabled', 'policies'] SOURCE_SERIALIZER_FIELDS = ["pk", "name", "slug", "enabled", "policies"]
# class SourceForm(forms.Form) # class SourceForm(forms.Form)

View File

@ -12,10 +12,10 @@ class UserForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ['username', 'name', 'email', 'is_staff', 'is_active', 'attributes'] fields = ["username", "name", "email", "is_staff", "is_active", "attributes"]
widgets = { widgets = {
'name': forms.TextInput, "name": forms.TextInput,
} }
field_classes = { field_classes = {
'attributes': YAMLField, "attributes": YAMLField,
} }

View File

@ -11,15 +11,16 @@ def impersonate(get_response):
# User is superuser and has __impersonate ID set # User is superuser and has __impersonate ID set
if request.user.is_superuser and "__impersonate" in request.GET: if request.user.is_superuser and "__impersonate" in request.GET:
request.session['impersonate_id'] = request.GET["__impersonate"] request.session["impersonate_id"] = request.GET["__impersonate"]
# user wants to stop impersonation # user wants to stop impersonation
elif "__unimpersonate" in request.GET and 'impersonate_id' in request.session: elif "__unimpersonate" in request.GET and "impersonate_id" in request.session:
del request.session['impersonate_id'] del request.session["impersonate_id"]
# Actually impersonate user # Actually impersonate user
if request.user.is_superuser and 'impersonate_id' in request.session: if request.user.is_superuser and "impersonate_id" in request.session:
request.user = User.objects.get(pk=request.session['impersonate_id']) request.user = User.objects.get(pk=request.session["impersonate_id"])
response = get_response(request) response = get_response(request)
return response return response
return middleware return middleware

View File

@ -1,5 +1,5 @@
"""passbook admin settings""" """passbook admin settings"""
MIDDLEWARE = [ MIDDLEWARE = [
'passbook.admin.middleware.impersonate', "passbook.admin.middleware.impersonate",
] ]

View File

@ -10,10 +10,11 @@ from passbook.lib.utils.template import render_to_string
register = template.Library() register = template.Library()
LOGGER = get_logger() LOGGER = get_logger()
@register.simple_tag() @register.simple_tag()
def get_links(model_instance): def get_links(model_instance):
"""Find all link_ methods on an object instance, run them and return as dict""" """Find all link_ methods on an object instance, run them and return as dict"""
prefix = 'link_' prefix = "link_"
links = {} links = {}
if not isinstance(model_instance, Model): if not isinstance(model_instance, Model):
@ -21,9 +22,11 @@ def get_links(model_instance):
return links return links
try: try:
for name, method in inspect.getmembers(model_instance, predicate=inspect.ismethod): for name, method in inspect.getmembers(
model_instance, predicate=inspect.ismethod
):
if name.startswith(prefix): if name.startswith(prefix):
human_name = name.replace(prefix, '').replace('_', ' ').capitalize() human_name = name.replace(prefix, "").replace("_", " ").capitalize()
link = method() link = method()
if link: if link:
links[human_name] = link links[human_name] = link
@ -36,7 +39,7 @@ def get_links(model_instance):
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def get_htmls(context, model_instance): def get_htmls(context, model_instance):
"""Find all html_ methods on an object instance, run them and return as dict""" """Find all html_ methods on an object instance, run them and return as dict"""
prefix = 'html_' prefix = "html_"
htmls = [] htmls = []
if not isinstance(model_instance, Model): if not isinstance(model_instance, Model):
@ -44,9 +47,11 @@ def get_htmls(context, model_instance):
return htmls return htmls
try: try:
for name, method in inspect.getmembers(model_instance, predicate=inspect.ismethod): for name, method in inspect.getmembers(
model_instance, predicate=inspect.ismethod
):
if name.startswith(prefix): if name.startswith(prefix):
template, _context = method(context.get('request')) template, _context = method(context.get("request"))
htmls.append(render_to_string(template, _context)) htmls.append(render_to_string(template, _context))
except NotImplementedError: except NotImplementedError:
pass pass

View File

@ -1,82 +1,157 @@
"""passbook URL Configuration""" """passbook URL Configuration"""
from django.urls import path from django.urls import path
from passbook.admin.views import (applications, audit, debug, factors, groups, from passbook.admin.views import (
invitations, overview, policy, applications,
property_mapping, providers, sources, users) audit,
debug,
factors,
groups,
invitations,
overview,
policy,
property_mapping,
providers,
sources,
users,
)
urlpatterns = [ urlpatterns = [
path('', overview.AdministrationOverviewView.as_view(), name='overview'), path("", overview.AdministrationOverviewView.as_view(), name="overview"),
# Applications # Applications
path('applications/', applications.ApplicationListView.as_view(), path(
name='applications'), "applications/", applications.ApplicationListView.as_view(), name="applications"
path('applications/create/', applications.ApplicationCreateView.as_view(), ),
name='application-create'), path(
path('applications/<uuid:pk>/update/', "applications/create/",
applications.ApplicationUpdateView.as_view(), name='application-update'), applications.ApplicationCreateView.as_view(),
path('applications/<uuid:pk>/delete/', name="application-create",
applications.ApplicationDeleteView.as_view(), name='application-delete'), ),
path(
"applications/<uuid:pk>/update/",
applications.ApplicationUpdateView.as_view(),
name="application-update",
),
path(
"applications/<uuid:pk>/delete/",
applications.ApplicationDeleteView.as_view(),
name="application-delete",
),
# Sources # Sources
path('sources/', sources.SourceListView.as_view(), name='sources'), path("sources/", sources.SourceListView.as_view(), name="sources"),
path('sources/create/', sources.SourceCreateView.as_view(), name='source-create'), path("sources/create/", sources.SourceCreateView.as_view(), name="source-create"),
path('sources/<uuid:pk>/update/', sources.SourceUpdateView.as_view(), name='source-update'), path(
path('sources/<uuid:pk>/delete/', sources.SourceDeleteView.as_view(), name='source-delete'), "sources/<uuid:pk>/update/",
sources.SourceUpdateView.as_view(),
name="source-update",
),
path(
"sources/<uuid:pk>/delete/",
sources.SourceDeleteView.as_view(),
name="source-delete",
),
# Policies # Policies
path('policies/', policy.PolicyListView.as_view(), name='policies'), path("policies/", policy.PolicyListView.as_view(), name="policies"),
path('policies/create/', policy.PolicyCreateView.as_view(), name='policy-create'), path("policies/create/", policy.PolicyCreateView.as_view(), name="policy-create"),
path('policies/<uuid:pk>/update/', policy.PolicyUpdateView.as_view(), name='policy-update'), path(
path('policies/<uuid:pk>/delete/', policy.PolicyDeleteView.as_view(), name='policy-delete'), "policies/<uuid:pk>/update/",
path('policies/<uuid:pk>/test/', policy.PolicyTestView.as_view(), name='policy-test'), policy.PolicyUpdateView.as_view(),
name="policy-update",
),
path(
"policies/<uuid:pk>/delete/",
policy.PolicyDeleteView.as_view(),
name="policy-delete",
),
path(
"policies/<uuid:pk>/test/", policy.PolicyTestView.as_view(), name="policy-test"
),
# Providers # Providers
path('providers/', providers.ProviderListView.as_view(), name='providers'), path("providers/", providers.ProviderListView.as_view(), name="providers"),
path('providers/create/', path(
providers.ProviderCreateView.as_view(), name='provider-create'), "providers/create/",
path('providers/<int:pk>/update/', providers.ProviderCreateView.as_view(),
providers.ProviderUpdateView.as_view(), name='provider-update'), name="provider-create",
path('providers/<int:pk>/delete/', ),
providers.ProviderDeleteView.as_view(), name='provider-delete'), path(
"providers/<int:pk>/update/",
providers.ProviderUpdateView.as_view(),
name="provider-update",
),
path(
"providers/<int:pk>/delete/",
providers.ProviderDeleteView.as_view(),
name="provider-delete",
),
# Factors # Factors
path('factors/', factors.FactorListView.as_view(), name='factors'), path("factors/", factors.FactorListView.as_view(), name="factors"),
path('factors/create/', path("factors/create/", factors.FactorCreateView.as_view(), name="factor-create"),
factors.FactorCreateView.as_view(), name='factor-create'), path(
path('factors/<uuid:pk>/update/', "factors/<uuid:pk>/update/",
factors.FactorUpdateView.as_view(), name='factor-update'), factors.FactorUpdateView.as_view(),
path('factors/<uuid:pk>/delete/', name="factor-update",
factors.FactorDeleteView.as_view(), name='factor-delete'), ),
path(
"factors/<uuid:pk>/delete/",
factors.FactorDeleteView.as_view(),
name="factor-delete",
),
# Factors # Factors
path('property-mappings/', property_mapping.PropertyMappingListView.as_view(), path(
name='property-mappings'), "property-mappings/",
path('property-mappings/create/', property_mapping.PropertyMappingListView.as_view(),
property_mapping.PropertyMappingCreateView.as_view(), name='property-mapping-create'), name="property-mappings",
path('property-mappings/<uuid:pk>/update/', ),
property_mapping.PropertyMappingUpdateView.as_view(), name='property-mapping-update'), path(
path('property-mappings/<uuid:pk>/delete/', "property-mappings/create/",
property_mapping.PropertyMappingDeleteView.as_view(), name='property-mapping-delete'), property_mapping.PropertyMappingCreateView.as_view(),
name="property-mapping-create",
),
path(
"property-mappings/<uuid:pk>/update/",
property_mapping.PropertyMappingUpdateView.as_view(),
name="property-mapping-update",
),
path(
"property-mappings/<uuid:pk>/delete/",
property_mapping.PropertyMappingDeleteView.as_view(),
name="property-mapping-delete",
),
# Invitations # Invitations
path('invitations/', invitations.InvitationListView.as_view(), name='invitations'), path("invitations/", invitations.InvitationListView.as_view(), name="invitations"),
path('invitations/create/', path(
invitations.InvitationCreateView.as_view(), name='invitation-create'), "invitations/create/",
path('invitations/<uuid:pk>/delete/', invitations.InvitationCreateView.as_view(),
invitations.InvitationDeleteView.as_view(), name='invitation-delete'), name="invitation-create",
),
path(
"invitations/<uuid:pk>/delete/",
invitations.InvitationDeleteView.as_view(),
name="invitation-delete",
),
# Users # Users
path('users/', users.UserListView.as_view(), path("users/", users.UserListView.as_view(), name="users"),
name='users'), path("users/create/", users.UserCreateView.as_view(), name="user-create"),
path('users/create/', users.UserCreateView.as_view(), name='user-create'), path("users/<int:pk>/update/", users.UserUpdateView.as_view(), name="user-update"),
path('users/<int:pk>/update/', path("users/<int:pk>/delete/", users.UserDeleteView.as_view(), name="user-delete"),
users.UserUpdateView.as_view(), name='user-update'), path(
path('users/<int:pk>/delete/', "users/<int:pk>/reset/",
users.UserDeleteView.as_view(), name='user-delete'), users.UserPasswordResetView.as_view(),
path('users/<int:pk>/reset/', name="user-password-reset",
users.UserPasswordResetView.as_view(), name='user-password-reset'), ),
# Groups # Groups
path('group/', groups.GroupListView.as_view(), name='group'), path("group/", groups.GroupListView.as_view(), name="group"),
path('group/create/', groups.GroupCreateView.as_view(), name='group-create'), path("group/create/", groups.GroupCreateView.as_view(), name="group-create"),
path('group/<uuid:pk>/update/', groups.GroupUpdateView.as_view(), name='group-update'), path(
path('group/<uuid:pk>/delete/', groups.GroupDeleteView.as_view(), name='group-delete'), "group/<uuid:pk>/update/", groups.GroupUpdateView.as_view(), name="group-update"
),
path(
"group/<uuid:pk>/delete/", groups.GroupDeleteView.as_view(), name="group-delete"
),
# Audit Log # Audit Log
path('audit/', audit.EventListView.as_view(), name='audit-log'), path("audit/", audit.EventListView.as_view(), name="audit-log"),
# Groups # Groups
path('groups/', groups.GroupListView.as_view(), name='groups'), path("groups/", groups.GroupListView.as_view(), name="groups"),
# Debug # Debug
path('debug/request/', debug.DebugRequestView.as_view(), name='debug-request'), path("debug/request/", debug.DebugRequestView.as_view(), name="debug-request"),
] ]

View File

@ -1,8 +1,9 @@
"""passbook Application administration""" """passbook Application administration"""
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.contrib.auth.mixins import \ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
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 _
@ -18,55 +19,61 @@ class ApplicationListView(LoginRequiredMixin, PermissionListMixin, ListView):
"""Show list of all applications""" """Show list of all applications"""
model = Application model = Application
permission_required = 'passbook_core.view_application' permission_required = "passbook_core.view_application"
ordering = 'name' ordering = "name"
paginate_by = 40 paginate_by = 40
template_name = 'administration/application/list.html' template_name = "administration/application/list.html"
def get_queryset(self): def get_queryset(self):
return super().get_queryset().select_subclasses() return super().get_queryset().select_subclasses()
class ApplicationCreateView(SuccessMessageMixin, LoginRequiredMixin, class ApplicationCreateView(
DjangoPermissionRequiredMixin, CreateAssignPermView): SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create new Application""" """Create new Application"""
model = Application model = Application
form_class = ApplicationForm form_class = ApplicationForm
permission_required = 'passbook_core.add_application' permission_required = "passbook_core.add_application"
template_name = 'generic/create.html' template_name = "generic/create.html"
success_url = reverse_lazy('passbook_admin:applications') success_url = reverse_lazy("passbook_admin:applications")
success_message = _('Successfully created Application') success_message = _("Successfully created Application")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['type'] = 'Application' kwargs["type"] = "Application"
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class ApplicationUpdateView(SuccessMessageMixin, LoginRequiredMixin, class ApplicationUpdateView(
PermissionRequiredMixin, UpdateView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
"""Update application""" """Update application"""
model = Application model = Application
form_class = ApplicationForm form_class = ApplicationForm
permission_required = 'passbook_core.change_application' permission_required = "passbook_core.change_application"
template_name = 'generic/update.html' template_name = "generic/update.html"
success_url = reverse_lazy('passbook_admin:applications') success_url = reverse_lazy("passbook_admin:applications")
success_message = _('Successfully updated Application') success_message = _("Successfully updated Application")
class ApplicationDeleteView(SuccessMessageMixin, LoginRequiredMixin, class ApplicationDeleteView(
PermissionRequiredMixin, DeleteView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
):
"""Delete application""" """Delete application"""
model = Application model = Application
permission_required = 'passbook_core.delete_application' permission_required = "passbook_core.delete_application"
template_name = 'generic/delete.html' template_name = "generic/delete.html"
success_url = reverse_lazy('passbook_admin:applications') success_url = reverse_lazy("passbook_admin:applications")
success_message = _('Successfully deleted Application') success_message = _("Successfully deleted Application")
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message) messages.success(self.request, self.success_message)

View File

@ -9,10 +9,10 @@ class EventListView(PermissionListMixin, ListView):
"""Show list of all invitations""" """Show list of all invitations"""
model = Event model = Event
template_name = 'administration/audit/list.html' template_name = "administration/audit/list.html"
permission_required = 'passbook_audit.view_event' permission_required = "passbook_audit.view_event"
ordering = '-created' ordering = "-created"
paginate_by = 10 paginate_by = 10
def get_queryset(self): def get_queryset(self):
return Event.objects.all().order_by('-created') return Event.objects.all().order_by("-created")

View File

@ -6,10 +6,10 @@ from django.views.generic import TemplateView
class DebugRequestView(LoginRequiredMixin, TemplateView): class DebugRequestView(LoginRequiredMixin, TemplateView):
"""Show debug info about request""" """Show debug info about request"""
template_name = 'administration/debug/request.html' template_name = "administration/debug/request.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['request_dict'] = {} kwargs["request_dict"] = {}
for key in dir(self.request): for key in dir(self.request):
kwargs['request_dict'][key] = getattr(self.request, key) kwargs["request_dict"][key] = getattr(self.request, key)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -1,8 +1,9 @@
"""passbook Factor administration""" """passbook Factor administration"""
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.contrib.auth.mixins import \ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
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
@ -18,62 +19,69 @@ from passbook.lib.views import CreateAssignPermView
def all_subclasses(cls): def all_subclasses(cls):
"""Recursively return all subclassess of cls""" """Recursively return all subclassess of cls"""
return set(cls.__subclasses__()).union( return set(cls.__subclasses__()).union(
[s for c in cls.__subclasses__() for s in all_subclasses(c)]) [s for c in cls.__subclasses__() for s in all_subclasses(c)]
)
class FactorListView(LoginRequiredMixin, PermissionListMixin, ListView): class FactorListView(LoginRequiredMixin, PermissionListMixin, ListView):
"""Show list of all factors""" """Show list of all factors"""
model = Factor model = Factor
template_name = 'administration/factor/list.html' template_name = "administration/factor/list.html"
permission_required = 'passbook_core.view_factor' permission_required = "passbook_core.view_factor"
ordering = 'order' ordering = "order"
paginate_by = 40 paginate_by = 40
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['types'] = { kwargs["types"] = {
x.__name__: x._meta.verbose_name for x in all_subclasses(Factor)} x.__name__: x._meta.verbose_name for x in all_subclasses(Factor)
}
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def get_queryset(self): def get_queryset(self):
return super().get_queryset().select_subclasses() return super().get_queryset().select_subclasses()
class FactorCreateView(SuccessMessageMixin, LoginRequiredMixin, class FactorCreateView(
DjangoPermissionRequiredMixin, CreateAssignPermView): SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create new Factor""" """Create new Factor"""
model = Factor model = Factor
template_name = 'generic/create.html' template_name = "generic/create.html"
permission_required = 'passbook_core.add_factor' permission_required = "passbook_core.add_factor"
success_url = reverse_lazy('passbook_admin:factors') success_url = reverse_lazy("passbook_admin:factors")
success_message = _('Successfully created Factor') success_message = _("Successfully created Factor")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
factor_type = self.request.GET.get('type') factor_type = self.request.GET.get("type")
model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type) model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
kwargs['type'] = model._meta.verbose_name kwargs["type"] = model._meta.verbose_name
return kwargs return kwargs
def get_form_class(self): def get_form_class(self):
factor_type = self.request.GET.get('type') factor_type = self.request.GET.get("type")
model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type) model = next(x for x in all_subclasses(Factor) if x.__name__ == factor_type)
if not model: if not model:
raise Http404 raise Http404
return path_to_class(model.form) return path_to_class(model.form)
class FactorUpdateView(SuccessMessageMixin, LoginRequiredMixin, class FactorUpdateView(
PermissionRequiredMixin, UpdateView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
"""Update factor""" """Update factor"""
model = Factor model = Factor
permission_required = 'passbook_core.update_application' permission_required = "passbook_core.update_application"
template_name = 'generic/update.html' template_name = "generic/update.html"
success_url = reverse_lazy('passbook_admin:factors') success_url = reverse_lazy("passbook_admin:factors")
success_message = _('Successfully updated Factor') success_message = _("Successfully updated Factor")
def get_form_class(self): def get_form_class(self):
form_class_path = self.get_object().form form_class_path = self.get_object().form
@ -81,21 +89,26 @@ class FactorUpdateView(SuccessMessageMixin, LoginRequiredMixin,
return form_class return form_class
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()
)
class FactorDeleteView(SuccessMessageMixin, LoginRequiredMixin, class FactorDeleteView(
PermissionRequiredMixin, DeleteView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
):
"""Delete factor""" """Delete factor"""
model = Factor model = Factor
template_name = 'generic/delete.html' template_name = "generic/delete.html"
permission_required = 'passbook_core.delete_factor' permission_required = "passbook_core.delete_factor"
success_url = reverse_lazy('passbook_admin:factors') success_url = reverse_lazy("passbook_admin:factors")
success_message = _('Successfully deleted 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): def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message) messages.success(self.request, self.success_message)

View File

@ -1,8 +1,9 @@
"""passbook Group administration""" """passbook Group administration"""
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.contrib.auth.mixins import \ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
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 _
@ -18,40 +19,45 @@ class GroupListView(LoginRequiredMixin, PermissionListMixin, ListView):
"""Show list of all groups""" """Show list of all groups"""
model = Group model = Group
permission_required = 'passbook_core.view_group' permission_required = "passbook_core.view_group"
ordering = 'name' ordering = "name"
paginate_by = 40 paginate_by = 40
template_name = 'administration/group/list.html' template_name = "administration/group/list.html"
class GroupCreateView(SuccessMessageMixin, LoginRequiredMixin, class GroupCreateView(
DjangoPermissionRequiredMixin, CreateAssignPermView): SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create new Group""" """Create new Group"""
model = Group model = Group
form_class = GroupForm form_class = GroupForm
permission_required = 'passbook_core.add_group' permission_required = "passbook_core.add_group"
template_name = 'generic/create.html' template_name = "generic/create.html"
success_url = reverse_lazy('passbook_admin:groups') success_url = reverse_lazy("passbook_admin:groups")
success_message = _('Successfully created Group') success_message = _("Successfully created Group")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['type'] = 'Group' kwargs["type"] = "Group"
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class GroupUpdateView(SuccessMessageMixin, LoginRequiredMixin, class GroupUpdateView(
PermissionRequiredMixin, UpdateView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
"""Update group""" """Update group"""
model = Group model = Group
form_class = GroupForm form_class = GroupForm
permission_required = 'passbook_core.change_group' permission_required = "passbook_core.change_group"
template_name = 'generic/update.html' template_name = "generic/update.html"
success_url = reverse_lazy('passbook_admin:groups') success_url = reverse_lazy("passbook_admin:groups")
success_message = _('Successfully updated Group') success_message = _("Successfully updated Group")
class GroupDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView): class GroupDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView):
@ -59,9 +65,9 @@ class GroupDeleteView(SuccessMessageMixin, LoginRequiredMixin, DeleteView):
model = Group model = Group
template_name = 'generic/delete.html' template_name = "generic/delete.html"
success_url = reverse_lazy('passbook_admin:groups') success_url = reverse_lazy("passbook_admin:groups")
success_message = _('Successfully deleted Group') success_message = _("Successfully deleted Group")
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message) messages.success(self.request, self.success_message)

View File

@ -1,8 +1,9 @@
"""passbook Invitation administration""" """passbook Invitation administration"""
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.contrib.auth.mixins import \ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
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
@ -20,47 +21,49 @@ class InvitationListView(LoginRequiredMixin, PermissionListMixin, ListView):
"""Show list of all invitations""" """Show list of all invitations"""
model = Invitation model = Invitation
permission_required = 'passbook_core.view_invitation' permission_required = "passbook_core.view_invitation"
template_name = 'administration/invitation/list.html' template_name = "administration/invitation/list.html"
class InvitationCreateView(SuccessMessageMixin, LoginRequiredMixin, class InvitationCreateView(
DjangoPermissionRequiredMixin, CreateAssignPermView): SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create new Invitation""" """Create new Invitation"""
model = Invitation model = Invitation
form_class = InvitationForm form_class = InvitationForm
permission_required = 'passbook_core.add_invitation' permission_required = "passbook_core.add_invitation"
template_name = 'generic/create.html' template_name = "generic/create.html"
success_url = reverse_lazy('passbook_admin:invitations') success_url = reverse_lazy("passbook_admin:invitations")
success_message = _('Successfully created Invitation') success_message = _("Successfully created Invitation")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['type'] = 'Invitation' kwargs["type"] = "Invitation"
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def form_valid(self, form): def form_valid(self, form):
obj = form.save(commit=False) obj = form.save(commit=False)
obj.created_by = self.request.user obj.created_by = self.request.user
obj.save() obj.save()
invitation_created.send( invitation_created.send(sender=self, request=self.request, invitation=obj)
sender=self,
request=self.request,
invitation=obj)
return HttpResponseRedirect(self.success_url) return HttpResponseRedirect(self.success_url)
class InvitationDeleteView(SuccessMessageMixin, LoginRequiredMixin, class InvitationDeleteView(
PermissionRequiredMixin, DeleteView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
):
"""Delete invitation""" """Delete invitation"""
model = Invitation model = Invitation
permission_required = 'passbook_core.delete_invitation' permission_required = "passbook_core.delete_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 deleted Invitation') success_message = _("Successfully deleted Invitation")
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message) messages.success(self.request, self.success_message)

View File

@ -5,34 +5,45 @@ from django.views.generic import TemplateView
from passbook import __version__ from passbook import __version__
from passbook.admin.mixins import AdminRequiredMixin from passbook.admin.mixins import AdminRequiredMixin
from passbook.core.models import (Application, Factor, Invitation, Policy, from passbook.core.models import (
Provider, Source, User) Application,
Factor,
Invitation,
Policy,
Provider,
Source,
User,
)
from passbook.root.celery import CELERY_APP from passbook.root.celery import CELERY_APP
class AdministrationOverviewView(AdminRequiredMixin, TemplateView): class AdministrationOverviewView(AdminRequiredMixin, TemplateView):
"""Overview View""" """Overview View"""
template_name = 'administration/overview.html' template_name = "administration/overview.html"
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
"""Handle post (clear cache from modal)""" """Handle post (clear cache from modal)"""
if 'clear' in self.request.POST: if "clear" in self.request.POST:
cache.clear() cache.clear()
return redirect(reverse('passbook_core:auth-login')) return redirect(reverse("passbook_core:auth-login"))
return self.get(*args, **kwargs) return self.get(*args, **kwargs)
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['application_count'] = len(Application.objects.all()) kwargs["application_count"] = len(Application.objects.all())
kwargs['policy_count'] = len(Policy.objects.all()) kwargs["policy_count"] = len(Policy.objects.all())
kwargs['user_count'] = len(User.objects.all()) kwargs["user_count"] = len(User.objects.all())
kwargs['provider_count'] = len(Provider.objects.all()) kwargs["provider_count"] = len(Provider.objects.all())
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["version"] = __version__
kwargs['worker_count'] = len(CELERY_APP.control.ping(timeout=0.5)) kwargs["worker_count"] = len(CELERY_APP.control.ping(timeout=0.5))
kwargs['providers_without_application'] = Provider.objects.filter(application=None) kwargs["providers_without_application"] = Provider.objects.filter(
kwargs['policies_without_attachment'] = len(Policy.objects.filter(policymodel__isnull=True)) application=None
kwargs['cached_policies'] = len(cache.keys('policy_*')) )
kwargs["policies_without_attachment"] = len(
Policy.objects.filter(policymodel__isnull=True)
)
kwargs["cached_policies"] = len(cache.keys("policy_*"))
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -1,8 +1,9 @@
"""passbook Policy administration""" """passbook Policy administration"""
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.contrib.auth.mixins import \ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
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
@ -22,49 +23,54 @@ class PolicyListView(LoginRequiredMixin, PermissionListMixin, ListView):
"""Show list of all policies""" """Show list of all policies"""
model = Policy model = Policy
permission_required = 'passbook_core.view_policy' permission_required = "passbook_core.view_policy"
template_name = 'administration/policy/list.html' template_name = "administration/policy/list.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['types'] = { kwargs["types"] = {
x.__name__: x._meta.verbose_name for x in Policy.__subclasses__()} x.__name__: x._meta.verbose_name for x in Policy.__subclasses__()
}
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def get_queryset(self): def get_queryset(self):
return super().get_queryset().order_by('order').select_subclasses() return super().get_queryset().order_by("order").select_subclasses()
class PolicyCreateView(SuccessMessageMixin, LoginRequiredMixin, class PolicyCreateView(
DjangoPermissionRequiredMixin, CreateAssignPermView): SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create new Policy""" """Create new Policy"""
model = Policy model = Policy
permission_required = 'passbook_core.add_policy' permission_required = "passbook_core.add_policy"
template_name = 'generic/create.html' template_name = "generic/create.html"
success_url = reverse_lazy('passbook_admin:policies') success_url = reverse_lazy("passbook_admin:policies")
success_message = _('Successfully created Policy') success_message = _("Successfully created Policy")
def get_form_class(self): def get_form_class(self):
policy_type = self.request.GET.get('type') policy_type = self.request.GET.get("type")
model = next(x for x in Policy.__subclasses__() model = next(x for x in Policy.__subclasses__() if x.__name__ == policy_type)
if x.__name__ == policy_type)
if not model: if not model:
raise Http404 raise Http404
return path_to_class(model.form) return path_to_class(model.form)
class PolicyUpdateView(SuccessMessageMixin, LoginRequiredMixin, class PolicyUpdateView(
PermissionRequiredMixin, UpdateView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
"""Update policy""" """Update policy"""
model = Policy model = Policy
permission_required = 'passbook_core.change_policy' permission_required = "passbook_core.change_policy"
template_name = 'generic/update.html' template_name = "generic/update.html"
success_url = reverse_lazy('passbook_admin:policies') success_url = reverse_lazy("passbook_admin:policies")
success_message = _('Successfully updated Policy') success_message = _("Successfully updated Policy")
def get_form_class(self): def get_form_class(self):
form_class_path = self.get_object().form form_class_path = self.get_object().form
@ -72,22 +78,27 @@ class PolicyUpdateView(SuccessMessageMixin, LoginRequiredMixin,
return form_class return form_class
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()
)
class PolicyDeleteView(SuccessMessageMixin, LoginRequiredMixin, class PolicyDeleteView(
PermissionRequiredMixin, DeleteView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
):
"""Delete policy""" """Delete policy"""
model = Policy model = Policy
permission_required = 'passbook_core.delete_policy' permission_required = "passbook_core.delete_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 deleted 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): def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message) messages.success(self.request, self.success_message)
@ -99,15 +110,17 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
model = Policy model = Policy
form_class = PolicyTestForm form_class = PolicyTestForm
permission_required = 'passbook_core.view_policy' permission_required = "passbook_core.view_policy"
template_name = 'administration/policy/test.html' template_name = "administration/policy/test.html"
object = None object = None
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 get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['policy'] = self.get_object() kwargs["policy"] = self.get_object()
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def post(self, *args, **kwargs): def post(self, *args, **kwargs):
@ -116,13 +129,13 @@ class PolicyTestView(LoginRequiredMixin, DetailView, PermissionRequiredMixin, Fo
def form_valid(self, form): def form_valid(self, form):
policy = self.get_object() policy = self.get_object()
user = form.cleaned_data.get('user') user = form.cleaned_data.get("user")
policy_engine = PolicyEngine([policy], user, self.request) policy_engine = PolicyEngine([policy], user, self.request)
policy_engine.use_cache = False policy_engine.use_cache = False
policy_engine.build() policy_engine.build()
result = policy_engine.passing result = policy_engine.passing
if result: if result:
messages.success(self.request, _('User successfully passed policy.')) messages.success(self.request, _("User successfully passed policy."))
else: else:
messages.error(self.request, _("User didn't pass policy.")) messages.error(self.request, _("User didn't pass policy."))
return self.render_to_response(self.get_context_data(form=form, result=result)) return self.render_to_response(self.get_context_data(form=form, result=result))

View File

@ -1,8 +1,9 @@
"""passbook PropertyMapping administration""" """passbook PropertyMapping administration"""
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.contrib.auth.mixins import \ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
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
@ -18,65 +19,78 @@ from passbook.lib.views import CreateAssignPermView
def all_subclasses(cls): def all_subclasses(cls):
"""Recursively return all subclassess of cls""" """Recursively return all subclassess of cls"""
return set(cls.__subclasses__()).union( return set(cls.__subclasses__()).union(
[s for c in cls.__subclasses__() for s in all_subclasses(c)]) [s for c in cls.__subclasses__() for s in all_subclasses(c)]
)
class PropertyMappingListView(LoginRequiredMixin, PermissionListMixin, ListView): class PropertyMappingListView(LoginRequiredMixin, PermissionListMixin, ListView):
"""Show list of all property_mappings""" """Show list of all property_mappings"""
model = PropertyMapping model = PropertyMapping
permission_required = 'passbook_core.view_propertymapping' permission_required = "passbook_core.view_propertymapping"
template_name = 'administration/property_mapping/list.html' template_name = "administration/property_mapping/list.html"
ordering = 'name' ordering = "name"
paginate_by = 40 paginate_by = 40
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['types'] = { kwargs["types"] = {
x.__name__: x._meta.verbose_name for x in all_subclasses(PropertyMapping)} x.__name__: x._meta.verbose_name for x in all_subclasses(PropertyMapping)
}
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def get_queryset(self): def get_queryset(self):
return super().get_queryset().select_subclasses() return super().get_queryset().select_subclasses()
class PropertyMappingCreateView(SuccessMessageMixin, LoginRequiredMixin, class PropertyMappingCreateView(
DjangoPermissionRequiredMixin, CreateAssignPermView): SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create new PropertyMapping""" """Create new PropertyMapping"""
model = PropertyMapping model = PropertyMapping
permission_required = 'passbook_core.add_propertymapping' permission_required = "passbook_core.add_propertymapping"
template_name = 'generic/create.html' template_name = "generic/create.html"
success_url = reverse_lazy('passbook_admin:property-mappings') success_url = reverse_lazy("passbook_admin:property-mappings")
success_message = _('Successfully created Property Mapping') success_message = _("Successfully created Property Mapping")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs = super().get_context_data(**kwargs) kwargs = super().get_context_data(**kwargs)
property_mapping_type = self.request.GET.get('type') property_mapping_type = self.request.GET.get("type")
model = next(x for x in all_subclasses(PropertyMapping) model = next(
if x.__name__ == property_mapping_type) x
kwargs['type'] = model._meta.verbose_name for x in all_subclasses(PropertyMapping)
if x.__name__ == property_mapping_type
)
kwargs["type"] = model._meta.verbose_name
return kwargs return kwargs
def get_form_class(self): def get_form_class(self):
property_mapping_type = self.request.GET.get('type') property_mapping_type = self.request.GET.get("type")
model = next(x for x in all_subclasses(PropertyMapping) model = next(
if x.__name__ == property_mapping_type) x
for x in all_subclasses(PropertyMapping)
if x.__name__ == property_mapping_type
)
if not model: if not model:
raise Http404 raise Http404
return path_to_class(model.form) return path_to_class(model.form)
class PropertyMappingUpdateView(SuccessMessageMixin, LoginRequiredMixin, class PropertyMappingUpdateView(
PermissionRequiredMixin, UpdateView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
"""Update property_mapping""" """Update property_mapping"""
model = PropertyMapping model = PropertyMapping
permission_required = 'passbook_core.change_propertymapping' permission_required = "passbook_core.change_propertymapping"
template_name = 'generic/update.html' template_name = "generic/update.html"
success_url = reverse_lazy('passbook_admin:property-mappings') success_url = reverse_lazy("passbook_admin:property-mappings")
success_message = _('Successfully updated Property Mapping') success_message = _("Successfully updated Property Mapping")
def get_form_class(self): def get_form_class(self):
form_class_path = self.get_object().form form_class_path = self.get_object().form
@ -84,22 +98,31 @@ class PropertyMappingUpdateView(SuccessMessageMixin, LoginRequiredMixin,
return form_class return form_class
def get_object(self, queryset=None): def get_object(self, queryset=None):
return PropertyMapping.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first() return (
PropertyMapping.objects.filter(pk=self.kwargs.get("pk"))
.select_subclasses()
.first()
)
class PropertyMappingDeleteView(SuccessMessageMixin, LoginRequiredMixin, class PropertyMappingDeleteView(
PermissionRequiredMixin, DeleteView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
):
"""Delete property_mapping""" """Delete property_mapping"""
model = PropertyMapping model = PropertyMapping
permission_required = 'passbook_core.delete_propertymapping' permission_required = "passbook_core.delete_propertymapping"
template_name = 'generic/delete.html' template_name = "generic/delete.html"
success_url = reverse_lazy('passbook_admin:property-mappings') success_url = reverse_lazy("passbook_admin:property-mappings")
success_message = _('Successfully deleted Property Mapping') success_message = _("Successfully deleted Property Mapping")
def get_object(self, queryset=None): def get_object(self, queryset=None):
return PropertyMapping.objects.filter(pk=self.kwargs.get('pk')).select_subclasses().first() return (
PropertyMapping.objects.filter(pk=self.kwargs.get("pk"))
.select_subclasses()
.first()
)
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message) messages.success(self.request, self.success_message)

View File

@ -1,8 +1,9 @@
"""passbook Provider administration""" """passbook Provider administration"""
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.contrib.auth.mixins import \ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
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
@ -19,48 +20,55 @@ class ProviderListView(LoginRequiredMixin, PermissionListMixin, ListView):
"""Show list of all providers""" """Show list of all providers"""
model = Provider model = Provider
permission_required = 'passbook_core.add_provider' permission_required = "passbook_core.add_provider"
template_name = 'administration/provider/list.html' template_name = "administration/provider/list.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['types'] = { kwargs["types"] = {
x.__name__: x._meta.verbose_name for x in Provider.__subclasses__()} x.__name__: x._meta.verbose_name for x in Provider.__subclasses__()
}
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def get_queryset(self): def get_queryset(self):
return super().get_queryset().select_subclasses() return super().get_queryset().select_subclasses()
class ProviderCreateView(SuccessMessageMixin, LoginRequiredMixin, class ProviderCreateView(
DjangoPermissionRequiredMixin, CreateAssignPermView): SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create new Provider""" """Create new Provider"""
model = Provider model = Provider
permission_required = 'passbook_core.add_provider' permission_required = "passbook_core.add_provider"
template_name = 'generic/create.html' template_name = "generic/create.html"
success_url = reverse_lazy('passbook_admin:providers') success_url = reverse_lazy("passbook_admin:providers")
success_message = _('Successfully created Provider') success_message = _("Successfully created Provider")
def get_form_class(self): def get_form_class(self):
provider_type = self.request.GET.get('type') provider_type = self.request.GET.get("type")
model = next(x for x in Provider.__subclasses__() model = next(
if x.__name__ == provider_type) x for x in Provider.__subclasses__() if x.__name__ == provider_type
)
if not model: if not model:
raise Http404 raise Http404
return path_to_class(model.form) return path_to_class(model.form)
class ProviderUpdateView(SuccessMessageMixin, LoginRequiredMixin, class ProviderUpdateView(
PermissionRequiredMixin, UpdateView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
"""Update provider""" """Update provider"""
model = Provider model = Provider
permission_required = 'passbook_core.change_provider' permission_required = "passbook_core.change_provider"
template_name = 'generic/update.html' template_name = "generic/update.html"
success_url = reverse_lazy('passbook_admin:providers') success_url = reverse_lazy("passbook_admin:providers")
success_message = _('Successfully updated Provider') success_message = _("Successfully updated Provider")
def get_form_class(self): def get_form_class(self):
form_class_path = self.get_object().form form_class_path = self.get_object().form
@ -68,22 +76,31 @@ class ProviderUpdateView(SuccessMessageMixin, LoginRequiredMixin,
return form_class return form_class
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()
)
class ProviderDeleteView(SuccessMessageMixin, LoginRequiredMixin, class ProviderDeleteView(
PermissionRequiredMixin, DeleteView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
):
"""Delete provider""" """Delete provider"""
model = Provider model = Provider
permission_required = 'passbook_core.delete_provider' permission_required = "passbook_core.delete_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 deleted 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): def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message) messages.success(self.request, self.success_message)

View File

@ -1,8 +1,9 @@
"""passbook Source administration""" """passbook Source administration"""
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.contrib.auth.mixins import \ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
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
@ -18,55 +19,63 @@ from passbook.lib.views import CreateAssignPermView
def all_subclasses(cls): def all_subclasses(cls):
"""Recursively return all subclassess of cls""" """Recursively return all subclassess of cls"""
return set(cls.__subclasses__()).union( return set(cls.__subclasses__()).union(
[s for c in cls.__subclasses__() for s in all_subclasses(c)]) [s for c in cls.__subclasses__() for s in all_subclasses(c)]
)
class SourceListView(LoginRequiredMixin, PermissionListMixin, ListView): class SourceListView(LoginRequiredMixin, PermissionListMixin, ListView):
"""Show list of all sources""" """Show list of all sources"""
model = Source model = Source
permission_required = 'passbook_core.view_source' permission_required = "passbook_core.view_source"
ordering = 'name' ordering = "name"
paginate_by = 40 paginate_by = 40
template_name = 'administration/source/list.html' template_name = "administration/source/list.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['types'] = { kwargs["types"] = {
x.__name__: x._meta.verbose_name for x in all_subclasses(Source)} x.__name__: x._meta.verbose_name for x in all_subclasses(Source)
}
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def get_queryset(self): def get_queryset(self):
return super().get_queryset().select_subclasses() return super().get_queryset().select_subclasses()
class SourceCreateView(SuccessMessageMixin, LoginRequiredMixin, class SourceCreateView(
DjangoPermissionRequiredMixin, CreateAssignPermView): SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create new Source""" """Create new Source"""
model = Source model = Source
permission_required = 'passbook_core.add_source' permission_required = "passbook_core.add_source"
template_name = 'generic/create.html' template_name = "generic/create.html"
success_url = reverse_lazy('passbook_admin:sources') success_url = reverse_lazy("passbook_admin:sources")
success_message = _('Successfully created Source') success_message = _("Successfully created Source")
def get_form_class(self): def get_form_class(self):
source_type = self.request.GET.get('type') source_type = self.request.GET.get("type")
model = next(x for x in all_subclasses(Source) if x.__name__ == source_type) model = next(x for x in all_subclasses(Source) if x.__name__ == source_type)
if not model: if not model:
raise Http404 raise Http404
return path_to_class(model.form) return path_to_class(model.form)
class SourceUpdateView(SuccessMessageMixin, LoginRequiredMixin, class SourceUpdateView(
PermissionRequiredMixin, UpdateView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
"""Update source""" """Update source"""
model = Source model = Source
permission_required = 'passbook_core.change_source' permission_required = "passbook_core.change_source"
template_name = 'generic/update.html' template_name = "generic/update.html"
success_url = reverse_lazy('passbook_admin:sources') success_url = reverse_lazy("passbook_admin:sources")
success_message = _('Successfully updated Source') success_message = _("Successfully updated Source")
def get_form_class(self): def get_form_class(self):
form_class_path = self.get_object().form form_class_path = self.get_object().form
@ -74,22 +83,27 @@ class SourceUpdateView(SuccessMessageMixin, LoginRequiredMixin,
return form_class return form_class
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()
)
class SourceDeleteView(SuccessMessageMixin, LoginRequiredMixin, class SourceDeleteView(
PermissionRequiredMixin, DeleteView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
):
"""Delete source""" """Delete source"""
model = Source model = Source
permission_required = 'passbook_core.delete_source' permission_required = "passbook_core.delete_source"
template_name = 'generic/delete.html' template_name = "generic/delete.html"
success_url = reverse_lazy('passbook_admin:sources') success_url = reverse_lazy("passbook_admin:sources")
success_message = _('Successfully deleted 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): def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message) messages.success(self.request, self.success_message)

View File

@ -1,8 +1,9 @@
"""passbook User administration""" """passbook User administration"""
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.contrib.auth.mixins import \ from django.contrib.auth.mixins import (
PermissionRequiredMixin as DjangoPermissionRequiredMixin PermissionRequiredMixin as DjangoPermissionRequiredMixin,
)
from django.contrib.messages.views import SuccessMessageMixin from django.contrib.messages.views import SuccessMessageMixin
from django.shortcuts import redirect from django.shortcuts import redirect
from django.urls import reverse, reverse_lazy from django.urls import reverse, reverse_lazy
@ -19,50 +20,56 @@ class UserListView(LoginRequiredMixin, PermissionListMixin, ListView):
"""Show list of all users""" """Show list of all users"""
model = User model = User
permission_required = 'passbook_core.view_user' permission_required = "passbook_core.view_user"
ordering = 'username' ordering = "username"
paginate_by = 40 paginate_by = 40
template_name = 'administration/user/list.html' template_name = "administration/user/list.html"
class UserCreateView(SuccessMessageMixin, LoginRequiredMixin, class UserCreateView(
DjangoPermissionRequiredMixin, CreateAssignPermView): SuccessMessageMixin,
LoginRequiredMixin,
DjangoPermissionRequiredMixin,
CreateAssignPermView,
):
"""Create user""" """Create user"""
model = User model = User
form_class = UserForm form_class = UserForm
permission_required = 'passbook_core.add_user' permission_required = "passbook_core.add_user"
template_name = 'generic/create.html' template_name = "generic/create.html"
success_url = reverse_lazy('passbook_admin:users') success_url = reverse_lazy("passbook_admin:users")
success_message = _('Successfully created User') success_message = _("Successfully created User")
class UserUpdateView(SuccessMessageMixin, LoginRequiredMixin, class UserUpdateView(
PermissionRequiredMixin, UpdateView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, UpdateView
):
"""Update user""" """Update user"""
model = User model = User
form_class = UserForm form_class = UserForm
permission_required = 'passbook_core.change_user' permission_required = "passbook_core.change_user"
# By default the object's name is user which is used by other checks # By default the object's name is user which is used by other checks
context_object_name = 'object' context_object_name = "object"
template_name = 'generic/update.html' template_name = "generic/update.html"
success_url = reverse_lazy('passbook_admin:users') success_url = reverse_lazy("passbook_admin:users")
success_message = _('Successfully updated User') success_message = _("Successfully updated User")
class UserDeleteView(SuccessMessageMixin, LoginRequiredMixin, class UserDeleteView(
PermissionRequiredMixin, DeleteView): SuccessMessageMixin, LoginRequiredMixin, PermissionRequiredMixin, DeleteView
):
"""Delete user""" """Delete user"""
model = User model = User
permission_required = 'passbook_core.delete_user' permission_required = "passbook_core.delete_user"
template_name = 'generic/delete.html' template_name = "generic/delete.html"
success_url = reverse_lazy('passbook_admin:users') success_url = reverse_lazy("passbook_admin:users")
success_message = _('Successfully deleted User') success_message = _("Successfully deleted User")
def delete(self, request, *args, **kwargs): def delete(self, request, *args, **kwargs):
messages.success(self.request, self.success_message) messages.success(self.request, self.success_message)
@ -73,13 +80,16 @@ class UserPasswordResetView(LoginRequiredMixin, PermissionRequiredMixin, DetailV
"""Get Password reset link for user""" """Get Password reset link for user"""
model = User model = User
permission_required = 'passbook_core.reset_user_password' permission_required = "passbook_core.reset_user_password"
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
"""Create nonce for user and return link""" """Create nonce for user and return link"""
super().get(request, *args, **kwargs) super().get(request, *args, **kwargs)
nonce = Nonce.objects.create(user=self.object) nonce = Nonce.objects.create(user=self.object)
link = request.build_absolute_uri(reverse( link = request.build_absolute_uri(
'passbook_core:auth-password-reset', kwargs={'nonce': nonce.uuid})) 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') messages.success(
request, _("Password reset link: <pre>%(link)s</pre>" % {"link": link})
)
return redirect("passbook_admin:users")

View File

@ -6,7 +6,7 @@ from django.apps import AppConfig
class PassbookAPIConfig(AppConfig): class PassbookAPIConfig(AppConfig):
"""passbook API Config""" """passbook API Config"""
name = 'passbook.api' name = "passbook.api"
label = 'passbook_api' label = "passbook_api"
mountpoint = 'api/' mountpoint = "api/"
verbose_name = 'passbook API' verbose_name = "passbook API"

View File

@ -9,13 +9,13 @@ class CustomObjectPermissions(DjangoObjectPermissions):
"""Similar to `DjangoObjectPermissions`, but adding 'view' permissions.""" """Similar to `DjangoObjectPermissions`, but adding 'view' permissions."""
perms_map = { perms_map = {
'GET': ['%(app_label)s.view_%(model_name)s'], "GET": ["%(app_label)s.view_%(model_name)s"],
'OPTIONS': ['%(app_label)s.view_%(model_name)s'], "OPTIONS": ["%(app_label)s.view_%(model_name)s"],
'HEAD': ['%(app_label)s.view_%(model_name)s'], "HEAD": ["%(app_label)s.view_%(model_name)s"],
'POST': ['%(app_label)s.add_%(model_name)s'], "POST": ["%(app_label)s.add_%(model_name)s"],
'PUT': ['%(app_label)s.change_%(model_name)s'], "PUT": ["%(app_label)s.change_%(model_name)s"],
'PATCH': ['%(app_label)s.change_%(model_name)s'], "PATCH": ["%(app_label)s.change_%(model_name)s"],
'DELETE': ['%(app_label)s.delete_%(model_name)s'], "DELETE": ["%(app_label)s.delete_%(model_name)s"],
} }

View File

@ -5,6 +5,6 @@ from passbook.api.v1.urls import urlpatterns as v1_urls
from passbook.api.v2.urls import urlpatterns as v2_urls from passbook.api.v2.urls import urlpatterns as v2_urls
urlpatterns = [ urlpatterns = [
path('v1/', include(v1_urls)), path("v1/", include(v1_urls)),
path('v2/', include(v2_urls)), path("v2/", include(v2_urls)),
] ]

View File

@ -7,16 +7,16 @@ from oauth2_provider.views.mixins import ScopedResourceMixin
class OpenIDUserInfoView(ScopedResourceMixin, View): class OpenIDUserInfoView(ScopedResourceMixin, View):
"""Passbook v1 OpenID API""" """Passbook v1 OpenID API"""
required_scopes = ['openid:userinfo'] required_scopes = ["openid:userinfo"]
def get(self, request, *_, **__): def get(self, request, *_, **__):
"""Passbook v1 OpenID API""" """Passbook v1 OpenID API"""
payload = { payload = {
'sub': request.user.uuid.int, "sub": request.user.uuid.int,
'name': request.user.get_full_name(), "name": request.user.get_full_name(),
'given_name': request.user.name, "given_name": request.user.name,
'family_name': '', "family_name": "",
'preferred_username': request.user.username, "preferred_username": request.user.username,
'email': request.user.email, "email": request.user.email,
} }
return JsonResponse(payload) return JsonResponse(payload)

View File

@ -3,6 +3,4 @@ from django.urls import path
from passbook.api.v1.openid import OpenIDUserInfoView from passbook.api.v1.openid import OpenIDUserInfoView
urlpatterns = [ urlpatterns = [path("openid/", OpenIDUserInfoView.as_view(), name="openid")]
path('openid/', OpenIDUserInfoView.as_view(), name='openid')
]

View File

@ -34,70 +34,73 @@ from passbook.policies.webhook.api import WebhookPolicyViewSet
from passbook.providers.app_gw.api import ApplicationGatewayProviderViewSet from passbook.providers.app_gw.api import ApplicationGatewayProviderViewSet
from passbook.providers.oauth.api import OAuth2ProviderViewSet from passbook.providers.oauth.api import OAuth2ProviderViewSet
from passbook.providers.oidc.api import OpenIDProviderViewSet from passbook.providers.oidc.api import OpenIDProviderViewSet
from passbook.providers.saml.api import (SAMLPropertyMappingViewSet, from passbook.providers.saml.api import SAMLPropertyMappingViewSet, SAMLProviderViewSet
SAMLProviderViewSet) from passbook.sources.ldap.api import LDAPPropertyMappingViewSet, LDAPSourceViewSet
from passbook.sources.ldap.api import (LDAPPropertyMappingViewSet,
LDAPSourceViewSet)
from passbook.sources.oauth.api import OAuthSourceViewSet from passbook.sources.oauth.api import OAuthSourceViewSet
LOGGER = get_logger() LOGGER = get_logger()
router = routers.DefaultRouter() router = routers.DefaultRouter()
for _passbook_app in get_apps(): for _passbook_app in get_apps():
if hasattr(_passbook_app, 'api_mountpoint'): if hasattr(_passbook_app, "api_mountpoint"):
for prefix, viewset in _passbook_app.api_mountpoint: for prefix, viewset in _passbook_app.api_mountpoint:
router.register(prefix, viewset) router.register(prefix, viewset)
LOGGER.debug("Mounted API URLs", app_name=_passbook_app.name) LOGGER.debug("Mounted API URLs", app_name=_passbook_app.name)
router.register('core/applications', ApplicationViewSet) router.register("core/applications", ApplicationViewSet)
router.register('core/invitations', InvitationViewSet) router.register("core/invitations", InvitationViewSet)
router.register('core/groups', GroupViewSet) router.register("core/groups", GroupViewSet)
router.register('core/users', UserViewSet) router.register("core/users", UserViewSet)
router.register('audit/events', EventViewSet) router.register("audit/events", EventViewSet)
router.register('sources/all', SourceViewSet) router.register("sources/all", SourceViewSet)
router.register('sources/ldap', LDAPSourceViewSet) router.register("sources/ldap", LDAPSourceViewSet)
router.register('sources/oauth', OAuthSourceViewSet) router.register("sources/oauth", OAuthSourceViewSet)
router.register('policies/all', PolicyViewSet) router.register("policies/all", PolicyViewSet)
router.register('policies/passwordexpiry', PasswordExpiryPolicyViewSet) router.register("policies/passwordexpiry", PasswordExpiryPolicyViewSet)
router.register('policies/groupmembership', GroupMembershipPolicyViewSet) router.register("policies/groupmembership", GroupMembershipPolicyViewSet)
router.register('policies/haveibeenpwned', HaveIBeenPwendPolicyViewSet) router.register("policies/haveibeenpwned", HaveIBeenPwendPolicyViewSet)
router.register('policies/fieldmatcher', FieldMatcherPolicyViewSet) router.register("policies/fieldmatcher", FieldMatcherPolicyViewSet)
router.register('policies/password', PasswordPolicyViewSet) router.register("policies/password", PasswordPolicyViewSet)
router.register('policies/reputation', ReputationPolicyViewSet) router.register("policies/reputation", ReputationPolicyViewSet)
router.register('policies/ssologin', SSOLoginPolicyViewSet) router.register("policies/ssologin", SSOLoginPolicyViewSet)
router.register('policies/webhook', WebhookPolicyViewSet) router.register("policies/webhook", WebhookPolicyViewSet)
router.register('providers/all', ProviderViewSet) router.register("providers/all", ProviderViewSet)
router.register('providers/applicationgateway', ApplicationGatewayProviderViewSet) router.register("providers/applicationgateway", ApplicationGatewayProviderViewSet)
router.register('providers/oauth', OAuth2ProviderViewSet) router.register("providers/oauth", OAuth2ProviderViewSet)
router.register('providers/openid', OpenIDProviderViewSet) router.register("providers/openid", OpenIDProviderViewSet)
router.register('providers/saml', SAMLProviderViewSet) router.register("providers/saml", SAMLProviderViewSet)
router.register('propertymappings/all', PropertyMappingViewSet) router.register("propertymappings/all", PropertyMappingViewSet)
router.register('propertymappings/ldap', LDAPPropertyMappingViewSet) router.register("propertymappings/ldap", LDAPPropertyMappingViewSet)
router.register('propertymappings/saml', SAMLPropertyMappingViewSet) router.register("propertymappings/saml", SAMLPropertyMappingViewSet)
router.register('factors/all', FactorViewSet) router.register("factors/all", FactorViewSet)
router.register('factors/captcha', CaptchaFactorViewSet) router.register("factors/captcha", CaptchaFactorViewSet)
router.register('factors/dummy', DummyFactorViewSet) router.register("factors/dummy", DummyFactorViewSet)
router.register('factors/email', EmailFactorViewSet) router.register("factors/email", EmailFactorViewSet)
router.register('factors/otp', OTPFactorViewSet) router.register("factors/otp", OTPFactorViewSet)
router.register('factors/password', PasswordFactorViewSet) router.register("factors/password", PasswordFactorViewSet)
info = openapi.Info( info = openapi.Info(
title="passbook API", title="passbook API",
default_version='v2', default_version="v2",
# description="Test description", # description="Test description",
# terms_of_service="https://www.google.com/policies/terms/", # terms_of_service="https://www.google.com/policies/terms/",
contact=openapi.Contact(email="hello@beryju.org"), contact=openapi.Contact(email="hello@beryju.org"),
license=openapi.License(name="MIT License"), license=openapi.License(name="MIT License"),
) )
SchemaView = get_schema_view( SchemaView = get_schema_view(
info, info, public=True, permission_classes=(CustomObjectPermissions,),
public=True,
permission_classes=(CustomObjectPermissions,),
) )
urlpatterns = [ urlpatterns = [
url(r'^swagger(?P<format>\.json|\.yaml)$', url(
SchemaView.without_ui(cache_timeout=0), name='schema-json'), r"^swagger(?P<format>\.json|\.yaml)$",
path('swagger/', SchemaView.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'), SchemaView.without_ui(cache_timeout=0),
path('redoc/', SchemaView.with_ui('redoc', cache_timeout=0), name='schema-redoc'), name="schema-json",
),
path(
"swagger/",
SchemaView.with_ui("swagger", cache_timeout=0),
name="schema-swagger-ui",
),
path("redoc/", SchemaView.with_ui("redoc", cache_timeout=0), name="schema-redoc"),
] + router.urls ] + router.urls

View File

@ -2,4 +2,4 @@
from passbook.lib.admin import admin_autoregister from passbook.lib.admin import admin_autoregister
admin_autoregister('passbook_audit') admin_autoregister("passbook_audit")

View File

@ -11,7 +11,16 @@ class EventSerializer(ModelSerializer):
class Meta: class Meta:
model = Event model = Event
fields = ['pk', 'user', 'action', 'date', 'app', 'context', 'request_ip', 'created', ] fields = [
"pk",
"user",
"action",
"date",
"app",
"context",
"request_ip",
"created",
]
class EventViewSet(ReadOnlyModelViewSet): class EventViewSet(ReadOnlyModelViewSet):

View File

@ -7,10 +7,10 @@ from django.apps import AppConfig
class PassbookAuditConfig(AppConfig): class PassbookAuditConfig(AppConfig):
"""passbook audit app""" """passbook audit app"""
name = 'passbook.audit' name = "passbook.audit"
label = 'passbook_audit' label = "passbook_audit"
verbose_name = 'passbook Audit' verbose_name = "passbook Audit"
mountpoint = 'audit/' mountpoint = "audit/"
def ready(self): def ready(self):
import_module('passbook.audit.signals') import_module("passbook.audit.signals")

View File

@ -18,20 +18,55 @@ class Migration(migrations.Migration):
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='AuditEntry', name="AuditEntry",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('action', models.TextField(choices=[('login', 'login'), ('login_failed', 'login_failed'), ('logout', 'logout'), ('authorize_application', 'authorize_application'), ('suspicious_request', 'suspicious_request'), ('sign_up', 'sign_up'), ('password_reset', 'password_reset'), ('invitation_created', 'invitation_created'), ('invitation_used', 'invitation_used')])), "uuid",
('date', models.DateTimeField(auto_now_add=True)), models.UUIDField(
('app', models.TextField()), default=uuid.uuid4,
('context', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)), editable=False,
('request_ip', models.GenericIPAddressField()), primary_key=True,
('created', models.DateTimeField(auto_now_add=True)), serialize=False,
('user', models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to=settings.AUTH_USER_MODEL)), ),
),
(
"action",
models.TextField(
choices=[
("login", "login"),
("login_failed", "login_failed"),
("logout", "logout"),
("authorize_application", "authorize_application"),
("suspicious_request", "suspicious_request"),
("sign_up", "sign_up"),
("password_reset", "password_reset"),
("invitation_created", "invitation_created"),
("invitation_used", "invitation_used"),
]
),
),
("date", models.DateTimeField(auto_now_add=True)),
("app", models.TextField()),
(
"context",
django.contrib.postgres.fields.jsonb.JSONField(
blank=True, default=dict
),
),
("request_ip", models.GenericIPAddressField()),
("created", models.DateTimeField(auto_now_add=True)),
(
"user",
models.ForeignKey(
null=True,
on_delete=django.db.models.deletion.SET_NULL,
to=settings.AUTH_USER_MODEL,
),
),
], ],
options={ options={
'verbose_name': 'Audit Entry', "verbose_name": "Audit Entry",
'verbose_name_plural': 'Audit Entries', "verbose_name_plural": "Audit Entries",
}, },
), ),
] ]

View File

@ -8,12 +8,9 @@ class Migration(migrations.Migration):
dependencies = [ dependencies = [
migrations.swappable_dependency(settings.AUTH_USER_MODEL), migrations.swappable_dependency(settings.AUTH_USER_MODEL),
('passbook_audit', '0001_initial'), ("passbook_audit", "0001_initial"),
] ]
operations = [ operations = [
migrations.RenameModel( migrations.RenameModel(old_name="AuditEntry", new_name="Event",),
old_name='AuditEntry',
new_name='Event',
),
] ]

View File

@ -8,17 +8,33 @@ import passbook.audit.models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('passbook_audit', '0002_auto_20191028_0829'), ("passbook_audit", "0002_auto_20191028_0829"),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='event', name="event",
options={'verbose_name': 'Audit Event', 'verbose_name_plural': 'Audit Events'}, options={
"verbose_name": "Audit Event",
"verbose_name_plural": "Audit Events",
},
), ),
migrations.AlterField( migrations.AlterField(
model_name='event', model_name="event",
name='action', name="action",
field=models.TextField(choices=[('LOGIN', 'login'), ('LOGIN_FAILED', 'login_failed'), ('LOGOUT', 'logout'), ('AUTHORIZE_APPLICATION', 'authorize_application'), ('SUSPICIOUS_REQUEST', 'suspicious_request'), ('SIGN_UP', 'sign_up'), ('PASSWORD_RESET', 'password_reset'), ('INVITE_CREATED', 'invitation_created'), ('INVITE_USED', 'invitation_used'), ('CUSTOM', 'custom')]), field=models.TextField(
choices=[
("LOGIN", "login"),
("LOGIN_FAILED", "login_failed"),
("LOGOUT", "logout"),
("AUTHORIZE_APPLICATION", "authorize_application"),
("SUSPICIOUS_REQUEST", "suspicious_request"),
("SIGN_UP", "sign_up"),
("PASSWORD_RESET", "password_reset"),
("INVITE_CREATED", "invitation_created"),
("INVITE_USED", "invitation_used"),
("CUSTOM", "custom"),
]
),
), ),
] ]

View File

@ -6,17 +6,14 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('passbook_audit', '0003_auto_20191205_1407'), ("passbook_audit", "0003_auto_20191205_1407"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="event", name="request_ip",),
model_name='event',
name='request_ip',
),
migrations.AddField( migrations.AddField(
model_name='event', model_name="event",
name='client_ip', name="client_ip",
field=models.GenericIPAddressField(null=True), field=models.GenericIPAddressField(null=True),
), ),
] ]

View File

@ -18,19 +18,20 @@ from passbook.lib.utils.http import get_client_ip
LOGGER = get_logger() LOGGER = get_logger()
class EventAction(Enum): class EventAction(Enum):
"""All possible actions to save into the audit log""" """All possible actions to save into the audit log"""
LOGIN = 'login' LOGIN = "login"
LOGIN_FAILED = 'login_failed' LOGIN_FAILED = "login_failed"
LOGOUT = 'logout' LOGOUT = "logout"
AUTHORIZE_APPLICATION = 'authorize_application' AUTHORIZE_APPLICATION = "authorize_application"
SUSPICIOUS_REQUEST = 'suspicious_request' SUSPICIOUS_REQUEST = "suspicious_request"
SIGN_UP = 'sign_up' SIGN_UP = "sign_up"
PASSWORD_RESET = 'password_reset' # noqa # nosec PASSWORD_RESET = "password_reset" # noqa # nosec
INVITE_CREATED = 'invitation_created' INVITE_CREATED = "invitation_created"
INVITE_USED = 'invitation_used' INVITE_USED = "invitation_used"
CUSTOM = 'custom' CUSTOM = "custom"
@staticmethod @staticmethod
def as_choices(): def as_choices():
@ -41,7 +42,9 @@ class EventAction(Enum):
class Event(UUIDModel): class Event(UUIDModel):
"""An individual audit log event""" """An individual audit log event"""
user = models.ForeignKey(settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL) user = models.ForeignKey(
settings.AUTH_USER_MODEL, null=True, on_delete=models.SET_NULL
)
action = models.TextField(choices=EventAction.as_choices()) action = models.TextField(choices=EventAction.as_choices())
date = models.DateTimeField(auto_now_add=True) date = models.DateTimeField(auto_now_add=True)
app = models.TextField() app = models.TextField()
@ -56,28 +59,30 @@ class Event(UUIDModel):
return request.resolver_match.app_name return request.resolver_match.app_name
@staticmethod @staticmethod
def new(action: EventAction, def new(
app: Optional[str] = None, action: EventAction,
_inspect_offset: int = 1, app: Optional[str] = None,
**kwargs) -> 'Event': _inspect_offset: int = 1,
**kwargs,
) -> "Event":
"""Create new Event instance from arguments. Instance is NOT saved.""" """Create new Event instance from arguments. Instance is NOT saved."""
if not isinstance(action, EventAction): if not isinstance(action, EventAction):
raise ValueError(f"action must be EventAction instance but was {type(action)}") raise ValueError(
f"action must be EventAction instance but was {type(action)}"
)
if not app: if not app:
app = getmodule(stack()[_inspect_offset][0]).__name__ app = getmodule(stack()[_inspect_offset][0]).__name__
event = Event( event = Event(action=action.value, app=app, context=kwargs)
action=action.value,
app=app,
context=kwargs)
LOGGER.debug("Created Audit event", action=action, context=kwargs) LOGGER.debug("Created Audit event", action=action, context=kwargs)
return event return event
def from_http(self, request: HttpRequest, def from_http(
user: Optional[settings.AUTH_USER_MODEL] = None) -> 'Event': self, request: HttpRequest, user: Optional[settings.AUTH_USER_MODEL] = None
) -> "Event":
"""Add data from a Django-HttpRequest, allowing the creation of """Add data from a Django-HttpRequest, allowing the creation of
Events independently from requests. Events independently from requests.
`user` arguments optionally overrides user from requests.""" `user` arguments optionally overrides user from requests."""
if hasattr(request, 'user'): if hasattr(request, "user"):
if isinstance(request.user, AnonymousUser): if isinstance(request.user, AnonymousUser):
self.user = get_anonymous_user() self.user = get_anonymous_user()
else: else:
@ -85,7 +90,7 @@ class Event(UUIDModel):
if user: if user:
self.user = user self.user = user
# User 255.255.255.255 as fallback if IP cannot be determined # User 255.255.255.255 as fallback if IP cannot be determined
self.client_ip = get_client_ip(request) or '255.255.255.255' self.client_ip = get_client_ip(request) or "255.255.255.255"
# If there's no app set, we get it from the requests too # If there's no app set, we get it from the requests too
if not self.app: if not self.app:
self.app = Event._get_app_from_request(request) self.app = Event._get_app_from_request(request)
@ -94,10 +99,12 @@ class Event(UUIDModel):
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
if not self._state.adding: if not self._state.adding:
raise ValidationError("you may not edit an existing %s" % self._meta.model_name) raise ValidationError(
"you may not edit an existing %s" % self._meta.model_name
)
return super().save(*args, **kwargs) return super().save(*args, **kwargs)
class Meta: class Meta:
verbose_name = _('Audit Event') verbose_name = _("Audit Event")
verbose_name_plural = _('Audit Events') verbose_name_plural = _("Audit Events")

View File

@ -3,8 +3,7 @@ from django.contrib.auth.signals import user_logged_in, user_logged_out
from django.dispatch import receiver from django.dispatch import receiver
from passbook.audit.models import Event, EventAction from passbook.audit.models import Event, EventAction
from passbook.core.signals import (invitation_created, invitation_used, from passbook.core.signals import invitation_created, invitation_used, user_signed_up
user_signed_up)
@receiver(user_logged_in) @receiver(user_logged_in)
@ -32,11 +31,15 @@ def on_user_signed_up(sender, request, user, **_):
# pylint: disable=unused-argument # pylint: disable=unused-argument
def on_invitation_created(sender, request, invitation, **_): def on_invitation_created(sender, request, invitation, **_):
"""Log Invitation creation""" """Log Invitation creation"""
Event.new(EventAction.INVITE_CREATED, invitation_uuid=invitation.uuid.hex).from_http(request) Event.new(
EventAction.INVITE_CREATED, invitation_uuid=invitation.uuid.hex
).from_http(request)
@receiver(invitation_used) @receiver(invitation_used)
# pylint: disable=unused-argument # pylint: disable=unused-argument
def on_invitation_used(sender, request, invitation, **_): def on_invitation_used(sender, request, invitation, **_):
"""Log Invitation usage""" """Log Invitation usage"""
Event.new(EventAction.INVITE_USED, invitation_uuid=invitation.uuid.hex).from_http(request) Event.new(EventAction.INVITE_USED, invitation_uuid=invitation.uuid.hex).from_http(
request
)

View File

@ -2,4 +2,4 @@
from passbook.lib.admin import admin_autoregister from passbook.lib.admin import admin_autoregister
admin_autoregister('passbook_core') admin_autoregister("passbook_core")

View File

@ -11,8 +11,16 @@ class ApplicationSerializer(ModelSerializer):
class Meta: class Meta:
model = Application model = Application
fields = ['pk', 'name', 'slug', 'launch_url', 'icon_url', fields = [
'provider', 'policies', 'skip_authorization'] "pk",
"name",
"slug",
"launch_url",
"icon_url",
"provider",
"policies",
"skip_authorization",
]
class ApplicationViewSet(ModelViewSet): class ApplicationViewSet(ModelViewSet):

View File

@ -8,16 +8,16 @@ from passbook.core.models import Factor
class FactorSerializer(ModelSerializer): class FactorSerializer(ModelSerializer):
"""Factor Serializer""" """Factor Serializer"""
__type__ = SerializerMethodField(method_name='get_type') __type__ = SerializerMethodField(method_name="get_type")
def get_type(self, obj): def get_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object""" """Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace('factor', '') return obj._meta.object_name.lower().replace("factor", "")
class Meta: class Meta:
model = Factor model = Factor
fields = ['pk', 'name', 'slug', 'order', 'enabled', '__type__'] fields = ["pk", "name", "slug", "order", "enabled", "__type__"]
class FactorViewSet(ReadOnlyModelViewSet): class FactorViewSet(ReadOnlyModelViewSet):

View File

@ -11,7 +11,7 @@ class GroupSerializer(ModelSerializer):
class Meta: class Meta:
model = Group model = Group
fields = ['pk', 'name', 'parent', 'user_set', 'attributes'] fields = ["pk", "name", "parent", "user_set", "attributes"]
class GroupViewSet(ModelViewSet): class GroupViewSet(ModelViewSet):

View File

@ -11,7 +11,13 @@ class InvitationSerializer(ModelSerializer):
class Meta: class Meta:
model = Invitation model = Invitation
fields = ['pk', 'expires', 'fixed_username', 'fixed_email', 'needs_confirmation'] fields = [
"pk",
"expires",
"fixed_username",
"fixed_email",
"needs_confirmation",
]
class InvitationViewSet(ModelViewSet): class InvitationViewSet(ModelViewSet):

View File

@ -9,16 +9,16 @@ from passbook.policies.forms import GENERAL_FIELDS
class PolicySerializer(ModelSerializer): class PolicySerializer(ModelSerializer):
"""Policy Serializer""" """Policy Serializer"""
__type__ = SerializerMethodField(method_name='get_type') __type__ = SerializerMethodField(method_name="get_type")
def get_type(self, obj): def get_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object""" """Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace('policy', '') return obj._meta.object_name.lower().replace("policy", "")
class Meta: class Meta:
model = Policy model = Policy
fields = ['pk'] + GENERAL_FIELDS + ['__type__'] fields = ["pk"] + GENERAL_FIELDS + ["__type__"]
class PolicyViewSet(ReadOnlyModelViewSet): class PolicyViewSet(ReadOnlyModelViewSet):

View File

@ -8,16 +8,16 @@ from passbook.core.models import PropertyMapping
class PropertyMappingSerializer(ModelSerializer): class PropertyMappingSerializer(ModelSerializer):
"""PropertyMapping Serializer""" """PropertyMapping Serializer"""
__type__ = SerializerMethodField(method_name='get_type') __type__ = SerializerMethodField(method_name="get_type")
def get_type(self, obj): def get_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object""" """Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace('propertymapping', '') return obj._meta.object_name.lower().replace("propertymapping", "")
class Meta: class Meta:
model = PropertyMapping model = PropertyMapping
fields = ['pk', 'name', '__type__'] fields = ["pk", "name", "__type__"]
class PropertyMappingViewSet(ReadOnlyModelViewSet): class PropertyMappingViewSet(ReadOnlyModelViewSet):

View File

@ -8,16 +8,16 @@ from passbook.core.models import Provider
class ProviderSerializer(ModelSerializer): class ProviderSerializer(ModelSerializer):
"""Provider Serializer""" """Provider Serializer"""
__type__ = SerializerMethodField(method_name='get_type') __type__ = SerializerMethodField(method_name="get_type")
def get_type(self, obj): def get_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object""" """Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace('provider', '') return obj._meta.object_name.lower().replace("provider", "")
class Meta: class Meta:
model = Provider model = Provider
fields = ['pk', 'property_mappings', '__type__'] fields = ["pk", "property_mappings", "__type__"]
class ProviderViewSet(ReadOnlyModelViewSet): class ProviderViewSet(ReadOnlyModelViewSet):

View File

@ -9,16 +9,16 @@ from passbook.core.models import Source
class SourceSerializer(ModelSerializer): class SourceSerializer(ModelSerializer):
"""Source Serializer""" """Source Serializer"""
__type__ = SerializerMethodField(method_name='get_type') __type__ = SerializerMethodField(method_name="get_type")
def get_type(self, obj): def get_type(self, obj):
"""Get object type so that we know which API Endpoint to use to get the full object""" """Get object type so that we know which API Endpoint to use to get the full object"""
return obj._meta.object_name.lower().replace('source', '') return obj._meta.object_name.lower().replace("source", "")
class Meta: class Meta:
model = Source model = Source
fields = SOURCE_SERIALIZER_FIELDS + ['__type__'] fields = SOURCE_SERIALIZER_FIELDS + ["__type__"]
class SourceViewSet(ReadOnlyModelViewSet): class SourceViewSet(ReadOnlyModelViewSet):

View File

@ -11,7 +11,7 @@ class UserSerializer(ModelSerializer):
class Meta: class Meta:
model = User model = User
fields = ['pk', 'username', 'name', 'email'] fields = ["pk", "username", "name", "email"]
class UserViewSet(ModelViewSet): class UserViewSet(ModelViewSet):

View File

@ -5,7 +5,7 @@ from django.apps import AppConfig
class PassbookCoreConfig(AppConfig): class PassbookCoreConfig(AppConfig):
"""passbook core app config""" """passbook core app config"""
name = 'passbook.core' name = "passbook.core"
label = 'passbook_core' label = "passbook_core"
verbose_name = 'passbook Core' verbose_name = "passbook Core"
mountpoint = '' mountpoint = ""

View File

@ -9,21 +9,29 @@ from passbook.core.models import Application, Provider
class ApplicationForm(forms.ModelForm): class ApplicationForm(forms.ModelForm):
"""Application Form""" """Application Form"""
provider = forms.ModelChoiceField(queryset=Provider.objects.all().select_subclasses(), provider = forms.ModelChoiceField(
required=False) queryset=Provider.objects.all().select_subclasses(), required=False
)
class Meta: class Meta:
model = Application model = Application
fields = ['name', 'slug', 'launch_url', 'icon_url', fields = [
'provider', 'policies', 'skip_authorization'] "name",
"slug",
"launch_url",
"icon_url",
"provider",
"policies",
"skip_authorization",
]
widgets = { widgets = {
'name': forms.TextInput(), "name": forms.TextInput(),
'launch_url': forms.TextInput(), "launch_url": forms.TextInput(),
'icon_url': forms.TextInput(), "icon_url": forms.TextInput(),
'policies': FilteredSelectMultiple(_('policies'), False) "policies": FilteredSelectMultiple(_("policies"), False),
} }
labels = { labels = {
'launch_url': _('Launch URL'), "launch_url": _("Launch URL"),
'icon_url': _('Icon URL'), "icon_url": _("Icon URL"),
} }

View File

@ -11,55 +11,64 @@ from passbook.lib.utils.ui import human_list
LOGGER = get_logger() LOGGER = get_logger()
class LoginForm(forms.Form): 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() 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') == ['e-mail']: 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 = { self.fields["uid_field"].widget.attrs = {
'placeholder': _(human_list([x.title() for x in CONFIG.y('passbook.uid_fields')])) "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"""
if CONFIG.y('passbook.uid_fields') == ['email']: if CONFIG.y("passbook.uid_fields") == ["email"]:
validate_email(self.cleaned_data.get('uid_field')) validate_email(self.cleaned_data.get("uid_field"))
return self.cleaned_data.get('uid_field') return self.cleaned_data.get("uid_field")
class SignUpForm(forms.Form): class SignUpForm(forms.Form):
"""SignUp Form""" """SignUp Form"""
title = _('Sign Up') title = _("Sign Up")
name = forms.CharField(label=_('Name'), name = forms.CharField(
widget=forms.TextInput(attrs={'placeholder': _('Name')})) label=_("Name"), widget=forms.TextInput(attrs={"placeholder": _("Name")})
username = forms.CharField(label=_('Username'), )
widget=forms.TextInput(attrs={'placeholder': _('Username')})) username = forms.CharField(
email = forms.EmailField(label=_('E-Mail'), label=_("Username"),
widget=forms.TextInput(attrs={'placeholder': _('E-Mail')})) widget=forms.TextInput(attrs={"placeholder": _("Username")}),
password = forms.CharField(label=_('Password'), )
widget=forms.PasswordInput(attrs={'placeholder': _('Password')})) email = forms.EmailField(
password_repeat = forms.CharField(label=_('Repeat Password'), label=_("E-Mail"), widget=forms.TextInput(attrs={"placeholder": _("E-Mail")})
widget=forms.PasswordInput(attrs={ )
'placeholder': _('Repeat Password') password = forms.CharField(
})) label=_("Password"),
widget=forms.PasswordInput(attrs={"placeholder": _("Password")}),
)
password_repeat = forms.CharField(
label=_("Repeat Password"),
widget=forms.PasswordInput(attrs={"placeholder": _("Repeat Password")}),
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
# All fields which have initial data supplied are set to read only # All fields which have initial data supplied are set to read only
if 'initial' in kwargs: if "initial" in kwargs:
for field in kwargs.get('initial').keys(): for field in kwargs.get("initial").keys():
self.fields[field].widget.attrs['readonly'] = 'readonly' self.fields[field].widget.attrs["readonly"] = "readonly"
def clean_username(self): def clean_username(self):
"""Check if username is used already""" """Check if username is used already"""
username = self.cleaned_data.get('username') username = self.cleaned_data.get("username")
if User.objects.filter(username=username).exists(): if User.objects.filter(username=username).exists():
LOGGER.warning("Username %s already exists", username) LOGGER.warning("Username %s already exists", username)
raise ValidationError(_("Username already exists")) raise ValidationError(_("Username already exists"))
@ -67,7 +76,7 @@ class SignUpForm(forms.Form):
def clean_email(self): def clean_email(self):
"""Check if email is already used in django or other auth sources""" """Check if email is already used in django or other auth sources"""
email = self.cleaned_data.get('email') email = self.cleaned_data.get("email")
# Check if user exists already, error early # Check if user exists already, error early
if User.objects.filter(email=email).exists(): if User.objects.filter(email=email).exists():
LOGGER.debug("email %s exists in django", email) LOGGER.debug("email %s exists in django", email)
@ -76,8 +85,8 @@ class SignUpForm(forms.Form):
def clean_password_repeat(self): def clean_password_repeat(self):
"""Check if Password adheres to filter and if passwords matche""" """Check if Password adheres to filter and if passwords matche"""
password = self.cleaned_data.get('password') password = self.cleaned_data.get("password")
password_repeat = self.cleaned_data.get('password_repeat') password_repeat = self.cleaned_data.get("password_repeat")
if password != password_repeat: if password != password_repeat:
raise ValidationError(_("Passwords don't match")) raise ValidationError(_("Passwords don't match"))
return self.cleaned_data.get('password_repeat') return self.cleaned_data.get("password_repeat")

View File

@ -9,24 +9,29 @@ class GroupForm(forms.ModelForm):
"""Group Form""" """Group Form"""
members = forms.ModelMultipleChoiceField( members = forms.ModelMultipleChoiceField(
User.objects.all(), required=False, widget=FilteredSelectMultiple('users', False)) User.objects.all(),
required=False,
widget=FilteredSelectMultiple("users", False),
)
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
if self.instance.pk: if self.instance.pk:
self.initial['members'] = self.instance.user_set.values_list('pk', flat=True) self.initial["members"] = self.instance.user_set.values_list(
"pk", flat=True
)
def save(self, *args, **kwargs): def save(self, *args, **kwargs):
instance = super().save(*args, **kwargs) instance = super().save(*args, **kwargs)
if instance.pk: if instance.pk:
instance.user_set.clear() instance.user_set.clear()
instance.user_set.add(*self.cleaned_data['members']) instance.user_set.add(*self.cleaned_data["members"])
return instance return instance
class Meta: class Meta:
model = Group model = Group
fields = ['name', 'parent', 'members', 'attributes'] fields = ["name", "parent", "members", "attributes"]
widgets = { widgets = {
'name': forms.TextInput(), "name": forms.TextInput(),
} }

View File

@ -12,27 +12,27 @@ class InvitationForm(forms.ModelForm):
def clean_fixed_username(self): def clean_fixed_username(self):
"""Check if username is already used""" """Check if username is already used"""
username = self.cleaned_data.get('fixed_username') username = self.cleaned_data.get("fixed_username")
if User.objects.filter(username=username).exists(): if User.objects.filter(username=username).exists():
raise ValidationError(_('Username is already in use.')) raise ValidationError(_("Username is already in use."))
return username return username
def clean_fixed_email(self): def clean_fixed_email(self):
"""Check if email is already used""" """Check if email is already used"""
email = self.cleaned_data.get('fixed_email') email = self.cleaned_data.get("fixed_email")
if User.objects.filter(email=email).exists(): if User.objects.filter(email=email).exists():
raise ValidationError(_('E-Mail is already in use.')) raise ValidationError(_("E-Mail is already in use."))
return email return email
class Meta: class Meta:
model = Invitation model = Invitation
fields = ['expires', 'fixed_username', 'fixed_email', 'needs_confirmation'] 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)",
} }
widgets = { widgets = {
'fixed_username': forms.TextInput(), "fixed_username": forms.TextInput(),
'fixed_email': forms.TextInput(), "fixed_email": forms.TextInput(),
} }

View File

@ -13,10 +13,8 @@ class DebugPolicyForm(forms.ModelForm):
class Meta: class Meta:
model = DebugPolicy model = DebugPolicy
fields = GENERAL_FIELDS + ['result', 'wait_min', 'wait_max'] fields = GENERAL_FIELDS + ["result", "wait_min", "wait_max"]
widgets = { widgets = {
'name': forms.TextInput(), "name": forms.TextInput(),
}
labels = {
'result': _('Allow user')
} }
labels = {"result": _("Allow user")}

View File

@ -13,29 +13,30 @@ class UserDetailForm(forms.ModelForm):
class Meta: class Meta:
model = User model = User
fields = ['username', 'name', 'email'] fields = ["username", "name", "email"]
widgets = { widgets = {"name": forms.TextInput}
'name': forms.TextInput
}
class PasswordChangeForm(forms.Form): class PasswordChangeForm(forms.Form):
"""Form to update password""" """Form to update password"""
password = forms.CharField(label=_('Password'), password = forms.CharField(
widget=forms.PasswordInput(attrs={ label=_("Password"),
'placeholder': _('New Password'), widget=forms.PasswordInput(
'autocomplete': 'new-password' attrs={"placeholder": _("New Password"), "autocomplete": "new-password"}
})) ),
password_repeat = forms.CharField(label=_('Repeat Password'), )
widget=forms.PasswordInput(attrs={ password_repeat = forms.CharField(
'placeholder': _('Repeat Password'), label=_("Repeat Password"),
'autocomplete': 'new-password' widget=forms.PasswordInput(
})) attrs={"placeholder": _("Repeat Password"), "autocomplete": "new-password"}
),
)
def clean_password_repeat(self): def clean_password_repeat(self):
"""Check if Password adheres to filter and if passwords matche""" """Check if Password adheres to filter and if passwords matche"""
password = self.cleaned_data.get('password') password = self.cleaned_data.get("password")
password_repeat = self.cleaned_data.get('password_repeat') password_repeat = self.cleaned_data.get("password_repeat")
if password != password_repeat: if password != password_repeat:
raise ValidationError(_("Passwords don't match")) raise ValidationError(_("Passwords don't match"))
return self.cleaned_data.get('password_repeat') return self.cleaned_data.get("password_repeat")

View File

@ -18,206 +18,433 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('auth', '0011_update_proxy_permissions'), ("auth", "0011_update_proxy_permissions"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='User', name="User",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('password', models.CharField(max_length=128, verbose_name='password')), "id",
('last_login', models.DateTimeField(blank=True, null=True, verbose_name='last login')), models.AutoField(
('is_superuser', models.BooleanField(default=False, help_text='Designates that this user has all permissions without explicitly assigning them.', verbose_name='superuser status')), auto_created=True,
('username', models.CharField(error_messages={'unique': 'A user with that username already exists.'}, help_text='Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.', max_length=150, unique=True, validators=[django.contrib.auth.validators.UnicodeUsernameValidator()], verbose_name='username')), primary_key=True,
('first_name', models.CharField(blank=True, max_length=30, verbose_name='first name')), serialize=False,
('last_name', models.CharField(blank=True, max_length=150, verbose_name='last name')), verbose_name="ID",
('email', models.EmailField(blank=True, max_length=254, verbose_name='email address')), ),
('is_staff', models.BooleanField(default=False, help_text='Designates whether the user can log into this admin site.', verbose_name='staff status')), ),
('is_active', models.BooleanField(default=True, help_text='Designates whether this user should be treated as active. Unselect this instead of deleting accounts.', verbose_name='active')), ("password", models.CharField(max_length=128, verbose_name="password")),
('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), (
('uuid', models.UUIDField(default=uuid.uuid4, editable=False)), "last_login",
('name', models.TextField()), models.DateTimeField(
('password_change_date', models.DateTimeField(auto_now_add=True)), blank=True, null=True, verbose_name="last login"
),
),
(
"is_superuser",
models.BooleanField(
default=False,
help_text="Designates that this user has all permissions without explicitly assigning them.",
verbose_name="superuser status",
),
),
(
"username",
models.CharField(
error_messages={
"unique": "A user with that username already exists."
},
help_text="Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only.",
max_length=150,
unique=True,
validators=[
django.contrib.auth.validators.UnicodeUsernameValidator()
],
verbose_name="username",
),
),
(
"first_name",
models.CharField(
blank=True, max_length=30, verbose_name="first name"
),
),
(
"last_name",
models.CharField(
blank=True, max_length=150, verbose_name="last name"
),
),
(
"email",
models.EmailField(
blank=True, max_length=254, verbose_name="email address"
),
),
(
"is_staff",
models.BooleanField(
default=False,
help_text="Designates whether the user can log into this admin site.",
verbose_name="staff status",
),
),
(
"is_active",
models.BooleanField(
default=True,
help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.",
verbose_name="active",
),
),
(
"date_joined",
models.DateTimeField(
default=django.utils.timezone.now, verbose_name="date joined"
),
),
("uuid", models.UUIDField(default=uuid.uuid4, editable=False)),
("name", models.TextField()),
("password_change_date", models.DateTimeField(auto_now_add=True)),
], ],
options={ options={
'verbose_name': 'user', "verbose_name": "user",
'verbose_name_plural': 'users', "verbose_name_plural": "users",
'abstract': False, "abstract": False,
}, },
managers=[ managers=[("objects", django.contrib.auth.models.UserManager()),],
('objects', django.contrib.auth.models.UserManager()),
],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Policy', name="Policy",
fields=[ fields=[
('created', models.DateTimeField(auto_now_add=True)), ("created", models.DateTimeField(auto_now_add=True)),
('last_updated', models.DateTimeField(auto_now=True)), ("last_updated", models.DateTimeField(auto_now=True)),
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('name', models.TextField(blank=True, null=True)), "uuid",
('action', models.CharField(choices=[('allow', 'allow'), ('deny', 'deny')], max_length=20)), models.UUIDField(
('negate', models.BooleanField(default=False)), default=uuid.uuid4,
('order', models.IntegerField(default=0)), editable=False,
('timeout', models.IntegerField(default=30)), primary_key=True,
serialize=False,
),
),
("name", models.TextField(blank=True, null=True)),
(
"action",
models.CharField(
choices=[("allow", "allow"), ("deny", "deny")], max_length=20
),
),
("negate", models.BooleanField(default=False)),
("order", models.IntegerField(default=0)),
("timeout", models.IntegerField(default=30)),
],
options={"abstract": False,},
),
migrations.CreateModel(
name="PolicyModel",
fields=[
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"policies",
models.ManyToManyField(blank=True, to="passbook_core.Policy"),
),
],
options={"abstract": False,},
),
migrations.CreateModel(
name="PropertyMapping",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("name", models.TextField()),
], ],
options={ options={
'abstract': False, "verbose_name": "Property Mapping",
"verbose_name_plural": "Property Mappings",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='PolicyModel', name="DebugPolicy",
fields=[ fields=[
('created', models.DateTimeField(auto_now_add=True)), (
('last_updated', models.DateTimeField(auto_now=True)), "policy_ptr",
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), models.OneToOneField(
('policies', models.ManyToManyField(blank=True, to='passbook_core.Policy')), auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_core.Policy",
),
),
("result", models.BooleanField(default=False)),
("wait_min", models.IntegerField(default=5)),
("wait_max", models.IntegerField(default=30)),
], ],
options={ options={
'abstract': False, "verbose_name": "Debug Policy",
"verbose_name_plural": "Debug Policies",
}, },
bases=("passbook_core.policy",),
), ),
migrations.CreateModel( migrations.CreateModel(
name='PropertyMapping', name="Factor",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('name', models.TextField()), "policymodel_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_core.PolicyModel",
),
),
("name", models.TextField()),
("slug", models.SlugField(unique=True)),
("order", models.IntegerField()),
("enabled", models.BooleanField(default=True)),
], ],
options={ options={"abstract": False,},
'verbose_name': 'Property Mapping', bases=("passbook_core.policymodel",),
'verbose_name_plural': 'Property Mappings',
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='DebugPolicy', name="Source",
fields=[ fields=[
('policy_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Policy')), (
('result', models.BooleanField(default=False)), "policymodel_ptr",
('wait_min', models.IntegerField(default=5)), models.OneToOneField(
('wait_max', models.IntegerField(default=30)), auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_core.PolicyModel",
),
),
("name", models.TextField()),
("slug", models.SlugField()),
("enabled", models.BooleanField(default=True)),
], ],
options={ options={"abstract": False,},
'verbose_name': 'Debug Policy', bases=("passbook_core.policymodel",),
'verbose_name_plural': 'Debug Policies',
},
bases=('passbook_core.policy',),
), ),
migrations.CreateModel( migrations.CreateModel(
name='Factor', name="Provider",
fields=[ fields=[
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')), (
('name', models.TextField()), "id",
('slug', models.SlugField(unique=True)), models.AutoField(
('order', models.IntegerField()), auto_created=True,
('enabled', models.BooleanField(default=True)), primary_key=True,
], serialize=False,
options={ verbose_name="ID",
'abstract': False, ),
}, ),
bases=('passbook_core.policymodel',), (
), "property_mappings",
migrations.CreateModel( models.ManyToManyField(
name='Source', blank=True, default=None, to="passbook_core.PropertyMapping"
fields=[ ),
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')), ),
('name', models.TextField()),
('slug', models.SlugField()),
('enabled', models.BooleanField(default=True)),
],
options={
'abstract': False,
},
bases=('passbook_core.policymodel',),
),
migrations.CreateModel(
name='Provider',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('property_mappings', models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping')),
], ],
), ),
migrations.CreateModel( migrations.CreateModel(
name='Nonce', name="Nonce",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('expires', models.DateTimeField(default=passbook.core.models.default_nonce_duration)), "uuid",
('expiring', models.BooleanField(default=True)), models.UUIDField(
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
(
"expires",
models.DateTimeField(
default=passbook.core.models.default_nonce_duration
),
),
("expiring", models.BooleanField(default=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
],
options={"verbose_name": "Nonce", "verbose_name_plural": "Nonces",},
),
migrations.CreateModel(
name="Invitation",
fields=[
(
"uuid",
models.UUIDField(
default=uuid.uuid4,
editable=False,
primary_key=True,
serialize=False,
),
),
("expires", models.DateTimeField(blank=True, default=None, null=True)),
("fixed_username", models.TextField(blank=True, default=None)),
("fixed_email", models.TextField(blank=True, default=None)),
("needs_confirmation", models.BooleanField(default=True)),
(
"created_by",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
], ],
options={ options={
'verbose_name': 'Nonce', "verbose_name": "Invitation",
'verbose_name_plural': 'Nonces', "verbose_name_plural": "Invitations",
}, },
), ),
migrations.CreateModel( migrations.CreateModel(
name='Invitation', name="Group",
fields=[ fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)), (
('expires', models.DateTimeField(blank=True, default=None, null=True)), "uuid",
('fixed_username', models.TextField(blank=True, default=None)), models.UUIDField(
('fixed_email', models.TextField(blank=True, default=None)), default=uuid.uuid4,
('needs_confirmation', models.BooleanField(default=True)), editable=False,
('created_by', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), primary_key=True,
serialize=False,
),
),
("name", models.CharField(max_length=80, verbose_name="name")),
(
"tags",
django.contrib.postgres.fields.jsonb.JSONField(
blank=True, default=dict
),
),
(
"parent",
models.ForeignKey(
blank=True,
null=True,
on_delete=django.db.models.deletion.SET_NULL,
related_name="children",
to="passbook_core.Group",
),
),
], ],
options={ options={"unique_together": {("name", "parent")},},
'verbose_name': 'Invitation',
'verbose_name_plural': 'Invitations',
},
),
migrations.CreateModel(
name='Group',
fields=[
('uuid', models.UUIDField(default=uuid.uuid4, editable=False, primary_key=True, serialize=False)),
('name', models.CharField(max_length=80, verbose_name='name')),
('tags', django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict)),
('parent', models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='children', to='passbook_core.Group')),
],
options={
'unique_together': {('name', 'parent')},
},
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='groups', name="groups",
field=models.ManyToManyField(to='passbook_core.Group'), field=models.ManyToManyField(to="passbook_core.Group"),
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='user_permissions', name="user_permissions",
field=models.ManyToManyField(blank=True, help_text='Specific permissions for this user.', related_name='user_set', related_query_name='user', to='auth.Permission', verbose_name='user permissions'), field=models.ManyToManyField(
blank=True,
help_text="Specific permissions for this user.",
related_name="user_set",
related_query_name="user",
to="auth.Permission",
verbose_name="user permissions",
),
), ),
migrations.CreateModel( migrations.CreateModel(
name='UserSourceConnection', name="UserSourceConnection",
fields=[ fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), (
('created', models.DateTimeField(auto_now_add=True)), "id",
('last_updated', models.DateTimeField(auto_now=True)), models.AutoField(
('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)), auto_created=True,
('source', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, to='passbook_core.Source')), primary_key=True,
serialize=False,
verbose_name="ID",
),
),
("created", models.DateTimeField(auto_now_add=True)),
("last_updated", models.DateTimeField(auto_now=True)),
(
"user",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to=settings.AUTH_USER_MODEL,
),
),
(
"source",
models.ForeignKey(
on_delete=django.db.models.deletion.CASCADE,
to="passbook_core.Source",
),
),
], ],
options={ options={"unique_together": {("user", "source")},},
'unique_together': {('user', 'source')},
},
), ),
migrations.CreateModel( migrations.CreateModel(
name='Application', name="Application",
fields=[ fields=[
('policymodel_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.PolicyModel')), (
('name', models.TextField()), "policymodel_ptr",
('slug', models.SlugField()), models.OneToOneField(
('launch_url', models.URLField(blank=True, null=True)), auto_created=True,
('icon_url', models.TextField(blank=True, null=True)), on_delete=django.db.models.deletion.CASCADE,
('skip_authorization', models.BooleanField(default=False)), parent_link=True,
('provider', models.OneToOneField(blank=True, default=None, null=True, on_delete=django.db.models.deletion.SET_DEFAULT, to='passbook_core.Provider')), primary_key=True,
serialize=False,
to="passbook_core.PolicyModel",
),
),
("name", models.TextField()),
("slug", models.SlugField()),
("launch_url", models.URLField(blank=True, null=True)),
("icon_url", models.TextField(blank=True, null=True)),
("skip_authorization", models.BooleanField(default=False)),
(
"provider",
models.OneToOneField(
blank=True,
default=None,
null=True,
on_delete=django.db.models.deletion.SET_DEFAULT,
to="passbook_core.Provider",
),
),
], ],
options={ options={"abstract": False,},
'abstract': False, bases=("passbook_core.policymodel",),
},
bases=('passbook_core.policymodel',),
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='sources', name="sources",
field=models.ManyToManyField(through='passbook_core.UserSourceConnection', to='passbook_core.Source'), field=models.ManyToManyField(
through="passbook_core.UserSourceConnection", to="passbook_core.Source"
),
), ),
] ]

View File

@ -6,12 +6,12 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('passbook_core', '0001_initial'), ("passbook_core", "0001_initial"),
] ]
operations = [ operations = [
migrations.AlterModelOptions( migrations.AlterModelOptions(
name='user', name="user",
options={'permissions': (('reset_user_password', 'Reset Password'),)}, options={"permissions": (("reset_user_password", "Reset Password"),)},
), ),
] ]

View File

@ -6,13 +6,13 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('passbook_core', '0001_initial'), ("passbook_core", "0001_initial"),
] ]
operations = [ operations = [
migrations.AddField( migrations.AddField(
model_name='nonce', model_name="nonce",
name='description', name="description",
field=models.TextField(blank=True, default=''), field=models.TextField(blank=True, default=""),
), ),
] ]

View File

@ -7,23 +7,25 @@ from django.db import migrations, models
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('passbook_core', '0002_nonce_description'), ("passbook_core", "0002_nonce_description"),
] ]
operations = [ operations = [
migrations.RenameField( migrations.RenameField(
model_name='group', model_name="group", old_name="tags", new_name="attributes",
old_name='tags',
new_name='attributes',
), ),
migrations.AddField( migrations.AddField(
model_name='source', model_name="source",
name='property_mappings', name="property_mappings",
field=models.ManyToManyField(blank=True, default=None, to='passbook_core.PropertyMapping'), field=models.ManyToManyField(
blank=True, default=None, to="passbook_core.PropertyMapping"
),
), ),
migrations.AddField( migrations.AddField(
model_name='user', model_name="user",
name='attributes', name="attributes",
field=django.contrib.postgres.fields.jsonb.JSONField(blank=True, default=dict), field=django.contrib.postgres.fields.jsonb.JSONField(
blank=True, default=dict
),
), ),
] ]

View File

@ -6,9 +6,8 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('passbook_core', '0002_auto_20191010_1058'), ("passbook_core", "0002_auto_20191010_1058"),
('passbook_core', '0002_nonce_description'), ("passbook_core", "0002_nonce_description"),
] ]
operations = [ operations = []
]

View File

@ -6,12 +6,9 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('passbook_core', '0003_auto_20191011_0914'), ("passbook_core", "0003_auto_20191011_0914"),
] ]
operations = [ operations = [
migrations.RemoveField( migrations.RemoveField(model_name="policy", name="action",),
model_name='policy',
name='action',
),
] ]

View File

@ -6,9 +6,8 @@ from django.db import migrations
class Migration(migrations.Migration): class Migration(migrations.Migration):
dependencies = [ dependencies = [
('passbook_core', '0004_remove_policy_action'), ("passbook_core", "0004_remove_policy_action"),
('passbook_core', '0003_merge_20191010_1541'), ("passbook_core", "0003_merge_20191010_1541"),
] ]
operations = [ operations = []
]

View File

@ -27,12 +27,18 @@ def default_nonce_duration():
"""Default duration a Nonce is valid""" """Default duration a Nonce is valid"""
return now() + timedelta(hours=4) 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"""
name = models.CharField(_('name'), max_length=80) name = models.CharField(_("name"), max_length=80)
parent = models.ForeignKey('Group', blank=True, null=True, parent = models.ForeignKey(
on_delete=models.SET_NULL, related_name='children') "Group",
blank=True,
null=True,
on_delete=models.SET_NULL,
related_name="children",
)
attributes = JSONField(default=dict, blank=True) attributes = JSONField(default=dict, blank=True)
def __str__(self): def __str__(self):
@ -40,7 +46,8 @@ class Group(UUIDModel):
class Meta: class Meta:
unique_together = (('name', 'parent',),) unique_together = (("name", "parent",),)
class User(GuardianUserMixin, AbstractUser): class User(GuardianUserMixin, AbstractUser):
"""Custom User model to allow easier adding o f user-based settings""" """Custom User model to allow easier adding o f user-based settings"""
@ -48,8 +55,8 @@ class User(GuardianUserMixin, AbstractUser):
uuid = models.UUIDField(default=uuid4, editable=False) uuid = models.UUIDField(default=uuid4, editable=False)
name = models.TextField() name = models.TextField()
sources = models.ManyToManyField('Source', through='UserSourceConnection') sources = models.ManyToManyField("Source", through="UserSourceConnection")
groups = models.ManyToManyField('Group') groups = models.ManyToManyField("Group")
password_change_date = models.DateTimeField(auto_now_add=True) password_change_date = models.DateTimeField(auto_now_add=True)
attributes = JSONField(default=dict, blank=True) attributes = JSONField(default=dict, blank=True)
@ -62,28 +69,29 @@ class User(GuardianUserMixin, AbstractUser):
class Meta: class Meta:
permissions = ( permissions = (("reset_user_password", "Reset Password"),)
('reset_user_password', 'Reset Password'),
)
class Provider(models.Model): class Provider(models.Model):
"""Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application""" """Application-independent Provider instance. For example SAML2 Remote, OAuth2 Application"""
property_mappings = models.ManyToManyField('PropertyMapping', default=None, blank=True) property_mappings = models.ManyToManyField(
"PropertyMapping", default=None, blank=True
)
objects = InheritanceManager() objects = InheritanceManager()
# This class defines no field for easier inheritance # This class defines no field for easier inheritance
def __str__(self): def __str__(self):
if hasattr(self, 'name'): if hasattr(self, "name"):
return getattr(self, 'name') return getattr(self, "name")
return super().__str__() return super().__str__()
class PolicyModel(UUIDModel, CreatedUpdatedModel): class PolicyModel(UUIDModel, CreatedUpdatedModel):
"""Base model which can have policies applied to it""" """Base model which can have policies applied to it"""
policies = models.ManyToManyField('Policy', blank=True) policies = models.ManyToManyField("Policy", blank=True)
class UserSettings: class UserSettings:
@ -108,8 +116,8 @@ class Factor(PolicyModel):
enabled = models.BooleanField(default=True) enabled = models.BooleanField(default=True)
objects = InheritanceManager() objects = InheritanceManager()
type = '' type = ""
form = '' form = ""
def user_settings(self) -> Optional[UserSettings]: def user_settings(self) -> Optional[UserSettings]:
"""Entrypoint to integrate with User settings. Can either return None if no """Entrypoint to integrate with User settings. Can either return None if no
@ -129,8 +137,9 @@ class Application(PolicyModel):
slug = models.SlugField() slug = models.SlugField()
launch_url = models.URLField(null=True, blank=True) launch_url = models.URLField(null=True, blank=True)
icon_url = models.TextField(null=True, blank=True) icon_url = models.TextField(null=True, blank=True)
provider = models.OneToOneField('Provider', null=True, blank=True, provider = models.OneToOneField(
default=None, on_delete=models.SET_DEFAULT) "Provider", null=True, blank=True, default=None, on_delete=models.SET_DEFAULT
)
skip_authorization = models.BooleanField(default=False) skip_authorization = models.BooleanField(default=False)
objects = InheritanceManager() objects = InheritanceManager()
@ -151,9 +160,11 @@ class Source(PolicyModel):
name = models.TextField() name = models.TextField()
slug = models.SlugField() slug = models.SlugField()
enabled = models.BooleanField(default=True) enabled = models.BooleanField(default=True)
property_mappings = models.ManyToManyField('PropertyMapping', default=None, blank=True) property_mappings = models.ManyToManyField(
"PropertyMapping", default=None, blank=True
)
form = '' # ModelForm-based class ued to create/edit instance form = "" # ModelForm-based class ued to create/edit instance
objects = InheritanceManager() objects = InheritanceManager()
@ -185,7 +196,7 @@ class UserSourceConnection(CreatedUpdatedModel):
class Meta: class Meta:
unique_together = (('user', 'source'),) unique_together = (("user", "source"),)
class Policy(UUIDModel, CreatedUpdatedModel): class Policy(UUIDModel, CreatedUpdatedModel):
@ -215,25 +226,25 @@ class DebugPolicy(Policy):
wait_min = models.IntegerField(default=5) wait_min = models.IntegerField(default=5)
wait_max = models.IntegerField(default=30) wait_max = models.IntegerField(default=30)
form = 'passbook.core.forms.policies.DebugPolicyForm' form = "passbook.core.forms.policies.DebugPolicyForm"
def passes(self, request: PolicyRequest) -> PolicyResult: def passes(self, request: PolicyRequest) -> PolicyResult:
"""Wait random time then return result""" """Wait random time then return result"""
wait = SystemRandom().randrange(self.wait_min, self.wait_max) wait = SystemRandom().randrange(self.wait_min, self.wait_max)
LOGGER.debug("Policy waiting", policy=self, delay=wait) LOGGER.debug("Policy waiting", policy=self, delay=wait)
sleep(wait) sleep(wait)
return PolicyResult(self.result, 'Debugging') return PolicyResult(self.result, "Debugging")
class Meta: class Meta:
verbose_name = _('Debug Policy') verbose_name = _("Debug Policy")
verbose_name_plural = _('Debug Policies') verbose_name_plural = _("Debug Policies")
class Invitation(UUIDModel): class Invitation(UUIDModel):
"""Single-use invitation link""" """Single-use invitation link"""
created_by = models.ForeignKey('User', on_delete=models.CASCADE) created_by = models.ForeignKey("User", on_delete=models.CASCADE)
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)
@ -242,24 +253,26 @@ class Invitation(UUIDModel):
@property @property
def link(self): def link(self):
"""Get link to use invitation""" """Get link to use invitation"""
return reverse_lazy('passbook_core:auth-sign-up') + f'?invitation={self.uuid.hex}' return (
reverse_lazy("passbook_core:auth-sign-up") + f"?invitation={self.uuid.hex}"
)
def __str__(self): def __str__(self):
return f"Invitation {self.uuid.hex} created by {self.created_by}" return f"Invitation {self.uuid.hex} created by {self.created_by}"
class Meta: class Meta:
verbose_name = _('Invitation') verbose_name = _("Invitation")
verbose_name_plural = _('Invitations') verbose_name_plural = _("Invitations")
class Nonce(UUIDModel): class Nonce(UUIDModel):
"""One-time link for password resets/sign-up-confirmations""" """One-time link for password resets/sign-up-confirmations"""
expires = models.DateTimeField(default=default_nonce_duration) expires = models.DateTimeField(default=default_nonce_duration)
user = models.ForeignKey('User', on_delete=models.CASCADE) user = models.ForeignKey("User", on_delete=models.CASCADE)
expiring = models.BooleanField(default=True) expiring = models.BooleanField(default=True)
description = models.TextField(default='', blank=True) description = models.TextField(default="", blank=True)
@property @property
def is_expired(self) -> bool: def is_expired(self) -> bool:
@ -271,8 +284,8 @@ class Nonce(UUIDModel):
class Meta: class Meta:
verbose_name = _('Nonce') verbose_name = _("Nonce")
verbose_name_plural = _('Nonces') verbose_name_plural = _("Nonces")
class PropertyMapping(UUIDModel): class PropertyMapping(UUIDModel):
@ -280,7 +293,7 @@ class PropertyMapping(UUIDModel):
name = models.TextField() name = models.TextField()
form = '' form = ""
objects = InheritanceManager() objects = InheritanceManager()
def __str__(self): def __str__(self):
@ -288,5 +301,5 @@ class PropertyMapping(UUIDModel):
class Meta: class Meta:
verbose_name = _('Property Mapping') verbose_name = _("Property Mapping")
verbose_name_plural = _('Property Mappings') verbose_name_plural = _("Property Mappings")

View File

@ -7,10 +7,10 @@ from structlog import get_logger
LOGGER = get_logger() LOGGER = get_logger()
user_signed_up = Signal(providing_args=['request', 'user']) user_signed_up = Signal(providing_args=["request", "user"])
invitation_created = Signal(providing_args=['request', 'invitation']) invitation_created = Signal(providing_args=["request", "invitation"])
invitation_used = Signal(providing_args=['request', 'invitation', 'user']) invitation_used = Signal(providing_args=["request", "invitation", "user"])
password_changed = Signal(providing_args=['user', 'password']) password_changed = Signal(providing_args=["user", "password"])
@receiver(post_save) @receiver(post_save)
@ -18,6 +18,7 @@ password_changed = Signal(providing_args=['user', 'password'])
def invalidate_policy_cache(sender, instance, **_): def invalidate_policy_cache(sender, instance, **_):
"""Invalidate Policy cache when policy is updated""" """Invalidate Policy cache when policy is updated"""
from passbook.core.models import Policy from passbook.core.models import Policy
if isinstance(instance, Policy): if isinstance(instance, Policy):
LOGGER.debug("Invalidating policy cache", policy=instance) LOGGER.debug("Invalidating policy cache", policy=instance)
keys = cache.keys("%s#*" % instance.pk) keys = cache.keys("%s#*" % instance.pk)

View File

@ -7,8 +7,9 @@ from passbook.root.celery import CELERY_APP
LOGGER = get_logger() LOGGER = get_logger()
@CELERY_APP.task() @CELERY_APP.task()
def clean_nonces(): def clean_nonces():
"""Remove expired nonces""" """Remove expired nonces"""
amount, _ = Nonce.objects.filter(expires__lt=now(), expiring=True).delete() amount, _ = Nonce.objects.filter(expires__lt=now(), expiring=True).delete()
LOGGER.debug('Deleted expired nonces', amount=amount) LOGGER.debug("Deleted expired nonces", amount=amount)

View File

@ -9,29 +9,37 @@ from passbook.policies.engine import PolicyEngine
register = template.Library() register = template.Library()
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def user_factors(context: RequestContext) -> List[UserSettings]: def user_factors(context: RequestContext) -> List[UserSettings]:
"""Return list of all factors which apply to user""" """Return list of all factors which apply to user"""
user = context.get('request').user user = context.get("request").user
_all_factors = Factor.objects.filter(enabled=True).order_by('order').select_subclasses() _all_factors = (
Factor.objects.filter(enabled=True).order_by("order").select_subclasses()
)
matching_factors: List[UserSettings] = [] matching_factors: List[UserSettings] = []
for factor in _all_factors: for factor in _all_factors:
user_settings = factor.user_settings() user_settings = factor.user_settings()
policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request')) policy_engine = PolicyEngine(
factor.policies.all(), user, context.get("request")
)
policy_engine.build() policy_engine.build()
if policy_engine.passing and user_settings: if policy_engine.passing and user_settings:
matching_factors.append(user_settings) matching_factors.append(user_settings)
return matching_factors return matching_factors
@register.simple_tag(takes_context=True) @register.simple_tag(takes_context=True)
def user_sources(context: RequestContext) -> List[UserSettings]: def user_sources(context: RequestContext) -> List[UserSettings]:
"""Return a list of all sources which are enabled for the user""" """Return a list of all sources which are enabled for the user"""
user = context.get('request').user user = context.get("request").user
_all_sources = Source.objects.filter(enabled=True).select_subclasses() _all_sources = Source.objects.filter(enabled=True).select_subclasses()
matching_sources: List[UserSettings] = [] matching_sources: List[UserSettings] = []
for factor in _all_sources: for factor in _all_sources:
user_settings = factor.user_settings() user_settings = factor.user_settings()
policy_engine = PolicyEngine(factor.policies.all(), user, context.get('request')) policy_engine = PolicyEngine(
factor.policies.all(), user, context.get("request")
)
policy_engine.build() policy_engine.build()
if policy_engine.passing and user_settings: if policy_engine.passing and user_settings:
matching_sources.append(user_settings) matching_sources.append(user_settings)

View File

@ -15,70 +15,78 @@ class TestAuthenticationViews(TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.sign_up_data = { self.sign_up_data = {
'name': 'Test', "name": "Test",
'username': 'beryjuorg', "username": "beryjuorg",
'email': 'unittest@passbook.beryju.org', "email": "unittest@passbook.beryju.org",
'password': 'B3ryju0rg!', "password": "B3ryju0rg!",
'password_repeat': 'B3ryju0rg!', "password_repeat": "B3ryju0rg!",
} }
self.login_data = { self.login_data = {
'uid_field': 'unittest@example.com', "uid_field": "unittest@example.com",
} }
self.user = User.objects.create_superuser( self.user = User.objects.create_superuser(
username='unittest user', username="unittest user",
email='unittest@example.com', email="unittest@example.com",
password=''.join(SystemRandom().choice( password="".join(
string.ascii_uppercase + string.digits) for _ in range(8))) SystemRandom().choice(string.ascii_uppercase + string.digits)
for _ in range(8)
),
)
def test_sign_up_view(self): def test_sign_up_view(self):
"""Test account.sign_up view (Anonymous)""" """Test account.sign_up view (Anonymous)"""
self.client.logout() self.client.logout()
response = self.client.get(reverse('passbook_core:auth-sign-up')) response = self.client.get(reverse("passbook_core:auth-sign-up"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
def test_login_view(self): def test_login_view(self):
"""Test account.login view (Anonymous)""" """Test account.login view (Anonymous)"""
self.client.logout() self.client.logout()
response = self.client.get(reverse('passbook_core:auth-login')) response = self.client.get(reverse("passbook_core:auth-login"))
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)
# test login with post # test login with post
form = LoginForm(self.login_data) form = LoginForm(self.login_data)
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
response = self.client.post(reverse('passbook_core:auth-login'), data=form.cleaned_data) response = self.client.post(
reverse("passbook_core:auth-login"), data=form.cleaned_data
)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
def test_logout_view(self): def test_logout_view(self):
"""Test account.logout view""" """Test account.logout view"""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse('passbook_core:auth-logout')) response = self.client.get(reverse("passbook_core:auth-logout"))
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
def test_sign_up_view_auth(self): def test_sign_up_view_auth(self):
"""Test account.sign_up view (Authenticated)""" """Test account.sign_up view (Authenticated)"""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse('passbook_core:auth-logout')) response = self.client.get(reverse("passbook_core:auth-logout"))
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
def test_login_view_auth(self): def test_login_view_auth(self):
"""Test account.login view (Authenticated)""" """Test account.login view (Authenticated)"""
self.client.force_login(self.user) self.client.force_login(self.user)
response = self.client.get(reverse('passbook_core:auth-login')) response = self.client.get(reverse("passbook_core:auth-login"))
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
def test_login_view_post(self): def test_login_view_post(self):
"""Test account.login view POST (Anonymous)""" """Test account.login view POST (Anonymous)"""
login_response = self.client.post(reverse('passbook_core:auth-login'), data=self.login_data) 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.status_code, 302)
self.assertEqual(login_response.url, reverse('passbook_core:auth-process')) self.assertEqual(login_response.url, reverse("passbook_core:auth-process"))
def test_sign_up_view_post(self): def test_sign_up_view_post(self):
"""Test account.sign_up view POST (Anonymous)""" """Test account.sign_up view POST (Anonymous)"""
form = SignUpForm(self.sign_up_data) form = SignUpForm(self.sign_up_data)
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
response = self.client.post(reverse('passbook_core:auth-sign-up'), data=form.cleaned_data) response = self.client.post(
reverse("passbook_core:auth-sign-up"), data=form.cleaned_data
)
self.assertEqual(response.status_code, 302) self.assertEqual(response.status_code, 302)
# def test_reset_password_init_view(self): # def test_reset_password_init_view(self):

View File

@ -14,12 +14,17 @@ class TestOverviewViews(TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user = User.objects.create_superuser( self.user = User.objects.create_superuser(
username='unittest user', username="unittest user",
email='unittest@example.com', email="unittest@example.com",
password=''.join(SystemRandom().choice( password="".join(
string.ascii_uppercase + string.digits) for _ in range(8))) SystemRandom().choice(string.ascii_uppercase + string.digits)
for _ in range(8)
),
)
self.client.force_login(self.user) self.client.force_login(self.user)
def test_overview(self): def test_overview(self):
"""Test UserSettingsView""" """Test UserSettingsView"""
self.assertEqual(self.client.get(reverse('passbook_core:overview')).status_code, 200) self.assertEqual(
self.client.get(reverse("passbook_core:overview")).status_code, 200
)

View File

@ -15,33 +15,43 @@ class TestUserViews(TestCase):
def setUp(self): def setUp(self):
super().setUp() super().setUp()
self.user = User.objects.create_superuser( self.user = User.objects.create_superuser(
username='unittest user', username="unittest user",
email='unittest@example.com', email="unittest@example.com",
password=''.join(SystemRandom().choice( password="".join(
string.ascii_uppercase + string.digits) for _ in range(8))) SystemRandom().choice(string.ascii_uppercase + string.digits)
for _ in range(8)
),
)
self.client.force_login(self.user) self.client.force_login(self.user)
def test_user_settings(self): def test_user_settings(self):
"""Test UserSettingsView""" """Test UserSettingsView"""
self.assertEqual(self.client.get(reverse('passbook_core:user-settings')).status_code, 200) self.assertEqual(
self.client.get(reverse("passbook_core:user-settings")).status_code, 200
)
def test_user_delete(self): def test_user_delete(self):
"""Test UserDeleteView""" """Test UserDeleteView"""
self.assertEqual(self.client.post(reverse('passbook_core:user-delete')).status_code, 302) self.assertEqual(
self.assertEqual(User.objects.filter(username='unittest user').exists(), False) self.client.post(reverse("passbook_core:user-delete")).status_code, 302
)
self.assertEqual(User.objects.filter(username="unittest user").exists(), False)
self.setUp() self.setUp()
def test_user_change_password(self): def test_user_change_password(self):
"""Test UserChangePasswordView""" """Test UserChangePasswordView"""
form_data = { form_data = {"password": "test2", "password_repeat": "test2"}
'password': 'test2',
'password_repeat': 'test2'
}
form = PasswordChangeForm(data=form_data) form = PasswordChangeForm(data=form_data)
self.assertTrue(form.is_valid()) self.assertTrue(form.is_valid())
self.assertEqual(self.client.get( self.assertEqual(
reverse('passbook_core:user-change-password')).status_code, 200) self.client.get(reverse("passbook_core:user-change-password")).status_code,
self.assertEqual(self.client.post( 200,
reverse('passbook_core:user-change-password'), data=form_data).status_code, 302) )
self.assertEqual(
self.client.post(
reverse("passbook_core:user-change-password"), data=form_data
).status_code,
302,
)
self.user.refresh_from_db() self.user.refresh_from_db()
self.assertTrue(self.user.check_password('test2')) self.assertTrue(self.user.check_password("test2"))

View File

@ -13,22 +13,25 @@ class TestUtilViews(TestCase):
def setUp(self): def setUp(self):
self.user = User.objects.create_superuser( self.user = User.objects.create_superuser(
username='unittest user', username="unittest user",
email='unittest@example.com', email="unittest@example.com",
password=''.join(SystemRandom().choice( password="".join(
string.ascii_uppercase + string.digits) for _ in range(8))) SystemRandom().choice(string.ascii_uppercase + string.digits)
for _ in range(8)
),
)
self.factory = RequestFactory() self.factory = RequestFactory()
def test_loading_view(self): def test_loading_view(self):
"""Test loading view""" """Test loading view"""
request = self.factory.get('something') request = self.factory.get("something")
response = LoadingView.as_view(target_url='somestring')(request) response = LoadingView.as_view(target_url="somestring")(request)
response.render() response.render()
self.assertIn('somestring', response.content.decode('utf-8')) self.assertIn("somestring", response.content.decode("utf-8"))
def test_permission_denied_view(self): def test_permission_denied_view(self):
"""Test PermissionDeniedView""" """Test PermissionDeniedView"""
request = self.factory.get('something') request = self.factory.get("something")
request.user = self.user request.user = self.user
response = PermissionDeniedView.as_view()(request) response = PermissionDeniedView.as_view()(request)
self.assertEqual(response.status_code, 200) self.assertEqual(response.status_code, 200)

View File

@ -9,21 +9,38 @@ LOGGER = get_logger()
urlpatterns = [ urlpatterns = [
# Authentication views # Authentication views
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(), path(
name='auth-sign-up-confirm'), "auth/sign_up/<uuid:nonce>/confirm/",
path('auth/process/denied/', view.FactorPermissionDeniedView.as_view(), name='auth-denied'), authentication.SignUpConfirmView.as_view(),
path('auth/password/reset/<uuid:nonce>/', authentication.PasswordResetView.as_view(), name="auth-sign-up-confirm",
name='auth-password-reset'), ),
path('auth/process/', view.AuthenticationView.as_view(), name='auth-process'), path(
path('auth/process/<slug:factor>/', view.AuthenticationView.as_view(), name='auth-process'), "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/<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(
name='user-change-password'), "_/user/change_password/",
user.UserChangePasswordView.as_view(),
name="user-change-password",
),
# Overview # Overview
path('', overview.OverviewView.as_view(), name='overview'), path("", overview.OverviewView.as_view(), name="overview"),
] ]

View File

@ -11,6 +11,7 @@ from passbook.policies.engine import PolicyEngine
LOGGER = get_logger() LOGGER = get_logger()
class AccessMixin: 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"""
@ -23,12 +24,18 @@ class AccessMixin:
try: try:
return provider.application return provider.application
except Application.DoesNotExist as exc: except Application.DoesNotExist as exc:
messages.error(self.request, _('Provider "%(name)s" has no application assigned' % { messages.error(
'name': provider self.request,
})) _(
'Provider "%(name)s" has no application assigned'
% {"name": provider}
),
)
raise exc raise exc
def user_has_access(self, application: Application, user: User) -> Tuple[bool, List[str]]: def user_has_access(
self, application: Application, user: User
) -> Tuple[bool, List[str]]:
"""Check if user has access to application.""" """Check if user has access to application."""
LOGGER.debug("Checking permissions", user=user, application=application) LOGGER.debug("Checking permissions", user=user, application=application)
policy_engine = PolicyEngine(application.policies.all(), user, self.request) policy_engine = PolicyEngine(application.policies.all(), user, self.request)

View File

@ -25,41 +25,41 @@ LOGGER = get_logger()
class LoginView(UserPassesTestMixin, FormView): class LoginView(UserPassesTestMixin, FormView):
"""Allow users to sign in""" """Allow users to sign in"""
template_name = 'login/form.html' template_name = "login/form.html"
form_class = LoginForm form_class = LoginForm
success_url = '.' success_url = "."
# Allow only not authenticated users to login # Allow only not authenticated users to login
def test_func(self): def test_func(self):
return self.request.user.is_authenticated is False return self.request.user.is_authenticated is False
def handle_no_permission(self): def handle_no_permission(self):
if 'next' in self.request.GET: if "next" in self.request.GET:
return redirect(self.request.GET.get('next')) return redirect(self.request.GET.get("next"))
return redirect(reverse('passbook_core:overview')) return redirect(reverse("passbook_core:overview"))
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['config'] = CONFIG.y('passbook') kwargs["config"] = CONFIG.y("passbook")
kwargs['is_login'] = True kwargs["is_login"] = True
kwargs['title'] = _('Log in to your account') kwargs["title"] = _("Log in to your account")
kwargs['primary_action'] = _('Log in') kwargs["primary_action"] = _("Log in")
kwargs['show_sign_up_notice'] = CONFIG.y('passbook.sign_up.enabled') kwargs["show_sign_up_notice"] = CONFIG.y("passbook.sign_up.enabled")
kwargs['sources'] = [] kwargs["sources"] = []
sources = Source.objects.filter(enabled=True).select_subclasses() sources = Source.objects.filter(enabled=True).select_subclasses()
for source in sources: for source in sources:
login_button = source.login_button login_button = source.login_button
if login_button: if login_button:
kwargs['sources'].append(login_button) kwargs["sources"].append(login_button)
if kwargs['sources']: if kwargs["sources"]:
self.template_name = 'login/with_sources.html' self.template_name = "login/with_sources.html"
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def get_user(self, uid_value) -> Optional[User]: def get_user(self, uid_value) -> Optional[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 # Workaround for E-Mail -> email
if search_field == 'e-mail': if search_field == "e-mail":
search_field = 'email' 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", user=users.first(), uid_field=search_field) LOGGER.debug("Found user", user=users.first(), uid_field=search_field)
@ -68,18 +68,20 @@ class LoginView(UserPassesTestMixin, FormView):
def form_valid(self, form: LoginForm) -> HttpResponse: def form_valid(self, form: LoginForm) -> HttpResponse:
"""Form data is valid""" """Form data is valid"""
pre_user = self.get_user(form.cleaned_data.get('uid_field')) pre_user = self.get_user(form.cleaned_data.get("uid_field"))
if not pre_user: if not pre_user:
# No user found # No user found
return self.invalid_login(self.request) return self.invalid_login(self.request)
# self.request.session.flush() # self.request.session.flush()
self.request.session[AuthenticationView.SESSION_PENDING_USER] = pre_user.pk self.request.session[AuthenticationView.SESSION_PENDING_USER] = pre_user.pk
return _redirect_with_qs('passbook_core:auth-process', self.request.GET) return _redirect_with_qs("passbook_core:auth-process", self.request.GET)
def invalid_login(self, request: HttpRequest, disabled_user: User = None) -> HttpResponse: def invalid_login(
self, request: HttpRequest, disabled_user: User = None
) -> HttpResponse:
"""Handle login for disabled users/invalid login attempts""" """Handle login for disabled users/invalid login attempts"""
LOGGER.debug("invalid_login", user=disabled_user) LOGGER.debug("invalid_login", user=disabled_user)
messages.error(request, _('Failed to authenticate.')) messages.error(request, _("Failed to authenticate."))
return self.render_to_response(self.get_context_data()) return self.render_to_response(self.get_context_data())
@ -90,15 +92,15 @@ class LogoutView(LoginRequiredMixin, View):
"""Log current user out""" """Log current user out"""
logout(request) logout(request)
messages.success(request, _("You've successfully been logged out.")) messages.success(request, _("You've successfully been logged out."))
return redirect(reverse('passbook_core:auth-login')) return redirect(reverse("passbook_core:auth-login"))
class SignUpView(UserPassesTestMixin, FormView): class SignUpView(UserPassesTestMixin, FormView):
"""Sign up new user, optionally consume one-use invitation link.""" """Sign up new user, optionally consume one-use invitation link."""
template_name = 'login/form.html' template_name = "login/form.html"
form_class = SignUpForm form_class = SignUpForm
success_url = '.' success_url = "."
# Invitation instance, if invitation link was used # Invitation instance, if invitation link was used
_invitation = None _invitation = None
# Instance of newly created user # Instance of newly created user
@ -109,38 +111,38 @@ class SignUpView(UserPassesTestMixin, FormView):
return self.request.user.is_authenticated is False return self.request.user.is_authenticated is False
def handle_no_permission(self): def handle_no_permission(self):
return redirect(reverse('passbook_core:overview')) return redirect(reverse("passbook_core:overview"))
def dispatch(self, request, *args, **kwargs): def dispatch(self, request, *args, **kwargs):
"""Check if sign-up is enabled or invitation link given""" """Check if sign-up is enabled or invitation link given"""
allowed = False allowed = False
if 'invitation' in request.GET: if "invitation" in request.GET:
invitations = Invitation.objects.filter(uuid=request.GET.get('invitation')) invitations = Invitation.objects.filter(uuid=request.GET.get("invitation"))
allowed = invitations.exists() allowed = invitations.exists()
if allowed: if allowed:
self._invitation = invitations.first() self._invitation = invitations.first()
if CONFIG.y('passbook.sign_up.enabled'): if CONFIG.y("passbook.sign_up.enabled"):
allowed = True allowed = True
if not allowed: if not allowed:
messages.error(request, _('Sign-ups are currently disabled.')) messages.error(request, _("Sign-ups are currently disabled."))
return redirect(reverse('passbook_core:auth-login')) return redirect(reverse("passbook_core:auth-login"))
return super().dispatch(request, *args, **kwargs) return super().dispatch(request, *args, **kwargs)
def get_initial(self): def get_initial(self):
if self._invitation: if self._invitation:
initial = {} initial = {}
if self._invitation.fixed_username: if self._invitation.fixed_username:
initial['username'] = self._invitation.fixed_username initial["username"] = self._invitation.fixed_username
if self._invitation.fixed_email: if self._invitation.fixed_email:
initial['email'] = self._invitation.fixed_email initial["email"] = self._invitation.fixed_email
return initial return initial
return super().get_initial() return super().get_initial()
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['config'] = CONFIG.y('passbook') kwargs["config"] = CONFIG.y("passbook")
kwargs['is_login'] = True kwargs["is_login"] = True
kwargs['title'] = _('Sign Up') kwargs["title"] = _("Sign Up")
kwargs['primary_action'] = _('Sign up') kwargs["primary_action"] = _("Sign up")
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def form_valid(self, form: SignUpForm) -> HttpResponse: def form_valid(self, form: SignUpForm) -> HttpResponse:
@ -173,9 +175,8 @@ class SignUpView(UserPassesTestMixin, FormView):
# self._user.save() # 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", form.cleaned_data.get("email"))
form.cleaned_data.get('email')) return redirect(reverse("passbook_core:auth-login"))
return redirect(reverse('passbook_core:auth-login'))
def consume_invitation(self): def consume_invitation(self):
"""Consume invitation if an invitation was used""" """Consume invitation if an invitation was used"""
@ -184,7 +185,8 @@ class SignUpView(UserPassesTestMixin, FormView):
sender=self, sender=self,
request=self.request, request=self.request,
invitation=self._invitation, invitation=self._invitation,
user=self._user) user=self._user,
)
self._invitation.delete() self._invitation.delete()
@staticmethod @staticmethod
@ -204,20 +206,17 @@ class SignUpView(UserPassesTestMixin, FormView):
""" """
# Create user # Create user
new_user = User.objects.create( new_user = User.objects.create(
username=data.get('username'), username=data.get("username"),
email=data.get('email'), email=data.get("email"),
name=data.get('name'), name=data.get("name"),
) )
new_user.is_active = True new_user.is_active = True
try: try:
new_user.set_password(data.get('password')) new_user.set_password(data.get("password"))
new_user.save() new_user.save()
request.user = new_user request.user = new_user
# Send signal for other auth sources # Send signal for other auth sources
user_signed_up.send( user_signed_up.send(sender=SignUpView, user=new_user, request=request)
sender=SignUpView,
user=new_user,
request=request)
return new_user return new_user
except PasswordPolicyInvalid as exc: except PasswordPolicyInvalid as exc:
new_user.delete() new_user.delete()
@ -233,11 +232,11 @@ class SignUpConfirmView(View):
nonce.user.is_active = True nonce.user.is_active = True
nonce.user.save() nonce.user.save()
# Workaround: hardcoded reference to ModelBackend, needs testing # Workaround: hardcoded reference to ModelBackend, needs testing
nonce.user.backend = 'django.contrib.auth.backends.ModelBackend' nonce.user.backend = "django.contrib.auth.backends.ModelBackend"
login(request, nonce.user) login(request, nonce.user)
nonce.delete() nonce.delete()
messages.success(request, _('Successfully confirmed registration.')) messages.success(request, _("Successfully confirmed registration."))
return redirect('passbook_core:overview') return redirect("passbook_core:overview")
class PasswordResetView(View): class PasswordResetView(View):
@ -248,9 +247,11 @@ class PasswordResetView(View):
# 3. (Optional) Trap user in password change view # 3. (Optional) Trap user in password change view
nonce = get_object_or_404(Nonce, uuid=nonce) nonce = get_object_or_404(Nonce, uuid=nonce)
# Workaround: hardcoded reference to ModelBackend, needs testing # Workaround: hardcoded reference to ModelBackend, needs testing
nonce.user.backend = 'django.contrib.auth.backends.ModelBackend' nonce.user.backend = "django.contrib.auth.backends.ModelBackend"
login(request, nonce.user) login(request, nonce.user)
nonce.delete() nonce.delete()
messages.success(request, _(('Temporarily authenticated with Nonce, ' messages.success(
'please change your password'))) request,
return redirect('passbook_core:user-change-password') _(("Temporarily authenticated with Nonce, " "please change your password")),
)
return redirect("passbook_core:user-change-password")

View File

@ -1,8 +1,11 @@
"""passbook core error views""" """passbook core error views"""
from django.http.response import (HttpResponseBadRequest, from django.http.response import (
HttpResponseForbidden, HttpResponseNotFound, HttpResponseBadRequest,
HttpResponseServerError) HttpResponseForbidden,
HttpResponseNotFound,
HttpResponseServerError,
)
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.views.generic import TemplateView from django.views.generic import TemplateView
@ -10,54 +13,53 @@ from django.views.generic import TemplateView
class BadRequestTemplateResponse(TemplateResponse, HttpResponseBadRequest): class BadRequestTemplateResponse(TemplateResponse, HttpResponseBadRequest):
"""Combine Template response with Http Code 400""" """Combine Template response with Http Code 400"""
class ForbiddenTemplateResponse(TemplateResponse, HttpResponseForbidden): class ForbiddenTemplateResponse(TemplateResponse, HttpResponseForbidden):
"""Combine Template response with Http Code 403""" """Combine Template response with Http Code 403"""
class NotFoundTemplateResponse(TemplateResponse, HttpResponseNotFound): class NotFoundTemplateResponse(TemplateResponse, HttpResponseNotFound):
"""Combine Template response with Http Code 404""" """Combine Template response with Http Code 404"""
class ServerErrorTemplateResponse(TemplateResponse, HttpResponseServerError): class ServerErrorTemplateResponse(TemplateResponse, HttpResponseServerError):
"""Combine Template response with Http Code 500""" """Combine Template response with Http Code 500"""
class BadRequestView(TemplateView): class BadRequestView(TemplateView):
"""Show Bad Request message""" """Show Bad Request message"""
response_class = BadRequestTemplateResponse response_class = BadRequestTemplateResponse
template_name = 'error/400.html' template_name = "error/400.html"
extra_context = {"is_login": True}
extra_context = {
'is_login': True
}
class ForbiddenView(TemplateView): class ForbiddenView(TemplateView):
"""Show Forbidden message""" """Show Forbidden message"""
response_class = ForbiddenTemplateResponse response_class = ForbiddenTemplateResponse
template_name = 'error/403.html' template_name = "error/403.html"
extra_context = {"is_login": True}
extra_context = {
'is_login': True
}
class NotFoundView(TemplateView): class NotFoundView(TemplateView):
"""Show Not Found message""" """Show Not Found message"""
response_class = NotFoundTemplateResponse response_class = NotFoundTemplateResponse
template_name = 'error/404.html' template_name = "error/404.html"
extra_context = {"is_login": True}
extra_context = {
'is_login': True
}
class ServerErrorView(TemplateView): class ServerErrorView(TemplateView):
"""Show Server Error message""" """Show Server Error message"""
response_class = ServerErrorTemplateResponse response_class = ServerErrorTemplateResponse
template_name = 'error/500.html' template_name = "error/500.html"
extra_context = { extra_context = {"is_login": True}
'is_login': True
}
# pylint: disable=useless-super-delegation # pylint: disable=useless-super-delegation
def dispatch(self, *args, **kwargs): def dispatch(self, *args, **kwargs):

View File

@ -11,13 +11,15 @@ class OverviewView(LoginRequiredMixin, TemplateView):
"""Overview for logged in user, incase user opens passbook directly """Overview for logged in user, incase user opens passbook directly
and is not being forwarded""" and is not being forwarded"""
template_name = 'overview/index.html' template_name = "overview/index.html"
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['applications'] = [] kwargs["applications"] = []
for application in Application.objects.all(): for application in Application.objects.all():
engine = PolicyEngine(application.policies.all(), self.request.user, self.request) engine = PolicyEngine(
application.policies.all(), self.request.user, self.request
)
engine.build() engine.build()
if engine.passing: if engine.passing:
kwargs['applications'].append(application) kwargs["applications"].append(application)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -17,11 +17,11 @@ from passbook.lib.config import CONFIG
class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView): class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
"""Update User settings""" """Update User settings"""
template_name = 'user/settings.html' template_name = "user/settings.html"
form_class = UserDetailForm form_class = UserDetailForm
success_message = _('Successfully updated user.') success_message = _("Successfully updated user.")
success_url = reverse_lazy('passbook_core:user-settings') success_url = reverse_lazy("passbook_core:user-settings")
def get_object(self): def get_object(self):
return self.request.user return self.request.user
@ -30,44 +30,44 @@ class UserSettingsView(SuccessMessageMixin, LoginRequiredMixin, UpdateView):
class UserDeleteView(LoginRequiredMixin, DeleteView): class UserDeleteView(LoginRequiredMixin, DeleteView):
"""Delete user account""" """Delete user account"""
template_name = 'generic/delete.html' template_name = "generic/delete.html"
def get_object(self): def get_object(self):
return self.request.user return self.request.user
def get_success_url(self): def get_success_url(self):
messages.success(self.request, _('Successfully deleted user.')) messages.success(self.request, _("Successfully deleted user."))
logout(self.request) logout(self.request)
return reverse('passbook_core:auth-login') return reverse("passbook_core:auth-login")
class UserChangePasswordView(LoginRequiredMixin, FormView): class UserChangePasswordView(LoginRequiredMixin, FormView):
"""View for users to update their password""" """View for users to update their password"""
form_class = PasswordChangeForm form_class = PasswordChangeForm
template_name = 'login/form_with_user.html' template_name = "login/form_with_user.html"
def form_valid(self, form: PasswordChangeForm): def form_valid(self, form: PasswordChangeForm):
try: try:
# user.set_password checks against Policies so we don't need to manually do it here # user.set_password checks against Policies so we don't need to manually do it here
self.request.user.set_password(form.cleaned_data.get('password')) self.request.user.set_password(form.cleaned_data.get("password"))
self.request.user.save() self.request.user.save()
update_session_auth_hash(self.request, self.request.user) update_session_auth_hash(self.request, self.request.user)
messages.success(self.request, _('Successfully changed password')) messages.success(self.request, _("Successfully changed password"))
except PasswordPolicyInvalid as exc: except PasswordPolicyInvalid as exc:
# Manually inject error into form # Manually inject error into form
# pylint: disable=protected-access # pylint: disable=protected-access
errors = form._errors.setdefault("password_repeat", ErrorList('')) errors = form._errors.setdefault("password_repeat", ErrorList(""))
# pylint: disable=protected-access # pylint: disable=protected-access
errors = form._errors.setdefault("password", ErrorList()) errors = form._errors.setdefault("password", ErrorList())
for error in exc.messages: for error in exc.messages:
errors.append(error) errors.append(error)
return self.form_invalid(form) return self.form_invalid(form)
return redirect('passbook_core:overview') return redirect("passbook_core:overview")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['config'] = CONFIG.y('passbook') kwargs["config"] = CONFIG.y("passbook")
kwargs['is_login'] = True kwargs["is_login"] = True
kwargs['title'] = _('Change Password') kwargs["title"] = _("Change Password")
kwargs['primary_action'] = _('Change') kwargs["primary_action"] = _("Change")
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -6,8 +6,8 @@ from django.views.generic import TemplateView
class LoadingView(TemplateView): class LoadingView(TemplateView):
"""View showing a loading template, and forwarding to real view using html forwarding.""" """View showing a loading template, and forwarding to real view using html forwarding."""
template_name = 'login/loading.html' template_name = "login/loading.html"
title = _('Loading') title = _("Loading")
target_url = None target_url = None
def get_url(self): def get_url(self):
@ -15,18 +15,19 @@ class LoadingView(TemplateView):
return self.target_url return self.target_url
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['is_login'] = True kwargs["is_login"] = True
kwargs['title'] = self.title kwargs["title"] = self.title
kwargs['target_url'] = self.get_url() kwargs["target_url"] = self.get_url()
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
class PermissionDeniedView(TemplateView): class PermissionDeniedView(TemplateView):
"""Generic Permission denied view""" """Generic Permission denied view"""
template_name = 'login/denied.html' template_name = "login/denied.html"
title = _('Permission denied.') title = _("Permission denied.")
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['is_login'] = True kwargs["is_login"] = True
kwargs['title'] = self.title kwargs["title"] = self.title
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -17,16 +17,16 @@ class AuthenticationFactor(TemplateView):
authenticator: AuthenticationView authenticator: AuthenticationView
pending_user: User pending_user: User
request: HttpRequest = None request: HttpRequest = None
template_name = 'login/form_with_user.html' template_name = "login/form_with_user.html"
def __init__(self, authenticator: AuthenticationView): def __init__(self, authenticator: AuthenticationView):
self.authenticator = authenticator self.authenticator = authenticator
self.pending_user = None self.pending_user = None
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['config'] = CONFIG.y('passbook') kwargs["config"] = CONFIG.y("passbook")
kwargs['is_login'] = True kwargs["is_login"] = True
kwargs['title'] = _('Log in to your account') kwargs["title"] = _("Log in to your account")
kwargs['primary_action'] = _('Log in') kwargs["primary_action"] = _("Log in")
kwargs['user'] = self.pending_user kwargs["user"] = self.pending_user
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)

View File

@ -2,4 +2,4 @@
from passbook.lib.admin import admin_autoregister from passbook.lib.admin import admin_autoregister
admin_autoregister('passbook_factors_captcha') admin_autoregister("passbook_factors_captcha")

View File

@ -11,7 +11,7 @@ class CaptchaFactorSerializer(ModelSerializer):
class Meta: class Meta:
model = CaptchaFactor model = CaptchaFactor
fields = ['pk', 'name', 'slug', 'order', 'enabled', 'public_key', 'private_key'] fields = ["pk", "name", "slug", "order", "enabled", "public_key", "private_key"]
class CaptchaFactorViewSet(ModelViewSet): class CaptchaFactorViewSet(ModelViewSet):

View File

@ -5,6 +5,6 @@ from django.apps import AppConfig
class PassbookFactorCaptchaConfig(AppConfig): class PassbookFactorCaptchaConfig(AppConfig):
"""passbook captcha app""" """passbook captcha app"""
name = 'passbook.factors.captcha' name = "passbook.factors.captcha"
label = 'passbook_factors_captcha' label = "passbook_factors_captcha"
verbose_name = 'passbook Factors.Captcha' verbose_name = "passbook Factors.Captcha"

View File

@ -16,7 +16,11 @@ class CaptchaFactor(FormView, AuthenticationFactor):
def get_form(self, form_class=None): def get_form(self, form_class=None):
form = CaptchaForm(**self.get_form_kwargs()) form = CaptchaForm(**self.get_form_kwargs())
form.fields['captcha'].public_key = self.authenticator.current_factor.public_key form.fields["captcha"].public_key = self.authenticator.current_factor.public_key
form.fields['captcha'].private_key = self.authenticator.current_factor.private_key form.fields[
form.fields['captcha'].widget.attrs["data-sitekey"] = form.fields['captcha'].public_key "captcha"
].private_key = self.authenticator.current_factor.private_key
form.fields["captcha"].widget.attrs["data-sitekey"] = form.fields[
"captcha"
].public_key
return form return form

View File

@ -13,17 +13,18 @@ class CaptchaForm(forms.Form):
captcha = ReCaptchaField() captcha = ReCaptchaField()
class CaptchaFactorForm(forms.ModelForm): class CaptchaFactorForm(forms.ModelForm):
"""Form to edit CaptchaFactor Instance""" """Form to edit CaptchaFactor Instance"""
class Meta: class Meta:
model = CaptchaFactor model = CaptchaFactor
fields = GENERAL_FIELDS + ['public_key', 'private_key'] fields = GENERAL_FIELDS + ["public_key", "private_key"]
widgets = { widgets = {
'name': forms.TextInput(), "name": forms.TextInput(),
'order': forms.NumberInput(), "order": forms.NumberInput(),
'policies': FilteredSelectMultiple(_('policies'), False), "policies": FilteredSelectMultiple(_("policies"), False),
'public_key': forms.TextInput(), "public_key": forms.TextInput(),
'private_key': forms.TextInput(), "private_key": forms.TextInput(),
} }

View File

@ -9,21 +9,31 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('passbook_core', '0001_initial'), ("passbook_core", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='CaptchaFactor', name="CaptchaFactor",
fields=[ fields=[
('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')), (
('public_key', models.TextField()), "factor_ptr",
('private_key', models.TextField()), models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_core.Factor",
),
),
("public_key", models.TextField()),
("private_key", models.TextField()),
], ],
options={ options={
'verbose_name': 'Captcha Factor', "verbose_name": "Captcha Factor",
'verbose_name_plural': 'Captcha Factors', "verbose_name_plural": "Captcha Factors",
}, },
bases=('passbook_core.factor',), bases=("passbook_core.factor",),
), ),
] ]

View File

@ -11,13 +11,13 @@ class CaptchaFactor(Factor):
public_key = models.TextField() public_key = models.TextField()
private_key = models.TextField() private_key = models.TextField()
type = 'passbook.factors.captcha.factor.CaptchaFactor' type = "passbook.factors.captcha.factor.CaptchaFactor"
form = 'passbook.factors.captcha.forms.CaptchaFactorForm' form = "passbook.factors.captcha.forms.CaptchaFactorForm"
def __str__(self): def __str__(self):
return f"Captcha Factor {self.slug}" return f"Captcha Factor {self.slug}"
class Meta: class Meta:
verbose_name = _('Captcha Factor') verbose_name = _("Captcha Factor")
verbose_name_plural = _('Captcha Factors') verbose_name_plural = _("Captcha Factors")

View File

@ -1,10 +1,8 @@
"""passbook captcha_factor settings""" """passbook captcha_factor settings"""
# https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do # https://developers.google.com/recaptcha/docs/faq#id-like-to-run-automated-tests-with-recaptcha.-what-should-i-do
RECAPTCHA_PUBLIC_KEY = '6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI' RECAPTCHA_PUBLIC_KEY = "6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
RECAPTCHA_PRIVATE_KEY = '6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe' RECAPTCHA_PRIVATE_KEY = "6LeIxAcTAAAAAGG-vFI1TnRWxMZNFuojJ4WifJWe"
NOCAPTCHA = True NOCAPTCHA = True
INSTALLED_APPS = [ INSTALLED_APPS = ["captcha"]
'captcha' SILENCED_SYSTEM_CHECKS = ["captcha.recaptcha_test_key_error"]
]
SILENCED_SYSTEM_CHECKS = ['captcha.recaptcha_test_key_error']

View File

@ -2,4 +2,4 @@
from passbook.lib.admin import admin_autoregister from passbook.lib.admin import admin_autoregister
admin_autoregister('passbook_factors_dummy') admin_autoregister("passbook_factors_dummy")

View File

@ -11,7 +11,7 @@ class DummyFactorSerializer(ModelSerializer):
class Meta: class Meta:
model = DummyFactor model = DummyFactor
fields = ['pk', 'name', 'slug', 'order', 'enabled'] fields = ["pk", "name", "slug", "order", "enabled"]
class DummyFactorViewSet(ModelViewSet): class DummyFactorViewSet(ModelViewSet):

View File

@ -6,6 +6,6 @@ from django.apps import AppConfig
class PassbookFactorDummyConfig(AppConfig): class PassbookFactorDummyConfig(AppConfig):
"""passbook dummy factor config""" """passbook dummy factor config"""
name = 'passbook.factors.dummy' name = "passbook.factors.dummy"
label = 'passbook_factors_dummy' label = "passbook_factors_dummy"
verbose_name = 'passbook Factors.Dummy' verbose_name = "passbook Factors.Dummy"

View File

@ -15,7 +15,7 @@ class DummyFactorForm(forms.ModelForm):
model = DummyFactor model = DummyFactor
fields = GENERAL_FIELDS fields = GENERAL_FIELDS
widgets = { widgets = {
'name': forms.TextInput(), "name": forms.TextInput(),
'order': forms.NumberInput(), "order": forms.NumberInput(),
'policies': FilteredSelectMultiple(_('policies'), False) "policies": FilteredSelectMultiple(_("policies"), False),
} }

View File

@ -9,19 +9,29 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('passbook_core', '0001_initial'), ("passbook_core", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='DummyFactor', name="DummyFactor",
fields=[ fields=[
('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')), (
"factor_ptr",
models.OneToOneField(
auto_created=True,
on_delete=django.db.models.deletion.CASCADE,
parent_link=True,
primary_key=True,
serialize=False,
to="passbook_core.Factor",
),
),
], ],
options={ options={
'verbose_name': 'Dummy Factor', "verbose_name": "Dummy Factor",
'verbose_name_plural': 'Dummy Factors', "verbose_name_plural": "Dummy Factors",
}, },
bases=('passbook_core.factor',), bases=("passbook_core.factor",),
), ),
] ]

View File

@ -7,13 +7,13 @@ from passbook.core.models import Factor
class DummyFactor(Factor): class DummyFactor(Factor):
"""Dummy factor, mostly used to debug""" """Dummy factor, mostly used to debug"""
type = 'passbook.factors.dummy.factor.DummyFactor' type = "passbook.factors.dummy.factor.DummyFactor"
form = 'passbook.factors.dummy.forms.DummyFactorForm' form = "passbook.factors.dummy.forms.DummyFactorForm"
def __str__(self): def __str__(self):
return f"Dummy Factor {self.slug}" return f"Dummy Factor {self.slug}"
class Meta: class Meta:
verbose_name = _('Dummy Factor') verbose_name = _("Dummy Factor")
verbose_name_plural = _('Dummy Factors') verbose_name_plural = _("Dummy Factors")

View File

@ -2,4 +2,4 @@
from passbook.lib.admin import admin_autoregister from passbook.lib.admin import admin_autoregister
admin_autoregister('passbook_factors_email') admin_autoregister("passbook_factors_email")

View File

@ -11,19 +11,24 @@ class EmailFactorSerializer(ModelSerializer):
class Meta: class Meta:
model = EmailFactor model = EmailFactor
fields = ['pk', 'name', 'slug', 'order', 'enabled', 'host', fields = [
'port', "pk",
'username', "name",
'password', "slug",
'use_tls', "order",
'use_ssl', "enabled",
'timeout', "host",
'from_address', "port",
'ssl_keyfile', "username",
'ssl_certfile', ] "password",
extra_kwargs = { "use_tls",
'password': {'write_only': True} "use_ssl",
} "timeout",
"from_address",
"ssl_keyfile",
"ssl_certfile",
]
extra_kwargs = {"password": {"write_only": True}}
class EmailFactorViewSet(ModelViewSet): class EmailFactorViewSet(ModelViewSet):

View File

@ -7,9 +7,9 @@ from django.apps import AppConfig
class PassbookFactorEmailConfig(AppConfig): class PassbookFactorEmailConfig(AppConfig):
"""passbook email factor config""" """passbook email factor config"""
name = 'passbook.factors.email' name = "passbook.factors.email"
label = 'passbook_factors_email' label = "passbook_factors_email"
verbose_name = 'passbook Factors.Email' verbose_name = "passbook Factors.Email"
def ready(self): def ready(self):
import_module('passbook.factors.email.tasks') import_module("passbook.factors.email.tasks")

View File

@ -18,27 +18,31 @@ class EmailFactorView(AuthenticationFactor):
"""Dummy factor for testing with multiple factors""" """Dummy factor for testing with multiple factors"""
def get_context_data(self, **kwargs): def get_context_data(self, **kwargs):
kwargs['show_password_forget_notice'] = CONFIG.y('passbook.password_reset.enabled') kwargs["show_password_forget_notice"] = CONFIG.y(
"passbook.password_reset.enabled"
)
return super().get_context_data(**kwargs) return super().get_context_data(**kwargs)
def get(self, request, *args, **kwargs): def get(self, request, *args, **kwargs):
nonce = Nonce.objects.create(user=self.pending_user) nonce = Nonce.objects.create(user=self.pending_user)
# Send mail to user # Send mail to user
message = TemplateEmailMessage( message = TemplateEmailMessage(
subject=_('Forgotten password'), subject=_("Forgotten password"),
template_name='email/account_password_reset.html', template_name="email/account_password_reset.html",
to=[self.pending_user.email], to=[self.pending_user.email],
template_context={ template_context={
'url': self.request.build_absolute_uri( "url": self.request.build_absolute_uri(
reverse('passbook_core:auth-password-reset', reverse(
kwargs={ "passbook_core:auth-password-reset",
'nonce': nonce.uuid kwargs={"nonce": nonce.uuid},
}) )
)}) )
},
)
send_mails(self.authenticator.current_factor, message) send_mails(self.authenticator.current_factor, message)
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."))
return redirect('passbook_core:auth-login') return redirect("passbook_core:auth-login")
def post(self, request: HttpRequest): def post(self, request: HttpRequest):
"""Just redirect to next factor""" """Just redirect to next factor"""

View File

@ -14,30 +14,30 @@ class EmailFactorForm(forms.ModelForm):
model = EmailFactor model = EmailFactor
fields = GENERAL_FIELDS + [ fields = GENERAL_FIELDS + [
'host', "host",
'port', "port",
'username', "username",
'password', "password",
'use_tls', "use_tls",
'use_ssl', "use_ssl",
'timeout', "timeout",
'from_address', "from_address",
'ssl_keyfile', "ssl_keyfile",
'ssl_certfile', "ssl_certfile",
] ]
widgets = { widgets = {
'name': forms.TextInput(), "name": forms.TextInput(),
'order': forms.NumberInput(), "order": forms.NumberInput(),
'policies': FilteredSelectMultiple(_('policies'), False), "policies": FilteredSelectMultiple(_("policies"), False),
'host': forms.TextInput(), "host": forms.TextInput(),
'username': forms.TextInput(), "username": forms.TextInput(),
'password': forms.TextInput(), "password": forms.TextInput(),
'ssl_keyfile': forms.TextInput(), "ssl_keyfile": forms.TextInput(),
'ssl_certfile': forms.TextInput(), "ssl_certfile": forms.TextInput(),
} }
labels = { labels = {
'use_tls': _('Use TLS'), "use_tls": _("Use TLS"),
'use_ssl': _('Use SSL'), "use_ssl": _("Use SSL"),
'ssl_keyfile': _('SSL Keyfile (optional)'), "ssl_keyfile": _("SSL Keyfile (optional)"),
'ssl_certfile': _('SSL Certfile (optional)'), "ssl_certfile": _("SSL Certfile (optional)"),
} }

View File

@ -9,29 +9,42 @@ class Migration(migrations.Migration):
initial = True initial = True
dependencies = [ dependencies = [
('passbook_core', '0001_initial'), ("passbook_core", "0001_initial"),
] ]
operations = [ operations = [
migrations.CreateModel( migrations.CreateModel(
name='EmailFactor', name="EmailFactor",
fields=[ fields=[
('factor_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='passbook_core.Factor')), (
('host', models.TextField(default='localhost')), "factor_ptr",
('port', models.IntegerField(default=25)), models.OneToOneField(
('username', models.TextField(blank=True, default='')), auto_created=True,
('password', models.TextField(blank=True, default='')), on_delete=django.db.models.deletion.CASCADE,
('use_tls', models.BooleanField(default=False)), parent_link=True,
('use_ssl', models.BooleanField(default=False)), primary_key=True,
('timeout', models.IntegerField(default=0)), serialize=False,
('ssl_keyfile', models.TextField(blank=True, default=None, null=True)), to="passbook_core.Factor",
('ssl_certfile', models.TextField(blank=True, default=None, null=True)), ),
('from_address', models.EmailField(default='system@passbook.local', max_length=254)), ),
("host", models.TextField(default="localhost")),
("port", models.IntegerField(default=25)),
("username", models.TextField(blank=True, default="")),
("password", models.TextField(blank=True, default="")),
("use_tls", models.BooleanField(default=False)),
("use_ssl", models.BooleanField(default=False)),
("timeout", models.IntegerField(default=0)),
("ssl_keyfile", models.TextField(blank=True, default=None, null=True)),
("ssl_certfile", models.TextField(blank=True, default=None, null=True)),
(
"from_address",
models.EmailField(default="system@passbook.local", max_length=254),
),
], ],
options={ options={
'verbose_name': 'Email Factor', "verbose_name": "Email Factor",
'verbose_name_plural': 'Email Factors', "verbose_name_plural": "Email Factors",
}, },
bases=('passbook_core.factor',), bases=("passbook_core.factor",),
), ),
] ]

Some files were not shown because too many files have changed in this diff Show More