Repository: python-social-auth/social-core (python)

Issue:
Okta OpenID Connect Custom Auth Server Error
### Expected behaviour

When using a custom authorization server, with OktaOpenIdConnect, the proper openid-configuration URL should be generated.

### Actual behaviour

Okta OpenID Connect has two different URLs for the [openid-configuration API](https://developer.okta.com/docs/reference/api/oidc/#well-known-openid-configuration). 

* `https://${yourOktaDomain}/.well-known/openid-configuration`
* `https://${yourOktaDomain}/oauth2/${authorizationServerId}/.well-known/openid-configuration`

When using a authorization server the URL generation code truncates off the `oauth2/${authorizationServerId}` resulting in a `404` error when attempting to hit the openid-configuration URL. For the non-custom authorization server use case, the code works fine.

### What are the steps to reproduce this issue?

1. Setup a custom authorization server in Okta for OpenID Connect
2. Set the `SOCIAL_AUTH_OKTA_OPENIDCONNECT_API_URL` variable to your custom server URL
3. Attempt to authenticate through the app.

### Any logs, error output, etc?

I'm using this module through https://github.com/netbox-community/netbox which is currently pinned to version 4.1.0. The behavior I describe in this ticket is actually the opposite, custom-auth servers work and non-custom auth servers break. I wrote this issued based on [this commit](https://github.com/python-social-auth/social-core/commit/5c8f653ed04de0724e55de096a13e0d08da86fc0#diff-0990f86a2ca92170c32636801c9fad59f839302df2d28d822be2c9e76069708c) which sets up the behavior described in this issue.


Pull request:
Bug/okta OIDC configuration url fix
## Proposed changes

Describe the big picture of your changes here to communicate to the maintainers
why we should accept this pull request. If it fixes a bug or resolves a feature
request, be sure to link to that issue.

## Types of changes

Please check the type of change your PR introduces:

- [ ] Release (new release request)
- [X] Bugfix (non-breaking change which fixes an issue)
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Code style update (PEP8, lint, formatting, renaming, etc)
- [ ] Refactoring (no functional changes, no api changes)
- [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected)
- [ ] Build related changes (build process, tests runner, etc)
- [ ] Other (please describe):

## Checklist

_Put an `x` in the boxes that apply. You can also fill these out after creating
the PR. If you're unsure about any of them, don't hesitate to ask. We're here to
help! This is simply a reminder of what we are going to look for before merging
your code._

- [X] Lint and unit tests pass locally with my changes
- [X] I have added tests that prove my fix is effective or that my feature works

Should fix #662 without breaking non-custom auth server configurations.

Patch:
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index 98158e73..1c6a4e78 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -17,7 +17,7 @@ repos:
   - id: pretty-format-json
     args: [--no-sort-keys, --autofix, --no-ensure-ascii]
 - repo: https://github.com/astral-sh/ruff-pre-commit
-  rev: v0.11.8
+  rev: v0.11.9
   hooks:
   - id: ruff
     args: [--fix, --exit-non-zero-on-fix]
@@ -59,6 +59,6 @@ repos:
   hooks:
   - id: actionlint
 - repo: https://github.com/woodruffw/zizmor-pre-commit
-  rev: v1.6.0
+  rev: v1.7.0
   hooks:
   - id: zizmor
diff --git a/social_core/backends/okta.py b/social_core/backends/okta.py
index 87c31e25..8b06186c 100644
--- a/social_core/backends/okta.py
+++ b/social_core/backends/okta.py
@@ -3,7 +3,7 @@ Okta OAuth2 and OpenIdConnect:
     https://python-social-auth.readthedocs.io/en/latest/backends/okta.html
 """
 
-from urllib.parse import urljoin
+from urllib.parse import urljoin, urlparse, urlunparse
 
 from ..utils import append_slash
 from .oauth import BaseOAuth2
@@ -22,15 +22,26 @@ class OktaMixin(BaseOAuth2):
     def _url(self, path):
         return urljoin(append_slash(self.setting("API_URL")), path)
 
-    def oidc_config(self):
-        return self.get_json(
-            self._url(
-                "/.well-known/openid-configuration?client_id={}".format(
-                    self.setting("KEY")
-                )
-            )
+    def oidc_config_url(self):
+        # https://developer.okta.com/docs/reference/api/oidc/#well-known-openid-configuration
+        url = urlparse(self.api_url())
+
+        # If the URL path does not contain an authorizedServerId, we need
+        # to truncate the path in order to generate a proper openid-configuration
+        # URL.
+        if url.path == "/oauth2/":
+            url = url._replace(path="")
+
+        return urljoin(
+            urlunparse(url),
+            "./.well-known/openid-configuration?client_id={}".format(
+                self.setting("KEY")
+            ),
         )
 
+    def oidc_config(self):
+        return self.get_json(self.oidc_config_url())
+
 
 class OktaOAuth2(OktaMixin, BaseOAuth2):
     """Okta OAuth authentication backend"""


Test patch:
diff --git a/social_core/tests/backends/test_okta.py b/social_core/tests/backends/test_okta.py
index 7635b38a..a3191ec8 100644
--- a/social_core/tests/backends/test_okta.py
+++ b/social_core/tests/backends/test_okta.py
@@ -187,3 +187,24 @@ class OktaOpenIdConnectTest(OpenIdConnectTest):
 
     def test_everything_works(self):
         self.do_login()
+
+    def test_okta_oidc_config(self):
+        # With no custom authorization server
+        self.strategy.set_settings(
+            {
+                "SOCIAL_AUTH_OKTA_OPENIDCONNECT_API_URL": "https://dev-000000.oktapreview.com/oauth2",
+            }
+        )
+        self.assertEqual(
+            self.backend.oidc_config_url(),
+            "https://dev-000000.oktapreview.com/.well-known/openid-configuration?client_id=a-key",
+        )
+        self.strategy.set_settings(
+            {
+                "SOCIAL_AUTH_OKTA_OPENIDCONNECT_API_URL": "https://dev-000000.oktapreview.com/oauth2/id-123456",
+            }
+        )
+        self.assertEqual(
+            self.backend.oidc_config_url(),
+            "https://dev-000000.oktapreview.com/oauth2/id-123456/.well-known/openid-configuration?client_id=a-key",
+        )