Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add scim package #187

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 63 additions & 63 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,15 @@
"line_number": 20
}
],
"conftest.py": [
{
"type": "Secret Keyword",
"filename": "conftest.py",
"hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4",
"is_verified": false,
"line_number": 123
}
],
"docker-compose.yml": [
{
"type": "Secret Keyword",
Expand Down Expand Up @@ -205,119 +214,110 @@
"line_number": 19
}
],
"tests/conftest.py": [
"testapp/main/settings/dev.py": [
{
"type": "Secret Keyword",
"filename": "tests/conftest.py",
"hashed_secret": "e5e9fa1ba31ecd1ae84f75caaa474f3a663f05f4",
"filename": "testapp/main/settings/dev.py",
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
"is_verified": false,
"line_number": 120
"line_number": 17
}
],
"tests/mitol/common/utils/test_urls.py": [
"testapp/main/settings/example.dev.py": [
{
"type": "Basic Auth Credentials",
"filename": "tests/mitol/common/utils/test_urls.py",
"hashed_secret": "9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684",
"type": "Secret Keyword",
"filename": "testapp/main/settings/example.dev.py",
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
"is_verified": false,
"line_number": 18
"line_number": 14
}
],
"tests/mitol/digitalcredentials/test_backend.py": [
"testapp/main/settings/shared.py": [
{
"type": "Secret Keyword",
"filename": "tests/mitol/digitalcredentials/test_backend.py",
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
"filename": "testapp/main/settings/shared.py",
"hashed_secret": "8f2581750096043a1c68bedea8cfa6e13ad1a2e4",
"is_verified": false,
"line_number": 150
}
],
"tests/mitol/digitalcredentials/test_requests_utils.py": [
"line_number": 41
},
{
"type": "Base64 High Entropy String",
"filename": "tests/mitol/digitalcredentials/test_requests_utils.py",
"hashed_secret": "00df9aef8d11911143efbe3abdbd640d3d18cf06",
"type": "Basic Auth Credentials",
"filename": "testapp/main/settings/shared.py",
"hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3",
"is_verified": false,
"line_number": 60
}
],
"tests/mitol/google_sheets/test_api.py": [
"line_number": 125
},
{
"type": "Secret Keyword",
"filename": "tests/mitol/google_sheets/test_api.py",
"hashed_secret": "a0281cd072cea8e80e7866b05dc124815760b6c9",
"filename": "testapp/main/settings/shared.py",
"hashed_secret": "9bc34549d565d9505b287de0cd20ac77be1d3f2c",
"is_verified": false,
"line_number": 46
"line_number": 205
}
],
"tests/mitol/google_sheets/test_utils.py": [
"testapp/main/settings/test.py": [
{
"type": "Secret Keyword",
"filename": "tests/mitol/google_sheets/test_utils.py",
"hashed_secret": "43ed4c2d8375dfc89e3dc8c917f404b9481d355b",
"filename": "testapp/main/settings/test.py",
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
"is_verified": false,
"line_number": 15
"line_number": 9
}
],
"tests/mitol/google_sheets/test_views.py": [
"tests/common/utils/test_urls.py": [
{
"type": "Secret Keyword",
"filename": "tests/mitol/google_sheets/test_views.py",
"hashed_secret": "43ed4c2d8375dfc89e3dc8c917f404b9481d355b",
"type": "Basic Auth Credentials",
"filename": "tests/common/utils/test_urls.py",
"hashed_secret": "9d4e1e23bd5b727046a9e3b4b7db57bd8d6ee684",
"is_verified": false,
"line_number": 29
"line_number": 18
}
],
"tests/testapp/settings/dev.py": [
"tests/digitalcredentials/test_backend.py": [
{
"type": "Secret Keyword",
"filename": "tests/testapp/settings/dev.py",
"filename": "tests/digitalcredentials/test_backend.py",
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
"is_verified": false,
"line_number": 17
"line_number": 150
}
],
"tests/testapp/settings/example.dev.py": [
"tests/digitalcredentials/test_requests_utils.py": [
{
"type": "Secret Keyword",
"filename": "tests/testapp/settings/example.dev.py",
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
"type": "Base64 High Entropy String",
"filename": "tests/digitalcredentials/test_requests_utils.py",
"hashed_secret": "00df9aef8d11911143efbe3abdbd640d3d18cf06",
"is_verified": false,
"line_number": 14
"line_number": 60
}
],
"tests/testapp/settings/shared.py": [
"tests/google_sheets/test_api.py": [
{
"type": "Secret Keyword",
"filename": "tests/testapp/settings/shared.py",
"hashed_secret": "8f2581750096043a1c68bedea8cfa6e13ad1a2e4",
"is_verified": false,
"line_number": 40
},
{
"type": "Basic Auth Credentials",
"filename": "tests/testapp/settings/shared.py",
"hashed_secret": "afc848c316af1a89d49826c5ae9d00ed769415f3",
"filename": "tests/google_sheets/test_api.py",
"hashed_secret": "a0281cd072cea8e80e7866b05dc124815760b6c9",
"is_verified": false,
"line_number": 120
},
"line_number": 46
}
],
"tests/google_sheets/test_utils.py": [
{
"type": "Secret Keyword",
"filename": "tests/testapp/settings/shared.py",
"hashed_secret": "9bc34549d565d9505b287de0cd20ac77be1d3f2c",
"filename": "tests/google_sheets/test_utils.py",
"hashed_secret": "43ed4c2d8375dfc89e3dc8c917f404b9481d355b",
"is_verified": false,
"line_number": 200
"line_number": 15
}
],
"tests/testapp/settings/test.py": [
"tests/google_sheets/test_views.py": [
{
"type": "Secret Keyword",
"filename": "tests/testapp/settings/test.py",
"hashed_secret": "6367c48dd193d56ea7b0baad25b19455e529f5ee",
"filename": "tests/google_sheets/test_views.py",
"hashed_secret": "43ed4c2d8375dfc89e3dc8c917f404b9481d355b",
"is_verified": false,
"line_number": 9
"line_number": 29
}
]
},
"generated_at": "2025-02-25T16:15:37Z"
"generated_at": "2025-02-27T14:24:52Z"
}
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ This repository is the home of MIT Open Learning's reusable django apps.

This set of libraries is managed using [uv](https://docs.astral.sh/uv/).

### Setup

To run this app in local development mode, copy `testapp/main/settings/example.dev.py` to `testapp/main/settings/dev.py`. This file has the same defaults as `testapp/main/settings/test.py`, but it is gitignored so you can safely add secrets to it. `manage.py` and `main/wsgi.py` both load `dev.py`.


#### Use on your host system

- Install `xmlsec` native libraries for your OS: https://xmlsec.readthedocs.io/en/stable/install.html
Expand Down Expand Up @@ -63,7 +68,7 @@ To add a new one, it's easiest to copy one of the existing apps. There's one cal
* Under `[tool.uv.sources]`, add a new entry for the new app, using (again) the same format as the other entries.
4. Test building: `uv build --package mitol-django-<appname>` . (This ensures that uv is OK with your changes.)
5. Add space for the app in the `tests` app: `mkdir tests/mitol/<appname>` and add a blank `__init__.py` to it.
6. Add the app to `testapp/settings/shared.py`
6. Add the app to `testapp/main/settings/shared.py`
* You must add it to `INSTALLED_APPS`.
* If your app has configuration settings, add to the `import_settings_module` call at the top too.

Expand Down
28 changes: 27 additions & 1 deletion tests/conftest.py → conftest.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
from datetime import timedelta # noqa: INP001
import json
from contextlib import contextmanager
from datetime import timedelta
from os import environ
from pathlib import Path
from types import SimpleNamespace

import pytest
Expand Down Expand Up @@ -119,3 +122,26 @@ def google_sheets_client_creds_settings(settings):
settings.MITOL_GOOGLE_SHEETS_DRIVE_CLIENT_ID = "nhijg1i.apps.googleusercontent.com"
settings.MITOL_GOOGLE_SHEETS_DRIVE_CLIENT_SECRET = "secret" # noqa: S105
return settings


@pytest.fixture(scope="session")
def open_data_fixture_file():
"""Create a fixture that provides a function to load data fixtures"""

@contextmanager
def _open_data_fixture_file(path):
with Path.open(Path(__file__).parent / "tests/data" / path, "r") as f:
yield f

return _open_data_fixture_file


@pytest.fixture(scope="session")
def load_data_fixture_json(open_data_fixture_file):
"""Return a function that will load fixture data as json"""

def _load_data_fixture_json(path):
with open_data_fixture_file(path) as f:
return json.load(f)

return _load_data_fixture_json
17 changes: 13 additions & 4 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ dependencies = [
"mitol-django-payment_gateway",
"mitol-django-uvtestapp",
"mitol-django-transcoding",
"mitol-django-scim",
]
readme = "README.md"
requires-python = ">= 3.10"
Expand All @@ -33,11 +34,13 @@ build-backend = "hatchling.build"
managed = true
dev-dependencies = [
"GitPython",
"anys>=0.3.1",
"bumpver",
"click",
"click-log",
"cloup",
"coverage<=7.6.1",
"deepmerge>=2.0",
"dj-database-url",
"django-stubs[compatible-mypy]",
"factory-boy~=3.2",
Expand Down Expand Up @@ -85,6 +88,7 @@ mitol-django-openedx = { workspace = true }
mitol-django-payment_gateway = { workspace = true }
mitol-django-uvtestapp = { workspace = true }
mitol-django-transcoding = { workspace = true }
mitol-django-scim = { workspace = true }

[tool.hatch.build.targets.sdist]
include = ["CHANGELOG.md", "README.md", "py.typed", "**/*.py"]
Expand All @@ -97,13 +101,13 @@ exclude = ["BUILD", "pyproject.toml"]
[tool.pytest.ini_options]
addopts = "--cov . --cov-report term --cov-report html --cov-report xml --reuse-db"
norecursedirs = ".git .tox .* CVS _darcs {arch} *.egg dist"
DJANGO_SETTINGS_MODULE = "testapp.settings.test"
DJANGO_SETTINGS_MODULE = "main.settings.test"
# -- recommended but optional:
python_files = ["tests/mitol/**/test_*.py"]
pythonpath = ["src", "tests"]
python_files = ["tests/**/test_*.py"]
pythonpath = ["testapp", "src", "tests"]

[tool.django-stubs]
django_settings_module = "testapp.settings.test"
django_settings_module = "main.settings.test"

[tool.mypy]
namespace_packages = true
Expand Down Expand Up @@ -216,6 +220,11 @@ convention = "pep257"
[tool.ruff.lint.flake8-quotes]
inline-quotes = "double"


[tool.ruff.lint.flake8-tidy-imports.banned-api]
"django.contrib.auth.models.User".msg = "use get_user_model() or settings.AUTH_USER_MODEL"


[tool.ruff.lint.per-file-ignores]
"tests/**" = ["S101"]
"test_*.py" = ["S101"]
Expand Down
7 changes: 7 additions & 0 deletions src/scim/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Changelog
All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project uses date-based versioning.

<!-- scriv-insert-here -->
36 changes: 36 additions & 0 deletions src/scim/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
## SCIM

## Prerequisites

- You need the following a local [Keycloak](https://www.keycloak.org/) instance running. Note which major version you are running (should be at least 26.x).
- You should have custom user profile fields setup on your `olapps` realm:
- `fullName`: required, otherwise defaults
- `emailOptIn`: defaults

## Install the scim-for-keycloak plugin

Sign up for an account on https://scim-for-keycloak.de and follow the instructions here: https://scim-for-keycloak.de/documentation/installation/install

## Configure SCIM

In the SCIM admin console, do the following:

### Configure Remote SCIM Provider

- In django-admin, go to OAuth Toolkit and create a new access token
- Go to Remote SCIM Provider
- Click the `+` button
- Specify a base URL for your learn API backend: `http://<IP_OR_HOSTNAME>:8063/scim/v2/`
- At the bottom of the page, click "Use default configuration"
- Add a new authentication method:
- Type: Long Life Bearer Token
- Bearer Token: the access token you created above
- On the Schemas tab, edit the User schema and add these custom attributes:
- Add a `fullName` attribute and set the Custom Attribute Name to `fullName`
- Add an attribute named `emailOptIn` with the following settings:
- Type: integer
- Custom Attribute Name: `emailOptIn`
- On the Realm Assignments tab, assign to the `olapps` realm
- Go to the Synchronization tab and perform one:
- Identifier attribute: email
- Synchronization strategy: Search and Bulk
5 changes: 5 additions & 0 deletions src/scim/changelog.d/scriv.ini
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[scriv]
format = md
md_header_level = 2
entry_title_template = file: ../../scripts/scriv/entry_title.${config:format}.j2
version = literal: __init__.py: __version__
1 change: 1 addition & 0 deletions src/scim/mitol/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
__import__("pkg_resources").declare_namespace(__name__)
6 changes: 6 additions & 0 deletions src/scim/mitol/scim/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
"""mitol.scim"""

default_app_config = "mitol.scim.apps.ScimApp"

__version__ = "0.0.0"
__distributionname__ = "mitol-django-scim"
Loading
Loading