Repository: techmatters/terraso-backend

Issue:
feature: Implement OAuth authorization server
## Description

We want to set up the Terraso backend to act as an authorization server for [OAuth2](https://oauth.net/2/). It should support the following:
- [x] Typical OAuth endpoints are provided
- [x] OIDC configuration file hosted at known location
- [x] A sample application can successfully authorize against our server (in our case, Moodle)

## Additional context

- Need to check to see if Moodle supports [PKCE](https://www.oauth.com/oauth2-servers/pkce/)


Discussion:

Resolving patch:
diff --git a/terraso_backend/config/settings.py b/terraso_backend/config/settings.py
--- a/terraso_backend/config/settings.py
+++ b/terraso_backend/config/settings.py
@@ -133,6 +133,10 @@
     },
     "OAUTH2_VALIDATOR_CLASS": "apps.auth.provider.oauth_validator.TerrasoOAuth2Validator",
     "OIDC_RSA_PRIVATE_KEY": config("OAUTH_OIDC_KEY", default="").replace("\\n", "\n"),
+    # for some reason I cannot figure out, the .well-known/openid-configuration
+    # endpoints always use http:// schema
+    # easiest way to force https:// is to provide this explicitly
+    "OIDC_ISS_ENDPOINT": config("OIDC_ISS_ENDPOINT", default="https://api.terraso.org/oauth/"),
     "PKCE_REQUIRED": False,
 }
 
diff --git a/terraso_backend/config/urls.py b/terraso_backend/config/urls.py
--- a/terraso_backend/config/urls.py
+++ b/terraso_backend/config/urls.py
@@ -13,13 +13,65 @@
 # You should have received a copy of the GNU Affero General Public License
 # along with this program. If not, see https://www.gnu.org/licenses/.
 
+import oauth2_provider.views as oauth2_views
+from django.conf import settings
 from django.contrib import admin
 from django.urls import include, path
 
+# OAuth2 provider endpoints
+oauth2_endpoint_views = [
+    path("authorize/", oauth2_views.AuthorizationView.as_view(), name="authorize"),
+    path("token/", oauth2_views.TokenView.as_view(), name="token"),
+    path("revoke-token/", oauth2_views.RevokeTokenView.as_view(), name="revoke-token"),
+]
+
+if settings.OAUTH2_PROVIDER.get("OIDC_ENABLED", False):
+    # OIDC specific views
+    oauth2_endpoint_views += [
+        path(
+            ".well-known/openid-configuration/",
+            oauth2_views.ConnectDiscoveryInfoView.as_view(),
+            name="openid-configuration",
+        ),
+        path(".well-known/jwks.json", oauth2_views.JwksInfoView.as_view(), name="jwks-info"),
+        path("userinfo/", oauth2_views.UserInfoView.as_view(), name="user-info"),
+    ]
+
+if settings.DEBUG:
+    # OAuth2 Application Management endpoints
+    oauth2_endpoint_views += [
+        path("applications/", oauth2_views.ApplicationList.as_view(), name="list"),
+        path(
+            "applications/register/",
+            oauth2_views.ApplicationRegistration.as_view(),
+            name="register",
+        ),
+        path("applications/<pk>/", oauth2_views.ApplicationDetail.as_view(), name="detail"),
+        path("applications/<pk>/delete/", oauth2_views.ApplicationDelete.as_view(), name="delete"),
+        path("applications/<pk>/update/", oauth2_views.ApplicationUpdate.as_view(), name="update"),
+    ]
+
+    # OAuth2 Token Management endpoints
+    oauth2_endpoint_views += [
+        path(
+            "authorized-tokens/",
+            oauth2_views.AuthorizedTokensListView.as_view(),
+            name="authorized-token-list",
+        ),
+        path(
+            "authorized-tokens/<pk>/delete/",
+            oauth2_views.AuthorizedTokenDeleteView.as_view(),
+            name="authorized-token-delete",
+        ),
+    ]
+
+
 urlpatterns = [
     path("", include("apps.core.urls", namespace="terraso_core")),
     path("admin/", admin.site.urls),
-    path("oauth/", include("oauth2_provider.urls", namespace="oauth2_provider")),
+    path(
+        "oauth/", include((oauth2_endpoint_views, "oauth2_provider"), namespace="oauth2_provider")
+    ),
     path("auth/", include("apps.auth.urls", namespace="terraso_auth")),
     path("graphql/", include("apps.graphql.urls", namespace="terraso_graphql")),
     path("storage/", include("apps.storage.urls", namespace="terraso_storage")),


Test patch: