Skip to content

Commit

Permalink
Upstream changes (Auto Approve, Social Auth, Test cases etc)
Browse files Browse the repository at this point in the history
* login with github

* added django template

* removed django template

* initial commit

* remove user groups

* updated urls

* initial commit

* Updated dependency issues

* Datamesh update (#11)

* Datamesh async fixed

* Datamesh join function script

* Email alert integration (#13)

* Integration - Email Notification

* Integration - Email Notification

* updated serializer

* updated unit test

* datamesh join script (#14)

* datamesh join script

* datamesh join script

* comment removed

* updated datamesh join script

* updated datamesh join script

* updated join_record_datamesh function

* removed print statement

* Partner implementation (#17)

* Partner Implementation

* removed organization

* removed partner organization

* removed partner organization

* - partner unit test

* - updated test fixtures

* - removed comment

* Resolve permissions for Organization

* fix core endpoint issue (#21)

* fix core endpoint issue

* updated comment

* Updated environment variables

* Updated environment values

* Handle for Gateway response 400

* datamesh join:
- product <-> third party tool and
- product <-> product_team

* completed project tool service datamesh join

* completed release service datamesh join

* completed dev/partner service datamesh join

* Fix Datamesh request issue

* updated admin username and password

* revert find endpoint fix

* updated datamesh join module name

* Added script to load datamesh

* Handled blank response for join records

* datamesh POST/PUT request implementation

* updated post request for different use-cases

* datamesh implemented delete request

* post empty response validation

* updated datamesh script for pk

* converted into functions

* updated put request

* updated PUT request for case -> The relation that exists but needs to create join

* Allow users to be auto-approved after registration

* added fields on Relationship model

* model via forward lookup

* param service name via forward_lookup

* written function for the case - Handles The relation that only needs to update ID or UUID

* Get response after a POST and PUT request

* refactor the functions

* updated func parameter

* issue fix - delete join

* delete status join

* migration file

* User Type Implementation (#45)

* added user type

* admin dashboard

* added survey_status

* serializer fields added

* help text

* User profile update API (#48)

* user profile update API

* updated get_permission

* unit test cases

* added serializer field

* serializer organization_name validation

* updated core-user serializer update method

* User registration update (#53)

* register with org validation

* uncommented invitation_token method

* updated load initial script for org creation

* set auto approve to false

* core user create method

* default org name

* added new var to update

* code format

* updated gunicorn timeout

* removed unwanted files from upstream pr

* updated migration files

* added new line

* removed unwanted changes

* removed prepare_get_request() function

* updated DEFAULT_ORG to lowercase

* Fix None type DEFAULT_ORG issue

Co-authored-by: ashishkmishra36 <ashish.kumar@ajackus.com>
Co-authored-by: Ashish K Mishra <70134840+ashishkmishra36@users.noreply.github.com>
Co-authored-by: Yasmin Ansari <yasmin@ajackus.com>
Co-authored-by: mthombare <83965396+mthombare@users.noreply.github.com>
Co-authored-by: manish <manish.thombare@ajackus.com>
Co-authored-by: Yasmin Ansari <yasmin.ansari@ajackus.com>
Co-authored-by: Greg Lind <gwlind@gmail.com>
  • Loading branch information
8 people committed May 25, 2022
1 parent efba3f7 commit fb76767
Show file tree
Hide file tree
Showing 17 changed files with 337 additions and 34 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ The following tables list the configurable parameters of buildly and their defau
| `DEFAULT_ORG` | The first organization created in the database | `` |
| `SECRET_KEY` | Used to provide cryptographic signing, and should be set to a unique, unpredictable value | None |
| `SUPER_USER_PASSWORD` | Used to define the super user password when it's created for the first time | `admin` in Debug mode or None |
| `AUTO_APPROVE_USER` | If approval process is set to auto-approve users can automatically login without admin apprvoal | False |

#### Database Connection
| Parameter | Description | Default |
Expand Down
4 changes: 3 additions & 1 deletion buildly/settings/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@
# Github social auth
SOCIAL_AUTH_GITHUB_KEY = os.getenv('SOCIAL_AUTH_GITHUB_KEY', '')
SOCIAL_AUTH_GITHUB_SECRET = os.getenv('SOCIAL_AUTH_GITHUB_SECRET', '')
SOCIAL_AUTH_GITHUB_SCOPE = ['email']

# Google social auth
SOCIAL_AUTH_GOOGLE_OAUTH2_KEY = os.getenv('SOCIAL_AUTH_GOOGLE_OAUTH2_KEY', '')
Expand All @@ -115,7 +116,8 @@
# Whitelist of domains allowed to login via social auths
# i.e. ['example.com', 'buildly.io','treeaid.org']
if os.getenv('SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS'):
SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = os.getenv('SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS').split(',')
SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS = os.getenv(
'SOCIAL_AUTH_GOOGLE_OAUTH2_WHITELISTED_DOMAINS').split(',')
if os.getenv('SOCIAL_AUTH_MICROSOFT_WHITELISTED_DOMAINS'):
SOCIAL_AUTH_GOOGLE_MICROSOFT_DOMAINS = os.getenv('SOCIAL_AUTH_MICROSOFT_WHITELISTED_DOMAINS').split(',')

Expand Down
3 changes: 2 additions & 1 deletion buildly/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@

# User and Organization configuration
SUPER_USER_PASSWORD = os.getenv('SUPER_USER_PASSWORD')
DEFAULT_ORG = os.getenv('DEFAULT_ORG')
DEFAULT_ORG = os.getenv('DEFAULT_ORG').lower() if os.getenv('DEFAULT_ORG') else None
AUTO_APPROVE_USER = os.getenv('AUTO_APPROVE_USER', False)

# Swagger settings - for generate_swagger management command

Expand Down
2 changes: 1 addition & 1 deletion buildly/settings/production.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
MIDDLEWARE = MIDDLEWARE_CORS + MIDDLEWARE


CORS_ORIGIN_ALLOW_ALL = os.getenv('CORS_ORIGIN_ALLOW_ALL', False)
CORS_ORIGIN_ALLOW_ALL = True

CORS_ORIGIN_WHITELIST = os.environ['CORS_ORIGIN_WHITELIST'].split(',')

Expand Down
6 changes: 3 additions & 3 deletions core/admin.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,16 @@ class OrganizationAdmin(admin.ModelAdmin):


class CoreGroupAdmin(admin.ModelAdmin):
list_display = ('name', 'organization', 'is_global', 'is_org_level', 'is_default', 'permissions')
list_display = ('id', 'name', 'organization', 'is_global', 'is_org_level', 'is_default', 'permissions')
display = 'Core Group'
search_fields = ('name', 'organization__name', )


class CoreUserAdmin(UserAdmin):
list_display = ('username', 'first_name', 'last_name', 'organization', 'is_active')
list_display = ('username', 'first_name', 'last_name', 'email', 'organization', 'is_active')
display = 'Core User'
list_filter = ('is_staff', 'organization')
search_fields = ('first_name', 'first_name', 'username', 'title', 'organization__name', )
search_fields = ('first_name', 'last_name', 'username', 'title', 'organization__name', )
fieldsets = (
(None, {'fields': ('username', 'password')}),
(_('Personal info'), {'fields': ('title', 'first_name', 'last_name', 'email', 'contact_info', 'organization')}),
Expand Down
155 changes: 150 additions & 5 deletions core/serializers.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import jwt
import secrets

from urllib.parse import urljoin

from django.contrib.auth import password_validation
Expand All @@ -17,7 +16,7 @@
from core.email_utils import send_email, send_email_body

from core.models import CoreUser, CoreGroup, EmailTemplate, LogicModule, Organization, PERMISSIONS_ORG_ADMIN, \
TEMPLATE_RESET_PASSWORD
TEMPLATE_RESET_PASSWORD, PERMISSIONS_VIEW_ONLY


class LogicModuleSerializer(serializers.ModelSerializer):
Expand Down Expand Up @@ -120,13 +119,19 @@ class Meta:
def create(self, validated_data):
# get or create organization
organization = validated_data.pop('organization')
organization, is_new_org = Organization.objects.get_or_create(**organization)

org_name = organization['name']
organization, is_new_org = Organization.objects.get_or_create(name=str(org_name).lower())

core_groups = validated_data.pop('core_groups', [])
invitation_token = validated_data.pop('invitation_token', None)

# create core user
invitation_token = validated_data.pop('invitation_token', None)
validated_data['is_active'] = is_new_org or bool(invitation_token)
if bool(settings.AUTO_APPROVE_USER): # If auto-approval set to true
validated_data['is_active'] = True
else:
validated_data['is_active'] = is_new_org or bool(invitation_token)

coreuser = CoreUser.objects.create(
organization=organization,
**validated_data
Expand All @@ -135,6 +140,23 @@ def create(self, validated_data):
coreuser.set_password(validated_data['password'])
coreuser.save()

# check whether org_name is "default"
if org_name in ['default']:
default_org_user = CoreGroup.objects.filter(organization__name=settings.DEFAULT_ORG,
is_org_level=True,
permissions=PERMISSIONS_VIEW_ONLY).first()
coreuser.core_groups.add(default_org_user)

# check whether an old organization
if not is_new_org:
coreuser.is_active = False
coreuser.save()

org_user = CoreGroup.objects.filter(organization__name=organization,
is_org_level=True,
permissions=PERMISSIONS_VIEW_ONLY).first()
coreuser.core_groups.add(org_user)

# add org admin role to the user if org is new
if is_new_org:
group_org_admin = CoreGroup.objects.get(organization=organization,
Expand All @@ -149,6 +171,42 @@ def create(self, validated_data):
return coreuser


class CoreUserProfileSerializer(serializers.Serializer):
""" Let's user update his first_name,last_name,title,contact_info,
password and organization_name """

first_name = serializers.CharField(required=False)
last_name = serializers.CharField(required=False)
title = serializers.CharField(required=False)
contact_info = serializers.CharField(required=False)
password = serializers.CharField(required=False)
organization_name = serializers.CharField(required=False)

class Meta:
model = CoreUser
fields = ('first_name', 'last_name', 'password', 'title', 'contact_info', 'organization_name')

def update(self, instance, validated_data):

organization_name = validated_data.pop('organization_name', None).lower()

name = Organization.objects.filter(name=organization_name).first()
if name is not None:
instance.organization = name
instance.organization_name = name

instance.first_name = validated_data.get('first_name', instance.first_name)
instance.last_name = validated_data.get('last_name', instance.last_name)
instance.title = validated_data.get('title', instance.title)
instance.contact_info = validated_data.get('contact_info', instance.contact_info)
password = validated_data.get('password', None)
if password is not None:
instance.set_password(password)
instance.save()

return instance


class CoreUserInvitationSerializer(serializers.Serializer):
emails = serializers.ListField(child=serializers.EmailField(),
min_length=1, max_length=10)
Expand Down Expand Up @@ -278,3 +336,90 @@ def create(self, validated_data):
validated_data['client_id'] = secrets.token_urlsafe(75)
validated_data['client_secret'] = secrets.token_urlsafe(190)
return super(ApplicationSerializer, self).create(validated_data)


class CoreUserUpdateOrganizationSerializer(serializers.ModelSerializer):
""" Let's user update his organization_name,and email from the one time pop-up screen.
Also this assigns permissions to users """

email = serializers.CharField(required=False)
organization_name = serializers.CharField(required=False)
core_groups = CoreGroupSerializer(read_only=True, many=True)
organization = OrganizationSerializer(read_only=True)

class Meta:
model = CoreUser
fields = ('id', 'core_user_uuid', 'first_name', 'last_name', 'email', 'username', 'is_active', 'title',
'contact_info', 'privacy_disclaimer_accepted', 'organization_name', 'organization', 'core_groups')

def update(self, instance, validated_data):

organization_name = str(validated_data.pop('organization_name')).lower()
instance.email = validated_data.get('email', instance.email)

if instance.email is not None:
instance.save()
is_new_org = Organization.objects.filter(name=organization_name)

# check whether org_name is "default"
if organization_name == 'default':
default_org = Organization.objects.filter(name=settings.DEFAULT_ORG).first()
instance.organization = default_org
instance.save()
# now attach the user role as USER to default organization
default_org_user = CoreGroup.objects.filter(organization__name=settings.DEFAULT_ORG,
is_org_level=True,
permissions=PERMISSIONS_VIEW_ONLY).first()
instance.core_groups.add(default_org_user)

# remove any other group permissions he is not added
for single_group in instance.core_groups.all():
default_org_groups = CoreGroup.objects.filter(organization__name=settings.DEFAULT_ORG,
is_org_level=True,
permissions=PERMISSIONS_VIEW_ONLY)
if single_group not in default_org_groups:
instance.core_groups.remove(single_group)

# check whether an old organization
elif is_new_org.exists():

organization = Organization.objects.get(name=organization_name)
instance.organization = organization
instance.is_active = False
instance.save()
# now attach the user role as USER
org_user = CoreGroup.objects.filter(organization__name=organization_name,
is_org_level=True,
permissions=PERMISSIONS_VIEW_ONLY).first()

instance.core_groups.add(org_user)

# remove any other group permissions he is not added
for single_group in instance.core_groups.all():
org_groups = CoreGroup.objects.filter(organization__name=organization_name,
is_org_level=True,
permissions=PERMISSIONS_VIEW_ONLY)
if single_group not in org_groups:
instance.core_groups.remove(single_group)

# if the current user is the first user
elif not is_new_org.exists():
# first update the org name for that user
organization = Organization.objects.create(name=organization_name)
instance.organization = organization
instance.save()
# now attach the user role as ADMIN
org_admin = CoreGroup.objects.get(organization=organization,
is_org_level=True,
permissions=PERMISSIONS_ORG_ADMIN)
instance.core_groups.add(org_admin)

return instance


class CoreUserEmailNotificationSerializer(serializers.Serializer):
"""
Serializer for email Notification
"""
organization_uuid = serializers.UUIDField()
notification_messages = serializers.CharField()
2 changes: 1 addition & 1 deletion core/tests/fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
'email': 'test@example.com',
'username': 'johnsnow',
'password': '123qwe',
'organization_name': 'Buidly',
'organization_name': 'buidly',
'organization_uuid': uuid.uuid4(),
}

Expand Down
30 changes: 28 additions & 2 deletions core/tests/test_coreuserview.py
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,7 @@ def test_registration_of_invited_org_user(self, request_factory, org_admin):
assert user.first_name == TEST_USER_DATA['first_name']
assert user.last_name == TEST_USER_DATA['last_name']
assert user.organization.name == TEST_USER_DATA['organization_name']
assert user.is_active
assert not user.is_active

# check this user is NOT org admin
assert not user.is_org_admin
Expand All @@ -162,7 +162,7 @@ def test_reused_token_invalidation(self, request_factory, org_admin):
request = request_factory.post(reverse('coreuser-list'), data)
response = CoreUserViewSet.as_view({'post': 'create'})(request)
assert response.status_code == 400

def test_email_mismatch_token_invalidation(self, request_factory, org_admin):
data = TEST_USER_DATA.copy()
token = create_invitation_token("foobar@example.com", org_admin.organization)
Expand Down Expand Up @@ -233,6 +233,18 @@ def test_coreuser_update_groups(self, request_factory, org_admin):
coreuser = CoreUser.objects.get(pk=pk)
assert set(coreuser.core_groups.all()) == set(new_groups)

def test_coreuser_profile_update(self, request_factory, org_admin):
user = factories.CoreUser.create(is_active=False, organization=org_admin.organization, username='org_user')
pk = user.pk

data = {'contact_info': "data"}

request = request_factory.patch(reverse('coreuser-update-profile', args=(pk,)), data)
request.user = org_admin
response = CoreUserViewSet.as_view({'patch': 'partial_update'})(request, pk=pk)
assert response.status_code == 200
coreuser = CoreUser.objects.get(pk=pk)
assert coreuser.contact_info == "data"

@pytest.mark.django_db()
class TestCoreUserInvite:
Expand Down Expand Up @@ -453,3 +465,17 @@ def test_coreuser_retrieve_me(self, request_factory, org_member):
response = CoreUserViewSet.as_view({'get': 'me'})(request)
assert response.status_code == 200
assert response.data['username'] == org_member.username


@pytest.mark.django_db()
class TestNotification:

def test_notification(self, request_factory):
dif_org = factories.Organization(name='Another Org')
data = {
"organization_uuid": dif_org.organization_uuid,
"notification_messages": "message"
}
request = request_factory.post(reverse('coreuser-notification'), data)
response = CoreUserViewSet.as_view({'post': 'notification'})(request)
assert response.status_code == 200
Loading

0 comments on commit fb76767

Please sign in to comment.