Compare commits
64 Commits
version/20
...
version/20
Author | SHA1 | Date | |
---|---|---|---|
941bc61b31 | |||
282b364606 | |||
ad4bc4083d | |||
ebe282eb1a | |||
830c26ca25 | |||
ed3b4a3d4a | |||
975c4ddc04 | |||
7e2896298a | |||
cba9cf8361 | |||
bf12580f64 | |||
75ef4ce596 | |||
c2f3ce11b0 | |||
3c256fecc6 | |||
0285b84133 | |||
99a371a02c | |||
c7e6eb8896 | |||
674bd9e05c | |||
b79901df87 | |||
b248f450dd | |||
05db9e5c40 | |||
234a5e2b66 | |||
aea1736f70 | |||
9f4a4449f5 | |||
b6b55e2336 | |||
8f2805e05b | |||
4f3583cd7e | |||
617e90dca3 | |||
f7408626a8 | |||
4dcb15af46 | |||
89beb7a9f7 | |||
28eeb4798e | |||
79b92e764e | |||
919336a519 | |||
27e04589c1 | |||
ba44fbdac8 | |||
0e093a8917 | |||
d0bfb99859 | |||
93bdea3769 | |||
e681654af7 | |||
cab7593dca | |||
cf92f9aefc | |||
8d72b3498d | |||
42ab858c50 | |||
a1abae9ab1 | |||
8f36b49061 | |||
64b4e851ce | |||
40a62ac1e5 | |||
5df60e4d87 | |||
50ebc8522d | |||
eddca478dc | |||
99a7fca08e | |||
a7e3602908 | |||
74169860cf | |||
52bb774f73 | |||
f26fcaf825 | |||
b8e92e2f11 | |||
08adfc94d6 | |||
236fafb735 | |||
5ad9ddee3c | |||
24d220ff49 | |||
3364c195b7 | |||
50aa87d141 | |||
72b375023d | |||
77ba186818 |
@ -1,5 +1,5 @@
|
||||
[bumpversion]
|
||||
current_version = 2021.9.1
|
||||
current_version = 2021.9.3
|
||||
tag = True
|
||||
commit = True
|
||||
parse = (?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)\-?(?P<release>.*)
|
||||
|
1
.github/codespell-words.txt
vendored
Normal file
1
.github/codespell-words.txt
vendored
Normal file
@ -0,0 +1 @@
|
||||
keypair
|
20
.github/workflows/release-publish.yml
vendored
20
.github/workflows/release-publish.yml
vendored
@ -33,14 +33,14 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik:2021.9.1,
|
||||
beryju/authentik:2021.9.3,
|
||||
beryju/authentik:latest,
|
||||
ghcr.io/goauthentik/server:2021.9.1,
|
||||
ghcr.io/goauthentik/server:2021.9.3,
|
||||
ghcr.io/goauthentik/server:latest
|
||||
platforms: linux/amd64,linux/arm64
|
||||
context: .
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.1', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.3', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik:latest
|
||||
docker tag beryju/authentik:latest beryju/authentik:stable
|
||||
@ -75,14 +75,14 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik-proxy:2021.9.1,
|
||||
beryju/authentik-proxy:2021.9.3,
|
||||
beryju/authentik-proxy:latest,
|
||||
ghcr.io/goauthentik/proxy:2021.9.1,
|
||||
ghcr.io/goauthentik/proxy:2021.9.3,
|
||||
ghcr.io/goauthentik/proxy:latest
|
||||
file: proxy.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.1', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.3', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik-proxy:latest
|
||||
docker tag beryju/authentik-proxy:latest beryju/authentik-proxy:stable
|
||||
@ -117,14 +117,14 @@ jobs:
|
||||
with:
|
||||
push: ${{ github.event_name == 'release' }}
|
||||
tags: |
|
||||
beryju/authentik-ldap:2021.9.1,
|
||||
beryju/authentik-ldap:2021.9.3,
|
||||
beryju/authentik-ldap:latest,
|
||||
ghcr.io/goauthentik/ldap:2021.9.1,
|
||||
ghcr.io/goauthentik/ldap:2021.9.3,
|
||||
ghcr.io/goauthentik/ldap:latest
|
||||
file: ldap.Dockerfile
|
||||
platforms: linux/amd64,linux/arm64
|
||||
- name: Building Docker Image (stable)
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.1', 'rc') }}
|
||||
if: ${{ github.event_name == 'release' && !contains('2021.9.3', 'rc') }}
|
||||
run: |
|
||||
docker pull beryju/authentik-ldap:latest
|
||||
docker tag beryju/authentik-ldap:latest beryju/authentik-ldap:stable
|
||||
@ -175,7 +175,7 @@ jobs:
|
||||
SENTRY_PROJECT: authentik
|
||||
SENTRY_URL: https://sentry.beryju.org
|
||||
with:
|
||||
version: authentik@2021.9.1
|
||||
version: authentik@2021.9.3
|
||||
environment: beryjuorg-prod
|
||||
sourcemaps: './web/dist'
|
||||
url_prefix: '~/static/dist'
|
||||
|
2
.github/workflows/release-tag.yml
vendored
2
.github/workflows/release-tag.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
||||
docker-compose run -u root server test
|
||||
- name: Extract version number
|
||||
id: get_version
|
||||
uses: actions/github-script@v4.1
|
||||
uses: actions/github-script@v5
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
|
5
Makefile
5
Makefile
@ -20,6 +20,11 @@ test:
|
||||
lint-fix:
|
||||
isort authentik tests lifecycle
|
||||
black authentik tests lifecycle
|
||||
codespell -I .github/codespell-words.txt -w authentik
|
||||
codespell -I .github/codespell-words.txt -w internal
|
||||
codespell -I .github/codespell-words.txt -w cmd
|
||||
codespell -I .github/codespell-words.txt -w web/src
|
||||
codespell -I .github/codespell-words.txt -w website/src
|
||||
|
||||
lint:
|
||||
pyright authentik tests lifecycle
|
||||
|
1
Pipfile
1
Pipfile
@ -48,6 +48,7 @@ duo-client = "*"
|
||||
ua-parser = "*"
|
||||
deepmerge = "*"
|
||||
colorama = "*"
|
||||
codespell = "*"
|
||||
|
||||
[dev-packages]
|
||||
bandit = "*"
|
||||
|
210
Pipfile.lock
generated
210
Pipfile.lock
generated
@ -1,7 +1,7 @@
|
||||
{
|
||||
"_meta": {
|
||||
"hash": {
|
||||
"sha256": "19d5324fd1a4af125ed57a683030ca14ee2d3648117748e4b32656875484728e"
|
||||
"sha256": "babb6061c555f8f239f00210b2a0356763bdaaca2f3d704cf3444891b84db84d"
|
||||
},
|
||||
"pipfile-spec": 6,
|
||||
"requires": {},
|
||||
@ -120,19 +120,19 @@
|
||||
},
|
||||
"boto3": {
|
||||
"hashes": [
|
||||
"sha256:68ee81a7ef40380a5ab973e242bbf8739d56a49f8691508c48760fb5066933e3",
|
||||
"sha256:c2fd29e53464e4ab79c224363c20a02af19f7ecc8baf37f7886a893fc672272a"
|
||||
"sha256:7b45b224442c479de4bc6e6e9cb0557b642fc7a77edc8702e393ccaa2e0aa128",
|
||||
"sha256:c388da7dc1a596755f39de990a72e05cee558d098e81de63de55bd9598cc5134"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.18.45"
|
||||
"version": "==1.18.48"
|
||||
},
|
||||
"botocore": {
|
||||
"hashes": [
|
||||
"sha256:1d31e461dfc9ddb9a86fdd47ebc61751adc8739b4f7160c687c04092e5fbe0aa",
|
||||
"sha256:b3a77dcc7d54a3725aa0a9b44e4a79142a013584cd0568750a9ee9ab6970538f"
|
||||
"sha256:2c25a76f09223b2f00ad578df34492b7b84cd4828fc90c08ccbdd1d70abbd7eb",
|
||||
"sha256:9d5b70be2f417d0aa30788049fd20473ad27218eccd05e71f545b4b4e09c79a0"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==1.21.45"
|
||||
"version": "==1.21.48"
|
||||
},
|
||||
"cachetools": {
|
||||
"hashes": [
|
||||
@ -286,6 +286,14 @@
|
||||
],
|
||||
"version": "==0.2.0"
|
||||
},
|
||||
"codespell": {
|
||||
"hashes": [
|
||||
"sha256:19d3fe5644fef3425777e66f225a8c82d39059dcfe9edb3349a8a2cf48383ee5",
|
||||
"sha256:b864c7d917316316ac24272ee992d7937c3519be4569209c5b60035ac5d569b5"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.1.0"
|
||||
},
|
||||
"colorama": {
|
||||
"hashes": [
|
||||
"sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b",
|
||||
@ -371,11 +379,11 @@
|
||||
},
|
||||
"django-filter": {
|
||||
"hashes": [
|
||||
"sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06",
|
||||
"sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1"
|
||||
"sha256:632a251fa8f1aadb4b8cceff932bb52fe2f826dd7dfe7f3eac40e5c463d6836e",
|
||||
"sha256:f4a6737a30104c98d2e2a5fb93043f36dd7978e0c7ddc92f5998e85433ea5063"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==2.4.0"
|
||||
"version": "==21.1"
|
||||
},
|
||||
"django-guardian": {
|
||||
"hashes": [
|
||||
@ -482,11 +490,11 @@
|
||||
},
|
||||
"geoip2": {
|
||||
"hashes": [
|
||||
"sha256:599914784cea08b50fb50c22ed6a59143b5ff2d027ba782d2d5b6f3668293821",
|
||||
"sha256:7ad10942e9fd6c54bc850d8311618f04dacd3fff5b55ba48b7ad83502fc884bd"
|
||||
"sha256:f150bed3190d543712a17467208388d31bd8ddb49b2226fba53db8aaedb8ba89",
|
||||
"sha256:f9172cdfb2a5f9225ace5e30dd7426413ad28798a5f474cd1538780686bd6a87"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==4.3.0"
|
||||
"version": "==4.4.0"
|
||||
},
|
||||
"google-auth": {
|
||||
"hashes": [
|
||||
@ -706,10 +714,10 @@
|
||||
},
|
||||
"maxminddb": {
|
||||
"hashes": [
|
||||
"sha256:c47b8acba98d03b8c762684d899623c257976f3eb0c9d557ff865d20cddc9d6b"
|
||||
"sha256:e37707ec4fab115804670e0fb7aedb4b57075a8b6f80052bdc648d3c005184e5"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.1.0"
|
||||
"version": "==2.2.0"
|
||||
},
|
||||
"msgpack": {
|
||||
"hashes": [
|
||||
@ -900,39 +908,39 @@
|
||||
},
|
||||
"pycryptodome": {
|
||||
"hashes": [
|
||||
"sha256:09c1555a3fa450e7eaca41ea11cd00afe7c91fef52353488e65663777d8524e0",
|
||||
"sha256:12222a5edc9ca4a29de15fbd5339099c4c26c56e13c2ceddf0b920794f26165d",
|
||||
"sha256:1723ebee5561628ce96748501cdaa7afaa67329d753933296321f0be55358dce",
|
||||
"sha256:1c5e1ca507de2ad93474be5cfe2bfa76b7cf039a1a32fc196f40935944871a06",
|
||||
"sha256:2603c98ae04aac675fefcf71a6c87dc4bb74a75e9071ae3923bbc91a59f08d35",
|
||||
"sha256:2dea65df54349cdfa43d6b2e8edb83f5f8d6861e5cf7b1fbc3e34c5694c85e27",
|
||||
"sha256:31c1df17b3dc5f39600a4057d7db53ac372f492c955b9b75dd439f5d8b460129",
|
||||
"sha256:38661348ecb71476037f1e1f553159b80d256c00f6c0b00502acac891f7116d9",
|
||||
"sha256:3e2e3a06580c5f190df843cdb90ea28d61099cf4924334d5297a995de68e4673",
|
||||
"sha256:3f840c49d38986f6e17dbc0673d37947c88bc9d2d9dba1c01b979b36f8447db1",
|
||||
"sha256:501ab36aae360e31d0ec370cf5ce8ace6cb4112060d099b993bc02b36ac83fb6",
|
||||
"sha256:60386d1d4cfaad299803b45a5bc2089696eaf6cdd56f9fc17479a6f89595cfc8",
|
||||
"sha256:6260e24d41149268122dd39d4ebd5941e9d107f49463f7e071fd397e29923b0c",
|
||||
"sha256:6bbf7fee7b7948b29d7e71fcacf48bac0c57fb41332007061a933f2d996f9713",
|
||||
"sha256:6d2df5223b12437e644ce0a3be7809471ffa71de44ccd28b02180401982594a6",
|
||||
"sha256:758949ca62690b1540dfb24ad773c6da9cd0e425189e83e39c038bbd52b8e438",
|
||||
"sha256:77997519d8eb8a4adcd9a47b9cec18f9b323e296986528186c0e9a7a15d6a07e",
|
||||
"sha256:7fd519b89585abf57bf47d90166903ec7b43af4fe23c92273ea09e6336af5c07",
|
||||
"sha256:98213ac2b18dc1969a47bc65a79a8fca02a414249d0c8635abb081c7f38c91b6",
|
||||
"sha256:99b2f3fc51d308286071d0953f92055504a6ffe829a832a9fc7a04318a7683dd",
|
||||
"sha256:9b6f711b25e01931f1c61ce0115245a23cdc8b80bf8539ac0363bdcf27d649b6",
|
||||
"sha256:a3105a0eb63eacf98c2ecb0eb4aa03f77f40fbac2bdde22020bb8a536b226bb8",
|
||||
"sha256:a8eb8b6ea09ec1c2535bf39914377bc8abcab2c7d30fa9225eb4fe412024e427",
|
||||
"sha256:a92d5c414e8ee1249e850789052608f582416e82422502dc0ac8c577808a9067",
|
||||
"sha256:d3d6958d53ad307df5e8469cc44474a75393a434addf20ecd451f38a72fe29b8",
|
||||
"sha256:e0a4d5933a88a2c98bbe19c0c722f5483dc628d7a38338ac2cb64a7dbd34064b",
|
||||
"sha256:e3bf558c6aeb49afa9f0c06cee7fb5947ee5a1ff3bd794b653d39926b49077fa",
|
||||
"sha256:e61e363d9a5d7916f3a4ce984a929514c0df3daf3b1b2eb5e6edbb131ee771cf",
|
||||
"sha256:f977cdf725b20f6b8229b0c87acb98c7717e742ef9f46b113985303ae12a99da",
|
||||
"sha256:fc7489a50323a0df02378bc2fff86eb69d94cc5639914346c736be981c6a02e7"
|
||||
"sha256:04e14c732c3693d2830839feed5129286ce47ffa8bfe90e4ae042c773e51c677",
|
||||
"sha256:11d3164fb49fdee000fde05baecce103c0c698168ef1a18d9c7429dd66f0f5bb",
|
||||
"sha256:217dcc0c92503f7dd4b3d3b7d974331a4419f97f555c99a845c3b366fed7056b",
|
||||
"sha256:24c1b7705d19d8ae3e7255431efd2e526006855df62620118dd7b5374c6372f6",
|
||||
"sha256:309529d2526f3fb47102aeef376b3459110a6af7efb162e860b32e3a17a46f06",
|
||||
"sha256:3a153658d97258ca20bf18f7fe31c09cc7c558b6f8974a6ec74e19f6c634bd64",
|
||||
"sha256:3f9fb499e267039262569d08658132c9cd8b136bf1d8c56b72f70ed05551e526",
|
||||
"sha256:3faa6ebd35c61718f3f8862569c1f38450c24f3ededb213e1a64806f02f584bc",
|
||||
"sha256:40083b0d7f277452c7f2dd4841801f058cc12a74c219ee4110d65774c6a58bef",
|
||||
"sha256:49e54f2245befb0193848c8c8031d8d1358ed4af5a1ae8d0a3ba669a5cdd3a72",
|
||||
"sha256:4e8fc4c48365ce8a542fe48bf1360da05bb2851df12f64fc94d751705e7cdbe7",
|
||||
"sha256:54d4e4d45f349d8c4e2f31c2734637ff62a844af391b833f789da88e43a8f338",
|
||||
"sha256:66301e4c42dee43ee2da256625d3fe81ef98cc9924c2bd535008cc3ad8ded77b",
|
||||
"sha256:6b45fcace5a5d9c57ba87cf804b161adc62aa826295ce7f7acbcbdc0df74ed37",
|
||||
"sha256:7efec2418e9746ec48e264eea431f8e422d931f71c57b1c96ee202b117f58fa9",
|
||||
"sha256:851e6d4930b160417235955322db44adbdb19589918670d63f4acd5d92959ac0",
|
||||
"sha256:8e82524e7c354033508891405574d12e612cc4fdd3b55d2c238fc1a3e300b606",
|
||||
"sha256:8ec154ec445412df31acf0096e7f715e30e167c8f2318b8f5b1ab7c28f4c82f7",
|
||||
"sha256:91ba4215a1f37d0f371fe43bc88c5ff49c274849f3868321c889313787de7672",
|
||||
"sha256:97e7df67a4da2e3f60612bbfd6c3f243a63a15d8f4797dd275e1d7b44a65cb12",
|
||||
"sha256:9a2312440057bf29b9582f72f14d79692044e63bfbc4b4bbea8559355f44f3dd",
|
||||
"sha256:a7471646d8cd1a58bb696d667dcb3853e5c9b341b68dcf3c3cc0893d0f98ca5f",
|
||||
"sha256:ac3012c36633564b2b5539bb7c6d9175f31d2ce74844e9abe654c428f02d0fd8",
|
||||
"sha256:b1daf251395af7336ddde6a0015ba5e632c18fe646ba930ef87402537358e3b4",
|
||||
"sha256:b217b4525e60e1af552d62bec01b4685095436d4de5ecde0f05d75b2f95ba6d4",
|
||||
"sha256:c61ea053bd5d4c12a063d7e704fbe1c45abb5d2510dab55bd95d166ba661604f",
|
||||
"sha256:c6469d1453f5864e3321a172b0aa671b938d753cbf2376b99fa2ab8841539bb8",
|
||||
"sha256:cefe6b267b8e5c3c72e11adec35a9c7285b62e8ea141b63e87055e9a9e5f2f8c",
|
||||
"sha256:d713dc0910e5ded07852a05e9b75f1dd9d3a31895eebee0668f612779b2a748c",
|
||||
"sha256:db15fa07d2a4c00beeb5e9acdfdbc1c79f9ccfbdc1a8f36c82c4aa44951b33c9"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==3.10.1"
|
||||
"version": "==3.10.4"
|
||||
},
|
||||
"pyjwt": {
|
||||
"hashes": [
|
||||
@ -1084,11 +1092,11 @@
|
||||
},
|
||||
"sentry-sdk": {
|
||||
"hashes": [
|
||||
"sha256:494bf9990a54400710b7b6eb7d8174fc89039a4c82feedcf498d800d54e2ee81",
|
||||
"sha256:963326914aa219ad5abda5b479b871fed2b3d2ddc9d27037340637647c9a4cdb"
|
||||
"sha256:4297555ddc37c7136740e6b547b7d68f5bca0b4832f94ac097e5d531a4c77528",
|
||||
"sha256:ea04bc3be6eb082f34ff3f8f6380ea9c691766592298f3f975a435dafac6bf6a"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.4.0"
|
||||
"version": "==1.4.1"
|
||||
},
|
||||
"service-identity": {
|
||||
"hashes": [
|
||||
@ -1178,11 +1186,11 @@
|
||||
"secure"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
|
||||
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
|
||||
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
|
||||
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.26.6"
|
||||
"version": "==1.26.7"
|
||||
},
|
||||
"uvicorn": {
|
||||
"extras": [
|
||||
@ -1574,7 +1582,7 @@
|
||||
"sha256:9c2ea1e62d871267b78307fe511c0838ba0da28698c5732d54e2790bf3ba9899",
|
||||
"sha256:e17d6e2b81095c9db0a03a8025a957f334d6ea30b26f9ec70805411e5c7c81f2"
|
||||
],
|
||||
"markers": "python_version < '4' and python_full_version >= '3.6.1'",
|
||||
"markers": "python_version < '4.0' and python_full_version >= '3.6.1'",
|
||||
"version": "==5.9.3"
|
||||
},
|
||||
"lazy-object-proxy": {
|
||||
@ -1644,11 +1652,11 @@
|
||||
},
|
||||
"platformdirs": {
|
||||
"hashes": [
|
||||
"sha256:15b056538719b1c94bdaccb29e5f81879c7f7f0f4a153f46086d155dffcd4f0f",
|
||||
"sha256:8003ac87717ae2c7ee1ea5a84a1a61e87f3fbd16eb5aadba194ea30a9019f648"
|
||||
"sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2",
|
||||
"sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d"
|
||||
],
|
||||
"markers": "python_version >= '3.6'",
|
||||
"version": "==2.3.0"
|
||||
"version": "==2.4.0"
|
||||
},
|
||||
"pluggy": {
|
||||
"hashes": [
|
||||
@ -1750,49 +1758,49 @@
|
||||
},
|
||||
"regex": {
|
||||
"hashes": [
|
||||
"sha256:04f6b9749e335bb0d2f68c707f23bb1773c3fb6ecd10edf0f04df12a8920d468",
|
||||
"sha256:08d74bfaa4c7731b8dac0a992c63673a2782758f7cfad34cf9c1b9184f911354",
|
||||
"sha256:0fc1f8f06977c2d4f5e3d3f0d4a08089be783973fc6b6e278bde01f0544ff308",
|
||||
"sha256:121f4b3185feaade3f85f70294aef3f777199e9b5c0c0245c774ae884b110a2d",
|
||||
"sha256:1413b5022ed6ac0d504ba425ef02549a57d0f4276de58e3ab7e82437892704fc",
|
||||
"sha256:1743345e30917e8c574f273f51679c294effba6ad372db1967852f12c76759d8",
|
||||
"sha256:28fc475f560d8f67cc8767b94db4c9440210f6958495aeae70fac8faec631797",
|
||||
"sha256:31a99a4796bf5aefc8351e98507b09e1b09115574f7c9dbb9cf2111f7220d2e2",
|
||||
"sha256:328a1fad67445550b982caa2a2a850da5989fd6595e858f02d04636e7f8b0b13",
|
||||
"sha256:473858730ef6d6ff7f7d5f19452184cd0caa062a20047f6d6f3e135a4648865d",
|
||||
"sha256:4cde065ab33bcaab774d84096fae266d9301d1a2f5519d7bd58fc55274afbf7a",
|
||||
"sha256:5f6a808044faae658f546dd5f525e921de9fa409de7a5570865467f03a626fc0",
|
||||
"sha256:610b690b406653c84b7cb6091facb3033500ee81089867ee7d59e675f9ca2b73",
|
||||
"sha256:66256b6391c057305e5ae9209941ef63c33a476b73772ca967d4a2df70520ec1",
|
||||
"sha256:6eebf512aa90751d5ef6a7c2ac9d60113f32e86e5687326a50d7686e309f66ed",
|
||||
"sha256:79aef6b5cd41feff359acaf98e040844613ff5298d0d19c455b3d9ae0bc8c35a",
|
||||
"sha256:808ee5834e06f57978da3e003ad9d6292de69d2bf6263662a1a8ae30788e080b",
|
||||
"sha256:8e44769068d33e0ea6ccdf4b84d80c5afffe5207aa4d1881a629cf0ef3ec398f",
|
||||
"sha256:999ad08220467b6ad4bd3dd34e65329dd5d0df9b31e47106105e407954965256",
|
||||
"sha256:9b006628fe43aa69259ec04ca258d88ed19b64791693df59c422b607b6ece8bb",
|
||||
"sha256:9d05ad5367c90814099000442b2125535e9d77581855b9bee8780f1b41f2b1a2",
|
||||
"sha256:a577a21de2ef8059b58f79ff76a4da81c45a75fe0bfb09bc8b7bb4293fa18983",
|
||||
"sha256:a617593aeacc7a691cc4af4a4410031654f2909053bd8c8e7db837f179a630eb",
|
||||
"sha256:abb48494d88e8a82601af905143e0de838c776c1241d92021e9256d5515b3645",
|
||||
"sha256:ac88856a8cbccfc14f1b2d0b829af354cc1743cb375e7f04251ae73b2af6adf8",
|
||||
"sha256:b4c220a1fe0d2c622493b0a1fd48f8f991998fb447d3cd368033a4b86cf1127a",
|
||||
"sha256:b844fb09bd9936ed158ff9df0ab601e2045b316b17aa8b931857365ea8586906",
|
||||
"sha256:bdc178caebd0f338d57ae445ef8e9b737ddf8fbc3ea187603f65aec5b041248f",
|
||||
"sha256:c206587c83e795d417ed3adc8453a791f6d36b67c81416676cad053b4104152c",
|
||||
"sha256:c61dcc1cf9fd165127a2853e2c31eb4fb961a4f26b394ac9fe5669c7a6592892",
|
||||
"sha256:c7cb4c512d2d3b0870e00fbbac2f291d4b4bf2634d59a31176a87afe2777c6f0",
|
||||
"sha256:d4a332404baa6665b54e5d283b4262f41f2103c255897084ec8f5487ce7b9e8e",
|
||||
"sha256:d5111d4c843d80202e62b4fdbb4920db1dcee4f9366d6b03294f45ed7b18b42e",
|
||||
"sha256:e1e8406b895aba6caa63d9fd1b6b1700d7e4825f78ccb1e5260551d168db38ed",
|
||||
"sha256:e8690ed94481f219a7a967c118abaf71ccc440f69acd583cab721b90eeedb77c",
|
||||
"sha256:ed283ab3a01d8b53de3a05bfdf4473ae24e43caee7dcb5584e86f3f3e5ab4374",
|
||||
"sha256:ed4b50355b066796dacdd1cf538f2ce57275d001838f9b132fab80b75e8c84dd",
|
||||
"sha256:ee329d0387b5b41a5dddbb6243a21cb7896587a651bebb957e2d2bb8b63c0791",
|
||||
"sha256:f3bf1bc02bc421047bfec3343729c4bbbea42605bcfd6d6bfe2c07ade8b12d2a",
|
||||
"sha256:f585cbbeecb35f35609edccb95efd95a3e35824cd7752b586503f7e6087303f1",
|
||||
"sha256:f60667673ff9c249709160529ab39667d1ae9fd38634e006bec95611f632e759"
|
||||
"sha256:0628ed7d6334e8f896f882a5c1240de8c4d9b0dd7c7fb8e9f4692f5684b7d656",
|
||||
"sha256:09eb62654030f39f3ba46bc6726bea464069c29d00a9709e28c9ee9623a8da4a",
|
||||
"sha256:0bba1f6df4eafe79db2ecf38835c2626dbd47911e0516f6962c806f83e7a99ae",
|
||||
"sha256:10a7a9cbe30bd90b7d9a1b4749ef20e13a3528e4215a2852be35784b6bd070f0",
|
||||
"sha256:17310b181902e0bb42b29c700e2c2346b8d81f26e900b1328f642e225c88bce1",
|
||||
"sha256:1e8d1898d4fb817120a5f684363b30108d7b0b46c7261264b100d14ec90a70e7",
|
||||
"sha256:2054dea683f1bda3a804fcfdb0c1c74821acb968093d0be16233873190d459e3",
|
||||
"sha256:29385c4dbb3f8b3a55ce13de6a97a3d21bd00de66acd7cdfc0b49cb2f08c906c",
|
||||
"sha256:295bc8a13554a25ad31e44c4bedabd3c3e28bba027e4feeb9bb157647a2344a7",
|
||||
"sha256:2cdb3789736f91d0b3333ac54d12a7e4f9efbc98f53cb905d3496259a893a8b3",
|
||||
"sha256:3baf3eaa41044d4ced2463fd5d23bf7bd4b03d68739c6c99a59ce1f95599a673",
|
||||
"sha256:4e61100200fa6ab7c99b61476f9f9653962ae71b931391d0264acfb4d9527d9c",
|
||||
"sha256:6266fde576e12357b25096351aac2b4b880b0066263e7bc7a9a1b4307991bb0e",
|
||||
"sha256:650c4f1fc4273f4e783e1d8e8b51a3e2311c2488ba0fcae6425b1e2c248a189d",
|
||||
"sha256:658e3477676009083422042c4bac2bdad77b696e932a3de001c42cc046f8eda2",
|
||||
"sha256:6adc1bd68f81968c9d249aab8c09cdc2cbe384bf2d2cb7f190f56875000cdc72",
|
||||
"sha256:6c4d83d21d23dd854ffbc8154cf293f4e43ba630aa9bd2539c899343d7f59da3",
|
||||
"sha256:6f74b6d8f59f3cfb8237e25c532b11f794b96f5c89a6f4a25857d85f84fbef11",
|
||||
"sha256:7783d89bd5413d183a38761fbc68279b984b9afcfbb39fa89d91f63763fbfb90",
|
||||
"sha256:7e3536f305f42ad6d31fc86636c54c7dafce8d634e56fef790fbacb59d499dd5",
|
||||
"sha256:821e10b73e0898544807a0692a276e539e5bafe0a055506a6882814b6a02c3ec",
|
||||
"sha256:835962f432bce92dc9bf22903d46c50003c8d11b1dc64084c8fae63bca98564a",
|
||||
"sha256:85c61bee5957e2d7be390392feac7e1d7abd3a49cbaed0c8cee1541b784c8561",
|
||||
"sha256:86f9931eb92e521809d4b64ec8514f18faa8e11e97d6c2d1afa1bcf6c20a8eab",
|
||||
"sha256:8a5c2250c0a74428fd5507ae8853706fdde0f23bfb62ee1ec9418eeacf216078",
|
||||
"sha256:8aec4b4da165c4a64ea80443c16e49e3b15df0f56c124ac5f2f8708a65a0eddc",
|
||||
"sha256:8c268e78d175798cd71d29114b0a1f1391c7d011995267d3b62319ec1a4ecaa1",
|
||||
"sha256:8d80087320632457aefc73f686f66139801959bf5b066b4419b92be85be3543c",
|
||||
"sha256:95e89a8558c8c48626dcffdf9c8abac26b7c251d352688e7ab9baf351e1c7da6",
|
||||
"sha256:9c371dd326289d85906c27ec2bc1dcdedd9d0be12b543d16e37bad35754bde48",
|
||||
"sha256:9c7cb25adba814d5f419733fe565f3289d6fa629ab9e0b78f6dff5fa94ab0456",
|
||||
"sha256:a731552729ee8ae9c546fb1c651c97bf5f759018fdd40d0e9b4d129e1e3a44c8",
|
||||
"sha256:aea4006b73b555fc5bdb650a8b92cf486d678afa168cf9b38402bb60bf0f9c18",
|
||||
"sha256:b0e3f59d3c772f2c3baaef2db425e6fc4149d35a052d874bb95ccfca10a1b9f4",
|
||||
"sha256:b15dc34273aefe522df25096d5d087abc626e388a28a28ac75a4404bb7668736",
|
||||
"sha256:c000635fd78400a558bd7a3c2981bb2a430005ebaa909d31e6e300719739a949",
|
||||
"sha256:c31f35a984caffb75f00a86852951a337540b44e4a22171354fb760cefa09346",
|
||||
"sha256:c50a6379763c733562b1fee877372234d271e5c78cd13ade5f25978aa06744db",
|
||||
"sha256:c94722bf403b8da744b7d0bb87e1f2529383003ceec92e754f768ef9323f69ad",
|
||||
"sha256:dcbbc9cfa147d55a577d285fd479b43103188855074552708df7acc31a476dd9",
|
||||
"sha256:fb9f5844db480e2ef9fce3a72e71122dd010ab7b2920f777966ba25f7eb63819"
|
||||
],
|
||||
"version": "==2021.8.28"
|
||||
"version": "==2021.9.24"
|
||||
},
|
||||
"requests": {
|
||||
"hashes": [
|
||||
@ -1863,11 +1871,11 @@
|
||||
"secure"
|
||||
],
|
||||
"hashes": [
|
||||
"sha256:39fb8672126159acb139a7718dd10806104dec1e2f0f6c88aab05d17df10c8d4",
|
||||
"sha256:f57b4c16c62fa2760b7e3d97c35b255512fb6b59a259730f36ba32ce9f8e342f"
|
||||
"sha256:4987c65554f7a2dbf30c18fd48778ef124af6fab771a377103da0585e2336ece",
|
||||
"sha256:c4fdf4019605b6e5423637e01bc9fe4daef873709a7973e195ceba0a62bbc844"
|
||||
],
|
||||
"index": "pypi",
|
||||
"version": "==1.26.6"
|
||||
"version": "==1.26.7"
|
||||
},
|
||||
"wrapt": {
|
||||
"hashes": [
|
||||
|
@ -1,3 +1,3 @@
|
||||
"""authentik"""
|
||||
__version__ = "2021.9.1"
|
||||
__version__ = "2021.9.3"
|
||||
ENV_GIT_HASH_KEY = "GIT_BUILD_HASH"
|
||||
|
@ -84,7 +84,7 @@ class SystemSerializer(PassiveSerializer):
|
||||
return now()
|
||||
|
||||
def get_embedded_outpost_host(self, request: Request) -> str:
|
||||
"""Get the FQDN configured on the embeddded outpost"""
|
||||
"""Get the FQDN configured on the embedded outpost"""
|
||||
outposts = Outpost.objects.filter(managed=MANAGED_OUTPOST)
|
||||
if not outposts.exists():
|
||||
return ""
|
||||
|
@ -11,7 +11,7 @@ from drf_spectacular.types import OpenApiTypes
|
||||
|
||||
|
||||
def build_standard_type(obj, **kwargs):
|
||||
"""Build a basic type with optional add ons."""
|
||||
"""Build a basic type with optional add owns."""
|
||||
schema = build_basic_type(obj)
|
||||
schema.update(kwargs)
|
||||
return schema
|
||||
|
@ -63,7 +63,7 @@ class ConfigView(APIView):
|
||||
|
||||
@extend_schema(responses={200: ConfigSerializer(many=False)})
|
||||
def get(self, request: Request) -> Response:
|
||||
"""Retrive public configuration options"""
|
||||
"""Retrieve public configuration options"""
|
||||
config = ConfigSerializer(
|
||||
{
|
||||
"error_reporting_enabled": CONFIG.y("error_reporting.enabled"),
|
||||
|
@ -10,10 +10,13 @@ from rest_framework.permissions import AllowAny
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.throttling import AnonRateThrottle
|
||||
from rest_framework.views import APIView
|
||||
from structlog.stdlib import get_logger
|
||||
|
||||
from authentik.api.tasks import sentry_proxy
|
||||
from authentik.lib.config import CONFIG
|
||||
|
||||
LOGGER = get_logger()
|
||||
|
||||
|
||||
class PlainTextParser(BaseParser):
|
||||
"""Plain text parser."""
|
||||
@ -45,6 +48,7 @@ class SentryTunnelView(APIView):
|
||||
"""Sentry tunnel, to prevent ad blockers from blocking sentry"""
|
||||
# Only allow usage of this endpoint when error reporting is enabled
|
||||
if not CONFIG.y_bool("error_reporting.enabled", False):
|
||||
LOGGER.debug("error reporting disabled")
|
||||
return HttpResponse(status=400)
|
||||
# Body is 2 json objects separated by \n
|
||||
full_body = request.body
|
||||
@ -55,6 +59,7 @@ class SentryTunnelView(APIView):
|
||||
# Check that the DSN is what we expect
|
||||
dsn = header.get("dsn", "")
|
||||
if dsn != settings.SENTRY_DSN:
|
||||
LOGGER.debug("Invalid dsn", have=dsn, expected=settings.SENTRY_DSN)
|
||||
return HttpResponse(status=400)
|
||||
sentry_proxy.delay(full_body.decode())
|
||||
return HttpResponse(status=204)
|
||||
|
@ -308,7 +308,7 @@ class UserViewSet(UsedByMixin, ModelViewSet):
|
||||
"""Allow users to change information on their own profile"""
|
||||
data = UserSelfSerializer(instance=User.objects.get(pk=request.user.pk), data=request.data)
|
||||
if not data.is_valid():
|
||||
return Response(data.errors)
|
||||
return Response(data.errors, status=400)
|
||||
new_user = data.save()
|
||||
# If we're impersonating, we need to update that user object
|
||||
# since it caches the full object
|
||||
|
@ -26,7 +26,7 @@ class Migration(migrations.Migration):
|
||||
),
|
||||
(
|
||||
"username_link",
|
||||
"Link to a user with identical username address. Can have security implications when a username is used with another source.",
|
||||
"Link to a user with identical username. Can have security implications when a username is used with another source.",
|
||||
),
|
||||
(
|
||||
"username_deny",
|
||||
|
@ -283,7 +283,7 @@ class SourceUserMatchingModes(models.TextChoices):
|
||||
)
|
||||
USERNAME_LINK = "username_link", _(
|
||||
(
|
||||
"Link to a user with identical username address. Can have security implications "
|
||||
"Link to a user with identical username. Can have security implications "
|
||||
"when a username is used with another source."
|
||||
)
|
||||
)
|
||||
|
@ -1,7 +1,10 @@
|
||||
"""NotificationTransport API Views"""
|
||||
from typing import Any
|
||||
|
||||
from drf_spectacular.types import OpenApiTypes
|
||||
from drf_spectacular.utils import OpenApiResponse, extend_schema
|
||||
from rest_framework.decorators import action
|
||||
from rest_framework.exceptions import ValidationError
|
||||
from rest_framework.fields import CharField, ListField, SerializerMethodField
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
@ -29,6 +32,14 @@ class NotificationTransportSerializer(ModelSerializer):
|
||||
"""Return selected mode with a UI Label"""
|
||||
return TransportMode(instance.mode).label
|
||||
|
||||
def validate(self, attrs: dict[Any, str]) -> dict[Any, str]:
|
||||
"""Ensure the required fields are set."""
|
||||
mode = attrs.get("mode")
|
||||
if mode in [TransportMode.WEBHOOK, TransportMode.WEBHOOK_SLACK]:
|
||||
if "webhook_url" not in attrs or attrs.get("webhook_url", "") == "":
|
||||
raise ValidationError("Webhook URL may not be empty.")
|
||||
return attrs
|
||||
|
||||
class Meta:
|
||||
|
||||
model = NotificationTransport
|
||||
|
@ -6,6 +6,7 @@ from typing import TYPE_CHECKING, Optional, Type, Union
|
||||
from uuid import uuid4
|
||||
|
||||
from django.conf import settings
|
||||
from django.core.validators import URLValidator
|
||||
from django.db import models
|
||||
from django.http import HttpRequest
|
||||
from django.http.request import QueryDict
|
||||
@ -223,7 +224,7 @@ class NotificationTransport(models.Model):
|
||||
name = models.TextField(unique=True)
|
||||
mode = models.TextField(choices=TransportMode.choices)
|
||||
|
||||
webhook_url = models.TextField(blank=True)
|
||||
webhook_url = models.TextField(blank=True, validators=[URLValidator()])
|
||||
webhook_mapping = models.ForeignKey(
|
||||
"NotificationWebhookMapping", on_delete=models.SET_DEFAULT, null=True, default=None
|
||||
)
|
||||
|
@ -4,7 +4,13 @@ from django.urls import reverse
|
||||
from rest_framework.test import APITestCase
|
||||
|
||||
from authentik.core.models import User
|
||||
from authentik.events.models import Event, EventAction, Notification, NotificationSeverity
|
||||
from authentik.events.models import (
|
||||
Event,
|
||||
EventAction,
|
||||
Notification,
|
||||
NotificationSeverity,
|
||||
TransportMode,
|
||||
)
|
||||
|
||||
|
||||
class TestEventsAPI(APITestCase):
|
||||
@ -41,3 +47,23 @@ class TestEventsAPI(APITestCase):
|
||||
)
|
||||
notification.refresh_from_db()
|
||||
self.assertTrue(notification.seen)
|
||||
|
||||
def test_transport(self):
|
||||
"""Test transport API"""
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:notificationtransport-list"),
|
||||
data={
|
||||
"name": "foo-with",
|
||||
"mode": TransportMode.WEBHOOK,
|
||||
"webhook_url": "http://foo.com",
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 201)
|
||||
response = self.client.post(
|
||||
reverse("authentik_api:notificationtransport-list"),
|
||||
data={
|
||||
"name": "foo-without",
|
||||
"mode": TransportMode.WEBHOOK,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 400)
|
||||
|
@ -77,7 +77,7 @@ def sanitize_dict(source: dict[Any, Any]) -> dict[Any, Any]:
|
||||
final_dict = {}
|
||||
for key, value in source.items():
|
||||
if is_dataclass(value):
|
||||
# Because asdict calls `copy.deepcopy(obj)` on everything thats not tuple/dict,
|
||||
# Because asdict calls `copy.deepcopy(obj)` on everything that's not tuple/dict,
|
||||
# and deepcopy doesn't work with HttpRequests (neither django nor rest_framework).
|
||||
# Currently, the only dataclass that actually holds an http request is a PolicyRequest
|
||||
if isinstance(value, PolicyRequest):
|
||||
|
@ -57,11 +57,11 @@ class FlowPlan:
|
||||
markers: list[StageMarker] = field(default_factory=list)
|
||||
|
||||
def append_stage(self, stage: Stage, marker: Optional[StageMarker] = None):
|
||||
"""Append `stage` to all stages, optionall with stage marker"""
|
||||
"""Append `stage` to all stages, optionally with stage marker"""
|
||||
return self.append(FlowStageBinding(stage=stage), marker)
|
||||
|
||||
def append(self, binding: FlowStageBinding, marker: Optional[StageMarker] = None):
|
||||
"""Append `stage` to all stages, optionall with stage marker"""
|
||||
"""Append `stage` to all stages, optionally with stage marker"""
|
||||
self.bindings.append(binding)
|
||||
self.markers.append(marker or StageMarker())
|
||||
|
||||
|
@ -438,7 +438,7 @@ class TestFlowExecutor(APITestCase):
|
||||
|
||||
# third request, this should trigger the re-evaluate
|
||||
# A get request will evaluate the policies and this will return stage 4
|
||||
# but it won't save it, hence we cant' check the plan
|
||||
# but it won't save it, hence we can't check the plan
|
||||
response = self.client.get(exec_url)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertJSONEqual(
|
||||
|
@ -11,7 +11,7 @@ from authentik.lib.sentry import SentryIgnoredException
|
||||
|
||||
|
||||
def get_attrs(obj: SerializerModel) -> dict[str, Any]:
|
||||
"""Get object's attributes via their serializer, and covert it to a normal dict"""
|
||||
"""Get object's attributes via their serializer, and convert it to a normal dict"""
|
||||
data = dict(obj.serializer(obj).data)
|
||||
to_remove = (
|
||||
"policies",
|
||||
|
@ -126,12 +126,12 @@ class FlowExecutorView(APIView):
|
||||
|
||||
# pylint: disable=unused-argument, too-many-return-statements
|
||||
def dispatch(self, request: HttpRequest, flow_slug: str) -> HttpResponse:
|
||||
# Early check if theres an active Plan for the current session
|
||||
# Early check if there's an active Plan for the current session
|
||||
if SESSION_KEY_PLAN in self.request.session:
|
||||
self.plan = self.request.session[SESSION_KEY_PLAN]
|
||||
if self.plan.flow_pk != self.flow.pk.hex:
|
||||
self._logger.warning(
|
||||
"f(exec): Found existing plan for other flow, deleteing plan",
|
||||
"f(exec): Found existing plan for other flow, deleting plan",
|
||||
)
|
||||
# Existing plan is deleted from session and instance
|
||||
self.plan = None
|
||||
@ -433,7 +433,7 @@ class ToDefaultFlow(View):
|
||||
plan: FlowPlan = self.request.session[SESSION_KEY_PLAN]
|
||||
if plan.flow_pk != flow.pk.hex:
|
||||
LOGGER.warning(
|
||||
"f(def): Found existing plan for other flow, deleteing plan",
|
||||
"f(def): Found existing plan for other flow, deleting plan",
|
||||
flow_slug=flow.slug,
|
||||
)
|
||||
del self.request.session[SESSION_KEY_PLAN]
|
||||
|
@ -32,7 +32,7 @@ class TestConfig(TestCase):
|
||||
config = ConfigLoader()
|
||||
environ["foo"] = "bar"
|
||||
self.assertEqual(config.parse_uri("env://foo"), "bar")
|
||||
self.assertEqual(config.parse_uri("env://fo?bar"), "bar")
|
||||
self.assertEqual(config.parse_uri("env://foo?bar"), "bar")
|
||||
|
||||
def test_uri_file(self):
|
||||
"""Test URI parsing (file load)"""
|
||||
|
@ -27,7 +27,7 @@ class TestHTTP(TestCase):
|
||||
token = Token.objects.create(
|
||||
identifier="test", user=self.user, intent=TokenIntents.INTENT_API
|
||||
)
|
||||
# Invalid, non-existant token
|
||||
# Invalid, non-existent token
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
**{
|
||||
@ -36,7 +36,7 @@ class TestHTTP(TestCase):
|
||||
},
|
||||
)
|
||||
self.assertEqual(get_client_ip(request), "127.0.0.1")
|
||||
# Invalid, user doesn't have permisions
|
||||
# Invalid, user doesn't have permissions
|
||||
request = self.factory.get(
|
||||
"/",
|
||||
**{
|
||||
|
@ -104,7 +104,7 @@ class OutpostConsumer(AuthJsonConsumer):
|
||||
expected=self.outpost.config.kubernetes_replicas,
|
||||
).inc()
|
||||
LOGGER.debug(
|
||||
"added outpost instace to cache",
|
||||
"added outpost instance to cache",
|
||||
outpost=self.outpost,
|
||||
instance_uuid=self.last_uid,
|
||||
)
|
||||
|
@ -38,6 +38,7 @@ class DockerController(BaseController):
|
||||
"AUTHENTIK_HOST": self.outpost.config.authentik_host.lower(),
|
||||
"AUTHENTIK_INSECURE": str(self.outpost.config.authentik_host_insecure).lower(),
|
||||
"AUTHENTIK_TOKEN": self.outpost.token.key,
|
||||
"AUTHENTIK_HOST_BROWSER": self.outpost.config.authentik_host_browser,
|
||||
}
|
||||
|
||||
def _comp_env(self, container: Container) -> bool:
|
||||
@ -215,6 +216,7 @@ class DockerController(BaseController):
|
||||
"AUTHENTIK_HOST": self.outpost.config.authentik_host,
|
||||
"AUTHENTIK_INSECURE": str(self.outpost.config.authentik_host_insecure),
|
||||
"AUTHENTIK_TOKEN": self.outpost.token.key,
|
||||
"AUTHENTIK_HOST_BROWSER": self.outpost.config.authentik_host_browser,
|
||||
},
|
||||
"labels": self._get_labels(),
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ class KubernetesObjectReconciler(Generic[T]):
|
||||
except (OpenApiException, HTTPError) as exc:
|
||||
# pylint: disable=no-member
|
||||
if isinstance(exc, ApiException) and exc.status == 404:
|
||||
self.logger.debug("Failed to get current, assuming non-existant")
|
||||
self.logger.debug("Failed to get current, assuming non-existent")
|
||||
return
|
||||
self.logger.debug("Other unhandled error", exc=exc)
|
||||
raise exc
|
||||
@ -129,7 +129,7 @@ class KubernetesObjectReconciler(Generic[T]):
|
||||
raise NotImplementedError
|
||||
|
||||
def retrieve(self) -> T:
|
||||
"""API Wrapper to retrive object"""
|
||||
"""API Wrapper to retrieve object"""
|
||||
raise NotImplementedError
|
||||
|
||||
def delete(self, reference: T):
|
||||
|
@ -89,6 +89,15 @@ class DeploymentReconciler(KubernetesObjectReconciler[V1Deployment]):
|
||||
)
|
||||
),
|
||||
),
|
||||
V1EnvVar(
|
||||
name="AUTHENTIK_HOST_BROWSER",
|
||||
value_from=V1EnvVarSource(
|
||||
secret_key_ref=V1SecretKeySelector(
|
||||
name=self.name,
|
||||
key="authentik_host_browser",
|
||||
)
|
||||
),
|
||||
),
|
||||
V1EnvVar(
|
||||
name="AUTHENTIK_TOKEN",
|
||||
value_from=V1EnvVarSource(
|
||||
|
@ -26,7 +26,7 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
|
||||
def reconcile(self, current: V1Secret, reference: V1Secret):
|
||||
super().reconcile(current, reference)
|
||||
for key in reference.data.keys():
|
||||
if current.data[key] != reference.data[key]:
|
||||
if key not in current.data or current.data[key] != reference.data[key]:
|
||||
raise NeedsUpdate()
|
||||
|
||||
def get_reference_object(self) -> V1Secret:
|
||||
@ -40,6 +40,9 @@ class SecretReconciler(KubernetesObjectReconciler[V1Secret]):
|
||||
str(self.controller.outpost.config.authentik_host_insecure)
|
||||
),
|
||||
"token": b64string(self.controller.outpost.token.key),
|
||||
"authentik_host_browser": b64string(
|
||||
self.controller.outpost.config.authentik_host_browser
|
||||
),
|
||||
},
|
||||
)
|
||||
|
||||
|
@ -19,12 +19,16 @@ class ServiceReconciler(KubernetesObjectReconciler[V1Service]):
|
||||
self.api = CoreV1Api(controller.client)
|
||||
|
||||
def reconcile(self, current: V1Service, reference: V1Service):
|
||||
super().reconcile(current, reference)
|
||||
if len(current.spec.ports) != len(reference.spec.ports):
|
||||
raise NeedsRecreate()
|
||||
for port in reference.spec.ports:
|
||||
if port not in current.spec.ports:
|
||||
raise NeedsRecreate()
|
||||
# run the base reconcile last, as that will probably raise NeedsUpdate
|
||||
# after an authentik update. However the ports might have also changed during
|
||||
# the update, so this causes the service to be re-created with higher
|
||||
# priority than being updated.
|
||||
super().reconcile(current, reference)
|
||||
|
||||
def get_reference_object(self) -> V1Service:
|
||||
"""Get deployment object for outpost"""
|
||||
|
@ -30,7 +30,7 @@ class DockerInlineTLS:
|
||||
return str(path)
|
||||
|
||||
def write(self) -> TLSConfig:
|
||||
"""Create TLSConfig with Certificate Keypairs"""
|
||||
"""Create TLSConfig with Certificate Key pairs"""
|
||||
# So yes, this is quite ugly. But sadly, there is no clean way to pass
|
||||
# docker-py (which is using requests (which is using urllib3)) a certificate
|
||||
# for verification or authentication as string.
|
||||
|
@ -64,6 +64,7 @@ class OutpostConfig:
|
||||
|
||||
authentik_host: str = ""
|
||||
authentik_host_insecure: bool = False
|
||||
authentik_host_browser: str = ""
|
||||
|
||||
log_level: str = CONFIG.y("log_level")
|
||||
error_reporting_enabled: bool = CONFIG.y_bool("error_reporting.enabled")
|
||||
|
@ -181,7 +181,7 @@ def outpost_post_save(model_class: str, model_pk: Any):
|
||||
|
||||
|
||||
def outpost_send_update(model_instace: Model):
|
||||
"""Send outpost update to all registered outposts, irregardless to which authentik
|
||||
"""Send outpost update to all registered outposts, regardless to which authentik
|
||||
instance they are connected"""
|
||||
channel_layer = get_channel_layer()
|
||||
if isinstance(model_instace, OutpostModel):
|
||||
@ -208,7 +208,7 @@ def _outpost_single_update(outpost: Outpost, layer=None):
|
||||
@CELERY_APP.task()
|
||||
def outpost_local_connection():
|
||||
"""Checks the local environment and create Service connections."""
|
||||
# Explicitly check against token filename, as thats
|
||||
# Explicitly check against token filename, as that's
|
||||
# only present when the integration is enabled
|
||||
if Path(SERVICE_TOKEN_FILENAME).exists():
|
||||
LOGGER.debug("Detected in-cluster Kubernetes Config")
|
||||
|
@ -46,7 +46,7 @@ def cache_key(binding: PolicyBinding, request: PolicyRequest) -> str:
|
||||
|
||||
|
||||
class PolicyProcess(PROCESS_CLASS):
|
||||
"""Evaluate a single policy within a seprate process"""
|
||||
"""Evaluate a single policy within a separate process"""
|
||||
|
||||
connection: Connection
|
||||
binding: PolicyBinding
|
||||
|
@ -34,7 +34,7 @@ def update_score(request: HttpRequest, username: str, amount: int):
|
||||
@receiver(user_login_failed)
|
||||
# pylint: disable=unused-argument
|
||||
def handle_failed_login(sender, request, credentials, **_):
|
||||
"""Lower Score for failed loging attempts"""
|
||||
"""Lower Score for failed login attempts"""
|
||||
if "username" in credentials:
|
||||
update_score(request, credentials.get("username"), -1)
|
||||
|
||||
|
@ -14,7 +14,7 @@ from authentik.policies.types import PolicyRequest
|
||||
|
||||
|
||||
def clear_policy_cache():
|
||||
"""Ensure no policy-related keys are stil cached"""
|
||||
"""Ensure no policy-related keys are still cached"""
|
||||
keys = cache.keys("policy_*")
|
||||
cache.delete(keys)
|
||||
|
||||
|
@ -1,11 +1,11 @@
|
||||
"""LDAP Provider Docker Contoller"""
|
||||
"""LDAP Provider Docker Controller"""
|
||||
from authentik.outposts.controllers.base import DeploymentPort
|
||||
from authentik.outposts.controllers.docker import DockerController
|
||||
from authentik.outposts.models import DockerServiceConnection, Outpost
|
||||
|
||||
|
||||
class LDAPDockerController(DockerController):
|
||||
"""LDAP Provider Docker Contoller"""
|
||||
"""LDAP Provider Docker Controller"""
|
||||
|
||||
def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
|
||||
super().__init__(outpost, connection)
|
||||
|
@ -1,11 +1,11 @@
|
||||
"""LDAP Provider Kubernetes Contoller"""
|
||||
"""LDAP Provider Kubernetes Controller"""
|
||||
from authentik.outposts.controllers.base import DeploymentPort
|
||||
from authentik.outposts.controllers.kubernetes import KubernetesController
|
||||
from authentik.outposts.models import KubernetesServiceConnection, Outpost
|
||||
|
||||
|
||||
class LDAPKubernetesController(KubernetesController):
|
||||
"""LDAP Provider Kubernetes Contoller"""
|
||||
"""LDAP Provider Kubernetes Controller"""
|
||||
|
||||
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
|
||||
super().__init__(outpost, connection)
|
||||
|
@ -153,7 +153,7 @@ def protected_resource_view(scopes: list[str]):
|
||||
|
||||
if not set(scopes).issubset(set(token.scope)):
|
||||
LOGGER.warning(
|
||||
"Scope missmatch.",
|
||||
"Scope mismatch.",
|
||||
required=set(scopes),
|
||||
token_has=set(token.scope),
|
||||
)
|
||||
|
@ -33,7 +33,7 @@ class UserInfoView(View):
|
||||
for scope in ScopeMapping.objects.filter(scope_name__in=scopes).order_by("scope_name"):
|
||||
if scope.description != "":
|
||||
scope_descriptions.append({"id": scope.scope_name, "name": scope.description})
|
||||
# GitHub Compatibility Scopes are handeled differently, since they required custom paths
|
||||
# GitHub Compatibility Scopes are handled differently, since they required custom paths
|
||||
# Hence they don't exist as Scope objects
|
||||
github_scope_map = {
|
||||
SCOPE_GITHUB_USER: ("GitHub Compatibility: Access your User Information"),
|
||||
|
@ -1,5 +1,5 @@
|
||||
"""ProxyProvider API Views"""
|
||||
from typing import Any
|
||||
from typing import Any, Optional
|
||||
|
||||
from drf_spectacular.utils import extend_schema_field
|
||||
from rest_framework.exceptions import ValidationError
|
||||
@ -10,6 +10,7 @@ from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet
|
||||
from authentik.core.api.providers import ProviderSerializer
|
||||
from authentik.core.api.used_by import UsedByMixin
|
||||
from authentik.core.api.utils import PassiveSerializer
|
||||
from authentik.lib.utils.time import timedelta_from_string
|
||||
from authentik.providers.oauth2.views.provider import ProviderInfoView
|
||||
from authentik.providers.proxy.models import ProxyMode, ProxyProvider
|
||||
|
||||
@ -106,6 +107,16 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
|
||||
"""Proxy provider serializer for outposts"""
|
||||
|
||||
oidc_configuration = SerializerMethodField()
|
||||
token_validity = SerializerMethodField()
|
||||
|
||||
@extend_schema_field(OpenIDConnectConfigurationSerializer)
|
||||
def get_oidc_configuration(self, obj: ProxyProvider):
|
||||
"""Embed OpenID Connect provider information"""
|
||||
return ProviderInfoView(request=self.context["request"]._request).get_info(obj)
|
||||
|
||||
def get_token_validity(self, obj: ProxyProvider) -> Optional[float]:
|
||||
"""Get token validity as second count"""
|
||||
return timedelta_from_string(obj.token_validity).total_seconds()
|
||||
|
||||
class Meta:
|
||||
|
||||
@ -127,13 +138,9 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
|
||||
"basic_auth_user_attribute",
|
||||
"mode",
|
||||
"cookie_domain",
|
||||
"token_validity",
|
||||
]
|
||||
|
||||
@extend_schema_field(OpenIDConnectConfigurationSerializer)
|
||||
def get_oidc_configuration(self, obj: ProxyProvider):
|
||||
"""Embed OpenID Connect provider information"""
|
||||
return ProviderInfoView(request=self.context["request"]._request).get_info(obj)
|
||||
|
||||
|
||||
class ProxyOutpostConfigViewSet(ReadOnlyModelViewSet):
|
||||
"""ProxyProvider Viewset"""
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Proxy Provider Docker Contoller"""
|
||||
"""Proxy Provider Docker Controller"""
|
||||
from urllib.parse import urlparse
|
||||
|
||||
from authentik.outposts.controllers.base import DeploymentPort
|
||||
@ -8,7 +8,7 @@ from authentik.providers.proxy.models import ProxyProvider
|
||||
|
||||
|
||||
class ProxyDockerController(DockerController):
|
||||
"""Proxy Provider Docker Contoller"""
|
||||
"""Proxy Provider Docker Controller"""
|
||||
|
||||
def __init__(self, outpost: Outpost, connection: DockerServiceConnection):
|
||||
super().__init__(outpost, connection)
|
||||
@ -29,6 +29,11 @@ class ProxyDockerController(DockerController):
|
||||
labels[f"traefik.http.routers.{traefik_name}-router.rule"] = f"Host({','.join(hosts)})"
|
||||
labels[f"traefik.http.routers.{traefik_name}-router.tls"] = "true"
|
||||
labels[f"traefik.http.routers.{traefik_name}-router.service"] = f"{traefik_name}-service"
|
||||
labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"] = "/"
|
||||
labels[
|
||||
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.path"
|
||||
] = "/akprox/ping"
|
||||
labels[
|
||||
f"traefik.http.services.{traefik_name}-service.loadbalancer.healthcheck.port"
|
||||
] = "9300"
|
||||
labels[f"traefik.http.services.{traefik_name}-service.loadbalancer.server.port"] = "9000"
|
||||
return labels
|
||||
|
@ -1,4 +1,4 @@
|
||||
"""Proxy Provider Kubernetes Contoller"""
|
||||
"""Proxy Provider Kubernetes Controller"""
|
||||
from authentik.outposts.controllers.base import DeploymentPort
|
||||
from authentik.outposts.controllers.kubernetes import KubernetesController
|
||||
from authentik.outposts.models import KubernetesServiceConnection, Outpost
|
||||
@ -7,7 +7,7 @@ from authentik.providers.proxy.controllers.k8s.traefik import TraefikMiddlewareR
|
||||
|
||||
|
||||
class ProxyKubernetesController(KubernetesController):
|
||||
"""Proxy Provider Kubernetes Contoller"""
|
||||
"""Proxy Provider Kubernetes Controller"""
|
||||
|
||||
def __init__(self, outpost: Outpost, connection: KubernetesServiceConnection):
|
||||
super().__init__(outpost, connection)
|
||||
|
@ -119,7 +119,7 @@ class LDAPPasswordChanger:
|
||||
return True
|
||||
|
||||
def ad_password_complexity(self, password: str, user: Optional[User] = None) -> bool:
|
||||
"""Check if password matches Active direcotry password policies
|
||||
"""Check if password matches Active directory password policies
|
||||
|
||||
https://docs.microsoft.com/en-us/windows/security/threat-protection/
|
||||
security-policy-settings/password-must-meet-complexity-requirements
|
||||
|
@ -48,7 +48,7 @@ def ldap_password_validate(sender, password: str, plan_context: dict[str, Any],
|
||||
password, plan_context.get(PLAN_CONTEXT_PENDING_USER, None)
|
||||
)
|
||||
if not passing:
|
||||
raise ValidationError(_("Password does not match Active Direcory Complexity."))
|
||||
raise ValidationError(_("Password does not match Active Directory Complexity."))
|
||||
|
||||
|
||||
@receiver(password_changed)
|
||||
|
@ -46,9 +46,9 @@ class OAuthSourceSerializer(SourceSerializer):
|
||||
type = SerializerMethodField()
|
||||
|
||||
@extend_schema_field(SourceTypeSerializer)
|
||||
def get_type(self, instace: OAuthSource) -> SourceTypeSerializer:
|
||||
def get_type(self, instance: OAuthSource) -> SourceTypeSerializer:
|
||||
"""Get source's type configuration"""
|
||||
return SourceTypeSerializer(instace.type).data
|
||||
return SourceTypeSerializer(instance.type).data
|
||||
|
||||
def validate(self, attrs: dict) -> dict:
|
||||
provider_type = MANAGER.find_type(attrs.get("provider_type", ""))
|
||||
|
@ -14,7 +14,7 @@ class Migration(migrations.Migration):
|
||||
model_name="oauthsource",
|
||||
name="access_token_url",
|
||||
field=models.CharField(
|
||||
help_text="URL used by authentik to retrive tokens.",
|
||||
help_text="URL used by authentik to retrieve tokens.",
|
||||
max_length=255,
|
||||
verbose_name="Access Token URL",
|
||||
),
|
||||
|
@ -15,7 +15,7 @@ class Migration(migrations.Migration):
|
||||
name="access_token_url",
|
||||
field=models.CharField(
|
||||
blank=True,
|
||||
help_text="URL used by authentik to retrive tokens.",
|
||||
help_text="URL used by authentik to retrieve tokens.",
|
||||
max_length=255,
|
||||
verbose_name="Access Token URL",
|
||||
),
|
||||
|
@ -39,7 +39,7 @@ class Migration(migrations.Migration):
|
||||
model_name="oauthsource",
|
||||
name="access_token_url",
|
||||
field=models.CharField(
|
||||
help_text="URL used by authentik to retrive tokens.",
|
||||
help_text="URL used by authentik to retrieve tokens.",
|
||||
max_length=255,
|
||||
null=True,
|
||||
verbose_name="Access Token URL",
|
||||
|
@ -37,7 +37,7 @@ class OAuthSource(Source):
|
||||
max_length=255,
|
||||
null=True,
|
||||
verbose_name=_("Access Token URL"),
|
||||
help_text=_("URL used by authentik to retrive tokens."),
|
||||
help_text=_("URL used by authentik to retrieve tokens."),
|
||||
)
|
||||
profile_url = models.CharField(
|
||||
max_length=255,
|
||||
|
@ -24,7 +24,7 @@ class Migration(migrations.Migration):
|
||||
migrations.AddField(
|
||||
model_name="plexsource",
|
||||
name="plex_token",
|
||||
field=models.TextField(default="", help_text="Plex token used to check firends"),
|
||||
field=models.TextField(default="", help_text="Plex token used to check friends"),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name="plexsource",
|
||||
|
@ -13,6 +13,6 @@ class Migration(migrations.Migration):
|
||||
migrations.AlterField(
|
||||
model_name="plexsource",
|
||||
name="plex_token",
|
||||
field=models.TextField(help_text="Plex token used to check firends"),
|
||||
field=models.TextField(help_text="Plex token used to check friends"),
|
||||
),
|
||||
]
|
||||
|
@ -50,7 +50,7 @@ class PlexSource(Source):
|
||||
default=True,
|
||||
help_text=_("Allow friends to authenticate, even if you don't share a server."),
|
||||
)
|
||||
plex_token = models.TextField(help_text=_("Plex token used to check firends"))
|
||||
plex_token = models.TextField(help_text=_("Plex token used to check friends"))
|
||||
|
||||
@property
|
||||
def component(self) -> str:
|
||||
|
@ -54,7 +54,7 @@ class CaptchaChallengeResponse(ChallengeResponse):
|
||||
|
||||
|
||||
class CaptchaStageView(ChallengeStageView):
|
||||
"""Simple captcha checker, logic is handeled in django-captcha module"""
|
||||
"""Simple captcha checker, logic is handled in django-captcha module"""
|
||||
|
||||
response_class = CaptchaChallengeResponse
|
||||
|
||||
|
@ -53,9 +53,11 @@ class PromptChallengeResponse(ChallengeResponse):
|
||||
def __init__(self, *args, **kwargs):
|
||||
stage: PromptStage = kwargs.pop("stage", None)
|
||||
plan: FlowPlan = kwargs.pop("plan", None)
|
||||
request: HttpRequest = kwargs.pop("request", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
self.stage = stage
|
||||
self.plan = plan
|
||||
self.request = request
|
||||
if not self.stage:
|
||||
return
|
||||
# list() is called so we only load the fields once
|
||||
@ -104,8 +106,9 @@ class PromptChallengeResponse(ChallengeResponse):
|
||||
self._validate_password_fields(*[field.field_key for field in password_fields])
|
||||
|
||||
user = self.plan.context.get(PLAN_CONTEXT_PENDING_USER, get_anonymous_user())
|
||||
engine = ListPolicyEngine(self.stage.validation_policies.all(), user)
|
||||
engine.request.context = attrs
|
||||
engine = ListPolicyEngine(self.stage.validation_policies.all(), user, self.request)
|
||||
engine.request.context[PLAN_CONTEXT_PROMPT] = attrs
|
||||
engine.request.context.update(attrs)
|
||||
engine.build()
|
||||
result = engine.result
|
||||
if not result.passing:
|
||||
@ -173,6 +176,7 @@ class PromptStageView(ChallengeStageView):
|
||||
return PromptChallengeResponse(
|
||||
instance=None,
|
||||
data=data,
|
||||
request=self.request,
|
||||
stage=self.executor.current_stage,
|
||||
plan=self.executor.plan,
|
||||
)
|
||||
|
@ -21,7 +21,7 @@ services:
|
||||
networks:
|
||||
- internal
|
||||
server:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.3}
|
||||
restart: unless-stopped
|
||||
command: server
|
||||
environment:
|
||||
@ -44,7 +44,7 @@ services:
|
||||
- "0.0.0.0:9000:9000"
|
||||
- "0.0.0.0:9443:9443"
|
||||
worker:
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.1}
|
||||
image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2021.9.3}
|
||||
restart: unless-stopped
|
||||
command: worker
|
||||
networks:
|
||||
|
2
go.mod
2
go.mod
@ -34,7 +34,7 @@ require (
|
||||
github.com/recws-org/recws v1.3.1
|
||||
github.com/sirupsen/logrus v1.8.1
|
||||
go.mongodb.org/mongo-driver v1.5.2 // indirect
|
||||
goauthentik.io/api v0.202191.3
|
||||
goauthentik.io/api v0.202192.5
|
||||
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
|
||||
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
|
||||
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558
|
||||
|
4
go.sum
4
go.sum
@ -554,8 +554,8 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
|
||||
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
|
||||
goauthentik.io/api v0.202191.3 h1:2qrU8j2x6qPte9GEjCmUWL1KrMZu3DByjBQBfXAFGUE=
|
||||
goauthentik.io/api v0.202191.3/go.mod h1:02nnD4FRd8lu8A1+ZuzqownBgvAhdCKzqkKX8v7JMTE=
|
||||
goauthentik.io/api v0.202192.5 h1:BS4E71K2uZXy1vAdGVFLJJU0KwvAkkqKg42cYv46ud0=
|
||||
goauthentik.io/api v0.202192.5/go.mod h1:02nnD4FRd8lu8A1+ZuzqownBgvAhdCKzqkKX8v7JMTE=
|
||||
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
|
||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||
|
@ -17,4 +17,4 @@ func OutpostUserAgent() string {
|
||||
return fmt.Sprintf("authentik-outpost@%s (build=%s)", VERSION, BUILD())
|
||||
}
|
||||
|
||||
const VERSION = "2021.9.1"
|
||||
const VERSION = "2021.9.3"
|
||||
|
@ -116,9 +116,9 @@ func (a *APIController) OnRefresh() error {
|
||||
log.WithError(err).Error("Failed to fetch outpost configuration")
|
||||
return err
|
||||
}
|
||||
outpost := outposts.Results[0]
|
||||
a.Outpost = outposts.Results[0]
|
||||
|
||||
log.WithField("name", outpost.Name).Debug("Fetched outpost configuration")
|
||||
log.WithField("name", a.Outpost.Name).Debug("Fetched outpost configuration")
|
||||
return a.Server.Refresh()
|
||||
}
|
||||
|
||||
|
@ -79,6 +79,11 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultInsufficientAccessRights}, errors.New("access denied")
|
||||
}
|
||||
|
||||
if req.SearchRequest.Scope == ldap.ScopeBaseObject {
|
||||
pi.log.Debug("base scope, showing domain info")
|
||||
return pi.SearchBase(req, flags.CanSearch)
|
||||
}
|
||||
if !flags.CanSearch {
|
||||
pi.log.Debug("User can't search, showing info about user")
|
||||
return pi.SearchMe(req, flags)
|
||||
@ -111,6 +116,12 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
|
||||
"client": utils.GetIP(req.conn.RemoteAddr()),
|
||||
}).Inc()
|
||||
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, req.Filter)
|
||||
case "groupOfUniqueNames":
|
||||
fallthrough
|
||||
case "goauthentik.io/ldap/group":
|
||||
fallthrough
|
||||
case "goauthentik.io/ldap/virtual-group":
|
||||
fallthrough
|
||||
case GroupObjectClass:
|
||||
wg := sync.WaitGroup{}
|
||||
wg.Add(2)
|
||||
@ -160,7 +171,15 @@ func (pi *ProviderInstance) Search(req SearchRequest) (ldap.ServerSearchResult,
|
||||
}()
|
||||
wg.Wait()
|
||||
entries = append(gEntries, uEntries...)
|
||||
case UserObjectClass, "":
|
||||
case "":
|
||||
fallthrough
|
||||
case "organizationalPerson":
|
||||
fallthrough
|
||||
case "inetOrgPerson":
|
||||
fallthrough
|
||||
case "goauthentik.io/ldap/user":
|
||||
fallthrough
|
||||
case UserObjectClass:
|
||||
uapisp := sentry.StartSpan(req.ctx, "authentik.providers.ldap.search.api_user")
|
||||
searchReq, skip := parseFilterForUser(c.CoreApi.CoreUsersList(uapisp.Context()), parsedFilter, false)
|
||||
if skip {
|
||||
@ -197,7 +216,7 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry {
|
||||
"name": {u.Name},
|
||||
"displayName": {u.Name},
|
||||
"mail": {*u.Email},
|
||||
"objectClass": {UserObjectClass, "organizationalPerson", "goauthentik.io/ldap/user"},
|
||||
"objectClass": {UserObjectClass, "organizationalPerson", "inetOrgPerson", "goauthentik.io/ldap/user"},
|
||||
"uidNumber": {pi.GetUidNumber(u)},
|
||||
"gidNumber": {pi.GetUidNumber(u)},
|
||||
})
|
||||
@ -207,9 +226,9 @@ func (pi *ProviderInstance) UserEntry(u api.User) *ldap.Entry {
|
||||
func (pi *ProviderInstance) GroupEntry(g LDAPGroup) *ldap.Entry {
|
||||
attrs := AKAttrsToLDAP(g.akAttributes)
|
||||
|
||||
objectClass := []string{GroupObjectClass, "goauthentik.io/ldap/group"}
|
||||
objectClass := []string{GroupObjectClass, "groupOfUniqueNames", "goauthentik.io/ldap/group"}
|
||||
if g.isVirtualGroup {
|
||||
objectClass = []string{GroupObjectClass, "goauthentik.io/ldap/group", "goauthentik.io/ldap/virtual-group"}
|
||||
objectClass = append(objectClass, "goauthentik.io/ldap/virtual-group")
|
||||
}
|
||||
|
||||
attrs = pi.ensureAttributes(attrs, map[string][]string{
|
||||
|
53
internal/outpost/ldap/instance_search_base.go
Normal file
53
internal/outpost/ldap/instance_search_base.go
Normal file
@ -0,0 +1,53 @@
|
||||
package ldap
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/nmcclain/ldap"
|
||||
"goauthentik.io/internal/constants"
|
||||
)
|
||||
|
||||
func (pi *ProviderInstance) SearchBase(req SearchRequest, authz bool) (ldap.ServerSearchResult, error) {
|
||||
dn := ""
|
||||
if authz {
|
||||
dn = req.SearchRequest.BaseDN
|
||||
}
|
||||
return ldap.ServerSearchResult{
|
||||
Entries: []*ldap.Entry{
|
||||
{
|
||||
DN: dn,
|
||||
Attributes: []*ldap.EntryAttribute{
|
||||
{
|
||||
Name: "distinguishedName",
|
||||
Values: []string{pi.BaseDN},
|
||||
},
|
||||
{
|
||||
Name: "objectClass",
|
||||
Values: []string{"top", "domain"},
|
||||
},
|
||||
{
|
||||
Name: "supportedLDAPVersion",
|
||||
Values: []string{"3"},
|
||||
},
|
||||
{
|
||||
Name: "namingContexts",
|
||||
Values: []string{
|
||||
pi.BaseDN,
|
||||
pi.GroupDN,
|
||||
pi.UserDN,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "vendorName",
|
||||
Values: []string{"goauthentik.io"},
|
||||
},
|
||||
{
|
||||
Name: "vendorVersion",
|
||||
Values: []string{fmt.Sprintf("authentik LDAP Outpost Version %s (build %s)", constants.VERSION, constants.BUILD())},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Referrals: []string{}, Controls: []ldap.Control{}, ResultCode: ldap.LDAPResultSuccess,
|
||||
}, nil
|
||||
}
|
@ -38,7 +38,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
|
||||
SearchRequest: searchReq,
|
||||
BindDN: bindDN,
|
||||
conn: conn,
|
||||
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("client", utils.GetIP(conn.RemoteAddr())).WithField("filter", searchReq.Filter).WithField("baseDN", searchReq.BaseDN),
|
||||
log: ls.log.WithField("bindDN", bindDN).WithField("requestId", rid).WithField("scope", ldap.ScopeMap[searchReq.Scope]).WithField("client", utils.GetIP(conn.RemoteAddr())).WithField("filter", searchReq.Filter).WithField("baseDN", searchReq.BaseDN),
|
||||
id: rid,
|
||||
ctx: span.Context(),
|
||||
}
|
||||
@ -74,7 +74,7 @@ func (ls *LDAPServer) Search(bindDN string, searchReq ldap.SearchRequest, conn n
|
||||
}
|
||||
for _, provider := range ls.providers {
|
||||
providerBase, _ := goldap.ParseDN(provider.BaseDN)
|
||||
if providerBase.AncestorOf(bd) {
|
||||
if providerBase.AncestorOf(bd) || providerBase.Equal(bd) {
|
||||
return provider.Search(req)
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,9 @@ func ldapResolveTypeSingle(in interface{}) *string {
|
||||
|
||||
func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute {
|
||||
attrList := []*ldap.EntryAttribute{}
|
||||
if attrs == nil {
|
||||
return attrList
|
||||
}
|
||||
a := attrs.(*map[string]interface{})
|
||||
for attrKey, attrValue := range *a {
|
||||
entry := &ldap.EntryAttribute{Name: attrKey}
|
||||
|
@ -18,7 +18,7 @@ type OIDCEndpoint struct {
|
||||
func GetOIDCEndpoint(p api.ProxyOutpostConfig, authentikHost string) OIDCEndpoint {
|
||||
authUrl := p.OidcConfiguration.AuthorizationEndpoint
|
||||
endUrl := p.OidcConfiguration.EndSessionEndpoint
|
||||
if browserHost, found := os.LookupEnv("AUTHENTIK_HOST_BROWSER"); found {
|
||||
if browserHost, found := os.LookupEnv("AUTHENTIK_HOST_BROWSER"); found && browserHost != "" {
|
||||
host := os.Getenv("AUTHENTIK_HOST")
|
||||
authUrl = strings.ReplaceAll(authUrl, host, browserHost)
|
||||
endUrl = strings.ReplaceAll(endUrl, host, browserHost)
|
||||
|
@ -18,12 +18,22 @@ func GetStore(p api.ProxyOutpostConfig) sessions.Store {
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if p.TokenValidity.IsSet() {
|
||||
t := p.TokenValidity.Get()
|
||||
// Add one to the validity to ensure we don't have a session with indefinite length
|
||||
rs.Options.MaxAge = int(*t) + 1
|
||||
}
|
||||
rs.Options.Domain = *p.CookieDomain
|
||||
log.Info("using redis session backend")
|
||||
store = rs
|
||||
} else {
|
||||
cs := sessions.NewCookieStore([]byte(*p.CookieSecret))
|
||||
cs.Options.Domain = *p.CookieDomain
|
||||
if p.TokenValidity.IsSet() {
|
||||
t := p.TokenValidity.Get()
|
||||
// Add one to the validity to ensure we don't have a session with indefinite length
|
||||
cs.Options.MaxAge = int(*t) + 1
|
||||
}
|
||||
log.Info("using cookie session backend")
|
||||
store = cs
|
||||
}
|
||||
|
@ -45,6 +45,15 @@ func (ps *ProxyServer) Handle(rw http.ResponseWriter, r *http.Request) {
|
||||
host := web.GetHost(r)
|
||||
a, ok := ps.apps[host]
|
||||
if !ok {
|
||||
// If we only have one handler, host name switching doesn't matter
|
||||
if len(ps.apps) == 1 {
|
||||
ps.log.WithField("host", host).Warning("passing to single app mux")
|
||||
for k := range ps.apps {
|
||||
ps.apps[k].ServeHTTP(rw, r)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
ps.log.WithField("host", host).Warning("no app for hostname")
|
||||
rw.WriteHeader(400)
|
||||
return
|
||||
|
@ -36,13 +36,15 @@ func (ws *WebServer) configureStatic() {
|
||||
}
|
||||
statRouter.PathPrefix("/static/dist/").Handler(distHandler)
|
||||
statRouter.PathPrefix("/static/authentik/").Handler(authentikHandler)
|
||||
|
||||
// Prevent font-loading issues on safari, which loads fonts relatively to the URL the browser is on
|
||||
statRouter.PathPrefix("/if/flow/{flow_slug}/assets").HandlerFunc(func(rw http.ResponseWriter, r *http.Request) {
|
||||
vars := mux.Vars(r)
|
||||
|
||||
http.StripPrefix(fmt.Sprintf("/if/flow/%s", vars["flow_slug"]), distFs).ServeHTTP(rw, r)
|
||||
disableIndex(http.StripPrefix(fmt.Sprintf("/if/flow/%s", vars["flow_slug"]), distFs)).ServeHTTP(rw, r)
|
||||
})
|
||||
statRouter.PathPrefix("/if/admin/assets").Handler(http.StripPrefix("/if/admin", distFs))
|
||||
statRouter.PathPrefix("/if/admin/assets").Handler(disableIndex(http.StripPrefix("/if/admin", distFs)))
|
||||
statRouter.PathPrefix("/if/user/assets").Handler(disableIndex(http.StripPrefix("/if/user", distFs)))
|
||||
|
||||
statRouter.PathPrefix("/media/").Handler(http.StripPrefix("/media", fs))
|
||||
|
||||
|
25
schema.yml
25
schema.yml
@ -1,7 +1,7 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: authentik
|
||||
version: 2021.9.1
|
||||
version: 2021.9.3
|
||||
description: Making authentication simple.
|
||||
contact:
|
||||
email: hello@beryju.org
|
||||
@ -11395,7 +11395,7 @@ paths:
|
||||
/root/config/:
|
||||
get:
|
||||
operationId: root_config_retrieve
|
||||
description: Retrive public configuration options
|
||||
description: Retrieve public configuration options
|
||||
tags:
|
||||
- root
|
||||
security:
|
||||
@ -21580,6 +21580,7 @@ components:
|
||||
readOnly: true
|
||||
webhook_url:
|
||||
type: string
|
||||
format: uri
|
||||
webhook_mapping:
|
||||
type: string
|
||||
format: uuid
|
||||
@ -21609,6 +21610,7 @@ components:
|
||||
$ref: '#/components/schemas/NotificationTransportModeEnum'
|
||||
webhook_url:
|
||||
type: string
|
||||
format: uri
|
||||
webhook_mapping:
|
||||
type: string
|
||||
format: uuid
|
||||
@ -21906,7 +21908,7 @@ components:
|
||||
access_token_url:
|
||||
type: string
|
||||
nullable: true
|
||||
description: URL used by authentik to retrive tokens.
|
||||
description: URL used by authentik to retrieve tokens.
|
||||
maxLength: 255
|
||||
profile_url:
|
||||
type: string
|
||||
@ -21981,7 +21983,7 @@ components:
|
||||
access_token_url:
|
||||
type: string
|
||||
nullable: true
|
||||
description: URL used by authentik to retrive tokens.
|
||||
description: URL used by authentik to retrieve tokens.
|
||||
maxLength: 255
|
||||
profile_url:
|
||||
type: string
|
||||
@ -25808,6 +25810,7 @@ components:
|
||||
$ref: '#/components/schemas/NotificationTransportModeEnum'
|
||||
webhook_url:
|
||||
type: string
|
||||
format: uri
|
||||
webhook_mapping:
|
||||
type: string
|
||||
format: uuid
|
||||
@ -25934,7 +25937,7 @@ components:
|
||||
access_token_url:
|
||||
type: string
|
||||
nullable: true
|
||||
description: URL used by authentik to retrive tokens.
|
||||
description: URL used by authentik to retrieve tokens.
|
||||
maxLength: 255
|
||||
profile_url:
|
||||
type: string
|
||||
@ -26112,7 +26115,7 @@ components:
|
||||
description: Allow friends to authenticate, even if you don't share a server.
|
||||
plex_token:
|
||||
type: string
|
||||
description: Plex token used to check firends
|
||||
description: Plex token used to check friends
|
||||
PatchedPolicyBindingRequest:
|
||||
type: object
|
||||
description: PolicyBinding Serializer
|
||||
@ -26745,7 +26748,7 @@ components:
|
||||
description: Allow friends to authenticate, even if you don't share a server.
|
||||
plex_token:
|
||||
type: string
|
||||
description: Plex token used to check firends
|
||||
description: Plex token used to check friends
|
||||
required:
|
||||
- component
|
||||
- name
|
||||
@ -26840,7 +26843,7 @@ components:
|
||||
description: Allow friends to authenticate, even if you don't share a server.
|
||||
plex_token:
|
||||
type: string
|
||||
description: Plex token used to check firends
|
||||
description: Plex token used to check friends
|
||||
required:
|
||||
- name
|
||||
- plex_token
|
||||
@ -27375,11 +27378,17 @@ components:
|
||||
Exclusive with internal_host.
|
||||
cookie_domain:
|
||||
type: string
|
||||
token_validity:
|
||||
type: number
|
||||
format: float
|
||||
nullable: true
|
||||
readOnly: true
|
||||
required:
|
||||
- external_host
|
||||
- name
|
||||
- oidc_configuration
|
||||
- pk
|
||||
- token_validity
|
||||
ProxyProvider:
|
||||
type: object
|
||||
description: ProxyProvider Serializer
|
||||
|
@ -217,6 +217,7 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||
"objectClass": [
|
||||
"user",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"goauthentik.io/ldap/user",
|
||||
],
|
||||
"uidNumber": [str(2000 + outpost_user.pk)],
|
||||
@ -243,6 +244,7 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||
"objectClass": [
|
||||
"user",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"goauthentik.io/ldap/user",
|
||||
],
|
||||
"uidNumber": [str(2000 + embedded_account.pk)],
|
||||
@ -269,6 +271,7 @@ class TestProviderLDAP(SeleniumTestCase):
|
||||
"objectClass": [
|
||||
"user",
|
||||
"organizationalPerson",
|
||||
"inetOrgPerson",
|
||||
"goauthentik.io/ldap/user",
|
||||
],
|
||||
"uidNumber": [str(2000 + USER().pk)],
|
||||
|
@ -6,3 +6,5 @@ dist
|
||||
coverage
|
||||
# don't lint generated code
|
||||
api/
|
||||
# Import order matters
|
||||
poly.ts
|
||||
|
42
web/package-lock.json
generated
42
web/package-lock.json
generated
@ -15,7 +15,7 @@
|
||||
"@babel/preset-env": "^7.15.6",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@goauthentik/api": "^2021.9.1-rc3-1632158454",
|
||||
"@goauthentik/api": "^2021.9.2-1632578262",
|
||||
"@lingui/cli": "^3.11.1",
|
||||
"@lingui/core": "^3.11.1",
|
||||
"@lingui/macro": "^3.11.1",
|
||||
@ -50,8 +50,8 @@
|
||||
"lit": "^2.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"prettier": "^2.4.1",
|
||||
"rapidoc": "^9.1.2",
|
||||
"rollup": "^2.56.2",
|
||||
"rapidoc": "^9.1.3",
|
||||
"rollup": "^2.57.0",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-cssimport": "^1.0.2",
|
||||
@ -1691,9 +1691,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@goauthentik/api": {
|
||||
"version": "2021.9.1-rc3-1632158454",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.9.1-rc3-1632158454.tgz",
|
||||
"integrity": "sha512-oE+UT0/mDcGn11J/sekgsQRg72p4R0N0CdQXD2Q2pgT/+q0zR0PIkjFWjnue4X8VR4D426WFAGp7WdnVVdeo3Q=="
|
||||
"version": "2021.9.2-1632578262",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.9.2-1632578262.tgz",
|
||||
"integrity": "sha512-pOFIODBsWPCDA3z+ZSp5UPDb0/cvuj6uVbDzVkPUJxF/fymgvmBcdY8MTIKz/usLhL7qvPra1h4RZWITWVxa6g=="
|
||||
},
|
||||
"node_modules/@humanwhocodes/config-array": {
|
||||
"version": "0.5.0",
|
||||
@ -6808,9 +6808,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rapidoc": {
|
||||
"version": "9.1.2",
|
||||
"resolved": "https://registry.npmjs.org/rapidoc/-/rapidoc-9.1.2.tgz",
|
||||
"integrity": "sha512-tW0ainfTZrCs3CklurkcCcoN0tIVilWPoy8ueUNQnt4oyu30vWjShq6+3Il3th3i/UQ1DAozHnMhWXOGKtkb4g==",
|
||||
"version": "9.1.3",
|
||||
"resolved": "https://registry.npmjs.org/rapidoc/-/rapidoc-9.1.3.tgz",
|
||||
"integrity": "sha512-9Mx/V/yO7hlM6mo5a6of0uJpm9rFBMFvqtr0Zw34JVIP19DN+cuYvUlB0MLrekn2sq0nvpzOW6cNkBIC1QSYYg==",
|
||||
"dependencies": {
|
||||
"@apitools/openapi-parser": "^0.0.9",
|
||||
"base64-arraybuffer": "^1.0.1",
|
||||
@ -7080,9 +7080,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "2.56.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.3.tgz",
|
||||
"integrity": "sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg==",
|
||||
"version": "2.57.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.57.0.tgz",
|
||||
"integrity": "sha512-bKQIh1rWKofRee6mv8SrF2HdP6pea5QkwBZSMImJysFj39gQuiV8MEPBjXOCpzk3wSYp63M2v2wkWBmFC8O/rg==",
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
@ -9760,9 +9760,9 @@
|
||||
"integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg=="
|
||||
},
|
||||
"@goauthentik/api": {
|
||||
"version": "2021.9.1-rc3-1632158454",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.9.1-rc3-1632158454.tgz",
|
||||
"integrity": "sha512-oE+UT0/mDcGn11J/sekgsQRg72p4R0N0CdQXD2Q2pgT/+q0zR0PIkjFWjnue4X8VR4D426WFAGp7WdnVVdeo3Q=="
|
||||
"version": "2021.9.2-1632578262",
|
||||
"resolved": "https://registry.npmjs.org/@goauthentik/api/-/api-2021.9.2-1632578262.tgz",
|
||||
"integrity": "sha512-pOFIODBsWPCDA3z+ZSp5UPDb0/cvuj6uVbDzVkPUJxF/fymgvmBcdY8MTIKz/usLhL7qvPra1h4RZWITWVxa6g=="
|
||||
},
|
||||
"@humanwhocodes/config-array": {
|
||||
"version": "0.5.0",
|
||||
@ -13611,9 +13611,9 @@
|
||||
}
|
||||
},
|
||||
"rapidoc": {
|
||||
"version": "9.1.2",
|
||||
"resolved": "https://registry.npmjs.org/rapidoc/-/rapidoc-9.1.2.tgz",
|
||||
"integrity": "sha512-tW0ainfTZrCs3CklurkcCcoN0tIVilWPoy8ueUNQnt4oyu30vWjShq6+3Il3th3i/UQ1DAozHnMhWXOGKtkb4g==",
|
||||
"version": "9.1.3",
|
||||
"resolved": "https://registry.npmjs.org/rapidoc/-/rapidoc-9.1.3.tgz",
|
||||
"integrity": "sha512-9Mx/V/yO7hlM6mo5a6of0uJpm9rFBMFvqtr0Zw34JVIP19DN+cuYvUlB0MLrekn2sq0nvpzOW6cNkBIC1QSYYg==",
|
||||
"requires": {
|
||||
"@apitools/openapi-parser": "^0.0.9",
|
||||
"base64-arraybuffer": "^1.0.1",
|
||||
@ -13816,9 +13816,9 @@
|
||||
}
|
||||
},
|
||||
"rollup": {
|
||||
"version": "2.56.3",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.56.3.tgz",
|
||||
"integrity": "sha512-Au92NuznFklgQCUcV96iXlxUbHuB1vQMaH76DHl5M11TotjOHwqk9CwcrT78+Tnv4FN9uTBxq6p4EJoYkpyekg==",
|
||||
"version": "2.57.0",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.57.0.tgz",
|
||||
"integrity": "sha512-bKQIh1rWKofRee6mv8SrF2HdP6pea5QkwBZSMImJysFj39gQuiV8MEPBjXOCpzk3wSYp63M2v2wkWBmFC8O/rg==",
|
||||
"requires": {
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
|
@ -47,7 +47,7 @@
|
||||
"@babel/preset-env": "^7.15.6",
|
||||
"@babel/preset-typescript": "^7.15.0",
|
||||
"@fortawesome/fontawesome-free": "^5.15.4",
|
||||
"@goauthentik/api": "^2021.9.1-rc3-1632158454",
|
||||
"@goauthentik/api": "^2021.9.2-1632578262",
|
||||
"@lingui/cli": "^3.11.1",
|
||||
"@lingui/core": "^3.11.1",
|
||||
"@lingui/macro": "^3.11.1",
|
||||
@ -82,8 +82,8 @@
|
||||
"lit": "^2.0.0",
|
||||
"moment": "^2.29.1",
|
||||
"prettier": "^2.4.1",
|
||||
"rapidoc": "^9.1.2",
|
||||
"rollup": "^2.56.2",
|
||||
"rapidoc": "^9.1.3",
|
||||
"rollup": "^2.57.0",
|
||||
"rollup-plugin-commonjs": "^10.1.0",
|
||||
"rollup-plugin-copy": "^3.4.0",
|
||||
"rollup-plugin-cssimport": "^1.0.2",
|
||||
|
@ -1,7 +1,5 @@
|
||||
import "@webcomponents/webcomponentsjs";
|
||||
import "construct-style-sheets-polyfill";
|
||||
|
||||
import "lit/polyfill-support";
|
||||
|
||||
// @ts-ignore
|
||||
window["polymerSkipLoadingFontRoboto"] = true;
|
||||
import "construct-style-sheets-polyfill";
|
||||
import "@webcomponents/webcomponentsjs";
|
||||
import "lit/polyfill-support";
|
||||
|
@ -3,7 +3,7 @@ export const SUCCESS_CLASS = "pf-m-success";
|
||||
export const ERROR_CLASS = "pf-m-danger";
|
||||
export const PROGRESS_CLASS = "pf-m-in-progress";
|
||||
export const CURRENT_CLASS = "pf-m-current";
|
||||
export const VERSION = "2021.9.1";
|
||||
export const VERSION = "2021.9.3";
|
||||
export const PAGE_SIZE = 20;
|
||||
export const TITLE_DEFAULT = "authentik";
|
||||
export const ROUTE_SEPARATOR = ";";
|
||||
|
@ -7,12 +7,11 @@ import { SpinnerButton } from "./SpinnerButton";
|
||||
@customElement("ak-action-button")
|
||||
export class ActionButton extends SpinnerButton {
|
||||
@property({ attribute: false })
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
apiRequest: () => Promise<any> = () => {
|
||||
apiRequest: () => Promise<unknown> = () => {
|
||||
throw new Error();
|
||||
};
|
||||
|
||||
callAction = (): Promise<void> => {
|
||||
callAction = (): Promise<unknown> => {
|
||||
this.setLoading();
|
||||
return this.apiRequest().catch((e: Error | Response) => {
|
||||
if (e instanceof Error) {
|
||||
|
@ -15,7 +15,7 @@ export class SpinnerButton extends LitElement {
|
||||
isRunning = false;
|
||||
|
||||
@property()
|
||||
callAction?: () => Promise<void>;
|
||||
callAction?: () => Promise<unknown>;
|
||||
|
||||
static get styles(): CSSResult[] {
|
||||
return [
|
||||
|
@ -1,9 +1,11 @@
|
||||
import { html, TemplateResult } from "lit";
|
||||
import { customElement, property } from "lit/decorators";
|
||||
|
||||
import { CoreApi } from "@goauthentik/api";
|
||||
|
||||
import { DEFAULT_CONFIG } from "../../api/Config";
|
||||
import { ERROR_CLASS, SECONDARY_CLASS, SUCCESS_CLASS } from "../../constants";
|
||||
import { PFSize } from "../Spinner";
|
||||
import { ActionButton } from "./ActionButton";
|
||||
|
||||
@customElement("ak-token-copy-button")
|
||||
@ -27,21 +29,82 @@ export class TokenCopyButton extends ActionButton {
|
||||
if (!token.key) {
|
||||
return Promise.reject();
|
||||
}
|
||||
return navigator.clipboard.writeText(token.key).then(() => {
|
||||
this.buttonClass = SUCCESS_CLASS;
|
||||
setTimeout(() => {
|
||||
this.buttonClass = SECONDARY_CLASS;
|
||||
}, 1500);
|
||||
});
|
||||
setTimeout(() => {
|
||||
this.buttonClass = SECONDARY_CLASS;
|
||||
}, 1500);
|
||||
this.buttonClass = SUCCESS_CLASS;
|
||||
return token.key;
|
||||
})
|
||||
.catch((err: Response | undefined) => {
|
||||
.catch((err: Error | Response | undefined) => {
|
||||
this.buttonClass = ERROR_CLASS;
|
||||
return err?.json().then((errResp) => {
|
||||
throw new Error(errResp["detail"]);
|
||||
if (err instanceof Error) {
|
||||
setTimeout(() => {
|
||||
this.buttonClass = SECONDARY_CLASS;
|
||||
}, 1500);
|
||||
throw err;
|
||||
}
|
||||
return err?.json().then((errResp) => {
|
||||
setTimeout(() => {
|
||||
this.buttonClass = SECONDARY_CLASS;
|
||||
}, 1500);
|
||||
throw new Error(errResp["detail"]);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
render(): TemplateResult {
|
||||
return html`<button
|
||||
class="pf-c-button pf-m-progress ${this.classList.toString()}"
|
||||
@click=${() => {
|
||||
if (this.isRunning === true) {
|
||||
return;
|
||||
}
|
||||
this.setLoading();
|
||||
// Because safari is stupid, it only allows navigator.clipboard.write directly
|
||||
// in the @click handler.
|
||||
// And also chrome is stupid, because it doesn't accept Promises as values for
|
||||
// ClipboardItem, so now there's two implementations
|
||||
if (
|
||||
navigator.userAgent.includes("Safari") &&
|
||||
!navigator.userAgent.includes("Chrome")
|
||||
) {
|
||||
navigator.clipboard.write([
|
||||
new ClipboardItem({
|
||||
"text/plain": (this.callAction() as Promise<string>)
|
||||
.then((key: string) => {
|
||||
this.setDone(SUCCESS_CLASS);
|
||||
return new Blob([key], {
|
||||
type: "text/plain",
|
||||
});
|
||||
})
|
||||
.catch((err: Error) => {
|
||||
this.setDone(ERROR_CLASS);
|
||||
throw err;
|
||||
}),
|
||||
}),
|
||||
]);
|
||||
} else {
|
||||
(this.callAction() as Promise<string>)
|
||||
.then((key: string) => {
|
||||
navigator.clipboard.writeText(key).then(() => {
|
||||
this.setDone(SUCCESS_CLASS);
|
||||
});
|
||||
})
|
||||
.catch((err: Response | undefined) => {
|
||||
return err?.json().then((errResp) => {
|
||||
this.setDone(ERROR_CLASS);
|
||||
throw new Error(errResp["detail"]);
|
||||
});
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
${this.isRunning
|
||||
? html`<span class="pf-c-button__progress">
|
||||
<ak-spinner size=${PFSize.Medium}></ak-spinner>
|
||||
</span>`
|
||||
: ""}
|
||||
<slot></slot>
|
||||
</button>`;
|
||||
}
|
||||
}
|
||||
|
@ -199,41 +199,36 @@ export class Form<T> extends LitElement {
|
||||
);
|
||||
return r;
|
||||
})
|
||||
.catch((ex: Response | Error) => {
|
||||
.catch(async (ex: Response | Error) => {
|
||||
if (ex instanceof Error) {
|
||||
throw ex;
|
||||
}
|
||||
let msg = ex.statusText;
|
||||
if (ex.status > 399 && ex.status < 500) {
|
||||
return ex.json().then((errorMessage: ValidationError) => {
|
||||
if (!errorMessage) return errorMessage;
|
||||
if (errorMessage instanceof Error) {
|
||||
throw errorMessage;
|
||||
const errorMessage: ValidationError = await ex.json();
|
||||
if (!errorMessage) return errorMessage;
|
||||
if (errorMessage instanceof Error) {
|
||||
throw errorMessage;
|
||||
}
|
||||
// assign all input-related errors to their elements
|
||||
const elements: PaperInputElement[] = ironForm._getSubmittableElements();
|
||||
elements.forEach((element) => {
|
||||
const elementName = element.name;
|
||||
if (!elementName) return;
|
||||
if (camelToSnake(elementName) in errorMessage) {
|
||||
element.errorMessage =
|
||||
errorMessage[camelToSnake(elementName)].join(", ");
|
||||
element.invalid = true;
|
||||
}
|
||||
// assign all input-related errors to their elements
|
||||
const elements: PaperInputElement[] = ironForm._getSubmittableElements();
|
||||
elements.forEach((element) => {
|
||||
const elementName = element.name;
|
||||
if (!elementName) return;
|
||||
if (camelToSnake(elementName) in errorMessage) {
|
||||
element.errorMessage =
|
||||
errorMessage[camelToSnake(elementName)].join(", ");
|
||||
element.invalid = true;
|
||||
}
|
||||
});
|
||||
if ("non_field_errors" in errorMessage) {
|
||||
this.nonFieldErrors = errorMessage["non_field_errors"];
|
||||
}
|
||||
throw new APIError(errorMessage);
|
||||
});
|
||||
}
|
||||
throw ex;
|
||||
})
|
||||
.catch((ex: Error) => {
|
||||
let msg = ex.toString();
|
||||
// Only change the message when we have `detail`.
|
||||
// Everything else is handled in the form.
|
||||
if (ex instanceof APIError && "detail" in ex.response) {
|
||||
msg = ex.response.detail;
|
||||
if ("non_field_errors" in errorMessage) {
|
||||
this.nonFieldErrors = errorMessage["non_field_errors"];
|
||||
}
|
||||
// Only change the message when we have `detail`.
|
||||
// Everything else is handled in the form.
|
||||
if ("detail" in errorMessage) {
|
||||
msg = errorMessage.detail;
|
||||
}
|
||||
}
|
||||
// error is local or not from rest_framework
|
||||
showMessage({
|
||||
|
@ -15,7 +15,7 @@ import { EVENT_SIDEBAR_TOGGLE } from "../../constants";
|
||||
import { first } from "../../utils";
|
||||
|
||||
// If the viewport is wider than MIN_WIDTH, the sidebar
|
||||
// is shown besides the content, and not overlayed.
|
||||
// is shown besides the content, and not overlaid.
|
||||
export const MIN_WIDTH = 1200;
|
||||
|
||||
export const DefaultTenant: CurrentTenant = {
|
||||
|
@ -17,6 +17,7 @@ import PFBase from "@patternfly/patternfly/patternfly-base.css";
|
||||
import { CurrentTenant, EventsApi } from "@goauthentik/api";
|
||||
|
||||
import { DEFAULT_CONFIG, tenant } from "../api/Config";
|
||||
import { configureSentry } from "../api/Sentry";
|
||||
import { me } from "../api/Users";
|
||||
import { WebsocketClient } from "../common/ws";
|
||||
import {
|
||||
@ -78,6 +79,7 @@ export class UserInterface extends LitElement {
|
||||
}
|
||||
.pf-c-brand {
|
||||
min-height: 48px;
|
||||
height: 48px;
|
||||
}
|
||||
.has-notifications {
|
||||
color: #2b9af3;
|
||||
@ -99,6 +101,7 @@ export class UserInterface extends LitElement {
|
||||
this.firstUpdated();
|
||||
});
|
||||
tenant().then((tenant) => (this.tenant = tenant));
|
||||
configureSentry(true);
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
|
@ -605,8 +605,8 @@ msgid "Certificate Fingerprint (SHA256)"
|
||||
msgstr "Certificate Fingerprint (SHA256)"
|
||||
|
||||
#: src/pages/crypto/CertificateKeyPairListPage.ts
|
||||
msgid "Certificate Subjet"
|
||||
msgstr "Certificate Subjet"
|
||||
msgid "Certificate Subject"
|
||||
msgstr "Certificate Subject"
|
||||
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
msgid "Certificate used to sign outgoing Responses going to the Service Provider."
|
||||
@ -2331,8 +2331,8 @@ msgstr "Link to a user with identical email address. Can have security implicati
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||
msgid "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
||||
msgstr "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
||||
msgid "Link to a user with identical username. Can have security implications when a username is used with another source."
|
||||
msgstr "Link to a user with identical username. Can have security implications when a username is used with another source."
|
||||
|
||||
#: src/pages/stages/invitation/InvitationListLink.ts
|
||||
msgid "Link to use the invitation."
|
||||
@ -3659,8 +3659,8 @@ msgid "Select users to add"
|
||||
msgstr "Select users to add"
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Select which scopes can be used by the client. The client stil has to specify the scope to access the data."
|
||||
msgstr "Select which scopes can be used by the client. The client stil has to specify the scope to access the data."
|
||||
msgid "Select which scopes can be used by the client. The client still has to specify the scope to access the data."
|
||||
msgstr "Select which scopes can be used by the client. The client still has to specify the scope to access the data."
|
||||
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||
msgid "Select which server a user has to be a member of to be allowed to authenticate."
|
||||
@ -4552,6 +4552,14 @@ msgstr "Title"
|
||||
msgid "To"
|
||||
msgstr "To"
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
msgid "To create a recovery link, the current tenant needs to have a recovery flow configured."
|
||||
msgstr "To create a recovery link, the current tenant needs to have a recovery flow configured."
|
||||
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "To directly reset a user's password, configure a recovery flow on the currently active tenant."
|
||||
msgstr "To directly reset a user's password, configure a recovery flow on the currently active tenant."
|
||||
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
msgid "To use SSL instead, use 'ldaps://' and disable this option."
|
||||
msgstr "To use SSL instead, use 'ldaps://' and disable this option."
|
||||
|
@ -601,7 +601,7 @@ msgid "Certificate Fingerprint (SHA256)"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/crypto/CertificateKeyPairListPage.ts
|
||||
msgid "Certificate Subjet"
|
||||
msgid "Certificate Subject"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/saml/SAMLProviderForm.ts
|
||||
@ -2323,7 +2323,7 @@ msgstr ""
|
||||
|
||||
#: src/pages/sources/oauth/OAuthSourceForm.ts
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||
msgid "Link to a user with identical username address. Can have security implications when a username is used with another source."
|
||||
msgid "Link to a user with identical username. Can have security implications when a username is used with another source."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/stages/invitation/InvitationListLink.ts
|
||||
@ -3651,7 +3651,7 @@ msgid "Select users to add"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/providers/oauth2/OAuth2ProviderForm.ts
|
||||
msgid "Select which scopes can be used by the client. The client stil has to specify the scope to access the data."
|
||||
msgid "Select which scopes can be used by the client. The client still has to specify the scope to access the data."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/plex/PlexSourceForm.ts
|
||||
@ -4537,6 +4537,14 @@ msgstr ""
|
||||
msgid "To"
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserViewPage.ts
|
||||
msgid "To create a recovery link, the current tenant needs to have a recovery flow configured."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/users/UserListPage.ts
|
||||
msgid "To directly reset a user's password, configure a recovery flow on the currently active tenant."
|
||||
msgstr ""
|
||||
|
||||
#: src/pages/sources/ldap/LDAPSourceForm.ts
|
||||
msgid "To use SSL instead, use 'ldaps://' and disable this option."
|
||||
msgstr ""
|
||||
|
@ -137,7 +137,7 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Certificate Subjet`}</span
|
||||
>${t`Certificate Subject`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
|
@ -20,9 +20,14 @@ import { first } from "../../utils";
|
||||
@customElement("ak-event-transport-form")
|
||||
export class TransportForm extends ModelForm<NotificationTransport, string> {
|
||||
loadInstance(pk: string): Promise<NotificationTransport> {
|
||||
return new EventsApi(DEFAULT_CONFIG).eventsTransportsRetrieve({
|
||||
uuid: pk,
|
||||
});
|
||||
return new EventsApi(DEFAULT_CONFIG)
|
||||
.eventsTransportsRetrieve({
|
||||
uuid: pk,
|
||||
})
|
||||
.then((transport) => {
|
||||
this.onModeChange(transport.mode);
|
||||
return transport;
|
||||
});
|
||||
}
|
||||
|
||||
@property({ type: Boolean })
|
||||
@ -72,12 +77,6 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
||||
`;
|
||||
}
|
||||
|
||||
firstUpdated(): void {
|
||||
if (this.instance) {
|
||||
this.onModeChange(this.instance.mode);
|
||||
}
|
||||
}
|
||||
|
||||
onModeChange(mode: string): void {
|
||||
if (
|
||||
mode === NotificationTransportModeEnum.Webhook ||
|
||||
@ -114,6 +113,7 @@ export class TransportForm extends ModelForm<NotificationTransport, string> {
|
||||
?hidden=${!this.showWebhook}
|
||||
label=${t`Webhook URL`}
|
||||
name="webhookUrl"
|
||||
?required=${true}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
|
@ -104,7 +104,7 @@ export class StageBindingForm extends ModelForm<FlowStageBinding, string> {
|
||||
})
|
||||
.then((flows) => {
|
||||
return flows.results.map((flow) => {
|
||||
// No ?selected check here, as this input isnt shown on update forms
|
||||
// No ?selected check here, as this input isn't shown on update forms
|
||||
return html`<option value=${ifDefined(flow.pk)}>
|
||||
${flow.name} (${flow.slug})
|
||||
</option>`;
|
||||
|
@ -42,6 +42,7 @@ export class OutpostDeploymentModal extends ModalButton {
|
||||
</label>
|
||||
<div>
|
||||
<ak-token-copy-button
|
||||
class="pf-m-primary"
|
||||
identifier="${ifDefined(this.outpost?.tokenIdentifier)}"
|
||||
>
|
||||
${t`Click to copy token`}
|
||||
|
@ -14,7 +14,7 @@ import { ModelForm } from "../../elements/forms/ModelForm";
|
||||
@customElement("ak-property-mapping-notification-form")
|
||||
export class PropertyMappingNotification extends ModelForm<NotificationWebhookMapping, string> {
|
||||
loadInstance(pk: string): Promise<NotificationWebhookMapping> {
|
||||
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsNotificationRetrieveRaw({
|
||||
return new PropertymappingsApi(DEFAULT_CONFIG).propertymappingsNotificationRetrieve({
|
||||
pmUuid: pk,
|
||||
});
|
||||
}
|
||||
|
@ -277,7 +277,7 @@ ${this.instance?.redirectUris}</textarea
|
||||
)}
|
||||
</select>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Select which scopes can be used by the client. The client stil has to specify the scope to access the data.`}
|
||||
${t`Select which scopes can be used by the client. The client still has to specify the scope to access the data.`}
|
||||
</p>
|
||||
<p class="pf-c-form__helper-text">
|
||||
${t`Hold control/command to select multiple items.`}
|
||||
|
@ -219,7 +219,7 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||
?selected=${this.instance?.userMatchingMode ===
|
||||
UserMatchingModeEnum.UsernameLink}
|
||||
>
|
||||
${t`Link to a user with identical username address. Can have security implications when a username is used with another source.`}
|
||||
${t`Link to a user with identical username. Can have security implications when a username is used with another source.`}
|
||||
</option>
|
||||
<option
|
||||
value=${UserMatchingModeEnum.UsernameDeny}
|
||||
|
@ -204,7 +204,7 @@ export class PlexSourceForm extends ModelForm<PlexSource, string> {
|
||||
?selected=${this.instance?.userMatchingMode ===
|
||||
UserMatchingModeEnum.UsernameLink}
|
||||
>
|
||||
${t`Link to a user with identical username address. Can have security implications when a username is used with another source.`}
|
||||
${t`Link to a user with identical username. Can have security implications when a username is used with another source.`}
|
||||
</option>
|
||||
<option
|
||||
value=${UserMatchingModeEnum.UsernameDeny}
|
||||
|
@ -74,7 +74,7 @@ export class UserWriteStageForm extends ModelForm<UserWriteStage, string> {
|
||||
${t`Mark newly created users as inactive.`}
|
||||
</p>
|
||||
</ak-form-element-horizontal>
|
||||
<ak-form-element-horizontal label=${t`Group`} name="searchGroup">
|
||||
<ak-form-element-horizontal label=${t`Group`} name="createUsersGroup">
|
||||
<select class="pf-c-form-control">
|
||||
<option
|
||||
value=""
|
||||
|
@ -171,72 +171,78 @@ export class UserListPage extends TablePage<User> {
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
${until(
|
||||
tenant().then((te) => {
|
||||
if (!te.flowRecovery) {
|
||||
return html``;
|
||||
}
|
||||
return html`<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text"
|
||||
>${t`Recovery`}</span
|
||||
>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
<ak-action-button
|
||||
.apiRequest=${() => {
|
||||
return new CoreApi(DEFAULT_CONFIG)
|
||||
.coreUsersRecoveryRetrieve({
|
||||
id: item.pk || 0,
|
||||
})
|
||||
.then((rec) => {
|
||||
showMessage({
|
||||
level: MessageLevel.success,
|
||||
message: t`Successfully generated recovery link`,
|
||||
description: rec.link,
|
||||
});
|
||||
})
|
||||
.catch((ex: Response) => {
|
||||
ex.json().then(() => {
|
||||
<div class="pf-c-description-list__group">
|
||||
<dt class="pf-c-description-list__term">
|
||||
<span class="pf-c-description-list__text">${t`Recovery`}</span>
|
||||
</dt>
|
||||
<dd class="pf-c-description-list__description">
|
||||
<div class="pf-c-description-list__text">
|
||||
${until(
|
||||
tenant().then((tenant) => {
|
||||
if (!tenant.flowRecovery) {
|
||||
return html`
|
||||
<p>
|
||||
${t`To directly reset a user's password, configure a recovery flow on the currently active tenant.`}
|
||||
</p>
|
||||
`;
|
||||
}
|
||||
return html`
|
||||
<ak-action-button
|
||||
.apiRequest=${() => {
|
||||
return new CoreApi(DEFAULT_CONFIG)
|
||||
.coreUsersRecoveryRetrieve({
|
||||
id: item.pk || 0,
|
||||
})
|
||||
.then((rec) => {
|
||||
showMessage({
|
||||
level: MessageLevel.error,
|
||||
message: t`No recovery flow is configured.`,
|
||||
level: MessageLevel.success,
|
||||
message: t`Successfully generated recovery link`,
|
||||
description: rec.link,
|
||||
});
|
||||
})
|
||||
.catch((ex: Response) => {
|
||||
ex.json().then(() => {
|
||||
showMessage({
|
||||
level: MessageLevel.error,
|
||||
message: t`No recovery flow is configured.`,
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
${t`Copy recovery link`}
|
||||
</ak-action-button>
|
||||
${item.email
|
||||
? html`<ak-forms-modal
|
||||
.closeAfterSuccessfulSubmit=${false}
|
||||
>
|
||||
<span slot="submit"> ${t`Send link`} </span>
|
||||
<span slot="header">
|
||||
${t`Send recovery link to user`}
|
||||
</span>
|
||||
<ak-user-reset-email-form
|
||||
slot="form"
|
||||
.user=${item}
|
||||
}}
|
||||
>
|
||||
${t`Copy recovery link`}
|
||||
</ak-action-button>
|
||||
${item.email
|
||||
? html`<ak-forms-modal
|
||||
.closeAfterSuccessfulSubmit=${false}
|
||||
>
|
||||
</ak-user-reset-email-form>
|
||||
<button
|
||||
slot="trigger"
|
||||
class="pf-c-button pf-m-secondary"
|
||||
>
|
||||
${t`Email recovery link`}
|
||||
</button>
|
||||
</ak-forms-modal>`
|
||||
: html`<span
|
||||
>${t`Recovery link cannot be emailed, user has no email address saved.`}</span
|
||||
>`}
|
||||
</div>
|
||||
</dd>
|
||||
</div>`;
|
||||
}),
|
||||
)}
|
||||
<span slot="submit">
|
||||
${t`Send link`}
|
||||
</span>
|
||||
<span slot="header">
|
||||
${t`Send recovery link to user`}
|
||||
</span>
|
||||
<ak-user-reset-email-form
|
||||
slot="form"
|
||||
.user=${item}
|
||||
>
|
||||
</ak-user-reset-email-form>
|
||||
<button
|
||||
slot="trigger"
|
||||
class="pf-c-button pf-m-secondary"
|
||||
>
|
||||
${t`Email recovery link`}
|
||||
</button>
|
||||
</ak-forms-modal>`
|
||||
: html`<span
|
||||
>${t`Recovery link cannot be emailed, user has no email address saved.`}</span
|
||||
>`}
|
||||
`;
|
||||
}),
|
||||
)}
|
||||
</div>
|
||||
</dd>
|
||||
</div>
|
||||
</dl>
|
||||
</div>
|
||||
</td>
|
||||
|
@ -197,6 +197,7 @@ export class UserViewPage extends LitElement {
|
||||
</div>
|
||||
<div class="pf-c-card__footer">
|
||||
<ak-action-button
|
||||
class="pf-m-secondary"
|
||||
.apiRequest=${() => {
|
||||
return new CoreApi(DEFAULT_CONFIG)
|
||||
.coreUsersRecoveryRetrieve({
|
||||
@ -208,6 +209,13 @@ export class UserViewPage extends LitElement {
|
||||
message: t`Successfully generated recovery link`,
|
||||
description: rec.link,
|
||||
});
|
||||
})
|
||||
.catch(() => {
|
||||
showMessage({
|
||||
level: MessageLevel.error,
|
||||
message: t`To create a recovery link, the current tenant needs to have a recovery flow configured.`,
|
||||
description: "",
|
||||
});
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
@ -29,6 +29,9 @@ export class LibraryPage extends LitElement {
|
||||
@property({ attribute: false })
|
||||
selectedApp?: Application;
|
||||
|
||||
@property()
|
||||
query?: string;
|
||||
|
||||
fuse?: Fuse<Application>;
|
||||
|
||||
constructor() {
|
||||
@ -38,6 +41,10 @@ export class LibraryPage extends LitElement {
|
||||
this.fuse = new Fuse(apps.results, {
|
||||
keys: ["slug", "name"],
|
||||
});
|
||||
if (!this.fuse || !this.query) return;
|
||||
const matchingApps = this.fuse.search(this.query);
|
||||
if (matchingApps.length < 1) return;
|
||||
this.selectedApp = matchingApps[0].item;
|
||||
});
|
||||
}
|
||||
|
||||
@ -116,9 +123,9 @@ export class LibraryPage extends LitElement {
|
||||
${config.enabledFeatures.search
|
||||
? html`<input
|
||||
@input=${(ev: InputEvent) => {
|
||||
const query = (ev.target as HTMLInputElement).value;
|
||||
this.query = (ev.target as HTMLInputElement).value;
|
||||
if (!this.fuse) return;
|
||||
const apps = this.fuse.search(query);
|
||||
const apps = this.fuse.search(this.query);
|
||||
if (apps.length < 1) return;
|
||||
this.selectedApp = apps[0].item;
|
||||
}}
|
||||
|
@ -25,7 +25,7 @@ log_level: debug
|
||||
secret_key: "A long key you can generate with `pwgen 40 1` for example"
|
||||
```
|
||||
|
||||
Afterwards, you can start authentik by running `make run`.
|
||||
Afterwards, you can start authentik by running `make run`. authentik is now accessible under `localhost:9000`.
|
||||
|
||||
Generally speaking, authentik is a Django application, ran by gunicorn, proxied by a Go application. The Go application serves static files.
|
||||
|
||||
@ -37,15 +37,7 @@ Run `make gen` to generate an updated OpenAPI document for any changes you made.
|
||||
|
||||
## Frontend
|
||||
|
||||
By default, no transpiled bundle of the frontend is included. To build the UI, you need Node 12 or newer.
|
||||
|
||||
The Frontend also uses a generated API client to talk with the backend. To generate this client, [openapitools/openapi-generator-cli](https://github.com/OpenAPITools/openapi-generator) is used.
|
||||
|
||||
If you want to generate the client without installing anything, run this command:
|
||||
|
||||
```shell
|
||||
make gen-web
|
||||
```
|
||||
By default, no compiled bundle of the frontend is included. To build the UI, you need Node 14 or newer.
|
||||
|
||||
To build the UI, run these commands:
|
||||
|
||||
|
@ -6,6 +6,10 @@ title: Example Flows
|
||||
You can apply theses flows multiple times to stay updated, however this will discard all changes you've made.
|
||||
:::
|
||||
|
||||
:::info
|
||||
The example flows provided below will **override** the default flows, please review the contents of the example flow before importing and consider exporting the affected existing flows first.
|
||||
:::
|
||||
|
||||
## Enrollment (2 Stage)
|
||||
|
||||
Flow: right-click [here](/flows/enrollment-2-stage.akflow) and save the file.
|
||||
|
@ -48,14 +48,14 @@ To use an S3-compatible storage, set the following settings.
|
||||
- `AUTHENTIK_REDIS__OUTPOST_SESSION_DB`: Database for sessions for the embedded outpost, defaults to 3
|
||||
- `AUTHENTIK_REDIS__CACHE_TIMEOUT`: Timeout for cached data until it expires in seconds, defaults to 300
|
||||
- `AUTHENTIK_REDIS__CACHE_TIMEOUT_FLOWS`: Timeout for cached flow plans until they expire in seconds, defaults to 300
|
||||
- `AUTHENTIK_REDIS__CACHE_TIMEOUT_POLICIES`: Timeout for cached polices until they expire in seconds, defaults to 300
|
||||
- `AUTHENTIK_REDIS__CACHE_TIMEOUT_POLICIES`: Timeout for cached policies until they expire in seconds, defaults to 300
|
||||
- `AUTHENTIK_REDIS__CACHE_TIMEOUT_REPUTATION`: Timeout for cached reputation until they expire in seconds, defaults to 300
|
||||
|
||||
## authentik Settings
|
||||
|
||||
### AUTHENTIK_SECRET_KEY
|
||||
|
||||
Secret key used for cookie singing and unique user IDs, don't change this after the first install.
|
||||
Secret key used for cookie signing and unique user IDs, don't change this after the first install.
|
||||
|
||||
### AUTHENTIK_LOG_LEVEL
|
||||
|
||||
|
@ -12,9 +12,9 @@ This installation method is for test-setups and small-scale productive setups.
|
||||
|
||||
## Preparation
|
||||
|
||||
Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/goauthentik/authentik/version/2021.9.1/docker-compose.yml). Place it in a directory of your choice.
|
||||
Download the latest `docker-compose.yml` from [here](https://raw.githubusercontent.com/goauthentik/authentik/version/2021.9.3/docker-compose.yml). Place it in a directory of your choice.
|
||||
|
||||
To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.9.1 >> .env`
|
||||
To optionally deploy a different version run `echo AUTHENTIK_TAG=2021.9.3 >> .env`
|
||||
|
||||
If this is a fresh authentik install run the following commands to generate a password:
|
||||
|
||||
@ -31,7 +31,7 @@ echo "AUTHENTIK_ERROR_REPORTING__ENABLED=true" >> .env
|
||||
|
||||
## Email configuration (optional, but recommended)
|
||||
|
||||
It is also recommended to configure global email credentials. These are used by authentik to notify you about alerts, configuration issues. They can also be used by [Email stages](flow/stages/email/index.md) to send verification/recovery emails.
|
||||
It is also recommended to configure global email credentials. These are used by authentik to notify you about alerts and configuration issues. They can also be used by [Email stages](flow/stages/email/index.md) to send verification/recovery emails.
|
||||
|
||||
Append this block to your `.env` file
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user