root: Merge pull request #886 from goauthentik/openapi-v3

OpenAPI v3
This commit is contained in:
Jens L 2021-05-17 21:25:07 +02:00 committed by GitHub
commit e177ab33e0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
197 changed files with 25220 additions and 20509 deletions

View File

@ -30,7 +30,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: prepare ts api client
run: |
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/api --additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0
make gen-web
- name: Building Docker Image
uses: docker/build-push-action@v2
with:
@ -51,9 +51,8 @@ jobs:
go-version: "^1.15"
- name: prepare go api client
run: |
make gen-outpost
cd outpost
go get -u github.com/go-swagger/go-swagger/cmd/swagger
swagger generate client -f ../swagger.yaml -A authentik -t pkg/
go build -v ./cmd/proxy/server.go
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.1.0
@ -91,9 +90,8 @@ jobs:
go-version: "^1.15"
- name: prepare go api client
run: |
make gen-outpost
cd outpost
go get -u github.com/go-swagger/go-swagger/cmd/swagger
swagger generate client -f ../swagger.yaml -A authentik -t pkg/
go build -v ./cmd/ldap/server.go
- name: Set up QEMU
uses: docker/setup-qemu-action@v1.1.0

View File

@ -13,7 +13,7 @@ jobs:
- uses: actions/checkout@v2
- name: prepare ts api client
run: |
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/api --additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0
make gen-web
- name: Pre-release test
run: |
sudo apt-get install -y pwgen

View File

@ -1,5 +1,7 @@
.SHELLFLAGS += -x -e
PWD = $(shell pwd)
UID = $(shell id -u)
GID = $(shell id -g)
all: lint-fix lint test gen
@ -25,16 +27,35 @@ lint:
bandit -r authentik tests lifecycle -x node_modules
pylint authentik tests lifecycle
gen:
./manage.py generate_swagger -o swagger.yaml -f yaml
gen-build:
./manage.py spectacular --file schema.yml
gen-web:
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
openapitools/openapi-generator-cli generate \
-i /local/swagger.yaml \
-i /local/schema.yml \
-g typescript-fetch \
-o /local/web/api \
--additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0
cd web/api && npx tsc
gen-outpost:
docker run \
--rm -v ${PWD}:/local \
--user ${UID}:${GID} \
openapitools/openapi-generator-cli generate \
--git-host goauthentik.io \
--git-repo-id outpost \
--git-user-id api \
-i /local/schema.yml \
-g go \
-o /local/outpost/api \
--additional-properties=packageName=api,enumClassPrefix=true
rm -f outpost/api/go.mod outpost/api/go.sum
gen: gen-build gen-web gen-outpost
run:
go run -v cmd/server/main.go

View File

@ -22,7 +22,7 @@ django-storages = "*"
djangorestframework = "*"
djangorestframework-guardian = "*"
docker = "*"
drf_yasg = "*"
drf-spectacular = "*"
facebook-sdk = "*"
geoip2 = "*"
gunicorn = "*"

206
Pipfile.lock generated
View File

