Skip to content

Commit

Permalink
Remove sso app
Browse files Browse the repository at this point in the history
Remove sso app.
  • Loading branch information
ldjebran committed Sep 25, 2024
1 parent 5b7a050 commit 3e19f6f
Show file tree
Hide file tree
Showing 61 changed files with 72 additions and 6,711 deletions.
4 changes: 2 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ api-lint:
awx-link:
[ -d "/awx_devel/awx.egg-info" ] || $(PYTHON) /awx_devel/tools/scripts/egg_info_dev

TEST_DIRS ?= awx/main/tests/unit awx/main/tests/functional awx/conf/tests awx/sso/tests
TEST_DIRS ?= awx/main/tests/unit awx/main/tests/functional awx/conf/tests
PYTEST_ARGS ?= -n auto
## Run all API unit tests.
test:
Expand Down Expand Up @@ -446,7 +446,7 @@ test_unit:
@if [ "$(VENV_BASE)" ]; then \
. $(VENV_BASE)/awx/bin/activate; \
fi; \
py.test awx/main/tests/unit awx/conf/tests/unit awx/sso/tests/unit
py.test awx/main/tests/unit awx/conf/tests/unit

## Output test coverage as HTML (into htmlcov directory).
coverage_html:
Expand Down
3 changes: 1 addition & 2 deletions awx/api/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from awx.conf import fields, register, register_validate
from awx.api.fields import OAuth2ProviderField
from oauth2_provider.settings import oauth2_settings
from awx.sso.common import is_remote_auth_enabled


register(
Expand Down Expand Up @@ -109,7 +108,7 @@


def authentication_validate(serializer, attrs):
if attrs.get('DISABLE_LOCAL_AUTH', False) and not is_remote_auth_enabled():
if attrs.get('DISABLE_LOCAL_AUTH', False):
raise serializers.ValidationError(_("There are no remote authentication systems configured."))
return attrs

Expand Down
23 changes: 2 additions & 21 deletions awx/api/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,8 +134,6 @@
# AWX Utils
from awx.api.validators import HostnameRegexValidator

from awx.sso.common import get_external_account

logger = logging.getLogger('awx.api.serializers')

# Fields that should be summarized regardless of object type.
Expand Down Expand Up @@ -961,8 +959,6 @@ def get_types(self):

class UserSerializer(BaseSerializer):
password = serializers.CharField(required=False, default='', help_text=_('Field used to change the password.'))
ldap_dn = serializers.CharField(source='profile.ldap_dn', read_only=True)
external_account = serializers.SerializerMethodField(help_text=_('Set if the account is managed by an external service'))
is_system_auditor = serializers.BooleanField(default=False)
show_capabilities = ['edit', 'delete']

Expand All @@ -979,22 +975,13 @@ class Meta:
'is_superuser',
'is_system_auditor',
'password',
'ldap_dn',
'last_login',
'external_account',
)
extra_kwargs = {'last_login': {'read_only': True}}

def to_representation(self, obj):
ret = super(UserSerializer, self).to_representation(obj)
if self.get_external_account(obj):
# If this is an external account it shouldn't have a password field
ret.pop('password', None)
else:
# If its an internal account lets assume there is a password and return $encrypted$ to the user
ret['password'] = '$encrypted$'
if obj and type(self) is UserSerializer:
ret['auth'] = obj.social_auth.values('provider', 'uid')
ret['password'] = '$encrypted$'
return ret

def get_validation_exclusions(self, obj=None):
Expand Down Expand Up @@ -1027,10 +1014,7 @@ def validate_password(self, value):
return value

def _update_password(self, obj, new_password):
# For now we're not raising an error, just not saving password for
# users managed by LDAP who already have an unusable password set.
# Get external password will return something like ldap or enterprise or None if the user isn't external. We only want to allow a password update for a None option
if new_password and new_password != '$encrypted$' and not self.get_external_account(obj):
if new_password and new_password != '$encrypted$':
obj.set_password(new_password)
obj.save(update_fields=['password'])

Expand All @@ -1045,9 +1029,6 @@ def _update_password(self, obj, new_password):
obj.set_unusable_password()
obj.save(update_fields=['password'])

