1
0
Fork 0
mirror of https://github.com/mealie-recipes/mealie.git synced 2025-07-23 15:19:41 +02:00

feat: Login with OAuth via OpenID Connect (OIDC) (#3280)
Some checks are pending
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Docker Nightly Production / Frontend and End-to-End Tests (push) Waiting to run
Docker Nightly Production / Build Tagged Release (push) Blocked by required conditions
Docker Nightly Production / Backend Server Tests (push) Waiting to run
Docker Nightly Production / Notify Discord (push) Blocked by required conditions

* initial oidc implementation

* add dynamic scheme

* e2e test setup

* add caching

* fix

* try this

* add libldap-2.5 to runtime dependencies (#2849)

* New translations en-us.json (Norwegian) (#2851)

* New Crowdin updates (#2855)

* New translations en-us.json (Italian)

* New translations en-us.json (Norwegian)

* New translations en-us.json (Portuguese)

* fix

* remove cache

* cache yarn deps

* cache docker image

* cleanup action

* lint

* fix tests

* remove not needed variables

* run code gen

* fix tests

* add docs

* move code into custom scheme

* remove unneeded type

* fix oidc admin

* add more tests

* add better spacing on login page

* create auth providers

* clean up testing stuff

* type fixes

* add OIDC auth method to postgres enum

* add option to bypass login screen and go directly to iDP

* remove check so we can fallback to another auth method oauth fails

* Add provider name to be shown at the login screen

* add new properties to admin about api

* fix spec

* add a prompt to change auth method when changing password

* Create new auth section. Add more info on auth methods

* update docs

* run ruff

* update docs

* format

* docs gen

* formatting

* initialize logger in class

* mypy type fixes

* docs gen

* add models to get proper fields in docs and fix serialization

* validate id token before using it

* only request a mealie token on initial callback

* remove unused method

* fix unit tests

* docs gen

* check for valid idToken before getting token

* add iss to mealie token

* check to see if we already have a mealie token before getting one

* fix lock file

* update authlib

* update lock file

* add remember me environment variable

* add user group setting to allow only certain groups to log in

---------

Co-authored-by: Carter Mintey <cmintey8@gmail.com>
Co-authored-by: Carter <35710697+cmintey@users.noreply.github.com>
This commit is contained in:
Hayden 2024-03-10 13:51:36 -05:00 committed by GitHub
parent bea1a592d7
commit 5f6844eceb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
53 changed files with 1533 additions and 400 deletions

View file

@ -6,8 +6,11 @@ from pytest import MonkeyPatch
from mealie.core import security
from mealie.core.config import get_app_settings
from mealie.core.dependencies import validate_file_token
from mealie.core.security.providers.credentials_provider import CredentialsProvider, CredentialsRequest
from mealie.core.security.providers.ldap_provider import LDAPProvider
from mealie.db.db_setup import session_context
from mealie.db.models.users.users import AuthMethod
from mealie.schema.user.auth import CredentialsRequestForm
from mealie.schema.user.user import PrivateUser
from tests.utils import random_string
@ -113,6 +116,11 @@ def test_create_file_token():
assert file_path == validate_file_token(file_token)
def get_provider(session, username: str, password: str):
request_data = CredentialsRequest(username=username, password=password)
return LDAPProvider(session, request_data)
def test_ldap_user_creation(monkeypatch: MonkeyPatch):
user, mail, name, password, query_bind, query_password = setup_env(monkeypatch)
@ -125,7 +133,8 @@ def test_ldap_user_creation(monkeypatch: MonkeyPatch):
get_app_settings.cache_clear()
with session_context() as session:
result = security.authenticate_user(session, user, password)
provider = get_provider(session, user, password)
result = provider.get_user()
assert result
assert result.username == user
@ -146,9 +155,10 @@ def test_ldap_user_creation_fail(monkeypatch: MonkeyPatch):
get_app_settings.cache_clear()
with session_context() as session:
result = security.authenticate_user(session, user, password + "a")
provider = get_provider(session, user, password + "a")
result = provider.get_user()
assert result is False
assert result is None
def test_ldap_user_creation_non_admin(monkeypatch: MonkeyPatch):
@ -164,7 +174,8 @@ def test_ldap_user_creation_non_admin(monkeypatch: MonkeyPatch):
get_app_settings.cache_clear()
with session_context() as session:
result = security.authenticate_user(session, user, password)
provider = get_provider(session, user, password)
result = provider.get_user()
assert result
assert result.username == user
@ -186,7 +197,8 @@ def test_ldap_user_creation_admin(monkeypatch: MonkeyPatch):
get_app_settings.cache_clear()
with session_context() as session:
result = security.authenticate_user(session, user, password)
provider = get_provider(session, user, password)
result = provider.get_user()
assert result
assert result.username == user
@ -198,35 +210,17 @@ def test_ldap_user_creation_admin(monkeypatch: MonkeyPatch):
def test_ldap_disabled(monkeypatch: MonkeyPatch):
monkeypatch.setenv("LDAP_AUTH_ENABLED", "False")
user = random_string(10)
password = random_string(10)
class LdapConnMock:
def simple_bind_s(self, dn, bind_pw):
assert False # When LDAP is disabled, this method should not be called
def search_s(self, dn, scope, filter, attrlist):
pass
def set_option(self, option, invalue):
pass
def unbind_s(self):
pass
def start_tls_s(self):
pass
def ldap_initialize_mock(url):
assert url == ""
return LdapConnMock()
monkeypatch.setattr(ldap, "initialize", ldap_initialize_mock)
class Request:
def __init__(self, auth_strategy: str):
self.cookies = {"mealie.auth.strategy": auth_strategy}
get_app_settings.cache_clear()
with session_context() as session:
security.authenticate_user(session, user, password)
form = CredentialsRequestForm("username", "password", False)
provider = security.get_auth_provider(session, Request("local"), form)
assert isinstance(provider, CredentialsProvider)
def test_user_login_ldap_auth_method(monkeypatch: MonkeyPatch, ldap_user: PrivateUser):
@ -245,7 +239,8 @@ def test_user_login_ldap_auth_method(monkeypatch: MonkeyPatch, ldap_user: Privat
get_app_settings.cache_clear()
with session_context() as session:
result = security.authenticate_user(session, ldap_user.username, ldap_password)
provider = get_provider(session, ldap_user.username, ldap_password)
result = provider.get_user()
assert result
assert result.username == ldap_user.username