Skip to content
Open
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
86 changes: 86 additions & 0 deletions src/django_gauth/_checks.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# myapp/checks.py
import logging
from enum import Enum
from typing import Any, Callable

from django.conf import settings # pylint: disable=E0401
from django.core.checks import Error # pylint: disable=E0401

logger = logging.getLogger(__name__)

__app_label__ = "django_gauth"


class ErrorCodes(Enum):
"""Error Codes for app checks"""

E001 = ("MISSING_REQUIRED_SETTINGS", "Please define the required project settings")
E002 = (
"MISSING_REQUIRED_MIDDLEWARE",
"Please include required middleware in settings",
)
E003 = (
"MISSING_REQUIRED_GOOGLE_CREDENTIALS",
"Please include required google oauth2 web client \
credentials ( GOOGLE_CLIENT_ID & GOOGLE_CLIENT_SECRET )",
)
E004 = (
"INVALID_GAUTH_SCOPE",
"Please set valid oauth2 SCOPE"
)

formulate_check_id: Callable = lambda code: f"{__app_label__}.{code}"


def check_project_settings(
app_configs: object, **kwargs: Any # pylint: disable=W0613
) -> list:
errors = []
if not hasattr(settings, "SECRET_KEY"):
errors.append(
Error(
"SECRET_KEY setting not defined. Required for app:{__app_label__} to work.",
hint="Define SECRET_KEY in your project settings.py",
id=formulate_check_id(ErrorCodes.E001.name),
)
)
if not hasattr(settings, "GOOGLE_CLIENT_ID"):
errors.append(
Error(
"GOOGLE_CLIENT_ID is not defined in settings."\
+ f"Required for app:{__app_label__} to work.",
hint="Define GOOGLE_CLIENT_ID in your project settings.py",
id=formulate_check_id(ErrorCodes.E003.name),
)
)
if not hasattr(settings, "GOOGLE_CLIENT_SECRET"):
errors.append(
Error(
"GOOGLE_CLIENT_SECRET is not defined in settings."\
+ f"Required for app:{__app_label__} to work.",
hint="Define GOOGLE_CLIENT_SECRET in your project settings.py",
id=formulate_check_id(ErrorCodes.E003.name),
)
)

return errors


def check_project_middlewares(
app_configs: object, **kwargs: Any # pylint: disable=W0613
) -> list:
errors = []

session_middleware_path = "django.contrib.sessions.middleware.SessionMiddleware"

if not session_middleware_path in settings.MIDDLEWARE:
errors.append(
Error(
"Django SessionMiddleware is not included in settings. "\
+ "Required for app:{__app_label__} to work.",
hint=f"Define {session_middleware_path} in your "\
+ "project`s MIDDLEWARE variable in settings.py",
id=formulate_check_id(ErrorCodes.E002.name),
)
)
return errors
11 changes: 6 additions & 5 deletions src/django_gauth/admin.py
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
import pprint
from django.contrib import admin # pylint: disable=E0401

from django.contrib import admin # pylint: disable=E0401
from django.contrib.sessions.models import Session # pylint: disable=E0401


# pylint: disable=R0903
class SessionAdmin(admin.ModelAdmin):
"""
Session information on admin panel
"""

list_display = ["session_key", "_session_data", "expire_date"]
readonly_fields = ["_session_data"]
exclude = [
"session_data"
] # This line ensures that encoded session in not shown .
# Comment this if you want to see the encoded session data as well .
exclude = ["session_data"] # This line ensures that encoded session in not shown .
# Comment this if you want to see the encoded session data as well .

def _session_data(self, obj):

Expand Down
117 changes: 116 additions & 1 deletion src/django_gauth/apps.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,124 @@
from django.apps import AppConfig # pylint: disable=E0401
import warnings
from typing import Any

from django.apps import AppConfig # pylint: disable=E0401

# pylint: disable=E0602
from django.conf import settings # pylint: disable=E0401
from django.core.checks import Info # pylint: disable=E0401
from django.core.checks import register # pylint: disable=E0401
from django.core.checks import Tags as DjangoTags # pylint: disable=E0401
from django.core.checks import Warning as SysCheckWarning # pylint: disable=E0401

from django_gauth import defaults
from django_gauth._checks import (
check_project_middlewares,
check_project_settings,
formulate_check_id,
ErrorCodes
)

warnings.simplefilter("default")


# pylint: disable=R0903
class Tags(DjangoTags):
"""Extending with Custom Tags

NOTE : Do this if none of the existing tags work for you:
https://docs.djangoproject.com/en/3.1/ref/checks/#builtin-tags
"""

django_gauth_compatibility = "django_gauth_compatibility"


# pylint: disable=R0903
class DjangoGauthConfig(AppConfig):
"""
App Configurator @ django_gauth
"""

default_auto_field = "django.db.models.BigAutoField"
name = "django_gauth"

def ready(self) -> None:
register(Tags.compatibility)(check_project_middlewares)
register(Tags.django_gauth_compatibility)(check_project_settings)


