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 group role mapping #306

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ coverage.coverage
.vscode/settings.json

venv/
.venv/
42 changes: 42 additions & 0 deletions actions/role.add.user.group.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
# This is slightly a misnomer, as it is actually adding a user to a group,
# but it follows the same pattern as the other role actions
description: Adds a user directly to an existing group
enabled: true
entry_point: src/openstack_actions.py
name: role.add.user.group
parameters:
lib_entry_point:
default: workflows.role_actions.add_user_to_group
immutable: true
type: string
requires_openstack:
default: true
immutable: true
type: boolean
cloud_account:
description: The clouds.yaml account to use whilst performing this action
required: true
type: string
default: "dev"
enum:
- "dev"
- "prod"
user_identifier:
description: User to add to (Name or ID)
type: string
required: true
domain:
description: Authentication domain to search, this much match between group and user
type: string
required: true
default: default
enum:
- default
- stfc
- jasmin
group_name:
description: Group to add the user to
type: string
required: true
runner_type: python-script
4 changes: 2 additions & 2 deletions actions/role.add.yaml → actions/role.add.user.project.yaml
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
---
description: Add User Role to Project
description: Add User directly to a project
enabled: true
entry_point: src/openstack_actions.py
name: role.add
name: role.add.user.project
parameters:
lib_entry_point:
default: workflows.role_actions.role_add
Expand Down
36 changes: 36 additions & 0 deletions actions/role.assign.group.to.project.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
---
description: Add a pre-created group to a given project
enabled: true
entry_point: src/openstack_actions.py
name: role.assign.group.to.project
parameters:
lib_entry_point:
default: workflows.role_actions.assign_group_to_project
immutable: true
type: string
requires_openstack:
default: true
immutable: true
type: boolean
cloud_account:
description: The clouds.yaml account to use whilst performing this action
required: true
type: string
default: "dev"
enum:
- "dev"
- "prod"
project_identifier:
description: Project to add the group to
required: true
type: string
role_identifier:
description: Role to add use with group members
required: true
type: string
default: "user"
group_name:
description: Group to add to a project
type: string
required: true
runner_type: python-script
2 changes: 1 addition & 1 deletion docs/ACTIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@ examples of atomic Actions are shown below (not an exhaustive list):
| project.create | Action to create a new openstack project (not-configured) |
| project.show | Action to find and list an Openstack Project's properties given it's Name or ID |
| quota.set | Action to set project quota |
| role.add | Action to add user role to a project |
| role.add.user.project | Action to add user role to a project |
| router.add.interface | Action to add interface to a router |
| router.create | Action to create a Openstack router |
| security.group.create | Action to create new security group for a project |
Expand Down
1 change: 1 addition & 0 deletions lib/enums/user_domains.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class UserDomains(_AutoName):
DEFAULT = auto()
STFC = auto()
OPENID = auto() # irisiam - now openid since Stein
JASMIN = auto()

@staticmethod
def from_string(val: str):
Expand Down
41 changes: 41 additions & 0 deletions lib/openstack_api/openstack_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,24 @@
from structs.role_details import RoleDetails


def assign_group_role_to_project(conn: Connection, project, role, group) -> None:
"""
Assigns a given role to the specified group
:param conn: openstack connection object
:param project: The project name or ID to assign the role to
:param role: The role name or ID to assign
:param group: The group name or ID to assign the role to
"""
# This is run rarely in comparison to most actions, as it
# likely gets ran once or twice per new project
# Assume the user has already checked the group exists and has stripped it
role = conn.identity.find_role(role, ignore_missing=False)
project = conn.identity.find_project(project, ignore_missing=False)
group = conn.identity.find_group(group, ignore_missing=False)

conn.identity.assign_project_role_to_group(project=project, group=group, role=role)


def assign_role_to_user(conn: Connection, details: RoleDetails) -> None:
"""
Assigns a given role to the specified user
Expand All @@ -19,6 +37,29 @@ def assign_role_to_user(conn: Connection, details: RoleDetails) -> None:
conn.identity.assign_project_role_to_user(project=project, user=user, role=role)


def add_user_to_group(
conn: Connection,
user_identifier: str,
domain: str,
group_name: str,
) -> None:
"""
Assigns a user to an existing group
:param conn: Openstack connection object
:param user_identifier: Name or ID of the user to assign to the group
:param domain: The domain the user and group is associated with
:param group_name: Name of the group to assign the user to
"""
found_domain = conn.identity.find_domain(domain, ignore_missing=False)
user = conn.identity.find_user(
user_identifier, domain_id=found_domain.id, ignore_missing=False
)
group = conn.identity.find_group(
group_name, domain_id=found_domain.id, ignore_missing=False
)
conn.identity.add_user_to_group(user=user, group=group)


def remove_role_from_user(conn: Connection, details: RoleDetails) -> None:
"""
Assigns a given role to the specified user
Expand Down
47 changes: 46 additions & 1 deletion lib/workflows/role_actions.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
from openstack.connection import Connection

from enums.user_domains import UserDomains
from structs.role_details import RoleDetails
from openstack_api import openstack_roles
from openstack_api.openstack_roles import assign_group_role_to_project
from structs.role_details import RoleDetails


