Skip to content

Commit

Permalink
feat: add GOOGLE_SSO_PRE_VALIDATE_CALLBACK option.
Browse files Browse the repository at this point in the history
Fixes #43
  • Loading branch information
chrismaille committed Aug 29, 2024
1 parent d0f3073 commit 8163b93
Show file tree
Hide file tree
Showing 7 changed files with 66 additions and 1 deletion.
5 changes: 5 additions & 0 deletions django_google_sso/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@
GOOGLE_SSO_AUTHENTICATION_BACKEND = getattr(
settings, "GOOGLE_SSO_AUTHENTICATION_BACKEND", None
)
GOOGLE_SSO_PRE_VALIDATE_CALLBACK = getattr(
settings,
"GOOGLE_SSO_PRE_VALIDATE_CALLBACK",
"django_google_sso.hooks.pre_validate_user",
)
GOOGLE_SSO_PRE_CREATE_CALLBACK = getattr(
settings,
"GOOGLE_SSO_PRE_CREATE_CALLBACK",
Expand Down
13 changes: 13 additions & 0 deletions django_google_sso/hooks.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,16 @@ def pre_create_user(google_user_info: dict, request: HttpRequest) -> dict | None
is always the user email.
"""
return {}


def pre_validate_user(google_user_info: dict, request: HttpRequest) -> bool:
"""
Callback function called before user is validated.
Must return a boolean to indicate if user is valid to login.
params:
google_user_info: dict containing user info received from Google.
request: HttpRequest object.
"""
return True
3 changes: 3 additions & 0 deletions django_google_sso/tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,9 @@ def client_with_session(client, settings, mocker, google_response):
settings.GOOGLE_SSO_ALLOWABLE_DOMAINS = ["example.com"]
settings.GOOGLE_SSO_PRE_LOGIN_CALLBACK = "django_google_sso.hooks.pre_login_user"
settings.GOOGLE_SSO_PRE_CREATE_CALLBACK = "django_google_sso.hooks.pre_create_user"
settings.GOOGLE_SSO_PRE_VALIDATE_CALLBACK = (
"django_google_sso.hooks.pre_validate_user"
)
importlib.reload(conf)
session = client.session
session.update({"sso_state": "foo", "sso_next_url": SECRET_PATH})
Expand Down
8 changes: 7 additions & 1 deletion django_google_sso/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,14 @@ def callback(request: HttpRequest) -> HttpResponseRedirect:
google_user_data = google.get_user_info()
user_helper = UserHelper(google_user_data, request)

# Run Pre-Validate Callback
module_path = ".".join(conf.GOOGLE_SSO_PRE_VALIDATE_CALLBACK.split(".")[:-1])
pre_validate_fn = conf.GOOGLE_SSO_PRE_VALIDATE_CALLBACK.split(".")[-1]
module = importlib.import_module(module_path)
user_is_valid = getattr(module, pre_validate_fn)(google_user_data, request)

# Check if User Info is valid to login
if not user_helper.email_is_valid:
if not user_helper.email_is_valid or not user_is_valid:
send_message(
request,
_(
Expand Down
20 changes: 20 additions & 0 deletions docs/users.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,21 @@ For staff user creation _only_, you can add all users using "*" as the value:
GOOGLE_SSO_STAFF_LIST = ["*"]
```

## Fine-tuning validation before user creation

If you need to do some custom validation _before_ user is created, you can set the
`GOOGLE_SSO_PRE_VALIDATE_CALLBACK` setting to import a custom function that will be called before the user is created.
This function will receive two arguments: the `google_user_info` dict from Google User API and `request` objects.

```python
# myapp/hooks.py
def pre_validate_user(google_info, request):
# Check some info from google_info and/or request
return True # The user can be created
```

Please note, even if this function returns `True`, the user can be denied if their email is not valid.

## Fine-tuning users before creation

If you need to do some processing _before_ user is created, you can set the
Expand Down Expand Up @@ -106,6 +121,11 @@ GOOGLE_SSO_PRE_LOGIN_CALLBACK = "myapp.hooks.pre_login_user"
Please remember this function will be invoked only if user exists, and if it is active.
In other words, if the user is eligible for login.

!!! tip "You can add your hooks to customize all steps:"
* `GOOGLE_SSO_PRE_VALIDATE_CALLBACK`: Run before the user is validated.
* `GOOGLE_SSO_PRE_CREATE_CALLBACK`: Run before the user is created.
* `GOOGLE_SSO_PRE_LOGIN_CALLBACK`: Run before the user is logged in.


!!! warning "Be careful with these options"
The idea here is to make your life easier, especially when testing. But if you are not careful, you can give
Expand Down
15 changes: 15 additions & 0 deletions example_google_app/backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,18 @@ def pre_create_callback(google_info, request) -> dict:
"username": f"{user_key}_{user_id}",
"date_joined": arrow.utcnow().shift(days=-1).datetime,
}


def pre_validate_callback(google_info, request) -> bool:
"""Callback function called before user is validated.
Must return a boolean to indicate if user is valid to login.
params:
google_info: dict containing user info received from Google.
request: HttpRequest object.
"""
messages.info(
request, f"Running Pre-Validate callback for email: {google_info.get('email')}."
)
return True
3 changes: 3 additions & 0 deletions example_google_app/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,9 @@
# Optional: Change default login text
# GOOGLE_SSO_TEXT = "Login using Google Account"

# Optional: Add pre-validate logic
GOOGLE_SSO_PRE_VALIDATE_CALLBACK = "backend.pre_validate_callback"

# Optional: Add pre-create logic
GOOGLE_SSO_PRE_CREATE_CALLBACK = "backend.pre_create_callback"

Expand Down

0 comments on commit 8163b93

Please sign in to comment.