@register(Tags.django_gauth_compatibility)
def set_defaults(
app_configs: object, **kwargs: Any # pylint: disable=W0613
) -> list:
errors = []
if not hasattr(settings, "SCOPE"):
setattr(settings, "SCOPE", [])
errors.append(
SysCheckWarning(
"SCOPE setting is not defined. Defaulting to `[]`."\
+ "It may affect the normal flow of oauth and might not run as expected."\
+ "Please rectify ASAP.",
hint=(
"See https://masterpiece93.github.io/django-gauth/settings/ "\
+ "for more information."
),
id=formulate_check_id(ErrorCodes.E004.name),
)
)

if not hasattr(settings, "GOOGLE_AUTH_FINAL_REDIRECT_URL"):
setattr(
settings,
"GOOGLE_AUTH_FINAL_REDIRECT_URL",
defaults.GOOGLE_AUTH_FINAL_REDIRECT_URL,
)
_msg = "GOOGLE_AUTH_FINAL_REDIRECT_URL settings is not defined."\
+f"Defaulting to `{defaults.GOOGLE_AUTH_FINAL_REDIRECT_URL}`"
warnings.warn(_msg)
else:
if not settings.GOOGLE_AUTH_FINAL_REDIRECT_URL:
_msg = "GOOGLE_AUTH_FINAL_REDIRECT_URL setting is set to"\
+ f"`{settings.GOOGLE_AUTH_FINAL_REDIRECT_URL}` which is logically incorrect."
info = Info(_msg)
errors.append(info)
if not hasattr(settings, "CREDENTIALS_SESSION_KEY_NAME"):
setattr(
settings,
"CREDENTIALS_SESSION_KEY_NAME",
defaults.CREDENTIALS_SESSION_KEY_NAME,
)
_msg = "CREDENTIALS_SESSION_KEY_NAME settings is not defined."\
+ "Defaulting to `{defaults.CREDENTIALS_SESSION_KEY_NAME}`"
warnings.warn(_msg)
else:
if not settings.CREDENTIALS_SESSION_KEY_NAME:
_msg = "CREDENTIALS_SESSION_KEY_NAME setting is set to"\
+ "`{settings.CREDENTIALS_SESSION_KEY_NAME}` which is logically incorrect."
info = Info(_msg)
errors.append(info)
if not hasattr(settings, "STATE_KEY_NAME"):
setattr(settings, "STATE_KEY_NAME", defaults.STATE_KEY_NAME)
_msg = "STATE_KEY_NAME settings is not defined."\
+ "Defaulting to `{defaults.STATE_KEY_NAME}`"
warnings.warn(_msg)
else:
if not settings.STATE_KEY_NAME:
_msg = f"STATE_KEY_NAME setting is set to `{settings.STATE_KEY_NAME}`"\
+ "which is logically incorrect."
info = Info(_msg)
errors.append(info)

if not hasattr(settings, "FINAL_REDIRECT_KEY_NAME"):
setattr(
settings, "FINAL_REDIRECT_KEY_NAME", defaults.FINAL_REDIRECT_KEY_NAME
)
_msg = "FINAL_REDIRECT_KEY_NAME settings is not defined."\
+ "Defaulting to `{defaults.FINAL_REDIRECT_KEY_NAME}`"
warnings.warn(_msg)
else:
if not settings.FINAL_REDIRECT_KEY_NAME:
_msg = "FINAL_REDIRECT_KEY_NAME setting is set to"\
+ "`{settings.FINAL_REDIRECT_KEY_NAME}` which is logically incorrect."
info = Info(_msg)
errors.append(info)
return errors
2 changes: 1 addition & 1 deletion src/django_gauth/urls.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# urls

from django.urls import path # pylint: disable=E0401
from django.urls import path # pylint: disable=E0401

from . import views

Expand Down
8 changes: 4 additions & 4 deletions src/django_gauth/utilities.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from typing import Any, Dict, Tuple, Union
from urllib.parse import urlparse

from django.conf import Settings, settings # pylint: disable=E0401
from google.oauth2.credentials import Credentials # pylint: disable=E0401
from django.conf import Settings, settings # pylint: disable=E0401
from google.oauth2.credentials import Credentials # pylint: disable=E0401

