Skip to content

Commit f9fcaff

Browse files
committed
feat: idp/rp test apps
Implement a minimal testing IDP and RP for maintainers. There is a single Application configured in the IDP for the RP sample application it used the OIDC Authorization + PKCE flow. This is a meant to be a starting point for building out further test scenarios.
1 parent 641ab0b commit f9fcaff

26 files changed

+2339
-0
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,3 +51,6 @@ _build
5151

5252
/venv/
5353
/coverage.xml
54+
55+
db.sqlite3
56+
venv/

tests/app/README.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Test Apps
2+
3+
These apps are for local end to end testing of DOT features. They were implemented to save maintainers the trouble of setting up
4+
local test environments.
5+
6+
## /tests/app/idp
7+
8+
This is an example IDP implementation for end to end testing. There are pre-configured fixtures which will work with the sample RP.
9+
10+
username: superuser
11+
password: password
12+
13+
### Development Tasks
14+
15+
* starting up the idp
16+
17+
```bash
18+
cd tests/app/idp
19+
# create a virtual env if that is something you do
20+
python manage.py migrate
21+
python manage.py loaddata fixtures/seed.json
22+
python manage.py runserver
23+
# open http://localhost:8000/admin
24+
25+
```
26+
27+
* update fixtures
28+
29+
You can update data in the IDP and then dump the data to a new seed file as follows.
30+
31+
```
32+
python -Xutf8 ./manage.py dumpdata -e sessions -e admin.logentry -e auth.permission -e contenttypes.contenttype --natural-foreign --natural-primary --indent 2 > fixtures/seed.json
33+
```
34+
35+
## /test/app/rp
36+
37+
This is an example RP. It is a SPA built with Svelte.
38+
39+
### Development Tasks
40+
41+
* starting the RP
42+
43+
```bash
44+
cd test/apps/rp
45+
npm install
46+
npm run dev
47+
# open http://localhost:5173
48+
```

tests/app/idp/README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# TEST IDP
2+
3+
This is an example IDP implementation for end to end testing.
4+
5+
username: superuser
6+
password: password
7+
8+
## Development Tasks
9+
10+
* update fixtures
11+
12+
```
13+
python -Xutf8 ./manage.py dumpdata -e sessions -e admin.logentry -e auth.permission -e contenttypes.contenttype -e oauth2_provider.grant -e oauth2_provider.accesstoken -e oauth2_provider.refreshtoken -e oauth2_provider.idtoken --natural-foreign --natural-primary --indent 2 > fixtures/seed.json
14+
```
15+
16+
*check seeds as you produce them to makre sure any unrequired models are excluded to keep our seeds as small as possible.*

tests/app/idp/fixtures/seed.json

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
[
2+
{
3+
"model": "auth.user",
4+
"fields": {
5+
"password": "pbkdf2_sha256$390000$29LoVHfFRlvEOJ9clv73Wx$fx5ejfUJ+nYsnBXFf21jZvDsq4o3p5io3TrAGKAVTq4=",
6+
"last_login": "2023-10-05T14:39:15.980Z",
7+
"is_superuser": true,
8+
"username": "superuser",
9+
"first_name": "",
10+
"last_name": "",
11+
"email": "",
12+
"is_staff": true,
13+
"is_active": true,
14+
"date_joined": "2023-05-01T19:53:59.622Z",
15+
"groups": [],
16+
"user_permissions": []
17+
}
18+
},
19+
{
20+
"model": "oauth2_provider.application",
21+
"fields": {
22+
"client_id": "2EIxgjlyy5VgCp2fjhEpKLyRtSMMPK0hZ0gBpNdm",
23+
"user": null,
24+
"redirect_uris": "http://localhost:5173\r\nhttp://127.0.0.1:5173",
25+
"post_logout_redirect_uris": "http://localhost:5173\r\nhttp://127.0.0.1:5173",
26+
"client_type": "public",
27+
"authorization_grant_type": "authorization-code",
28+
"client_secret": "pbkdf2_sha256$600000$HEYByn6WXiQUI1D6ezTnAf$qPLekt0t3ZssnzEOvQkeOSfxx7tbs/gcC3O0CthtP2A=",
29+
"hash_client_secret": true,
30+
"name": "OIDC - Authorization Code",
31+
"skip_authorization": true,
32+
"created": "2023-05-01T20:27:46.167Z",
33+
"updated": "2023-05-11T16:37:21.669Z",
34+
"algorithm": "RS256"
35+
}
36+
}
37+
]

tests/app/idp/idp/__init__.py

Whitespace-only changes.

