Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add AzureAD/EntraID functionality #1778

Merged
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
4b47173
:sparkles: Allow AzureADApplication to use public_client redirect as …
jemrobinson Feb 14, 2024
06fc735
:sparkles: Add GraphAPI function for granting delegated application r…
jemrobinson Feb 19, 2024
fde6eaf
:recycle: Grant permissions programmatically rather than requiring ma…
jemrobinson Feb 19, 2024
83d97d3
:sparkles: Add GraphAPI function for granting application role permis…
jemrobinson Feb 15, 2024
1c1443c
:sparkles: Add service principal creation
jemrobinson Feb 19, 2024
c3db73f
:sparkles: Add delegated_role_assignments option to AzureADApplication.
jemrobinson Feb 20, 2024
8b220e4
:alien: Use Enum when setting TXT record
jemrobinson Apr 8, 2024
f84350a
:rotating_light: Minor linting fix in graph_api.create_application
jemrobinson Apr 8, 2024
d2ffb39
:loud_sound: Improve errors when creating/updating users in GraphAPI
jemrobinson Apr 8, 2024
0c035ee
:bug: Extract username from userPrincipalName instead of trying to us…
jemrobinson Apr 8, 2024
ed95da2
:sparkles: Add remove_user function to GraphAPI
jemrobinson Apr 8, 2024
bd9ee41
:coffin: Remove unused AzureAD linux schema
jemrobinson Apr 8, 2024
67307be
:sparkles: Added missing methods to AzureAD users
jemrobinson Apr 8, 2024
05d192a
:bug: Ensure that GraphAPI token has correct permissions for granting…
jemrobinson Apr 9, 2024
40f7bf3
:recycle: Simplify user removal logic following suggestion from @JimM…
jemrobinson Apr 11, 2024
f627151
:bug: Remove infinite loop following suggestion from @JimMadge
jemrobinson Apr 11, 2024
c6fa939
:loud_sound: Improve docstrings for application role granting
jemrobinson Apr 11, 2024
10af228
:recycle: Improve code style/efficiency following suggestion from @Ji…
jemrobinson Apr 11, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
107 changes: 25 additions & 82 deletions data_safe_haven/administration/users/azure_ad_users.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections.abc import Sequence
from typing import Any

from data_safe_haven.exceptions import DataSafeHavenMicrosoftGraphError
from data_safe_haven.external import GraphApi
from data_safe_haven.functions import password
from data_safe_haven.utility import LoggingSingleton
Expand Down Expand Up @@ -46,16 +47,8 @@ def add(self, new_users: Sequence[ResearchUser]) -> None:
request_json, user.email_address, user.phone_number
)
self.logger.info(
f"Ensured user '{user.preferred_username}' exists in AzureAD"
f"Ensured user '[green]{user.preferred_username}[/]' exists in AzureAD"
)
# Decorate all users with the Linux schema
self.set_user_attributes()
# # Ensure that all users belong to an associated group the same name and UID
# for user in self.list():
# self.graph_api.create_group(user.username, user.uid_number)
# self.graph_api.add_user_to_group(user.username, user.username)
# # Also add the user to the research users group
# self.graph_api.add_user_to_group(user.username, self.researchers_group_name)

def list(self) -> Sequence[ResearchUser]:
user_list = self.graph_api.read_users()
Expand Down Expand Up @@ -84,84 +77,34 @@ def list(self) -> Sequence[ResearchUser]:
)
]

def register(self, sre_name: str, usernames: Sequence[str]) -> None:
"""Add usernames to SRE security group"""
group_name = f"Data Safe Haven SRE {sre_name} Users"
for username in usernames:
self.graph_api.add_user_to_group(username, group_name)

