microsoft_auth/conf.py
from importlib import import_module
from django.test.signals import setting_changed
from django.utils.functional import SimpleLazyObject
from django.utils.translation import gettext_lazy as _
constance_config = None
settings = None
""" List of all possible default configs for microsoft_auth
DEFAULT_CONFIG['defaults'] and DEFAULT_CONFIG['fieldsets'] are in a format
usable by django-constance
(https://django-constance.readthedocs.io/en/latest/) so these values can
be directly added to the CONSTANCE_CONFIG and CONSTANCE_CONFIG_FIELDSETS
in your global settings
django-constance will also require the following field definition:
CONSTANCE_ADDITIONAL_FIELDS = {
'microsoft_choices': ['django.forms.fields.ChoiceField', {
'widget': 'django.forms.Select',
'choices': (('ma', 'Microsoft Accounts'),
('xbl', 'Xbox Live Accounts'))}]}
MICROSOFT_AUTH_CONFIG_CLASS is the only microsoft_auth setting not present
here. See bottom of file for more on it.
"""
LOGIN_TYPE_MA = "ma"
LOGIN_TYPE_XBL = "xbl"
HOOK_SETTINGS = [
"MICROSOFT_AUTH_AUTHENTICATE_HOOK",
"MICROSOFT_AUTH_CALLBACK_HOOK",
]
CACHE_TIMEOUT = 86400
CACHE_KEY_OPENID = "microsoft_auth_openid_config"
CACHE_KEY_JWKS = "microsoft_auth_jwks"
DEFAULT_CONFIG = {
"defaults": {
"MICROSOFT_AUTH_LOGIN_ENABLED": (
True,
_("Whether or not Microsoft OAuth login is enabled."),
bool,
),
"MICROSOFT_AUTH_LOGIN_TYPE": (
LOGIN_TYPE_MA,
_(
"""Type of Microsoft login to use.
Microsoft Accounts is normal Microsoft login.
Xbox Live Accounts use the old Microsoft Account login screen
and then also authenticate against Xbox Live to retrieve
Gamertag."""
),
"microsoft_choices",
),
"MICROSOFT_AUTH_TENANT_ID": (
"common",
_("Microsoft Office 365 Tenant ID"),
str,
),
"MICROSOFT_AUTH_CLIENT_ID": (
"",
_(
"""Microsoft OAuth Client ID, see
https://apps.dev.microsoft.com/ for more."""
),
str,
),
"MICROSOFT_AUTH_CLIENT_SECRET": (
"",
_(
"""Microsoft OAuth Client Secret, see
https://apps.dev.microsoft.com/ for more."""
),
str,
),
"MICROSOFT_AUTH_EXTRA_SCOPES": (
"",
_(
"""Extra OAuth scopes for authentication. Required
scopes are always provided ('openid email'
for Microsoft Auth and 'XboxLive.signin
XboxLive.offline_access' for Xbox). Scopes are space
delimited."""
),
str,
),
"MICROSOFT_AUTH_EXTRA_PARAMETERS": (
{},
_(
"""Any optional parameters to include when building the
microsoft authorization code request url. Parameters are
parsed from a dict and appended in the order they appear
in the dict"""
),
dict,
),
"MICROSOFT_AUTH_AUTO_CREATE": (
True,
_(
"""Autocreate user that attempt to login if they do not
already exist?"""
),
bool,
),
"MICROSOFT_AUTH_REGISTER_INACTIVE_ADMIN": (
False,
_(
"""Automatically register admin class for auth type
that is not active (Xbox when Microsoft Auth is
enabled and Microsoft Auth when Xbox is enabled).
Requires restart of app for setting to take effect."""
),
bool,
),
"MICROSOFT_AUTH_XBL_SYNC_USERNAME": (
False,
_(
"""Automatically sync the username from the Xbox Live
Gamertag?"""
),
bool,
),
"MICROSOFT_AUTH_AUTO_REPLACE_ACCOUNTS": (
False,
_(
"""Automatically replace an existing Microsoft Account
paired to a user when authenticating."""
),
bool,
),
"MICROSOFT_AUTH_AUTHENTICATE_HOOK": (
"",
_(
"""Callable hook to call after authenticating a user on the
`microsoft_auth.backends.MicrosoftAuthenticationBackend`.
If the login type is Microsoft Auth, the parameters will be
`(User: user, oauthlib.oauth2.rfc6749.tokens.OAuth2Token:
token)`
If the login type is Xbox Live, the parameters will be
`(User:user, dict: token)` where token is the Xbox Token,
see `microsoft_auth.client.MicrosoftClient.fetch_xbox_token`
for format"""
),
str,
),
"MICROSOFT_AUTH_CALLBACK_HOOK": (
"",
_(
"""
Callable hook to call right before completing the `auth_callback` view.
Really useful for adding custom data to message or chaning the
expected base URL that gets passed back up to the window that
initiated the original Authorize request.
The parameters that will be passed will be `(HttpRequest:
request, dict: context)`.
The expected return value is the updated context dictionary.
You should NOT remove the data that is currently there.
`base_url` is the expected root URL of the window that
initiated the authorize request
`message` is a dictionary that will be serialized as a JSON
string and passoed back to the initiating window.
"""
),
str,
),
"MICROSOFT_AUTH_PROXIES": (
{},
_(
"""Use proxies for authentication
See https://requests.readthedocs.io/en/master/user/advanced/#proxies/"""
),
dict,
),
},
"fieldsets": {
"Microsoft Login": (
"MICROSOFT_AUTH_LOGIN_ENABLED",
"MICROSOFT_AUTH_LOGIN_TYPE",
"MICROSOFT_AUTH_TENANT_ID",
"MICROSOFT_AUTH_CLIENT_ID",
"MICROSOFT_AUTH_CLIENT_SECRET",
"MICROSOFT_AUTH_EXTRA_SCOPES",
"MICROSOFT_AUTH_EXTRA_PARAMETERS",
"MICROSOFT_AUTH_AUTO_CREATE",
"MICROSOFT_AUTH_REGISTER_INACTIVE_ADMIN",
"MICROSOFT_AUTH_XBL_SYNC_USERNAME",
"MICROSOFT_AUTH_AUTO_REPLACE_ACCOUNTS",
"MICROSOFT_AUTH_AUTHENTICATE_HOOK",
"MICROSOFT_AUTH_CALLBACK_HOOK",
)
},
"fields": {
"microsoft_choices": [
"django.forms.fields.ChoiceField",
{
"widget": "django.forms.Select",
"choices": (
(LOGIN_TYPE_MA, "Microsoft Auth"),
(LOGIN_TYPE_XBL, "Xbox Live"),
),
},
]
},
}
class SimpleConfig:
def __init__(self, config=None):
self._defaults = {}
if config:
self.add_default_config(config)
def add_default_config(self, config):
tmp_dict = {}
for key, value in config["defaults"].items():
tmp_dict[key] = value[0]
self._defaults.update(tmp_dict)
def __getattr__(self, attr):
val = None
# Django settings take priority
try:
# Check if present in user settings
val = getattr(settings, attr)
except AttributeError:
pass
# Check Constance first if it is installed
if val is None and constance_config:
try:
val = getattr(constance_config, attr)
except AttributeError:
pass
if val is None:
# Fall back to defaults
try:
val = self._defaults[attr]
except KeyError:
raise AttributeError
return val
def init_config():
global config, constance_config, settings
from django.conf import settings as django_settings
settings = django_settings
# set constance config global
if "constance" in settings.INSTALLED_APPS:
from constance import config as constance_config
else:
constance_config = None
# retrieve and set config class
auth_config_class = getattr(settings, "MICROSOFT_AUTH_CONFIG_CLASS", None)
if auth_config_class is not None:
module, _, obj = auth_config_class.rpartition(".")
conf = import_module(module)
config = getattr(conf, obj)
# If class is not already instantiated, do so
if callable(config):
config = config()
if hasattr(config, "add_default_config"):
config.add_default_config(DEFAULT_CONFIG)
else:
config = SimpleConfig(DEFAULT_CONFIG)
return config
""" Override MICROSOFT_AUTH_CONFIG_CLASS to inject your own custom dynamic
settings class into microsoft_auth. Useful if you want to manage config
using a dynamic settings manager such as django-constance
Optionally the class can have an 'add_default_config' method to add the
above DEFAULT_CONFIG to config manager
"""
config = SimpleLazyObject(init_config)
def reload_settings(*args, **kwargs):
global config
setting = kwargs.get("setting", kwargs.get("key"))
# only reinitialize config if settings changed
if setting.startswith("MICROSOFT_AUTH_"):
init_config()
setting_changed.connect(reload_settings)