wagnerdelima / drf-social-oauth2

drf-social-oauth2 makes it easy to integrate Django social authentication with major OAuth2 providers, i.e., Facebook, Twitter, Google, etc.
https://drf-social-oauth2.readthedocs.io/en/latest/
MIT License
271 stars 34 forks source link

Running convert-token for the second time returns html headers with user environment variables #147

Closed snowcrash-arin closed 1 year ago

snowcrash-arin commented 1 year ago

Describe the bug Running convert-token for the second time using the same backend token exposes user environment variables.

To Reproduce Steps to reproduce the behavior:

  1. Get backend token(I used Google)
  2. Run curl with -i parameter(-i shows header contents)
  3. Run it again to expose the environment variables.

Expected behavior Don't expose user environment variables, doesn't matter how many times convert-token gets run with a same token.

Screenshots Running the convert-token for the first time: Screenshot 2022-11-17 at 10 29 27 AM

Running the convert-token for the second time with the same backend token: Screenshot 2022-11-17 at 10 46 27 AM

Desktop (please complete the following information):

PIP Freeze asgiref==3.5.2 cachetools==5.2.0 certifi==2022.9.24 cffi==1.15.1 charset-normalizer==2.1.1 cryptography==38.0.1 defusedxml==0.7.1 Deprecated==1.2.13 Django==4.1.2 django-environ==0.9.0 django-filter==22.1 django-oauth-toolkit==2.2.0 djangorestframework==3.14.0 drf-social-oauth2==1.2.1 ecdsa==0.18.0 google-api-core==2.10.1 google-auth==2.12.0 google-auth-httplib2==0.1.0 google-cloud-secret-manager==2.12.5 googleapis-common-protos==1.56.4 grpc-google-iam-v1==0.12.4 grpcio==1.49.1 grpcio-status==1.49.1 httplib2==0.20.4 idna==3.4 jwcrypto==1.4.2 Markdown==3.4.1 oauthlib==3.2.2 proto-plus==1.22.1 protobuf==4.21.7 psycopg2==2.9.4 pyasn1==0.4.8 pyasn1-modules==0.2.8 pycparser==2.21 PyJWT==2.6.0 pyparsing==3.0.9 python-jose==3.3.0 python3-openid==3.2.0 pytz==2022.5 requests==2.28.1 requests-oauthlib==1.3.1 rsa==4.9 six==1.16.0 social-auth-app-django==5.0.0 social-auth-core==4.3.0 sqlparse==0.4.3 uritemplate==4.1.1 urllib3==1.26.12 wrapt==1.14.1

Django settings.py """ Django settings for Timesheet project.

Generated by 'django-admin startproject' using Django 4.1.2.

For more information on this file, see https://docs.djangoproject.com/en/4.1/topics/settings/

For the full list of settings and their values, see https://docs.djangoproject.com/en/4.1/ref/settings/ """

from pathlib import Path import io import os from urllib.parse import urlparse

import environ from google.cloud import secretmanager

Build paths inside the project like this: BASE_DIR / 'subdir'.

BASE_DIR = Path(file).resolve().parent.parent

[START gaestd_py_django_secret_config]

env = environ.Env(DEBUG=(bool, False)) env_file = BASE_DIR / "../.env"

if os.path.isfile(env_file):

Use a local secret file, if provided

env.read_env(env_file)

[START_EXCLUDE]

elif os.getenv("TRAMPOLINE_CI", None):

Create local settings if running with CI, for unit testing

placeholder = (
    f"SECRET_KEY=a\n"
    f"DATABASE_URL=sqlite://{BASE_DIR / 'db.sqlite3'}"
)
env.read_env(io.StringIO(placeholder))

[END_EXCLUDE]

elif os.environ.get("GOOGLE_CLOUD_PROJECT", None):

Pull secrets from Secret Manager

project_id = os.environ.get("GOOGLE_CLOUD_PROJECT")

client = secretmanager.SecretManagerServiceClient()
settings_name = os.environ.get("SETTINGS_NAME", "django_settings")
name = f"projects/{project_id}/secrets/{settings_name}/versions/latest"
payload = client.access_secret_version(name=name).payload.data.decode("UTF-8")

env.read_env(io.StringIO(payload))

else: raise Exception("No local .env or GOOGLE_CLOUD_PROJECT detected. No secrets found.")

SECRET_KEY = env("SECRET_KEY")

SECURITY WARNING: don't run with debug turned on in production!

Change this to "False" when you are ready for production

DEBUG = env("DEBUG")

[START gaestd_py_django_csrf]

SECURITY WARNING: It's recommended that you use this when

running in production. The URL will be known once you first deploy

to App Engine. This code takes the URL and converts it to both these settings formats.

APPENGINE_URL = env("APPENGINE_URL", default=None) if APPENGINE_URL:

Ensure a scheme is present in the URL before it's processed.

if not urlparse(APPENGINE_URL).scheme:
    APPENGINE_URL = f"https://{APPENGINE_URL}"

