From cda99c5c03e34459ce8c365d077b1e54ed8ec2c2 Mon Sep 17 00:00:00 2001
From: jkuensem <jkuensem@physik.uni-bielefeld.de>
Date: Mon, 20 Jan 2020 11:47:29 +0100
Subject: [PATCH] TMSS-134: Adapt TMSS to use a local OpenID Connect identity
 provider, add pyop example code

---
 SAS/TMSS/src/tmss/settings.py                 |   65 +-
 SAS/TMSS/src/tmss/urls.py                     |    2 +-
 SAS/TMSS/test/oidc/create-keycloak-user.sh    |    9 +
 SAS/TMSS/test/oidc/example/README.md          |    8 +
 SAS/TMSS/test/oidc/example/__init__.py        |    0
 .../__pycache__/__init__.cpython-34.pyc       |  Bin 0 -> 170 bytes
 .../example/__pycache__/app.cpython-34.pyc    |  Bin 0 -> 2407 bytes
 .../example/__pycache__/views.cpython-34.pyc  |  Bin 0 -> 5088 bytes
 .../example/__pycache__/wsgi.cpython-34.pyc   |  Bin 0 -> 309 bytes
 SAS/TMSS/test/oidc/example/app.py             |   60 +
 SAS/TMSS/test/oidc/example/application.cfg    |    5 +
 SAS/TMSS/test/oidc/example/https.crt          |   24 +
 SAS/TMSS/test/oidc/example/https.key          |   27 +
 SAS/TMSS/test/oidc/example/requirements.txt   |    3 +
 SAS/TMSS/test/oidc/example/signing_key.pem    |   15 +
 .../test/oidc/example/templates/logout.jinja2 |    8 +
 SAS/TMSS/test/oidc/example/views.py           |  124 ++
 SAS/TMSS/test/oidc/example/wsgi.py            |    7 +
 SAS/TMSS/test/oidc/realm-export.json          | 1808 +++++++++++++++++
 SAS/TMSS/test/oidc/tmss_keycloak_Dockerfile   |   10 +
 20 files changed, 2170 insertions(+), 5 deletions(-)
 create mode 100644 SAS/TMSS/test/oidc/create-keycloak-user.sh
 create mode 100644 SAS/TMSS/test/oidc/example/README.md
 create mode 100644 SAS/TMSS/test/oidc/example/__init__.py
 create mode 100644 SAS/TMSS/test/oidc/example/__pycache__/__init__.cpython-34.pyc
 create mode 100644 SAS/TMSS/test/oidc/example/__pycache__/app.cpython-34.pyc
 create mode 100644 SAS/TMSS/test/oidc/example/__pycache__/views.cpython-34.pyc
 create mode 100644 SAS/TMSS/test/oidc/example/__pycache__/wsgi.cpython-34.pyc
 create mode 100644 SAS/TMSS/test/oidc/example/app.py
 create mode 100644 SAS/TMSS/test/oidc/example/application.cfg
 create mode 100644 SAS/TMSS/test/oidc/example/https.crt
 create mode 100644 SAS/TMSS/test/oidc/example/https.key
 create mode 100644 SAS/TMSS/test/oidc/example/requirements.txt
 create mode 100644 SAS/TMSS/test/oidc/example/signing_key.pem
 create mode 100644 SAS/TMSS/test/oidc/example/templates/logout.jinja2
 create mode 100644 SAS/TMSS/test/oidc/example/views.py
 create mode 100644 SAS/TMSS/test/oidc/example/wsgi.py
 create mode 100644 SAS/TMSS/test/oidc/realm-export.json
 create mode 100644 SAS/TMSS/test/oidc/tmss_keycloak_Dockerfile

diff --git a/SAS/TMSS/src/tmss/settings.py b/SAS/TMSS/src/tmss/settings.py
index 046f8de3590..2e90189375c 100644
--- a/SAS/TMSS/src/tmss/settings.py
+++ b/SAS/TMSS/src/tmss/settings.py
@@ -18,8 +18,54 @@ from lofar.common import dbcredentials
 # logger
 logger = logging.getLogger('django_auth_ldap')
 logger.addHandler(logging.StreamHandler())
-logger.setLevel(logging.INFO)
-
+logger.setLevel(logging.DEBUG)
+
+LOGGING = {
+    'version': 1,
+    'disable_existing_loggers': False,
+    'filters': {
+        'require_debug_false': {
+            '()': 'django.utils.log.RequireDebugFalse',
+        },
+        'require_debug_true': {
+            '()': 'django.utils.log.RequireDebugTrue',
+        },
+    },
+    'formatters': {
+        'django.server': {
+            '()': 'django.utils.log.ServerFormatter',
+            'format': '[%(server_time)s] %(message)s',
+        },
+    },
+    'handlers': {
+        'console': {
+            'level': 'DEBUG',
+            'filters': ['require_debug_true'],
+            'class': 'logging.StreamHandler',
+        },
+        'django.server': {
+            'level': 'DEBUG',
+            'class': 'logging.StreamHandler',
+            'formatter': 'django.server',
+        },
+        'mail_admins': {
+            'level': 'ERROR',
+            'filters': ['require_debug_false'],
+            'class': 'django.utils.log.AdminEmailHandler'
+        },
+    },
+    'loggers': {
+        'django': {
+            'handlers': ['console', 'mail_admins'],
+            'level': 'DEBUG',
+        },
+        'django.server': {
+            'handlers': ['django.server'],
+            'level': 'DEBUG',
+            'propagate': False,
+        },
+    }
+}
 
 # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
 BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
