Skip to content

Commit 06a8988

Browse files
authored
feat: TT-169 add enpoints to add/remove users to/from groups (#263)
* feat: TT-169 add endpoint namespaces * feat: TT-169 add/remove user to/from group * feat: TT-169 use get_user info * test: TT-169 add test for add/remove users of groups * test: TT-169 add namespace tests * test: TT-169 add expected user value
1 parent 8e42184 commit 06a8988

File tree

4 files changed

+177
-11
lines changed

4 files changed

+177
-11
lines changed

tests/time_tracker_api/users/users_namespace_test.py

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,45 @@ def test_if_user_is_in_group(
144144
)
145145
assert HTTPStatus.OK == response.status_code
146146
assert 'value' in json.loads(response.data)
147+
148+
149+
@patch('utils.azure_users.AzureConnection.get_msal_client', Mock())
150+
@patch('utils.azure_users.AzureConnection.get_token', Mock())
151+
@patch('utils.azure_users.AzureConnection.add_user_to_group')
152+
def test_add_to_group(
153+
add_user_to_group_mock,
154+
client: FlaskClient,
155+
valid_header: dict,
156+
user_id: str,
157+
):
158+
159+
add_user_to_group_mock.return_value = {}
160+
valid_data = {'group_name': 'dummy_group'}
161+
162+
response = client.post(
163+
f'/users/{user_id}/groups/add', headers=valid_header, json=valid_data
164+
)
165+
assert HTTPStatus.OK == response.status_code
166+
add_user_to_group_mock.assert_called_once()
167+
168+
169+
@patch('utils.azure_users.AzureConnection.get_msal_client', Mock())
170+
@patch('utils.azure_users.AzureConnection.get_token', Mock())
171+
@patch('utils.azure_users.AzureConnection.remove_user_from_group')
172+
def test_remove_from_group(
173+
remove_user_from_group_mock,
174+
client: FlaskClient,
175+
valid_header: dict,
176+
user_id: str,
177+
):
178+
179+
remove_user_from_group_mock.return_value = {}
180+
valid_data = {'group_name': 'dummy_group'}
181+
182+
response = client.post(
183+
f'/users/{user_id}/groups/remove',
184+
headers=valid_header,
185+
json=valid_data,
186+
)
187+
assert HTTPStatus.OK == response.status_code
188+
remove_user_from_group_mock.assert_called_once()

tests/utils/azure_users_test.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -176,3 +176,55 @@ def test_get_groups_and_users_called_once_by_instance(
176176
azure_connection.get_groups_by_user_id(user_id)
177177

178178
get_groups_and_users_mock.assert_called_once()
179+
180+
181+
@patch('utils.azure_users.AzureConnection.get_msal_client', Mock())
182+
@patch('utils.azure_users.AzureConnection.get_token', Mock())
183+
@patch('utils.azure_users.AzureConnection.get_user')
184+
@patch('utils.azure_users.AzureConnection.get_group_id_by_group_name')
185+
@patch('requests.post')
186+
def test_add_user_to_group(
187+
post_mock, get_group_id_by_group_name_mock, get_user_mock
188+
):
189+
get_group_id_by_group_name_mock.return_value = 'dummy_group'
190+
test_user = AzureUser('ID1', None, None, [], [])
191+
get_user_mock.return_value = test_user
192+
193+
response_mock = Mock()
194+
response_mock.status_code = 204
195+
post_mock.return_value = response_mock
196+
197+
azure_connection = AzureConnection()
198+
expected_value = azure_connection.add_user_to_group(
199+
'dummy_user_id', 'dummy_group'
200+
)
201+
202+
get_group_id_by_group_name_mock.assert_called_once()
203+
get_user_mock.assert_called_once()
204+
assert expected_value == test_user
205+
206+
207+
@patch('utils.azure_users.AzureConnection.get_msal_client', Mock())
208+
@patch('utils.azure_users.AzureConnection.get_token', Mock())
209+
@patch('utils.azure_users.AzureConnection.get_user')
210+
@patch('utils.azure_users.AzureConnection.get_group_id_by_group_name')
211+
@patch('requests.delete')
212+
def test_remove_user_from_group(
213+
delete_mock, get_group_id_by_group_name_mock, get_user_mock
214+
):
215+
get_group_id_by_group_name_mock.return_value = 'dummy_group'
216+
test_user = AzureUser('ID1', None, None, [], [])
217+
get_user_mock.return_value = test_user
218+
219+
response_mock = Mock()
220+
response_mock.status_code = 204
221+
delete_mock.return_value = response_mock
222+
223+
azure_connection = AzureConnection()
224+
expected_value = azure_connection.remove_user_from_group(
225+
'dummy_user_id', 'dummy_group'
226+
)
227+
228+
get_group_id_by_group_name_mock.assert_called_once()
229+
get_user_mock.assert_called_once()
230+
assert expected_value == test_user

time_tracker_api/users/users_namespace.py

Lines changed: 53 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -51,19 +51,16 @@
5151
},
5252
)
5353

