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:
		@ -30,6 +30,8 @@ class SourceTypeSerializer(PassiveSerializer):
 | 
			
		||||
    authorization_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)
 | 
			
		||||
    oidc_well_known_url = CharField(read_only=True, allow_null=True)
 | 
			
		||||
    oidc_jwks_url = CharField(read_only=True, allow_null=True)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
class OAuthSourceSerializer(SourceSerializer):
 | 
			
		||||
@ -56,7 +58,11 @@ class OAuthSourceSerializer(SourceSerializer):
 | 
			
		||||
 | 
			
		||||
    def validate(self, attrs: dict) -> dict:
 | 
			
		||||
        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 != "":
 | 
			
		||||
            try:
 | 
			
		||||
                well_known_config = session.get(well_known)
 | 
			
		||||
@ -69,20 +75,22 @@ class OAuthSourceSerializer(SourceSerializer):
 | 
			
		||||
                attrs["authorization_url"] = config["authorization_endpoint"]
 | 
			
		||||
                attrs["access_token_url"] = config["token_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:
 | 
			
		||||
                raise ValidationError(
 | 
			
		||||
                    {"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 != "":
 | 
			
		||||
            attrs["oidc_jwks_url"] = jwks_url
 | 
			
		||||
            try:
 | 
			
		||||
                jwks_config = session.get(jwks_url)
 | 
			
		||||
                jwks_config.raise_for_status()
 | 
			
		||||
            except RequestException as 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()
 | 
			
		||||
            attrs["oidc_jwks"] = config
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -51,3 +51,7 @@ class AzureADType(SourceType):
 | 
			
		||||
    authorization_url = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize"
 | 
			
		||||
    access_token_url = "https://login.microsoftonline.com/common/oauth2/v2.0/token"  # nosec
 | 
			
		||||
    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"
 | 
			
		||||
    access_token_url = "https://github.com/login/oauth/access_token"  # nosec
 | 
			
		||||
    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"
 | 
			
		||||
    access_token_url = "https://oauth2.googleapis.com/token"  # nosec
 | 
			
		||||
    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
 | 
			
		||||
    access_token_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:
 | 
			
		||||
        """Get Icon URL for login"""
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										10
									
								
								schema.yml
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								schema.yml
									
									
									
									
									
								
							@ -40934,10 +40934,20 @@ components:
 | 
			
		||||
          type: string
 | 
			
		||||
          readOnly: true
 | 
			
		||||
          nullable: true
 | 
			
		||||
        oidc_well_known_url:
 | 
			
		||||
          type: string
 | 
			
		||||
          readOnly: true
 | 
			
		||||
          nullable: true
 | 
			
		||||
        oidc_jwks_url:
 | 
			
		||||
          type: string
 | 
			
		||||
          readOnly: true
 | 
			
		||||
          nullable: true
 | 
			
		||||
      required:
 | 
			
		||||
      - access_token_url
 | 
			
		||||
      - authorization_url
 | 
			
		||||
      - name
 | 
			
		||||
      - oidc_jwks_url
 | 
			
		||||
      - oidc_well_known_url
 | 
			
		||||
      - profile_url
 | 
			
		||||
      - request_token_url
 | 
			
		||||
      - slug
 | 
			
		||||
 | 
			
		||||
@ -192,7 +192,11 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
 | 
			
		||||
                          >
 | 
			
		||||
                              <input
 | 
			
		||||
                                  type="text"
 | 
			
		||||
                                  value="${ifDefined(this.instance?.oidcWellKnownUrl)}"
 | 
			
		||||
                                  value="${first(
 | 
			
		||||
                                      this.instance?.oidcWellKnownUrl,
 | 
			
		||||
                                      this.providerType.oidcWellKnownUrl,
 | 
			
		||||
                                      "",
 | 
			
		||||
                                  )}"
 | 
			
		||||
                                  class="pf-c-form-control"
 | 
			
		||||
                              />
 | 
			
		||||
                              <p class="pf-c-form__helper-text">
 | 
			
		||||
@ -207,7 +211,11 @@ export class OAuthSourceForm extends ModelForm<OAuthSource, string> {
 | 
			
		||||
                          >
 | 
			
		||||
                              <input
 | 
			
		||||
                                  type="text"
 | 
			
		||||
                                  value="${ifDefined(this.instance?.oidcJwksUrl)}"
 | 
			
		||||
                                  value="${first(
 | 
			
		||||
                                      this.instance?.oidcJwksUrl,
 | 
			
		||||
                                      this.providerType.oidcJwksUrl,
 | 
			
		||||
                                      "",
 | 
			
		||||
                                  )}"
 | 
			
		||||
                                  class="pf-c-form-control"
 | 
			
		||||
                              />
 | 
			
		||||
                              <p class="pf-c-form__helper-text">
 | 
			
		||||
 | 
			
		||||
