Skip to content

Commit e4fd4e4

Browse files
author
Ondřej Kulatý
committed
Add support for SAML
1 parent b39c179 commit e4fd4e4

File tree

4 files changed

+105
-30
lines changed

4 files changed

+105
-30
lines changed

auth_token/config.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,10 @@
6262
'EXPIRATION_DELTA': 0, # Authorization token expiration will be extended, only if original expiration is at
6363
# least "X" seconds older, than new one (default: 0 seconds, i.e. always extend)
6464
'TAKEOVER_ENABLED': True, # Turns on/off takeover functionality
65-
'MS_SSO_APP_ID': None, # Set AppID for MS SSO authentication
66-
'MS_SSO_TENANT_ID': None, # Set TentnatID for MS SSO authentication
65+
'MS_SSO_PROTOCOL': None, # Protocol of SSO (possible values are "oauth" or "saml")
66+
'MS_SSO_APP_ID': None, # Set AppID for MS SSO authentication (OAuth only)
67+
'MS_SSO_TENANT_ID': None, # Set TentnatID for MS SSO authentication (OAuth only)
68+
'MS_SSO_METADATA_URL': None, # Set Metadata URL for MS SSO authentication (SAML only)
6769
}
6870

6971

auth_token/contrib/ms_sso/backends.py

Lines changed: 44 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,29 +6,64 @@
66

77
UserModel = get_user_model()
88

9-
10-
class MsSsoBackend(ModelBackend):
9+
class BaseMsSsoBackend(ModelBackend):
1110
"""
1211
Authenticates device with MS SSO
1312
"""
1413

15-
def _get_user_from_ms_user_data(self, ms_user_data):
16-
username = ms_user_data['userPrincipalName']
14+
def _get_natural_key(self):
15+
raise NotImplementedError
16+
17+
def _get_user_from_natural_key(self, natural_key):
1718
try:
18-
return UserModel._default_manager.get_by_natural_key(username)
19+
return UserModel._default_manager.det_by_natural_key(self._get_natural_key())
1920
except UserModel.DoesNotExist:
2021
return None
2122

22-
def authenticate(self, request, mso_token=None, **kwargs):
23+
def authenticate(self, request, **kwargs):
2324
if not mso_token:
2425
return None
2526

26-
ms_user_data = get_user_data(mso_token)
27-
if not ms_user_data:
27+
self.ms_user_data = get_user_data(mso_token)
28+
if not self.ms_user_data:
2829
return None
2930

30-
user = self._get_user_from_ms_user_data(ms_user_data)
31+
user = self._get_user_from_natural_key()
3132
if user and self.user_can_authenticate(user):
3233
return user
3334
else:
3435
return None
36+
37+
class MsSsoOauthBackend(ModelBackend):
38+
"""
39+
Authenticates device with MS SSO
40+
"""
41+
42+
def _get_natural_key(self):
43+
username = self.ms_user_data['userPrincipalName']
44+
try:
45+
return UserModel._default_manager.get_by_natural_key(username)
46+
except UserModel.DoesNotExist:
47+
return None
48+
49+
def authenticate(self, request, mso_token=None, **kwargs):
50+
if not mso_token:
51+
return None
52+
53+
self.ms_user_data = get_user_data(mso_token)
54+
if not self.ms_user_data:
55+
return None
56+
57+
return super().authenticate(request)
58+
59+
60+
class MsSsoSamlBackend(ModelBackend):
61+
"""
62+
Authenticates device with MS SSO
63+
"""
64+
65+
def _get_natural_key(self):
66+
return "ABC"
67+
68+
def authenticate(self, request, mso_token=None, **kwargs):
69+
self.request = request

auth_token/contrib/ms_sso/urls.py

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,37 @@
11
from django.urls import path
22

