""" Django settings for pingcap_sso project. Generated by 'django-admin startproject' using Django 2.1.2. For more information on this file, see https://docs.djangoproject.com/en/2.1/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/2.1/ref/settings/ """ import os import logging from pathlib import Path from environs import Env import sentry_sdk from sentry_sdk.integrations.django import DjangoIntegration from django.utils.translation import gettext_lazy from pingcap_sso.utils.log_filters import skip_illegal_level, get_level BASE_DIR = Path(__file__).resolve().parent.parent env = Env() env.read_env(BASE_DIR / ".env") # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = env.str("SECRET_KEY", default="django-insecure-secret-key") FORCE_SCRIPT_NAME = env.str("FORCE_SCRIPT_NAME", default='/accounts') # SECURITY WARNING: don't run with debug turned on in production! DEBUG = env.bool("DEBUG", default=True) STAGING = env.bool("STAGING", default=True) ALLOWED_HOSTS = [] TRUSTED_HOSTS = [] VPN_IP_LIST = [] # Application definition INSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', 'corsheaders', 'pingcap_sso', 'pingcap_sso.discourse', 'pingcap_sso.blog', 'oauth2_provider', "social_django", 'django_celery_beat', 'sms', 'pingcap_sso.user_behavior_track', 'pingcap_sso.admin_audit_log', ] MIDDLEWARE = [ 'pingcap_sso.middlewares.SameSiteNoneMiddleware', 'django.middleware.security.SecurityMiddleware', 'django.contrib.sessions.middleware.SessionMiddleware', 'django.middleware.locale.LocaleMiddleware', 'corsheaders.middleware.CorsMiddleware', 'django.middleware.common.CommonMiddleware', 'pingcap_sso.middlewares.EnsureCsrfCookieViewMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.messages.middleware.MessageMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware', 'pingcap_sso.user_behavior_track.middlewares.TrackMiddleware', 'pingcap_sso.admin_audit_log.middlewares.AdminAuditLogMiddleware', 'pingcap_sso.github.middleware.SocialAuthExceptionMiddleware', ] ROOT_URLCONF = 'pingcap_sso.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.jinja2.Jinja2', 'DIRS': [], 'APP_DIRS': True, 'OPTIONS': { 'environment': 'pingcap_sso.utils.jinja2.environment', 'extensions': [ 'webpack_loader.contrib.jinja2ext.WebpackExtension', "jinja2.ext.i18n", ], 'context_processors': [ 'django.template.context_processors.debug', 'django.template.context_processors.request', 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ] }, }, { 'BACKEND': 'django.template.backends.django.DjangoTemplates', 'DIRS': [], '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', ], }, }, ] WSGI_APPLICATION = 'pingcap_sso.wsgi.application' # Database # https://docs.djangoproject.com/en/2.1/ref/settings/#databases DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'pingcap_account', 'USER': 'root', 'PASSWORD': '', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': {'init_command': 'SET default_storage_engine=INNODB;SET NAMES UTF8;'}, }, 'discourse': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'discourse', 'USER': 'root', 'PASSWORD': '', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': {'init_command': 'SET default_storage_engine=INNODB;SET NAMES UTF8;'}, }, 'blog': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'community_blog', 'USER': 'root', 'PASSWORD': '', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': {'init_command': 'SET default_storage_engine=INNODB;SET NAMES UTF8;'}, }, } DATABASE_ROUTERS = ['pingcap_sso.db_routers.DiscourseRouter'] DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' CACHES = { "default": { "BACKEND": "django_redis.cache.RedisCache", "LOCATION": "redis://127.0.0.1:6379/1", "OPTIONS": { "CLIENT_CLASS": "django_redis.client.DefaultClient", } } } # Password validation # https://docs.djangoproject.com/en/2.1/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator', }, { 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator', }, { 'NAME': 'pingcap_sso.validators.password.ComplexityPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator', }, { 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator', }, ] AUTH_PASSWORD_ROTATION_DAYS = 180 # User settings AUTH_USER_MODEL = 'pingcap_sso.User' AUTHENTICATION_BACKENDS = [ 'social_core.backends.github.GithubOAuth2', 'pingcap_sso.backends.SimpleModelBackend' ] LOGIN_URL = '/accounts/login' LOGIN_REDIRECT_URL = '/' SOCIAL_AUTH_PIPELINE = ( 'social_core.pipeline.social_auth.social_details', 'social_core.pipeline.social_auth.social_uid', 'social_core.pipeline.social_auth.auth_allowed', 'social_core.pipeline.social_auth.social_user', # 'social_core.pipeline.user.get_username', 'pingcap_sso.github.pipeline.get_username', # Attention: it is dangerous if we don't trust third party's email 'social_core.pipeline.social_auth.associate_by_email', 'pingcap_sso.github.pipeline.check_if_user_exists', # 'social_core.pipeline.user.create_user', 'social_core.pipeline.social_auth.associate_user', 'social_core.pipeline.social_auth.load_extra_data', 'social_core.pipeline.user.user_details', 'pingcap_sso.github.pipeline.track_signup_channel', ) SOCIAL_AUTH_URL_NAMESPACE = 'social' SOCIAL_AUTH_GITHUB_KEY = env.str("SOCIAL_AUTH_GITHUB_KEY", default="") SOCIAL_AUTH_GITHUB_SECRET = env.str("SOCIAL_AUTH_GITHUB_SECRET", default="") SOCIAL_AUTH_GITHUB_SCOPE = ['user:email'] SOCIAL_AUTH_ALLOWED_REDIRECT_HOSTS = env.list("SOCIAL_AUTH_ALLOWED_REDIRECT_HOSTS", default=[]) # Internationalization # https://docs.djangoproject.com/en/2.1/topics/i18n/ LANGUAGE_CODE = 'en-us' TIME_ZONE = 'Asia/Shanghai' USE_I18N = True USE_L10N = True USE_TZ = True LOCALE_PATHS = ( BASE_DIR / 'pingcap_sso/locale', ) LANGUAGES = { ('zh-hans', '中文简体'), ('en-us', 'English'), } PASSWORD_HASHERS = [ 'django.contrib.auth.hashers.PBKDF2PasswordHasher', 'pingcap_sso.discourse.password.PBKDF2PasswordHasher', 'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher', 'django.contrib.auth.hashers.Argon2PasswordHasher', 'django.contrib.auth.hashers.BCryptSHA256PasswordHasher', ] REST_FRAMEWORK = { 'DEFAULT_RENDERER_CLASSES': ( 'rest_framework.renderers.JSONRenderer', ), 'DEFAULT_PERMISSION_CLASSES': ( 'pingcap_sso.api.permissions.DenyAllPermission', ), 'DEFAULT_AUTHENTICATION_CLASSES': ( 'pingcap_sso.api.authentication.APITokenAuthentication', 'pingcap_sso.api.authentication.SessionAuthentication', ), 'TEST_REQUEST_DEFAULT_FORMAT': 'json', 'DEFAULT_THROTTLE_CLASSES': ( 'rest_framework.throttling.AnonRateThrottle', 'rest_framework.throttling.UserRateThrottle' ), 'DEFAULT_THROTTLE_RATES': { 'anon': '30/m', 'user': '100/m', 'admin': '200/m' } } TEST_RUNNER = 'pingcap_sso.test_utils.runner.ManagedModelTestRunner' # Celery Config CELERY_BROKER_URL = 'redis://127.0.0.1:6379/3' CELERY_RESULT_BACKEND = 'redis://127.0.0.1:6379/3' CELERY_TASK_REJECT_ON_WORKER_LOST = True CELERY_TASK_ACKS_LATE = True CELERY_BEAT_SCHEDULER = 'django_celery_beat.schedulers:DatabaseScheduler' # Oauth2 provider settings OAUTH2_PROVIDER = { "OIDC_ENABLED": True, "OIDC_RSA_PRIVATE_KEY": env.str("OIDC_RSA_PRIVATE_KEY", default="").replace("\\n", "\n"), "PKCE_REQUIRED": False, 'OIDC_ISS_ENDPOINT': env.str("OIDC_ISS_ENDPOINT", default='http://127.0.0.1:8000'), 'OAUTH2_VALIDATOR_CLASS': 'pingcap_sso.oidc.validators.OIDCValidator', 'SCOPES': { "profile": gettext_lazy("Access to your profile, include email and phone"), "openid": gettext_lazy("Limited access to your public data") } } SSO_SET_COOKIES = {} SSO_PROVIDER_ENDPOINT = 'https://accounts.pingcap.cn' SSO_SYNC_COOKIE_ASKTUG_COM_CALLBACK = 'https://asktug.com/_/sso/asktug/set-cookie' SSO_SYNC_COOKIE_TIDB_IO_CALLBACK = 'https://tidb.net/_/sso/tidbio/set-cookie' SSO_ASKTUG_COM_COOKIE_DOMAIN = 'asktug.com' SSO_TIDB_IO_COOKIE_DOMAIN = 'tidb.net' RESERVED_USERNAMES = [ 'admin', 'moderator', 'administrator', 'mod', 'sys', 'system', 'community', 'info', 'you', 'name', 'username', 'user', 'nickname', 'discourse', 'discourseorg', 'discourseforum', 'support', 'hp', 'account-created', 'password-reset', 'admin-login', 'confirm-admin', 'account-created', 'activate-account', 'confirm-email-token', 'authorize-email', 'pingcap', '管理员', '系统管理员', '邮箱管理员', '支持人员' ] # Discourse sso settings DISCOURSE_SSO_TOKEN = '' DISCOURSE_SSO_CALLBACK_URI = 'http://discourse.example.com/session/sso_login' DISOCURSE_ENABLE_SSO_SYNC = True DISCOURSE_SSO_SYNC_ENDPOINT = '' DISCOURSE_API_KEY = '' DISCOURSE_ENDPOINT_PREFIX = '' DISCOURSE_SECRET_TOKEN = '' DISCOURSE_VERIFIED_GROUP_ID = 30015 # Captcha settings CAPTCHA_BACKEND = 'pingcap_sso.captcha.dummy.Captcha' CAPTCHA_RE_SECRET_KEY = '' CAPTCHA_ENABLED = env.bool("CAPTCHA_ENABLED", default=False) # Sms settings SMS_BACKEND = 'sms.backends.console.SmsBackend' UCLOUD_SMS_PUBLICK_KEY = '' UCLOUD_SMS_PRIVATE_KEY = '' UCLOUD_SMS_PROJECT_ID = '' UCLOUD_SMS_SIGNATURE = '' ALIYUN_SMS_ACCESS_KEY = '' ALIYUN_SMS_ACCESS_SECRET = '' BLOCKED_PHONE_NUMBERS = [] # TianYanCha https://open.tianyancha.com/open/816 TIANYANCHA_TOKEN = env.str("TIANYANCHA_TOKEN", default='') # Only admin and trusted ip can access metrics endpoint, trusted ip can be both single ip and cidr PROMETHEUS_TRUSTED_IP= env.str("PROMETHEUS_TRUSTED_IP", default='117.50.84.0/24') # PingCAP Learn settings https://learn.pingkai.cn/ PE_ADMIN_USERNAME = '' PE_ADMIN_PASSWORD = '' # PE Sidecar API PE_SIDECAR_API_URL = env.str("PE_SIDECAR_API_URL", default='https://learn-sidecar.pingkai.cn') PE_SIDECAR_API_TOKEN = env.str("PE_SIDECAR_API_TOKEN", default='') SM4_KEY = "" SM4_IV = "" # Cors settings CORS_ORIGIN_REGEX_WHITELIST = [ r"^https://(\w+\.)*pingkai\.cn$", r"^https://([A-Za-z0-9-]+\.)*pingkai\.cn$", ] CORS_URLS_REGEX = r'^(/api/search-company|/api/points/|/api/official/|/_analytics|/api/me|/api/orgs|/api/operation|/api/admin/archmage|/api/captcha/).*$' CORS_ALLOW_CREDENTIALS = True CORS_ALLOW_HEADERS = ( "content-disposition", "accept", "accept-encoding", "authorization", "content-type", "dnt", "origin", "user-agent", "x-csrftoken", "x-requested-with", ) # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/2.1/howto/static-files/ STATIC_URL = f'{FORCE_SCRIPT_NAME}/static/' STATIC_ROOT = 'static_collected' STATICFILES_DIRS = ( BASE_DIR / "account_frontend/dist/static", BASE_DIR / "admin_frontend/dist", ) STORAGES = { "default": { "BACKEND": "pingcap_sso.file_storage.aliyun.AliyunMediaStorage", }, "staticfiles": { "BACKEND": "django.contrib.staticfiles.storage.StaticFilesStorage", }, } CRM_FENXIANGXIAOKE = { 'appId': '', 'appSecret': '', 'permanentCode': '' } COMMUNITY_IP2LOCATION_ENDPOINT = 'http://172.16.4.219:9084/api/lookup' # PingCAP Lark open platform application # It is used to communicate with pingcap lark # https://open.feishu.cn/app/cli_a5f2b513de3f900c/baseinfo PINGCAP_LARK_APP_ID = env.str("PINGCAP_LARK_APP_ID", default="") PINGCAP_LARK_APP_SECRET = env.str("PINGCAP_LARK_APP_SECRET", default="") PINGCAP_LARK_APP_WEBHOOK_TOKEN = env.str("PINGCAP_LARK_APP_WEBHOOK_TOKEN", default="") PINGCAP_CN_LARK_APP_ID = env.str("PINGCAP_CN_LARK_APP_ID", default="") PINGCAP_CN_LARK_APP_SECRET = env.str("PINGCAP_CN_LARK_APP_SECRET", default="") PINGCAP_CN_LARK_APP_WEBHOOK_TOKEN = env.str("PINGCAP_CN_LARK_APP_WEBHOOK_TOKEN", default="") # AI related settings DEEPSEEK_API_KEY = env.str("DEEPSEEK_API_KEY", default="") # Wechat Login WECHAT_MP_APP_ID = env.str("WECHAT_MP_APP_ID", default="") WECHAT_MP_APP_SECRET = env.str("WECHAT_MP_APP_SECRET", default="") WECHAT_OPEN_APP_ID = env.str("WECHAT_OPEN_APP_ID", default="") WECHAT_OPEN_APP_SECRET = env.str("WECHAT_OPEN_APP_SECRET", default="") # Email config EMAIL_BACKEND = 'django.core.mail.backends.console.EmailBackend' EMAIL_HOST = '' EMAIL_HOST_USER = '' EMAIL_HOST_PASSWORD = '' EMAIL_PORT = 465 EMAIL_USE_TLS = False EMAIL_USE_SSL = True EMAIL_TIME_OUT = 5 DEFAULT_FROM_EMAIL = 'accounts.noreply@pingcap.com' # Logging config LOG_DIRECTORY = '/var/log/pingcap_sso/' LOG_INFO = 'info.log' LOG_WARNING = 'warning.log' LOG_ERROR = 'error.log' LOG_CRITICAL = 'critical.log' LOGGING = { 'version': 1, 'disable_existing_loggers': False, 'formatters': { 'verbose': { 'format': '[%(asctime)s] %(levelname)s [%(name)s.%(funcName)s:%(lineno)d] %(message)s', 'datefmt': '%Y-%m-%d %H:%M:%S' }, }, 'handlers': { 'console': { 'level': 'DEBUG', 'class': 'logging.StreamHandler', 'formatter': 'verbose', } }, 'filters': { 'skip_illegal_info_level': { '()': 'django.utils.log.CallbackFilter', 'callback': skip_illegal_level(logging.DEBUG, logging.WARNING), }, 'info_level': { '()': 'django.utils.log.CallbackFilter', 'callback': get_level(logging.INFO), }, 'warning_level': { '()': 'django.utils.log.CallbackFilter', 'callback': get_level(logging.WARNING), }, 'error_level': { '()': 'django.utils.log.CallbackFilter', 'callback': get_level(logging.ERROR), }, 'critical_level': { '()': 'django.utils.log.CallbackFilter', 'callback': get_level(logging.CRITICAL), }, }, 'loggers': { '': { 'level': 'DEBUG', 'handlers': ['console'], }, } } try: from .local_settings import * # noqa except ImportError: pass # Add the isolated analytics connection after local_settings, because # production local_settings may replace DATABASES in full. cloud_ops_db_host = env.str('CLOUD_OPS_DB_HOST', default='') if cloud_ops_db_host: DATABASES['cloud_ops_analytics'] = { 'ENGINE': 'django.db.backends.mysql', 'NAME': env.str('CLOUD_OPS_DB_NAME', default='cloud_ops_analytics'), 'USER': env.str('CLOUD_OPS_DB_USER', default=''), 'PASSWORD': env.str('CLOUD_OPS_DB_PASSWORD', default=''), 'HOST': cloud_ops_db_host, 'PORT': env.int('CLOUD_OPS_DB_PORT', default=3306), 'CONN_MAX_AGE': 60, 'OPTIONS': { 'init_command': 'SET NAMES utf8mb4;SET time_zone="+00:00";' }, } WEBPACK_LOADER = { 'DEFAULT': { 'CACHE': not DEBUG, 'BUNDLE_DIR_NAME': '', # must end with slash 'STATS_FILE': os.path.join(BASE_DIR, 'admin_frontend/webpack-stats.json'), 'POLL_INTERVAL': 0.1, 'TIMEOUT': None, 'IGNORE': [r'.+\.hot-update.js', r'.+\.map'] }, 'POINTS': { 'CACHE': not DEBUG, 'BUNDLE_DIR_NAME': '', # must end with slash 'STATS_FILE': os.path.join(BASE_DIR, 'points_frontend/webpack-stats.json'), 'POLL_INTERVAL': 0.1, 'TIMEOUT': None, 'IGNORE': [r'.+\.hot-update.js', r'.+\.map'] } } SENTRY_DSN = env.str("SENTRY_DSN", default='') if SENTRY_DSN: sentry_sdk.init( dsn=SENTRY_DSN, integrations=[DjangoIntegration()], # If you wish to associate users to errors (assuming you are using # django.contrib.auth) you may enable sending PII data. send_default_pii=True, # Set traces_sample_rate to 1.0 to capture 100% # of transactions for tracing. traces_sample_rate=0.1, # Set profiles_sample_rate to 1.0 to profile 100% # of sampled transactions. # We recommend adjusting this value in production. profiles_sample_rate=0.1, ) if not DEBUG: LOGGING['handlers'].update({ 'file_info': { 'level': 'INFO', 'class': 'logging.handlers.RotatingFileHandler', 'filename': LOG_DIRECTORY + LOG_INFO, 'formatter': 'verbose', 'filters': ['info_level'], 'maxBytes': 1024 * 1024 * 500, 'backupCount': 10 }, 'file_warning': { 'level': 'WARNING', 'class': 'logging.handlers.RotatingFileHandler', 'filename': LOG_DIRECTORY + LOG_WARNING, 'formatter': 'verbose', 'filters': ['warning_level'], 'maxBytes': 1024 * 1024 * 500, 'backupCount': 10 }, 'file_error': { 'level': 'ERROR', 'class': 'logging.handlers.RotatingFileHandler', 'filename': LOG_DIRECTORY + LOG_ERROR, 'formatter': 'verbose', 'filters': ['error_level'], 'maxBytes': 1024 * 1024 * 500, 'backupCount': 10 }, 'file_critical': { 'level': 'CRITICAL', 'class': 'logging.handlers.RotatingFileHandler', 'filename': LOG_DIRECTORY + LOG_CRITICAL, 'formatter': 'verbose', 'filters': ['critical_level'], 'maxBytes': 1024 * 1024 * 500, } }) LOGGING['loggers']['']['handlers'] += [ 'file_info', 'file_warning', 'file_error', 'file_critical']