def role_add(
Expand Down Expand Up @@ -31,6 +32,50 @@ def role_add(
return True # the above method always returns None


def assign_group_to_project(
conn: Connection,
project_identifier: str,
role_identifier: str,
group_name: str,
) -> bool:
"""
Assigns a pre-created group to map the specified role to members of that
group into a project.
:param conn: Openstack connection object
:param project_identifier: Name or ID of the project to assign to
:param role_identifier: Name or ID of the role to assign
:param group_name: Name of the group to assign the user to
:return: True always
"""
assign_group_role_to_project(
conn, project=project_identifier, role=role_identifier, group=group_name
)
return True


def add_user_to_group(
conn: Connection,
user_identifier: str,
domain: str,
group_name: str,
) -> bool:
"""
Assigns a user to an existing group
:param conn: Openstack connection object
:param user_identifier: Name or ID of the user to assign to the group
:param domain: The domain the user and group is associated with
:param group_name: Name of the group to assign the user to
:return: True always
"""
openstack_roles.add_user_to_group(
conn,
user_identifier=user_identifier,
domain=domain,
group_name=group_name,
)
return True


def role_remove(
conn: Connection,
user_identifier: str,
Expand Down
56 changes: 54 additions & 2 deletions tests/lib/openstack_api/test_openstack_roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
import pytest

from enums.user_domains import UserDomains
from openstack_api.openstack_roles import assign_role_to_user, remove_role_from_user
from openstack_api.openstack_roles import (
assign_role_to_user,
remove_role_from_user,
assign_group_role_to_project,
add_user_to_group,
)
from exceptions.missing_mandatory_param_error import MissingMandatoryParamError
from structs.role_details import RoleDetails

Expand Down Expand Up @@ -72,7 +77,7 @@ def test_assign_roles_throws_missing_role(missing_param_test):
mock_conn.identity.assign_project_role_to_user.assert_not_called()


def test_assign_roles_successful():
def test_assign_roles_successful_single_user():
"""
Tests that assignment is successful
"""
Expand Down Expand Up @@ -101,6 +106,53 @@ def test_assign_roles_successful():
)


def test_assign_group_role_to_project_successful():
"""
Tests that a group can be assigned to a project successfully
"""
mock_conn = MagicMock()
assign_group_role_to_project(mock_conn, "project", "role", "group")

mock_conn.identity.find_project.assert_called_once_with(
"project", ignore_missing=False
)
mock_conn.identity.find_role.assert_called_once_with("role", ignore_missing=False)
mock_conn.identity.find_group.assert_called_once_with("group", ignore_missing=False)

mock_conn.identity.assign_project_role_to_group(
project=mock_conn.identity.find_project.return_value,
group=mock_conn.identity.find_group.return_value,
role=mock_conn.identity.find_role.return_value,
)


def test_add_user_to_group():
"""
Tests that a user can be added to an existing group
"""
mock_conn = MagicMock()
add_user_to_group(mock_conn, "user", "domain", "group")

mock_conn.identity.find_domain.assert_called_once_with(
"domain", ignore_missing=False
)
mock_conn.identity.find_user.assert_called_once_with(
"user",
domain_id=mock_conn.identity.find_domain.return_value.id,
ignore_missing=False,
)
mock_conn.identity.find_group.assert_called_once_with(
"group",
domain_id=mock_conn.identity.find_domain.return_value.id,
ignore_missing=False,
)

mock_conn.identity.add_user_to_group.assert_called_once_with(
user=mock_conn.identity.find_user.return_value,
group=mock_conn.identity.find_group.return_value,
)


def test_remove_roles_throws_missing_project(missing_param_test):
"""
Tests that an exception is thrown if the specified project is missing
Expand Down
61 changes: 60 additions & 1 deletion tests/lib/workflows/test_role_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

from enums.user_domains import UserDomains
from structs.role_details import RoleDetails
from workflows.role_actions import role_add, role_remove
from workflows.role_actions import (
role_add,
role_remove,
assign_group_to_project,
add_user_to_group,
)


@patch("workflows.role_actions.openstack_roles.assign_role_to_user")
Expand Down Expand Up @@ -40,6 +45,60 @@ def test_role_add(mock_api_assign_role_to_user):
assert status


@patch("workflows.role_actions.assign_group_role_to_project")
def test_role_assign_group(mock_api_assign_group_role_to_project):
"""
Tests that role_add function forwards on request to openstack API to assign a user to a group
"""
mock_conn = MagicMock()
mock_project_identifier = "project-id"
mock_role_identifier = "user"
mock_group_name = "group-name"

status = assign_group_to_project(
mock_conn,
mock_project_identifier,
mock_role_identifier,
mock_group_name,
)

mock_api_assign_group_role_to_project.assert_called_once_with(
mock_conn,
project=mock_project_identifier,
role=mock_role_identifier,
group=mock_group_name,
)

assert status


@patch("workflows.role_actions.openstack_roles.add_user_to_group")
def test_role_add_user_to_group(mock_api_add_user_to_group):
"""
Tests that role_add function forwards on request to add a user to the group
"""
mock_conn = MagicMock()
mock_user_identifier = "user-name"
mock_domain = "domain"
mock_group_name = "group-name"

status = add_user_to_group(
mock_conn,
mock_user_identifier,
mock_domain,
mock_group_name,
)

mock_api_add_user_to_group.assert_called_once_with(
mock_conn,
user_identifier=mock_user_identifier,
domain=mock_domain,
group_name=mock_group_name,
)

assert status


@patch("workflows.role_actions.openstack_roles.remove_role_from_user")
def test_role_remove(mock_api_remove_role_from_user):
"""
Expand Down