diff --git a/cmd/defaults.yaml b/cmd/defaults.yaml index f93142aacc..cbd2d93662 100644 --- a/cmd/defaults.yaml +++ b/cmd/defaults.yaml @@ -73,7 +73,7 @@ Database: # Postgres is used as soon as a value is set # The values describe the possible fields to set values postgres: - Host: + Host: Port: Database: MaxOpenConns: @@ -177,6 +177,9 @@ OIDC: GrantTypeRefreshToken: true RequestObjectSupported: true SigningKeyAlgorithm: RS256 + # Sets the default values for lifetime and expiration for OIDC + # This default can be overwritten in the default instance configuration and for each instance during runtime + # !!! Changing this after initial setup will have no impact without a restart !!! DefaultAccessTokenLifetime: 12h DefaultIdTokenLifetime: 12h DefaultRefreshTokenIdleExpiration: 720h #30d @@ -272,12 +275,12 @@ EncryptionKeys: UserAgentCookieKeyID: "userAgentCookieKey" SystemAPIUsers: - # add keys for authentication of the systemAPI here: - # you can specify any name for the user, but they will have to match the `issuer` and `sub` claim in the JWT: - # - superuser: - # Path: /path/to/superuser/key.pem # you can provide the key either by reference with the path - # - superuser2: - # KeyData: # or you can directly embed it as base64 encoded value +# add keys for authentication of the systemAPI here: +# you can specify any name for the user, but they will have to match the `issuer` and `sub` claim in the JWT: +# - superuser: +# Path: /path/to/superuser/key.pem # you can provide the key either by reference with the path +# - superuser2: +# KeyData: # or you can directly embed it as base64 encoded value #TODO: remove as soon as possible SystemDefaults: @@ -423,6 +426,15 @@ DefaultInstance: MaxAttempts: 0 ShouldShowLockoutFailure: true EmailTemplate: CjwhZG9jdHlwZSBodG1sPgo8aHRtbCB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94aHRtbCIgeG1sbnM6dj0idXJuOnNjaGVtYXMtbWljcm9zb2Z0LWNvbTp2bWwiIHhtbG5zOm89InVybjpzY2hlbWFzLW1pY3Jvc29mdC1jb206b2ZmaWNlOm9mZmljZSI+CjxoZWFkPgogIDx0aXRsZT4KCiAgPC90aXRsZT4KICA8IS0tW2lmICFtc29dPjwhLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iWC1VQS1Db21wYXRpYmxlIiBjb250ZW50PSJJRT1lZGdlIj4KICA8IS0tPCFbZW5kaWZdLS0+CiAgPG1ldGEgaHR0cC1lcXVpdj0iQ29udGVudC1UeXBlIiBjb250ZW50PSJ0ZXh0L2h0bWw7IGNoYXJzZXQ9VVRGLTgiPgogIDxtZXRhIG5hbWU9InZpZXdwb3J0IiBjb250ZW50PSJ3aWR0aD1kZXZpY2Utd2lkdGgsIGluaXRpYWwtc2NhbGU9MSI+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KICAgICNvdXRsb29rIGEgeyBwYWRkaW5nOjA7IH0KICAgIGJvZHkgeyBtYXJnaW46MDtwYWRkaW5nOjA7LXdlYmtpdC10ZXh0LXNpemUtYWRqdXN0OjEwMCU7LW1zLXRleHQtc2l6ZS1hZGp1c3Q6MTAwJTsgfQogICAgdGFibGUsIHRkIHsgYm9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlO21zby10YWJsZS1sc3BhY2U6MHB0O21zby10YWJsZS1yc3BhY2U6MHB0OyB9CiAgICBpbWcgeyBib3JkZXI6MDtoZWlnaHQ6YXV0bztsaW5lLWhlaWdodDoxMDAlOyBvdXRsaW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uOm5vbmU7LW1zLWludGVycG9sYXRpb24tbW9kZTpiaWN1YmljOyB9CiAgICBwIHsgZGlzcGxheTpibG9jazttYXJnaW46MTNweCAwOyB9CiAgPC9zdHlsZT4KICA8IS0tW2lmIG1zb10+CiAgPHhtbD4KICAgIDxvOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgICAgIDxvOkFsbG93UE5HLz4KICAgICAgPG86UGl4ZWxzUGVySW5jaD45NjwvbzpQaXhlbHNQZXJJbmNoPgogICAgPC9vOk9mZmljZURvY3VtZW50U2V0dGluZ3M+CiAgPC94bWw+CiAgPCFbZW5kaWZdLS0+CiAgPCEtLVtpZiBsdGUgbXNvIDExXT4KICA8c3R5bGUgdHlwZT0idGV4dC9jc3MiPgogICAgLm1qLW91dGxvb2stZ3JvdXAtZml4IHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyB9CiAgPC9zdHlsZT4KICA8IVtlbmRpZl0tLT4KCgogIDxzdHlsZSB0eXBlPSJ0ZXh0L2NzcyI+CiAgICBAbWVkaWEgb25seSBzY3JlZW4gYW5kIChtaW4td2lkdGg6NDgwcHgpIHsKICAgICAgLm1qLWNvbHVtbi1wZXItMTAwIHsgd2lkdGg6MTAwJSAhaW1wb3J0YW50OyBtYXgtd2lkdGg6IDEwMCU7IH0KICAgICAgLm1qLWNvbHVtbi1wZXItNjAgeyB3aWR0aDo2MCUgIWltcG9ydGFudDsgbWF4LXdpZHRoOiA2MCU7IH0KICAgIH0KICA8L3N0eWxlPgoKCiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4KCgoKICAgIEBtZWRpYSBvbmx5IHNjcmVlbiBhbmQgKG1heC13aWR0aDo0ODBweCkgewogICAgICB0YWJsZS5tai1mdWxsLXdpZHRoLW1vYmlsZSB7IHdpZHRoOiAxMDAlICFpbXBvcnRhbnQ7IH0KICAgICAgdGQubWotZnVsbC13aWR0aC1tb2JpbGUgeyB3aWR0aDogYXV0byAhaW1wb3J0YW50OyB9CiAgICB9CgogIDwvc3R5bGU+CiAgPHN0eWxlIHR5cGU9InRleHQvY3NzIj4uc2hhZG93IGEgewogICAgYm94LXNoYWRvdzogMHB4IDNweCAxcHggLTJweCByZ2JhKDAsIDAsIDAsIDAuMiksIDBweCAycHggMnB4IDBweCByZ2JhKDAsIDAsIDAsIDAuMTQpLCAwcHggMXB4IDVweCAwcHggcmdiYSgwLCAwLCAwLCAwLjEyKTsKICB9PC9zdHlsZT4KCiAge3tpZiAuRm9udFVSTH19CiAgPHN0eWxlPgogICAgQGZvbnQtZmFjZSB7CiAgICAgIGZvbnQtZmFtaWx5OiAne3suRm9udEZhY2VGYW1pbHl9fSc7CiAgICAgIGZvbnQtc3R5bGU6IG5vcm1hbDsKICAgICAgZm9udC1kaXNwbGF5OiBzd2FwOwogICAgICBzcmM6IHVybCh7ey5Gb250VVJMfX0pOwogICAgfQogIDwvc3R5bGU+CiAge3tlbmR9fQoKPC9oZWFkPgo8Ym9keSBzdHlsZT0id29yZC1zcGFjaW5nOm5vcm1hbDsiPgoKCjxkaXYKICAgICAgICBzdHlsZT0iIgo+CgogIDx0YWJsZQogICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJhY2tncm91bmQ6e3suQmFja2dyb3VuZENvbG9yfX07YmFja2dyb3VuZC1jb2xvcjp7ey5CYWNrZ3JvdW5kQ29sb3J9fTt3aWR0aDoxMDAlO2JvcmRlci1yYWRpdXM6MTZweDsiCiAgPgogICAgPHRib2R5PgogICAgPHRyPgogICAgICA8dGQ+CgoKICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIGNsYXNzPSIiIHN0eWxlPSJ3aWR0aDo4MDBweDsiIHdpZHRoPSI4MDAiID48dHI+PHRkIHN0eWxlPSJsaW5lLWhlaWdodDowcHg7Zm9udC1zaXplOjBweDttc28tbGluZS1oZWlnaHQtcnVsZTpleGFjdGx5OyI+PCFbZW5kaWZdLS0+CgoKICAgICAgICA8ZGl2ICBzdHlsZT0ibWFyZ2luOjBweCBhdXRvO2JvcmRlci1yYWRpdXM6MTZweDttYXgtd2lkdGg6ODAwcHg7Ij4KCiAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7Ym9yZGVyLXJhZGl1czoxNnB4OyIKICAgICAgICAgID4KICAgICAgICAgICAgPHRib2R5PgogICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iZGlyZWN0aW9uOmx0cjtmb250LXNpemU6MHB4O3BhZGRpbmc6MjBweCAwO3BhZGRpbmctbGVmdDowO3RleHQtYWxpZ246Y2VudGVyOyIKICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgcm9sZT0icHJlc2VudGF0aW9uIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBjbGFzcz0iIiB3aWR0aD0iODAwcHgiID48IVtlbmRpZl0tLT4KCiAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+CgoKICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9IiIgc3R5bGU9IndpZHRoOjgwMHB4OyIgd2lkdGg9IjgwMCIgPjx0cj48dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgICAgPGRpdiAgc3R5bGU9Im1hcmdpbjowcHggYXV0bzttYXgtd2lkdGg6ODAwcHg7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJ3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImRpcmVjdGlvbjpsdHI7Zm9udC1zaXplOjBweDtwYWRkaW5nOjA7dGV4dC1hbGlnbjpjZW50ZXI7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgcm9sZT0icHJlc2VudGF0aW9uIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBjbGFzcz0iIiBzdHlsZT0id2lkdGg6ODAwcHg7IiA+PCFbZW5kaWZdLS0+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgY2xhc3M9Im1qLWNvbHVtbi1wZXItMTAwIG1qLW91dGxvb2stZ3JvdXAtZml4IiBzdHlsZT0iZm9udC1zaXplOjA7bGluZS1oZWlnaHQ6MDt0ZXh0LWFsaWduOmxlZnQ7ZGlzcGxheTppbmxpbmUtYmxvY2s7d2lkdGg6MTAwJTtkaXJlY3Rpb246bHRyOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiA+PHRyPjx0ZCBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjgwMHB4OyIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8ZGl2CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBjbGFzcz0ibWotY29sdW1uLXBlci0xMDAgbWotb3V0bG9vay1ncm91cC1maXgiIHN0eWxlPSJmb250LXNpemU6MHB4O3RleHQtYWxpZ246bGVmdDtkaXJlY3Rpb246bHRyO2Rpc3BsYXk6aW5saW5lLWJsb2NrO3ZlcnRpY2FsLWFsaWduOnRvcDt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHdpZHRoPSIxMDAlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQgIHN0eWxlPSJ2ZXJ0aWNhbC1hbGlnbjp0b3A7cGFkZGluZzowOyI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7e2lmIC5Mb2dvVVJMfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iIiB3aWR0aD0iMTAwJSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRib2R5PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzo1MHB4IDAgMzBweCAwO3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYm9yZGVyLWNvbGxhcHNlOmNvbGxhcHNlO2JvcmRlci1zcGFjaW5nOjBweDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCAgc3R5bGU9IndpZHRoOjE4MHB4OyI+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGltZwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWlnaHQ9ImF1dG8iIHNyYz0ie3suTG9nb1VSTH19IiBzdHlsZT0iYm9yZGVyOjA7Ym9yZGVyLXJhZGl1czo4cHg7ZGlzcGxheTpibG9jaztvdXRsaW5lOm5vbmU7dGV4dC1kZWNvcmF0aW9uOm5vbmU7aGVpZ2h0OmF1dG87d2lkdGg6MTAwJTtmb250LXNpemU6MTNweDsiIHdpZHRoPSIxODAiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAvPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3tlbmR9fQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PC90ZD48L3RyPjwvdGFibGU+PCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PC90ZD48L3RyPjwvdGFibGU+PCFbZW5kaWZdLS0+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICA8L2Rpdj4KCgogICAgICAgICAgICAgICAgICAgICAgPCEtLVtpZiBtc28gfCBJRV0+PC90ZD48L3RyPjwvdGFibGU+PCFbZW5kaWZdLS0+CgoKICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PHRyPjx0ZCBjbGFzcz0iIiB3aWR0aD0iODAwcHgiID48IVtlbmRpZl0tLT4KCiAgICAgICAgICAgICAgICA8dGFibGUKICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9IndpZHRoOjEwMCU7IgogICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICA8dGQ+CgoKICAgICAgICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjx0YWJsZSBhbGlnbj0iY2VudGVyIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgY2xhc3M9IiIgc3R5bGU9IndpZHRoOjgwMHB4OyIgd2lkdGg9IjgwMCIgPjx0cj48dGQgc3R5bGU9ImxpbmUtaGVpZ2h0OjBweDtmb250LXNpemU6MHB4O21zby1saW5lLWhlaWdodC1ydWxlOmV4YWN0bHk7Ij48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgICAgPGRpdiAgc3R5bGU9Im1hcmdpbjowcHggYXV0bzttYXgtd2lkdGg6ODAwcHg7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgIDx0YWJsZQogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIGJvcmRlcj0iMCIgY2VsbHBhZGRpbmc9IjAiIGNlbGxzcGFjaW5nPSIwIiByb2xlPSJwcmVzZW50YXRpb24iIHN0eWxlPSJ3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgIDx0Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImRpcmVjdGlvbjpsdHI7Zm9udC1zaXplOjBweDtwYWRkaW5nOjA7dGV4dC1hbGlnbjpjZW50ZXI7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgcm9sZT0icHJlc2VudGF0aW9uIiBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCI+PHRyPjx0ZCBjbGFzcz0iIiBzdHlsZT0idmVydGljYWwtYWxpZ246dG9wO3dpZHRoOjQ4MHB4OyIgPjwhW2VuZGlmXS0tPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGNsYXNzPSJtai1jb2x1bW4tcGVyLTYwIG1qLW91dGxvb2stZ3JvdXAtZml4IiBzdHlsZT0iZm9udC1zaXplOjBweDt0ZXh0LWFsaWduOmxlZnQ7ZGlyZWN0aW9uOmx0cjtkaXNwbGF5OmlubGluZS1ibG9jazt2ZXJ0aWNhbC1hbGlnbjp0b3A7d2lkdGg6MTAwJTsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iMTAwJSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZCAgc3R5bGU9InZlcnRpY2FsLWFsaWduOnRvcDtwYWRkaW5nOjA7Ij4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iIiB3aWR0aD0iMTAwJSIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGJvZHk+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dGQKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBhbGlnbj0iY2VudGVyIiBzdHlsZT0iZm9udC1zaXplOjBweDtwYWRkaW5nOjEwcHggMjVweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxkaXYKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0eWxlPSJmb250LWZhbWlseTp7ey5Gb250RmFtaWx5fX07Zm9udC1zaXplOjI0cHg7Zm9udC13ZWlnaHQ6NTAwO2xpbmUtaGVpZ2h0OjE7dGV4dC1hbGlnbjpjZW50ZXI7Y29sb3I6e3suRm9udENvbG9yfX07IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID57ey5HcmVldGluZ319PC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MTBweCAyNXB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MTZweDtmb250LXdlaWdodDpsaWdodDtsaW5lLWhlaWdodDoxLjU7dGV4dC1hbGlnbjpjZW50ZXI7Y29sb3I6e3suRm9udENvbG9yfX07IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID57ey5UZXh0fX08L2Rpdj4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8dHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0ZAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGFsaWduPSJjZW50ZXIiIHZlcnRpY2FsLWFsaWduPSJtaWRkbGUiIGNsYXNzPSJzaGFkb3ciIHN0eWxlPSJmb250LXNpemU6MHB4O3BhZGRpbmc6MTBweCAyNXB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRhYmxlCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBib3JkZXI9IjAiIGNlbGxwYWRkaW5nPSIwIiBjZWxsc3BhY2luZz0iMCIgcm9sZT0icHJlc2VudGF0aW9uIiBzdHlsZT0iYm9yZGVyLWNvbGxhcHNlOnNlcGFyYXRlO2xpbmUtaGVpZ2h0OjEwMCU7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgYmdjb2xvcj0ie3suUHJpbWFyeUNvbG9yfX0iIHJvbGU9InByZXNlbnRhdGlvbiIgc3R5bGU9ImJvcmRlcjpub25lO2JvcmRlci1yYWRpdXM6NnB4O2N1cnNvcjphdXRvO21zby1wYWRkaW5nLWFsdDoxMHB4IDI1cHg7YmFja2dyb3VuZDp7ey5QcmltYXJ5Q29sb3J9fTsiIHZhbGlnbj0ibWlkZGxlIgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGEKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhyZWY9Int7LlVSTH19IiByZWw9Im5vb3BlbmVyIG5vcmVmZXJyZXIgbm90cmFjayIgc3R5bGU9ImRpc3BsYXk6aW5saW5lLWJsb2NrO2JhY2tncm91bmQ6e3suUHJpbWFyeUNvbG9yfX07Y29sb3I6I2ZmZmZmZjtmb250LWZhbWlseTp7ey5Gb250RmFtaWx5fX07Zm9udC1zaXplOjE0cHg7Zm9udC13ZWlnaHQ6NTAwO2xpbmUtaGVpZ2h0OjEyMCU7bWFyZ2luOjA7dGV4dC1kZWNvcmF0aW9uOm5vbmU7dGV4dC10cmFuc2Zvcm06bm9uZTtwYWRkaW5nOjEwcHggMjVweDttc28tcGFkZGluZy1hbHQ6MHB4O2JvcmRlci1yYWRpdXM6NnB4OyIgdGFyZ2V0PSJfYmxhbmsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAge3suQnV0dG9uVGV4dH19CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9hPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90ZD4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICB7e2lmIC5JbmNsdWRlRm9vdGVyfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxMHB4IDI1cHg7cGFkZGluZy10b3A6MjBweDtwYWRkaW5nLXJpZ2h0OjIwcHg7cGFkZGluZy1ib3R0b206MjBweDtwYWRkaW5nLWxlZnQ6MjBweDt3b3JkLWJyZWFrOmJyZWFrLXdvcmQ7IgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDxwCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHlsZT0iYm9yZGVyLXRvcDpzb2xpZCAycHggI2RiZGJkYjtmb250LXNpemU6MXB4O21hcmdpbjowcHggYXV0bzt3aWR0aDoxMDAlOyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9wPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48dGFibGUgYWxpZ249ImNlbnRlciIgYm9yZGVyPSIwIiBjZWxscGFkZGluZz0iMCIgY2VsbHNwYWNpbmc9IjAiIHN0eWxlPSJib3JkZXItdG9wOnNvbGlkIDJweCAjZGJkYmRiO2ZvbnQtc2l6ZToxcHg7bWFyZ2luOjBweCBhdXRvO3dpZHRoOjQ0MHB4OyIgcm9sZT0icHJlc2VudGF0aW9uIiB3aWR0aD0iNDQwcHgiID48dHI+PHRkIHN0eWxlPSJoZWlnaHQ6MDtsaW5lLWhlaWdodDowOyI+ICZuYnNwOwogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDx0cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPHRkCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgYWxpZ249ImNlbnRlciIgc3R5bGU9ImZvbnQtc2l6ZTowcHg7cGFkZGluZzoxNnB4O3dvcmQtYnJlYWs6YnJlYWstd29yZDsiCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgID4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPGRpdgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3R5bGU9ImZvbnQtZmFtaWx5Ont7LkZvbnRGYW1pbHl9fTtmb250LXNpemU6MTNweDtsaW5lLWhlaWdodDoxO3RleHQtYWxpZ246Y2VudGVyO2NvbG9yOnt7LkZvbnRDb2xvcn19OyIKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA+e3suRm9vdGVyVGV4dH19PC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RkPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHt7ZW5kfX0KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90YWJsZT4KCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RyPgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC90Ym9keT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgPC9kaXY+CgogICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KICAgICAgICAgICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgICAgICAgICAgPC90cj4KICAgICAgICAgICAgICAgICAgICAgICAgICA8L3Rib2R5PgogICAgICAgICAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgICAgICAgIDwvZGl2PgoKCiAgICAgICAgICAgICAgICAgICAgICA8IS0tW2lmIG1zbyB8IElFXT48L3RkPjwvdHI+PC90YWJsZT48IVtlbmRpZl0tLT4KCgogICAgICAgICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICAgICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgogICAgICAgICAgICAgIDwvdGQ+CiAgICAgICAgICAgIDwvdHI+CiAgICAgICAgICAgIDwvdGJvZHk+CiAgICAgICAgICA8L3RhYmxlPgoKICAgICAgICA8L2Rpdj4KCgogICAgICAgIDwhLS1baWYgbXNvIHwgSUVdPjwvdGQ+PC90cj48L3RhYmxlPjwhW2VuZGlmXS0tPgoKCiAgICAgIDwvdGQ+CiAgICA8L3RyPgogICAgPC90Ym9keT4KICA8L3RhYmxlPgoKPC9kaXY+Cgo8L2JvZHk+CjwvaHRtbD4K + # Sets the default values for lifetime and expiration for OIDC in each newly created instance + # This default can be overwritten for each instance during runtime + # Overwrites the system defaults + # If defined but not all durations are set it will result in an error + OIDCSettings: + AccessTokenLifetime: 12h + IdTokenLifetime: 12h + RefreshTokenIdleExpiration: 720h #30d + RefreshTokenExpiration: 2160h #90d # this configuration sets the default email configuration SMTPConfiguration: # configuration of the host diff --git a/console/angular.json b/console/angular.json index 776703f84e..346d797c25 100644 --- a/console/angular.json +++ b/console/angular.json @@ -32,6 +32,7 @@ "grpc-web", "@angular/common/locales/de", "codemirror/mode/javascript/javascript", + "codemirror/mode/xml/xml", "src/app/proto/generated/zitadel/admin_pb", "src/app/proto/generated/zitadel/org_pb", "src/app/proto/generated/zitadel/management_pb", diff --git a/console/src/app/modules/policies/oidc-configuration/oidc-configuration.component.html b/console/src/app/modules/policies/oidc-configuration/oidc-configuration.component.html index 29bea3b0ac..728cd0ca2c 100644 --- a/console/src/app/modules/policies/oidc-configuration/oidc-configuration.component.html +++ b/console/src/app/modules/policies/oidc-configuration/oidc-configuration.component.html @@ -46,6 +46,7 @@ color="primary" type="submit" mat-raised-button + data-e2e="save-button" > {{ 'ACTIONS.SAVE' | translate }} diff --git a/console/src/app/modules/policies/oidc-configuration/oidc-configuration.component.ts b/console/src/app/modules/policies/oidc-configuration/oidc-configuration.component.ts index a930d4c80b..974133bb6f 100644 --- a/console/src/app/modules/policies/oidc-configuration/oidc-configuration.component.ts +++ b/console/src/app/modules/policies/oidc-configuration/oidc-configuration.component.ts @@ -2,7 +2,12 @@ import { Component, OnInit } from '@angular/core'; import { AbstractControl, UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms'; import { Duration } from 'google-protobuf/google/protobuf/duration_pb'; import { take } from 'rxjs'; -import { SetDefaultLanguageResponse, UpdateOIDCSettingsRequest } from 'src/app/proto/generated/zitadel/admin_pb'; +import { + AddOIDCSettingsRequest, + AddOIDCSettingsResponse, + UpdateOIDCSettingsRequest, + UpdateOIDCSettingsResponse, +} from 'src/app/proto/generated/zitadel/admin_pb'; import { OIDCSettings } from 'src/app/proto/generated/zitadel/settings_pb'; import { AdminService } from 'src/app/services/admin.service'; import { GrpcAuthService } from 'src/app/services/grpc-auth.service'; @@ -15,6 +20,7 @@ import { ToastService } from 'src/app/services/toast.service'; }) export class OIDCConfigurationComponent implements OnInit { public oidcSettings!: OIDCSettings.AsObject; + private settingsSet: boolean = false; public loading: boolean = false; public form!: UntypedFormGroup; @@ -25,10 +31,10 @@ export class OIDCConfigurationComponent implements OnInit { private authService: GrpcAuthService, ) { this.form = this.fb.group({ - accessTokenLifetime: [{ disabled: true, value: 12 }, [Validators.required]], - idTokenLifetime: [{ disabled: true, value: 12 }, [Validators.required]], - refreshTokenExpiration: [{ disabled: true, value: 30 }, [Validators.required]], - refreshTokenIdleExpiration: [{ disabled: true, value: 90 }, [Validators.required]], + accessTokenLifetime: [{ disabled: true }, [Validators.required]], + idTokenLifetime: [{ disabled: true }, [Validators.required]], + refreshTokenExpiration: [{ disabled: true }, [Validators.required]], + refreshTokenIdleExpiration: [{ disabled: true }, [Validators.required]], }); } @@ -50,26 +56,27 @@ export class OIDCConfigurationComponent implements OnInit { .then((oidcConfiguration) => { if (oidcConfiguration.settings) { this.oidcSettings = oidcConfiguration.settings; + this.settingsSet = true; this.accessTokenLifetime?.setValue( oidcConfiguration.settings.accessTokenLifetime?.seconds ? oidcConfiguration.settings.accessTokenLifetime?.seconds / 60 / 60 - : 12, + : 0, ); this.idTokenLifetime?.setValue( oidcConfiguration.settings.idTokenLifetime?.seconds ? oidcConfiguration.settings.idTokenLifetime?.seconds / 60 / 60 - : 12, + : 0, ); this.refreshTokenExpiration?.setValue( oidcConfiguration.settings.refreshTokenExpiration?.seconds ? oidcConfiguration.settings.refreshTokenExpiration?.seconds / 60 / 60 / 24 - : 30, + : 0, ); this.refreshTokenIdleExpiration?.setValue( oidcConfiguration.settings.refreshTokenIdleExpiration?.seconds ? oidcConfiguration.settings.refreshTokenIdleExpiration?.seconds / 60 / 60 / 24 - : 90, + : 0, ); } }) @@ -78,31 +85,58 @@ export class OIDCConfigurationComponent implements OnInit { }); } - private updateData(): Promise { + private updateData(): Promise { const req = new UpdateOIDCSettingsRequest(); - const accessToken = new Duration().setSeconds((this.accessTokenLifetime?.value ?? 12) * 60 * 60); + const accessToken = new Duration().setSeconds((this.accessTokenLifetime?.value ?? 0) * 60 * 60); req.setAccessTokenLifetime(accessToken); - const idToken = new Duration().setSeconds((this.idTokenLifetime?.value ?? 12) * 60 * 60); + const idToken = new Duration().setSeconds((this.idTokenLifetime?.value ?? 0) * 60 * 60); req.setIdTokenLifetime(idToken); - const refreshToken = new Duration().setSeconds((this.refreshTokenExpiration?.value ?? 30) * 60 * 60 * 24); + const refreshToken = new Duration().setSeconds((this.refreshTokenExpiration?.value ?? 0) * 60 * 60 * 24); req.setRefreshTokenExpiration(refreshToken); - const refreshIdleToken = new Duration().setSeconds((this.refreshTokenIdleExpiration?.value ?? 90) * 60 * 60 * 24); + const refreshIdleToken = new Duration().setSeconds((this.refreshTokenIdleExpiration?.value ?? 0) * 60 * 60 * 24); req.setRefreshTokenIdleExpiration(refreshIdleToken); return (this.service as AdminService).updateOIDCSettings(req); } + private addData(): Promise { + const req = new AddOIDCSettingsRequest(); + + const accessToken = new Duration().setSeconds((this.accessTokenLifetime?.value ?? 0) * 60 * 60); + req.setAccessTokenLifetime(accessToken); + + const idToken = new Duration().setSeconds((this.idTokenLifetime?.value ?? 0) * 60 * 60); + req.setIdTokenLifetime(idToken); + + const refreshToken = new Duration().setSeconds((this.refreshTokenExpiration?.value ?? 0) * 60 * 60 * 24); + req.setRefreshTokenExpiration(refreshToken); + + const refreshIdleToken = new Duration().setSeconds((this.refreshTokenIdleExpiration?.value ?? 0) * 60 * 60 * 24); + req.setRefreshTokenIdleExpiration(refreshIdleToken); + + return (this.service as AdminService).addOIDCSettings(req); + } + public savePolicy(): void { - const prom = this.updateData(); - if (prom) { - prom + if (this.settingsSet) { + this.updateData() + .then(() => { + this.toast.showInfo('SETTING.SMTP.SAVED', true); + setTimeout(() => { + this.fetchData(); + }, 2000); + }) + .catch((error) => { + this.toast.showError(error); + }); + } else { + this.addData() .then(() => { this.toast.showInfo('SETTING.SMTP.SAVED', true); - this.loading = true; setTimeout(() => { this.fetchData(); }, 2000); diff --git a/console/src/app/services/admin.service.ts b/console/src/app/services/admin.service.ts index 4cd72b0352..fb1c666790 100644 --- a/console/src/app/services/admin.service.ts +++ b/console/src/app/services/admin.service.ts @@ -180,6 +180,8 @@ import { UpdateLockoutPolicyResponse, UpdateLoginPolicyRequest, UpdateLoginPolicyResponse, + AddOIDCSettingsRequest, + AddOIDCSettingsResponse, UpdateOIDCSettingsRequest, UpdateOIDCSettingsResponse, UpdatePasswordAgePolicyRequest, @@ -623,6 +625,10 @@ export class AdminService { return this.grpcService.admin.updateOIDCSettings(req, null).then((resp) => resp.toObject()); } + public addOIDCSettings(req: AddOIDCSettingsRequest): Promise { + return this.grpcService.admin.addOIDCSettings(req, null).then((resp) => resp.toObject()); + } + /* LOG and FILE Notifications */ public getLogNotificationProvider(): Promise { diff --git a/docs/docs/apis/proto/admin.md b/docs/docs/apis/proto/admin.md index 50d6b506cf..066c39f8b5 100644 --- a/docs/docs/apis/proto/admin.md +++ b/docs/docs/apis/proto/admin.md @@ -284,6 +284,18 @@ Get OIDC settings (e.g token lifetimes, etc.) GET: /settings/oidc +### AddOIDCSettings + +> **rpc** AddOIDCSettings([AddOIDCSettingsRequest](#addoidcsettingsrequest)) +[AddOIDCSettingsResponse](#addoidcsettingsresponse) + +Add oidc settings (e.g token lifetimes, etc) + + + + POST: /settings/oidc + + ### UpdateOIDCSettings > **rpc** UpdateOIDCSettings([UpdateOIDCSettingsRequest](#updateoidcsettingsrequest)) @@ -1739,6 +1751,31 @@ This is an empty request +### AddOIDCSettingsRequest + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| access_token_lifetime | google.protobuf.Duration | - | | +| id_token_lifetime | google.protobuf.Duration | - | | +| refresh_token_idle_expiration | google.protobuf.Duration | - | | +| refresh_token_expiration | google.protobuf.Duration | - | | + + + + +### AddOIDCSettingsResponse + + + +| Field | Type | Description | Validation | +| ----- | ---- | ----------- | ----------- | +| details | zitadel.v1.ObjectDetails | - | | + + + + ### AddSMSProviderTwilioRequest diff --git a/e2e/cypress/e2e/settings/oidc-settings.cy.ts b/e2e/cypress/e2e/settings/oidc-settings.cy.ts new file mode 100644 index 0000000000..274ff08528 --- /dev/null +++ b/e2e/cypress/e2e/settings/oidc-settings.cy.ts @@ -0,0 +1,38 @@ +import { apiAuth } from '../../support/api/apiauth'; +import { ensureOIDCSettingsSet } from '../../support/api/oidc-settings'; + +describe('oidc settings', () => { + const oidcSettingsPath = `/settings?id=oidc`; + const accessTokenPrecondition = 1; + const idTokenPrecondition = 2; + const refreshTokenExpirationPrecondition = 7; + const refreshTokenIdleExpirationPrecondition = 2; + + before(`ensure they are set`, () => { + apiAuth().then((apiCallProperties) => { + ensureOIDCSettingsSet( + apiCallProperties, + accessTokenPrecondition, + idTokenPrecondition, + refreshTokenExpirationPrecondition, + refreshTokenIdleExpirationPrecondition, + ); + cy.visit(oidcSettingsPath); + }); + }); + + it(`should update oidc settings`, () => { + cy.get('[formcontrolname="accessTokenLifetime"]').should('value', accessTokenPrecondition).clear().type('2'); + cy.get('[formcontrolname="idTokenLifetime"]').should('value', idTokenPrecondition).clear().type('24'); + cy.get('[formcontrolname="refreshTokenExpiration"]') + .should('value', refreshTokenExpirationPrecondition) + .clear() + .type('30'); + cy.get('[formcontrolname="refreshTokenIdleExpiration"]') + .should('value', refreshTokenIdleExpirationPrecondition) + .clear() + .type('7'); + cy.get('[data-e2e="save-button"]').click(); + cy.get('.data-e2e-success'); + }); +}); diff --git a/e2e/cypress/support/api/apiauth.ts b/e2e/cypress/support/api/apiauth.ts index 3256fbc217..4f78586e20 100644 --- a/e2e/cypress/support/api/apiauth.ts +++ b/e2e/cypress/support/api/apiauth.ts @@ -3,6 +3,7 @@ import { login, User } from 'support/login/users'; export interface apiCallProperties { authHeader: string; mgntBaseURL: string; + adminBaseURL: string; } export function apiAuth(): Cypress.Chainable { @@ -10,6 +11,7 @@ export function apiAuth(): Cypress.Chainable { return { authHeader: `Bearer ${token}`, mgntBaseURL: `${Cypress.env('BACKEND_URL')}/management/v1/`, + adminBaseURL: `${Cypress.env('BACKEND_URL')}/admin/v1/`, }; }); } diff --git a/e2e/cypress/support/api/ensure.ts b/e2e/cypress/support/api/ensure.ts index a41fae0aa2..5e5cffb643 100644 --- a/e2e/cypress/support/api/ensure.ts +++ b/e2e/cypress/support/api/ensure.ts @@ -40,6 +40,46 @@ export function ensureSomethingExists( }); } +export function ensureSomethingIsSet( + api: apiCallProperties, + path: string, + find: (entity: any) => SearchResult, + createPath: string, + body: any, +): Cypress.Chainable { + return getSomething(api, path, find) + .then((sRes) => { + if (sRes.entity) { + return cy.wrap({ + id: sRes.entity.id, + initialSequence: 0, + }); + } + return cy + .request({ + method: 'PUT', + url: createPath, + headers: { + Authorization: api.authHeader, + }, + body: body, + failOnStatusCode: false, + followRedirect: false, + }) + .then((cRes) => { + expect(cRes.status).to.equal(200); + return { + id: cRes.body.id, + initialSequence: sRes.sequence, + }; + }); + }) + .then((data) => { + awaitDesiredById(90, (entity) => !!entity, data.initialSequence, api, path, find); + return cy.wrap(data.id); + }); +} + export function ensureSomethingDoesntExist( api: apiCallProperties, searchPath: string, @@ -97,6 +137,24 @@ function searchSomething( }); } +function getSomething( + api: apiCallProperties, + searchPath: string, + find: (entity: any) => SearchResult, +): Cypress.Chainable { + return cy + .request({ + method: 'GET', + url: searchPath, + headers: { + Authorization: api.authHeader, + }, + }) + .then((res) => { + return find(res.body); + }); +} + function awaitDesired( trials: number, expectEntity: (entity: any) => boolean, @@ -116,3 +174,23 @@ function awaitDesired( } }); } + +function awaitDesiredById( + trials: number, + expectEntity: (entity: any) => boolean, + initialSequence: number, + api: apiCallProperties, + path: string, + find: (entity: any) => SearchResult, +) { + getSomething(api, path, find).then((resp) => { + const foundExpectedEntity = expectEntity(resp.entity); + const foundExpectedSequence = resp.sequence > initialSequence; + + if (!foundExpectedEntity || !foundExpectedSequence) { + expect(trials, `trying ${trials} more times`).to.be.greaterThan(0); + cy.wait(1000); + awaitDesiredById(trials - 1, expectEntity, initialSequence, api, path, find); + } + }); +} diff --git a/e2e/cypress/support/api/oidc-settings.ts b/e2e/cypress/support/api/oidc-settings.ts new file mode 100644 index 0000000000..409ab1ac7f --- /dev/null +++ b/e2e/cypress/support/api/oidc-settings.ts @@ -0,0 +1,44 @@ +import { apiCallProperties } from './apiauth'; +import { ensureSomethingIsSet } from './ensure'; + +export function ensureOIDCSettingsSet( + api: apiCallProperties, + accessTokenLifetime, + idTokenLifetime, + refreshTokenExpiration, + refreshTokenIdleExpiration: number, +): Cypress.Chainable { + return ensureSomethingIsSet( + api, + `${api.adminBaseURL}settings/oidc`, + (settings: any) => { + let entity = null; + if ( + settings.settings?.accessTokenLifetime === hoursToDuration(accessTokenLifetime) && + settings.settings?.idTokenLifetime === hoursToDuration(idTokenLifetime) && + settings.settings?.refreshTokenExpiration === daysToDuration(refreshTokenExpiration) && + settings.settings?.refreshTokenIdleExpiration === daysToDuration(refreshTokenIdleExpiration) + ) { + entity = settings.settings; + } + return { + entity: entity, + sequence: settings.settings?.details?.sequence, + }; + }, + `${api.adminBaseURL}settings/oidc`, + { + accessTokenLifetime: hoursToDuration(accessTokenLifetime), + idTokenLifetime: hoursToDuration(idTokenLifetime), + refreshTokenExpiration: daysToDuration(refreshTokenExpiration), + refreshTokenIdleExpiration: daysToDuration(refreshTokenIdleExpiration), + }, + ); +} + +function hoursToDuration(hours: number): string { + return (hours * 3600).toString() + 's'; +} +function daysToDuration(days: number): string { + return hoursToDuration(24 * days); +} diff --git a/internal/api/grpc/admin/oidc_settings.go b/internal/api/grpc/admin/oidc_settings.go index 5cf263f558..c1a611ae81 100644 --- a/internal/api/grpc/admin/oidc_settings.go +++ b/internal/api/grpc/admin/oidc_settings.go @@ -18,6 +18,16 @@ func (s *Server) GetOIDCSettings(ctx context.Context, _ *admin_pb.GetOIDCSetting }, nil } +func (s *Server) AddOIDCSettings(ctx context.Context, req *admin_pb.AddOIDCSettingsRequest) (*admin_pb.AddOIDCSettingsResponse, error) { + result, err := s.command.AddOIDCSettings(ctx, AddOIDCConfigToConfig(req)) + if err != nil { + return nil, err + } + return &admin_pb.AddOIDCSettingsResponse{ + Details: object.DomainToChangeDetailsPb(result), + }, nil +} + func (s *Server) UpdateOIDCSettings(ctx context.Context, req *admin_pb.UpdateOIDCSettingsRequest) (*admin_pb.UpdateOIDCSettingsResponse, error) { result, err := s.command.ChangeOIDCSettings(ctx, UpdateOIDCConfigToConfig(req)) if err != nil { diff --git a/internal/api/grpc/admin/oidc_settings_converter.go b/internal/api/grpc/admin/oidc_settings_converter.go index fef50907a6..c7da617636 100644 --- a/internal/api/grpc/admin/oidc_settings_converter.go +++ b/internal/api/grpc/admin/oidc_settings_converter.go @@ -1,12 +1,13 @@ package admin import ( + "google.golang.org/protobuf/types/known/durationpb" + obj_grpc "github.com/zitadel/zitadel/internal/api/grpc/object" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/query" admin_pb "github.com/zitadel/zitadel/pkg/grpc/admin" settings_pb "github.com/zitadel/zitadel/pkg/grpc/settings" - "google.golang.org/protobuf/types/known/durationpb" ) func OIDCSettingsToPb(config *query.OIDCSettings) *settings_pb.OIDCSettings { @@ -19,6 +20,15 @@ func OIDCSettingsToPb(config *query.OIDCSettings) *settings_pb.OIDCSettings { } } +func AddOIDCConfigToConfig(req *admin_pb.AddOIDCSettingsRequest) *domain.OIDCSettings { + return &domain.OIDCSettings{ + AccessTokenLifetime: req.AccessTokenLifetime.AsDuration(), + IdTokenLifetime: req.IdTokenLifetime.AsDuration(), + RefreshTokenIdleExpiration: req.RefreshTokenIdleExpiration.AsDuration(), + RefreshTokenExpiration: req.RefreshTokenExpiration.AsDuration(), + } +} + func UpdateOIDCConfigToConfig(req *admin_pb.UpdateOIDCSettingsRequest) *domain.OIDCSettings { return &domain.OIDCSettings{ AccessTokenLifetime: req.AccessTokenLifetime.AsDuration(), diff --git a/internal/api/oidc/auth_request.go b/internal/api/oidc/auth_request.go index 3c082cc215..8d2e8201e0 100644 --- a/internal/api/oidc/auth_request.go +++ b/internal/api/oidc/auth_request.go @@ -88,7 +88,13 @@ func (o *OPStorage) CreateAccessToken(ctx context.Context, req op.TokenRequest) applicationID = authReq.ApplicationID userOrgID = authReq.UserOrgID } - resp, err := o.command.AddUserToken(setContextUserSystem(ctx), userOrgID, userAgentID, applicationID, req.GetSubject(), req.GetAudience(), req.GetScopes(), o.defaultAccessTokenLifetime) //PLANNED: lifetime from client + + accessTokenLifetime, _, _, _, err := o.getOIDCSettings(ctx) + if err != nil { + return "", time.Time{}, err + } + + resp, err := o.command.AddUserToken(setContextUserSystem(ctx), userOrgID, userAgentID, applicationID, req.GetSubject(), req.GetAudience(), req.GetScopes(), accessTokenLifetime) //PLANNED: lifetime from client if err != nil { return "", time.Time{}, err } @@ -106,9 +112,15 @@ func (o *OPStorage) CreateAccessAndRefreshTokens(ctx context.Context, req op.Tok if request, ok := req.(op.RefreshTokenRequest); ok { request.SetCurrentScopes(scopes) } + + accessTokenLifetime, _, refreshTokenIdleExpiration, refreshTokenExpiration, err := o.getOIDCSettings(ctx) + if err != nil { + return "", "", time.Time{}, err + } + resp, token, err := o.command.AddAccessAndRefreshToken(setContextUserSystem(ctx), userOrgID, userAgentID, applicationID, req.GetSubject(), - refreshToken, req.GetAudience(), scopes, authMethodsReferences, o.defaultAccessTokenLifetime, - o.defaultRefreshTokenIdleExpiration, o.defaultRefreshTokenExpiration, authTime) //PLANNED: lifetime from client + refreshToken, req.GetAudience(), scopes, authMethodsReferences, accessTokenLifetime, + refreshTokenIdleExpiration, refreshTokenExpiration, authTime) //PLANNED: lifetime from client if err != nil { if errors.IsErrorInvalidArgument(err) { err = oidc.ErrInvalidGrant().WithParent(err) @@ -248,3 +260,15 @@ func setContextUserSystem(ctx context.Context) context.Context { } return authz.SetCtxData(ctx, data) } + +func (o *OPStorage) getOIDCSettings(ctx context.Context) (accessTokenLifetime, idTokenLifetime, refreshTokenIdleExpiration, refreshTokenExpiration time.Duration, _ error) { + oidcSettings, err := o.query.OIDCSettingsByAggID(ctx, authz.GetInstance(ctx).InstanceID()) + if err != nil && !errors.IsNotFound(err) { + return time.Duration(0), time.Duration(0), time.Duration(0), time.Duration(0), err + } + + if oidcSettings != nil { + return oidcSettings.AccessTokenLifetime, oidcSettings.IdTokenLifetime, oidcSettings.RefreshTokenIdleExpiration, oidcSettings.RefreshTokenExpiration, nil + } + return o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime, o.defaultRefreshTokenIdleExpiration, o.defaultRefreshTokenExpiration, nil +} diff --git a/internal/api/oidc/client.go b/internal/api/oidc/client.go index b73f549eb8..3a76eb1dcb 100644 --- a/internal/api/oidc/client.go +++ b/internal/api/oidc/client.go @@ -51,7 +51,13 @@ func (o *OPStorage) GetClientByClientID(ctx context.Context, id string) (_ op.Cl for i, role := range projectRoles.ProjectRoles { allowedScopes[i] = ScopeProjectRolePrefix + role.Key } - return ClientFromBusiness(client, o.defaultLoginURL, o.defaultAccessTokenLifetime, o.defaultIdTokenLifetime, allowedScopes) + + accessTokenLifetime, idTokenLifetime, _, _, err := o.getOIDCSettings(ctx) + if err != nil { + return nil, err + } + + return ClientFromBusiness(client, o.defaultLoginURL, accessTokenLifetime, idTokenLifetime, allowedScopes) } func (o *OPStorage) GetKeyByIDAndUserID(ctx context.Context, keyID, userID string) (_ *jose.JSONWebKey, err error) { diff --git a/internal/command/instance.go b/internal/command/instance.go index c6761c0b0e..efe07e9302 100644 --- a/internal/command/instance.go +++ b/internal/command/instance.go @@ -104,6 +104,12 @@ type InstanceSetup struct { EmailTemplate []byte MessageTexts []*domain.CustomMessageText SMTPConfiguration *smtp.EmailConfig + OIDCSettings *struct { + AccessTokenLifetime time.Duration + IdTokenLifetime time.Duration + RefreshTokenIdleExpiration time.Duration + RefreshTokenExpiration time.Duration + } } type ZitadelConfig struct { @@ -346,6 +352,18 @@ func (c *Commands) SetUpInstance(ctx context.Context, setup *InstanceSetup) (str ) } + if setup.OIDCSettings != nil { + validations = append(validations, + c.prepareAddOIDCSettings( + instanceAgg, + setup.OIDCSettings.AccessTokenLifetime, + setup.OIDCSettings.IdTokenLifetime, + setup.OIDCSettings.RefreshTokenIdleExpiration, + setup.OIDCSettings.RefreshTokenExpiration, + ), + ) + } + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validations...) if err != nil { return "", nil, err diff --git a/internal/command/instance_oidc_settings.go b/internal/command/instance_oidc_settings.go index 29ea4173f5..9ad727ebd7 100644 --- a/internal/command/instance_oidc_settings.go +++ b/internal/command/instance_oidc_settings.go @@ -2,78 +2,131 @@ package command import ( "context" + "time" + "github.com/zitadel/zitadel/internal/api/authz" + "github.com/zitadel/zitadel/internal/command/preparation" "github.com/zitadel/zitadel/internal/domain" - caos_errs "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/errors" + "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/repository/instance" ) +func (c *Commands) prepareAddOIDCSettings(a *instance.Aggregate, accessTokenLifetime, idTokenLifetime, refreshTokenIdleExpiration, refreshTokenExpiration time.Duration) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if accessTokenLifetime == time.Duration(0) || + idTokenLifetime == time.Duration(0) || + refreshTokenIdleExpiration == time.Duration(0) || + refreshTokenExpiration == time.Duration(0) { + return nil, errors.ThrowInvalidArgument(nil, "INST-10s82j", "Errors.Invalid.Argument") + } + + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + writeModel, err := c.getOIDCSettingsWriteModel(ctx, filter) + if err != nil { + return nil, err + } + if writeModel.State == domain.OIDCSettingsStateActive { + return nil, errors.ThrowAlreadyExists(nil, "INST-0aaj1o", "Errors.OIDCSettings.AlreadyExists") + } + return []eventstore.Command{ + instance.NewOIDCSettingsAddedEvent( + ctx, + &a.Aggregate, + accessTokenLifetime, + idTokenLifetime, + refreshTokenIdleExpiration, + refreshTokenExpiration, + ), + }, nil + }, nil + } +} + +func (c *Commands) prepareUpdateOIDCSettings(a *instance.Aggregate, accessTokenLifetime, idTokenLifetime, refreshTokenIdleExpiration, refreshTokenExpiration time.Duration) preparation.Validation { + return func() (preparation.CreateCommands, error) { + if accessTokenLifetime == time.Duration(0) || + idTokenLifetime == time.Duration(0) || + refreshTokenIdleExpiration == time.Duration(0) || + refreshTokenExpiration == time.Duration(0) { + return nil, errors.ThrowInvalidArgument(nil, "INST-10sxks", "Errors.Invalid.Argument") + } + + return func(ctx context.Context, filter preparation.FilterToQueryReducer) ([]eventstore.Command, error) { + writeModel, err := c.getOIDCSettingsWriteModel(ctx, filter) + if err != nil { + return nil, err + } + if writeModel.State != domain.OIDCSettingsStateActive { + return nil, errors.ThrowNotFound(nil, "INST-90s32oj", "Errors.OIDCSettings.NotFound") + } + changedEvent, hasChanged, err := writeModel.NewChangedEvent( + ctx, + &a.Aggregate, + accessTokenLifetime, + idTokenLifetime, + refreshTokenIdleExpiration, + refreshTokenExpiration, + ) + if err != nil { + return nil, err + } + if !hasChanged { + return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-0pk2nu", "Errors.NoChangesFound") + } + return []eventstore.Command{ + changedEvent, + }, nil + }, nil + } +} + func (c *Commands) AddOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) { - oidcSettingWriteModel, err := c.getOIDCSettings(ctx) + instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) + validation := c.prepareAddOIDCSettings(instanceAgg, settings.AccessTokenLifetime, settings.IdTokenLifetime, settings.RefreshTokenIdleExpiration, settings.RefreshTokenExpiration) + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) if err != nil { return nil, err } - if oidcSettingWriteModel.State.Exists() { - return nil, caos_errs.ThrowAlreadyExists(nil, "COMMAND-d9nlw", "Errors.OIDCSettings.AlreadyExists") - } - instanceAgg := InstanceAggregateFromWriteModel(&oidcSettingWriteModel.WriteModel) - pushedEvents, err := c.eventstore.Push(ctx, instance.NewOIDCSettingsAddedEvent( - ctx, - instanceAgg, - settings.AccessTokenLifetime, - settings.IdTokenLifetime, - settings.RefreshTokenIdleExpiration, - settings.RefreshTokenExpiration)) + events, err := c.eventstore.Push(ctx, cmds...) if err != nil { return nil, err } - err = AppendAndReduce(oidcSettingWriteModel, pushedEvents...) - if err != nil { - return nil, err - } - return writeModelToObjectDetails(&oidcSettingWriteModel.WriteModel), nil + return &domain.ObjectDetails{ + Sequence: events[len(events)-1].Sequence(), + EventDate: events[len(events)-1].CreationDate(), + ResourceOwner: events[len(events)-1].Aggregate().InstanceID, + }, nil } func (c *Commands) ChangeOIDCSettings(ctx context.Context, settings *domain.OIDCSettings) (*domain.ObjectDetails, error) { - oidcSettingWriteModel, err := c.getOIDCSettings(ctx) + instanceAgg := instance.NewAggregate(authz.GetInstance(ctx).InstanceID()) + validation := c.prepareUpdateOIDCSettings(instanceAgg, settings.AccessTokenLifetime, settings.IdTokenLifetime, settings.RefreshTokenIdleExpiration, settings.RefreshTokenExpiration) + cmds, err := preparation.PrepareCommands(ctx, c.eventstore.Filter, validation) if err != nil { return nil, err } - if !oidcSettingWriteModel.State.Exists() { - return nil, caos_errs.ThrowNotFound(nil, "COMMAND-8snEr", "Errors.OIDCSettings.NotFound") - } - instanceAgg := InstanceAggregateFromWriteModel(&oidcSettingWriteModel.WriteModel) - - changedEvent, hasChanged, err := oidcSettingWriteModel.NewChangedEvent( - ctx, - instanceAgg, - settings.AccessTokenLifetime, - settings.IdTokenLifetime, - settings.RefreshTokenIdleExpiration, - settings.RefreshTokenExpiration) + events, err := c.eventstore.Push(ctx, cmds...) if err != nil { return nil, err } - if !hasChanged { - return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-398uF", "Errors.NoChangesFound") - } - pushedEvents, err := c.eventstore.Push(ctx, changedEvent) - if err != nil { - return nil, err - } - err = AppendAndReduce(oidcSettingWriteModel, pushedEvents...) - if err != nil { - return nil, err - } - return writeModelToObjectDetails(&oidcSettingWriteModel.WriteModel), nil + return &domain.ObjectDetails{ + Sequence: events[len(events)-1].Sequence(), + EventDate: events[len(events)-1].CreationDate(), + ResourceOwner: events[len(events)-1].Aggregate().InstanceID, + }, nil } -func (c *Commands) getOIDCSettings(ctx context.Context) (_ *InstanceOIDCSettingsWriteModel, err error) { +func (c *Commands) getOIDCSettingsWriteModel(ctx context.Context, filter preparation.FilterToQueryReducer) (_ *InstanceOIDCSettingsWriteModel, err error) { writeModel := NewInstanceOIDCSettingsWriteModel(ctx) - err = c.eventstore.FilterToQueryReducer(ctx, writeModel) + events, err := filter(ctx, writeModel.Query()) if err != nil { return nil, err } - - return writeModel, nil + if len(events) == 0 { + return writeModel, nil + } + writeModel.AppendEvents(events...) + err = writeModel.Reduce() + return writeModel, err } diff --git a/internal/command/instance_oidc_settings_test.go b/internal/command/instance_oidc_settings_test.go index 50e584edbb..d40d856027 100644 --- a/internal/command/instance_oidc_settings_test.go +++ b/internal/command/instance_oidc_settings_test.go @@ -6,6 +6,7 @@ import ( "time" "github.com/stretchr/testify/assert" + "github.com/zitadel/zitadel/internal/api/authz" "github.com/zitadel/zitadel/internal/domain" @@ -34,7 +35,7 @@ func TestCommandSide_AddOIDCConfig(t *testing.T) { res res }{ { - name: "oidc config, error already exists", + name: "oidc settings, error already exists", fields: fields{ eventstore: eventstoreExpect( t, @@ -52,7 +53,7 @@ func TestCommandSide_AddOIDCConfig(t *testing.T) { ), }, args: args{ - ctx: context.Background(), + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), oidcConfig: &domain.OIDCSettings{ AccessTokenLifetime: 1 * time.Hour, IdTokenLifetime: 1 * time.Hour, @@ -65,7 +66,7 @@ func TestCommandSide_AddOIDCConfig(t *testing.T) { }, }, { - name: "add secret generator, ok", + name: "add oidc settings, ok", fields: fields{ eventstore: eventstoreExpect( t, @@ -102,6 +103,86 @@ func TestCommandSide_AddOIDCConfig(t *testing.T) { }, }, }, + { + name: "add oidc settings, invalid argument 1", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 0 * time.Hour, + IdTokenLifetime: 1 * time.Hour, + RefreshTokenIdleExpiration: 1 * time.Hour, + RefreshTokenExpiration: 1 * time.Hour, + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, + { + name: "add oidc settings, invalid argument 2", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 1 * time.Hour, + IdTokenLifetime: 0 * time.Hour, + RefreshTokenIdleExpiration: 1 * time.Hour, + RefreshTokenExpiration: 1 * time.Hour, + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, + { + name: "add oidc settings, invalid argument 3", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 1 * time.Hour, + IdTokenLifetime: 1 * time.Hour, + RefreshTokenIdleExpiration: 0 * time.Hour, + RefreshTokenExpiration: 1 * time.Hour, + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, + { + name: "add oidc settings, invalid argument 4", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 1 * time.Hour, + IdTokenLifetime: 1 * time.Hour, + RefreshTokenIdleExpiration: 1 * time.Hour, + RefreshTokenExpiration: 0 * time.Hour, + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -141,7 +222,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) { res res }{ { - name: "oidc config not existing, not found error", + name: "oidc settings not existing, not found error", fields: fields{ eventstore: eventstoreExpect( t, @@ -150,11 +231,97 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) { }, args: args{ ctx: context.Background(), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 1 * time.Hour, + IdTokenLifetime: 1 * time.Hour, + RefreshTokenIdleExpiration: 1 * time.Hour, + RefreshTokenExpiration: 1 * time.Hour, + }, }, res: res{ err: caos_errs.IsNotFound, }, }, + { + name: "no changes, invalid argument error 1", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 0 * time.Hour, + IdTokenLifetime: 1 * time.Hour, + RefreshTokenIdleExpiration: 1 * time.Hour, + RefreshTokenExpiration: 1 * time.Hour, + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, + { + name: "no changes, invalid argument error 2", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 1 * time.Hour, + IdTokenLifetime: 0 * time.Hour, + RefreshTokenIdleExpiration: 1 * time.Hour, + RefreshTokenExpiration: 1 * time.Hour, + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, + { + name: "no changes, invalid argument error 3", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 1 * time.Hour, + IdTokenLifetime: 1 * time.Hour, + RefreshTokenIdleExpiration: 0 * time.Hour, + RefreshTokenExpiration: 1 * time.Hour, + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, + { + name: "no changes, invalid argument error 4", + fields: fields{ + eventstore: eventstoreExpect( + t, + ), + }, + args: args{ + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), + oidcConfig: &domain.OIDCSettings{ + AccessTokenLifetime: 1 * time.Hour, + IdTokenLifetime: 1 * time.Hour, + RefreshTokenIdleExpiration: 1 * time.Hour, + RefreshTokenExpiration: 0 * time.Hour, + }, + }, + res: res{ + err: caos_errs.IsErrorInvalidArgument, + }, + }, { name: "no changes, precondition error", fields: fields{ @@ -175,7 +342,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) { ), }, args: args{ - ctx: context.Background(), + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), oidcConfig: &domain.OIDCSettings{ AccessTokenLifetime: 1 * time.Hour, IdTokenLifetime: 1 * time.Hour, @@ -188,7 +355,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) { }, }, { - name: "secret generator change, ok", + name: "oidc settings change, ok", fields: fields{ eventstore: eventstoreExpect( t, @@ -206,8 +373,9 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) { ), expectPush( []*repository.Event{ - eventFromEventPusher( - newOIDCConfigChangedEvent(context.Background(), + eventFromEventPusherWithInstanceID("INSTANCE", + newOIDCConfigChangedEvent( + context.Background(), time.Hour*2, time.Hour*2, time.Hour*2, @@ -218,7 +386,7 @@ func TestCommandSide_ChangeOIDCConfig(t *testing.T) { ), }, args: args{ - ctx: context.Background(), + ctx: authz.WithInstanceID(context.Background(), "INSTANCE"), oidcConfig: &domain.OIDCSettings{ AccessTokenLifetime: 2 * time.Hour, IdTokenLifetime: 2 * time.Hour, diff --git a/internal/command/smtp.go b/internal/command/smtp.go index a3f99f6dc0..895ba5b4f9 100644 --- a/internal/command/smtp.go +++ b/internal/command/smtp.go @@ -9,7 +9,6 @@ import ( "github.com/zitadel/zitadel/internal/crypto" "github.com/zitadel/zitadel/internal/domain" "github.com/zitadel/zitadel/internal/errors" - caos_errs "github.com/zitadel/zitadel/internal/errors" "github.com/zitadel/zitadel/internal/eventstore" "github.com/zitadel/zitadel/internal/notification/channels/smtp" "github.com/zitadel/zitadel/internal/repository/instance" @@ -58,7 +57,7 @@ func (c *Commands) ChangeSMTPConfigPassword(ctx context.Context, password string return nil, err } if smtpConfigWriteModel.State != domain.SMTPConfigStateActive { - return nil, caos_errs.ThrowNotFound(nil, "COMMAND-3n9ls", "Errors.SMTPConfig.NotFound") + return nil, errors.ThrowNotFound(nil, "COMMAND-3n9ls", "Errors.SMTPConfig.NotFound") } var smtpPassword *crypto.CryptoValue if password != "" { @@ -180,7 +179,7 @@ func (c *Commands) prepareChangeSMTPConfig(a *instance.Aggregate, from, name, ho return nil, err } if !hasChanged { - return nil, caos_errs.ThrowPreconditionFailed(nil, "COMMAND-m0o3f", "Errors.NoChangesFound") + return nil, errors.ThrowPreconditionFailed(nil, "COMMAND-m0o3f", "Errors.NoChangesFound") } return []eventstore.Command{ changedEvent, diff --git a/internal/query/projection/oidc_settings.go b/internal/query/projection/oidc_settings.go index 1aeff373ec..ad9ea79290 100644 --- a/internal/query/projection/oidc_settings.go +++ b/internal/query/projection/oidc_settings.go @@ -8,7 +8,6 @@ import ( "github.com/zitadel/zitadel/internal/eventstore/handler" "github.com/zitadel/zitadel/internal/eventstore/handler/crdb" "github.com/zitadel/zitadel/internal/repository/instance" - "github.com/zitadel/zitadel/internal/repository/project" ) const ( @@ -43,7 +42,6 @@ func newOIDCSettingsProjection(ctx context.Context, config crdb.StatementHandler crdb.NewColumn(OIDCSettingsColumnInstanceID, crdb.ColumnTypeText), crdb.NewColumn(OIDCSettingsColumnSequence, crdb.ColumnTypeInt64), crdb.NewColumn(OIDCSettingsColumnAccessTokenLifetime, crdb.ColumnTypeInt64), - crdb.NewColumn(ExternalLoginCheckLifetimeCol, crdb.ColumnTypeInt64), crdb.NewColumn(OIDCSettingsColumnIdTokenLifetime, crdb.ColumnTypeInt64), crdb.NewColumn(OIDCSettingsColumnRefreshTokenIdleExpiration, crdb.ColumnTypeInt64), crdb.NewColumn(OIDCSettingsColumnRefreshTokenExpiration, crdb.ColumnTypeInt64), @@ -58,7 +56,7 @@ func newOIDCSettingsProjection(ctx context.Context, config crdb.StatementHandler func (p *oidcSettingsProjection) reducers() []handler.AggregateReducer { return []handler.AggregateReducer{ { - Aggregate: project.AggregateType, + Aggregate: instance.AggregateType, EventRedusers: []handler.EventReducer{ { Event: instance.OIDCSettingsAddedEventType, diff --git a/internal/repository/instance/oidc_settings.go b/internal/repository/instance/oidc_settings.go index f7d2a1e40f..b33f2265e7 100644 --- a/internal/repository/instance/oidc_settings.go +++ b/internal/repository/instance/oidc_settings.go @@ -14,7 +14,6 @@ const ( oidcSettingsPrefix = "oidc.settings." OIDCSettingsAddedEventType = instanceEventTypePrefix + oidcSettingsPrefix + "added" OIDCSettingsChangedEventType = instanceEventTypePrefix + oidcSettingsPrefix + "changed" - OIDCSettingsRemovedEventType = instanceEventTypePrefix + oidcSettingsPrefix + "removed" ) type OIDCSettingsAddedEvent struct { diff --git a/proto/zitadel/admin.proto b/proto/zitadel/admin.proto index 21e44a2ed9..3caaaf5403 100644 --- a/proto/zitadel/admin.proto +++ b/proto/zitadel/admin.proto @@ -402,6 +402,18 @@ service AdminService { }; } + // Add oidc settings (e.g token lifetimes, etc) + rpc AddOIDCSettings(AddOIDCSettingsRequest) returns (AddOIDCSettingsResponse) { + option (google.api.http) = { + post: "/settings/oidc"; + body: "*" + }; + + option (zitadel.v1.auth_option) = { + permission: "iam.write"; + }; + } + // Update oidc settings (e.g token lifetimes, etc) rpc UpdateOIDCSettings(UpdateOIDCSettingsRequest) returns (UpdateOIDCSettingsResponse) { option (google.api.http) = { @@ -2895,6 +2907,17 @@ message GetOIDCSettingsResponse { zitadel.settings.v1.OIDCSettings settings = 1; } +message AddOIDCSettingsRequest { + google.protobuf.Duration access_token_lifetime = 1; + google.protobuf.Duration id_token_lifetime = 2; + google.protobuf.Duration refresh_token_idle_expiration = 3; + google.protobuf.Duration refresh_token_expiration = 4; +} + +message AddOIDCSettingsResponse { + zitadel.v1.ObjectDetails details = 1; +} + message UpdateOIDCSettingsRequest { google.protobuf.Duration access_token_lifetime = 1; google.protobuf.Duration id_token_lifetime = 2;