all: implement black as code formatter
This commit is contained in:
parent
8eb3f0f708
commit
3bd1eadd51
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
|
@ -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:
|
||||||
|
|
5
Pipfile
5
Pipfile
|
@ -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
126
Pipfile.lock
generated
|
@ -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",
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
"""passbook"""
|
"""passbook"""
|
||||||
__version__ = '0.7.5-beta'
|
__version__ = "0.7.5-beta"
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
"""passbook admin settings"""
|
"""passbook admin settings"""
|
||||||
|
|
||||||
MIDDLEWARE = [
|
MIDDLEWARE = [
|
||||||
'passbook.admin.middleware.impersonate',
|
"passbook.admin.middleware.impersonate",
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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"],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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)),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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')
|
|
||||||
]
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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",
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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',
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"),
|
||||||
|
]
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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 = ""
|
||||||
|
|
|
@ -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"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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")}
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"),)},
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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=""),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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
|
||||||
|
),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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 = []
|
||||||
]
|
|
||||||
|
|
|
@ -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',
|
|
||||||
),
|
|
||||||
]
|
]
|
||||||
|
|
|
@ -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 = []
|
||||||
]
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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
|
||||||
|
)
|
||||||
|
|
|
@ -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"))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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(),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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']
|
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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",),
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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):
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -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"""
|
||||||
|
|
|
@ -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)"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
Reference in a new issue