mirror of
https://github.com/ansible/awx.git
synced 2026-02-21 05:00:07 -03:30
Add support for single-sign on using python-social-auth (with Google/Github OAuth2 and SAML support). Add support for RADIUS as another authentication backend.
This commit is contained in:
@@ -1,6 +1,9 @@
|
||||
# Copyright (c) 2015 Ansible, Inc.
|
||||
# All Rights Reserved.
|
||||
|
||||
# Python
|
||||
import urllib
|
||||
|
||||
# Django
|
||||
from django.utils.timezone import now as tz_now
|
||||
from django.conf import settings
|
||||
@@ -30,6 +33,13 @@ class TokenAuthentication(authentication.TokenAuthentication):
|
||||
auth = auth.encode(HTTP_HEADER_ENCODING)
|
||||
return auth
|
||||
|
||||
@staticmethod
|
||||
def _get_auth_token_cookie(request):
|
||||
token = request.COOKIES.get('token', '')
|
||||
if token:
|
||||
token = urllib.unquote(token).strip('"')
|
||||
return 'token %s' % token
|
||||
|
||||
def authenticate(self, request):
|
||||
self.request = request
|
||||
|
||||
@@ -40,7 +50,9 @@ class TokenAuthentication(authentication.TokenAuthentication):
|
||||
if not auth or auth[0].lower() != 'token':
|
||||
auth = authentication.get_authorization_header(request).split()
|
||||
if not auth or auth[0].lower() != 'token':
|
||||
return None
|
||||
auth = TokenAuthentication._get_auth_token_cookie(request).split()
|
||||
if not auth or auth[0].lower() != 'token':
|
||||
return None
|
||||
|
||||
if len(auth) == 1:
|
||||
msg = 'Invalid token header. No credentials provided.'
|
||||
|
||||
@@ -142,6 +142,8 @@ class APIView(views.APIView):
|
||||
'new_in_200': getattr(self, 'new_in_200', False),
|
||||
'new_in_210': getattr(self, 'new_in_210', False),
|
||||
'new_in_220': getattr(self, 'new_in_220', False),
|
||||
'new_in_230': getattr(self, 'new_in_230', False),
|
||||
'new_in_240': getattr(self, 'new_in_240', False),
|
||||
}
|
||||
|
||||
def get_description(self, html=False):
|
||||
@@ -158,7 +160,7 @@ class APIView(views.APIView):
|
||||
'''
|
||||
ret = super(APIView, self).metadata(request)
|
||||
added_in_version = '1.2'
|
||||
for version in ('2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
|
||||
for version in ('2.4.0', '2.3.0', '2.2.0', '2.1.0', '2.0.0', '1.4.8', '1.4.5', '1.4', '1.3'):
|
||||
if getattr(self, 'new_in_%s' % version.replace('.', ''), False):
|
||||
added_in_version = version
|
||||
break
|
||||
|
||||
@@ -601,6 +601,8 @@ class UserSerializer(BaseSerializer):
|
||||
ret = super(UserSerializer, self).to_native(obj)
|
||||
ret.pop('password', None)
|
||||
ret.fields.pop('password', None)
|
||||
if obj:
|
||||
ret['auth'] = obj.social_auth.values('provider', 'uid')
|
||||
return ret
|
||||
|
||||
def get_validation_exclusions(self):
|
||||
|
||||
@@ -3,4 +3,6 @@
|
||||
{% if new_in_145 %}> _Added in Ansible Tower 1.4.5_{% endif %}
|
||||
{% if new_in_148 %}> _Added in Ansible Tower 1.4.8_{% endif %}
|
||||
{% if new_in_200 %}> _New in Ansible Tower 2.0.0_{% endif %}
|
||||
{% if new_in_220 %}> _New in Ansible Tower 2.2.0_{% endif %}
|
||||
{% if new_in_220 %}> _New in Ansible Tower 2.2.0_{% endif %}
|
||||
{% if new_in_230 %}> _New in Ansible Tower 2.3.0_{% endif %}
|
||||
{% if new_in_240 %}> _New in Ansible Tower 2.4.0_{% endif %}
|
||||
@@ -224,6 +224,7 @@ v1_urls = patterns('awx.api.views',
|
||||
url(r'^$', 'api_v1_root_view'),
|
||||
url(r'^ping/$', 'api_v1_ping_view'),
|
||||
url(r'^config/$', 'api_v1_config_view'),
|
||||
url(r'^auth/$', 'auth_view'),
|
||||
url(r'^authtoken/$', 'auth_token_view'),
|
||||
url(r'^me/$', 'user_me_list'),
|
||||
url(r'^dashboard/$', 'dashboard_view'),
|
||||
|
||||
@@ -47,6 +47,9 @@ import qsstats
|
||||
# ANSIConv
|
||||
import ansiconv
|
||||
|
||||
# Python Social Auth
|
||||
from social.backends.utils import load_backends
|
||||
|
||||
# AWX
|
||||
from awx.main.task_engine import TaskSerializer, TASK_FILE, TEMPORARY_TASK_FILE
|
||||
from awx.main.tasks import mongodb_control
|
||||
@@ -514,6 +517,37 @@ class ScheduleUnifiedJobsList(SubListAPIView):
|
||||
view_name = 'Schedule Jobs List'
|
||||
new_in_148 = True
|
||||
|
||||
class AuthView(APIView):
|
||||
|
||||
authentication_classes = []
|
||||
permission_classes = (AllowAny,)
|
||||
new_in_240 = True
|
||||
|
||||
def get(self, request):
|
||||
data = SortedDict()
|
||||
err_backend, err_message = request.session.get('social_auth_error', (None, None))
|
||||
for name, backend in load_backends(settings.AUTHENTICATION_BACKENDS).items():
|
||||
login_url = reverse('social:begin', args=(name,))
|
||||
complete_url = request.build_absolute_uri(reverse('social:complete', args=(name,)))
|
||||
backend_data = {
|
||||
'login_url': login_url,
|
||||
'complete_url': complete_url,
|
||||
}
|
||||
if name == 'saml':
|
||||
backend_data['metadata_url'] = reverse('sso:saml_metadata')
|
||||
for idp in settings.SOCIAL_AUTH_SAML_ENABLED_IDPS.keys():
|
||||
saml_backend_data = dict(backend_data.items())
|
||||
saml_backend_data['login_url'] = '%s?idp=%s' % (login_url, idp)
|
||||
full_backend_name = '%s:%s' % (name, idp)
|
||||
if err_backend == full_backend_name and err_message:
|
||||
saml_backend_data['error'] = err_message
|
||||
data[full_backend_name] = saml_backend_data
|
||||
else:
|
||||
if err_backend == name and err_message:
|
||||
backend_data['error'] = err_message
|
||||
data[name] = backend_data
|
||||
return Response(data)
|
||||
|
||||
class AuthTokenView(APIView):
|
||||
|
||||
authentication_classes = []
|
||||
|
||||
Reference in New Issue
Block a user