ALLOWED_HOSTS = [urlparse(APPENGINE_URL).netloc]
CSRF_TRUSTED_ORIGINS = [APPENGINE_URL]
SECURE_SSL_REDIRECT = True

else: ALLOWED_HOSTS = ["*"]

[END gaestd_py_django_csrf]

Application definition

INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'rest_framework', 'oauth2_provider', 'social_django', 'drf_social_oauth2' ]

MIDDLEWARE = [ 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.common.CommonMiddleware', 'django.middleware.csrf.CsrfViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', ]

ROOT_URLCONF = 'Timesheet.urls'

TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [BASE_DIR, 'templates'], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', 'social_django.context_processors.backends', 'social_django.context_processors.login_redirect', ], }, }, ]

WSGI_APPLICATION = 'Timesheet.wsgi.application'

REST_FRAMEWORK = {

Use Django's standard django.contrib.auth permissions,

# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
    'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly'
],
'DEFAULT_AUTHENTICATION_CLASSES': (
    'oauth2_provider.contrib.rest_framework.OAuth2Authentication',  # django-oauth-toolkit >= 1.0.0
    'drf_social_oauth2.authentication.SocialAuthentication',
),

}

AUTHENTICATION_BACKENDS = ( 'social_core.backends.google.GoogleOAuth2', 'drf_social_oauth2.backends.DjangoOAuth2', 'django.contrib.auth.backends.ModelBackend', )

Google configuration

SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = 'GOOGLE_KEY' SOCIAL_AUTH_GOOGLE_OAUTH2_SECRET = 'GOOGLE_SECRET'

Define SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE to get extra permissions from Google.

SOCIAL_AUTH_GOOGLE_OAUTH2_SCOPE = [ 'https://www.googleapis.com/auth/userinfo.email', 'https://www.googleapis.com/auth/userinfo.profile', ]

Database

https://docs.djangoproject.com/en/4.1/ref/settings/#databases

Database

[START db_setup]

[START gaestd_py_django_database_config]

Use django-environ to parse the connection string

DATABASES = {"default": env.db()}

If the flag as been set, configure to use proxy

if os.getenv("USE_CLOUD_SQL_AUTH_PROXY", None): DATABASES["default"]["HOST"] = "127.0.0.1" DATABASES["default"]["PORT"] = 5432

Password validation

https://docs.djangoproject.com/en/4.1/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ]

Internationalization

https://docs.djangoproject.com/en/4.1/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True

Static files (CSS, JavaScript, Images)

https://docs.djangoproject.com/en/4.1/howto/static-files/

STATIC_ROOT = "static" STATIC_URL = "/static/" STATICFILES_DIRS = []

Default primary key field type

https://docs.djangoproject.com/en/4.1/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

snowcrash-arin commented 1 year ago

To add more details, running convert_token a second time with a different backend token doesn't yield the same result. The issue only happens when running for the second time with the same backend token.

fahedmahidi commented 1 year ago

To add even more details : the second request fails.

wagnerdelima commented 1 year ago

Guys, you are clearly not reading the docs. This package has almost 12k downloads a month. Surely I have misconfigured the settings.

snowcrash-arin commented 1 year ago

@wagnerdelima Can you provide details on which settings were misconfigured and confirm if you were able to replicate the issue? Just fyi, this behavior happens not only from curl, but also on the DRF API page.

wagnerdelima commented 1 year ago

@snowcrash-arin that's my concern exactly. I was not able to replicate the issue.

wagnerdelima commented 1 year ago

@fahedmahidi @snowcrash-arin FYI: I completely misread this issue. In your instructions, you added "Run curl with -i parameter(-i shows header contents)". Yes, it will add the headers in the response because you are passing -i:

image

snowcrash-arin commented 1 year ago

@fahedmahidi @snowcrash-arin FYI: I completely misread this issue. In your instructions, you added "Run curl with -i parameter(-i shows header contents)". Yes, it will add the headers in the response because you are passing -i:

image

Hi @wagnerdelima

If you look at the screenshot, it shows user environments, not just the headers, which is security concern. Additionally, I made note of the fact that this issue is occurring on the DRF API page.

wagnerdelima commented 1 year ago

@snowcrash-arin what do you mean by "DRF API page"?

wagnerdelima commented 1 year ago

I am investigating this issue @snowcrash-arin. So far I have got no clue on how to solve it. Any ideas will be welcome.

wagnerdelima commented 1 year ago

@snowcrash-arin @fahedmahidi I believe this is fixed now. Please retest it with the latest published version 2.1.1.

Also, check the newly published readthedocs page: https://drf-social-oauth2.readthedocs.io/en/latest/

snowcrash-arin commented 1 year ago

Thanks @wagnerdelima! I'll test it and let you know sometime this month. I stopped working on it due to the security concern.

wagnerdelima commented 1 year ago

@snowcrash-arin please do! Let me know :)