54+
group_name_field = fields.String(
55+
title='group_name',
56+
max_length=50,
57+
description='Name of the Group',
58+
example=Faker().word(['time-tracker-admin', 'time-tracker-tester']),
59+
)
60+
5461
# Data to check if a user is in the group
5562
user_in_group_input = ns.model(
56-
'UserInGroupInput',
57-
{
58-
'group_name': fields.String(
59-
title='group_name',
60-
max_length=50,
61-
description='Name of the Group to verify',
62-
example=Faker().word(
63-
['time-tracker-admin', 'time-tracker-tester']
64-
),
65-
),
66-
},
63+
'UserInGroupInput', {'group_name': group_name_field},
6764
)
6865

6966
user_in_group_response = ns.model(
@@ -146,3 +143,48 @@ class UserInGroup(Resource):
146143
def post(self, user_id):
147144
"""Check if user belongs to group"""
148145
return AzureConnection().is_user_in_group(user_id, ns.payload)
146+
147+
148+
add_user_to_group_input = ns.model(
149+
'AddUserToGroupInput', {'group_name': group_name_field},
150+
)
151+
152+
153+
@ns.route('/<string:user_id>/groups/add')
154+
@ns.param('user_id', 'The user identifier')
155+
class AddToGroup(Resource):
156+
@ns.doc('add_to_group')
157+
@ns.expect(add_user_to_group_input)
158+
@ns.marshal_with(user_response_fields)
159+
def post(self, user_id):
160+
"""
161+
Add user to an EXISTING group in the Azure Tenant directory.
162+
Available options for `group_name`:
163+
```
164+
- time-tracker-admin
165+
- time-tracker-tester
166+
```
167+
"""
168+
return AzureConnection().add_user_to_group(
169+
user_id, ns.payload['group_name']
170+
)
171+
172+
173+
remove_user_from_group_input = ns.model(
174+
'RemoveUserFromGroupInput', {'group_name': group_name_field},
175+
)
176+
177+
178+
@ns.route('/<string:user_id>/groups/remove')
179+
@ns.param('user_id', 'The user identifier')
180+
class RemoveFromGroup(Resource):
181+
@ns.doc('remove_from_group')
182+
@ns.expect(remove_user_from_group_input)
183+
@ns.marshal_with(user_response_fields)
184+
def post(self, user_id):
185+
"""
186+
Remove user from an EXISTING group in the Azure Tenant directory.
187+
"""
188+
return AzureConnection().remove_user_from_group(
189+
user_id, ns.payload['group_name']
190+
)

utils/azure_users.py

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,36 @@ def update_role(self, user_id, role_id, is_grant):
139139

140140
return self.to_azure_user(response.json())
141141

142+
def add_user_to_group(self, user_id, group_name):
143+
group_id = self.get_group_id_by_group_name(group_name)
144+
endpoint = "{endpoint}/groups/{group_id}/$links/members?api-version=1.6".format(
145+
endpoint=self.config.ENDPOINT, group_id=group_id,
146+
)
147+
data = {'url': f'{self.config.ENDPOINT}/directoryObjects/{user_id}'}
148+
response = requests.post(
149+
endpoint,
150+
auth=BearerAuth(self.access_token),
151+
data=json.dumps(data),
152+
headers=HTTP_PATCH_HEADERS,
153+
)
154+
assert 204 == response.status_code
155+
156+
return self.get_user(user_id)
157+
158+
def remove_user_from_group(self, user_id, group_name):
159+
group_id = self.get_group_id_by_group_name(group_name)
160+
endpoint = "{endpoint}/groups/{group_id}/$links/members/{user_id}?api-version=1.6".format(
161+
endpoint=self.config.ENDPOINT, group_id=group_id, user_id=user_id
162+
)
163+
response = requests.delete(
164+
endpoint,
165+
auth=BearerAuth(self.access_token),
166+
headers=HTTP_PATCH_HEADERS,
167+
)
168+
assert 204 == response.status_code
169+
170+
return self.get_user(user_id)
171+
142172
def get_non_test_users(self) -> List[AzureUser]:
143173
test_user_ids = self.get_test_user_ids()
144174
return [user for user in self.users() if user.id not in test_user_ids]

0 commit comments

Comments
 (0)