@ -1,7 +1,7 @@
{
"_meta": {
"hash": {
"sha256": "8a32708c1c04f8da03c817df973de28c37c97ee773f571ce0b3f3f834e1b7094"
"sha256": "61354b75aa954ea0a995ee1909b861092a4be5c1af66d3c00c7c7845e056d064"
},
"pipfile-spec": 6,
"requires": {
@ -56,6 +56,7 @@
"sha256:f881853d2643a29e643609da57b96d5f9c9b93f62429dcc1cbb413c7d07f0e1a",
"sha256:fe60131d21b31fd1a14bd43e6bb88256f69dfc3188b3a89d736d6c71ed43ec95"
],
"markers": "python_version >= '3.6'",
"version": "==3.7.4.post0"
},
"aioredis": {
@ -70,6 +71,7 @@
"sha256:03e16e94f2b34c31f8bf1206d8ddd3ccaa4c315f7f6a1879b7b1210d229568c2",
"sha256:493a2ac6788ce270a2f6a765b017299f60c1998f5a8617908ee9be082f7300fb"
],
"markers": "python_version >= '3.6'",
"version": "==5.0.6"
},
"asgiref": {
@ -77,6 +79,7 @@
"sha256:92906c611ce6c967347bbfea733f13d6313901d54dcca88195eaeb52b2a8e8ee",
"sha256:d1216dfbdfb63826470995d31caed36225dcaf34f182e0fa257a4dd9e86f1b78"
],
"markers": "python_version >= '3.6'",
"version": "==3.3.4"
},
"async-timeout": {
@ -84,6 +87,7 @@
"sha256:0c3c816a028d47f659d6ff5c745cb2acf1f966da1fe5c19c77a70282b25f4c5f",
"sha256:4291ca197d287d274d0b6cb5d6f8f8f82d434ed288f962539ff18cc9012f9ea3"
],
"markers": "python_full_version >= '3.5.3'",
"version": "==3.0.1"
},
"attrs": {
@ -91,6 +95,7 @@
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0"
},
"autobahn": {
@ -98,6 +103,7 @@
"sha256:9195df8af03b0ff29ccd4b7f5abbde957ee90273465942205f9a1bad6c3f07ac",
"sha256:e126c1f583e872fb59e79d36977cfa1f2d0a8a79f90ae31f406faae7664b8e03"
],
"markers": "python_version >= '3.7'",
"version": "==21.3.1"
},
"automat": {
@ -127,6 +133,7 @@
"sha256:4b4aa58c61d4b125bc6ec1597924b2749e19de8f2c9a374ac087aa2561e71828",
"sha256:69dc0b6fdc0855f5a4f8b1d29c96b9cec44e71054fea0f968e5904d6ccfd4fd9"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.20.73"
},
"cachetools": {
@ -134,6 +141,7 @@
"sha256:2cc0b89715337ab6dbba85b5b50effe2b0c74e035d83ee8ed637cf52f12ae001",
"sha256:61b5ed1e22a0924aed1d23b478f37e8d52549ff8a961de2909c69bf950020cff"
],
"markers": "python_version ~= '3.5'",
"version": "==4.2.2"
},
"cbor2": {
@ -220,6 +228,7 @@
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0"
},
"click": {
@ -227,6 +236,7 @@
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2"
},
"click-didyoumean": {
@ -256,20 +266,6 @@
],
"version": "==15.1.0"
},
"coreapi": {
"hashes": [
"sha256:46145fcc1f7017c076a2ef684969b641d18a2991051fddec9458ad3f78ffc1cb",
"sha256:bf39d118d6d3e171f10df9ede5666f63ad80bba9a29a8ec17726a66cf52ee6f3"
],
"version": "==2.3.3"
},
"coreschema": {
"hashes": [
"sha256:5e6ef7bf38c1525d5e55a895934ab4273548629f16aed5c0a6caa74ebf45551f",
"sha256:9503506007d482ab0867ba14724b93c18a33b22b6d19fb419ef2d239dd4a1607"
],
"version": "==0.0.4"
},
"cryptography": {
"hashes": [
"sha256:0f1212a66329c80d68aeeb39b8a16d54ef57071bf22ff4e521657b27372e327d",
@ -300,6 +296,7 @@
"sha256:76ffae916ba3aa66b46996c14fa713e46004788167a4873d647544e750e0e99f",
"sha256:a9af943c79717bc52fe64a3c236ae5d3adccc8b5be19c881b442d2c3db233393"
],
"markers": "python_version >= '3.6'",
"version": "==3.0.2"
},
"defusedxml": {
@ -402,13 +399,13 @@
"index": "pypi",
"version": "==5.0.0"
},
"drf-yasg": {
"drf-spectacular": {
"hashes": [
"sha256:8b72e5b1875931a8d11af407be3a9a5ba8776541492947a0df5bafda6b7f8267",
"sha256:d50f197c7f02545d0b736df88c6d5cf874f8fea2507ad85ad7de6ae5bf2d9e5a"
"sha256:4a77c233c99e028b8905cd2cf05388524838ade97b95d5c6e4861e0c3c95af31",
"sha256:d7f64b97e8940b143a0e8d1de20523e8d7d5fe8ec557aec574513282c413b0b3"
],
"index": "pypi",
"version": "==1.20.0"
"version": "==0.16.0"
},
"facebook-sdk": {
"hashes": [
@ -422,6 +419,7 @@
"hashes": [
"sha256:b1bead90b70cf6ec3f0710ae53a525360fa360d306a86583adc6bf83a4db537d"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.18.2"
},
"geoip2": {
@ -437,6 +435,7 @@
"sha256:588bdb03a41ecb4978472b847881e5518b5d9ec6153d3d679aa127a55e13b39f",
"sha256:9ad25fba07f46a628ad4d0ca09f38dcb262830df2ac95b217f9b0129c9e42206"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.30.0"
},
"gunicorn": {
@ -452,6 +451,7 @@
"sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6",
"sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042"
],
"markers": "python_version >= '3.6'",
"version": "==0.12.0"
},
"hiredis": {
@ -498,6 +498,7 @@
"sha256:f52010e0a44e3d8530437e7da38d11fb822acfb0d5b12e9cd5ba655509937ca0",
"sha256:f8196f739092a78e4f6b1b2172679ed3343c39c61a3e9d722ce6fcf1dac2824a"
],
"markers": "python_version >= '3.6'",
"version": "==2.0.0"
},
"httptools": {
@ -546,27 +547,15 @@
"sha256:1a29730d366e996aaacffb2f1f1cb9593dc38e2ddd30c91250c6dde09ea9b417",
"sha256:f38b2b640938a4f35ade69ac3d053042959b62a0f1076a5bbaa1b9526605a8a2"
],
"markers": "python_version >= '3.5'",
"version": "==0.5.1"
},
"itypes": {
"hashes": [
"sha256:03da6872ca89d29aef62773672b2d408f490f80db48b23079a4b194c86dd04c6",
"sha256:af886f129dea4a2a1e3d36595a2d139589e4dd287f5cab0b40e799ee81570ff1"
],
"version": "==1.2.0"
},
"jinja2": {
"hashes": [
"sha256:2f2de5285cf37f33d33ecd4a9080b75c87cd0c1994d5a9c6df17131ea1f049c6",
"sha256:ea8d7dd814ce9df6de6a761ec7f1cac98afe305b8cdc4aaae4e114b8d8ce24c5"
],
"version": "==3.0.0"
},
"jmespath": {
"hashes": [
"sha256:b85d0567b8666149a93172712e68920734333c0ce7e89b78b3e987f71e5ed4f9",
"sha256:cdf6525904cc597730141d61b36f2e4b8ecc257c420fa2f4549bac2c2d0cb72f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.0"
},
"jsonschema": {
@ -581,6 +570,7 @@
"sha256:6dc509178ac4269b0e66ab4881f70a2035c33d3a622e20585f965986a5182006",
"sha256:f4965fba0a4718d47d470beeb5d6446e3357a62402b16c510b6a2f251e05ac3c"
],
"markers": "python_version >= '3.6'",
"version": "==5.0.2"
},
"kubernetes": {
@ -593,7 +583,10 @@
},
"ldap3": {
"hashes": [
"sha256:8c949edbad2be8a03e719ba48bd6779f327ec156929562814b3e84ab56889c8c",
"sha256:afc6fc0d01f02af82cd7bfabd3bbfd5dc96a6ae91e97db0a2dab8a0f1b436056",
"sha256:18c3ee656a6775b9b0d60f7c6c5b094d878d1d90fc03d56731039f0a4b546a91",
"sha256:4139c91f0eef9782df7b77c8cbc6243086affcb6a8a249b768a9658438e5da59",
"sha256:c1df41d89459be6f304e0ceec4b00fdea533dbbcd83c802b1272dcdb94620b57"
],
"index": "pypi",
@ -651,49 +644,11 @@
"index": "pypi",
"version": "==4.6.3"
},
"markupsafe": {
"hashes": [
"sha256:007dc055dbce5b1104876acee177dbfd18757e19d562cd440182e1f492e96b95",
"sha256:031bf79a27d1c42f69c276d6221172417b47cb4b31cdc73d362a9bf5a1889b9f",
"sha256:161d575fa49395860b75da5135162481768b11208490d5a2143ae6785123e77d",
"sha256:24bbc3507fb6dfff663af7900a631f2aca90d5a445f272db5fc84999fa5718bc",
"sha256:2efaeb1baff547063bad2b2893a8f5e9c459c4624e1a96644bbba08910ae34e0",
"sha256:32200f562daaab472921a11cbb63780f1654552ae49518196fc361ed8e12e901",
"sha256:3261fae28155e5c8634dd7710635fe540a05b58f160cef7713c7700cb9980e66",
"sha256:3b54a9c68995ef4164567e2cd1a5e16db5dac30b2a50c39c82db8d4afaf14f63",
"sha256:3c352ff634e289061711608f5e474ec38dbaa21e3e168820d53d5f4015e5b91b",
"sha256:3fb47f97f1d338b943126e90b79cad50d4fcfa0b80637b5a9f468941dbbd9ce5",
"sha256:441ce2a8c17683d97e06447fcbccbdb057cbf587c78eb75ae43ea7858042fe2c",
"sha256:45535241baa0fc0ba2a43961a1ac7562ca3257f46c4c3e9c0de38b722be41bd1",
"sha256:4aca81a687975b35e3e80bcf9aa93fe10cd57fac37bf18b2314c186095f57e05",
"sha256:4cc563836f13c57f1473bc02d1e01fc37bab70ad4ee6be297d58c1d66bc819bf",
"sha256:4fae0677f712ee090721d8b17f412f1cbceefbf0dc180fe91bab3232f38b4527",
"sha256:58bc9fce3e1557d463ef5cee05391a05745fd95ed660f23c1742c711712c0abb",
"sha256:664832fb88b8162268928df233f4b12a144a0c78b01d38b81bdcf0fc96668ecb",
"sha256:70820a1c96311e02449591cbdf5cd1c6a34d5194d5b55094ab725364375c9eb2",
"sha256:79b2ae94fa991be023832e6bcc00f41dbc8e5fe9d997a02db965831402551730",
"sha256:83cf0228b2f694dcdba1374d5312f2277269d798e65f40344964f642935feac1",
"sha256:87de598edfa2230ff274c4de7fcf24c73ffd96208c8e1912d5d0fee459767d75",
"sha256:8f806bfd0f218477d7c46a11d3e52dc7f5fdfaa981b18202b7dc84bbc287463b",
"sha256:90053234a6479738fd40d155268af631c7fca33365f964f2208867da1349294b",
"sha256:a00dce2d96587651ef4fa192c17e039e8cfab63087c67e7d263a5533c7dad715",
"sha256:a08cd07d3c3c17cd33d9e66ea9dee8f8fc1c48e2d11bd88fd2dc515a602c709b",
"sha256:a19d39b02a24d3082856a5b06490b714a9d4179321225bbf22809ff1e1887cc8",
"sha256:d00a669e4a5bec3ee6dbeeeedd82a405ced19f8aeefb109a012ea88a45afff96",
"sha256:dab0c685f21f4a6c95bfc2afd1e7eae0033b403dd3d8c1b6d13a652ada75b348",
"sha256:df561f65049ed3556e5b52541669310e88713fdae2934845ec3606f283337958",
"sha256:e4570d16f88c7f3032ed909dc9e905a17da14a1c4cfd92608e3fda4cb1208bbd",
"sha256:e77e4b983e2441aff0c0d07ee711110c106b625f440292dfe02a2f60c8218bd6",
"sha256:e79212d09fc0e224d20b43ad44bb0a0a3416d1e04cf6b45fed265114a5d43d20",
"sha256:f58b5ba13a5689ca8317b98439fccfbcc673acaaf8241c1869ceea40f5d585bf",
"sha256:fef86115fdad7ae774720d7103aa776144cf9b66673b4afa9bcaa7af990ed07b"
],
"version": "==2.0.0"
},
"maxminddb": {
"hashes": [
"sha256:47e86a084dd814fac88c99ea34ba3278a74bc9de5a25f4b815b608798747c7dc"
],
"markers": "python_version >= '3.6'",
"version": "==2.0.3"
},
"msgpack": {
@ -769,6 +724,7 @@
"sha256:f21756997ad8ef815d8ef3d34edd98804ab5ea337feedcd62fb52d22bf531281",
"sha256:fc13a9524bc18b6fb6e0dbec3533ba0496bbed167c56d0aabefd965584557d80"
],
"markers": "python_version >= '3.6'",
"version": "==5.1.0"
},
"oauthlib": {
@ -776,6 +732,7 @@
"sha256:bee41cc35fcca6e988463cacc3bcb8a96224f470ca547e697b604cc697b2f889",
"sha256:df884cd6cbe20e32633f1db1072e9356f53638e4361bef4e8b03c9127c9328ea"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.1.0"
},
"packaging": {
@ -791,6 +748,7 @@
"sha256:030e4f9df5f53db2292eec37c6255957eb76168c6f974e4176c711cf91ed34aa",
"sha256:b6c5a9643e3545bcbfd9451766cbaa5d9c67e7303c7bc32c750b6fa70ecb107d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.1"
},
"prompt-toolkit": {
@ -798,6 +756,7 @@
"sha256:bf00f22079f5fadc949f42ae8ff7f05702826a97059ffcc6281036ad40ac6f04",
"sha256:e1b4f11b9336a28fa11810bc623c357420f69dfdb6d2dac41ca2c21a55c033bc"
],
"markers": "python_full_version >= '3.6.1'",
"version": "==3.0.18"
},
"psycopg2-binary": {
@ -843,15 +802,37 @@
},
"pyasn1": {
"hashes": [
"sha256:7ab8a544af125fb704feadb008c99a88805126fb525280b2270bb25cc1d78a12",
"sha256:03840c999ba71680a131cfaee6fab142e1ed9bbd9c693e285cc6aca0d555e576",
"sha256:014c0e9976956a08139dc0712ae195324a75e142284d5f87f1a87ee1b068a359",
"sha256:08c3c53b75eaa48d71cf8c710312316392ed40899cb34710d092e96745a358b7",
"sha256:6e7545f1a61025a4e58bb336952c5061697da694db1cae97b116e9c46abcf7c8",
"sha256:5c9414dcfede6e441f7e8f81b43b34e834731003427e5b09e4e00e3172a10f00",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba",
"sha256:0458773cfe65b153891ac249bcf1b5f8f320b7c2ce462151f8fa74de8934becf",
"sha256:fec3e9d8e36808a28efb59b489e4528c10ad0f480e57dcc32b4de5c9d8c9fdf3",
"sha256:78fa6da68ed2727915c4767bb386ab32cdba863caa7dbe473eaae45f9959da86",
"sha256:39c7e2ec30515947ff4e87fb6f456dfc6e84857d34be479c9d4a4ba4bf46aa5d",
"sha256:aef77c9fb94a3ac588e87841208bdec464471d9871bd5050a287cc9a475cd0ba"
"sha256:99fcc3c8d804d1bc6d9a099921e39d827026409a58f2a720dcdb89374ea0c776",
"sha256:e89bf84b5437b532b0803ba5c9a5e054d21fec423a89952a74f87fa2c9b7bce2"
],
"version": "==0.4.8"
},
"pyasn1-modules": {
"hashes": [
"sha256:426edb7a5e8879f1ec54a1864f16b882c2837bfd06eee62f2c982315ee2473ed",
"sha256:c29a5e5cc7a3f05926aff34e097e84f8589cd790ce0ed41b67aed6857b26aafd",
"sha256:65cebbaffc913f4fe9e4808735c95ea22d7a7775646ab690518c056784bc21b4",
"sha256:cbac4bc38d117f2a49aeedec4407d23e8866ea4ac27ff2cf7fb3e5b570df19e0",
"sha256:b80486a6c77252ea3a3e9b1e360bc9cf28eaac41263d173c032581ad2f20fe45",
"sha256:a99324196732f53093a84c4369c996713eb8c89d360a496b599fb1a9c47fc3eb",
"sha256:0fe1b68d1e486a1ed5473f1302bd991c1611d319bba158e98b106ff86e1d7199",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74",
"sha256:15b7c67fabc7fc240d87fb9aabf999cf82311a6d6fb2c70d00d3d0604878c811",
"sha256:f39edd8c4ecaa4556e989147ebf219227e2cd2e8a43c7e7fcb1f1c18c5fd6a3d",
"sha256:905f84c712230b2c592c19470d3ca8d552de726050d1d1716282a1f6146be65e",
"sha256:a50b808ffeb97cb3601dd25981f6b016cbb3d31fbf57a8b8a87428e6158d0c74"
"sha256:0845a5582f6a02bb3e1bde9ecfc4bfcae6ec3210dd270522fee602365430c3f8",
"sha256:fe0644d9ab041506b62782e92b06b8c68cca799e1a9636ec398675459e031405"
],
"version": "==0.2.8"
},
@ -860,6 +841,7 @@
"sha256:2d475327684562c3a96cc71adf7dc8c4f0565175cf86b6d7a404ff4c771f15f0",
"sha256:7582ad22678f0fcd81102833f60ef8d0e57288b6b5fb00323d101be910e35705"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.20"
},
"pycryptodome": {
@ -903,6 +885,7 @@
"sha256:412e00137858f04bde0729913874a48485665f2d36fe9ee449f26be864af9316",
"sha256:7ead136e03655af85069b6f47b23eb7c3e5c221aa9f022a4fbb499f5b7308f29"
],
"markers": "python_version >= '3.5'",
"version": "==2.0.2"
},
"pyjwt": {
@ -925,12 +908,14 @@
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pyrsistent": {
"hashes": [
"sha256:2e636185d9eb976a18a8a8e96efce62f2905fea90041958d8cc2a189756ebf3e"
],
"markers": "python_version >= '3.5'",
"version": "==0.17.3"
},
"python-dateutil": {
@ -938,6 +923,7 @@
"sha256:73ebfe9dbf22e832286dafa60473e4cd239f8592f699aa5adaf10050e6e1823c",
"sha256:75bb3f31ea686f1197762692a9ee6a7550b59fc6ca3a1f4b5d7e32fb98e2da2a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.8.1"
},
"python-dotenv": {
@ -994,6 +980,7 @@
"sha256:0e7e0cfca8660dea8b7d5cd8c4f6c5e29e11f31158c0b0ae91a397f00e5a05a2",
"sha256:432b788c4530cfe16d8d943a09d40ca6c16149727e4afe8c2c9d5580c59d9f24"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==3.5.3"
},
"requests": {
@ -1001,10 +988,12 @@
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.1"
},
"requests-oauthlib": {
"hashes": [
"sha256:fa6c47b933f01060936d87ae9327fead68768b69c6c9ea2109c48be30f2d4dbc",
"sha256:7f71572defaecd16372f9006f33c2ec8c077c3cfa6f5911a9a90202beb513f3d",
"sha256:b4261601a71fd721a8bd6d7aa1cc1d6a8a93b4a9f5e96626f8e4d91e8beeaa6a"
],
@ -1019,50 +1008,6 @@
"markers": "python_version >= '3.6'",
"version": "==4.7.2"
},
"ruamel.yaml": {
"hashes": [
"sha256:44bc6b54fddd45e4bc0619059196679f9e8b79c027f4131bb072e6a22f4d5e28",
"sha256:ac79fb25f5476e8e9ed1c53b8a2286d2c3f5dde49eb37dbcee5c7eb6a8415a22"
],
"version": "==0.17.4"
},
"ruamel.yaml.clib": {
"hashes": [
"sha256:058a1cc3df2a8aecc12f983a48bda99315cebf55a3b3a5463e37bb599b05727b",
"sha256:1236df55e0f73cd138c0eca074ee086136c3f16a97c2ac719032c050f7e0622f",
"sha256:1f8c0a4577c0e6c99d208de5c4d3fd8aceed9574bb154d7a2b21c16bb924154c",
"sha256:2602e91bd5c1b874d6f93d3086f9830f3e907c543c7672cf293a97c3fabdcd91",
"sha256:28116f204103cb3a108dfd37668f20abe6e3cafd0d3fd40dba126c732457b3cc",
"sha256:2d24bd98af676f4990c4d715bcdc2a60b19c56a3fb3a763164d2d8ca0e806ba7",
"sha256:2fd336a5c6415c82e2deb40d08c222087febe0aebe520f4d21910629018ab0f3",
"sha256:30dca9bbcbb1cc858717438218d11eafb78666759e5094dd767468c0d577a7e7",
"sha256:44c7b0498c39f27795224438f1a6be6c5352f82cb887bc33d962c3a3acc00df6",
"sha256:464e66a04e740d754170be5e740657a3b3b6d2bcc567f0c3437879a6e6087ff6",
"sha256:46d6d20815064e8bb023ea8628cfb7402c0f0e83de2c2227a88097e239a7dffd",
"sha256:4df5019e7783d14b79217ad9c56edf1ba7485d614ad5a385d1b3c768635c81c0",
"sha256:4e52c96ca66de04be42ea2278012a2342d89f5e82b4512fb6fb7134e377e2e62",
"sha256:5254af7d8bdf4d5484c089f929cb7f5bafa59b4f01d4f48adda4be41e6d29f99",
"sha256:52ae5739e4b5d6317b52f5b040b1b6639e8af68a5b8fd606a8b08658fbd0cab5",
"sha256:53b9dd1abd70e257a6e32f934ebc482dac5edb8c93e23deb663eac724c30b026",
"sha256:6c0a5dc52fc74eb87c67374a4e554d4761fd42a4d01390b7e868b30d21f4b8bb",
"sha256:73b3d43e04cc4b228fa6fa5d796409ece6fcb53a6c270eb2048109cbcbc3b9c2",
"sha256:74161d827407f4db9072011adcfb825b5258a5ccb3d2cd518dd6c9edea9e30f1",
"sha256:75f0ee6839532e52a3a53f80ce64925ed4aed697dd3fa890c4c918f3304bd4f4",
"sha256:839dd72545ef7ba78fd2aa1a5dd07b33696adf3e68fae7f31327161c1093001b",
"sha256:8be05be57dc5c7b4a0b24edcaa2f7275866d9c907725226cdde46da09367d923",
"sha256:8e8fd0a22c9d92af3a34f91e8a2594eeb35cba90ab643c5e0e643567dc8be43e",
"sha256:a873e4d4954f865dcb60bdc4914af7eaae48fb56b60ed6daa1d6251c72f5337c",
"sha256:ab845f1f51f7eb750a78937be9f79baea4a42c7960f5a94dde34e69f3cce1988",
"sha256:b1e981fe1aff1fd11627f531524826a4dcc1f26c726235a52fcb62ded27d150f",
"sha256:b4b0d31f2052b3f9f9b5327024dc629a253a83d8649d4734ca7f35b60ec3e9e5",
"sha256:c6ac7e45367b1317e56f1461719c853fd6825226f45b835df7436bb04031fd8a",
"sha256:daf21aa33ee9b351f66deed30a3d450ab55c14242cfdfcd377798e2c0d25c9f1",
"sha256:e9f7d1d8c26a6a12c23421061f9022bb62704e38211fe375c645485f38df34a2",
"sha256:f6061a31880c1ed6b6ce341215336e2f3d0c1deccd84957b6fa8ca474b41e89f"
],
"markers": "platform_python_implementation == 'CPython' and python_version < '3.10'",
"version": "==0.2.2"
},
"s3transfer": {
"hashes": [
"sha256:9b3752887a2880690ce628bc263d6d13a3864083aeacff4890c1c9839a5eb0bc",
@ -1091,6 +1036,7 @@
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"sqlparse": {
@ -1098,6 +1044,7 @@
"sha256:017cde379adbd6a1f15a61873f43e8274179378e95ef3fede90b5aa64d304ed0",
"sha256:0f91fd2e829c44362cbcfab3e9ae12e22badaa8a29ad5ff599f9ec109f0454e8"
],
"markers": "python_version >= '3.5'",
"version": "==0.4.1"
},
"structlog": {
@ -1153,6 +1100,7 @@
"sha256:7d6f89745680233f1c4db9ddb748df5e88d2a7a37962be174c0fd04c8dba1dc8",
"sha256:c16b55f9a67b2419cfdf8846576e2ec9ba94fe6978a83080c352a80db31c93fb"
],
"markers": "python_version >= '3.6'",
"version": "==21.2.1"
},
"typing-extensions": {
@ -1168,6 +1116,7 @@
"sha256:07620c3f3f8eed1f12600845892b0e036a2420acf513c53f7de0abd911a5894f",
"sha256:5af8ad10cec94f215e3f48112de2022e1d5a37ed427fbd88652fa908f2ab7cae"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==3.0.1"
},
"urllib3": {
@ -1212,6 +1161,7 @@
"sha256:4c9dceab6f76ed92105027c49c823800dd33cacce13bdedc5b914e3514b7fb30",
"sha256:7d3b1624a953da82ef63462013bbd271d3eb75751489f9807598e8f340bd637e"
],
"markers": "python_version >= '3.6'",
"version": "==5.0.0"
},
"watchgod": {
@ -1241,6 +1191,7 @@
"sha256:2e50d26ca593f70aba7b13a489435ef88b8fc3b5c5643c1ce8808ff9b40f0b32",
"sha256:d376bd60eace9d437ab6d7ee16f4ab4e821c9dae591e1b783c58ebd8aaf80c5c"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.59.0"
},
"websockets": {
@ -1327,6 +1278,7 @@
"sha256:f0b059678fd549c66b89bed03efcabb009075bd131c248ecdf087bdb6faba24a",
"sha256:fcbb48a93e8699eae920f8d92f7160c03567b421bc17362a9ffbbd706a816f71"
],
"markers": "python_version >= '3.6'",
"version": "==1.6.3"
},
"zope.interface": {
@ -1383,6 +1335,7 @@
"sha256:f44e517131a98f7a76696a7b21b164bcb85291cee106a23beccce454e1f433a4",
"sha256:f7ee479e96f7ee350db1cf24afa5685a5899e2b34992fb99e1f7c1b0b758d263"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==5.4.0"
}
},
@ -1399,6 +1352,7 @@
"sha256:4db03ab5fc3340cf619dbc25e42c2cc3755154ce6009469766d7143d1fc2ee4e",
"sha256:8a398dfce302c13f14bab13e2b14fe385d32b73f4e4853b9bdfb64598baa1975"
],
"markers": "python_version ~= '3.6'",
"version": "==2.5.6"
},
"attrs": {
@ -1406,6 +1360,7 @@
"sha256:149e90d6d8ac20db7a955ad60cf0e6881a3f20d37096140088356da6c716b0b1",
"sha256:ef6aaac3ca6cd92904cdd0d83f629a15f18053ec84e6432106f7a4d04ae4f5fb"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==21.2.0"
},
"bandit": {
@ -1444,6 +1399,7 @@
"sha256:0d6f53a15db4120f2b08c94f11e7d93d2c911ee118b6b30a04ec3ee8310179fa",
"sha256:f864054d66fd9118f2e67044ac8981a54775ec5b67aed0441892edb553d21da5"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==4.0.0"
},
"click": {
@ -1451,6 +1407,7 @@
"sha256:d2b5255c7c6349bc1bd1e59e08cd12acbbd63ce649f2588755783aa94dfb6b1a",
"sha256:dacca89f4bfadd5de3d7489b7c8a566eee0d3676333fbb50030263894c38c0dc"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==7.1.2"
},
"colorama": {
@ -1524,6 +1481,7 @@
"sha256:6c4cc71933456991da20917998acbe6cf4fb41eeaab7d6d67fbc05ecd4c865b0",
"sha256:96bf5c08b157a666fec41129e6d327235284cca4c81e92109260f353ba138005"
],
"markers": "python_version >= '3.4'",
"version": "==4.0.7"
},
"gitpython": {
@ -1531,6 +1489,7 @@
"sha256:29fe82050709760081f588dd50ce83504feddbebdc4da6956d02351552b1c135",
"sha256:ee24bdc93dce357630764db659edaf6b8d664d4ff5447ccfeedd2dc5c253f41e"
],
"markers": "python_version >= '3.5'",
"version": "==3.1.17"
},
"idna": {
@ -1552,6 +1511,7 @@
"sha256:0a943902919f65c5684ac4e0154b1ad4fac6dcaa5d9f3426b732f1c8b5419be6",
"sha256:2bb1680aad211e3c9944dbce1d4ba09a989f04e238296c87fe2139faa26d655d"
],
"markers": "python_version >= '3.6' and python_version < '4.0'",
"version": "==5.8.0"
},
"lazy-object-proxy": {
@ -1579,6 +1539,7 @@
"sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93",
"sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4, 3.5'",
"version": "==1.6.0"
},
"mccabe": {
@ -1615,6 +1576,7 @@
"sha256:42df03e7797b796625b1029c0400279c7c34fd7df24a7d7818a1abb5b38710dd",
"sha256:c68c661ac5cc81058ac94247278eeda6d2e6aecb3e227b0387c30d277e7ef8d4"
],
"markers": "python_version >= '2.6'",
"version": "==5.6.0"
},
"pluggy": {
@ -1622,6 +1584,7 @@
"sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0",
"sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.13.1"
},
"py": {
@ -1629,6 +1592,7 @@
"sha256:21b81bda15b66ef5e1a777a21c4dcd9c20ad3efd0b3f817e7a809035269e1bd3",
"sha256:3b80836aa6d1feeaa108e046da6423ab8f6ceda6468545ae8d02d9d58d18818a"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.10.0"
},
"pylint": {
@ -1659,6 +1623,7 @@
"sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1",
"sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==2.4.7"
},
"pytest": {
@ -1763,6 +1728,7 @@
"sha256:27973dd4a904a4f13b263a19c866c13b92a39ed1c964655f025f3f8d3d75b804",
"sha256:c210084e36a42ae6b9219e00e48287def368a26d03a048ddad7bfee44f75871e"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'",
"version": "==2.25.1"
},
"requests-mock": {
@ -1786,6 +1752,7 @@
"sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926",
"sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"
],
"markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==1.16.0"
},
"smmap": {
@ -1793,6 +1760,7 @@
"sha256:7e65386bd122d45405ddf795637b7f7d2b532e7e401d46bbe3fb49b9986d5182",
"sha256:a9a7479e4c572e2e775c404dcd3080c8dc49f39918c2cf74913d30c4c478e3c2"
],
"markers": "python_version >= '3.5'",
"version": "==4.0.0"
},
"stevedore": {
@ -1800,6 +1768,7 @@
"sha256:3a5bbd0652bf552748871eaa73a4a8dc2899786bc497a2aa1fcb4dcdb0debeee",
"sha256:50d7b78fbaf0d04cd62411188fa7eedcb03eb7f4c4b37005615ceebe582aa82a"
],
"markers": "python_version >= '3.6'",
"version": "==3.3.0"
},
"toml": {
@ -1807,6 +1776,7 @@
"sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b",
"sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"
],
"markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3'",
"version": "==0.10.2"
},
"urllib3": {

View File

@ -1,5 +1,5 @@
"""Meta API"""
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from rest_framework.fields import CharField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
@ -22,7 +22,7 @@ class AppsViewSet(ViewSet):
permission_classes = [IsAdminUser]
@swagger_auto_schema(responses={200: AppSerializer(many=True)})
@extend_schema(responses={200: AppSerializer(many=True)})
def list(self, request: Request) -> Response:
"""List current messages and pass into Serializer"""
data = []

View File

@ -7,12 +7,12 @@ from django.db.models import Count, ExpressionWrapper, F
from django.db.models.fields import DurationField
from django.db.models.functions import ExtractHour
from django.utils.timezone import now
from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method
from drf_spectacular.utils import extend_schema, extend_schema_field
from rest_framework.fields import IntegerField, SerializerMethodField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from rest_framework.views import APIView
from authentik.core.api.utils import PassiveSerializer
from authentik.events.models import Event, EventAction
@ -58,24 +58,24 @@ class LoginMetricsSerializer(PassiveSerializer):
logins_per_1h = SerializerMethodField()
logins_failed_per_1h = SerializerMethodField()
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
@extend_schema_field(CoordinateSerializer(many=True))
def get_logins_per_1h(self, _):
"""Get successful logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN)
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
@extend_schema_field(CoordinateSerializer(many=True))
def get_logins_failed_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours"""
return get_events_per_1h(action=EventAction.LOGIN_FAILED)
class AdministrationMetricsViewSet(ViewSet):
class AdministrationMetricsViewSet(APIView):
"""Login Metrics per 1h"""
permission_classes = [IsAdminUser]
@swagger_auto_schema(responses={200: LoginMetricsSerializer(many=False)})
def list(self, request: Request) -> Response:
@extend_schema(responses={200: LoginMetricsSerializer(many=False)})
def get(self, request: Request) -> Response:
"""Login Metrics per 1h"""
serializer = LoginMetricsSerializer(True)
return Response(serializer.data)

View File

@ -4,7 +4,8 @@ from importlib import import_module
from django.contrib import messages
from django.http.response import Http404
from django.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import CharField, ChoiceField, DateTimeField, ListField
from rest_framework.permissions import IsAdminUser
@ -34,9 +35,13 @@ class TaskViewSet(ViewSet):
"""Read-only view set that returns all background tasks"""
permission_classes = [IsAdminUser]
serializer_class = TaskSerializer
@swagger_auto_schema(
responses={200: TaskSerializer(many=False), 404: "Task not found"}
@extend_schema(
responses={
200: TaskSerializer(many=False),
404: OpenApiResponse(description="Task not found"),
}
)
# pylint: disable=invalid-name
def retrieve(self, request: Request, pk=None) -> Response:
@ -46,18 +51,19 @@ class TaskViewSet(ViewSet):
raise Http404
return Response(TaskSerializer(task, many=False).data)
@swagger_auto_schema(responses={200: TaskSerializer(many=True)})
@extend_schema(responses={200: TaskSerializer(many=True)})
def list(self, request: Request) -> Response:
"""List system tasks"""
tasks = sorted(TaskInfo.all().values(), key=lambda task: task.task_name)
return Response(TaskSerializer(tasks, many=True).data)
@swagger_auto_schema(
@extend_schema(
request=OpenApiTypes.NONE,
responses={
204: "Task retried successfully",
404: "Task not found",
500: "Failed to retry task",
}
204: OpenApiResponse(description="Task retried successfully"),
404: OpenApiResponse(description="Task not found"),
500: OpenApiResponse(description="Failed to retry task"),
},
)
@action(detail=True, methods=["post"])
# pylint: disable=invalid-name

View File

@ -2,14 +2,13 @@
from os import environ
from django.core.cache import cache
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from packaging.version import parse
from rest_framework.fields import SerializerMethodField
from rest_framework.mixins import ListModelMixin
from rest_framework.permissions import IsAuthenticated
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import GenericViewSet
from rest_framework.views import APIView
from authentik import ENV_GIT_HASH_KEY, __version__
from authentik.admin.tasks import VERSION_CACHE_KEY, update_latest_version
@ -47,17 +46,14 @@ class VersionSerializer(PassiveSerializer):
)
class VersionViewSet(ListModelMixin, GenericViewSet):
class VersionView(APIView):
"""Get running and latest version."""
permission_classes = [IsAuthenticated]
pagination_class = None
filter_backends = []
def get_queryset(self): # pragma: no cover
return None
@swagger_auto_schema(responses={200: VersionSerializer(many=False)})
def list(self, request: Request) -> Response:
@extend_schema(responses={200: VersionSerializer(many=False)})
def get(self, request: Request) -> Response:
"""Get running and latest version."""
return Response(VersionSerializer(True).data)

View File

@ -1,25 +1,22 @@
"""authentik administration overview"""
from rest_framework.mixins import ListModelMixin
from drf_spectacular.utils import extend_schema, inline_serializer
from rest_framework.fields import IntegerField
from rest_framework.permissions import IsAdminUser
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.serializers import Serializer
from rest_framework.viewsets import GenericViewSet
from rest_framework.views import APIView
from authentik.root.celery import CELERY_APP
class WorkerViewSet(ListModelMixin, GenericViewSet):
class WorkerView(APIView):
"""Get currently connected worker count."""
serializer_class = Serializer
permission_classes = [IsAdminUser]
def get_queryset(self): # pragma: no cover
return None
def list(self, request: Request) -> Response:
@extend_schema(
responses=inline_serializer("Workers", fields={"count": IntegerField()})
)
def get(self, request: Request) -> Response:
"""Get currently connected worker count."""
return Response(
{"pagination": {"count": len(CELERY_APP.control.ping(timeout=0.5))}}
)
return Response({"count": len(CELERY_APP.control.ping(timeout=0.5))})

View File

@ -74,21 +74,21 @@ class TestAdminAPI(TestCase):
def test_version(self):
"""Test Version API"""
response = self.client.get(reverse("authentik_api:admin_version-list"))
response = self.client.get(reverse("authentik_api:admin_version"))
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(body["version_current"], __version__)
def test_workers(self):
"""Test Workers API"""
response = self.client.get(reverse("authentik_api:admin_workers-list"))
response = self.client.get(reverse("authentik_api:admin_workers"))
self.assertEqual(response.status_code, 200)
body = loads(response.content)
self.assertEqual(body["pagination"]["count"], 0)
self.assertEqual(body["count"], 0)
def test_metrics(self):
"""Test metrics API"""
response = self.client.get(reverse("authentik_api:admin_metrics-list"))
response = self.client.get(reverse("authentik_api:admin_metrics"))
self.assertEqual(response.status_code, 200)
def test_apps(self):

View File

@ -3,6 +3,7 @@ from base64 import b64decode
from binascii import Error
from typing import Any, Optional, Union
from drf_spectacular.authentication import OpenApiAuthenticationExtension
from rest_framework.authentication import BaseAuthentication, get_authorization_header
from rest_framework.exceptions import AuthenticationFailed
from rest_framework.request import Request
@ -55,3 +56,18 @@ class AuthentikTokenAuthentication(BaseAuthentication):
return None
return (token.user, None) # pragma: no cover
class TokenSchema(OpenApiAuthenticationExtension):
"""Auth schema"""
target_class = AuthentikTokenAuthentication
name = "authentik"
def get_security_definition(self, auto_schema):
"""Auth schema"""
return {
"type": "apiKey",
"in": "header",
"name": "Authorization",
}

View File

@ -30,3 +30,47 @@ class Pagination(pagination.PageNumberPagination):
"results": data,
}
)
def get_paginated_response_schema(self, schema):
return {
"type": "object",
"properties": {
"pagination": {
"type": "object",
"properties": {
"next": {
"type": "number",
},
"previous": {
"type": "number",
},
"count": {
"type": "number",
},
"current": {
"type": "number",
},
"total_pages": {
"type": "number",
},
"start_index": {
"type": "number",
},
"end_index": {
"type": "number",
},
},
"required": [
"next",
"previous",
"count",
"current",
"total_pages",
"start_index",
"end_index",
],
},
"results": schema,
},
"required": ["pagination", "results"],
}