__all__ = [
"credentials_to_dict",
Expand Down Expand Up @@ -60,8 +60,8 @@ def check_gauth_authentication(session: Settings) -> Tuple[bool, object]:


def is_valid_google_url(url: str) -> bool:
VALID_SCHEME = "https" # pylint: disable=C0103
VALID_DOMAIN = "docs.google.com" # pylint: disable=C0103
VALID_SCHEME = "https" # pylint: disable=C0103
VALID_DOMAIN = "docs.google.com" # pylint: disable=C0103
try:
result = urlparse(url)
return (
Expand Down
66 changes: 18 additions & 48 deletions src/django_gauth/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,46 +3,14 @@
~@ankit.kumar05
"""

from django.conf import settings # pylint: disable=E0401
from django.shortcuts import redirect, render # pylint: disable=E0401
from django.urls import reverse # pylint: disable=E0401
from google.auth.transport import requests # pylint: disable=E0401
from google.oauth2 import id_token # pylint: disable=E0401
from google_auth_oauthlib.flow import Flow # pylint: disable=E0401

from django_gauth import defaults
from django_gauth.utilities import check_gauth_authentication, credentials_to_dict
from django.conf import settings # pylint: disable=E0401
from django.shortcuts import redirect, render # pylint: disable=E0401
from django.urls import reverse # pylint: disable=E0401
from google.auth.transport import requests # pylint: disable=E0401
from google.oauth2 import id_token # pylint: disable=E0401
from google_auth_oauthlib.flow import Flow # pylint: disable=E0401

if hasattr(settings, "SCOPE") and settings.SCOPE:
SCOPE = settings.SCOPE
else:
SCOPE = []

if (
hasattr(settings, "GOOGLE_AUTH_FINAL_REDIRECT_URL")
and settings.GOOGLE_AUTH_FINAL_REDIRECT_URL
):
GOOGLE_AUTH_FINAL_REDIRECT_URL = settings.GOOGLE_AUTH_FINAL_REDIRECT_URL
else:
GOOGLE_AUTH_FINAL_REDIRECT_URL = defaults.GOOGLE_AUTH_FINAL_REDIRECT_URL

if (
hasattr(settings, "CREDENTIALS_SESSION_KEY_NAME")
and settings.CREDENTIALS_SESSION_KEY_NAME
):
CREDENTIALS_SESSION_KEY_NAME = settings.CREDENTIALS_SESSION_KEY_NAME
else:
CREDENTIALS_SESSION_KEY_NAME = defaults.CREDENTIALS_SESSION_KEY_NAME

if hasattr(settings, "STATE_KEY_NAME") and settings.STATE_KEY_NAME:
STATE_KEY_NAME = settings.STATE_KEY_NAME
else:
STATE_KEY_NAME = defaults.STATE_KEY_NAME

if hasattr(settings, "FINAL_REDIRECT_KEY_NAME") and settings.FINAL_REDIRECT_KEY_NAME:
FINAL_REDIRECT_KEY_NAME = settings.STATE_KEY_NAME
else:
FINAL_REDIRECT_KEY_NAME = defaults.FINAL_REDIRECT_KEY_NAME
from django_gauth.utilities import check_gauth_authentication, credentials_to_dict


def index(request): # type: ignore
Expand Down Expand Up @@ -78,7 +46,7 @@ def login(request): # type: ignore
}
# if you need additional scopes, add them here
,
scopes=SCOPE,
scopes=settings.SCOPE,
)

# flow.redirect_uri = get_redirect_uri(request) # use this when
Expand All @@ -88,13 +56,13 @@ def login(request): # type: ignore
access_type="offline", prompt="select_account", include_granted_scopes="true"
)

request.session[STATE_KEY_NAME] = state
request.session[settings.STATE_KEY_NAME] = state
if (
"final_redirect" not in request.session
or not request.session[FINAL_REDIRECT_KEY_NAME]
or not request.session[settings.FINAL_REDIRECT_KEY_NAME]
):
request.session[FINAL_REDIRECT_KEY_NAME] = (
GOOGLE_AUTH_FINAL_REDIRECT_URL
request.session[settings.FINAL_REDIRECT_KEY_NAME] = (
settings.GOOGLE_AUTH_FINAL_REDIRECT_URL
or request.build_absolute_uri(reverse("django_gauth:index"))
) # directs where to land after login is successful.

Expand All @@ -106,7 +74,7 @@ def callback(request): # type: ignore
- Google IDP response control transfer
"""
# pull the state from the session
session_state = request.session.get(STATE_KEY_NAME)
session_state = request.session.get(settings.STATE_KEY_NAME)
redirect_uri = request.build_absolute_uri(reverse("django_gauth:callback"))
authorization_response = request.build_absolute_uri()
# Flow Creation
Expand Down Expand Up @@ -135,15 +103,17 @@ def callback(request): # type: ignore
credentials = flow.credentials
# verify token, while also retrieving information about the user
id_info = id_token.verify_oauth2_token(
id_token=credentials._id_token, # pylint: disable=W0212
id_token=credentials._id_token, # pylint: disable=W0212
request=requests.Request(),
audience=settings.GOOGLE_CLIENT_ID,
clock_skew_in_seconds=5,
)
# session setting
request.session["id_info"] = id_info
request.session[CREDENTIALS_SESSION_KEY_NAME] = credentials_to_dict(credentials)
request.session[settings.CREDENTIALS_SESSION_KEY_NAME] = credentials_to_dict(
credentials
)
# redirecting to the final redirect (i.e., logged in page)
redirect_response = redirect(request.session[FINAL_REDIRECT_KEY_NAME])
redirect_response = redirect(request.session[settings.FINAL_REDIRECT_KEY_NAME])

return redirect_response