def do_start(httpretty, auth_options): """ Do all the things to setup the authentication but don't actually complete it. - generate code, state, nonce and call authorization url - build target url - request access token, mock JWT signation verification request - mock userinfo request Return the expected request data """ # backend.start() generates the nonce and creates the Association start_url = auth_options.backend.start().url assert Association.objects.count() == 1 # set up the mocked auth requests nonce = auth_options.nonce or Association.objects.first().handle access_token_body = { "access_token": auth_options.access_token, "token_type": "bearer", "id_token": prepare_id_token(nonce, auth_options), } target_url = do_auth(httpretty, start_url, auth_options, access_token_body) # Now that the auth is all set up, get the start url. `requests` will follow the # redirect, so the response text is the body from the mock target url response = requests.get(start_url) assert response.url == target_url assert response.text == "foobar" # format the expected request data from the start and target urls request_data = parse_qs(urlparse(start_url).query) target_request_data = parse_qs(urlparse(target_url).query) request_data.update(target_request_data) return request_data
def post(self, request, *args, **kwargs): input_data = self.get_serializer_in_data() provider_name = self.get_provider_name(input_data) if not provider_name: return self.respond_error("Provider is not specified") self.set_input_data(request, input_data) decorate_request(request, provider_name) serializer_in = self.get_serializer_in(data=input_data) if self.oauth_v1( ) and request.backend.OAUTH_TOKEN_PARAMETER_NAME not in input_data: # oauth1 first stage (1st is get request_token, 2nd is get access_token) manual_redirect_uri = self.request.auth_data.pop( 'redirect_uri', None) manual_redirect_uri = self.get_redirect_uri(manual_redirect_uri) if manual_redirect_uri: self.request.backend.redirect_uri = manual_redirect_uri request_token = parse_qs(request.backend.set_unauthorized_token()) return Response(request_token) serializer_in.is_valid(raise_exception=True) try: user = self.get_object() except (AuthException, HTTPError) as e: return self.respond_error(e) if isinstance(user, HttpResponse): # error happened and pipeline returned HttpResponse instead of user return user resp_data = self.get_serializer(instance=user) self.do_login(request.backend, user) return Response(resp_data.data)
def post(self, request, *args, **kwargs): input_data = self.get_serializer_in_data() provider_name = self.get_provider_name(input_data) if not provider_name: return self.respond_error("Provider is not specified") self.set_input_data(request, input_data) decorate_request(request, provider_name) serializer_in = self.get_serializer_in(data=input_data) if self.oauth_v1() and request.backend.OAUTH_TOKEN_PARAMETER_NAME not in input_data: # oauth1 first stage (1st is get request_token, 2nd is get access_token) request_token = parse_qs(request.backend.set_unauthorized_token()) return Response(request_token) serializer_in.is_valid(raise_exception=True) try: user = self.get_object() except (AuthException, HTTPError) as e: return self.respond_error(e) if isinstance(user, HttpResponse): # An error happened and pipeline returned HttpResponse instead of user return user resp_data = self.get_serializer(instance=user) self.do_login(request.backend, user) # Validating / re-generating the token token, _ = ExpiringToken.objects.get_or_create( user=user ) if token.expired(): token.delete() token = ExpiringToken.objects.create( user=user ) return Response(resp_data.data)
def test_login_social_oauth1_jwt(self): resp = self.client.post( reverse('login_social_jwt_user'), data={'provider': 'twitter'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.data, parse_qs(self.request_token_body)) resp = self.client.post(reverse('login_social_jwt_user'), data={ 'provider': 'twitter', 'oauth_token': 'foobar', 'oauth_verifier': 'overifier' }) self.assertEqual(resp.status_code, 200)
def test_login_social_oauth1_session(self): resp = self.client.post( reverse('login_social_session'), data={'provider': 'twitter'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.data, parse_qs(self.request_token_body)) resp = self.client.post(reverse('login_social_session'), data={ 'provider': 'twitter', 'oauth_token': 'foobar', 'oauth_verifier': 'overifier' }) self.assertEqual(resp.status_code, 200)
def oauth_authorization_request(self, token): """Generate OAuth request to authorize token.""" if not isinstance(token, dict): token = parse_qs(token) params = self.auth_extra_arguments() or {} params.update(self.get_scope_argument()) params[self.OAUTH_TOKEN_PARAMETER_NAME] = token.get( self.OAUTH_TOKEN_PARAMETER_NAME) params['oauth_token_secret'] = token.get('oauth_token_secret') state = self.get_or_create_state() params[self.REDIRECT_URI_PARAMETER_NAME] = self.get_redirect_uri(state) return '{0}?{1}'.format(self.authorization_url(), urlencode(params))
def test_login_social_oauth1_token(self): """ Currently oauth1 works only if session is enabled. Probably it is possible to make it work without session, but it will be needed to change the logic in python-social-auth. """ resp = self.client.post( reverse('login_social_token_user'), data={'provider': 'twitter'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.data, parse_qs(self.request_token_body)) resp = self.client.post(reverse('login_social_token_user'), data={ 'provider': 'twitter', 'oauth_token': 'foobar', 'oauth_verifier': 'overifier' }) self.assertEqual(resp.status_code, 200)
def test_login_social_oauth1_knox(self): """ Currently oauth1 works only if session is enabled. Probably it is possible to make it work without session, but it will be needed to change the logic in python-social-auth. """ resp = self.client.post( reverse('login_social_knox_user'), data={'provider': 'twitter'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.data, parse_qs(self.request_token_body)) resp = self.client.post(reverse('login_social_knox_user'), data={ 'provider': 'twitter', 'oauth_token': 'foobar', 'oauth_verifier': 'overifier' }) self.assertEqual(resp.status_code, 200)
def do_auth(httpretty, start_url, auth_options, access_token_body): """ Mock all the relevant uris for auth Return the target url with the expected code, nonce, redirect uri and params """ complete_url = reverse("gateway:nhsid_complete") start_query = parse_qs(urlparse(start_url).query) target_url = auth_options.strategy.build_absolute_uri(complete_url) target_url = url_add_parameters(target_url, {"state": start_query["state"]}) # mock the authorization call and its redirect to the target_url httpretty.register_uri(httpretty.GET, start_url, status=301, location=target_url) httpretty.register_uri(httpretty.GET, target_url, status=200, body="foobar") # Mock the JWK keys request (used to validate JWT id_token); JWK_PUBLIC_KEYS includes # the real key and an unsupported one to ensure we can deal with unsupported keys httpretty.register_uri( httpretty.GET, auth_options.backend.jwks_uri(), status=200, body=json.dumps({"keys": JWK_PUBLIC_KEYS}), ) # Mock the call to get the access token httpretty.register_uri( httpretty.POST, uri=auth_options.backend.access_token_url(), status=auth_options.access_token_status, body=json.dumps(access_token_body) or "", content_type="text/json", ) # Mock the call to the userinfo url httpretty.register_uri( httpretty.GET, auth_options.backend.userinfo_url(), body=json.dumps(auth_options.user_data_body) or "", content_type="text/json", ) return target_url
def test_login_social_oauth1_jwt(self): try: import rest_framework_jwt except ImportError: return assert rest_framework_jwt is not None resp = self.client.post(reverse('login_social_jwt_user'), data={'provider': 'twitter'}) self.assertEqual(resp.status_code, 200) self.assertEqual(resp.data, parse_qs(self.request_token_body)) resp = self.client.post(reverse('login_social_token_user'), data={ 'provider': 'twitter', 'oauth_token': 'foobar', 'oauth_token_secret': 'boofar', 'oauth_verifier': 'overifier' }) self.assertEqual(resp.status_code, 200)
def get_unauthorized_token(self): """Get unauthorized token from session passed on state parameter.""" unauthed_tokens = self.session.get('_utoken') if not unauthed_tokens: raise AuthTokenError(self, 'Missing unauthorized token') data_token = self.data.get(self.OAUTH_TOKEN_PARAMETER_NAME) if data_token is None: raise AuthTokenError(self, 'Missing unauthorized token') token = None utoken = unauthed_tokens orig_utoken = utoken if not isinstance(utoken, dict): utoken = parse_qs(utoken) if utoken.get(self.OAUTH_TOKEN_PARAMETER_NAME) == data_token: token = utoken else: raise AuthTokenError(self, 'Incorrect tokens') return token
def post(self, request, *args, **kwargs): input_data = self.get_serializer_in_data() provider_name = self.get_provider_name(input_data) if not provider_name: return self.respond_error("Provider is not specified") self.set_input_data(request, input_data) decorate_request(request, provider_name) serializer_in = self.get_serializer_in(data=input_data) if self.oauth_v1() and request.backend.OAUTH_TOKEN_PARAMETER_NAME not in input_data: # oauth1 first stage (1st is get request_token, 2nd is get access_token) request_token = parse_qs(request.backend.set_unauthorized_token()) return Response(request_token) serializer_in.is_valid(raise_exception=True) try: user = self.get_object() except (AuthException, HTTPError) as e: return self.respond_error(e) if isinstance(user, HttpResponse): # An error happened and pipeline returned HttpResponse instead of user return user resp_data = self.get_serializer(instance=user) self.do_login(request.backend, user) return Response(resp_data.data)
def post(self, request, *args, **kwargs): input_data = self.get_serializer_in_data() provider_name = self.get_provider_name(input_data) if not provider_name: return self.respond_error("Provider is not specified") self.set_input_data(request, input_data) decorate_request(request, provider_name) serializer_in = self.get_serializer_in(data=input_data) if self.oauth_v1( ) and request.backend.OAUTH_TOKEN_PARAMETER_NAME not in input_data: # oauth1 first stage (1st is get request_token, 2nd is get access_token) manual_redirect_uri = self.request.auth_data.pop( 'redirect_uri', None) manual_redirect_uri = self.get_redirect_uri(manual_redirect_uri) if manual_redirect_uri: self.request.backend.redirect_uri = manual_redirect_uri request_token = parse_qs(request.backend.set_unauthorized_token()) return Response(request_token) serializer_in.is_valid(raise_exception=True) try: user = self.get_object() except (AuthException, HTTPError) as e: if isinstance(e, AuthForbidden): return Response( { 'message': 'Tài khoản đăng nhập không phải VNPAY, TRIPI or TEKO.' }, status=status.HTTP_400_BAD_REQUEST) return Response({'message': 'Đăng nhập thất bại!'}, status=status.HTTP_400_BAD_REQUEST) if isinstance( user, HttpResponse ): # An error happened and pipeline returned HttpResponse instead of user return user resp_data = self.get_serializer(instance=user) # self.do_login(request.backend, user) if user.is_active is False: return Response( { 'message': 'Tài khoản đã bị khóa, vui lòng liên hệ admin để được hỗ trợ.' }, status=status.HTTP_400_BAD_REQUEST) if not user.groups.all(): if not user.is_superuser: return Response( {'message': 'Bạn không có bất kì nhóm quyền nào'}, status=status.HTTP_400_BAD_REQUEST) elif len(user.groups.all()) > 1: return Response( { 'message': 'Bạn có nhiều hơn 1 nhóm quyền, liên hệ admin để được giải quyết' }, status=status.HTTP_400_BAD_REQUEST) else: group = user.get_group() if group is None: return Response( { 'message': 'Có lỗi xảy ra với nhóm quyền của bạn. Vui lòng liên hệ Admin để được hỗ trợ' }, status=status.HTTP_400_BAD_REQUEST) elif not group.status: return Response( { 'message': 'Nhóm quyền gắn với tài khoản của bạn đang tạm khóa. Vui lòng liên hệ Admin để được hỗ trợ' }, status=status.HTTP_400_BAD_REQUEST) return Response(resp_data.data)
def test_full_login_process(self): """Asserts the nominal login process works.""" sso_location = "http://testserver/account/saml/local-accepting-idp/sso/" entity_descriptor_list = [ generate_idp_metadata( entity_id=sso_location, sso_location=sso_location, ui_info_display_names=format_mdui_display_name( "Local accepting IdP"), ), ] # 1/ Select Idp in the provider list with mock.patch("urllib.request.urlopen") as urlopen_mock: class UrlOpenMock: """Mockin object for the urlopen""" def read(self): """Allow object to be read several times.""" return generate_idp_federation_metadata( entity_descriptor_list=entity_descriptor_list, ).encode("utf-8") urlopen_mock.return_value = UrlOpenMock() response = self.client.get( reverse("account:saml_fer_idp_choice"), ) self.assertContains( response, f'action="{reverse("account:social:begin", args=("saml_fer",))}"', ) self.assertContains(response, "local-accepting-idp") response = self.client.get( f'{reverse("account:social:begin", args=("saml_fer",))}?idp=local-accepting-idp', ) self.assertEqual(response.status_code, 302) self.assertTrue(response["Location"].startswith( "http://testserver/account/saml/local-accepting-idp/sso/?SAMLRequest=" )) # 2/ Fake the redirection to the SSO response = self.client.get( f'{reverse("account:social:begin", args=("saml_fer",))}?idp=local-accepting-idp', follow=False, ) # 3/ Generate SAML response using SAML request query_values = parse_qs(urlparse(response["Location"]).query) saml_request = query_values["SAMLRequest"] saml_relay_state = query_values["RelayState"] readable_saml_request = OneLogin_Saml2_Utils.decode_base64_and_inflate( saml_request, ) saml_request = OneLogin_Saml2_XML.to_etree(readable_saml_request) saml_acs_url = saml_request.get("AssertionConsumerServiceURL") request_id = saml_request.get("ID") auth_response = OneLogin_Saml2_Utils.b64encode( generate_auth_response( request_id, saml_acs_url, issuer= "http://testserver/account/saml/local-accepting-idp/sso/", )) # 4/ POST the data to our endpoint response = self.client.post( saml_acs_url, data={ "RelayState": saml_relay_state, "SAMLResponse": auth_response, }, ) self.assertEqual(response.status_code, 302) self.assertEqual(response["Location"], "/") # Assert the user is authenticated user = auth_get_user(self.client) self.assertTrue(user.is_authenticated) # Assert the user has an organization organization_access = user.organization_accesses.select_related( "organization").get() # also assert there is only one organization self.assertEqual(organization_access.role, STUDENT) self.assertEqual(organization_access.organization.name, "OrganizationDName")