sources/oauth: include default JWKS URLs for OAuth sources (#6992)
* sources/oauth: include default JWKS URLs for OAuth sources makes it easier to use pre-defined types like github, google, azure with JWT M2M instead of needing to create a generic OAuth Source Signed-off-by: Jens Langhammer <jens@goauthentik.io> * fix error Signed-off-by: Jens Langhammer <jens@goauthentik.io> --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io>
This commit is contained in:
parent
63c52fd936
commit
63426bc9a8
|
@ -30,6 +30,8 @@ class SourceTypeSerializer(PassiveSerializer):
|
||||||
authorization_url = CharField(read_only=True, allow_null=True)
|
authorization_url = CharField(read_only=True, allow_null=True)
|
||||||
access_token_url = CharField(read_only=True, allow_null=True)
|
access_token_url = CharField(read_only=True, allow_null=True)
|
||||||
profile_url = CharField(read_only=True, allow_null=True)
|
profile_url = CharField(read_only=True, allow_null=True)
|
||||||
|
oidc_well_known_url = CharField(read_only=True, allow_null=True)
|
||||||
|
oidc_jwks_url = CharField(read_only=True, allow_null=True)
|
||||||
|
|
||||||
|
|
||||||
class OAuthSourceSerializer(SourceSerializer):
|
class OAuthSourceSerializer(SourceSerializer):
|
||||||
|
@ -56,7 +58,11 @@ class OAuthSourceSerializer(SourceSerializer):
|
||||||
|
|
||||||
def validate(self, attrs: dict) -> dict:
|
def validate(self, attrs: dict) -> dict:
|
||||||
session = get_http_session()
|
session = get_http_session()
|
||||||
well_known = attrs.get("oidc_well_known_url")
|
source_type = registry.find_type(attrs["provider_type"])
|
||||||
|
|
||||||
|
well_known = attrs.get("oidc_well_known_url") or source_type.oidc_well_known_url
|
||||||
|
inferred_oidc_jwks_url = None
|
||||||
|
|
||||||
if well_known and well_known != "":
|
if well_known and well_known != "":
|
||||||
try:
|
try:
|
||||||
well_known_config = session.get(well_known)
|
well_known_config = session.get(well_known)
|
||||||
|
@ -69,20 +75,22 @@ class OAuthSourceSerializer(SourceSerializer):
|
||||||
attrs["authorization_url"] = config["authorization_endpoint"]
|
attrs["authorization_url"] = config["authorization_endpoint"]
|
||||||
attrs["access_token_url"] = config["token_endpoint"]
|
attrs["access_token_url"] = config["token_endpoint"]
|
||||||
attrs["profile_url"] = config["userinfo_endpoint"]
|
attrs["profile_url"] = config["userinfo_endpoint"]
|
||||||
attrs["oidc_jwks_url"] = config["jwks_uri"]
|
inferred_oidc_jwks_url = config["jwks_uri"]
|
||||||
except (IndexError, KeyError) as exc:
|
except (IndexError, KeyError) as exc:
|
||||||
raise ValidationError(
|
raise ValidationError(
|
||||||
{"oidc_well_known_url": f"Invalid well-known configuration: {exc}"}
|
{"oidc_well_known_url": f"Invalid well-known configuration: {exc}"}
|
||||||
)
|
)
|
||||||
|
|
||||||
jwks_url = attrs.get("oidc_jwks_url")
|
# Prefer user-entered URL to inferred URL to default URL
|
||||||
|
jwks_url = attrs.get("oidc_jwks_url") or inferred_oidc_jwks_url or source_type.oidc_jwks_url
|
||||||
if jwks_url and jwks_url != "":
|
if jwks_url and jwks_url != "":
|
||||||
|
attrs["oidc_jwks_url"] = jwks_url
|
||||||
try:
|
try:
|
||||||
jwks_config = session.get(jwks_url)
|
jwks_config = session.get(jwks_url)
|
||||||
jwks_config.raise_for_status()
|
jwks_config.raise_for_status()
|
||||||
except RequestException as exc:
|
except RequestException as exc:
|
||||||
text = exc.response.text if exc.response else str(exc)
|
text = exc.response.text if exc.response else str(exc)
|
||||||
raise ValidationError({"jwks_url": text})
|
raise ValidationError({"oidc_jwks_url": text})
|
||||||
config = jwks_config.json()
|
config = jwks_config.json()
|
||||||
attrs["oidc_jwks"] = config
|
attrs["oidc_jwks"] = config
|
||||||
|
|
||||||
|
|
|
@ -51,3 +51,7 @@ class AzureADType(SourceType):
|
||||||
authorization_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
|
authorization_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
|
||||||
access_token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
|
access_token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token" # nosec
|
||||||
profile_url = "https://graph.microsoft.com/v1.0/me"
|
profile_url = "https://graph.microsoft.com/v1.0/me"
|
||||||
|
oidc_well_known_url = (
|
||||||
|
"https://login.microsoftonline.com/common/.well-known/openid-configuration"
|
||||||
|
)
|
||||||
|
oidc_jwks_url = "https://login.microsoftonline.com/common/discovery/keys"
|
||||||
|
|
|
@ -76,3 +76,7 @@ class GitHubType(SourceType):
|
||||||
authorization_url = "https://github.com/login/oauth/authorize"
|
authorization_url = "https://github.com/login/oauth/authorize"
|
||||||
access_token_url = "https://github.com/login/oauth/access_token" # nosec
|
access_token_url = "https://github.com/login/oauth/access_token" # nosec
|
||||||
profile_url = "https://api.github.com/user"
|
profile_url = "https://api.github.com/user"
|
||||||
|
oidc_well_known_url = (
|
||||||
|
"https://token.actions.githubusercontent.com/.well-known/openid-configuration"
|
||||||
|
)
|
||||||
|
oidc_jwks_url = "https://token.actions.githubusercontent.com/.well-known/jwks"
|
||||||
|
|
|
@ -40,3 +40,5 @@ class GoogleType(SourceType):
|
||||||
authorization_url = "https://accounts.google.com/o/oauth2/auth"
|
authorization_url = "https://accounts.google.com/o/oauth2/auth"
|
||||||
access_token_url = "https://oauth2.googleapis.com/token" # nosec
|
access_token_url = "https://oauth2.googleapis.com/token" # nosec
|
||||||
profile_url = "https://www.googleapis.com/oauth2/v1/userinfo"
|
profile_url = "https://www.googleapis.com/oauth2/v1/userinfo"
|
||||||
|
oidc_well_known_url = "https://accounts.google.com/.well-known/openid-configuration"
|
||||||
|
oidc_jwks_url = "https://www.googleapis.com/oauth2/v3/certs"
|
||||||
|
|
|
@ -36,6 +36,8 @@ class SourceType:
|
||||||
authorization_url: Optional[str] = None
|
authorization_url: Optional[str] = None
|
||||||
access_token_url: Optional[str] = None
|
access_token_url: Optional[str] = None
|
||||||
profile_url: Optional[str] = None
|
profile_url: Optional[str] = None
|
||||||
|
oidc_well_known_url: Optional[str] = None
|
||||||
|
oidc_jwks_url: Optional[str] = None
|
||||||
|
|
||||||
def icon_url(self) -> str:
|
def icon_url(self) -> str:
|
||||||
"""Get Icon URL for login"""
|
"""Get Icon URL for login"""
|
||||||
|
|
10
schema.yml
10
schema.yml
|
@ -40934,10 +40934,20 @@ components:
|
||||||
type: string
|
type: string
|
||||||
readOnly: true
|
readOnly: true
|
||||||
nullable: true
|
nullable: true
|
||||||
|
oidc_well_known_url:
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
nullable: true
|
||||||
|
oidc_jwks_url:
|
||||||
|
type: string
|
||||||
|
readOnly: true
|
||||||
|
nullable: true
|
||||||
required:
|
required:
|
||||||
- access_token_url
|
- access_token_url
|
||||||
- authorization_url
|
- authorization_url
|
||||||
- name
|
- name
|
||||||
|
- oidc_jwks_url
|
||||||
|
- oidc_well_known_url
|
||||||
- profile_url
|
- profile_url
|
||||||
- request_token_url
|
- request_token_url
|
||||||
- slug
|
- slug
|
||||||
|
|
|
@ -192,7 +192,11 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value="${ifDefined(this.instance?.oidcWellKnownUrl)}"
|
value="${first(
|
||||||
|
this.instance?.oidcWellKnownUrl,
|
||||||
|
this.providerType.oidcWellKnownUrl,
|
||||||
|
"",
|
||||||
|
)}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
/>
|
/>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
|
@ -207,7 +211,11 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
type="text"
|
type="text"
|
||||||
value="${ifDefined(this.instance?.oidcJwksUrl)}"
|
value="${first(
|
||||||
|
this.instance?.oidcJwksUrl,
|
||||||
|
this.providerType.oidcJwksUrl,
|
||||||
|
"",
|
||||||
|
)}"
|
||||||
class="pf-c-form-control"
|
class="pf-c-form-control"
|
||||||
/>
|
/>
|
||||||
<p class="pf-c-form__helper-text">
|
<p class="pf-c-form__helper-text">
|
||||||
|
|
|
@ -613,9 +613,9 @@
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="saa0e2675da69651b">
|
<trans-unit id="saa0e2675da69651b">
|
||||||
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
<source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
|
||||||
<target>未找到 URL "
|
<target>未找到 URL "
|
||||||
<x id="0" equiv-text="${this.url}"/>"。</target>
|
<x id="0" equiv-text="${this.url}"/>"。</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s58cd9c2fe836d9c6">
|
<trans-unit id="s58cd9c2fe836d9c6">
|
||||||
|
@ -1067,8 +1067,8 @@
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sa8384c9c26731f83">
|
<trans-unit id="sa8384c9c26731f83">
|
||||||
<source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
|
<source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
|
||||||
<target>要允许任何重定向 URI,请将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
|
<target>要允许任何重定向 URI,请将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s55787f4dfcdce52b">
|
<trans-unit id="s55787f4dfcdce52b">
|
||||||
|
@ -1809,8 +1809,8 @@
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sa90b7809586c35ce">
|
<trans-unit id="sa90b7809586c35ce">
|
||||||
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
|
<source>Either input a full URL, a relative path, or use 'fa://fa-test' to use the Font Awesome icon "fa-test".</source>
|
||||||
<target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
|
<target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s0410779cb47de312">
|
<trans-unit id="s0410779cb47de312">
|
||||||
|
@ -3023,8 +3023,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s76768bebabb7d543">
|
<trans-unit id="s76768bebabb7d543">
|
||||||
<source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
|
<source>Field which contains members of a group. Note that if using the "memberUid" field, the value is assumed to contain a relative distinguished name. e.g. 'memberUid=some-user' instead of 'memberUid=cn=some-user,ou=groups,...'</source>
|
||||||
<target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
|
<target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s026555347e589f0e">
|
<trans-unit id="s026555347e589f0e">
|
||||||
|
@ -3816,8 +3816,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s7b1fba26d245cb1c">
|
<trans-unit id="s7b1fba26d245cb1c">
|
||||||
<source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
|
<source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
|
||||||
<target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
|
<target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s44536d20bb5c8257">
|
<trans-unit id="s44536d20bb5c8257">
|
||||||
|
@ -3826,8 +3826,8 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s3bb51cabb02b997e">
|
<trans-unit id="s3bb51cabb02b997e">
|
||||||
<source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
|
<source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
|
||||||
<target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target>
|
<target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s04bfd02201db5ab8">
|
<trans-unit id="s04bfd02201db5ab8">
|
||||||
|
@ -4023,10 +4023,10 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sa95a538bfbb86111">
|
<trans-unit id="sa95a538bfbb86111">
|
||||||
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
|
<source>Are you sure you want to update <x id="0" equiv-text="${this.objectLabel}"/> "<x id="1" equiv-text="${this.obj?.name}"/>"?</source>
|
||||||
<target>您确定要更新
|
<target>您确定要更新
|
||||||
<x id="0" equiv-text="${this.objectLabel}"/>"
|
<x id="0" equiv-text="${this.objectLabel}"/>"
|
||||||
<x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
|
<x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sc92d7cfb6ee1fec6">
|
<trans-unit id="sc92d7cfb6ee1fec6">
|
||||||
|
@ -5122,7 +5122,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="sdf1d8edef27236f0">
|
<trans-unit id="sdf1d8edef27236f0">
|
||||||
<source>A "roaming" authenticator, like a YubiKey</source>
|
<source>A "roaming" authenticator, like a YubiKey</source>
|
||||||
<target>像 YubiKey 这样的“漫游”身份验证器</target>
|
<target>像 YubiKey 这样的“漫游”身份验证器</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
@ -5457,10 +5457,10 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s2d5f69929bb7221d">
|
<trans-unit id="s2d5f69929bb7221d">
|
||||||
<source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
|
<source><x id="0" equiv-text="${prompt.name}"/> ("<x id="1" equiv-text="${prompt.fieldKey}"/>", of type <x id="2" equiv-text="${prompt.type}"/>)</source>
|
||||||
<target>
|
<target>
|
||||||
<x id="0" equiv-text="${prompt.name}"/>("
|
<x id="0" equiv-text="${prompt.name}"/>("
|
||||||
<x id="1" equiv-text="${prompt.fieldKey}"/>",类型为
|
<x id="1" equiv-text="${prompt.fieldKey}"/>",类型为
|
||||||
<x id="2" equiv-text="${prompt.type}"/>)</target>
|
<x id="2" equiv-text="${prompt.type}"/>)</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
@ -5509,7 +5509,7 @@ doesn't pass when either or both of the selected options are equal or above the
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
<trans-unit id="s1608b2f94fa0dbd4">
|
<trans-unit id="s1608b2f94fa0dbd4">
|
||||||
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
|
<source>If set to a duration above 0, the user will have the option to choose to "stay signed in", which will extend their session by the time specified here.</source>
|
||||||
<target>如果设置时长大于 0,用户可以选择“保持登录”选项,这将使用户的会话延长此处设置的时间。</target>
|
<target>如果设置时长大于 0,用户可以选择“保持登录”选项,这将使用户的会话延长此处设置的时间。</target>
|
||||||
|
|
||||||
</trans-unit>
|
</trans-unit>
|
||||||
|
|
Reference in New Issue