3-
from .views import MsCallback, MsLogin
3+
from .views import MsOauthLogin, MsSamlLogin, MsSamlCallback, MsOauthCallback
4+
from auth_token.config import settings
5+
6+
7+
def _get_view(key):
8+
VIEWS = {
9+
"oauth": {
10+
"login": MsOauthLogin,
11+
"callback": MsOauthCallback,
12+
},
13+
"saml": {
14+
"login": MsSamlLogin,
15+
"callback": MsSamlCallback,
16+
}
17+
}
18+
protocol = settings.MS_SSO_PROTOCOL
19+
if settings.MS_SSO_PROTOCOL not in VIEWS.keys():
20+
raise ValueError("MS SSO Protocol \"{protocol}\" is not supported.")
21+
22+
return VIEWS[protocol][key]
23+
424

525

626
urlpatterns = [
727
path(
828
'login/mso',
9-
MsLogin.as_view(),
29+
_get_view("login").as_view(),
1030
name='ms-sso-login',
1131
),
1232
path(
1333
'login/mso/callback',
14-
MsCallback.as_view(),
34+
_get_view("callback").as_view(),
1535
name='ms-sso-redirect',
1636
),
1737
]

auth_token/contrib/ms_sso/views.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
from .helpers import get_sign_in_flow, acquire_token_by_auth_code_flow
1212

1313

14-
class MsLogin(RedirectView):
14+
class MsOauthLogin(RedirectView):
1515

1616
def get_redirect_url(self, *args, **kwargs):
1717
sign_flow = get_sign_in_flow()
@@ -23,26 +23,19 @@ def get_redirect_url(self, *args, **kwargs):
2323
return sign_flow['auth_uri']
2424

2525

26-
class MsCallback(RedirectView):
26+
class MsSamlLogin(RedirectView):
2727

28-
allowed_cookie = True
29-
allowed_header = False
28+
def get_redirect_url(self, *args, **kwargs):
29+
return "http://placeholder"
3030

31-
def get(self, *args, **kwargs):
32-
if not hasattr(self.request, 'session'):
33-
raise ImproperlyConfigured('Django SessionMiddleware must be enabled to use MS SSO')
3431

35-
sign_flow = self.request.session.get('auth_token_ms_sso_auth_flow')
36-
if not sign_flow:
37-
messages.error(self.request, gettext('Microsoft SSO login was unsuccessful, please try it again'))
38-
return redirect_to_login('')
32+
class BaseMsCallback(RedirectView):
3933

40-
result = acquire_token_by_auth_code_flow(sign_flow, self.request.GET)
41-
if 'access_token' not in result:
42-
messages.error(self.request, gettext('Microsoft SSO login was unsuccessful, please try it again'))
43-
return redirect_to_login(sign_flow['next'])
34+
allowed_cookie = True
35+
allowed_header = False
4436

45-
user = authenticate(mso_token=result['access_token'])
37+
def _do_login(self, **kwargs):
38+
user = authenticate(**kwargs)
4639
if not user:
4740
messages.error(
4841
self.request, gettext('Microsoft SSO login was unsuccessful, please use another login method')
@@ -57,3 +50,28 @@ def get(self, *args, **kwargs):
5750
two_factor_login=False
5851
)
5952
return HttpResponseRedirect(sign_flow['next'])
53+
54+
55+
class MsOauthCallback(BaseMsCallback):
56+
57+
def get(self, *args, **kwargs):
58+
if not hasattr(self.request, 'session'):
59+
raise ImproperlyConfigured('Django SessionMiddleware must be enabled to use MS SSO')
60+
61+
sign_flow = self.request.session.get('auth_token_ms_sso_auth_flow')
62+
if not sign_flow:
63+
messages.error(self.request, gettext('Microsoft SSO login was unsuccessful, please try it again'))
64+
return redirect_to_login('')
65+
66+
result = acquire_token_by_auth_code_flow(sign_flow, self.request.GET)
67+
if 'access_token' not in result:
68+
messages.error(self.request, gettext('Microsoft SSO login was unsuccessful, please try it again'))
69+
return redirect_to_login(sign_flow['next'])
70+
71+
return _do_login(mso_token=result['access_token'])
72+
73+
74+
class MsSamlCallback(BaseMsCallback):
75+
76+
def post(self, *args, **kwargs):
77+
return self._do_login(saml_callback_request=self.request)

0 commit comments

Comments
 (0)