View File

@ -1,97 +0,0 @@
"""Swagger Pagination Schema class"""
from typing import OrderedDict
from drf_yasg import openapi
from drf_yasg.inspectors import PaginatorInspector
class PaginationInspector(PaginatorInspector):
"""Swagger Pagination Schema class"""
def get_paginated_response(self, paginator, response_schema):
"""
:param BasePagination paginator: the paginator
:param openapi.Schema response_schema: the response schema that must be paged.
:rtype: openapi.Schema
"""
return openapi.Schema(
type=openapi.TYPE_OBJECT,
properties=OrderedDict(
(
(
"pagination",
openapi.Schema(
type=openapi.TYPE_OBJECT,
properties=OrderedDict(
(
("next", openapi.Schema(type=openapi.TYPE_NUMBER)),
(
"previous",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
("count", openapi.Schema(type=openapi.TYPE_NUMBER)),
(
"current",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
(
"total_pages",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
(
"start_index",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
(
"end_index",
openapi.Schema(type=openapi.TYPE_NUMBER),
),
)
),
required=[
"next",
"previous",
"count",
"current",
"total_pages",
"start_index",
"end_index",
],
),
),
("results", response_schema),
)
),
required=["results", "pagination"],
)
def get_paginator_parameters(self, paginator):
"""
Get the pagination parameters for a single paginator **instance**.
Should return :data:`.NotHandled` if this inspector
does not know how to handle the given `paginator`.
:param BasePagination paginator: the paginator
:rtype: list[openapi.Parameter]
"""
return [
openapi.Parameter(
"page",
openapi.IN_QUERY,
"Page Index",
False,
None,
openapi.TYPE_INTEGER,
),
openapi.Parameter(
"page_size",
openapi.IN_QUERY,
"Page Size",
False,
None,
openapi.TYPE_INTEGER,
),
]

View File

@ -1,102 +1,70 @@
"""Error Response schema, from https://github.com/axnsan12/drf-yasg/issues/224"""
from drf_yasg import openapi
from drf_yasg.inspectors.view import SwaggerAutoSchema
from drf_yasg.utils import force_real_str, is_list_view
from rest_framework import exceptions, status
from rest_framework.settings import api_settings
from django.utils.translation import gettext_lazy as _
from drf_spectacular.plumbing import (
ResolvedComponent,
build_array_type,
build_basic_type,
build_object_type,
)
from drf_spectacular.settings import spectacular_settings
from drf_spectacular.types import OpenApiTypes
class ErrorResponseAutoSchema(SwaggerAutoSchema):
"""Inspector which includes an error schema"""
def build_standard_type(obj, **kwargs):
"""Build a basic type with optional add ons."""
schema = build_basic_type(obj)
schema.update(kwargs)
return schema
def get_generic_error_schema(self):
"""Get a generic error schema"""
return openapi.Schema(
"Generic API Error",
type=openapi.TYPE_OBJECT,
properties={
"detail": openapi.Schema(
type=openapi.TYPE_STRING, description="Error details"
),
"code": openapi.Schema(
type=openapi.TYPE_STRING, description="Error code"
),
},
required=["detail"],
GENERIC_ERROR = build_object_type(
description=_("Generic API Error"),
properties={
"detail": build_standard_type(OpenApiTypes.STR),
"code": build_standard_type(OpenApiTypes.STR),
},
required=["detail"],
)
VALIDATION_ERROR = build_object_type(
description=_("Validation Error"),
properties={
"non_field_errors": build_array_type(build_standard_type(OpenApiTypes.STR)),
"code": build_standard_type(OpenApiTypes.STR),
},
required=["detail"],
additionalProperties={},
)
def postprocess_schema_responses(result, generator, **kwargs): # noqa: W0613
"""Workaround to set a default response for endpoints.
Workaround suggested at
<https://github.com/tfranzel/drf-spectacular/issues/119#issuecomment-656970357>
for the missing drf-spectacular feature discussed in
<https://github.com/tfranzel/drf-spectacular/issues/101>.
"""
def create_component(name, schema, type_=ResolvedComponent.SCHEMA):
"""Register a component and return a reference to it."""
component = ResolvedComponent(
name=name,
type=type_,
schema=schema,
object=name,
)
generator.registry.register_on_missing(component)
return component
def get_validation_error_schema(self):
"""Get a generic validation error schema"""
return openapi.Schema(
"Validation Error",
type=openapi.TYPE_OBJECT,
properties={
api_settings.NON_FIELD_ERRORS_KEY: openapi.Schema(
description="List of validation errors not related to any field",
type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING),
),
},
additional_properties=openapi.Schema(
description=(
"A list of error messages for each "
"field that triggered a validation error"
),
type=openapi.TYPE_ARRAY,
items=openapi.Schema(type=openapi.TYPE_STRING),
),
)
generic_error = create_component("GenericError", GENERIC_ERROR)
validation_error = create_component("ValidationError", VALIDATION_ERROR)
def get_response_serializers(self):
responses = super().get_response_serializers()
definitions = self.components.with_scope(
openapi.SCHEMA_DEFINITIONS
) # type: openapi.ReferenceResolver
for path in result["paths"].values():
for method in path.values():
method["responses"].setdefault("400", validation_error.ref)
method["responses"].setdefault("403", generic_error.ref)
definitions.setdefault("GenericError", self.get_generic_error_schema)
definitions.setdefault("ValidationError", self.get_validation_error_schema)
definitions.setdefault("APIException", self.get_generic_error_schema)
result["components"] = generator.registry.build(
spectacular_settings.APPEND_COMPONENTS
)
if self.get_request_serializer() or self.get_query_serializer():
responses.setdefault(
exceptions.ValidationError.status_code,
openapi.Response(
description=force_real_str(
exceptions.ValidationError.default_detail
),
schema=openapi.SchemaRef(definitions, "ValidationError"),
),
)
security = self.get_security()
if security is None or len(security) > 0:
# Note: 401 error codes are coerced into 403 see
# rest_framework/views.py:433:handle_exception
# This is b/c the API uses token auth which doesn't have WWW-Authenticate header
responses.setdefault(
status.HTTP_403_FORBIDDEN,
openapi.Response(
description="Authentication credentials were invalid, absent or insufficient.",
schema=openapi.SchemaRef(definitions, "GenericError"),
),
)
if not is_list_view(self.path, self.method, self.view):
responses.setdefault(
exceptions.PermissionDenied.status_code,
openapi.Response(
description="Permission denied.",
schema=openapi.SchemaRef(definitions, "APIException"),
),
)
responses.setdefault(
exceptions.NotFound.status_code,
openapi.Response(
description=(
"Object does not exist or caller "
"has insufficient permissions to access it."
),
schema=openapi.SchemaRef(definitions, "APIException"),
),
)
return responses
return result

View File

@ -11,6 +11,6 @@ class TestConfig(APITestCase):
def test_config(self):
"""Test YAML generation"""
response = self.client.get(
reverse("authentik_api:configs-list"),
reverse("authentik_api:config"),
)
self.assertTrue(loads(response.content.decode()))

View File

@ -0,0 +1,22 @@
"""Schema generation tests"""
from django.urls import reverse
from rest_framework.test import APITestCase
from yaml import safe_load
class TestSchemaGeneration(APITestCase):
"""Generic admin tests"""
def test_schema(self):
"""Test generation"""
response = self.client.get(
reverse("authentik_api:schema"),
)
self.assertTrue(safe_load(response.content.decode()))
def test_browser(self):
"""Test API Browser"""
response = self.client.get(
reverse("authentik_api:schema-browser"),
)
self.assertEqual(response.status_code, 200)

View File

@ -1,31 +0,0 @@
"""Swagger generation tests"""
from json import loads
from django.urls import reverse
from rest_framework.test import APITestCase
from yaml import safe_load
class TestSwaggerGeneration(APITestCase):
"""Generic admin tests"""
def test_yaml(self):
"""Test YAML generation"""
response = self.client.get(
reverse("authentik_api:schema-json", kwargs={"format": ".yaml"}),
)
self.assertTrue(safe_load(response.content.decode()))
def test_json(self):
"""Test JSON generation"""
response = self.client.get(
reverse("authentik_api:schema-json", kwargs={"format": ".json"}),
)
self.assertTrue(loads(response.content.decode()))
def test_browser(self):
"""Test API Browser"""
response = self.client.get(
reverse("authentik_api:swagger"),
)
self.assertEqual(response.status_code, 200)

View File

@ -1,10 +1,10 @@
"""core Configs API"""
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from rest_framework.fields import BooleanField, CharField, ListField
from rest_framework.permissions import AllowAny
from rest_framework.request import Request
from rest_framework.response import Response
from rest_framework.viewsets import ViewSet
from rest_framework.views import APIView
from authentik.core.api.utils import PassiveSerializer
from authentik.lib.config import CONFIG
@ -29,13 +29,13 @@ class ConfigSerializer(PassiveSerializer):
error_reporting_send_pii = BooleanField(read_only=True)
class ConfigsViewSet(ViewSet):
class ConfigView(APIView):
"""Read-only view set that returns the current session's Configs"""
permission_classes = [AllowAny]
@swagger_auto_schema(responses={200: ConfigSerializer(many=False)})
def list(self, request: Request) -> Response:
@extend_schema(responses={200: ConfigSerializer(many=False)})
def get(self, request: Request) -> Response:
"""Retrive public configuration options"""
config = ConfigSerializer(
{

View File

@ -1,17 +1,15 @@
"""api v2 urls"""
from django.urls import path, re_path
from drf_yasg import openapi
from drf_yasg.views import get_schema_view
from django.urls import path
from drf_spectacular.views import SpectacularAPIView
from rest_framework import routers
from rest_framework.permissions import AllowAny
from authentik.admin.api.meta import AppsViewSet
from authentik.admin.api.metrics import AdministrationMetricsViewSet
from authentik.admin.api.tasks import TaskViewSet
from authentik.admin.api.version import VersionViewSet
from authentik.admin.api.workers import WorkerViewSet
from authentik.api.v2.config import ConfigsViewSet
from authentik.api.views import SwaggerView
from authentik.admin.api.version import VersionView
from authentik.admin.api.workers import WorkerView
from authentik.api.v2.config import ConfigView
from authentik.api.views import APIBrowserView
from authentik.core.api.applications import ApplicationViewSet
from authentik.core.api.groups import GroupViewSet
from authentik.core.api.propertymappings import PropertyMappingViewSet
@ -100,11 +98,6 @@ from authentik.stages.user_write.api import UserWriteStageViewSet
router = routers.DefaultRouter()
router.register("root/config", ConfigsViewSet, basename="configs")
router.register("admin/version", VersionViewSet, basename="admin_version")
router.register("admin/workers", WorkerViewSet, basename="admin_workers")
router.register("admin/metrics", AdministrationMetricsViewSet, basename="admin_metrics")
router.register("admin/system_tasks", TaskViewSet, basename="admin_system_tasks")
router.register("admin/apps", AppsViewSet, basename="apps")
@ -114,7 +107,6 @@ router.register("core/users", UserViewSet)
router.register("core/user_consent", UserConsentViewSet)
router.register("core/tokens", TokenViewSet)
router.register("outposts/outposts", OutpostViewSet)
router.register("outposts/instances", OutpostViewSet)
router.register("outposts/service_connections/all", ServiceConnectionViewSet)
router.register("outposts/service_connections/docker", DockerServiceConnectionViewSet)
@ -196,32 +188,25 @@ router.register("stages/user_write", UserWriteStageViewSet)
router.register("stages/dummy", DummyStageViewSet)
router.register("policies/dummy", DummyPolicyViewSet)
info = openapi.Info(
title="authentik API",
default_version="v2beta",
contact=openapi.Contact(email="hello@beryju.org"),
license=openapi.License(
name="GNU GPLv3",
url="https://github.com/goauthentik/authentik/blob/master/LICENSE",
),
)
SchemaView = get_schema_view(info, public=True, permission_classes=(AllowAny,))
urlpatterns = (
[
path("", SwaggerView.as_view(), name="swagger"),
path("", APIBrowserView.as_view(), name="schema-browser"),
]
+ router.urls
+ [
path(
"admin/metrics/",
AdministrationMetricsViewSet.as_view(),
name="admin_metrics",
),
path("admin/version/", VersionView.as_view(), name="admin_version"),
path("admin/workers/", WorkerView.as_view(), name="admin_workers"),
path("root/config/", ConfigView.as_view(), name="config"),
path(
"flows/executor/<slug:flow_slug>/",
FlowExecutorView.as_view(),
name="flow-executor",
),
re_path(
r"^swagger(?P<format>\.json|\.yaml)$",
SchemaView.without_ui(cache_timeout=0),
name="schema-json",
),
path("schema/", SpectacularAPIView.as_view(), name="schema"),
]
)

View File

@ -5,18 +5,15 @@ from django.urls import reverse
from django.views.generic import TemplateView
class SwaggerView(TemplateView):
"""Show swagger view based on rapi-doc"""
class APIBrowserView(TemplateView):
"""Show browser view based on rapi-doc"""
template_name = "api/swagger.html"
template_name = "api/browser.html"
def get_context_data(self, **kwargs: Any) -> dict[str, Any]:
path = self.request.build_absolute_uri(
reverse(
"authentik_api:schema-json",
kwargs={
"format": ".json",
},
"authentik_api:schema",
)
)
return super().get_context_data(path=path, **kwargs)

View File

@ -5,8 +5,8 @@ from django.core.cache import cache
from django.db.models import QuerySet
from django.http.response import HttpResponseBadRequest
from django.shortcuts import get_object_or_404
from drf_yasg import openapi
from drf_yasg.utils import no_body, swagger_auto_schema
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import SerializerMethodField
from rest_framework.parsers import MultiPartParser
@ -58,6 +58,9 @@ class ApplicationSerializer(ModelSerializer):
"meta_publisher",
"policy_engine_mode",
]
extra_kwargs = {
"meta_icon": {"read_only": True},
}
class ApplicationViewSet(ModelViewSet):
@ -92,10 +95,10 @@ class ApplicationViewSet(ModelViewSet):
applications.append(application)
return applications
@swagger_auto_schema(
@extend_schema(
responses={
204: "Access granted",
403: "Access denied",
204: OpenApiResponse(description="Access granted"),
403: OpenApiResponse(description="Access denied"),
}
)
@action(detail=True, methods=["GET"])
@ -111,12 +114,12 @@ class ApplicationViewSet(ModelViewSet):
return Response(status=204)
return Response(status=403)
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter(
@extend_schema(
parameters=[
OpenApiParameter(
name="superuser_full_list",
in_=openapi.IN_QUERY,
type=openapi.TYPE_BOOLEAN,
location=OpenApiParameter.QUERY,
type=OpenApiTypes.BOOL,
)
]
)
@ -151,17 +154,20 @@ class ApplicationViewSet(ModelViewSet):
return self.get_paginated_response(serializer.data)
@permission_required("authentik_core.change_application")
@swagger_auto_schema(
request_body=no_body,
manual_parameters=[
openapi.Parameter(
@extend_schema(
request=OpenApiTypes.NONE,
parameters=[
OpenApiParameter(
name="file",
in_=openapi.IN_FORM,
type=openapi.TYPE_FILE,
location=OpenApiParameter.QUERY, # TODO: In Form
type=OpenApiTypes.BINARY,
required=True,
)
],
responses={200: "Success", 400: "Bad request"},
responses={
200: OpenApiResponse(description="Success"),
400: OpenApiResponse(description="Bad request"),
},
)
@action(
detail=True,
@ -184,7 +190,7 @@ class ApplicationViewSet(ModelViewSet):
@permission_required(
"authentik_core.view_application", ["authentik_events.view_event"]
)
@swagger_auto_schema(responses={200: CoordinateSerializer(many=True)})
@extend_schema(responses={200: CoordinateSerializer(many=True)})
@action(detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=unused-argument
def metrics(self, request: Request, slug: str):

View File

@ -1,8 +1,8 @@
"""PropertyMapping API Views"""
from json import dumps
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework import mixins
from rest_framework.decorators import action
@ -81,7 +81,7 @@ class PropertyMappingViewSet(
def get_queryset(self):
return PropertyMapping.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response:
"""Get all creatable property-mapping types"""
@ -100,14 +100,17 @@ class PropertyMappingViewSet(
return Response(TypeCreateSerializer(data, many=True).data)
@permission_required("authentik_core.view_propertymapping")
@swagger_auto_schema(
request_body=PolicyTestSerializer(),
responses={200: PropertyMappingTestResultSerializer, 400: "Invalid parameters"},
manual_parameters=[
openapi.Parameter(
@extend_schema(
request=PolicyTestSerializer(),
responses={
200: PropertyMappingTestResultSerializer,
400: OpenApiResponse(description="Invalid parameters"),
},
parameters=[
OpenApiParameter(
name="format_result",
in_=openapi.IN_QUERY,
type=openapi.TYPE_BOOLEAN,
location=OpenApiParameter.QUERY,
type=OpenApiTypes.BOOL,
)
],
)

View File

@ -1,6 +1,6 @@
"""Provider API Views"""
from django.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.fields import ReadOnlyField
@ -22,7 +22,7 @@ class ProviderSerializer(ModelSerializer, MetaNameSerializer):
component = SerializerMethodField()
def get_component(self, obj: Provider): # pragma: no cover
def get_component(self, obj: Provider) -> str: # pragma: no cover
"""Get object component so that we know how to edit the object"""
# pyright: reportGeneralTypeIssues=false
if obj.__class__ == Provider:
@ -66,7 +66,7 @@ class ProviderViewSet(
def get_queryset(self):
return Provider.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response:
"""Get all creatable provider types"""

View File

@ -1,7 +1,7 @@
"""Source API Views"""
from typing import Iterable
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.request import Request
@ -24,7 +24,7 @@ class SourceSerializer(ModelSerializer, MetaNameSerializer):
component = SerializerMethodField()
def get_component(self, obj: Source):
def get_component(self, obj: Source) -> str:
"""Get object component so that we know how to edit the object"""
# pyright: reportGeneralTypeIssues=false
if obj.__class__ == Source:
@ -64,7 +64,7 @@ class SourceViewSet(
def get_queryset(self):
return Source.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response:
"""Get all creatable source types"""
@ -87,7 +87,7 @@ class SourceViewSet(
)
return Response(TypeCreateSerializer(data, many=True).data)
@swagger_auto_schema(responses={200: UserSettingSerializer(many=True)})
@extend_schema(responses={200: UserSettingSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])
def user_settings(self, request: Request) -> Response:
"""Get all sources the user can configure"""

View File

@ -1,6 +1,6 @@
"""Tokens API Viewset"""
from django.http.response import Http404
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import CharField
from rest_framework.request import Request
@ -67,10 +67,10 @@ class TokenViewSet(ModelViewSet):
serializer.save(user=self.request.user, intent=TokenIntents.INTENT_API)
@permission_required("authentik_core.view_token_key")
@swagger_auto_schema(
@extend_schema(
responses={
200: TokenViewSerializer(many=False),
404: "Token not found or expired",
404: OpenApiResponse(description="Token not found or expired"),
}
)
@action(detail=True, pagination_class=None, filter_backends=[])

View File

@ -7,7 +7,7 @@ from django.urls import reverse_lazy
from django.utils.http import urlencode
from django_filters.filters import BooleanFilter, CharFilter
from django_filters.filterset import FilterSet
from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method
from drf_spectacular.utils import OpenApiResponse, extend_schema, extend_schema_field
from guardian.utils import get_anonymous_user
from rest_framework.decorators import action
from rest_framework.fields import CharField, JSONField, SerializerMethodField
@ -77,13 +77,13 @@ class UserMetricsSerializer(PassiveSerializer):
logins_failed_per_1h = SerializerMethodField()
authorizations_per_1h = SerializerMethodField()
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
@extend_schema_field(CoordinateSerializer(many=True))
def get_logins_per_1h(self, _):
"""Get successful logins per hour for the last 24 hours"""
user = self.context["user"]
return get_events_per_1h(action=EventAction.LOGIN, user__pk=user.pk)
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
@extend_schema_field(CoordinateSerializer(many=True))
def get_logins_failed_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours"""
user = self.context["user"]
@ -91,7 +91,7 @@ class UserMetricsSerializer(PassiveSerializer):
action=EventAction.LOGIN_FAILED, context__username=user.username
)
@swagger_serializer_method(serializer_or_field=CoordinateSerializer(many=True))
@extend_schema_field(CoordinateSerializer(many=True))
def get_authorizations_per_1h(self, _):
"""Get failed logins per hour for the last 24 hours"""
user = self.context["user"]
@ -142,7 +142,7 @@ class UserViewSet(ModelViewSet):
def get_queryset(self):
return User.objects.all().exclude(pk=get_anonymous_user().pk)
@swagger_auto_schema(responses={200: SessionUserSerializer(many=False)})
@extend_schema(responses={200: SessionUserSerializer(many=False)})
@action(detail=False, pagination_class=None, filter_backends=[])
# pylint: disable=invalid-name
def me(self, request: Request) -> Response:
@ -158,7 +158,7 @@ class UserViewSet(ModelViewSet):
return Response(serializer.data)
@permission_required("authentik_core.view_user", ["authentik_events.view_event"])
@swagger_auto_schema(responses={200: UserMetricsSerializer(many=False)})
@extend_schema(responses={200: UserMetricsSerializer(many=False)})
@action(detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=invalid-name, unused-argument
def metrics(self, request: Request, pk: int) -> Response:
@ -169,8 +169,11 @@ class UserViewSet(ModelViewSet):
return Response(serializer.data)
@permission_required("authentik_core.reset_user_password")
@swagger_auto_schema(
responses={"200": LinkSerializer(many=False), "404": "No recovery flow found."},
@extend_schema(
responses={
"200": LinkSerializer(many=False),
"404": OpenApiResponse(description="No recovery flow found."),
},
)
@action(detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=invalid-name, unused-argument

View File

@ -28,6 +28,9 @@ class PassiveSerializer(Serializer):
) -> Model: # pragma: no cover
return Model()
class Meta:
model = Model
class MetaNameSerializer(PassiveSerializer):
"""Add verbose names to response"""

View File

@ -5,8 +5,8 @@ from cryptography.hazmat.primitives.serialization import load_pem_private_key
from cryptography.x509 import load_pem_x509_certificate
from django.http.response import HttpResponse
from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import (
CharField,
@ -125,9 +125,12 @@ class CertificateKeyPairViewSet(ModelViewSet):
filterset_class = CertificateKeyPairFilter
@permission_required(None, ["authentik_crypto.add_certificatekeypair"])
@swagger_auto_schema(
request_body=CertificateGenerationSerializer(),
responses={200: CertificateKeyPairSerializer, 400: "Bad request"},
@extend_schema(
request=CertificateGenerationSerializer(),
responses={
200: CertificateKeyPairSerializer,
400: OpenApiResponse(description="Bad request"),
},
)
@action(detail=False, methods=["POST"])
def generate(self, request: Request) -> Response:
@ -147,12 +150,12 @@ class CertificateKeyPairViewSet(ModelViewSet):
serializer = self.get_serializer(instance)
return Response(serializer.data)
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter(
@extend_schema(
parameters=[
OpenApiParameter(
name="download",
in_=openapi.IN_QUERY,
type=openapi.TYPE_BOOLEAN,
location=OpenApiParameter.QUERY,
type=OpenApiTypes.BOOL,
)
],
responses={200: CertificateDataSerializer(many=False)},
@ -180,12 +183,12 @@ class CertificateKeyPairViewSet(ModelViewSet):
CertificateDataSerializer({"data": certificate.certificate_data}).data
)
@swagger_auto_schema(
manual_parameters=[
openapi.Parameter(
@extend_schema(
parameters=[
OpenApiParameter(
name="download",
in_=openapi.IN_QUERY,
type=openapi.TYPE_BOOLEAN,
location=OpenApiParameter.QUERY,
type=OpenApiTypes.BOOL,
)
],
responses={200: CertificateDataSerializer(many=False)},

View File

@ -2,7 +2,8 @@
import django_filters
from django.db.models.aggregates import Count
from django.db.models.fields.json import KeyTextTransform
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.fields import CharField, DictField, IntegerField
@ -38,12 +39,6 @@ class EventSerializer(ModelSerializer):
]
class EventTopPerUserParams(PassiveSerializer):
"""Query params for top_per_user"""
top_n = IntegerField(default=15)
class EventTopPerUserSerializer(PassiveSerializer):
"""Response object of Event's top_per_user"""
@ -111,12 +106,19 @@ class EventViewSet(ReadOnlyModelViewSet):
]
filterset_class = EventsFilter
@swagger_auto_schema(
method="GET",
@extend_schema(
methods=["GET"],
responses={200: EventTopPerUserSerializer(many=True)},
query_serializer=EventTopPerUserParams,
parameters=[
OpenApiParameter(
"top_n",
type=OpenApiTypes.INT,
location=OpenApiParameter.QUERY,
required=False,
)
],
)
@action(detail=False, methods=["GET"])
@action(detail=False, methods=["GET"], pagination_class=None)
def top_per_user(self, request: Request):
"""Get the top_n events grouped by user count"""
filtered_action = request.query_params.get("action", EventAction.LOGIN)
@ -134,7 +136,7 @@ class EventViewSet(ReadOnlyModelViewSet):
.order_by("-counted_events")[:top_n]
)
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])
def actions(self, request: Request) -> Response:
"""Get all actions"""

View File

@ -1,5 +1,6 @@
"""NotificationTransport API Views"""
from drf_yasg.utils import no_body, swagger_auto_schema
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.request import Request
@ -22,7 +23,7 @@ class NotificationTransportSerializer(ModelSerializer):
mode_verbose = SerializerMethodField()
def get_mode_verbose(self, instance: NotificationTransport):
def get_mode_verbose(self, instance: NotificationTransport) -> str:
"""Return selected mode with a UI Label"""
return TransportMode(instance.mode).label
@ -58,12 +59,12 @@ class NotificationTransportViewSet(ModelViewSet):
serializer_class = NotificationTransportSerializer
@permission_required("authentik_events.change_notificationtransport")
@swagger_auto_schema(
@extend_schema(
responses={
200: NotificationTransportTestSerializer(many=False),
503: "Failed to test transport",
500: OpenApiResponse(description="Failed to test transport"),
},
request_body=no_body,
request=OpenApiTypes.NONE,
)
@action(detail=True, pagination_class=None, filter_backends=[], methods=["post"])
# pylint: disable=invalid-name, unused-argument
@ -83,4 +84,4 @@ class NotificationTransportViewSet(ModelViewSet):
response.is_valid()
return Response(response.data)
except NotificationTransportError as exc:
return Response(str(exc.__cause__ or None), status=503)
return Response(str(exc.__cause__ or None), status=500)

View File

@ -6,8 +6,8 @@ from django.db.models import Model
from django.http.response import HttpResponseBadRequest, JsonResponse
from django.urls import reverse
from django.utils.translation import gettext as _
from drf_yasg import openapi
from drf_yasg.utils import no_body, swagger_auto_schema
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework.decorators import action
from rest_framework.parsers import MultiPartParser
@ -41,7 +41,7 @@ class FlowSerializer(ModelSerializer):
cache_count = SerializerMethodField()
def get_cache_count(self, flow: Flow):
def get_cache_count(self, flow: Flow) -> int:
"""Get count of cached flows"""
return len(cache.keys(f"{cache_key(flow)}*"))
@ -61,6 +61,9 @@ class FlowSerializer(ModelSerializer):
"cache_count",
"policy_engine_mode",
]
extra_kwargs = {
"background": {"read_only": True},
}
class FlowDiagramSerializer(Serializer):
@ -97,16 +100,19 @@ class FlowViewSet(ModelViewSet):
filterset_fields = ["flow_uuid", "name", "slug", "designation"]
@permission_required(None, ["authentik_flows.view_flow_cache"])
@swagger_auto_schema(responses={200: CacheSerializer(many=False)})
@extend_schema(responses={200: CacheSerializer(many=False)})
@action(detail=False, pagination_class=None, filter_backends=[])
def cache_info(self, request: Request) -> Response:
"""Info about cached flows"""
return Response(data={"count": len(cache.keys("flow_*"))})
@permission_required(None, ["authentik_flows.clear_flow_cache"])
@swagger_auto_schema(
request_body=no_body,
responses={204: "Successfully cleared cache", 400: "Bad request"},
@extend_schema(
request=OpenApiTypes.NONE,
responses={
204: OpenApiResponse(description="Successfully cleared cache"),
400: OpenApiResponse(description="Bad request"),
},
)
@action(detail=False, methods=["POST"])
def cache_clear(self, request: Request) -> Response:
@ -133,17 +139,20 @@ class FlowViewSet(ModelViewSet):
"authentik_stages_prompt.change_prompt",
],
)
@swagger_auto_schema(
request_body=no_body,
manual_parameters=[
openapi.Parameter(
@extend_schema(
request=OpenApiTypes.NONE,
parameters=[
OpenApiParameter(
name="file",
in_=openapi.IN_FORM,
type=openapi.TYPE_FILE,
location=OpenApiParameter.QUERY, # TODO: Form
type=OpenApiTypes.BINARY,
required=True,
)
],
responses={204: "Successfully imported flow", 400: "Bad request"},
responses={
204: OpenApiResponse(description="Successfully imported flow"),
400: OpenApiResponse(description="Bad request"),
},
)
@action(detail=False, methods=["POST"], parser_classes=(MultiPartParser,))
def import_flow(self, request: Request) -> Response:
@ -171,11 +180,9 @@ class FlowViewSet(ModelViewSet):
"authentik_stages_prompt.view_prompt",
],
)
@swagger_auto_schema(
@extend_schema(
responses={
"200": openapi.Response(
"File Attachment", schema=openapi.Schema(type=openapi.TYPE_FILE)
),
"200": OpenApiResponse(response=OpenApiTypes.BINARY),
},
)
@action(detail=True, pagination_class=None, filter_backends=[])
@ -188,7 +195,7 @@ class FlowViewSet(ModelViewSet):
response["Content-Disposition"] = f'attachment; filename="{flow.slug}.akflow"'
return response
@swagger_auto_schema(responses={200: FlowDiagramSerializer()})
@extend_schema(responses={200: FlowDiagramSerializer()})
@action(detail=True, pagination_class=None, filter_backends=[], methods=["get"])
# pylint: disable=unused-argument
def diagram(self, request: Request, slug: str) -> Response:
@ -259,17 +266,20 @@ class FlowViewSet(ModelViewSet):
return Response({"diagram": diagram})
@permission_required("authentik_flows.change_flow")
@swagger_auto_schema(
request_body=no_body,
manual_parameters=[
openapi.Parameter(
@extend_schema(
request=OpenApiTypes.NONE,
parameters=[
OpenApiParameter(
name="file",
in_=openapi.IN_FORM,
type=openapi.TYPE_FILE,
location=OpenApiParameter.QUERY, # TODO: Form
type=OpenApiTypes.BINARY,
required=True,
)
],
responses={200: "Success", 400: "Bad request"},
responses={
200: OpenApiResponse(description="Success"),
400: OpenApiResponse(description="Bad request"),
},
)
@action(
detail=True,
@ -289,8 +299,11 @@ class FlowViewSet(ModelViewSet):
app.save()
return Response({})
@swagger_auto_schema(
responses={200: LinkSerializer(many=False), 400: "Flow not applicable"},
@extend_schema(
responses={
200: LinkSerializer(many=False),
400: OpenApiResponse(description="Flow not applicable"),
},
)
@action(detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=unused-argument

View File

@ -1,7 +1,7 @@
"""Flow Stage API Views"""
from typing import Iterable
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from rest_framework import mixins
from rest_framework.decorators import action
from rest_framework.fields import BooleanField
@ -68,7 +68,7 @@ class StageViewSet(
def get_queryset(self):
return Stage.objects.select_subclasses()
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response:
"""Get all creatable stage types"""
@ -86,7 +86,7 @@ class StageViewSet(
data = sorted(data, key=lambda x: x["name"])
return Response(TypeCreateSerializer(data, many=True).data)
@swagger_auto_schema(responses={200: StageUserSettingSerializer(many=True)})
@extend_schema(responses={200: StageUserSettingSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])
def user_settings(self, request: Request) -> Response:
"""Get all stages the user can configure"""

View File

@ -10,8 +10,8 @@ from django.template.response import TemplateResponse
from django.utils.decorators import method_decorator
from django.views.decorators.clickjacking import xframe_options_sameorigin
from django.views.generic import View
from drf_yasg import openapi
from drf_yasg.utils import no_body, swagger_auto_schema
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework.permissions import AllowAny
from rest_framework.views import APIView
from sentry_sdk import capture_exception
@ -22,7 +22,6 @@ from authentik.events.models import cleanse_dict
from authentik.flows.challenge import (
AccessDeniedChallenge,
Challenge,
ChallengeResponse,
ChallengeTypes,
HttpChallengeResponse,
RedirectChallenge,
@ -125,19 +124,21 @@ class FlowExecutorView(APIView):
self.current_stage_view.request = request
return super().dispatch(request)
@swagger_auto_schema(
@extend_schema(
responses={
200: Challenge(),
404: "No Token found", # This error can be raised by the email stage
404: OpenApiResponse(
description="No Token found"
), # This error can be raised by the email stage
},
request_body=no_body,
manual_parameters=[
openapi.Parameter(
"query",
openapi.IN_QUERY,
request=OpenApiTypes.NONE,
parameters=[
OpenApiParameter(
name="query",
location=OpenApiParameter.QUERY,
required=True,
description="Querystring as received",
type=openapi.TYPE_STRING,
type=OpenApiTypes.STR,
)
],
operation_id="flows_executor_get",
@ -157,16 +158,16 @@ class FlowExecutorView(APIView):
self._logger.warning(exc)
return to_stage_response(request, FlowErrorResponse(request, exc))
@swagger_auto_schema(
@extend_schema(
responses={200: Challenge()},
request_body=ChallengeResponse(),
manual_parameters=[
openapi.Parameter(
"query",
openapi.IN_QUERY,
request=OpenApiTypes.OBJECT,
parameters=[
OpenApiParameter(
name="query",
location=OpenApiParameter.QUERY,
required=True,
description="Querystring as received",
type=openapi.TYPE_STRING,
type=OpenApiTypes.STR,
)
],
operation_id="flows_executor_solve",

View File

@ -2,7 +2,7 @@
from dataclasses import asdict
from django.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from kubernetes.client.configuration import Configuration
from kubernetes.config.config_exception import ConfigException
from kubernetes.config.kube_config import load_kube_config_from_dict
@ -69,7 +69,7 @@ class ServiceConnectionViewSet(
search_fields = ["name"]
filterset_fields = ["name"]
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response:
"""Get all creatable service connection types"""
@ -87,7 +87,7 @@ class ServiceConnectionViewSet(
)
return Response(TypeCreateSerializer(data, many=True).data)
@swagger_auto_schema(responses={200: ServiceConnectionStateSerializer(many=False)})
@extend_schema(responses={200: ServiceConnectionStateSerializer(many=False)})
@action(detail=True, pagination_class=None, filter_backends=[])
# pylint: disable=unused-argument, invalid-name
def state(self, request: Request, pk: str) -> Response:

View File

@ -1,7 +1,7 @@
"""Outpost API Views"""
from dacite.core import from_dict
from dacite.exceptions import DaciteError
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, DateTimeField
from rest_framework.request import Request
@ -72,8 +72,8 @@ class OutpostViewSet(ModelViewSet):
]
ordering = ["name"]
@swagger_auto_schema(responses={200: OutpostHealthSerializer(many=True)})
@action(methods=["GET"], detail=True)
@extend_schema(responses={200: OutpostHealthSerializer(many=True)})
@action(methods=["GET"], detail=True, pagination_class=None)
# pylint: disable=invalid-name, unused-argument
def health(self, request: Request, pk: int) -> Response:
"""Get outposts current health"""
@ -90,7 +90,7 @@ class OutpostViewSet(ModelViewSet):
)
return Response(OutpostHealthSerializer(states, many=True).data)
@swagger_auto_schema(responses={200: OutpostDefaultConfigSerializer(many=False)})
@extend_schema(responses={200: OutpostDefaultConfigSerializer(many=False)})
@action(detail=False, methods=["GET"])
def default_settings(self, request: Request) -> Response:
"""Global default outpost config"""

View File

@ -1,6 +1,7 @@
"""policy API Views"""
from django.core.cache import cache
from drf_yasg.utils import no_body, swagger_auto_schema
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiResponse, extend_schema
from guardian.shortcuts import get_objects_for_user
from rest_framework import mixins
from rest_framework.decorators import action
@ -96,7 +97,7 @@ class PolicyViewSet(
"bindings", "promptstage_set"
)
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])
def types(self, request: Request) -> Response:
"""Get all creatable policy types"""
@ -114,16 +115,19 @@ class PolicyViewSet(
return Response(TypeCreateSerializer(data, many=True).data)
@permission_required(None, ["authentik_policies.view_policy_cache"])
@swagger_auto_schema(responses={200: CacheSerializer(many=False)})
@extend_schema(responses={200: CacheSerializer(many=False)})
@action(detail=False, pagination_class=None, filter_backends=[])
def cache_info(self, request: Request) -> Response:
"""Info about cached policies"""
return Response(data={"count": len(cache.keys("policy_*"))})
@permission_required(None, ["authentik_policies.clear_policy_cache"])
@swagger_auto_schema(
request_body=no_body,
responses={204: "Successfully cleared cache", 400: "Bad request"},
@extend_schema(
request=OpenApiTypes.NONE,
responses={
204: OpenApiResponse(description="Successfully cleared cache"),
400: OpenApiResponse(description="Bad request"),
},
)
@action(detail=False, methods=["POST"])
def cache_clear(self, request: Request) -> Response:
@ -137,9 +141,12 @@ class PolicyViewSet(
return Response(status=204)
@permission_required("authentik_policies.view_policy")
@swagger_auto_schema(
request_body=PolicyTestSerializer(),
responses={200: PolicyTestResultSerializer(), 400: "Invalid parameters"},
@extend_schema(
request=PolicyTestSerializer(),
responses={
200: PolicyTestResultSerializer(),
400: OpenApiResponse(description="Invalid parameters"),
},
)
@action(detail=True, pagination_class=None, filter_backends=[], methods=["POST"])
# pylint: disable=unused-argument, invalid-name

View File

@ -1,7 +1,7 @@
"""OAuth2Provider API Views"""
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import ReadOnlyField
from rest_framework.generics import get_object_or_404
@ -67,10 +67,10 @@ class OAuth2ProviderViewSet(ModelViewSet):
queryset = OAuth2Provider.objects.all()
serializer_class = OAuth2ProviderSerializer
@swagger_auto_schema(
@extend_schema(
responses={
200: OAuth2ProviderSetupURLs(many=False),
404: "Provider has no application assigned",
200: OAuth2ProviderSetupURLs,
404: OpenApiResponse(description="Provider has no application assigned"),
}
)
@action(methods=["GET"], detail=True)

View File

@ -1,7 +1,7 @@
"""ProxyProvider API Views"""
from typing import Any
from drf_yasg.utils import swagger_serializer_method
from drf_spectacular.utils import extend_schema_field
from rest_framework.exceptions import ValidationError
from rest_framework.fields import CharField, ListField, SerializerMethodField
from rest_framework.serializers import ModelSerializer
@ -107,7 +107,7 @@ class ProxyOutpostConfigSerializer(ModelSerializer):
"forward_auth_mode",
]
@swagger_serializer_method(serializer_or_field=OpenIDConnectConfigurationSerializer)
@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)

View File

@ -5,8 +5,8 @@ from defusedxml.ElementTree import fromstring
from django.http.response import HttpResponse
from django.shortcuts import get_object_or_404
from django.utils.translation import gettext_lazy as _
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.fields import CharField, FileField, ReadOnlyField
from rest_framework.parsers import MultiPartParser
@ -80,16 +80,16 @@ class SAMLProviderViewSet(ModelViewSet):
queryset = SAMLProvider.objects.all()
serializer_class = SAMLProviderSerializer
@swagger_auto_schema(
@extend_schema(
responses={
200: SAMLMetadataSerializer(many=False),
404: "Provider has no application assigned",
404: OpenApiResponse(description="Provider has no application assigned"),
},
manual_parameters=[
openapi.Parameter(
parameters=[
OpenApiParameter(
name="download",
in_=openapi.IN_QUERY,
type=openapi.TYPE_BOOLEAN,
location=OpenApiParameter.QUERY,
type=OpenApiTypes.BOOL,
)
],
)
@ -118,9 +118,12 @@ class SAMLProviderViewSet(ModelViewSet):
"authentik_crypto.add_certificatekeypair",
],
)
@swagger_auto_schema(
request_body=SAMLProviderImportSerializer(),
responses={204: "Successfully imported provider", 400: "Bad request"},
@extend_schema(
request=SAMLProviderImportSerializer(),
responses={
204: OpenApiResponse(description="Successfully imported provider"),
400: OpenApiResponse(description="Bad request"),
},
)
@action(detail=False, methods=["POST"], parser_classes=(MultiPartParser,))
def import_metadata(self, request: Request) -> Response:

View File

@ -128,7 +128,7 @@ INSTALLED_APPS = [
"authentik.stages.user_write",
"rest_framework",
"django_filters",
"drf_yasg",
"drf_spectacular",
"guardian",
"django_prometheus",
"channels",
@ -137,15 +137,28 @@ INSTALLED_APPS = [
GUARDIAN_MONKEY_PATCH = False
SWAGGER_SETTINGS = {
"DEFAULT_AUTO_SCHEMA_CLASS": "authentik.api.schema.ErrorResponseAutoSchema",
"DEFAULT_INFO": "authentik.api.v2.urls.info",
"DEFAULT_PAGINATOR_INSPECTORS": [
"authentik.api.pagination_schema.PaginationInspector",
],
"SECURITY_DEFINITIONS": {
"Bearer": {"type": "apiKey", "name": "Authorization", "in": "header"}
SPECTACULAR_SETTINGS = {
"TITLE": "authentik",
"DESCRIPTION": "Making authentication simple.",
"VERSION": __version__,
"COMPONENT_SPLIT_REQUEST": True,
"CONTACT": {
"email": "hello@beryju.org",
},
"LICENSE": {
"name": "GNU GPLv3",
"url": "https://github.com/goauthentik/authentik/blob/master/LICENSE",
},
"ENUM_NAME_OVERRIDES": {
"ChallengeChoices": "authentik.flows.challenge.ChallengeTypes",
"FlowDesignationEnum": "authentik.flows.models.FlowDesignation",
"PolicyEngineMode": "authentik.policies.models.PolicyEngineMode",
},
"ENUM_ADD_EXPLICIT_BLANK_NULL_CHOICE": False,
"POSTPROCESSING_HOOKS": [
"authentik.api.schema.postprocess_schema_responses",
"drf_spectacular.hooks.postprocess_schema_enums",
],
}
REST_FRAMEWORK = {
@ -167,6 +180,7 @@ REST_FRAMEWORK = {
"DEFAULT_RENDERER_CLASSES": [
"rest_framework.renderers.JSONRenderer",
],
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
}
CACHES = {
@ -446,7 +460,6 @@ _LOGGING_HANDLER_MAP = {
"kubernetes": "INFO",
"asyncio": "WARNING",
"aioredis": "WARNING",
"drf_yasg.utils": "WARNING",
}
for handler_name, level in _LOGGING_HANDLER_MAP.items():
# pyright: reportGeneralTypeIssues=false

View File

@ -1,7 +1,7 @@
"""Source API Views"""
from django.http.response import Http404
from django.utils.text import slugify
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
@ -48,8 +48,11 @@ class LDAPSourceViewSet(ModelViewSet):
serializer_class = LDAPSourceSerializer
lookup_field = "slug"
@swagger_auto_schema(
responses={200: TaskSerializer(many=False), 404: "Task not found"}
@extend_schema(
responses={
200: TaskSerializer(many=False),
404: OpenApiResponse(description="Task not found"),
}
)
@action(methods=["GET"], detail=True)
# pylint: disable=unused-argument

View File

@ -1,6 +1,6 @@
"""OAuth Source Serializer"""
from django.urls.base import reverse_lazy
from drf_yasg.utils import swagger_auto_schema, swagger_serializer_method
from drf_spectacular.utils import extend_schema, extend_schema_field
from rest_framework.decorators import action
from rest_framework.fields import BooleanField, CharField, SerializerMethodField
from rest_framework.request import Request
@ -43,7 +43,7 @@ class OAuthSourceSerializer(SourceSerializer):
type = SerializerMethodField()
@swagger_serializer_method(serializer_or_field=SourceTypeSerializer)
@extend_schema_field(SourceTypeSerializer)
def get_type(self, instace: OAuthSource) -> SourceTypeSerializer:
"""Get source's type configuration"""
return SourceTypeSerializer(instace.type).data
@ -85,7 +85,7 @@ class OAuthSourceViewSet(ModelViewSet):
serializer_class = OAuthSourceSerializer
lookup_field = "slug"
@swagger_auto_schema(responses={200: SourceTypeSerializer(many=True)})
@extend_schema(responses={200: SourceTypeSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])
def source_types(self, request: Request) -> Response:
"""Get all creatable source types"""

View File

@ -1,7 +1,7 @@
"""Plex Source Serializer"""
from django.shortcuts import get_object_or_404
from drf_yasg import openapi
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.types import OpenApiTypes
from drf_spectacular.utils import OpenApiParameter, OpenApiResponse, extend_schema
from rest_framework.decorators import action
from rest_framework.exceptions import PermissionDenied
from rest_framework.fields import CharField
@ -50,18 +50,18 @@ class PlexSourceViewSet(ModelViewSet):
lookup_field = "slug"
@permission_required(None)
@swagger_auto_schema(
request_body=PlexTokenRedeemSerializer(),
@extend_schema(
request=PlexTokenRedeemSerializer(),
responses={
200: RedirectChallenge(),
400: "Token not found",
403: "Access denied",
400: OpenApiResponse(description="Token not found"),
403: OpenApiResponse(description="Access denied"),
},
manual_parameters=[
openapi.Parameter(
parameters=[
OpenApiParameter(
name="slug",
in_=openapi.IN_QUERY,
type=openapi.TYPE_STRING,
location=OpenApiParameter.QUERY,
type=OpenApiTypes.STR,
)
],
)

View File

@ -1,5 +1,5 @@
"""SAMLSource API Views"""
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
@ -39,7 +39,7 @@ class SAMLSourceViewSet(ModelViewSet):
serializer_class = SAMLSourceSerializer
lookup_field = "slug"
@swagger_auto_schema(responses={200: SAMLMetadataSerializer(many=False)})
@extend_schema(responses={200: SAMLMetadataSerializer(many=False)})
@action(methods=["GET"], detail=True)
# pylint: disable=unused-argument
def metadata(self, request: Request, slug: str) -> Response:

View File

@ -1,6 +1,6 @@
"""AuthenticatorStaticStage API Views"""
from django_filters.rest_framework import DjangoFilterBackend
from django_otp.plugins.otp_static.models import StaticDevice
from django_otp.plugins.otp_static.models import StaticDevice, StaticToken
from guardian.utils import get_anonymous_user
from rest_framework.filters import OrderingFilter, SearchFilter
from rest_framework.permissions import IsAdminUser
@ -27,14 +27,24 @@ class AuthenticatorStaticStageViewSet(ModelViewSet):
serializer_class = AuthenticatorStaticStageSerializer
class StaticDeviceTokenSerializer(ModelSerializer):
"""Serializer for static device's tokens"""
class Meta:
model = StaticToken
fields = ["token"]
class StaticDeviceSerializer(ModelSerializer):
"""Serializer for static authenticator devices"""
token_set = StaticDeviceTokenSerializer(many=True, read_only=True)
class Meta:
model = StaticDevice
fields = ["name", "token_set", "pk"]
depth = 2
class StaticDeviceViewSet(ModelViewSet):

View File

@ -1,5 +1,5 @@
"""EmailStage API Views"""
from drf_yasg.utils import swagger_auto_schema
from drf_spectacular.utils import extend_schema
from rest_framework.decorators import action
from rest_framework.request import Request
from rest_framework.response import Response
@ -52,7 +52,7 @@ class EmailStageViewSet(ModelViewSet):
queryset = EmailStage.objects.all()
serializer_class = EmailStageSerializer
@swagger_auto_schema(responses={200: TypeCreateSerializer(many=True)})
@extend_schema(responses={200: TypeCreateSerializer(many=True)})
@action(detail=False, pagination_class=None, filter_backends=[])
def templates(self, request: Request) -> Response:
"""Get all available templates, including custom templates"""

View File

@ -305,8 +305,7 @@ stages:
displayName: Build static files for e2e
inputs:
script: |
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/api --additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0
sudo chmod 777 -R web/api/
make gen-web
cd web
cd api && npm i && cd ..
npm i
@ -394,36 +393,19 @@ stages:
- task: CmdLine@2
inputs:
script: bash <(curl -s https://codecov.io/bash)
- stage: generate
jobs:
- job: swagger_generate
pool:
vmImage: 'ubuntu-latest'
steps:
- task: NodeTool@0
inputs:
versionSpec: '16.x'
displayName: 'Install Node.js'
- task: CmdLine@2
inputs:
script: |
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/api --additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'web/api/'
artifact: 'ts_swagger_client'
publishLocation: 'pipeline'
- stage: Build
jobs:
- job: build_server
pool:
vmImage: 'ubuntu-latest'
steps:
- task: DownloadPipelineArtifact@2
- task: NodeTool@0
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
path: "web/api/"
versionSpec: '16.x'
displayName: 'Install Node.js'
- task: CmdLine@2
inputs:
script: make gen-web
- task: Bash@3
inputs:
targetType: 'inline'

3
outpost/.gitignore vendored
View File

@ -1,2 +1 @@
pkg/client/
pkg/models/
api/

View File

@ -1,8 +1,4 @@
all: clean generate
generate:
go get -u github.com/go-swagger/go-swagger/cmd/swagger
swagger generate client -f ../swagger.yaml -A authentik -t pkg/
all: clean
run:
go run -v .

View File

@ -1,13 +1,16 @@
# authentik outpost
[![CI Build status](https://img.shields.io/azure-devops/build/beryjuorg/authentik/3?style=flat-square)](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=8)
[![CI Build status](https://img.shields.io/azure-devops/build/beryjuorg/authentik/8?style=flat-square)](https://dev.azure.com/beryjuorg/authentik/_build?definitionId=8)
![Docker pulls (proxy)](https://img.shields.io/docker/pulls/beryju/authentik-proxy.svg?style=flat-square)
![Docker pulls (ldap)](https://img.shields.io/docker/pulls/beryju/authentik-ldap.svg?style=flat-square)
Reverse Proxy based on [oauth2_proxy](https://github.com/oauth2-proxy/oauth2-proxy), completely managed and monitored by authentik.
LDAP Server using [ldap](https://github.com/nmcclain/ldap), completely managed and monitored by authentik.
## Usage
authentik Proxy is built to be configured by authentik itself, hence the only options you can directly give it are connection params.
authentik Outpost is built to be configured by authentik itself, hence the only options you can directly give it are connection params.
The following environment variable are implemented:
@ -19,6 +22,6 @@ The following environment variable are implemented:
## Development
authentik Proxy uses an auto-generated API Client to communicate with authentik. This client is not kept in git. To generate the client locally, run `make generate`.
authentik outpost uses an auto-generated API Client to communicate with authentik. This client is not kept in git. To generate the client locally, run `make gen-outpost` in the root directory of the repo.
Afterwards you can build the proxy like any other Go project, using `go build`.
Afterwards you can build the outpost like any other Go project, using `go build ./cmd/proxy/server.go` or `go build ./cmd/ldap/server.go`.

View File

@ -15,7 +15,7 @@ variables:
stages:
- stage: generate
jobs:
- job: swagger_generate
- job: generate_api
pool:
vmImage: 'ubuntu-latest'
steps:
@ -24,16 +24,11 @@ stages:
version: '1.16.3'
- task: CmdLine@2
inputs:
script: |
sudo wget -O /usr/local/bin/swagger https://github.com/go-swagger/go-swagger/releases/latest/download/swagger_linux_amd64
sudo chmod +x /usr/local/bin/swagger
mkdir -p $(go env GOPATH)
swagger generate client -f ../swagger.yaml -A authentik -t pkg/
workingDirectory: 'outpost/'
script: make gen-outpost
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'outpost/pkg/'
artifact: 'go_swagger_client'
targetPath: 'outpost/api/'
artifact: 'go_api_client'
publishLocation: 'pipeline'
- stage: lint
jobs:
@ -47,12 +42,17 @@ stages:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'go_swagger_client'
path: "outpost/pkg/"
artifactName: 'go_api_client'
path: "outpost/api/"
- task: CmdLine@2
inputs:
script: |
docker run --rm -v $(pwd):/app -w /app golangci/golangci-lint:v1.39.0 golangci-lint run -v --timeout 200s
docker run \
--rm \
-v $(pwd):/app \
-w /app \
golangci/golangci-lint:v1.39.0 \
golangci-lint run -v --timeout 200s
workingDirectory: 'outpost/'
- stage: build_go
jobs:
@ -66,8 +66,8 @@ stages:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'go_swagger_client'
path: "outpost/pkg/"
artifactName: 'go_api_client'
path: "outpost/api/"
- task: Go@0
inputs:
command: 'build'
@ -83,8 +83,8 @@ stages:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'go_swagger_client'
path: "outpost/pkg/"
artifactName: 'go_api_client'
path: "outpost/api/"
- task: Go@0
inputs:
command: 'build'
@ -102,8 +102,8 @@ stages:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'go_swagger_client'
path: "outpost/pkg/"
artifactName: 'go_api_client'
path: "outpost/api/"
- task: Bash@3
inputs:
targetType: 'inline'
@ -138,8 +138,8 @@ stages:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'go_swagger_client'
path: "outpost/pkg/"
artifactName: 'go_api_client'
path: "outpost/api/"
- task: Bash@3
inputs:
targetType: 'inline'

View File

@ -1,23 +1,21 @@
module goauthentik.io/outpost
go 1.14
go 1.16
require (
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d // indirect
github.com/coreos/go-oidc v2.2.1+incompatible
github.com/felixge/httpsnoop v1.0.2 // indirect
github.com/getsentry/sentry-go v0.10.0
github.com/go-ldap/ldap/v3 v3.3.0
github.com/go-openapi/analysis v0.20.1 // indirect
github.com/go-openapi/errors v0.20.0
github.com/go-openapi/errors v0.20.0 // indirect
github.com/go-openapi/runtime v0.19.28
github.com/go-openapi/strfmt v0.20.1
github.com/go-openapi/swag v0.19.15
github.com/go-openapi/validate v0.20.2
github.com/go-openapi/swag v0.19.15 // indirect
github.com/go-openapi/validate v0.20.2 // indirect
github.com/go-redis/redis/v7 v7.4.0 // indirect
github.com/go-swagger/go-swagger v0.27.0 // indirect
github.com/golang/protobuf v1.5.2 // indirect
github.com/google/uuid v1.2.0 // indirect
github.com/google/uuid v1.2.0
github.com/gorilla/websocket v1.4.2
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a
github.com/justinas/alice v1.2.0
@ -25,9 +23,9 @@ require (
github.com/magiconair/properties v1.8.5 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/nmcclain/asn1-ber v0.0.0-20170104154839-2661553a0484 // indirect
github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3 // indirect
github.com/nmcclain/ldap v0.0.0-20191021200707-3b3b69a7e9e3
github.com/oauth2-proxy/oauth2-proxy v0.0.0-20200831161845-e4e5580852dc
github.com/pelletier/go-toml v1.9.0 // indirect
github.com/pelletier/go-toml v1.9.1 // indirect
github.com/pkg/errors v0.9.1
github.com/pquerna/cachecontrol v0.0.0-20201205024021-ac21108117ac // indirect
github.com/recws-org/recws v1.3.1
@ -37,10 +35,11 @@ require (
github.com/spf13/jwalterweatherman v1.1.0 // indirect
github.com/spf13/pflag v1.0.5 // indirect
github.com/spf13/viper v1.7.1 // indirect
go.mongodb.org/mongo-driver v1.5.2 // indirect
golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2 // indirect
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 // indirect
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558 // indirect
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83 // indirect
golang.org/x/net v0.0.0-20210510120150-4163338589ed // indirect
golang.org/x/oauth2 v0.0.0-20210323180902-22b0adad7558
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 // indirect
google.golang.org/appengine v1.6.7 // indirect
gopkg.in/ini.v1 v1.62.0 // indirect
gopkg.in/square/go-jose.v2 v2.5.1 // indirect

View File

@ -43,7 +43,6 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
github.com/CloudyKit/fastprinter v0.0.0-20200109182630-33d98a066a53/go.mod h1:+3IMCy2vIlbG1XG/0ggNQv0SvxCAIpPM5b1nCz56Xno=
github.com/CloudyKit/jet/v3 v3.0.0/go.mod h1:HKQPgSJmdK8hdoAbKUUWajkHyHo4RaU5rMdUywE7VMo=
github.com/FZambia/sentinel v1.0.0 h1:KJ0ryjKTZk5WMp0dXvSdNqp3lFaW1fNFuEYfrkLOYIc=
github.com/FZambia/sentinel v1.0.0/go.mod h1:ytL1Am/RLlAoAXG6Kj5LNuw/TRRQrv2rt2FT26vP5gI=
github.com/Joker/hpp v1.0.0/go.mod h1:8x5n+M1Hp5hC0g8okX3sR3vFQwynaX/UgSOM9MeBKzY=
github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
@ -104,7 +103,6 @@ github.com/coreos/go-semver v0.3.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3Ee
github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/coreos/pkg v0.0.0-20180928190104-399ea9e2e55f/go.mod h1:E3G3o1h8I7cfcXa63jLwjI0eiQQMgzzUDFVpN/nH/eA=
github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@ -126,9 +124,6 @@ github.com/etcd-io/bbolt v1.3.3/go.mod h1:ZF2nL25h33cCyBtcyWeZ2/I3HQOfTP+0PIEvHj
github.com/fasthttp-contrib/websocket v0.0.0-20160511215533-1f3b11f56072/go.mod h1:duJ4Jxv5lDcvg4QuQr0oowTf7dz4/CR8NtyCooz9HL8=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/fatih/structs v1.1.0/go.mod h1:9NiDSp5zOcgEDl+j00MP/WkGVPOlPRLejGD8Ga6PJ7M=
github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/felixge/httpsnoop v1.0.2 h1:+nS9g82KMXccJ/wp0zyRW9ZBHFETmMGtkk+2CTTrW4o=
github.com/felixge/httpsnoop v1.0.2/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
github.com/frankban/quicktest v1.10.0 h1:Gfh+GAJZOAoKZsIZeZbdn2JF10kN1XHNvjsvQK8gVkE=
github.com/frankban/quicktest v1.10.0/go.mod h1:ui7WezCLWMWxVWr1GETZY3smRy0G4KWq9vcPtJmFl7Y=
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
@ -177,8 +172,6 @@ github.com/go-openapi/errors v0.19.8/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpX
github.com/go-openapi/errors v0.19.9/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/errors v0.20.0 h1:Sxpo9PjEHDzhs3FbnGNonvDgWcMW2U7wGTcDDSFSceM=
github.com/go-openapi/errors v0.20.0/go.mod h1:cM//ZKUKyO06HSwqAelJ5NsEMMcpa6VpXe8DOa1Mi1M=
github.com/go-openapi/inflect v0.19.0 h1:9jCH9scKIbHeV9m12SmPilScz6krDxKRasNNSNPXu/4=
github.com/go-openapi/inflect v0.19.0/go.mod h1:lHpZVlpIQqLyKwJ4N+YSc9hchQy/i12fJykb83CRBH4=
github.com/go-openapi/jsonpointer v0.17.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.18.0/go.mod h1:cOnomiV+CVVwFLk0A/MExoFMjwdsUdVpsRhURCKh+3M=
github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg=
@ -208,7 +201,6 @@ github.com/go-openapi/runtime v0.19.4/go.mod h1:X277bwSUBxVlCYR3r7xgZZGKVvBd/29g
github.com/go-openapi/runtime v0.19.15/go.mod h1:dhGWCTKRXlAfGnQG0ONViOZpjfg0m2gUt9nTQPQZuoo=
github.com/go-openapi/runtime v0.19.16/go.mod h1:5P9104EJgYcizotuXhEuUrzVc+j1RiSjahULvYmlv98=
github.com/go-openapi/runtime v0.19.24/go.mod h1:Lm9YGCeecBnUUkFTxPC4s1+lwrkJ0pthx8YvyjCfkgk=
github.com/go-openapi/runtime v0.19.27/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
github.com/go-openapi/runtime v0.19.28 h1:9lYu6axek8LJrVkMVViVirRcpoaCxXX7+sSvmizGVnA=
github.com/go-openapi/runtime v0.19.28/go.mod h1:BvrQtn6iVb2QmiVXRsFAm6ZCAZBpbVKFfN6QWCp582M=
github.com/go-openapi/spec v0.17.0/go.mod h1:XkF/MOi14NmjsfZ8VtAKf8pIlbZzyoTvZsdfssdxcBI=
@ -259,9 +251,6 @@ github.com/go-redis/redis/v7 v7.4.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRf
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
github.com/go-stack/stack v1.8.0 h1:5SgMzNM5HxrEjV0ww2lTmX6E2Izsfxas4+YHWRs3Lsk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/go-swagger/go-swagger v0.27.0 h1:K7+nkBuf4oS1jTBrdvWqYFpqD69V5CN8HamZzCDDhAI=
github.com/go-swagger/go-swagger v0.27.0/go.mod h1:WodZVysInJilkW7e6IRw+dZGp5yW6rlMFZ4cb+THl9A=
github.com/go-swagger/scan-repo-boundary v0.0.0-20180623220736-973b3573c013/go.mod h1:b65mBPzqzZWxOZGxSWrqs4GInLIn+u99Q9q7p+GKni0=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
@ -323,7 +312,6 @@ github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiu
github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
github.com/gomodule/redigo v1.7.1-0.20190322064113-39e2c31b7ca3/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/gomodule/redigo v1.7.1-0.20190724094224-574c33c3df38/go.mod h1:B4C85qUVwatsJoIUNIfCRsp7qO0iAmpGFZ4EELWSbC4=
github.com/gomodule/redigo v1.8.1 h1:Abmo0bI7Xf0IhdIPc7HZQzZcShdnmxeoVuDDtIQp8N8=
github.com/gomodule/redigo v1.8.1/go.mod h1:P9dn9mFrCBvWhGE1wpxx6fgq7BAeLBk+UUUzlpkBYO0=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@ -350,7 +338,6 @@ github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hf
github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM=
github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI=
github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.2.0 h1:qJYtXnJRWmpe7m/3XlyhrsLrEURqHRM2kxzoxXqyUDs=
github.com/google/uuid v1.2.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
@ -359,8 +346,6 @@ github.com/googleapis/gax-go/v2 v2.0.5 h1:sjZBwGj9Jlw33ImPtvFviGYvseOtDM7hkSKB7+
github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoAMk2YaljkQ=
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
@ -399,8 +384,6 @@ github.com/iris-contrib/go.uuid v2.0.0+incompatible/go.mod h1:iz2lgM/1UnEf1kP0L/
github.com/iris-contrib/jade v1.1.3/go.mod h1:H/geBymxJhShH5kecoiOCSssPX7QWYH7UaeZTSWddIk=
github.com/iris-contrib/pongo2 v0.0.1/go.mod h1:Ssh+00+3GAZqSQb30AvBRNxBx7rf0GqwkjqxNd0u65g=
github.com/iris-contrib/schema v0.0.1/go.mod h1:urYA3uvUNG1TIIjOSCzHr9/LmbQo8LrOcOqfqxa4hXw=
github.com/jessevdk/go-flags v1.5.0 h1:1jKYvbxEjfUl0fmqTCOfonvskHHXMjBySTLW4y9LFvc=
github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a h1:zPPuIq2jAWWPTrGt70eK/BSch+gFAGrNzecsoENgu2o=
github.com/jinzhu/copier v0.0.0-20190924061706-b57f9002281a/go.mod h1:yL958EeXv8Ylng6IfnvG4oflryUi3vgA3xPs9hmII1s=
github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
@ -464,7 +447,6 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/markbates/oncer v0.0.0-20181203154359-bf2de49a0be2/go.mod h1:Ld9puTsIW75CHf65OeIOkyKbteujpZVXDpWK6YGZbxE=
github.com/markbates/safe v1.0.1/go.mod h1:nAqgmRi7cY2nqMc92/bSEeQA+R4OheNU2T1kNSCBdG0=
github.com/matryer/is v1.2.0 h1:92UTHpy8CDwaJ08GqLDzhhuixiBUUD1p3AU6PHddz4A=
github.com/matryer/is v1.2.0/go.mod h1:2fLPjFQM9rhQ15aVEtbuwhJinnOqrmgXPNdZsdwlWXA=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
@ -532,9 +514,8 @@ github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtP
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.4.0/go.mod h1:PN7xzY2wHTK0K9p34ErDQMlFxa51Fk0OUruD3k1mMwo=
github.com/pelletier/go-toml v1.7.0/go.mod h1:vwGMzjaWMwyfHwgIBhI2YUM4fB6nL6lVAvS1LBMMhTE=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
github.com/pelletier/go-toml v1.9.0 h1:NOd0BRdOKpPf0SxkL3HxSQOG7rNh+4kl6PHcBPFs7Q0=
github.com/pelletier/go-toml v1.9.0/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pelletier/go-toml v1.9.1 h1:a6qW1EVNZWH9WGI6CsYdD8WAylkoXBS5yv0XHlh17Tc=
github.com/pelletier/go-toml v1.9.1/go.mod h1:u1nR/EPcESfeI/szUZKdtJ0xRNbUoANCkoOuaOx1Y+c=
github.com/pierrec/lz4 v2.5.2+incompatible h1:WCjObylUIOlKy/+7Abdn34TLIkXiA4UWUMhxq9m9ZXI=
github.com/pierrec/lz4 v2.5.2+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
github.com/pingcap/errors v0.11.4 h1:lFuQV/oaUMGcD2tqt+01ROSmJs75VG1ToEOkZIZ4nE4=
@ -567,7 +548,6 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/ryanuber/columnize v2.1.0+incompatible/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
github.com/schollz/closestmatch v2.1.0+incompatible/go.mod h1:RtP1ddjLong6gTkbtmuhtR2uUrrJOpYzYRvbcPAid+g=
@ -594,7 +574,6 @@ github.com/spf13/cast v1.3.1 h1:nFm6S0SMdyzrzcmThSipiEubIDy8WEXKNZ0UOgiRpng=
github.com/spf13/cast v1.3.1/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE=
github.com/spf13/cobra v0.0.3/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ=
github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU=
github.com/spf13/cobra v1.1.3/go.mod h1:pGADOWyqRD/YMrPZigI/zbliZ2wVD/23d+is3pSWzOo=
github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo=
github.com/spf13/jwalterweatherman v1.1.0 h1:ue6voC5bR5F8YxI5S67j9i582FU4Qvo2bmqnqMYADFk=
github.com/spf13/jwalterweatherman v1.1.0/go.mod h1:aNWZUN0dPAAO/Ljvb5BEdw96iTZ0EXowPYD95IqWIGo=
@ -603,7 +582,6 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s=
github.com/spf13/viper v1.6.3/go.mod h1:jUMtyi0/lB5yZH/FjyGAoH7IMNrIhlBf6pXZmbMDvzw=
github.com/spf13/viper v1.7.0/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/spf13/viper v1.7.1 h1:pM5oEahlgWv/WnHXpgbKz7iLIxRf65tye2Ci+XFK5sk=
github.com/spf13/viper v1.7.1/go.mod h1:8WkrPz2fc9jxqZNCJI/76HCieCp4Q8HaLFoCha5qpdg=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -621,8 +599,6 @@ github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/tmc/grpc-websocket-proxy v0.0.0-20190109142713-0ad062ec5ee5/go.mod h1:ncp9v5uamzpCO7NfCPTXjqaC+bZgJeR0sMTm6dMHP7U=
github.com/toqueteos/webbrowser v1.2.0 h1:tVP/gpK69Fx+qMJKsLE7TD8LuGWPnEV71wBN9rrstGQ=
github.com/toqueteos/webbrowser v1.2.0/go.mod h1:XWoZq4cyp9WeUeak7w7LXRUQf1F1ATJMir8RTqb4ayM=
github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
@ -657,7 +633,6 @@ github.com/yudai/pp v2.0.1+incompatible/go.mod h1:PuxR/8QJ7cyCkFp/aUDS+JY727OFEZ
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/gopher-lua v0.0.0-20190206043414-8bfc7677f583/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20191213034115-f46add6fdb5c/go.mod h1:gqRgreBUhTSL0GeU64rtZ3Uq3wtjOa/TB2YfrtkCbVQ=
github.com/yuin/gopher-lua v0.0.0-20191220021717-ab39c6098bdb h1:ZkM6LRnq40pR1Ox0hTHlnpkcOTuFIDQpZ1IN8rKKhX0=
@ -670,8 +645,9 @@ go.mongodb.org/mongo-driver v1.3.4/go.mod h1:MSWZXKOynuguX+JSvwP8i+58jYCXxbia8HS
go.mongodb.org/mongo-driver v1.4.3/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.4.4/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.4.6/go.mod h1:WcMNYLx/IlOxLe6JRJiv2uXuCz6zBLndR4SoGjYphSc=
go.mongodb.org/mongo-driver v1.5.1 h1:9nOVLGDfOaZ9R0tBumx/BcuqkbFpyTCU2r/Po7A2azI=
go.mongodb.org/mongo-driver v1.5.1/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
go.mongodb.org/mongo-driver v1.5.2 h1:AsxOLoJTgP6YNM0fXWw4OjdluYmWzQYp+lFJL7xu9fU=
go.mongodb.org/mongo-driver v1.5.2/go.mod h1:gRXCHX4Jo7J0IJ1oDQyUxF7jfy19UfxniMS4xxMmUqw=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@ -731,8 +707,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@ -773,15 +747,13 @@ golang.org/x/net v0.0.0-20200602114024-627f9648deb9/go.mod h1:qpuaurCH72eLCgpAm/
golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201110031124-69a78807bb2b/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201202161906-c7110b5ffcbb/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210119194325-5f4716e94777/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210331060903-cb1fcc7394e5/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6 h1:0PC75Fz/kyMGhL0e1QnypqK2kQMqKt9csD1GnMJR+Zk=
golang.org/x/net v0.0.0-20210423184538-5f58ad60dda6/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@ -798,7 +770,6 @@ golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJ
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20180823144017-11551d06cbcc/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@ -849,12 +820,9 @@ golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83 h1:kHSDPqCtsHZOg0nVylfTo20DDhE9gG4Y0jn7hKQ0QAM=
golang.org/x/sys v0.0.0-20210426080607-c94f62235c83/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015 h1:hZR0X1kPW+nwyJ9xRxqZk1vx5RUObAPBdKVvXPDUH/E=
golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@ -921,8 +889,6 @@ golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roY
golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA=
golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=

View File

@ -1,21 +1,21 @@
package ak
import (
"context"
"fmt"
"math/rand"
"net/http"
"net/url"
"os"
"time"
"github.com/go-openapi/runtime"
"github.com/go-openapi/strfmt"
"github.com/google/uuid"
"github.com/pkg/errors"
"github.com/recws-org/recws"
"goauthentik.io/outpost/api"
"goauthentik.io/outpost/pkg"
"goauthentik.io/outpost/pkg/client"
"goauthentik.io/outpost/pkg/client/outposts"
httptransport "github.com/go-openapi/runtime/client"
"github.com/go-openapi/strfmt"
log "github.com/sirupsen/logrus"
)
@ -25,8 +25,7 @@ const ConfigErrorReportingEnvironment = "error_reporting_environment"
// APIController main controller which connects to the authentik api via http and ws
type APIController struct {
Client *client.Authentik
Auth runtime.ClientAuthInfoWriter
Client *api.APIClient
token string
Server Outpost
@ -41,31 +40,32 @@ type APIController struct {
// NewAPIController initialise new API Controller instance from URL and API token
func NewAPIController(akURL url.URL, token string) *APIController {
transport := httptransport.New(akURL.Host, client.DefaultBasePath, []string{akURL.Scheme})
transport.Transport = SetUserAgent(GetTLSTransport(), pkg.UserAgent())
// create the transport
auth := httptransport.BearerToken(token)
config := api.NewConfiguration()
config.Host = akURL.Host
config.Scheme = akURL.Scheme
config.HTTPClient = &http.Client{
Transport: SetUserAgent(GetTLSTransport(), pkg.UserAgent()),
}
config.AddDefaultHeader("Authorization", fmt.Sprintf("Bearer %s", token))
// create the API client, with the transport
apiClient := client.New(transport, strfmt.Default)
apiClient := api.NewAPIClient(config)
log := log.WithField("logger", "authentik.outpost.ak-api-controller")
// Because we don't know the outpost UUID, we simply do a list and pick the first
// The service account this token belongs to should only have access to a single outpost
outposts, err := apiClient.Outposts.OutpostsInstancesList(outposts.NewOutpostsInstancesListParams(), auth)
outposts, _, err := apiClient.OutpostsApi.OutpostsInstancesList(context.Background()).Execute()
if err != nil {
log.WithError(err).Error("Failed to fetch configuration")
os.Exit(1)
}
outpost := outposts.Payload.Results[0]
doGlobalSetup(outpost.Config.(map[string]interface{}))
outpost := outposts.Results[0]
doGlobalSetup(outpost.Config)
ac := &APIController{
Client: apiClient,
Auth: auth,
token: token,
logger: log,
@ -74,7 +74,7 @@ func NewAPIController(akURL url.URL, token string) *APIController {
instanceUUID: uuid.New(),
}
ac.logger.Debugf("HA Reload offset: %s", ac.reloadOffset)
ac.initWS(akURL, outpost.Pk)
ac.initWS(akURL, strfmt.UUID(outpost.Pk))
return ac
}

View File

@ -1,15 +1,16 @@
package ak
import (
"goauthentik.io/outpost/pkg/client/outposts"
"goauthentik.io/outpost/pkg/models"
"context"
"goauthentik.io/outpost/api"
)
func (a *APIController) Update() ([]*models.ProxyOutpostConfig, error) {
providers, err := a.Client.Outposts.OutpostsProxyList(outposts.NewOutpostsProxyListParams(), a.Auth)
func (a *APIController) Update() ([]api.ProxyOutpostConfig, error) {
providers, _, err := a.Client.OutpostsApi.OutpostsProxyList(context.Background()).Execute()
if err != nil {
a.logger.WithError(err).Error("Failed to fetch providers")
return nil, err
}
return providers.Payload.Results, nil
return providers.Results, nil
}

View File

@ -1,6 +1,7 @@
package ldap
import (
"context"
"errors"
"fmt"
"net/http"
@ -9,28 +10,27 @@ import (
"github.com/go-openapi/strfmt"
log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg/client/outposts"
)
func (ls *LDAPServer) Refresh() error {
outposts, err := ls.ac.Client.Outposts.OutpostsLdapList(outposts.NewOutpostsLdapListParams(), ls.ac.Auth)
outposts, _, err := ls.ac.Client.OutpostsApi.OutpostsLdapList(context.Background()).Execute()
if err != nil {
return err
}
if len(outposts.Payload.Results) < 1 {
if len(outposts.Results) < 1 {
return errors.New("no ldap provider defined")
}
providers := make([]*ProviderInstance, len(outposts.Payload.Results))
for idx, provider := range outposts.Payload.Results {
userDN := strings.ToLower(fmt.Sprintf("ou=users,%s", provider.BaseDn))
groupDN := strings.ToLower(fmt.Sprintf("ou=groups,%s", provider.BaseDn))
providers := make([]*ProviderInstance, len(outposts.Results))
for idx, provider := range outposts.Results {
userDN := strings.ToLower(fmt.Sprintf("ou=users,%s", *provider.BaseDn))
groupDN := strings.ToLower(fmt.Sprintf("ou=groups,%s", *provider.BaseDn))
providers[idx] = &ProviderInstance{
BaseDN: provider.BaseDn,
BaseDN: *provider.BaseDn,
GroupDN: groupDN,
UserDN: userDN,
appSlug: *provider.ApplicationSlug,
flowSlug: *provider.BindFlowSlug,
searchAllowedGroups: []*strfmt.UUID{provider.SearchGroup},
appSlug: provider.ApplicationSlug,
flowSlug: provider.BindFlowSlug,
searchAllowedGroups: []*strfmt.UUID{(*strfmt.UUID)(provider.SearchGroup.Get())},
boundUsersMutex: sync.RWMutex{},
boundUsers: make(map[string]UserFlags),
s: ls,

View File

@ -12,25 +12,14 @@ import (
"time"
goldap "github.com/go-ldap/ldap/v3"
httptransport "github.com/go-openapi/runtime/client"
"github.com/nmcclain/ldap"
"goauthentik.io/outpost/api"
"goauthentik.io/outpost/pkg"
"goauthentik.io/outpost/pkg/ak"
"goauthentik.io/outpost/pkg/client/core"
"goauthentik.io/outpost/pkg/client/flows"
"goauthentik.io/outpost/pkg/models"
)
const ContextUserKey = "ak_user"
type UIDResponse struct {
UIDFIeld string `json:"uid_field"`
}
type PasswordResponse struct {
Password string `json:"password"`
}
func (pi *ProviderInstance) getUsername(dn string) (string, error) {
if !strings.HasSuffix(strings.ToLower(dn), strings.ToLower(pi.BaseDN)) {
return "", errors.New("invalid base DN")
@ -60,16 +49,25 @@ func (pi *ProviderInstance) Bind(username string, bindDN, bindPW string, conn ne
pi.log.WithError(err).Warning("Failed to get remote IP")
return ldap.LDAPResultOperationsError, nil
}
// Create new http client that also sets the correct ip
client := &http.Client{
config := api.NewConfiguration()
// Carry over the bearer authentication, so that failed login attempts are attributed to the outpost
config.DefaultHeader = pi.s.ac.Client.GetConfig().DefaultHeader
config.Host = pi.s.ac.Client.GetConfig().Host
config.Scheme = pi.s.ac.Client.GetConfig().Scheme
config.HTTPClient = &http.Client{
Jar: jar,
Transport: newTransport(ak.SetUserAgent(ak.GetTLSTransport(), pkg.UserAgent()), map[string]string{
"X-authentik-remote-ip": host,
}),
}
// create the API client, with the transport
apiClient := api.NewAPIClient(config)
params := url.Values{}
params.Add("goauthentik.io/outpost/ldap", "true")
passed, err := pi.solveFlowChallenge(username, bindPW, client, params.Encode(), 1)
passed, err := pi.solveFlowChallenge(username, bindPW, apiClient, params.Encode(), 1)
if err != nil {
pi.log.WithField("bindDN", bindDN).WithError(err).Warning("failed to solve challenge")
return ldap.LDAPResultOperationsError, nil
@ -77,33 +75,26 @@ func (pi *ProviderInstance) Bind(username string, bindDN, bindPW string, conn ne
if !passed {
return ldap.LDAPResultInvalidCredentials, nil
}
_, err = pi.s.ac.Client.Core.CoreApplicationsCheckAccess(&core.CoreApplicationsCheckAccessParams{
Slug: pi.appSlug,
Context: context.Background(),
HTTPClient: client,
}, httptransport.PassThroughAuth)
r, err := pi.s.ac.Client.CoreApi.CoreApplicationsCheckAccessRetrieve(context.Background(), pi.appSlug).Execute()
if r.StatusCode == 403 {
pi.log.WithField("bindDN", bindDN).Info("Access denied for user")
return ldap.LDAPResultInsufficientAccessRights, nil
}
if err != nil {
if _, denied := err.(*core.CoreApplicationsCheckAccessForbidden); denied {
pi.log.WithField("bindDN", bindDN).Info("Access denied for user")
return ldap.LDAPResultInsufficientAccessRights, nil
}
pi.log.WithField("bindDN", bindDN).WithError(err).Warning("failed to check access")
return ldap.LDAPResultOperationsError, nil
}
pi.log.WithField("bindDN", bindDN).Info("User has access")
// Get user info to store in context
userInfo, err := pi.s.ac.Client.Core.CoreUsersMe(&core.CoreUsersMeParams{
Context: context.Background(),
HTTPClient: client,
}, httptransport.PassThroughAuth)
userInfo, _, err := pi.s.ac.Client.CoreApi.CoreUsersMeRetrieve(context.Background()).Execute()
if err != nil {
pi.log.WithField("bindDN", bindDN).WithError(err).Warning("failed to get user info")
return ldap.LDAPResultOperationsError, nil
}
pi.boundUsersMutex.Lock()
pi.boundUsers[bindDN] = UserFlags{
UserInfo: *userInfo.Payload.User,
CanSearch: pi.SearchAccessCheck(userInfo.Payload.User),
UserInfo: userInfo.User,
CanSearch: pi.SearchAccessCheck(userInfo.User),
}
defer pi.boundUsersMutex.Unlock()
pi.delayDeleteUserInfo(username)
@ -111,11 +102,11 @@ func (pi *ProviderInstance) Bind(username string, bindDN, bindPW string, conn ne
}
// SearchAccessCheck Check if the current user is allowed to search
func (pi *ProviderInstance) SearchAccessCheck(user *models.User) bool {
func (pi *ProviderInstance) SearchAccessCheck(user api.User) bool {
for _, group := range user.Groups {
for _, allowedGroup := range pi.searchAllowedGroups {
pi.log.WithField("userGroup", group.Pk).WithField("allowedGroup", allowedGroup).Trace("Checking search access")
if group.Pk.String() == allowedGroup.String() {
if group.Pk == allowedGroup.String() {
pi.log.WithField("group", group.Name).Info("Allowed access to search")
return true
}
@ -142,51 +133,48 @@ func (pi *ProviderInstance) delayDeleteUserInfo(dn string) {
}()
}
func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, client *http.Client, urlParams string, depth int) (bool, error) {
challenge, err := pi.s.ac.Client.Flows.FlowsExecutorGet(&flows.FlowsExecutorGetParams{
FlowSlug: pi.flowSlug,
Query: urlParams,
Context: context.Background(),
HTTPClient: client,
}, pi.s.ac.Auth)
func (pi *ProviderInstance) solveFlowChallenge(bindDN string, password string, client *api.APIClient, urlParams string, depth int) (bool, error) {
req := client.FlowsApi.FlowsExecutorGet(context.Background(), pi.flowSlug)
req.Query(urlParams)
challenge, _, err := req.Execute()
if err != nil {
pi.log.WithError(err).Warning("Failed to get challenge")
return false, err
}
pi.log.WithField("component", challenge.Payload.Component).WithField("type", *challenge.Payload.Type).Debug("Got challenge")
responseParams := &flows.FlowsExecutorSolveParams{
FlowSlug: pi.flowSlug,
Query: urlParams,
Context: context.Background(),
HTTPClient: client,
}
switch challenge.Payload.Component {
pi.log.WithField("component", challenge.Component).WithField("type", challenge.Type).Debug("Got challenge")
responseReq := client.FlowsApi.FlowsExecutorSolve(context.Background(), pi.flowSlug)
responseReq.Query(urlParams)
switch *challenge.Component {
case "ak-stage-identification":
responseParams.Data = &UIDResponse{UIDFIeld: bindDN}
responseReq.RequestBody(map[string]interface{}{
"uid_field": bindDN,
})
case "ak-stage-password":
responseParams.Data = &PasswordResponse{Password: password}
responseReq.RequestBody(map[string]interface{}{
"password": password,
})
case "ak-stage-access-denied":
return false, errors.New("got ak-stage-access-denied")
default:
return false, fmt.Errorf("unsupported challenge type: %s", challenge.Payload.Component)
return false, fmt.Errorf("unsupported challenge type: %s", *challenge.Component)
}
response, err := pi.s.ac.Client.Flows.FlowsExecutorSolve(responseParams, pi.s.ac.Auth)
pi.log.WithField("component", response.Payload.Component).WithField("type", *response.Payload.Type).Debug("Got response")
switch response.Payload.Component {
response, _, err := responseReq.Execute()
pi.log.WithField("component", response.Component).WithField("type", response.Type).Debug("Got response")
switch *response.Component {
case "ak-stage-access-denied":
return false, errors.New("got ak-stage-access-denied")
}
if *response.Payload.Type == "redirect" {
if response.Type == "redirect" {
return true, nil
}
if err != nil {
pi.log.WithError(err).Warning("Failed to submit challenge")
return false, err
}
if len(response.Payload.ResponseErrors) > 0 {
for key, errs := range response.Payload.ResponseErrors {
if len(*response.ResponseErrors) > 0 {
for key, errs := range *response.ResponseErrors {
for _, err := range errs {
pi.log.WithField("key", key).WithField("code", *err.Code).Debug(*err.String)
pi.log.WithField("key", key).WithField("code", err.Code).Debug(err.String)
return false, nil
}
}

View File

@ -1,13 +1,13 @@
package ldap
import (
"context"
"errors"
"fmt"
"net"
"strings"
"github.com/nmcclain/ldap"
"goauthentik.io/outpost/pkg/client/core"
)
func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest, conn net.Conn) (ldap.ServerSearchResult, error) {
@ -43,16 +43,16 @@ func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest,
default:
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("Search Error: unhandled filter type: %s [%s]", filterEntity, searchReq.Filter)
case GroupObjectClass:
groups, err := pi.s.ac.Client.Core.CoreGroupsList(core.NewCoreGroupsListParams(), pi.s.ac.Auth)
groups, _, err := pi.s.ac.Client.CoreApi.CoreGroupsList(context.Background()).Execute()
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err)
}
pi.log.WithField("count", len(groups.Payload.Results)).Trace("Got results from API")
for _, g := range groups.Payload.Results {
pi.log.WithField("count", len(groups.Results)).Trace("Got results from API")
for _, g := range groups.Results {
attrs := []*ldap.EntryAttribute{
{
Name: "cn",
Values: []string{*g.Name},
Values: []string{g.Name},
},
{
Name: "uid",
@ -69,31 +69,31 @@ func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest,
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
case UserObjectClass, "":
users, err := pi.s.ac.Client.Core.CoreUsersList(core.NewCoreUsersListParams(), pi.s.ac.Auth)
users, _, err := pi.s.ac.Client.CoreApi.CoreUsersList(context.Background()).Execute()
if err != nil {
return ldap.ServerSearchResult{ResultCode: ldap.LDAPResultOperationsError}, fmt.Errorf("API Error: %s", err)
}
for _, u := range users.Payload.Results {
for _, u := range users.Results {
attrs := []*ldap.EntryAttribute{
{
Name: "cn",
Values: []string{*u.Username},
Values: []string{u.Username},
},
{
Name: "uid",
Values: []string{u.UID},
Values: []string{u.Uid},
},
{
Name: "name",
Values: []string{*u.Name},
Values: []string{u.Name},
},
{
Name: "displayName",
Values: []string{*u.Name},
Values: []string{u.Name},
},
{
Name: "mail",
Values: []string{u.Email.String()},
Values: []string{*u.Email},
},
{
Name: "objectClass",
@ -101,13 +101,13 @@ func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest,
},
}
if u.IsActive {
if *u.IsActive {
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"inactive"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "accountStatus", Values: []string{"active"}})
}
if *u.IsSuperuser {
if u.IsSuperuser {
attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"inactive"}})
} else {
attrs = append(attrs, &ldap.EntryAttribute{Name: "superuser", Values: []string{"active"}})
@ -117,7 +117,7 @@ func (pi *ProviderInstance) Search(bindDN string, searchReq ldap.SearchRequest,
attrs = append(attrs, AKAttrsToLDAP(u.Attributes)...)
dn := fmt.Sprintf("cn=%s,%s", *u.Username, pi.UserDN)
dn := fmt.Sprintf("cn=%s,%s", u.Username, pi.UserDN)
entries = append(entries, &ldap.Entry{DN: dn, Attributes: attrs})
}
}

View File

@ -5,8 +5,8 @@ import (
"github.com/go-openapi/strfmt"
log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/api"
"goauthentik.io/outpost/pkg/ak"
"goauthentik.io/outpost/pkg/models"
"github.com/nmcclain/ldap"
)
@ -31,7 +31,7 @@ type ProviderInstance struct {
}
type UserFlags struct {
UserInfo models.User
UserInfo api.User
CanSearch bool
}

View File

@ -4,7 +4,7 @@ import (
"fmt"
"github.com/nmcclain/ldap"
"goauthentik.io/outpost/pkg/models"
"goauthentik.io/outpost/api"
)
func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute {
@ -22,7 +22,7 @@ func AKAttrsToLDAP(attrs interface{}) []*ldap.EntryAttribute {
return attrList
}
func (pi *ProviderInstance) GroupsForUser(user *models.User) []string {
func (pi *ProviderInstance) GroupsForUser(user api.User) []string {
groups := make([]string, len(user.Groups))
for i, group := range user.Groups {
groups[i] = pi.GetGroupDN(group)
@ -30,6 +30,6 @@ func (pi *ProviderInstance) GroupsForUser(user *models.User) []string {
return groups
}
func (pi *ProviderInstance) GetGroupDN(group *models.Group) string {
return fmt.Sprintf("cn=%s,%s", *group.Name, pi.GroupDN)
func (pi *ProviderInstance) GetGroupDN(group api.Group) string {
return fmt.Sprintf("cn=%s,%s", group.Name, pi.GroupDN)
}

View File

@ -4,7 +4,7 @@ import (
"net/url"
log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg/models"
"goauthentik.io/outpost/api"
)
func (s *Server) Refresh() error {
@ -21,10 +21,10 @@ func (s *Server) Refresh() error {
return nil
}
func (s *Server) bundleProviders(providers []*models.ProxyOutpostConfig) []*providerBundle {
func (s *Server) bundleProviders(providers []api.ProxyOutpostConfig) []*providerBundle {
bundles := make([]*providerBundle, len(providers))
for idx, provider := range providers {
externalHost, err := url.Parse(*provider.ExternalHost)
externalHost, err := url.Parse(provider.ExternalHost)
if err != nil {
log.WithError(err).Warning("Failed to parse URL, skipping provider")
}

View File

@ -15,8 +15,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/pkg/middleware"
"github.com/oauth2-proxy/oauth2-proxy/pkg/validation"
log "github.com/sirupsen/logrus"
"goauthentik.io/outpost/pkg/client/crypto"
"goauthentik.io/outpost/pkg/models"
"goauthentik.io/outpost/api"
)
type providerBundle struct {
@ -35,8 +34,8 @@ func intToPointer(i int) *int {
return &i
}
func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *options.Options {
externalHost, err := url.Parse(*provider.ExternalHost)
func (pb *providerBundle) prepareOpts(provider api.ProxyOutpostConfig) *options.Options {
externalHost, err := url.Parse(provider.ExternalHost)
if err != nil {
log.WithError(err).Warning("Failed to parse URL, skipping provider")
return nil
@ -47,25 +46,25 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
log.WithError(err).Warning("Failed to copy options, skipping provider")
return nil
}
providerOpts.ClientID = provider.ClientID
providerOpts.ClientSecret = provider.ClientSecret
providerOpts.ClientID = *provider.ClientId
providerOpts.ClientSecret = *provider.ClientSecret
providerOpts.Cookie.Secret = provider.CookieSecret
providerOpts.Cookie.Secret = *provider.CookieSecret
providerOpts.Cookie.Secure = externalHost.Scheme == "https"
providerOpts.SkipOIDCDiscovery = true
providerOpts.OIDCIssuerURL = *provider.OidcConfiguration.Issuer
providerOpts.LoginURL = *provider.OidcConfiguration.AuthorizationEndpoint
providerOpts.RedeemURL = *provider.OidcConfiguration.TokenEndpoint
providerOpts.OIDCJwksURL = *provider.OidcConfiguration.JwksURI
providerOpts.ProfileURL = *provider.OidcConfiguration.UserinfoEndpoint
providerOpts.OIDCIssuerURL = provider.OidcConfiguration.Issuer
providerOpts.LoginURL = provider.OidcConfiguration.AuthorizationEndpoint
providerOpts.RedeemURL = provider.OidcConfiguration.TokenEndpoint
providerOpts.OIDCJwksURL = provider.OidcConfiguration.JwksUri
providerOpts.ProfileURL = provider.OidcConfiguration.UserinfoEndpoint
if provider.SkipPathRegex != "" {
skipRegexes := strings.Split(provider.SkipPathRegex, "\n")
if *provider.SkipPathRegex != "" {
skipRegexes := strings.Split(*provider.SkipPathRegex, "\n")
providerOpts.SkipAuthRegex = skipRegexes
}
if provider.ForwardAuthMode {
if *provider.ForwardAuthMode {
providerOpts.UpstreamServers = []options.Upstream{
{
ID: "static",
@ -78,33 +77,27 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
providerOpts.UpstreamServers = []options.Upstream{
{
ID: "default",
URI: provider.InternalHost,
URI: *provider.InternalHost,
Path: "/",
InsecureSkipTLSVerify: !provider.InternalHostSslValidation,
InsecureSkipTLSVerify: !(*provider.InternalHostSslValidation),
},
}
}
if provider.Certificate != nil {
if provider.Certificate.Get() != nil {
pb.log.WithField("provider", provider.Name).Debug("Enabling TLS")
cert, err := pb.s.ak.Client.Crypto.CryptoCertificatekeypairsViewCertificate(&crypto.CryptoCertificatekeypairsViewCertificateParams{
Context: context.Background(),
KpUUID: *provider.Certificate,
}, pb.s.ak.Auth)
cert, _, err := pb.s.ak.Client.CryptoApi.CryptoCertificatekeypairsViewCertificateRetrieve(context.Background(), *provider.Certificate.Get()).Execute()
if err != nil {
pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch certificate")
return providerOpts
}
key, err := pb.s.ak.Client.Crypto.CryptoCertificatekeypairsViewPrivateKey(&crypto.CryptoCertificatekeypairsViewPrivateKeyParams{
Context: context.Background(),
KpUUID: *provider.Certificate,
}, pb.s.ak.Auth)
key, _, err := pb.s.ak.Client.CryptoApi.CryptoCertificatekeypairsViewPrivateKeyRetrieve(context.Background(), *provider.Certificate.Get()).Execute()
if err != nil {
pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to fetch private key")
return providerOpts
}
x509cert, err := tls.X509KeyPair([]byte(cert.Payload.Data), []byte(key.Payload.Data))
x509cert, err := tls.X509KeyPair([]byte(cert.Data), []byte(key.Data))
if err != nil {
pb.log.WithField("provider", provider.Name).WithError(err).Warning("Failed to parse certificate")
return providerOpts
@ -115,7 +108,7 @@ func (pb *providerBundle) prepareOpts(provider *models.ProxyOutpostConfig) *opti
return providerOpts
}
func (pb *providerBundle) Build(provider *models.ProxyOutpostConfig) {
func (pb *providerBundle) Build(provider api.ProxyOutpostConfig) {
opts := pb.prepareOpts(provider)
chain := alice.New()
@ -154,10 +147,10 @@ func (pb *providerBundle) Build(provider *models.ProxyOutpostConfig) {
os.Exit(1)
}
if provider.BasicAuthEnabled {
if *provider.BasicAuthEnabled {
oauthproxy.SetBasicAuth = true
oauthproxy.BasicAuthUserAttribute = provider.BasicAuthUserAttribute
oauthproxy.BasicAuthPasswordAttribute = provider.BasicAuthPasswordAttribute
oauthproxy.BasicAuthUserAttribute = *provider.BasicAuthUserAttribute
oauthproxy.BasicAuthPasswordAttribute = *provider.BasicAuthPasswordAttribute
}
pb.proxy = oauthproxy

View File

@ -21,7 +21,7 @@ import (
"github.com/oauth2-proxy/oauth2-proxy/pkg/sessions"
"github.com/oauth2-proxy/oauth2-proxy/pkg/upstream"
"github.com/oauth2-proxy/oauth2-proxy/providers"
"goauthentik.io/outpost/pkg/models"
"goauthentik.io/outpost/api"
log "github.com/sirupsen/logrus"
)
@ -94,7 +94,7 @@ type OAuthProxy struct {
}
// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
func NewOAuthProxy(opts *options.Options, provider *models.ProxyOutpostConfig) (*OAuthProxy, error) {
func NewOAuthProxy(opts *options.Options, provider api.ProxyOutpostConfig) (*OAuthProxy, error) {
logger := log.WithField("logger", "authentik.outpost.proxy").WithField("provider", provider.Name)
sessionStore, err := sessions.NewSessionStore(&opts.Session, &opts.Cookie)
if err != nil {
@ -133,7 +133,7 @@ func NewOAuthProxy(opts *options.Options, provider *models.ProxyOutpostConfig) (
CookieRefresh: opts.Cookie.Refresh,
CookieSameSite: opts.Cookie.SameSite,
forwardAuthMode: provider.ForwardAuthMode,
forwardAuthMode: *provider.ForwardAuthMode,
RobotsPath: "/robots.txt",
SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix),
SignOutPath: fmt.Sprintf("%s/sign_out", opts.ProxyPrefix),

23973
schema.yml Normal file

File diff suppressed because it is too large Load Diff

19149
swagger.yaml

File diff suppressed because it is too large Load Diff

View File

@ -9,7 +9,7 @@ trigger:
stages:
- stage: generate
jobs:
- job: swagger_generate
- job: generate_api
pool:
vmImage: 'ubuntu-latest'
steps:
@ -19,12 +19,11 @@ stages:
displayName: 'Install Node.js'
- task: CmdLine@2
inputs:
script: |
docker run --rm -v $(pwd):/local openapitools/openapi-generator-cli generate -i /local/swagger.yaml -g typescript-fetch -o /local/web/api --additional-properties=typescriptThreePlus=true,supportsES6=true,npmName=authentik-api,npmVersion=1.0.0
script: make gen-web
- task: PublishPipelineArtifact@1
inputs:
targetPath: 'web/api/'
artifact: 'ts_swagger_client'
artifact: 'ts_api_client'
publishLocation: 'pipeline'
- stage: lint
jobs:
@ -39,7 +38,7 @@ stages:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
artifactName: 'ts_api_client'
path: "web/api/"
- task: Npm@1
inputs:
@ -61,7 +60,7 @@ stages:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
artifactName: 'ts_api_client'
path: "web/api/"
- task: Npm@1
inputs:
@ -85,7 +84,7 @@ stages:
- task: DownloadPipelineArtifact@2
inputs:
buildType: 'current'
artifactName: 'ts_swagger_client'
artifactName: 'ts_api_client'
path: "web/api/"
- task: Npm@1
inputs:

View File

@ -15,13 +15,13 @@ export class LoggingMiddleware implements Middleware {
let globalConfigPromise: Promise<Config>;
export function config(): Promise<Config> {
if (!globalConfigPromise) {
globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigList();
globalConfigPromise = new RootApi(DEFAULT_CONFIG).rootConfigRetrieve();
}
return globalConfigPromise;
}
export const DEFAULT_CONFIG = new Configuration({
basePath: "/api/v2beta",
basePath: "",
headers: {
"X-CSRFToken": getCookie("authentik_csrf"),
},

View File

@ -1,4 +1,4 @@
import { ChallengeTypeEnum } from "authentik-api";
import { ChallengeChoices } from "authentik-api";
export interface Error {
code: string;
@ -10,7 +10,7 @@ export interface ErrorDict {
}
export interface Challenge {
type: ChallengeTypeEnum;
type: ChallengeChoices;
component?: string;
title?: string;
response_errors?: ErrorDict;

View File

@ -4,9 +4,14 @@ import { DEFAULT_CONFIG } from "./Config";
let globalMePromise: Promise<SessionUser>;
export function me(): Promise<SessionUser> {
if (!globalMePromise) {
globalMePromise = new CoreApi(DEFAULT_CONFIG).coreUsersMe().catch((ex) => {
globalMePromise = new CoreApi(DEFAULT_CONFIG).coreUsersMeRetrieve().catch((ex) => {
const defaultUser: SessionUser = {
user: {
pk: -1,
isSuperuser: false,
groups: [],
avatar: "",
uid: "",
username: "",
name: ""
}

View File

@ -17,7 +17,7 @@ export class TokenCopyButton extends ActionButton {
if (!this.identifier) {
return Promise.reject();
}
return new CoreApi(DEFAULT_CONFIG).coreTokensViewKey({
return new CoreApi(DEFAULT_CONFIG).coreTokensViewKeyRetrieve({
identifier: this.identifier
}).then((token) => {
if (!token.key) {

View File

@ -8,7 +8,7 @@ import { DEFAULT_CONFIG } from "../../api/Config";
export class AdminLoginsChart extends AKChart<LoginMetrics> {
apiRequest(): Promise<LoginMetrics> {
return new AdminApi(DEFAULT_CONFIG).adminMetricsList();
return new AdminApi(DEFAULT_CONFIG).adminMetricsRetrieve();
}
getChartData(data: LoginMetrics): ChartData {

View File

@ -11,7 +11,7 @@ export class ApplicationAuthorizeChart extends AKChart<Coordinate[]> {
applicationSlug!: string;
apiRequest(): Promise<Coordinate[]> {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsMetrics({ slug: this.applicationSlug });
return new CoreApi(DEFAULT_CONFIG).coreApplicationsMetricsList({ slug: this.applicationSlug });
}
getChartData(data: Coordinate[]): ChartData {

View File

@ -11,7 +11,7 @@ export class UserChart extends AKChart<UserMetrics> {
userId?: number;
apiRequest(): Promise<UserMetrics> {
return new CoreApi(DEFAULT_CONFIG).coreUsersMetrics({
return new CoreApi(DEFAULT_CONFIG).coreUsersMetricsRetrieve({
id: this.userId || 0,
});
}

View File

@ -14,7 +14,7 @@ import PFInputGroup from "@patternfly/patternfly/components/InputGroup/input-gro
import { MessageLevel } from "../messages/Message";
import { IronFormElement } from "@polymer/iron-form/iron-form";
import { camelToSnake, convertToSlug } from "../../utils";
import { ValidationError } from "authentik-api/src";
import { ValidationError } from "authentik-api";
import { EVENT_REFRESH } from "../../constants";
export class APIError extends Error {

View File

@ -32,7 +32,7 @@ export class NotificationDrawer extends LitElement {
firstUpdated(): void {
new EventsApi(DEFAULT_CONFIG).eventsNotificationsList({
seen: "false",
seen: false,
ordering: "-created",
}).then(r => {
this.notifications = r;
@ -73,7 +73,7 @@ export class NotificationDrawer extends LitElement {
<button class="pf-c-dropdown__toggle pf-m-plain" type="button" @click=${() => {
new EventsApi(DEFAULT_CONFIG).eventsNotificationsPartialUpdate({
uuid: item.pk || "",
data: {
patchedNotificationRequest: {
seen: true,
}
}).then(() => {

View File

@ -10,8 +10,8 @@ import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-user-oauth-code-list")
export class UserOAuthCodeList extends Table<ExpiringBaseGrantModel> {
@property()
userId?: string;
@property({ type: Number })
userId?: number;
apiEndpoint(page: number): Promise<AKResponse<ExpiringBaseGrantModel>> {
return new Oauth2Api(DEFAULT_CONFIG).oauth2AuthorizationCodesList({
@ -45,7 +45,7 @@ export class UserOAuthCodeList extends Table<ExpiringBaseGrantModel> {
.obj=${item}
objectLabel=${t`Authorization Code`}
.delete=${() => {
return new Oauth2Api(DEFAULT_CONFIG).oauth2AuthorizationCodesDelete({
return new Oauth2Api(DEFAULT_CONFIG).oauth2AuthorizationCodesDestroy({
id: item.pk || 0,
});
}}>

View File

@ -10,8 +10,8 @@ import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-user-oauth-refresh-list")
export class UserOAuthRefreshList extends Table<ExpiringBaseGrantModel> {
@property()
userId?: string;
@property({ type: Number })
userId?: number;
apiEndpoint(page: number): Promise<AKResponse<ExpiringBaseGrantModel>> {
return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensList({
@ -45,7 +45,7 @@ export class UserOAuthRefreshList extends Table<ExpiringBaseGrantModel> {
.obj=${item}
objectLabel=${t`Refresh Code`}
.delete=${() => {
return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensDelete({
return new Oauth2Api(DEFAULT_CONFIG).oauth2RefreshTokensDestroy({
id: item.pk || 0,
});
}}>

View File

@ -21,6 +21,7 @@ export const DefaultConfig: Config = {
errorReportingEnabled: false,
errorReportingEnvironment: "",
errorReportingSendPii: false,
uiFooterLinks: [],
};
@customElement("ak-sidebar-brand")

View File

@ -10,8 +10,8 @@ import { DEFAULT_CONFIG } from "../../api/Config";
@customElement("ak-user-consent-list")
export class UserConsentList extends Table<UserConsent> {
@property()
userId?: string;
@property({ type: Number })
userId?: number;
apiEndpoint(page: number): Promise<AKResponse<UserConsent>> {
return new CoreApi(DEFAULT_CONFIG).coreUserConsentList({
@ -41,7 +41,7 @@ export class UserConsentList extends Table<UserConsent> {
.obj=${item}
objectLabel=${t`Consent`}
.delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreUserConsentDelete({
return new CoreApi(DEFAULT_CONFIG).coreUserConsentDestroy({
id: item.pk || 0,
});
}}>

View File

@ -37,7 +37,7 @@ import { AuthenticatorValidateStageChallenge } from "./stages/authenticator_vali
import { WebAuthnAuthenticatorRegisterChallenge } from "./stages/authenticator_webauthn/WebAuthnAuthenticatorRegisterStage";
import { CaptchaChallenge } from "./stages/captcha/CaptchaStage";
import { StageHost } from "./stages/base";
import { Challenge, ChallengeTypeEnum, Config, FlowsApi } from "authentik-api";
import { Challenge, ChallengeChoices, Config, FlowsApi } from "authentik-api";
import { config, DEFAULT_CONFIG } from "../api/Config";
import { ifDefined } from "lit-html/directives/if-defined";
import { until } from "lit-html/directives/until";
@ -114,7 +114,7 @@ export class FlowExecutor extends LitElement implements StageHost {
this.loading = true;
return new FlowsApi(DEFAULT_CONFIG).flowsExecutorSolveRaw({
flowSlug: this.flowSlug,
data: formData || {},
requestBody: formData || {},
query: window.location.search.substring(1),
}).then((challengeRaw) => {
return challengeRaw.raw.json();
@ -155,7 +155,7 @@ export class FlowExecutor extends LitElement implements StageHost {
errorMessage(error: string): void {
this.challenge = <ShellChallenge>{
type: ChallengeTypeEnum.Shell,
type: ChallengeChoices.Shell,
body: `<header class="pf-c-login__main-header">
<h1 class="pf-c-title pf-m-3xl">
${t`Whoops!`}
@ -188,16 +188,16 @@ export class FlowExecutor extends LitElement implements StageHost {
return html``;
}
switch (this.challenge.type) {
case ChallengeTypeEnum.Redirect:
case ChallengeChoices.Redirect:
console.debug("authentik/flows: redirecting to url from server", (this.challenge as RedirectChallenge).to);
window.location.assign((this.challenge as RedirectChallenge).to);
return html`<ak-empty-state
?loading=${true}
header=${t`Loading`}>
</ak-empty-state>`;
case ChallengeTypeEnum.Shell:
case ChallengeChoices.Shell:
return html`${unsafeHTML((this.challenge as ShellChallenge).body)}`;
case ChallengeTypeEnum.Native:
case ChallengeChoices.Native:
switch (this.challenge.component) {
case "ak-stage-access-denied":
return html`<ak-stage-access-denied .host=${this} .challenge=${this.challenge as AccessDeniedChallenge}></ak-stage-access-denied>`;

View File

@ -38,8 +38,8 @@ export class PlexLoginInit extends BaseStage {
const authWindow = popupCenterScreen(authInfo.authUrl, "plex auth", 550, 700);
PlexAPIClient.pinPoll(this.challenge?.client_id || "", authInfo.pin.id).then(token => {
authWindow?.close();
new SourcesApi(DEFAULT_CONFIG).sourcesPlexRedeemToken({
data: {
new SourcesApi(DEFAULT_CONFIG).sourcesPlexRedeemTokenCreate({
plexTokenRedeemRequest: {
plexToken: token,
},
slug: this.challenge?.slug || "",

View File

@ -51,11 +51,11 @@ export abstract class Interface extends LitElement {
render(): TemplateResult {
return html`
${until(new AdminApi(DEFAULT_CONFIG).adminVersionList().then(version => {
${until(new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve().then(version => {
if (version.versionCurrent !== VERSION) {
return html`<ak-banner>
${t`A newer version of the frontend is available.`}
<button @click=${() => { window.location.reload(); }}>
<button @click=${() => { window.location.reload(true); }}>
${t`Reload`}
</button>
</ak-banner>`;

View File

@ -56,7 +56,7 @@ export class LibraryApplication extends LitElement {
if (!this.application) {
return html`<ak-spinner></ak-spinner>`;
}
return html` <a href="${ifDefined(this.application.launchUrl)}" class="pf-c-card pf-m-hoverable pf-m-compact">
return html` <a href="${ifDefined(this.application.launchUrl ?? "")}" class="pf-c-card pf-m-hoverable pf-m-compact">
<div class="pf-c-card__header">
${this.application.metaIcon
? html`<img class="app-icon pf-c-avatar" src="${ifDefined(this.application.metaIcon)}" alt="Application Icon"/>`

View File

@ -18,7 +18,7 @@ export class TopApplicationsTable extends LitElement {
}
firstUpdated(): void {
new EventsApi(DEFAULT_CONFIG).eventsEventsTopPerUser({
new EventsApi(DEFAULT_CONFIG).eventsEventsTopPerUserList({
action: "authorize_application",
topN: 11,
}).then((events) => {

View File

@ -1,20 +1,20 @@
import { t } from "@lingui/macro";
import { customElement, html, TemplateResult } from "lit-element";
import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
import { AdminApi, TaskStatusEnum } from "authentik-api";
import { AdminApi, StatusEnum } from "authentik-api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import { convertToTitle } from "../../../utils";
@customElement("ak-admin-status-card-backup")
export class BackupStatusCard extends AdminStatusCard<TaskStatusEnum> {
export class BackupStatusCard extends AdminStatusCard<StatusEnum> {
getPrimaryValue(): Promise<TaskStatusEnum> {
return new AdminApi(DEFAULT_CONFIG).adminSystemTasksRead({
getPrimaryValue(): Promise<StatusEnum> {
return new AdminApi(DEFAULT_CONFIG).adminSystemTasksRetrieve({
id: "backup_database"
}).then((value) => {
return value.status;
}).catch(() => {
return TaskStatusEnum.Error;
return StatusEnum.Error;
});
}
@ -22,14 +22,14 @@ export class BackupStatusCard extends AdminStatusCard<TaskStatusEnum> {
return html`${convertToTitle(this.value?.toString() || "")}`;
}
getStatus(value: TaskStatusEnum): Promise<AdminStatus> {
getStatus(value: StatusEnum): Promise<AdminStatus> {
switch (value) {
case TaskStatusEnum.Warning:
case StatusEnum.Warning:
return Promise.resolve<AdminStatus>({
icon: "fa fa-exclamation-triangle pf-m-warning",
message: t`Backup finished with warnings.`,
});
case TaskStatusEnum.Error:
case StatusEnum.Error:
return Promise.resolve<AdminStatus>({
icon: "fa fa-times-circle pf-m-danger",
message: t`Backup finished with errors.`,

View File

@ -8,7 +8,7 @@ import { AdminStatusCard, AdminStatus } from "./AdminStatusCard";
export class VersionStatusCard extends AdminStatusCard<Version> {
getPrimaryValue(): Promise<Version> {
return new AdminApi(DEFAULT_CONFIG).adminVersionList();
return new AdminApi(DEFAULT_CONFIG).adminVersionRetrieve();
}
getStatus(value: Version): Promise<AdminStatus> {

View File

@ -8,8 +8,8 @@ import { AdminStatus, AdminStatusCard } from "./AdminStatusCard";
export class WorkersStatusCard extends AdminStatusCard<number> {
getPrimaryValue(): Promise<number> {
return new AdminApi(DEFAULT_CONFIG).adminWorkersList({}).then((workers) => {
return workers.pagination.count;
return new AdminApi(DEFAULT_CONFIG).adminWorkersRetrieve().then((workers) => {
return workers.count;
});
}

View File

@ -31,7 +31,7 @@ export class PolicyStatusChart extends AKChart<FlowMetrics> {
async apiRequest(): Promise<FlowMetrics> {
const api = new FlowsApi(DEFAULT_CONFIG);
const cached = (await api.flowsInstancesCacheInfo()).count || 0;
const cached = (await api.flowsInstancesCacheInfoRetrieve()).count || 0;
const count = (await api.flowsInstancesList({
pageSize: 1
})).pagination.count;

View File

@ -34,7 +34,7 @@ export class GroupCountStatusChart extends AKChart<GroupMetrics> {
pageSize: 1
})).pagination.count;
const superusers = (await api.coreGroupsList({
isSuperuser: "true"
isSuperuser: true
})).pagination.count;
this.centerText = count.toString();
return {

View File

@ -1,6 +1,6 @@
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { SourcesApi, TaskStatusEnum } from "authentik-api";
import { SourcesApi, StatusEnum } from "authentik-api";
import { DEFAULT_CONFIG } from "../../../api/Config";
import "../../../elements/forms/ConfirmationForm";
import { AKChart } from "../../../elements/charts/Chart";
@ -38,10 +38,10 @@ export class LDAPSyncStatusChart extends AKChart<LDAPSyncStats> {
let unsynced = 0;
await Promise.all(sources.results.map(async (element) => {
try {
const health = await api.sourcesLdapSyncStatus({
const health = await api.sourcesLdapSyncStatusRetrieve({
slug: element.slug,
});
if (health.status !== TaskStatusEnum.Successful) {
if (health.status !== StatusEnum.Successful) {
failed += 1;
}
const now = new Date().getTime();

View File

@ -37,7 +37,7 @@ export class OutpostStatusChart extends AKChart<OutpostStats> {
let outdated = 0;
let unhealthy = 0;
await Promise.all(outposts.results.map(async (element) => {
const health = await api.outpostsOutpostsHealth({
const health = await api.outpostsInstancesHealthList({
uuid: element.pk || "",
});
if (health.length === 0) {

View File

@ -32,13 +32,13 @@ export class PolicyStatusChart extends AKChart<PolicyMetrics> {
async apiRequest(): Promise<PolicyMetrics> {
const api = new PoliciesApi(DEFAULT_CONFIG);
const cached = (await api.policiesAllCacheInfo()).count || 0;
const cached = (await api.policiesAllCacheInfoRetrieve()).count || 0;
const count = (await api.policiesAllList({
pageSize: 1
})).pagination.count;
const unbound = (await api.policiesAllList({
bindingsIsnull: "true",
promptstageIsnull: "true",
bindingsIsnull: true,
promptstageIsnull: true,
})).pagination.count;
this.centerText = count.toString();
return {

View File

@ -1,4 +1,4 @@
import { CoreApi, Application, ProvidersApi, Provider, ApplicationPolicyEngineModeEnum } from "authentik-api";
import { CoreApi, Application, ProvidersApi, Provider, PolicyEngineMode } from "authentik-api";
import { t } from "@lingui/macro";
import { CSSResult, customElement, property } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -18,7 +18,7 @@ import { ModelForm } from "../../elements/forms/ModelForm";
export class ApplicationForm extends ModelForm<Application, string> {
loadInstance(pk: string): Promise<Application> {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsRead({
return new CoreApi(DEFAULT_CONFIG).coreApplicationsRetrieve({
slug: pk
});
}
@ -43,17 +43,17 @@ export class ApplicationForm extends ModelForm<Application, string> {
if (this.instance) {
writeOp = new CoreApi(DEFAULT_CONFIG).coreApplicationsUpdate({
slug: this.instance.slug,
data: data
applicationRequest: data
});
} else {
writeOp = new CoreApi(DEFAULT_CONFIG).coreApplicationsCreate({
data: data
applicationRequest: data
});
}
const icon = this.getFormFile();
if (icon) {
return writeOp.then(app => {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIcon({
return new CoreApi(DEFAULT_CONFIG).coreApplicationsSetIconCreate({
slug: app.slug,
file: icon
});
@ -115,7 +115,7 @@ export class ApplicationForm extends ModelForm<Application, string> {
<i class="fas fa-caret-down pf-c-dropdown__toggle-icon" aria-hidden="true"></i>
</button>
<ul class="pf-c-dropdown__menu" hidden>
${until(new ProvidersApi(DEFAULT_CONFIG).providersAllTypes().then((types) => {
${until(new ProvidersApi(DEFAULT_CONFIG).providersAllTypesList().then((types) => {
return types.map((type) => {
return html`<li>
<ak-forms-modal>
@ -145,10 +145,10 @@ export class ApplicationForm extends ModelForm<Application, string> {
?required=${true}
name="policyEngineMode">
<select class="pf-c-form-control">
<option value=${ApplicationPolicyEngineModeEnum.Any} ?selected=${this.instance?.policyEngineMode === ApplicationPolicyEngineModeEnum.Any}>
<option value=${PolicyEngineMode.Any} ?selected=${this.instance?.policyEngineMode === PolicyEngineMode.Any}>
${t`ANY, any policy must match to grant access.`}
</option>
<option value=${ApplicationPolicyEngineModeEnum.All} ?selected=${this.instance?.policyEngineMode === ApplicationPolicyEngineModeEnum.All}>
<option value=${PolicyEngineMode.All} ?selected=${this.instance?.policyEngineMode === PolicyEngineMode.All}>
${t`ALL, all policies must match to grant access.`}
</option>
</select>

View File

@ -104,7 +104,7 @@ export class ApplicationListPage extends TablePage<Application> {
.obj=${item}
objectLabel=${t`Application`}
.delete=${() => {
return new CoreApi(DEFAULT_CONFIG).coreApplicationsDelete({
return new CoreApi(DEFAULT_CONFIG).coreApplicationsDestroy({
slug: item.slug || ""
});
}}>

View File

@ -26,7 +26,7 @@ export class ApplicationViewPage extends LitElement {
@property()
set applicationSlug(value: string) {
new CoreApi(DEFAULT_CONFIG).coreApplicationsRead({
new CoreApi(DEFAULT_CONFIG).coreApplicationsRetrieve({
slug: value
}).then((app) => {
this.application = app;

View File

@ -1,4 +1,4 @@
import { CertificateGeneration, CryptoApi } from "authentik-api";
import { CertificateGenerationRequest, CryptoApi } from "authentik-api";
import { CertificateKeyPair } from "authentik-api/src";
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
@ -8,15 +8,15 @@ import { Form } from "../../elements/forms/Form";
import "../../elements/forms/HorizontalFormElement";
@customElement("ak-crypto-certificate-generate-form")
export class CertificateKeyPairForm extends Form<CertificateGeneration> {
export class CertificateKeyPairForm extends Form<CertificateGenerationRequest> {
getSuccessMessage(): string {
return t`Successfully generated certificate-key pair.`;
}
send = (data: CertificateGeneration): Promise<CertificateKeyPair> => {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsGenerate({
data: data
send = (data: CertificateGenerationRequest): Promise<CertificateKeyPair> => {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsGenerateCreate({
certificateGenerationRequest: data
});
};

View File

@ -1,4 +1,4 @@
import { CertificateKeyPair, CryptoApi } from "authentik-api";
import { CertificateKeyPair, CertificateKeyPairRequest, CryptoApi } from "authentik-api";
import { t } from "@lingui/macro";
import { customElement } from "lit-element";
import { html, TemplateResult } from "lit-html";
@ -12,7 +12,7 @@ import { ModelForm } from "../../elements/forms/ModelForm";
export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string> {
loadInstance(pk: string): Promise<CertificateKeyPair> {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsRead({
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsRetrieve({
kpUuid: pk,
});
}
@ -29,11 +29,11 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
if (this.instance) {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsPartialUpdate({
kpUuid: this.instance.pk || "",
data: data
patchedCertificateKeyPairRequest: data
});
} else {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsCreate({
data: data
certificateKeyPairRequest: data as unknown as CertificateKeyPairRequest
});
}
};
@ -51,14 +51,14 @@ export class CertificateKeyPairForm extends ModelForm<CertificateKeyPair, string
name="certificateData"
?writeOnly=${this.instance !== undefined}
?required=${true}>
<textarea class="pf-c-form-control" required>${ifDefined(this.instance?.certificateData)}</textarea>
<textarea class="pf-c-form-control" required></textarea>
<p class="pf-c-form__helper-text">${t`PEM-encoded Certificate data.`}</p>
</ak-form-element-horizontal>
<ak-form-element-horizontal
name="keyData"
?writeOnly=${this.instance !== undefined}
label=${t`Private Key`}>
<textarea class="pf-c-form-control" >${ifDefined(this.instance?.keyData)}</textarea>
<textarea class="pf-c-form-control" ></textarea>
<p class="pf-c-form__helper-text">${t`Optional Private Key. If this is set, you can use this keypair for encryption.`}</p>
</ak-form-element-horizontal>
</form>`;

View File

@ -80,7 +80,7 @@ export class CertificateKeyPairListPage extends TablePage<CertificateKeyPair> {
.obj=${item}
objectLabel=${t`Certificate-Key Pair`}
.delete=${() => {
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsDelete({
return new CryptoApi(DEFAULT_CONFIG).cryptoCertificatekeypairsDestroy({
kpUuid: item.pk || ""
});
}}>

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