tests/app/idp/idp/asgi.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""
2+
ASGI config for idp project.
3+
4+
It exposes the ASGI callable as a module-level variable named ``application``.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
8+
"""
9+
10+
import os
11+
12+
from django.core.asgi import get_asgi_application
13+
14+
15+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "idp.settings")
16+
17+
application = get_asgi_application()

tests/app/idp/idp/settings.py

Lines changed: 214 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,214 @@
1+
"""
2+
Django settings for idp project.
3+
4+
Generated by 'django-admin startproject' using Django 4.2.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/4.2/topics/settings/
8+
9+
For the full list of settings and their values, see
10+
https://docs.djangoproject.com/en/4.2/ref/settings/
11+
"""
12+
13+
from pathlib import Path
14+
15+
16+
# Build paths inside the project like this: BASE_DIR / 'subdir'.
17+
BASE_DIR = Path(__file__).resolve().parent.parent
18+
19+
20+
# Quick-start development settings - unsuitable for production
21+
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
22+
23+
# SECURITY WARNING: keep the secret key used in production secret!
24+
SECRET_KEY = "django-insecure-vri27@j_q62e2it4$xiy9ca!7@qgjkhhan(*zs&lz0k@yukbb3"
25+
26+
# SECURITY WARNING: don't run with debug turned on in production!
27+
DEBUG = True
28+
29+
ALLOWED_HOSTS = []
30+
31+
32+
# Application definition
33+
34+
INSTALLED_APPS = [
35+
"django.contrib.admin",
36+
"django.contrib.auth",
37+
"django.contrib.contenttypes",
38+
"django.contrib.sessions",
39+
"django.contrib.messages",
40+
"django.contrib.staticfiles",
41+
"oauth2_provider",
42+
"corsheaders",
43+
]
44+
45+
MIDDLEWARE = [
46+
"django.middleware.security.SecurityMiddleware",
47+
"django.contrib.sessions.middleware.SessionMiddleware",
48+
"corsheaders.middleware.CorsMiddleware",
49+
"django.middleware.common.CommonMiddleware",
50+
"django.middleware.csrf.CsrfViewMiddleware",
51+
"django.contrib.auth.middleware.AuthenticationMiddleware",
52+
"django.contrib.messages.middleware.MessageMiddleware",
53+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
54+
]
55+
56+
ROOT_URLCONF = "idp.urls"
57+
58+
TEMPLATES = [
59+
{
60+
"BACKEND": "django.template.backends.django.DjangoTemplates",
61+
"DIRS": [BASE_DIR / "templates"],
62+
"APP_DIRS": True,
63+
"OPTIONS": {
64+
"context_processors": [
65+
"django.template.context_processors.debug",
66+
"django.template.context_processors.request",
67+
"django.contrib.auth.context_processors.auth",
68+
"django.contrib.messages.context_processors.messages",
69+
],
70+
},
71+
},
72+
]
73+
74+
WSGI_APPLICATION = "idp.wsgi.application"
75+
76+
77+
# Database
78+
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
79+
80+
DATABASES = {
81+
"default": {
82+
"ENGINE": "django.db.backends.sqlite3",
83+
"NAME": BASE_DIR / "db.sqlite3",
84+
}
85+
}
86+
87+
88+
# Password validation
89+
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
90+
91+
AUTH_PASSWORD_VALIDATORS = [
92+
{
93+
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
94+
},
95+
{
96+
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
97+
},
98+
{
99+
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
100+
},
101+
{
102+
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
103+
},
104+
]
105+
106+
107+
# Internationalization
108+
# https://docs.djangoproject.com/en/4.2/topics/i18n/
109+
110+
LANGUAGE_CODE = "en-us"
111+
112+
TIME_ZONE = "UTC"
113+
114+
USE_I18N = True
115+
116+
USE_TZ = True
117+
118+
119+
# Static files (CSS, JavaScript, Images)
120+
# https://docs.djangoproject.com/en/4.2/howto/static-files/
121+
122+
STATIC_URL = "static/"
123+
124+
# Default primary key field type
125+
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
126+
127+
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
128+
129+
OAUTH2_PROVIDER = {
130+
"OIDC_ENABLED": True,
131+
"OIDC_RP_INITIATED_LOGOUT_ENABLED": True,
132+
# this key is just for out test app, you should never store a key like this in a production environment.
133+
"OIDC_RSA_PRIVATE_KEY": """
134+
-----BEGIN RSA PRIVATE KEY-----
135+
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
184+
-----END RSA PRIVATE KEY-----
185+
""",
186+
"SCOPES": {
187+
"openid": "OpenID Connect scope",
188+
},
189+
}
190+
191+
# just for this example
192+
CORS_ORIGIN_ALLOW_ALL = True
193+
194+
LOGGING = {
195+
"version": 1,
196+
"disable_existing_loggers": False,
197+
"handlers": {
198+
"console": {
199+
"class": "logging.StreamHandler",
200+
},
201+
},
202+
"root": {
203+
"handlers": ["console"],
204+
"level": "WARNING",
205+
},
206+
"loggers": {
207+
# log oauth2_provider issues to facilitate troubleshooting
208+
"oauth2_provider": {
209+
"handlers": ["console"],
210+
"level": "DEBUG",
211+
"propagate": False,
212+
},
213+
},
214+
}

tests/app/idp/idp/urls.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
"""
2+
URL configuration for idp project.
3+
4+
The `urlpatterns` list routes URLs to views. For more information please see:
5+
https://docs.djangoproject.com/en/4.2/topics/http/urls/
6+
Examples:
7+
Function views
8+
1. Add an import: from my_app import views
9+
2. Add a URL to urlpatterns: path('', views.home, name='home')
10+
Class-based views
11+
1. Add an import: from other_app.views import Home
12+
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
13+
Including another URLconf
14+
1. Import the include() function: from django.urls import include, path
15+
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
16+
"""
17+
from django.contrib import admin
18+
from django.urls import include, path
19+
20+
21+
urlpatterns = [
22+
path("admin/", admin.site.urls),
23+
path("o/", include("oauth2_provider.urls", namespace="oauth2_provider")),
24+
path("accounts/", include("django.contrib.auth.urls")),
25+
]

tests/app/idp/idp/wsgi.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
"""
2+
WSGI config for idp project.
3+
4+
It exposes the WSGI callable as a module-level variable named ``application``.
5+
6+
For more information on this file, see
7+
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
8+
"""
9+
10+
import os
11+
12+
from django.core.wsgi import get_wsgi_application
13+
14+
15+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "idp.settings")
16+
17+
application = get_wsgi_application()

0 commit comments

Comments
 (0)