def remove(self, users: Sequence[ResearchUser]) -> None:
"""Disable a list of users in AzureAD"""
# for user_to_remove in users:
# matched_users = [user for user in self.users if user == user_to_remove]
# if not matched_users:
# continue
# user = matched_users[0]
# try:
# if self.graph_api.remove_user_from_group(
# user.username, self.researchers_group_name
# ):
# self.logger.info(
# f"Removed '{user.preferred_username}' from group '{self.researchers_group_name}'"
# )
# else:
# raise DataSafeHavenMicrosoftGraphError
# except DataSafeHavenMicrosoftGraphError:
# self.logger.error(
# f"Unable to remove '{user.preferred_username}' from group '{self.researchers_group_name}'"
# )
pass
"""Remove list of users from AzureAD"""
for user_to_remove in users:
matched_users = [user for user in self.list() if user == user_to_remove]
if not matched_users:
continue
user = matched_users[0]
jemrobinson marked this conversation as resolved.
Show resolved Hide resolved
try:
self.graph_api.remove_user(user.username)
self.logger.info(f"Removed '{user.preferred_username}'.")
except DataSafeHavenMicrosoftGraphError:
self.logger.error(f"Unable to remove '{user.preferred_username}'.")

def set(self, users: Sequence[ResearchUser]) -> None:
"""Set Guacamole users to specified list"""
"""Set AzureAD users to specified list"""
users_to_remove = [user for user in self.list() if user not in users]
self.remove(users_to_remove)
users_to_add = [user for user in users if user not in self.list()]
self.add(users_to_add)

def set_user_attributes(self) -> None:
"""Ensure that all users have Linux attributes"""
# next_uid = max(
# [int(user.uid_number) + 1 if user.uid_number else 0 for user in self.users]
# + [10000]
# )
# for user in self.users:
# try:
# # Get username from userPrincipalName
# username = user.user_principal_name.split("@")[0]
# if not user.homedir:
# user.homedir = f"/home/{username}"
# self.logger.debug(
# f"Added homedir {user.homedir} to user {user.preferred_username}"
# )
# if not user.shell:
# user.shell = "/bin/bash"
# self.logger.debug(
# f"Added shell {user.shell} to user {user.preferred_username}"
# )
# if not user.uid_number:
# # Set UID to the next unused one
# user.uid_number = next_uid
# next_uid += 1
# self.logger.debug(
# f"Added uid {user.uid_number} to user {user.preferred_username}"
# )
# if not user.username:
# user.username = username
# self.logger.debug(
# f"Added username {user.username} to user {user.preferred_username}"
# )
# # Ensure that the remote user matches the local model
# patch_json = {
# GraphApi.linux_schema: {
# "gidnumber": user.uid_number,
# "homedir": user.homedir,
# "shell": user.shell,
# "uid": user.uid_number,
# "user": user.username,
# }
# }
# self.graph_api.http_patch(
# f"{self.graph_api.base_endpoint}/users/{user.azure_oid}",
# json=patch_json,
# )
# self.logger.debug(f"Set Linux attributes for user {user.preferred_username}.")
# except Exception as exc:
# self.logger.error(
# f"Failed to set Linux attributes for user {user.preferred_username}.\n{str(exc)}"
# )
pass
def unregister(self, sre_name: str, usernames: Sequence[str]) -> None:
"""Remove usernames from SRE security group"""
group_name = f"Data Safe Haven SRE {sre_name}"
for username in usernames:
self.graph_api.remove_user_from_group(username, group_name)
7 changes: 6 additions & 1 deletion data_safe_haven/commands/deploy.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,12 @@ def sre(
# part of a Pulumi declarative command
graph_api = GraphApi(
tenant_id=config.shm.aad_tenant_id,
default_scopes=["Application.ReadWrite.All", "Group.ReadWrite.All"],
default_scopes=[
"Application.ReadWrite.All",
"AppRoleAssignment.ReadWrite.All",
"Directory.ReadWrite.All",
"Group.ReadWrite.All",
],
)

# Initialise Pulumi stack
Expand Down
2 changes: 1 addition & 1 deletion data_safe_haven/external/api/azure_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ def ensure_dns_txt_record(
parameters=RecordSet(
ttl=30, txt_records=[TxtRecord(value=[record_value])]
),
record_type="TXT",
record_type=RecordType.TXT,
relative_record_set_name=record_name,
resource_group_name=resource_group_name,
zone_name=zone_name,
Expand Down
Loading
Loading