@@ -55,6 +101,7 @@ INSTALLED_APPS = [
     'jsoneditor',
     'drf_yasg',
     'django_filters',
+    'mozilla_django_oidc',  # Load after auth
 ]
 
 MIDDLEWARE = [
@@ -129,6 +176,7 @@ REST_FRAMEWORK = {
     'DEFAULT_AUTHENTICATION_CLASSES': (
         'rest_framework.authentication.BasicAuthentication',
         'rest_framework.authentication.SessionAuthentication',
+        'mozilla_django_oidc.contrib.drf.OIDCAuthentication',
     ),
     'DEFAULT_PERMISSION_CLASSES': [
         #'rest_framework.permissions.AllowAny',
@@ -171,9 +219,18 @@ AUTH_LDAP_USER_ATTR_MAP = {
     "email": "mail"
 }
 
+# OPEN-ID CONNECT
+
+OIDC_RP_CLIENT_ID = os.environ.get('OIDC_RP_CLIENT_ID', 'id')               # Secret, do not put real credentials on Git
+OIDC_RP_CLIENT_SECRET = os.environ.get('OIDC_RP_CLIENT_SECRET', 'secret')   # Secret, do not put real credentials on Git
+OIDC_OP_AUTHORIZATION_ENDPOINT = "https://localhost:9090/authentication"
+OIDC_OP_TOKEN_ENDPOINT = "https://localhost:9090/token"
+OIDC_OP_USER_ENDPOINT = "https://localhost:9090/userinfo"
+
 AUTHENTICATION_BACKENDS = (
-    'django_auth_ldap.backend.LDAPBackend',
-    'django.contrib.auth.backends.ModelBackend',
+    #'django_auth_ldap.backend.LDAPBackend',
+    #'django.contrib.auth.backends.ModelBackend',
+    'mozilla_django_oidc.auth.OIDCAuthenticationBackend',
 )
 
 # Password validation
diff --git a/SAS/TMSS/src/tmss/urls.py b/SAS/TMSS/src/tmss/urls.py
index c96864991dd..1bc27fb4b0f 100644
--- a/SAS/TMSS/src/tmss/urls.py
+++ b/SAS/TMSS/src/tmss/urls.py
@@ -145,4 +145,4 @@ router.register(r'task_blueprint_C', viewsets.TaskBlueprintViewSetJSONeditorOnli
 urlpatterns.extend(router.urls)
 
 # prefix everything for proxy
-urlpatterns = [url(r'^api/', include(urlpatterns))]
\ No newline at end of file
+urlpatterns = [url(r'^api/', include(urlpatterns)), url(r'^oidc/', include('mozilla_django_oidc.urls')),]
\ No newline at end of file
diff --git a/SAS/TMSS/test/oidc/create-keycloak-user.sh b/SAS/TMSS/test/oidc/create-keycloak-user.sh
new file mode 100644
index 00000000000..1ede32adf3d
--- /dev/null
+++ b/SAS/TMSS/test/oidc/create-keycloak-user.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+cd keycloak/bin
+./kcadm.sh config credentials --server http://localhost:8080/auth --realm master --user admin --password admin
+
+USERID=$(./kcadm.sh create users -r demo -s username=monitoring-user -s enabled=true -o --fields id | jq '.id' | tr -d '"')
+echo $USERID
+./kcadm.sh update users/$USERID/reset-password -r demo -s type=password -s value=default -s temporary=false -n
+./kcadm.sh add-roles --uusername monitoring-user --rolename monitoring -r demo
+
diff --git a/SAS/TMSS/test/oidc/example/README.md b/SAS/TMSS/test/oidc/example/README.md
new file mode 100644
index 00000000000..eaff811bdcd
--- /dev/null
+++ b/SAS/TMSS/test/oidc/example/README.md
@@ -0,0 +1,8 @@
+# pyOP example application 
+To run the example application, execute the following commands:
+
+```bash
+cd example/
+pip install -r requirements.txt # install the dependencies
+gunicorn wsgi:app -b :9090 --certfile https.crt --keyfile https.key # run the application
+```
\ No newline at end of file
diff --git a/SAS/TMSS/test/oidc/example/__init__.py b/SAS/TMSS/test/oidc/example/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/SAS/TMSS/test/oidc/example/__pycache__/__init__.cpython-34.pyc b/SAS/TMSS/test/oidc/example/__pycache__/__init__.cpython-34.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..819d68a23dababb1257d69f3b39b35a9d08a0ef0
GIT binary patch
literal 170
zcmaFI!^_opL@|y52p)q77+?f49Dul(1xTbYFa&Ed`mJOr0tq9CU*Y<>sVSL>`dQhf
zsd>ezx%y_tsRjl{#^$=_mKJHcCWc9tx=Ch<X}YQA#-`>ehAGLZrk48U`9;}D`stY^
t`UREw1^THKiMa(isrvEpnR%Hd@$q^EmA5!-a`RJ4b5iX<78V0B0|4o)Dck@6

literal 0
HcmV?d00001

diff --git a/SAS/TMSS/test/oidc/example/__pycache__/app.cpython-34.pyc b/SAS/TMSS/test/oidc/example/__pycache__/app.cpython-34.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..004bd471eec2552f614d26573477e473b8be185d
GIT binary patch
literal 2407
zcmZ`)Npl-T6n-<KWwcp}o!D_!X9Gl)r7T1y6h+|#QVCElh#a8$K-XxxrL;X8-J>{C
z$p<Ro%!wbt|KL~5l~XSK050&l<!zFn(Y$%@_3Q4p_1Ayb8`WR_yz<ku0`M1{_&Lab
zj<5P13B$hy0YGR0vB1)@4S@}z1H_rj4g>`Vi$IDHmVlJz+5!Y+2wfoVTrNUTfv^gs
zI+sfj)F7+_sn6vy1PurmfGnWw0;|9)0MZ0j1y%!Ahgax9ON;$><2AYlZ7VB23_P`q
zOnEE=I*eu8&Pq@En_u%u=1S$!AodszFK&+0XR#DhFBNgrPra0**8Nt-&jsTW`74h+
z_3XZ<IO~rGJKRqnGajX4D7bv+`DrXCdcsr1rHF>HZ)pecSI4)3?*n|*_o$r#>;Sw3
z_{|3^wRHAgBE__|;a3}0_bt#`M{D<O!xs$i7`|xug5gVsFB-mV_>$pW!<XR)T=lyj
z#RAN5!3y+dw$4s(=IDg=W(A$J-mD0$fu9nv1^kqOHKE5akEdXS_KPSi0Xq%s46tPw
z;iMH*odvdP#?PT}9@qsVTtwj#jBvNh$gLUf3UXJ0U4!1f3#o15p9fcg6j!?rDSB{2
zyUgAL_CBV@-mgMhf>{-2IHe4!Yi3n|eE_Ryhkt>Itia16?AKsc!-{{nHC>7Y^J#KW
zvW|!tfALz^VR|D!;$f|I?R-;g$|IJ<B1)&rd9$6pT}8*(6BqLOG~VUW@vO`F(eXhC
z92;EA8%RD9DwS{SeKl`{K2gP$!e%^LC@Tr2#$1wR9l%ku9*?_+9%l=AAG1?$d6k`c
zrytqrckN6{al#|PNEx#`6agnK<)cbbHBORPrkrI3KW2Q|&~!~OY8;qWjX39*azkrI
zNzO{Hk~mVFrjz`7#h)=$JfuVEjY6!}3DXewII%TZ%;!VzLtGIif$&92&WxlNrEjiJ
z+{6=dPbf}S)ciodWwPW40(Ztj_>!k|;3?sg8~M~yy*CXx7KEMfX`7t$15bpCCZ6<g
zn9CEna(4M-+Eii`iD*Po=p;NOju(tb@k#&o-5xpJe|rC`FCT2v$6wG#oBc<$zxmDf
z>wodru9BLUB-D?il)p%&Mq`N(4OvkaS;>M{6g4SpQqsgll9h1EP>kC3%)td@4RvJY
zf6ju`xFt1k$puZCnzWFR)%hypg9g@MCqvWBq;}M0Y(Q!!!pq#lQ~3wTbGXVsPr4yz
z!t3trjyYC8?Dp>Pn>TOY*;wDW_wjK3qg#V}>w}&*T<05i?ryMK%;$ISb@yVqJLrx?
z+D#^L(&aBaEHdw6G@WFUEs98_G<S}4jOr64U^T2|tA6w>+KyGuJq>FKZ5mcpKTgv+
zZ&maRTVBA~TmB)Q>vZc38$6GZK|D_o$pH32gD;8*PYB|OxjQ!OAl4edvIK$_LL5S>
zgNRr_sI`D1{vr6`O=oUKgnLU*D+3}mu3~B6GqbgAMf3f3d28ClfU&nt(mVceG_7xQ
zm0~NcZDFfrg$OCBq^P61BC?8Z;^mabNBUgwJ=LyeE~R<`rP4+_xq^h$hB6LmGSO#U
zE^AHEn9EC=Uz|e+w)7wv^8_!v)MQDn;%M8E7x5#{YNE|e!RO>Widi0IsVN!;)WtES
zI}A*bTK0<FvKmg)mTRbMpCXl^zREkgb+x0UF_)k5Akl5F3qz`ifEacMKgZ{Qv~;(0
zbf-_L(pS4V6r^F)hn;Co3t2I`LqNc*|KN%@C+J|tVV9dzHxFF9cp^^S;?e}`eu`i<
W4)|xfe@u8St7R=?Xy$S1*Z%_n)tFiU

literal 0
HcmV?d00001

diff --git a/SAS/TMSS/test/oidc/example/__pycache__/views.cpython-34.pyc b/SAS/TMSS/test/oidc/example/__pycache__/views.cpython-34.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..0f4d528467e3f5573f883a850f931ff5a4c697dc
GIT binary patch
literal 5088
zcmbtYOLH5?5$;*MUnB@VCF%i6lqFaup+_uPijtU8Sc&3<a*1?k?IEZob^tE84`_BF
zim;Tblzd2Tag~2Ta>}pB#g|m&7tAp^`H~;tQ@Uq%@t{<8r9yH?J2SmAv)%oD{jL5}
zt`z_9&D!t2UI6$vO#Ec<{$sqt6$8M=KL##<$G~LZ8DJW~Xw(2V1!hWFr0CvDgP8^|
z17=2Bq}6B^%&hKbR6hr1PWQ9m=D{m~S<w9)xJB?vV3u@04{jN}3YZn}X26^QZx+m1
zJy!s?3f>%;bKup$tm)ArxbxsGfVlwPBAAQdErGcN-ZGfW;H`kU0^S)g&uHrsxM#sz
z1#=Y`PlH_seh%yk*fa3PVCH$?=b?82>{)ojz^;Nl2VVl+n-`O*8rbt-FF=>!II|bQ
zUP^50iOsUISxIcpfPFTxSxanI)fTw>`DE%+GIc>sT?D(HOkGZ<)~0vA1oq{`W<9Z4
zk9Vg-8tRZ&z<w_=!~3gXU!$)1Uh+uetw-Jm`+D*S+8eOj_~2VgnT8<?1L1PN9oSsv
z`<4iK>v_0C;}6{d?+eF|WL|LF5xgBWm@KsiLU2E7S^YkCbG<O|oz4l~Ry^y7w*(LS
zfgf@lm_<9Ai&n(FzH3Fik&?B&;E4O%LImPzY$z8$3%PjgcY^8RxozLx<zeUq{!{*Z
zz{3a+RoZH|aZIfus}H#)xEL?l$jWn%{TG(&*jt0>5Krl}t!TV}tR=G(tE|Rjk6Z^I
zn0`(cpU{9hm|TescLxVO^u=SF*6%o6$okY0PkGk~BQd!#I%6q33<j><>Ikdrk^f?!
zR|3awxB4P@;h?W%5u<@U-f><^_>MK(6s6>c;msqw!ZbDr>)nR2Gc5m-yKYcF4uos3
z{SCxfoEtaa?KM3Np4IFf4LF9yYu>rdZ``<b`|ifwdmnW+esuHT-p0WltFyuH-v02e
zeba995AQW`wWEV(*NK|_lc3+^FD(oqZ@zH&ak$w(kr@Xg`*NPnf+PcAh;!HuD@`%+
z@-pY~=rFKDnf~<2?%wEC=!q&`>Q(-PHz!*V0rVL342V+DOG7UMZ*U|_Z;61#zt;d!
z4x&7~!tnyl5~vXon9TeB7Nnyh92G%)3G6ikHe5^0an+GIvR#@mw7dU|C=dWmf6w4^
zA200fHZnWI%pmG)eDv)<@LVz*MpiTkoj;@J8zqsYvHU1jmkur8c6qC<G6yq%9Cz%f
zl$Du|YlTNLm-O>p9<^*MvSilg1Uiw&MMaKQ&P74XcG{6ZMkiz<>@=1n<Ku{mRkB^2
z+6{5m4-ly<I29JL0VBoM*?CrD;(hE5XPed(AY|$n+v9*x#i-$>0`f1sIk`R+kVpjt
zqk%4u?{nmMjLE+L0$rb1uK#Q7`roRD3(zZ4_%KFxik!|~VJJ!zxz`4~GC&L<D$@<c
z1fv)sb7!D81JNv?sSdl1n^J_|j#r#9RZ0k3A14K`Fncm;BoAkzIl7~D@Th6E8CD6s
zS);HsynvL(ofq7$JDN1=<Rrg7iWPs^ZaGLydrlJ*V7RgyP(nL?w|<NWt{;oQ@7}L}
z@ml4FEJVmrtpSpmEGLB78VILRQ;8$i(6REIrr7CdB-tVe8R9ZE>tvj^L^qVVLvCT7
zgkqM)vTo4r;uaa39}K#(j5))JPH`t!P#{%8$!sSOo)yUyGLbCMc7bq)Tr8qRBO~)P
zLfeW44w!|}66TEZ0Z!@EGx1gQo!VebE-DL`PrXNyhxDo;B_pI#Y=zBZ9xkym%d$$U
znObD$n7D^?!?ot-F_PEDksloU&7jYH$KGfMe#hw!G=H{fjpT>Cqf&GUZ!r9kB8;9Q
z49qGF^lpRgG%^ZJ6Fy%`5ZjtsTU^CuCUB+Rjb2OK$L0=R3Aa&ym}&NokHU8!h}dlM
zK*>?kr;n1RHQu$S%+m~|%a12Een1;%n-LYw(avcc9HI4dOw1po&zMsa%$_1qq|Yi#
z=<}a3h8>0B67<S3fHiS44N^oULN1y~a0&)OnqX@{uSy8F^9ppxw*`gC9N0y8V3(3k
z86F^1--7iYkxS+jm+Uf4<A)RHsEl_+Mo~a!@qvh1P|JP)nMfAG>r-cX*D;c6Fq>3^
zVKZR}&Gm{XsQhbrQMaPHicviQfcOA|v?ntPS8^tPI<6FQPQ%USVOuzT5-84BL@-s<
zRBpUxlZ4f<{3!4v+-PI(q|asMp%psqVRe6he`9OR=X^My951G>`}Hev(VCyY!9rZG
z)KN#Aa~V&n=tf+jyZ5NMNe$H`jgdLJyG6}yYN)^n!iT0BDqD0|K}jh~fua`mhK;m2
zlWe9*MA3~b>h~vR5!Yy)Q?wF4lsNNaGzl+ZLsjhpn`H}%PgdC?tKjbv_AcT$^~5$>
z4GT?TVPftPXi_*n0?jwr`Ts%lN@<R$1RX=A?BB=bZvr$3n7Ei}kRWCdB=|{!AVHC%
zifB}Yo~l(n#7P!$l2a-NJ9)$j>XLVqKP6YNh-tYZeuA$gD@mRyr;DHASe#gIWy}wx
z+EmS2j`JpQ>i?2DGk69;^e9TyjIu^Nq;3TpIrngT>*@B>*4~p(ws%^4&pzF5q;y(P
zZ4y7H=2U{rjgFG!$Y*F3l5QI3$O@wjsZv%v#_={HO#M(QP)0w(EBpeRlVwFRs+yo+
zA<Y1c?fbVdz$q2lPvX$lNdClv=6h9%(jZs`Vx{vOqU@y7!9_6kRIjo-avtBI73wdY
z#I?hPUkBY_5Y-2MI8Yxeoq@ZibcWU<wRnDFwvn5_dI@C$KCk46Af2wUx@-rnet<G1
zo=$X%QV!_$Uy&ubeM-W(r~O~RW%LSdv<)e~z*1}(kytlsRObz`gJZ*7b8JRNKsB5S
zm~#GK@FtdVr~(-rp(y+feF3~djgNvYlgujRN$!Lg7-&Eb<|!BtqJmbKC?Si<6G~@N
z@gluXf2j1)=Lp54EQqfGVHxj(dNWz+j5giHN=CX9<D*cP+Y#6Cq{=zNihiNZp5ynd
zTQU4hQf27w;qtR!P;VawA+P%Z6@|K<XjCU;T~>b<i83xKOIQVvtMHo@)fst5+@OZE
ziOj`WAqq5<qGrs|<X>6nh``ey4fGw+Fw`Msn$+Lvpe;=1S%RBBKBePl)Qk(L)QZ@a
z*?B|k(R$Zp8Fi)W9BeAxst>3Rav7%uMN*4S`ZQ7%!>p(;p3Ot<_DQtTvCOI-i~l14
z1+;o2IU6|bO}aeHehXEtsw|2q$T#L3eSB_OD*s!W(}*zyk%~F1CQ+huRYA2p)>NbF
z6|ud}`~@;EDDy$&xJm=de5>VK9&fee{L~kCOMT<YjKFYm^;N6Bpp;-$!cdSXQ*}ns
zipq24tO<WBx>5=`|G*3Efy;kN>=RP4TQbU6va0d#Ib(@!8dYO4wVGKktS_!+@ZZI&
IGv&&E0WV^oFaQ7m

literal 0
HcmV?d00001

diff --git a/SAS/TMSS/test/oidc/example/__pycache__/wsgi.cpython-34.pyc b/SAS/TMSS/test/oidc/example/__pycache__/wsgi.cpython-34.pyc
new file mode 100644
index 0000000000000000000000000000000000000000..4d49e3aecb16e15abd0ed70fdceb8eb407a468ec
GIT binary patch
literal 309
zcmY+6Jx&8L5QWFyY!b*uqN1nVg7^XmAt0ioL1|pD+Qfr#Vy`W4en^RO6e{k)Dcn-g
zZ~!Vs1R*h+d3rN?^L-Y>!{@h?`xgLsr}?Xl+yzZLCQ;N3EP&j54wixAAUQCVfy@9o
zU<Nw|vI7V(&q3`?7k0oHeczS&2ch7$Wa-!&HLF7OYojn$rZLN^4nYq8-BWIHjkfHi
zp7mOrM#}*{)h<{ZQ95#>rA|{jOd0KIFs)lNyYkJ0(J8+kUrugbAs$fbN4`$QqjxCg
uixoC4I<fI_F%{Y@McDcve$isnYBLJk)IW1xtt_51BD8&yf*<iNE7&*8olx`u

literal 0
HcmV?d00001

diff --git a/SAS/TMSS/test/oidc/example/app.py b/SAS/TMSS/test/oidc/example/app.py
new file mode 100644
index 00000000000..806dc4cab84
--- /dev/null
+++ b/SAS/TMSS/test/oidc/example/app.py
@@ -0,0 +1,60 @@
+from flask.app import Flask
+from flask.helpers import url_for
+from jwkest.jwk import RSAKey, rsa_load
+
+from pyop.authz_state import AuthorizationState
+from pyop.provider import Provider
+from pyop.subject_identifier import HashBasedSubjectIdentifierFactory
+from pyop.userinfo import Userinfo
+
+
+def init_oidc_provider(app):
+    with app.app_context():
+        issuer = url_for('oidc_provider.index')[:-1]
+        authentication_endpoint = url_for('oidc_provider.authentication_endpoint')
+        jwks_uri = url_for('oidc_provider.jwks_uri')
+        token_endpoint = url_for('oidc_provider.token_endpoint')
+        userinfo_endpoint = url_for('oidc_provider.userinfo_endpoint')
+        registration_endpoint = url_for('oidc_provider.registration_endpoint')
+        end_session_endpoint = url_for('oidc_provider.end_session_endpoint')
+
+    configuration_information = {
+        'issuer': issuer,
+        'authorization_endpoint': authentication_endpoint,
+        'jwks_uri': jwks_uri,
+        'token_endpoint': token_endpoint,
+        'userinfo_endpoint': userinfo_endpoint,
+        'registration_endpoint': registration_endpoint,
+        'end_session_endpoint': end_session_endpoint,
+        'scopes_supported': ['openid', 'profile'],
+        'response_types_supported': ['code', 'code id_token', 'code token', 'code id_token token'],  # code and hybrid
+        'response_modes_supported': ['query', 'fragment'],
+        'grant_types_supported': ['authorization_code', 'implicit'],
+        'subject_types_supported': ['pairwise'],
+        'token_endpoint_auth_methods_supported': ['client_secret_basic'],
+        'claims_parameter_supported': True
+    }
+
+    userinfo_db = Userinfo(app.users)
+    signing_key = RSAKey(key=rsa_load('signing_key.pem'), alg='RS256')
+    provider = Provider(signing_key, configuration_information,
+                        AuthorizationState(HashBasedSubjectIdentifierFactory(app.config['SUBJECT_ID_HASH_SALT'])),
+                        {}, userinfo_db)
+
+    return provider
+
+
+def oidc_provider_init_app(name=None):
+    name = name or __name__
+    app = Flask(name)
+    app.config.from_pyfile('application.cfg')
+
+    app.users = {'test_user': {'name': 'Testing Name'}}
+
+    from .views import oidc_provider_views
+    app.register_blueprint(oidc_provider_views)
+
+    # Initialize the oidc_provider after views to be able to set correct urls
+    app.provider = init_oidc_provider(app)
+
+    return app
diff --git a/SAS/TMSS/test/oidc/example/application.cfg b/SAS/TMSS/test/oidc/example/application.cfg
new file mode 100644
index 00000000000..36a1da67232
--- /dev/null
+++ b/SAS/TMSS/test/oidc/example/application.cfg
@@ -0,0 +1,5 @@
+SERVER_NAME = 'localhost:9090'
+SECRET_KEY = 'secret_key'
+SESSION_COOKIE_NAME='pyop_session'
+SUBJECT_ID_HASH_SALT = 'salt'
+PREFERRED_URL_SCHEME = 'https'
\ No newline at end of file
diff --git a/SAS/TMSS/test/oidc/example/https.crt b/SAS/TMSS/test/oidc/example/https.crt
new file mode 100644
index 00000000000..20a202d8992
--- /dev/null
+++ b/SAS/TMSS/test/oidc/example/https.crt
@@ -0,0 +1,24 @@
+-----BEGIN CERTIFICATE-----
+MIIEBjCCAu6gAwIBAgIJAIybVu7kfIK0MA0GCSqGSIb3DQEBBQUAMF8xCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQxGDAWBgNVBAMTD2xva2kuaXRzLnVtdS5zZTAeFw0xNTEy
+MTAxNDQyMDFaFw0yNTEyMDcxNDQyMDFaMF8xCzAJBgNVBAYTAkFVMRMwEQYDVQQI
+EwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQx
+GDAWBgNVBAMTD2xva2kuaXRzLnVtdS5zZTCCASIwDQYJKoZIhvcNAQEBBQADggEP
+ADCCAQoCggEBALiLDBwIteIobC+7JHoNeQRrTIbws9BghN4UUzyLo7+xeP9YwHaS
+tq6HqYK4cVLyx8k06Siw/4PwqMPNj9/B4f/ZXhEkXgbBP5TP36UgKrUIk4zInRFb
+Rjy+DcqjSZdgW1CKBKWJstXjSYen5rPm+voM/0msi164NPcfDMQIZmcQWh0MmEfG
+qlvdwTvjdaAQt8p7CGsxIdu4gPfhubknbTQKu+BVq5/RCVP7VU830PSr1RYhthX8
+Gt8ir32jEdDdjIrfA/zFx6PChyLkQFXtg/9WymnIM1j2ngNreL2nppwnqMYRnI9i
+C/y7MY4al3WeL9IETrtgh1jzXUNpgpJ03B0CAwEAAaOBxDCBwTAdBgNVHQ4EFgQU
+cTNphzIIRpQBQ2VT0Vx9xQYzNN0wgZEGA1UdIwSBiTCBhoAUcTNphzIIRpQBQ2VT
+0Vx9xQYzNN2hY6RhMF8xCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRl
+MSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxGDAWBgNVBAMTD2xv
+a2kuaXRzLnVtdS5zZYIJAIybVu7kfIK0MAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcN
+AQEFBQADggEBAJYJfUqOPTyZ+tflKoN4l+scIXpBxqtQbjX+MYli6VHpl+M8y163
+KsCglXPddL7Z58KBrUDx1m6f7dFQ3PMYn/S2dUcRrNOdfaDKZ5QgyYj/iVr8HSOh
+6i1OtMFaBqW5WyqA5YgvUz63hZ2kDOBHZcEfSn2+roylBUiueV9gFNKDWneNMLo2
+PMZxcGWZ3wIQbu9ahakbJUvTigFStKeLoY1A2ZSTH7W4elB5DDxYOKZSzd/KZpfn
+/o/Pc7YbbEUYgIyf3QNusdH+t2pw9ZkrlKMhiv9ZAmjAWMDY7O/i3r7u7AkODu7z
+OHbH3rJqkbaiS8/q0cMZG6AMUzPRglzsTc4=
+-----END CERTIFICATE-----
diff --git a/SAS/TMSS/test/oidc/example/https.key b/SAS/TMSS/test/oidc/example/https.key
new file mode 100644
index 00000000000..2abb725087f
--- /dev/null
+++ b/SAS/TMSS/test/oidc/example/https.key
@@ -0,0 +1,27 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIIEowIBAAKCAQEAuIsMHAi14ihsL7skeg15BGtMhvCz0GCE3hRTPIujv7F4/1jA
+dpK2roepgrhxUvLHyTTpKLD/g/Cow82P38Hh/9leESReBsE/lM/fpSAqtQiTjMid
+EVtGPL4NyqNJl2BbUIoEpYmy1eNJh6fms+b6+gz/SayLXrg09x8MxAhmZxBaHQyY
+R8aqW93BO+N1oBC3ynsIazEh27iA9+G5uSdtNAq74FWrn9EJU/tVTzfQ9KvVFiG2
+Ffwa3yKvfaMR0N2Mit8D/MXHo8KHIuRAVe2D/1bKacgzWPaeA2t4vaemnCeoxhGc
+j2IL/LsxjhqXdZ4v0gROu2CHWPNdQ2mCknTcHQIDAQABAoIBAQCooAWUqDDqUl1o
+z+voyt7FtvXaZ58mzMsb0h6suDwMMTKKwKI8tprOp4+wrrB+RvFfXUWftPwFp6XO
+JMtOfm7vxcM6jqyMJ5DdfYSx8c6UVR3eCoHbFjf70P3xJ3tbIuTNlw/f4w7Sejj6
+B+W6hVjXm4C55TwEdPWQyYJ0rehESxITn7DDjDNXxxwDqwAv8yPTYf8m7mb7qh3V
+EBnvZPIWpgEIVqV2crQSfHJwg29KhS7cnExJBYEPppBQ4aoUGyMqJN0EyBL4DrRo
+Ds6hPttLaXEmB+ACm/OQzhEeFKduob5OIKRSyp9Z8t6/B9uHiIKCENRU4O4zux57
+jZ8MIypRAoGBAO0anHYHlniyP5f/8u4kvTnW3wbDtRJ3L3zVSKN8shQmQnjNWmsI
+LhLWLU2OTRsXlJB7oYqNBojUBdeGimYot9kmjx4XkxELB6XAaYDG6pDvM7axC5qH
+iuC4jVHdEsIfy9dP6wZ/b+A+JOWWpS1vdAizfvgWI32JLGDPkjjUlzNvAoGBAMdA
+F5KZZLZFYsZM470/bb20qFMURDRI+5yz0VUHTNUQEr/xhMcYFske6ox14A7gXzwd
+SHAvTDkV8DsGu/FzSWzZmVhNc1EtdM3Cbe3Y7WJoIQoupuxBDEvrBE9riyOjW61q
+dYIO2ymfJfc2Vx6d7LAK9itXdqa3RIo7ZPK7EbMzAoGAAR1C5vsaJe8QhXJafewG
+R6NO4QVCcJfGzVtjQAFyBM45OcAdUKt1K/l9tQOaMSpnNFagZ7pJ8ZKthFnJhLlk
+Q8z+lzGdK1NV8d15oXVN3OiC4bTrTQqeCHhVkbDsSaVEm/pwLFOk/vTLz5hpplED
+xpaxXhEckZZ3cu0GzuWQ4FkCgYAb34tspqjAFtTKiNcTElx3vV4OwTcJWWxZb45J
+JsxIwgbdcxvv/h6x4/FL1PGTIzAvaKlJiFRRaBBDMZ35GPeckpQxFiSbppBAeIKI
+U2Bh888rbXtMcY0W0bm4ooLEaYXZrJrjptBh8jGNc7ycO9twhRgK2CFxERI1hDmK
++0BuoQKBgHLRFdJYFkNKB+j56vTlB5AFTXcjs6x0dZ4j0SDAqVYgIHErpd2bhgOz
+s698rfdadwbYN4UEqvV/2NhDLx/jag4BvCermK71HaXvYnvqKw3UHGAG8K/dCCFR
+eSuZnxTSoWAPjMLi64hRzbWVT+oeZuy83lG6qDygbI1z1nlYWdo3
+-----END RSA PRIVATE KEY-----
diff --git a/SAS/TMSS/test/oidc/example/requirements.txt b/SAS/TMSS/test/oidc/example/requirements.txt
new file mode 100644
index 00000000000..e6b8e9f26cf
--- /dev/null
+++ b/SAS/TMSS/test/oidc/example/requirements.txt
@@ -0,0 +1,3 @@
+pyop
+Flask
+gunicorn
\ No newline at end of file
diff --git a/SAS/TMSS/test/oidc/example/signing_key.pem b/SAS/TMSS/test/oidc/example/signing_key.pem
new file mode 100644
index 00000000000..74fe00d413e
--- /dev/null
+++ b/SAS/TMSS/test/oidc/example/signing_key.pem
@@ -0,0 +1,15 @@
+-----BEGIN RSA PRIVATE KEY-----
+MIICXAIBAAKBgQCi7nye2Ye1MrUD/sZAplfkpMkXHYduydvfvv/+Ihx1ClxKS/KG
+/1EhqyBVVvhHLRs9pimKMyLm2pBE51rGOt//XKhsoFAa37VID2iz7DQuV6DGgyBS
+FaKgaYBpinEQy2WcjU4eABnABV2r+K2UmGkqVJheqHqOqHUKasT4gy/6kQIDAQAB
+AoGAf7u+YX6ioNCvDwHHBVojn/H8YK3axmVkhiYkZWTysGM99VVTPridL2sMfzse
+jBZ1u8Av4tOyMg/5eLtz8+KmRjljpeAEFfsA1htWE8vESXnvDFwKldXD9Vi/kppb
+CYqASGCBUX3i1LPYffvjUxIgD+Tjx4k56c5EN5G331flDV0CQQDP8fWraegLJ+K1
+iXGNQzpaqG3EI3vf35Yb2bJpmD39QIXFIcJJ5MZHVW+1TyvgiavM4hS2+LGA8kGh
+OvMWfbYTAkEAyJWGBUmAW9mooo1Vw4tJjEWAHjHvzcQ4dqIju+WN8Xy5JTWkDD6Z
+VgKGtgLt2HfpSsgej14+Rh5mrjo4SbYxSwJAKG0syq9jOk/9xjc7STBJtvhJprkT
+SxnHsBBpnBfJ7WNO3l1KzVzZo2Kbvg7vQ87gBIvrZQsCT0RJuBOi0LuN2wJAAInm
+Qj1gSt7axRT8FfpZyDankW0w56yPOkJVNjv3lZ5wINl0B1RjtQdstTBs0xf/WGQR
+MPFf2XBbdjxRymDi4QJBAM3MUYPOlUk1UVCSQKyCkBwL3zMaPjBjD5LXkhGJzxsb
+T74NznwmCib/r0Rl/KmD7/bAq7R4aheOS/OMaZyhbkk=
+-----END RSA PRIVATE KEY-----
diff --git a/SAS/TMSS/test/oidc/example/templates/logout.jinja2 b/SAS/TMSS/test/oidc/example/templates/logout.jinja2
new file mode 100644
index 00000000000..4322588f219
--- /dev/null
+++ b/SAS/TMSS/test/oidc/example/templates/logout.jinja2
@@ -0,0 +1,8 @@
+<!doctype html>
+<title>Logout</title>
+
+Do you really want to logout?
+<form method="POST">
+    <button type="submit" name="logout" value="logout">Yes, logout</button>
+    <button type="submit" name="no_logout" value="no_logout">No, cancel logout</button>
+</form>
\ No newline at end of file
diff --git a/SAS/TMSS/test/oidc/example/views.py b/SAS/TMSS/test/oidc/example/views.py
new file mode 100644
index 00000000000..7d7335bb224
--- /dev/null
+++ b/SAS/TMSS/test/oidc/example/views.py
@@ -0,0 +1,124 @@
+from urllib.parse import urlencode, parse_qs
+
+import flask
+from flask import Blueprint, redirect
+from flask import current_app
+from flask import jsonify
+from flask.helpers import make_response
+from flask.templating import render_template
+from oic.oic.message import TokenErrorResponse, UserInfoErrorResponse, EndSessionRequest
+
+from pyop.access_token import AccessToken, BearerTokenError
+from pyop.exceptions import InvalidAuthenticationRequest, InvalidAccessToken, InvalidClientAuthentication, OAuthError, \
+    InvalidSubjectIdentifier, InvalidClientRegistrationRequest
+from pyop.util import should_fragment_encode
+
+oidc_provider_views = Blueprint('oidc_provider', __name__, url_prefix='')
+
+
+@oidc_provider_views.route('/')
+def index():
+    return 'Hello world!'
+
+
+@oidc_provider_views.route('/registration', methods=['POST'])
+def registration_endpoint():
+    try:
+        response = current_app.provider.handle_client_registration_request(flask.request.get_data().decode('utf-8'))
+        return make_response(jsonify(response.to_dict()), 201)
+    except InvalidClientRegistrationRequest as e:
+        return make_response(jsonify(e.to_dict()), status=400)
+
+
+@oidc_provider_views.route('/authentication', methods=['GET'])
+def authentication_endpoint():
+    # parse authentication request
+    try:
+        auth_req = current_app.provider.parse_authentication_request(urlencode(flask.request.args),
+                                                                     flask.request.headers)
+    except InvalidAuthenticationRequest as e:
+        current_app.logger.debug('received invalid authn request', exc_info=True)
+        error_url = e.to_error_url()
+        if error_url:
+            return redirect(error_url, 303)
+        else:
+            # show error to user
+            return make_response('Something went wrong: {}'.format(str(e)), 400)
+
+    # automagic authentication
+    authn_response = current_app.provider.authorize(auth_req, 'test_user')
+    response_url = authn_response.request(auth_req['redirect_uri'], should_fragment_encode(auth_req))
+    return redirect(response_url, 303)
+
+
+@oidc_provider_views.route('/.well-known/openid-configuration')
+def provider_configuration():
+    return jsonify(current_app.provider.provider_configuration.to_dict())
+
+
+@oidc_provider_views.route('/jwks')
+def jwks_uri():
+    return jsonify(current_app.provider.jwks)
+
+
+@oidc_provider_views.route('/token', methods=['POST'])
+def token_endpoint():
+    try:
+        token_response = current_app.provider.handle_token_request(flask.request.get_data().decode('utf-8'),
+                                                                   flask.request.headers)
+        return jsonify(token_response.to_dict())
+    except InvalidClientAuthentication as e:
+        current_app.logger.debug('invalid client authentication at token endpoint', exc_info=True)
+        error_resp = TokenErrorResponse(error='invalid_client', error_description=str(e))
+        response = make_response(error_resp.to_json(), 401)
+        response.headers['Content-Type'] = 'application/json'
+        response.headers['WWW-Authenticate'] = 'Basic'
+        return response
+    except OAuthError as e:
+        current_app.logger.debug('invalid request: %s', str(e), exc_info=True)
+        error_resp = TokenErrorResponse(error=e.oauth_error, error_description=str(e))
+        response = make_response(error_resp.to_json(), 400)
+        response.headers['Content-Type'] = 'application/json'
+        return response
+
+
+@oidc_provider_views.route('/userinfo', methods=['GET', 'POST'])
+def userinfo_endpoint():
+    try:
+        response = current_app.provider.handle_userinfo_request(flask.request.get_data().decode('utf-8'),
+                                                                flask.request.headers)
+        return jsonify(response.to_dict())
+    except (BearerTokenError, InvalidAccessToken) as e:
+        error_resp = UserInfoErrorResponse(error='invalid_token', error_description=str(e))
+        response = make_response(error_resp.to_json(), 401)
+        response.headers['WWW-Authenticate'] = AccessToken.BEARER_TOKEN_TYPE
+        response.headers['Content-Type'] = 'application/json'
+        return response
+
+
+def do_logout(end_session_request):
+    try:
+        current_app.provider.logout_user(end_session_request=end_session_request)
+    except InvalidSubjectIdentifier as e:
+        return make_response('Logout unsuccessful!', 400)
+
+    redirect_url = current_app.provider.do_post_logout_redirect(end_session_request)
+    if redirect_url:
+        return redirect(redirect_url, 303)
+
+    return make_response('Logout successful!')
+
+
+@oidc_provider_views.route('/logout', methods=['GET', 'POST'])
+def end_session_endpoint():
+    if flask.request.method == 'GET':
+        # redirect from RP
+        end_session_request = EndSessionRequest().deserialize(urlencode(flask.request.args))
+        flask.session['end_session_request'] = end_session_request.to_dict()
+        return render_template('logout.jinja2')
+    else:
+        form = parse_qs(flask.request.get_data().decode('utf-8'))
+        if 'logout' in form:
+            return do_logout(EndSessionRequest().from_dict(flask.session['end_session_request']))
+        else:
+            return make_response('You chose not to logout')
diff --git a/SAS/TMSS/test/oidc/example/wsgi.py b/SAS/TMSS/test/oidc/example/wsgi.py
new file mode 100644
index 00000000000..f98a0a82188
--- /dev/null
+++ b/SAS/TMSS/test/oidc/example/wsgi.py
@@ -0,0 +1,7 @@
+import logging
+
+from example.app import oidc_provider_init_app
+
+name = 'oidc_provider'
+app = oidc_provider_init_app(name)
+logging.basicConfig(level=logging.DEBUG)
diff --git a/SAS/TMSS/test/oidc/realm-export.json b/SAS/TMSS/test/oidc/realm-export.json
new file mode 100644
index 00000000000..5fa0d5bae6b
--- /dev/null
+++ b/SAS/TMSS/test/oidc/realm-export.json
@@ -0,0 +1,1808 @@
+
+  "id": "demo",
+  "realm": "demo",
+  "notBefore": 0,
+  "revokeRefreshToken": false,
+  "refreshTokenMaxReuse": 0,
+  "accessTokenLifespan": 300,
+  "accessTokenLifespanForImplicitFlow": 900,
+  "ssoSessionIdleTimeout": 1800,
+  "ssoSessionMaxLifespan": 36000,
+  "offlineSessionIdleTimeout": 2592000,
+  "offlineSessionMaxLifespanEnabled": false,
+  "offlineSessionMaxLifespan": 5184000,
+  "accessCodeLifespan": 60,
+  "accessCodeLifespanUserAction": 300,
+  "accessCodeLifespanLogin": 1800,
+  "actionTokenGeneratedByAdminLifespan": 43200,
+  "actionTokenGeneratedByUserLifespan": 300,
+  "enabled": true,
+  "sslRequired": "external",
+  "registrationAllowed": false,
+  "registrationEmailAsUsername": false,
+  "rememberMe": false,
+  "verifyEmail": false,
+  "loginWithEmailAllowed": true,
+  "duplicateEmailsAllowed": false,
+  "resetPasswordAllowed": false,
+  "editUsernameAllowed": false,
+  "bruteForceProtected": false,
+  "permanentLockout": false,
+  "maxFailureWaitSeconds": 900,
+  "minimumQuickLoginWaitSeconds": 60,
+  "waitIncrementSeconds": 60,
+  "quickLoginCheckMilliSeconds": 1000,
+  "maxDeltaTimeSeconds": 43200,
+  "failureFactor": 30,
+  "roles": {
+    "realm": [
+      {
+        "id": "9cdbfeb1-7dea-442b-a798-0845a15b3fef",
+        "name": "uma_authorization",
+        "description": "${role_uma_authorization}",
+        "composite": false,
+        "clientRole": false,
+        "containerId": "demo",
+        "attributes": {}
+      },
+      {
+        "id": "5c42617f-2d8c-422d-8d15-b7630067990e",
+        "name": "demo-administrator",
+        "composite": false,
+        "clientRole": false,
+        "containerId": "demo",
+        "attributes": {}
+      },
+      {
+        "id": "46b3080a-2957-4bd6-9d06-da15dac503e4",
+        "name": "promoter",
+        "composite": false,
+        "clientRole": false,
+        "containerId": "demo",
+        "attributes": {}
+      },
+      {
+        "id": "16a657ca-7a97-4d27-b1d1-8a65087a1f19",
+        "name": "offline_access",
+        "description": "${role_offline-access}",
+        "composite": false,
+        "clientRole": false,
+        "containerId": "demo",
+        "attributes": {}
+      },
+      {
+        "id": "cecacd7e-1775-4ac1-ba99-93d9020ead83",
+        "name": "monitoring",
+        "composite": false,
+        "clientRole": false,
+        "containerId": "demo",
+        "attributes": {}
+      }
+    ],
+    "client": {
+      "realm-management": [
+        {
+          "id": "e441b81d-6ae3-4cf7-bfbf-ed4215eb2f47",
+          "name": "create-client",
+          "description": "${role_create-client}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "8c7cd27d-dfc5-4f83-80bf-373255b888d2",
+          "name": "manage-realm",
+          "description": "${role_manage-realm}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "ed027a01-9172-4e2c-a3c5-959de3d24c22",
+          "name": "view-users",
+          "description": "${role_view-users}",
+          "composite": true,
+          "composites": {
+            "client": {
+              "realm-management": [
+                "query-users",
+                "query-groups"
+              ]
+            }
+          },
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "bf27552e-da67-4780-8d14-c795bc9f3407",
+          "name": "query-clients",
+          "description": "${role_query-clients}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "d6beb376-5920-411d-93fc-b90c091d3d2f",
+          "name": "manage-events",
+          "description": "${role_manage-events}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "3c5e7a53-7ead-4726-940f-d4190b0ec5c7",
+          "name": "manage-users",
+          "description": "${role_manage-users}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "031098a6-8e77-4e01-912b-e50a1f9e7749",
+          "name": "manage-authorization",
+          "description": "${role_manage-authorization}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "9fdf5253-30a4-4b3d-8ca7-2044396d9ccb",
+          "name": "query-groups",
+          "description": "${role_query-groups}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "d43dc9bd-5d06-4eae-9708-95f7e835fbfd",
+          "name": "view-clients",
+          "description": "${role_view-clients}",
+          "composite": true,
+          "composites": {
+            "client": {
+              "realm-management": [
+                "query-clients"
+              ]
+            }
+          },
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "693555e2-14e1-461e-98a1-c8cab8137f8f",
+          "name": "view-realm",
+          "description": "${role_view-realm}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "6b90a2d2-f366-457d-9469-c5da38f990a6",
+          "name": "query-realms",
+          "description": "${role_query-realms}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "c073af53-414a-41f0-b093-0b68c0f1ecba",
+          "name": "impersonation",
+          "description": "${role_impersonation}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "1283a4ff-5217-449c-8c85-ddff72ed73fd",
+          "name": "query-users",
+          "description": "${role_query-users}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "57a640d0-f017-46f8-a23e-d3fc7f8e7de8",
+          "name": "realm-admin",
+          "description": "${role_realm-admin}",
+          "composite": true,
+          "composites": {
+            "client": {
+              "realm-management": [
+                "manage-realm",
+                "create-client",
+                "view-users",
+                "query-clients",
+                "manage-events",
+                "manage-authorization",
+                "manage-users",
+                "query-groups",
+                "view-realm",
+                "view-clients",
+                "query-realms",
+                "impersonation",
+                "query-users",
+                "manage-clients",
+                "view-authorization",
+                "view-events",
+                "view-identity-providers",
+                "manage-identity-providers"
+              ]
+            }
+          },
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "5d4108b8-c2bc-44f2-9038-ea18bf3164ef",
+          "name": "manage-clients",
+          "description": "${role_manage-clients}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "7ee7d0f4-d61f-4e01-ac37-86a7d42e30a0",
+          "name": "view-authorization",
+          "description": "${role_view-authorization}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "9d6dd55e-2d70-4b35-9ae7-43b59ff84cd5",
+          "name": "view-events",
+          "description": "${role_view-events}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "e2b85d94-2efb-458f-ae69-84e04d0975e4",
+          "name": "view-identity-providers",
+          "description": "${role_view-identity-providers}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        },
+        {
+          "id": "466294e7-feb8-4d2f-ae45-6807160553ae",
+          "name": "manage-identity-providers",
+          "description": "${role_manage-identity-providers}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "0c80e92e-024a-4324-b516-cf814f7e09df",
+          "attributes": {}
+        }
+      ],
+      "demo-frontend": [],
+      "security-admin-console": [],
+      "admin-cli": [],
+      "demo-api": [],
+      "broker": [
+        {
+          "id": "b375956d-b283-45b7-aa0b-76e19d3c9b2f",
+          "name": "read-token",
+          "description": "${role_read-token}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "d165281c-4c36-4185-bfbd-589c8a52945f",
+          "attributes": {}
+        }
+      ],
+      "account": [
+        {
+          "id": "aada5867-9268-4e37-b206-491041cd3360",
+          "name": "view-profile",
+          "description": "${role_view-profile}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "f5c24837-4fc7-4994-9c1e-bd70f2cbeed6",
+          "attributes": {}
+        },
+        {
+          "id": "39893eb7-65a6-4d5e-9704-f59952deaeab",
+          "name": "manage-account",
+          "description": "${role_manage-account}",
+          "composite": true,
+          "composites": {
+            "client": {
+              "account": [
+                "manage-account-links"
+              ]
+            }
+          },
+          "clientRole": true,
+          "containerId": "f5c24837-4fc7-4994-9c1e-bd70f2cbeed6",
+          "attributes": {}
+        },
+        {
+          "id": "193f4d1a-4c4a-4029-9092-1aa44df23b42",
+          "name": "manage-account-links",
+          "description": "${role_manage-account-links}",
+          "composite": false,
+          "clientRole": true,
+          "containerId": "f5c24837-4fc7-4994-9c1e-bd70f2cbeed6",
+          "attributes": {}
+        }
+      ]
+    }
+  },
+  "groups": [
+    {
+      "id": "62909b3a-eb39-4916-a640-f401d0592a47",
+      "name": "administrator",
+      "path": "/administrator",
+      "attributes": {},
+      "realmRoles": [
+        "demo-administrator"
+      ],
+      "clientRoles": {},
+      "subGroups": []
+    },
+    {
+      "id": "3e007c9c-297e-4403-8ad0-1888e05211ad",
+      "name": "monitoring",
+      "path": "/monitoring",
+      "attributes": {},
+      "realmRoles": [
+        "monitoring"
+      ],
+      "clientRoles": {},
+      "subGroups": []
+    },
+    {
+      "id": "9ca100d3-c97c-495e-9e28-828bfe7e2405",
+      "name": "promoter",
+      "path": "/promoter",
+      "attributes": {},
+      "realmRoles": [
+        "promoter"
+      ],
+      "clientRoles": {},
+      "subGroups": []
+    }
+  ],
+  "requiredCredentials": [
+    "password"
+  ],
+  "otpPolicyType": "totp",
+  "otpPolicyAlgorithm": "HmacSHA1",
+  "otpPolicyInitialCounter": 0,
+  "otpPolicyDigits": 6,
+  "otpPolicyLookAheadWindow": 1,
+  "otpPolicyPeriod": 30,
+  "otpSupportedApplications": [
+    "FreeOTP",
+    "Google Authenticator"
+  ],
+  "clients": [
+    {
+      "id": "1f279ab9-a6b9-4d19-b9f3-f881ff2257eb",
+      "clientId": "admin-cli",
+      "name": "${client_admin-cli}",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": false,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": false,
+      "publicClient": true,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "f5c24837-4fc7-4994-9c1e-bd70f2cbeed6",
+      "clientId": "account",
+      "name": "${client_account}",
+      "baseUrl": "/auth/realms/demo/account",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [
+        "/auth/realms/demo/account/*"
+      ],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "0c80e92e-024a-4324-b516-cf814f7e09df",
+      "clientId": "realm-management",
+      "name": "${client_realm-management}",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": true,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "3bcece73-8800-42d4-82fe-7c0012be3780",
+      "clientId": "security-admin-console",
+      "name": "${client_security-admin-console}",
+      "baseUrl": "/auth/admin/demo/console/index.html",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [
+        "/auth/admin/demo/console/*"
+      ],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": true,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "protocolMappers": [
+        {
+          "id": "e70c7d3c-dd18-4084-81c8-c57a31a31cb9",
+          "name": "locale",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "locale",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "locale",
+            "jsonType.label": "String"
+          }
+        }
+      ],
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "2e0fa909-b029-4d69-ac0e-ff3e593d57e6",
+      "clientId": "demo-frontend",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [
+        "http://localhost:9002/*"
+      ],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": false,
+      "publicClient": true,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {
+        "saml.assertion.signature": "false",
+        "saml.force.post.binding": "false",
+        "saml.multivalued.roles": "false",
+        "saml.encrypt": "false",
+        "saml.server.signature": "false",
+        "saml.server.signature.keyinfo.ext": "false",
+        "exclude.session.state.from.auth.response": "false",
+        "saml_force_name_id_format": "false",
+        "saml.client.signature": "false",
+        "tls.client.certificate.bound.access.tokens": "false",
+        "saml.authnstatement": "false",
+        "display.on.consent.screen": "false",
+        "saml.onetimeuse.condition": "false"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": true,
+      "nodeReRegistrationTimeout": -1,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "d165281c-4c36-4185-bfbd-589c8a52945f",
+      "clientId": "broker",
+      "name": "${client_broker}",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": false,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": false,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {},
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": false,
+      "nodeReRegistrationTimeout": 0,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    },
+    {
+      "id": "79e1f43b-283a-40d9-a953-c60e9686d329",
+      "clientId": "demo-api",
+      "surrogateAuthRequired": false,
+      "enabled": true,
+      "clientAuthenticatorType": "client-secret",
+      "secret": "**********",
+      "redirectUris": [],
+      "webOrigins": [],
+      "notBefore": 0,
+      "bearerOnly": true,
+      "consentRequired": false,
+      "standardFlowEnabled": true,
+      "implicitFlowEnabled": false,
+      "directAccessGrantsEnabled": true,
+      "serviceAccountsEnabled": false,
+      "publicClient": false,
+      "frontchannelLogout": false,
+      "protocol": "openid-connect",
+      "attributes": {
+        "saml.assertion.signature": "false",
+        "saml.force.post.binding": "false",
+        "saml.multivalued.roles": "false",
+        "saml.encrypt": "false",
+        "saml.server.signature": "false",
+        "saml.server.signature.keyinfo.ext": "false",
+        "exclude.session.state.from.auth.response": "false",
+        "saml_force_name_id_format": "false",
+        "saml.client.signature": "false",
+        "tls.client.certificate.bound.access.tokens": "false",
+        "saml.authnstatement": "false",
+        "display.on.consent.screen": "false",
+        "saml.onetimeuse.condition": "false"
+      },
+      "authenticationFlowBindingOverrides": {},
+      "fullScopeAllowed": true,
+      "nodeReRegistrationTimeout": -1,
+      "defaultClientScopes": [
+        "web-origins",
+        "role_list",
+        "roles",
+        "profile",
+        "email"
+      ],
+      "optionalClientScopes": [
+        "address",
+        "phone",
+        "offline_access"
+      ]
+    }
+  ],
+  "clientScopes": [
+    {
+      "id": "90b602f0-d9a0-4781-9e9b-2dd5f7d28665",
+      "name": "address",
+      "description": "OpenID Connect built-in scope: address",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${addressScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "45f8b45d-72bb-47dc-ab28-2a5c83b81cf8",
+          "name": "address",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-address-mapper",
+          "consentRequired": false,
+          "config": {
+            "user.attribute.formatted": "formatted",
+            "user.attribute.country": "country",
+            "user.attribute.postal_code": "postal_code",
+            "userinfo.token.claim": "true",
+            "user.attribute.street": "street",
+            "id.token.claim": "true",
+            "user.attribute.region": "region",
+            "access.token.claim": "true",
+            "user.attribute.locality": "locality"
+          }
+        }
+      ]
+    },
+    {
+      "id": "10c965ec-c1af-4f24-9945-9d94fb7ee037",
+      "name": "email",
+      "description": "OpenID Connect built-in scope: email",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${emailScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "c31c85b3-9641-4656-b19a-eb399567522e",
+          "name": "email",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "email",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "email",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "33354d4c-bb3b-4410-9238-f5e8448dc955",
+          "name": "email verified",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "emailVerified",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "email_verified",
+            "jsonType.label": "boolean"
+          }
+        }
+      ]
+    },
+    {
+      "id": "1338f8cd-1cc1-4446-9f54-4e7db3e319d1",
+      "name": "offline_access",
+      "description": "OpenID Connect built-in scope: offline_access",
+      "protocol": "openid-connect",
+      "attributes": {
+        "consent.screen.text": "${offlineAccessScopeConsentText}",
+        "display.on.consent.screen": "true"
+      }
+    },
+    {
+      "id": "0c36241e-ca1f-415d-b218-4da0ef9fc57b",
+      "name": "phone",
+      "description": "OpenID Connect built-in scope: phone",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${phoneScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "9b8380cc-5aaf-4bf7-89b4-f29c7dba6a0c",
+          "name": "phone number",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "phoneNumber",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "phone_number",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "d2bdd3f1-3293-4c59-92c5-a64967a49ec7",
+          "name": "phone number verified",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "phoneNumberVerified",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "phone_number_verified",
+            "jsonType.label": "boolean"
+          }
+        }
+      ]
+    },
+    {
+      "id": "9ba88b22-dfd9-4544-966e-ab2e07e02283",
+      "name": "profile",
+      "description": "OpenID Connect built-in scope: profile",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "true",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${profileScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "abc6a671-5996-48ec-aac3-5d970b85e9a6",
+          "name": "family name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "lastName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "family_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "7a6e6867-8767-4782-8400-e8cba29588e2",
+          "name": "updated at",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "updatedAt",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "updated_at",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "a45c0185-4e57-4d73-a55c-de4e30886c84",
+          "name": "picture",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "picture",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "picture",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "8786b25f-17e7-4187-8d7d-d2aa61c6dcd8",
+          "name": "profile",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "profile",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "profile",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "95ece622-b3cc-49f5-b848-a9b8a5407ef4",
+          "name": "birthdate",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "birthdate",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "birthdate",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "59189670-aa93-4933-a796-53dab6a23e45",
+          "name": "locale",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "locale",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "locale",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "addccb2b-468f-404c-9e80-de9d344f6b94",
+          "name": "nickname",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "nickname",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "nickname",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "cd2fe8d9-f253-477f-94fd-29942d56849c",
+          "name": "website",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "website",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "website",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "450e97fc-e780-4262-8f26-0cf8ac903aa3",
+          "name": "username",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "username",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "preferred_username",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "4b40a0f9-44a1-4a01-b0c5-bfb8be5bccb6",
+          "name": "full name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-full-name-mapper",
+          "consentRequired": false,
+          "config": {
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "userinfo.token.claim": "true"
+          }
+        },
+        {
+          "id": "07343c50-155a-430c-8ebb-c68a931a0c80",
+          "name": "gender",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "gender",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "gender",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "8076d3ce-1d9f-4779-93c3-9b6a06e8bdac",
+          "name": "middle name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "middleName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "middle_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "d26abee7-b8a7-4ca5-9755-38e18cc539d6",
+          "name": "given name",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-property-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "firstName",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "given_name",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "83344728-39e4-4600-9c5b-b9d69da3c4f0",
+          "name": "zoneinfo",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-attribute-mapper",
+          "consentRequired": false,
+          "config": {
+            "userinfo.token.claim": "true",
+            "user.attribute": "zoneinfo",
+            "id.token.claim": "true",
+            "access.token.claim": "true",
+            "claim.name": "zoneinfo",
+            "jsonType.label": "String"
+          }
+        }
+      ]
+    },
+    {
+      "id": "289f7853-fa6e-4f17-a01d-6c409bddc25a",
+      "name": "role_list",
+      "description": "SAML role list",
+      "protocol": "saml",
+      "attributes": {
+        "consent.screen.text": "${samlRoleListScopeConsentText}",
+        "display.on.consent.screen": "true"
+      },
+      "protocolMappers": [
+        {
+          "id": "c0d2acb4-d20f-48de-94c7-ec6ac1c964e1",
+          "name": "role list",
+          "protocol": "saml",
+          "protocolMapper": "saml-role-list-mapper",
+          "consentRequired": false,
+          "config": {
+            "single": "false",
+            "attribute.nameformat": "Basic",
+            "attribute.name": "Role"
+          }
+        }
+      ]
+    },
+    {
+      "id": "6820c5fc-b7db-4a1d-b71a-8dbd0c7cf98c",
+      "name": "roles",
+      "description": "OpenID Connect scope for add user roles to the access token",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "false",
+        "display.on.consent.screen": "true",
+        "consent.screen.text": "${rolesScopeConsentText}"
+      },
+      "protocolMappers": [
+        {
+          "id": "1cb77c0d-6397-4b3d-acef-8ae1d4089944",
+          "name": "client roles",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-client-role-mapper",
+          "consentRequired": false,
+          "config": {
+            "multivalued": "true",
+            "user.attribute": "foo",
+            "access.token.claim": "true",
+            "claim.name": "resource_access.${client_id}.roles",
+            "jsonType.label": "String"
+          }
+        },
+        {
+          "id": "0cf6603d-891d-4f42-8da0-d187f0816a6d",
+          "name": "audience resolve",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-audience-resolve-mapper",
+          "consentRequired": false,
+          "config": {}
+        },
+        {
+          "id": "9e8111be-4598-4145-96da-a9a0fcda8169",
+          "name": "realm roles",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-usermodel-realm-role-mapper",
+          "consentRequired": false,
+          "config": {
+            "multivalued": "true",
+            "user.attribute": "foo",
+            "access.token.claim": "true",
+            "claim.name": "realm_access.roles",
+            "jsonType.label": "String"
+          }
+        }
+      ]
+    },
+    {
+      "id": "ba84e265-6519-45db-88fc-f86ec057f28c",
+      "name": "web-origins",
+      "description": "OpenID Connect scope for add allowed web origins to the access token",
+      "protocol": "openid-connect",
+      "attributes": {
+        "include.in.token.scope": "false",
+        "display.on.consent.screen": "false",
+        "consent.screen.text": ""
+      },
+      "protocolMappers": [
+        {
+          "id": "3d010883-cafc-4e4a-9246-3669d767f409",
+          "name": "allowed web origins",
+          "protocol": "openid-connect",
+          "protocolMapper": "oidc-allowed-origins-mapper",
+          "consentRequired": false,
+          "config": {}
+        }
+      ]
+    }
+  ],
+  "defaultDefaultClientScopes": [
+    "role_list",
+    "profile",
+    "email",
+    "roles",
+    "web-origins"
+  ],
+  "defaultOptionalClientScopes": [
+    "offline_access",
+    "address",
+    "phone"
+  ],
+  "browserSecurityHeaders": {
+    "contentSecurityPolicyReportOnly": "",
+    "xContentTypeOptions": "nosniff",
+    "xRobotsTag": "none",
+    "xFrameOptions": "SAMEORIGIN",
+    "xXSSProtection": "1; mode=block",
+    "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+    "strictTransportSecurity": "max-age=31536000; includeSubDomains"
+  },
+  "smtpServer": {},
+  "eventsEnabled": false,
+  "eventsListeners": [
+    "jboss-logging"
+  ],
+  "enabledEventTypes": [],
+  "adminEventsEnabled": false,
+  "adminEventsDetailsEnabled": false,
+  "components": {
+    "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
+      {
+        "id": "5d3fa6d3-b34b-4996-b19c-4b57c49968c0",
+        "name": "Allowed Client Scopes",
+        "providerId": "allowed-client-templates",
+        "subType": "authenticated",
+        "subComponents": {},
+        "config": {
+          "allow-default-scopes": [
+            "true"
+          ]
+        }
+      },
+      {
+        "id": "eee392b9-35d5-4166-90b3-87b8e7fe7102",
+        "name": "Allowed Client Scopes",
+        "providerId": "allowed-client-templates",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "allow-default-scopes": [
+            "true"
+          ]
+        }
+      },
+      {
+        "id": "5d4439dc-fae2-41ff-b1a9-36f70dc0ca79",
+        "name": "Trusted Hosts",
+        "providerId": "trusted-hosts",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "host-sending-registration-request-must-match": [
+            "true"
+          ],
+          "client-uris-must-match": [
+            "true"
+          ]
+        }
+      },
+      {
+        "id": "c1ba9983-f538-417e-8c7c-a4569946ac98",
+        "name": "Allowed Protocol Mapper Types",
+        "providerId": "allowed-protocol-mappers",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "allowed-protocol-mapper-types": [
+            "oidc-full-name-mapper",
+            "oidc-usermodel-attribute-mapper",
+            "oidc-usermodel-property-mapper",
+            "oidc-address-mapper",
+            "saml-user-attribute-mapper",
+            "saml-user-property-mapper",
+            "oidc-sha256-pairwise-sub-mapper",
+            "saml-role-list-mapper"
+          ]
+        }
+      },
+      {
+        "id": "506d7fe8-a747-4ad2-89be-6c467cd9820d",
+        "name": "Max Clients Limit",
+        "providerId": "max-clients",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {
+          "max-clients": [
+            "200"
+          ]
+        }
+      },
+      {
+        "id": "bdb7e39e-b1dd-4d19-826f-dcf0b1e8b505",
+        "name": "Allowed Protocol Mapper Types",
+        "providerId": "allowed-protocol-mappers",
+        "subType": "authenticated",
+        "subComponents": {},
+        "config": {
+          "allowed-protocol-mapper-types": [
+            "saml-user-attribute-mapper",
+            "oidc-full-name-mapper",
+            "saml-role-list-mapper",
+            "saml-user-property-mapper",
+            "oidc-usermodel-attribute-mapper",
+            "oidc-sha256-pairwise-sub-mapper",
+            "oidc-usermodel-property-mapper",
+            "oidc-address-mapper"
+          ]
+        }
+      },
+      {
+        "id": "4d541fe3-f769-4457-817d-5ca7af5705a0",
+        "name": "Full Scope Disabled",
+        "providerId": "scope",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {}
+      },
+      {
+        "id": "d1b29f0d-4c6b-4c03-95bf-e27c23ef3a2c",
+        "name": "Consent Required",
+        "providerId": "consent-required",
+        "subType": "anonymous",
+        "subComponents": {},
+        "config": {}
+      }
+    ],
+    "org.keycloak.keys.KeyProvider": [
+      {
+        "id": "5ca0e66f-c953-4088-842a-f65f1cfce006",
+        "name": "rsa-generated",
+        "providerId": "rsa-generated",
+        "subComponents": {},
+        "config": {
+          "priority": [
+            "100"
+          ]
+        }
+      },
+      {
+        "id": "004fea62-f268-4580-aafd-0b59b82740f2",
+        "name": "aes-generated",
+        "providerId": "aes-generated",
+        "subComponents": {},
+        "config": {
+          "priority": [
+            "100"
+          ]
+        }
+      },
+      {
+        "id": "b9531509-0e75-48dd-8860-7dbec1431d09",
+        "name": "hmac-generated",
+        "providerId": "hmac-generated",
+        "subComponents": {},
+        "config": {
+          "priority": [
+            "100"
+          ],
+          "algorithm": [
+            "HS256"
+          ]
+        }
+      }
+    ]
+  },
+  "internationalizationEnabled": false,
+  "supportedLocales": [],
+  "authenticationFlows": [
+    {
+      "id": "e18853d2-04f1-4abd-a397-6d204a640c0d",
+      "alias": "Handle Existing Account",
+      "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
+      "providerId": "basic-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "idp-confirm-link",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "idp-email-verification",
+          "requirement": "ALTERNATIVE",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "flowAlias": "Verify Existing Account by Re-authentication",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "1fea2aeb-619c-41fc-b8cc-d5b4529a500d",
+      "alias": "Verify Existing Account by Re-authentication",
+      "description": "Reauthentication of existing account",
+      "providerId": "basic-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "idp-username-password-form",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-otp-form",
+          "requirement": "OPTIONAL",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "382ebfeb-0fd2-4612-890b-6351a44fe1c1",
+      "alias": "browser",
+      "description": "browser based authentication",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "auth-cookie",
+          "requirement": "ALTERNATIVE",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-spnego",
+          "requirement": "DISABLED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "identity-provider-redirector",
+          "requirement": "ALTERNATIVE",
+          "priority": 25,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "flowAlias": "forms",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "34a5ff88-ffd5-4f60-9d65-99ddc6d9c41a",
+      "alias": "clients",
+      "description": "Base authentication for clients",
+      "providerId": "client-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "client-secret",
+          "requirement": "ALTERNATIVE",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "client-jwt",
+          "requirement": "ALTERNATIVE",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "client-secret-jwt",
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "client-x509",
+          "requirement": "ALTERNATIVE",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "3d8c95b8-49a5-416b-823b-5533e089e1a7",
+      "alias": "direct grant",
+      "description": "OpenID Connect Resource Owner Grant",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "direct-grant-validate-username",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "direct-grant-validate-password",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "direct-grant-validate-otp",
+          "requirement": "OPTIONAL",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "0ae57c67-dfd8-4264-b6ce-6731f71c3237",
+      "alias": "docker auth",
+      "description": "Used by Docker clients to authenticate against the IDP",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "docker-http-basic-authenticator",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "272cb097-3372-45c6-b0cc-d920a1cc6bd6",
+      "alias": "first broker login",
+      "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticatorConfig": "review profile config",
+          "authenticator": "idp-review-profile",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticatorConfig": "create unique user config",
+          "authenticator": "idp-create-user-if-unique",
+          "requirement": "ALTERNATIVE",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "requirement": "ALTERNATIVE",
+          "priority": 30,
+          "flowAlias": "Handle Existing Account",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "ab046190-a8d4-4d52-9503-35325498a15f",
+      "alias": "forms",
+      "description": "Username, password, otp and other auth forms.",
+      "providerId": "basic-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "auth-username-password-form",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-otp-form",
+          "requirement": "OPTIONAL",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "21f7be44-8819-4ba0-95cb-01a336745cd0",
+      "alias": "http challenge",
+      "description": "An authentication flow based on challenge-response HTTP Authentication Schemes",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "no-cookie-redirect",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "basic-auth",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "basic-auth-otp",
+          "requirement": "DISABLED",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "auth-spnego",
+          "requirement": "DISABLED",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "e2f99e58-44fb-4776-aaa4-ffc809294c15",
+      "alias": "registration",
+      "description": "registration flow",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "registration-page-form",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "flowAlias": "registration form",
+          "userSetupAllowed": false,
+          "autheticatorFlow": true
+        }
+      ]
+    },
+    {
+      "id": "6b1f9993-fd6b-4cf5-b218-99914899154b",
+      "alias": "registration form",
+      "description": "registration form",
+      "providerId": "form-flow",
+      "topLevel": false,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "registration-user-creation",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "registration-profile-action",
+          "requirement": "REQUIRED",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "registration-password-action",
+          "requirement": "REQUIRED",
+          "priority": 50,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "registration-recaptcha-action",
+          "requirement": "DISABLED",
+          "priority": 60,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "2ac061c4-101e-453d-9b00-ecced9adf98a",
+      "alias": "reset credentials",
+      "description": "Reset credentials for a user if they forgot their password or something",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "reset-credentials-choose-user",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "reset-credential-email",
+          "requirement": "REQUIRED",
+          "priority": 20,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "reset-password",
+          "requirement": "REQUIRED",
+          "priority": 30,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        },
+        {
+          "authenticator": "reset-otp",
+          "requirement": "OPTIONAL",
+          "priority": 40,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    },
+    {
+      "id": "d5cb10f4-333b-4b2e-85ef-533e16d25029",
+      "alias": "saml ecp",
+      "description": "SAML ECP Profile Authentication Flow",
+      "providerId": "basic-flow",
+      "topLevel": true,
+      "builtIn": true,
+      "authenticationExecutions": [
+        {
+          "authenticator": "http-basic-authenticator",
+          "requirement": "REQUIRED",
+          "priority": 10,
+          "userSetupAllowed": false,
+          "autheticatorFlow": false
+        }
+      ]
+    }
+  ],
+  "authenticatorConfig": [
+    {
+      "id": "9181f4be-676d-4639-8883-e8b3bd99dc9c",
+      "alias": "create unique user config",
+      "config": {
+        "require.password.update.after.registration": "false"
+      }
+    },
+    {
+      "id": "580e9d0d-3439-44cb-8be7-852cdf9de45a",
+      "alias": "review profile config",
+      "config": {
+        "update.profile.on.first.login": "missing"
+      }
+    }
+  ],
+  "requiredActions": [
+    {
+      "alias": "CONFIGURE_TOTP",
+      "name": "Configure OTP",
+      "providerId": "CONFIGURE_TOTP",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 10,
+      "config": {}
+    },
+    {
+      "alias": "terms_and_conditions",
+      "name": "Terms and Conditions",
+      "providerId": "terms_and_conditions",
+      "enabled": false,
+      "defaultAction": false,
+      "priority": 20,
+      "config": {}
+    },
+    {
+      "alias": "UPDATE_PASSWORD",
+      "name": "Update Password",
+      "providerId": "UPDATE_PASSWORD",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 30,
+      "config": {}
+    },
+    {
+      "alias": "UPDATE_PROFILE",
+      "name": "Update Profile",
+      "providerId": "UPDATE_PROFILE",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 40,
+      "config": {}
+    },
+    {
+      "alias": "VERIFY_EMAIL",
+      "name": "Verify Email",
+      "providerId": "VERIFY_EMAIL",
+      "enabled": true,
+      "defaultAction": false,
+      "priority": 50,
+      "config": {}
+    }
+  ],
+  "browserFlow": "browser",
+  "registrationFlow": "registration",
+  "directGrantFlow": "direct grant",
+  "resetCredentialsFlow": "reset credentials",
+  "clientAuthenticationFlow": "clients",
+  "dockerAuthenticationFlow": "docker auth",
+  "attributes": {
+    "_browser_header.xXSSProtection": "1; mode=block",
+    "_browser_header.xFrameOptions": "SAMEORIGIN",
+    "_browser_header.strictTransportSecurity": "max-age=31536000; includeSubDomains",
+    "permanentLockout": "false",
+    "quickLoginCheckMilliSeconds": "1000",
+    "_browser_header.xRobotsTag": "none",
+    "maxFailureWaitSeconds": "900",
+    "minimumQuickLoginWaitSeconds": "60",
+    "failureFactor": "30",
+    "actionTokenGeneratedByUserLifespan": "300",
+    "maxDeltaTimeSeconds": "43200",
+    "_browser_header.xContentTypeOptions": "nosniff",
+    "offlineSessionMaxLifespan": "5184000",
+    "actionTokenGeneratedByAdminLifespan": "43200",
+    "_browser_header.contentSecurityPolicyReportOnly": "",
+    "bruteForceProtected": "false",
+    "_browser_header.contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
+    "waitIncrementSeconds": "60",
+    "offlineSessionMaxLifespanEnabled": "false"
+  },
+  "keycloakVersion": "4.6.0.Final",
+  "userManagedAccessAllowed": false
+}
diff --git a/SAS/TMSS/test/oidc/tmss_keycloak_Dockerfile b/SAS/TMSS/test/oidc/tmss_keycloak_Dockerfile
new file mode 100644
index 00000000000..40af052fa68
--- /dev/null
+++ b/SAS/TMSS/test/oidc/tmss_keycloak_Dockerfile
@@ -0,0 +1,10 @@
+FROM jboss/keycloak:4.6.0.Final
+ 
+ENV KEYCLOAK_USER=admin
+ENV KEYCLOAK_PASSWORD=admin
+ENV KEYCLOAK_IMPORT=/tmp/realm-export.json
+ 
+ADD realm-export.json /tmp/realm-export.json
+ADD create-keycloak-user.sh /opt/jboss/create-keycloak-user.sh
+
+
-- 
GitLab