diff --git a/.coveragerc b/.coveragerc deleted file mode 100644 index 4e6fce89..00000000 --- a/.coveragerc +++ /dev/null @@ -1,24 +0,0 @@ -[run] -source=. - -[report] -fail_under=100 - -exclude_lines = - pragma: no cover - pragma: todo cover - def __str__ - def __unicode__ - def __repr__ - -omit= - */migrations/* - */apps.py - */admin.py - manage.py - timed/settings_*.py - timed/wsgi.py - timed/forms.py - setup.py - -show_missing = True diff --git a/.isort.cfg b/.isort.cfg deleted file mode 100644 index 3a892b0b..00000000 --- a/.isort.cfg +++ /dev/null @@ -1,9 +0,0 @@ -[settings] -skip=migrations,snapshots -known_first_party=timed -known_third_party=pytest_factoryboy -multi_line_output=3 -include_trailing_comma=True -force_grid_wrap=0 -combine_as_imports=True -line_length=88 diff --git a/README.md b/README.md index 78413a68..5e853472 100644 --- a/README.md +++ b/README.md @@ -42,37 +42,52 @@ To get the application working locally for development, make sure to create a fi ENV=dev ``` +If you have existing users from the previous LDAP authentication, you want to add this line as well: + +``` +DJANGO_OIDC_USERNAME_CLAIM=preferred_username +``` + ## Configuration Following options can be set as environment variables to configure Timed backend in documented [format](https://github.com/joke2k/django-environ#supported-types) according to type. -| Parameter | Description | Default | -| ----------------------------------- | ----------------------------------------------------- | ------------------- | -| `DJANGO_ENV_FILE` | Path to setup environment vars in a file | .env | -| `DJANGO_DEBUG` | Boolean that turns on/off debug mode | False | -| `DJANGO_SECRET_KEY` | Secret key for cryptographic signing | not set (required) | -| `DJANGO_ALLOWED_HOSTS` | List of hosts representing the host/domain names | not set (required) | -| `DJANGO_HOST_PROTOCOL` | Protocol host is running on (http or https) | http | -| `DJANGO_HOST_DOMAIN` | Main host name server is reachable on | not set (required) | -| `DJANGO_DATABASE_NAME` | Database name | timed | -| `DJANGO_DATABASE_USER` | Database username | timed | -| `DJANGO_DATABASE_HOST` | Database hostname | localhost | -| `DJANGO_DATABASE_PORT` | Database port | 5432 | -| `DJANGO_AUTH_LDAP_ENABLED` | Enable LDAP authentication | False | -| `DJANGO_AUTH_LDAP_SERVER_URI` | uri of LDAP server | not set | -| `DJANGO_AUTH_LDAP_BIND_DN` | distinguished name to use when binding to LDAP server | not set | -| `DJANGO_AUTH_LDAP_PASSWORD` | password to use with DJANGO_AUTH_LDAP_BIND_DN | not set | -| `DJANGO_AUTH_LDAP_USER_DN_TEMPLATE` | template to distinguish user’s username | not set | -| `EMAIL_URL` | Uri of email server | smtp://localhost:25 | -| `DJANGO_DEFAULT_FROM_EMAIL` | Default email address to use for various responses | webmaster@localhost | -| `DJANGO_SERVER_EMAIL` | Email address error messages are sent from | root@localhost | -| `DJANGO_ADMINS` | List of people who get error notifications | not set | -| `DJANGO_WORK_REPORT_PATH` | Path of custom work report template | not set | -| `UWSGI_INI` | Path to uwsgi.ini configuration | /app/uwsgi.ini | -| `UWSGI_MAX_REQUESTS` | uWSGI max requests | 2000 | -| `UWSGI_HARAKIRI` | uWSGI harakiri (request timeout) | 5 | -| `UWSGI_PROCESSES` | uWSGI number of processes | 4 | +| Parameter | Description | Default | +|----------------------------------------------|-----------------------------------------------------------------------------------------|--------------------------------------------------------------| +| `DJANGO_ENV_FILE` | Path to setup environment vars in a file | .env | +| `DJANGO_DEBUG` | Boolean that turns on/off debug mode | False | +| `DJANGO_SECRET_KEY` | Secret key for cryptographic signing | not set (required) | +| `DJANGO_ALLOWED_HOSTS` | List of hosts representing the host/domain names | not set (required) | +| `DJANGO_HOST_PROTOCOL` | Protocol host is running on (http or https) | http | +| `DJANGO_HOST_DOMAIN` | Main host name server is reachable on | not set (required) | +| `DJANGO_DATABASE_NAME` | Database name | timed | +| `DJANGO_DATABASE_USER` | Database username | timed | +| `DJANGO_DATABASE_HOST` | Database hostname | localhost | +| `DJANGO_DATABASE_PORT` | Database port | 5432 | +| `DJANGO_OIDC_DEFAULT_BASE_URL` | Base URL of the OIDC provider | http://timed.local/auth/realms/timed/protocol/openid-connect | +| `DJANGO_OIDC_OP_USER_ENDPOINT` | OIDC /userinfo endpoint | {`DJANGO_OIDC_DEFAULT_BASE_URL`}/userinfo | +| `DJANGO_OIDC_VERIFY_SSL` | Verify SSL on OIDC request | dev: False, prod: True | +| `DJANGO_OIDC_CREATE_USER` | Create new user if it doesn't exist in the database | False | +| `DJANGO_OIDC_USERNAME_CLAIM` | Username token claim for user lookup / creation | sub | +| `DJANGO_OIDC_EMAIL_CLAIM` | Email token claim for creating new users (if `DJANGO_OIDC_CREATE_USER` is enabled) | email | +| `DJANGO_OIDC_FIRSTNAME_CLAIM` | First name token claim for creating new users (if `DJANGO_OIDC_CREATE_USER` is enabled) | given_name | +| `DJANGO_OIDC_LASTNAME_CLAIM` | Last name token claim for creating new users (if `DJANGO_OIDC_CREATE_USER` is enabled) | family_name | +| `DJANGO_OIDC_BEARER_TOKEN_REVALIDATION_TIME` | Time (in seconds) to cache a bearer token before revalidation is needed | 60 | +| `DJANGO_OIDC_CHECK_INTROSPECT` | Use token introspection for confidential clients | True | +| `DJANGO_OIDC_INTROSPECT_ENDPOINT` | OIDC token introspection endpoint (if `DJANGO_OIDC_CHECK_INTROSPECT` is enabled) | {`DJANGO_OIDC_DEFAULT_BASE_URL`}/token/introspect | +| `DJANGO_OIDC_CLIENT_ID` | OIDC client secret (if `DJANGO_OIDC_CHECK_INTROSPECT` is enabled) | not set | +| `DJANGO_OIDC_CLIENT_SECRET` | OIDC client secret (if `DJANGO_OIDC_CHECK_INTROSPECT` is enabled) | not set | +| `EMAIL_URL` | Uri of email server | smtp://localhost:25 | +| `DJANGO_DEFAULT_FROM_EMAIL` | Default email address to use for various responses | webmaster@localhost | +| `DJANGO_SERVER_EMAIL` | Email address error messages are sent from | root@localhost | +| `DJANGO_ADMINS` | List of people who get error notifications | not set | +| `DJANGO_WORK_REPORT_PATH` | Path of custom work report template | not set | +| `UWSGI_INI` | Path to uwsgi.ini configuration | /app/uwsgi.ini | +| `UWSGI_MAX_REQUESTS` | uWSGI max requests | 2000 | +| `UWSGI_HARAKIRI` | uWSGI harakiri (request timeout) | 5 | +| `UWSGI_PROCESSES` | uWSGI number of processes | 4 | + ## Contributing diff --git a/.flake8 b/setup.cfg similarity index 57% rename from .flake8 rename to setup.cfg index 6cc391dc..72eceaca 100644 --- a/.flake8 +++ b/setup.cfg @@ -31,3 +31,38 @@ ignore = max-line-length = 80 exclude = migrations snapshots max-complexity = 10 + +[tool:isort] +skip=migrations,snapshots +known_first_party=timed +known_third_party=pytest_factoryboy +multi_line_output=3 +include_trailing_comma=True +force_grid_wrap=0 +combine_as_imports=True +line_length=88 + +[tool:pytest] +addopts = -n auto --reuse-db --randomly-seed=1521188766 --randomly-dont-reorganize + +[coverage:run] +source=. + +[coverage:report] +fail_under=100 +exclude_lines = + pragma: no cover + pragma: todo cover + def __str__ + def __unicode__ + def __repr__ +omit= + */migrations/* + */apps.py + */admin.py + manage.py + timed/settings_*.py + timed/wsgi.py + timed/forms.py + setup.py +show_missing = True diff --git a/timed/authentication.py b/timed/authentication.py index e0de2b60..5f535366 100644 --- a/timed/authentication.py +++ b/timed/authentication.py @@ -15,7 +15,7 @@ def get_introspection(self, access_token, id_token, payload): """Return user details dictionary.""" basic = base64.b64encode( - f"{settings.OIDC_OP_INTROSPECT_CLIENT_ID}:{settings.OIDC_OP_INTROSPECT_CLIENT_SECRET}".encode( + f"{settings.OIDC_RP_CLIENT_ID}:{settings.OIDC_RP_CLIENT_SECRET}".encode( "utf-8" ) ).decode() diff --git a/timed/settings.py b/timed/settings.py index 14aff22e..c00909e4 100644 --- a/timed/settings.py +++ b/timed/settings.py @@ -205,36 +205,36 @@ def default(default_dev=env.NOTSET, default_prod=env.NOTSET): # OIDC -OIDC_DEFAULT_BASE_URL = "http://timed.local/auth/realms/timed/protocol/openid-connect" +OIDC_DEFAULT_BASE_URL = env.str( + "DJANGO_OIDC_DEFAULT_BASE_URL", + default="http://timed.local/auth/realms/timed/protocol/openid-connect", +) + +# not needed in timed-backend +OIDC_OP_TOKEN_ENDPOINT = f"{OIDC_DEFAULT_BASE_URL}/token" OIDC_OP_USER_ENDPOINT = env.str( - "OIDC_USERINFO_ENDPOINT", default=default(f"{OIDC_DEFAULT_BASE_URL}/userinfo") -) -OIDC_OP_TOKEN_ENDPOINT = env.str( - "OIDC_TOKEN_ENDPOINT", default=default(f"{OIDC_DEFAULT_BASE_URL}/token") + "DJANGO_OIDC_USERINFO_ENDPOINT", default=f"{OIDC_DEFAULT_BASE_URL}/userinfo" ) -OIDC_RP_CLIENT_ID = env.str("OIDC_CLIENT_ID", default=None) -OIDC_RP_CLIENT_SECRET = env.str("OIDC_CLIENT_SECRET", default=None) -OIDC_VERIFY_SSL = env.bool("OIDC_VERIFY_SSL", default=default(False, True)) -OIDC_CREATE_USER = env.bool("OIDC_CREATE_USER", default=False) - -OIDC_USERNAME_CLAIM = env.str("OIDC_USERNAME_CLAIM", default="preferred_username") -OIDC_EMAIL_CLAIM = env.str("OIDC_EMAIL_CLAIM", default="email") -OIDC_FIRSTNAME_CLAIM = env.str("OIDC_FIRSTNAME_CLAIM", default="given_name") -OIDC_LASTNAME_CLAIM = env.str("OIDC_LASTNAME_CLAIM", default="family_name") +OIDC_VERIFY_SSL = env.bool("DJANGO_OIDC_VERIFY_SSL", default=default(False, True)) +OIDC_CREATE_USER = env.bool("DJANGO_OIDC_CREATE_USER", default=False) + +OIDC_USERNAME_CLAIM = env.str("DJANGO_OIDC_USERNAME_CLAIM", default="sub") +OIDC_EMAIL_CLAIM = env.str("DJANGO_OIDC_EMAIL_CLAIM", default="email") +OIDC_FIRSTNAME_CLAIM = env.str("DJANGO_OIDC_FIRSTNAME_CLAIM", default="given_name") +OIDC_LASTNAME_CLAIM = env.str("DJANGO_OIDC_LASTNAME_CLAIM", default="family_name") # time in seconds OIDC_BEARER_TOKEN_REVALIDATION_TIME = env.int( - "OIDC_BEARER_TOKEN_REVALIDATION_TIME", default=60 + "DJANGO_OIDC_BEARER_TOKEN_REVALIDATION_TIME", default=60 ) -OIDC_CHECK_INTROSPECT = env.bool("OIDC_CHECK_INTROSPECT", default=True) +# for checking confidential client authentication +OIDC_CHECK_INTROSPECT = env.bool("DJANGO_OIDC_CHECK_INTROSPECT", default=True) OIDC_OP_INTROSPECT_ENDPOINT = env.str( - "OIDC_INTROSPECT_ENDPOINT", - default=default(f"{OIDC_DEFAULT_BASE_URL}/token/introspect"), -) -OIDC_OP_INTROSPECT_CLIENT_ID = env.str("OIDC_INTROSPECT_CLIENT_ID", default=None) -OIDC_OP_INTROSPECT_CLIENT_SECRET = env.str( - "OIDC_INTROSPECT_CLIENT_SECRET", default=None + "DJANGO_OIDC_INTROSPECT_ENDPOINT", + default=f"{OIDC_DEFAULT_BASE_URL}/token/introspect", ) +OIDC_RP_CLIENT_ID = env.str("DJANGO_OIDC_CLIENT_ID", default=None) +OIDC_RP_CLIENT_SECRET = env.str("DJANGO_OIDC_CLIENT_SECRET", default=None) # Email definition diff --git a/timed/tests/test_authentication.py b/timed/tests/test_authentication.py index a50fa19a..d3952ae1 100644 --- a/timed/tests/test_authentication.py +++ b/timed/tests/test_authentication.py @@ -33,11 +33,11 @@ def test_authentication( requests_mock, settings, ): - userinfo = {"preferred_username": "1"} + userinfo = {"sub": "1"} requests_mock.get(settings.OIDC_OP_USER_ENDPOINT, text=json.dumps(userinfo)) if not is_id_token: - userinfo = {"client_id": "test_client", "preferred_username": "1"} + userinfo = {"client_id": "test_client", "sub": "1"} requests_mock.get( settings.OIDC_OP_USER_ENDPOINT, status_code=status.HTTP_401_UNAUTHORIZED ) @@ -72,7 +72,7 @@ def test_authentication_new_user( user_model = get_user_model() assert user_model.objects.filter(username=username).count() == 0 - userinfo = {"preferred_username": username} + userinfo = {"sub": username} requests_mock.get(settings.OIDC_OP_USER_ENDPOINT, text=json.dumps(userinfo)) request = rf.get("/openid", HTTP_AUTHORIZATION="Bearer Token")