@ -613,9 +613,9 @@
 | 
			
		||||
        
 | 
			
		||||
      </trans-unit>
 | 
			
		||||
      <trans-unit id="saa0e2675da69651b">
 | 
			
		||||
        <source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
 | 
			
		||||
        <target>未找到 URL " 
 | 
			
		||||
        <x id="0" equiv-text="${this.url}"/>"。</target>
 | 
			
		||||
        <source>The URL "<x id="0" equiv-text="${this.url}"/>" was not found.</source>
 | 
			
		||||
        <target>未找到 URL " 
 | 
			
		||||
        <x id="0" equiv-text="${this.url}"/>"。</target>
 | 
			
		||||
        
 | 
			
		||||
      </trans-unit>
 | 
			
		||||
      <trans-unit id="s58cd9c2fe836d9c6">
 | 
			
		||||
@ -1067,8 +1067,8 @@
 | 
			
		||||
        
 | 
			
		||||
      </trans-unit>
 | 
			
		||||
      <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>
 | 
			
		||||
        <target>要允许任何重定向 URI,请将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
 | 
			
		||||
        <source>To allow any redirect URI, set this value to ".*". Be aware of the possible security implications this can have.</source>
 | 
			
		||||
        <target>要允许任何重定向 URI,请将此值设置为 ".*"。请注意这可能带来的安全影响。</target>
 | 
			
		||||
        
 | 
			
		||||
      </trans-unit>
 | 
			
		||||
      <trans-unit id="s55787f4dfcdce52b">
 | 
			
		||||
@ -1809,8 +1809,8 @@
 | 
			
		||||
        
 | 
			
		||||
      </trans-unit>
 | 
			
		||||
      <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>
 | 
			
		||||
        <target>输入完整 URL、相对路径,或者使用 'fa://fa-test' 来使用 Font Awesome 图标 "fa-test"。</target>
 | 
			
		||||
        <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>
 | 
			
		||||
        
 | 
			
		||||
      </trans-unit>
 | 
			
		||||
      <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 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>
 | 
			
		||||
        <target>包含组成员的字段。请注意,如果使用 "memberUid" 字段,则假定该值包含相对可分辨名称。例如,'memberUid=some-user' 而不是 'memberUid=cn=some-user,ou=groups,...'</target>
 | 
			
		||||
        <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>
 | 
			
		||||
        
 | 
			
		||||
      </trans-unit>
 | 
			
		||||
      <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 id="s7b1fba26d245cb1c">
 | 
			
		||||
        <source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
 | 
			
		||||
        <target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
 | 
			
		||||
        <source>When using an external logging solution for archiving, this can be set to "minutes=5".</source>
 | 
			
		||||
        <target>使用外部日志记录解决方案进行存档时,可以将其设置为 "minutes=5"。</target>
 | 
			
		||||
        
 | 
			
		||||
      </trans-unit>
 | 
			
		||||
      <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 id="s3bb51cabb02b997e">
 | 
			
		||||
        <source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
 | 
			
		||||
        <target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target>
 | 
			
		||||
        <source>Format: "weeks=3;days=2;hours=3,seconds=2".</source>
 | 
			
		||||
        <target>格式:"weeks=3;days=2;hours=3,seconds=2"。</target>
 | 
			
		||||
        
 | 
			
		||||
      </trans-unit>
 | 
			
		||||
      <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 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>您确定要更新 
 | 
			
		||||
        <x id="0" equiv-text="${this.objectLabel}"/>" 
 | 
			
		||||
        <x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
 | 
			
		||||
        <x id="0" equiv-text="${this.objectLabel}"/>" 
 | 
			
		||||
        <x id="1" equiv-text="${this.obj?.name}"/>" 吗?</target>
 | 
			
		||||
        
 | 
			
		||||
      </trans-unit>
 | 
			
		||||
      <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 id="sdf1d8edef27236f0">
 | 
			
		||||
        <source>A "roaming" authenticator, like a YubiKey</source>
 | 
			
		||||
        <source>A "roaming" authenticator, like a YubiKey</source>
 | 
			
		||||
        <target>像 YubiKey 这样的“漫游”身份验证器</target>
 | 
			
		||||
        
 | 
			
		||||
      </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 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>
 | 
			
		||||
        <x id="0" equiv-text="${prompt.name}"/>(" 
 | 
			
		||||
        <x id="1" equiv-text="${prompt.fieldKey}"/>",类型为 
 | 
			
		||||
        <x id="0" equiv-text="${prompt.name}"/>(" 
 | 
			
		||||
        <x id="1" equiv-text="${prompt.fieldKey}"/>",类型为 
 | 
			
		||||
        <x id="2" equiv-text="${prompt.type}"/>)</target>
 | 
			
		||||
        
 | 
			
		||||
      </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 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>
 | 
			
		||||
        
 | 
			
		||||
      </trans-unit>
 | 
			
		||||
 | 
			
		||||
		Reference in New Issue
	
	Block a user