def get_external_account(self, obj):
return get_external_account(obj)

def create(self, validated_data):
new_password = validated_data.pop('password', None)
is_system_auditor = validated_data.pop('is_system_auditor', None)
Expand Down
40 changes: 14 additions & 26 deletions awx/api/views/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,6 @@
# ansi2html
from ansi2html import Ansi2HTMLConverter

# Python Social Auth
from social_core.backends.utils import load_backends

# Django OAuth Toolkit
from oauth2_provider.models import get_access_token_model

Expand Down Expand Up @@ -129,6 +126,9 @@
from awx.api.pagination import UnifiedJobEventPagination
from awx.main.utils import set_environ

if 'ansible_base.authentication' in getattr(settings, "INSTALLED_APPS", []):
from ansible_base.authentication.models.authenticator import Authenticator as AnsibleBaseAuthenticator

Check warning on line 130 in awx/api/views/__init__.py

View check run for this annotation

Codecov / codecov/patch

awx/api/views/__init__.py#L130

Added line #L130 was not covered by tests

logger = logging.getLogger('awx.api.views')


Expand Down Expand Up @@ -684,30 +684,18 @@ class AuthView(APIView):
swagger_topic = 'System Configuration'

def get(self, request):
from rest_framework.reverse import reverse

data = OrderedDict()
err_backend, err_message = request.session.get('social_auth_error', (None, None))
auth_backends = list(load_backends(settings.AUTHENTICATION_BACKENDS, force_load=True).items())
# Return auth backends in consistent order: Google, GitHub, SAML.
auth_backends.sort(key=lambda x: 'g' if x[0] == 'google-oauth2' else x[0])
for name, backend in auth_backends:
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 sorted(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 or err_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
if 'ansible_base.authentication' in getattr(settings, "INSTALLED_APPS", []):
# app is using ansible_base authentication
# add ansible_base authenticators
authenticators = AnsibleBaseAuthenticator.objects.filter(enabled=True, category="sso")

Check warning on line 691 in awx/api/views/__init__.py

View check run for this annotation

Codecov / codecov/patch

awx/api/views/__init__.py#L691

Added line #L691 was not covered by tests
for authenticator in authenticators:
login_url = authenticator.get_login_url()
data[authenticator.name] = {

Check warning on line 694 in awx/api/views/__init__.py

View check run for this annotation

Codecov / codecov/patch

awx/api/views/__init__.py#L693-L694

Added lines #L693 - L694 were not covered by tests
'login_url': login_url,
'name': authenticator.name,
}

return Response(data)


Expand Down
15 changes: 0 additions & 15 deletions awx/conf/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,18 +61,3 @@ def on_post_delete_setting(sender, **kwargs):
key = getattr(instance, '_saved_key_', None)
if key:
handle_setting_change(key, True)


@receiver(setting_changed)
def disable_local_auth(**kwargs):
if (kwargs['setting'], kwargs['value']) == ('DISABLE_LOCAL_AUTH', True):
from django.contrib.auth.models import User
from oauth2_provider.models import RefreshToken
from awx.main.models.oauth import OAuth2AccessToken
from awx.main.management.commands.revoke_oauth2_tokens import revoke_tokens

logger.warning("Triggering token invalidation for local users.")

qs = User.objects.filter(profile__ldap_dn='', enterprise_auth__isnull=True, social_auth__isnull=True)
revoke_tokens(RefreshToken.objects.filter(revoked=None, user__in=qs))
revoke_tokens(OAuth2AccessToken.objects.filter(user__in=qs))
19 changes: 0 additions & 19 deletions awx/conf/tests/functional/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
from awx.conf import fields
from awx.conf.registry import settings_registry
from awx.conf.models import Setting
from awx.sso import fields as sso_fields


@pytest.fixture
Expand Down Expand Up @@ -103,24 +102,6 @@ def test_setting_singleton_update(api_request, dummy_setting):
assert response.data['FOO_BAR'] == 4


@pytest.mark.django_db
def test_setting_singleton_update_hybriddictfield_with_forbidden(api_request, dummy_setting):
# Some HybridDictField subclasses have a child of _Forbidden,
# indicating that only the defined fields can be filled in. Make
# sure that the _Forbidden validator doesn't get used for the
# fields. See also https://github.com/ansible/awx/issues/4099.
with dummy_setting('FOO_BAR', field_class=sso_fields.SAMLOrgAttrField, category='FooBar', category_slug='foobar'), mock.patch(
'awx.conf.views.clear_setting_cache'
):
api_request(
'patch',
reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}),
data={'FOO_BAR': {'saml_admin_attr': 'Admins', 'saml_attr': 'Orgs'}},
)
response = api_request('get', reverse('api:setting_singleton_detail', kwargs={'category_slug': 'foobar'}))
assert response.data['FOO_BAR'] == {'saml_admin_attr': 'Admins', 'saml_attr': 'Orgs'}


@pytest.mark.django_db
def test_setting_singleton_update_dont_change_readonly_fields(api_request, dummy_setting):
with dummy_setting('FOO_BAR', field_class=fields.IntegerField, read_only=True, default=4, category='FooBar', category_slug='foobar'), mock.patch(
Expand Down
5 changes: 1 addition & 4 deletions awx/main/access.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,10 +642,7 @@ class UserAccess(BaseAccess):
"""

model = User
prefetch_related = (
'profile',
'resource',
)
prefetch_related = ('resource',)

def filtered_queryset(self):
if settings.ORG_ADMINS_CAN_SEE_ALL_USERS and (self.user.admin_of_organizations.exists() or self.user.auditor_of_organizations.exists()):
Expand Down
4 changes: 2 additions & 2 deletions awx/main/middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -93,8 +93,8 @@ def process_request(self, request):
user = request.user
if not user.pk:
return
if not (user.profile.ldap_dn or user.social_auth.exists() or user.enterprise_auth.exists()):
logout(request)

logout(request)

Check warning on line 97 in awx/main/middleware.py

View check run for this annotation

Codecov / codecov/patch

awx/main/middleware.py#L97

Added line #L97 was not covered by tests


class URLModificationMiddleware(MiddlewareMixin):
Expand Down
16 changes: 16 additions & 0 deletions awx/main/migrations/0196_delete_profile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Generated by Django 4.2.10 on 2024-09-16 10:22

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('main', '0195_EE_permissions'),
]

operations = [
migrations.DeleteModel(
name='Profile',
),
]
27 changes: 27 additions & 0 deletions awx/main/migrations/0197_remove_sso_app_content.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
# Generated by Django 4.2.10 on 2024-09-16 15:21

from django.db import migrations


class Migration(migrations.Migration):

dependencies = [
('main', '0196_delete_profile'),
]

operations = [
# delete all sso application migrations
migrations.RunSQL("DELETE FROM django_migrations WHERE app = 'sso';"),
# delete all sso application content group permissions
migrations.RunSQL(
"DELETE FROM auth_group_permissions "
"WHERE permission_id IN "
"(SELECT id FROM auth_permission WHERE content_type_id in (SELECT id FROM django_content_type WHERE app_label = 'sso'));"
),
# delete all sso application content permissions
migrations.RunSQL("DELETE FROM auth_permission " "WHERE content_type_id IN (SELECT id FROM django_content_type WHERE app_label = 'sso');"),
# delete sso application content type
migrations.RunSQL("DELETE FROM django_content_type WHERE app_label = 'sso';"),
# drop sso application created table
migrations.RunSQL("DROP TABLE IF EXISTS sso_userenterpriseauth;"),
]
16 changes: 1 addition & 15 deletions awx/main/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
# AWX
from awx.main.models.base import BaseModel, PrimordialModel, accepts_json, CLOUD_INVENTORY_SOURCES, VERBOSITY_CHOICES # noqa
from awx.main.models.unified_jobs import UnifiedJob, UnifiedJobTemplate, StdoutMaxBytesExceeded # noqa
from awx.main.models.organization import Organization, Profile, Team, UserSessionMembership # noqa
from awx.main.models.organization import Organization, Team, UserSessionMembership # noqa
from awx.main.models.credential import Credential, CredentialType, CredentialInputSource, ManagedCredentialType, build_safe_env # noqa
from awx.main.models.projects import Project, ProjectUpdate # noqa
from awx.main.models.receptor_address import ReceptorAddress # noqa
Expand Down Expand Up @@ -244,20 +244,6 @@ def user_is_system_auditor(user, tf):
User.add_to_class('is_system_auditor', user_is_system_auditor)


def user_is_in_enterprise_category(user, category):
ret = (category,) in user.enterprise_auth.values_list('provider') and not user.has_usable_password()
# NOTE: this if-else block ensures existing enterprise users are still able to
# log in. Remove it in a future release
if category == 'radius':
ret = ret or not user.has_usable_password()
elif category == 'saml':
ret = ret or user.social_auth.all()
return ret


User.add_to_class('is_in_enterprise_category', user_is_in_enterprise_category)


def o_auth2_application_get_absolute_url(self, request=None):
return reverse('api:o_auth2_application_detail', kwargs={'pk': self.pk}, request=request)

Expand Down
12 changes: 0 additions & 12 deletions awx/main/models/oauth.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,7 @@
# Django OAuth Toolkit
from oauth2_provider.models import AbstractApplication, AbstractAccessToken
from oauth2_provider.generators import generate_client_secret
from oauthlib import oauth2

from awx.sso.common import get_external_account
from awx.main.fields import OAuth2ClientSecretField


Expand Down Expand Up @@ -123,15 +121,5 @@ def _update_last_used():
connection.on_commit(_update_last_used)
return valid

def validate_external_users(self):
if self.user and settings.ALLOW_OAUTH2_FOR_EXTERNAL_USERS is False:
external_account = get_external_account(self.user)
if external_account is not None:
raise oauth2.AccessDeniedError(
_('OAuth2 Tokens cannot be created by users associated with an external authentication provider ({})').format(external_account)
)

def save(self, *args, **kwargs):
if not self.pk:
self.validate_external_users()
super(OAuth2AccessToken, self).save(*args, **kwargs)
22 changes: 3 additions & 19 deletions awx/main/models/organization.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,16 @@

# AWX
from awx.api.versioning import reverse
from awx.main.fields import AutoOneToOneField, ImplicitRoleField, OrderedManyToManyField
from awx.main.models.base import BaseModel, CommonModel, CommonModelNameNotUnique, CreatedModifiedModel, NotificationFieldsModel
from awx.main.fields import ImplicitRoleField, OrderedManyToManyField
from awx.main.models.base import BaseModel, CommonModel, CommonModelNameNotUnique, NotificationFieldsModel
from awx.main.models.rbac import (
ROLE_SINGLETON_SYSTEM_ADMINISTRATOR,
ROLE_SINGLETON_SYSTEM_AUDITOR,
)
from awx.main.models.unified_jobs import UnifiedJob
from awx.main.models.mixins import ResourceMixin, CustomVirtualEnvMixin, RelatedJobsMixin

__all__ = ['Organization', 'Team', 'Profile', 'UserSessionMembership']
__all__ = ['Organization', 'Team', 'UserSessionMembership']


class Organization(CommonModel, NotificationFieldsModel, ResourceMixin, CustomVirtualEnvMixin, RelatedJobsMixin):
Expand Down Expand Up @@ -167,22 +167,6 @@ def get_absolute_url(self, request=None):
return reverse('api:team_detail', kwargs={'pk': self.pk}, request=request)


class Profile(CreatedModifiedModel):
"""
Profile model related to User object. Currently stores LDAP DN for users
loaded from LDAP.
"""

class Meta:
app_label = 'main'

user = AutoOneToOneField('auth.User', related_name='profile', editable=False, on_delete=models.CASCADE)
ldap_dn = models.CharField(
max_length=1024,
default='',
)


class UserSessionMembership(BaseModel):
"""
A lookup table for API session membership given user. Note, there is a
Expand Down
Loading

0 comments on commit 3e19f6f

Please sign in to comment.